From faf3c70c7715ba86eb56fdccc6cf831bf87b2ee0 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 5 Jul 2018 09:40:35 +0200 Subject: [PATCH 001/235] tools: Add experimental code for a pairing protocol * configure.ac (GNUPG_CACHE_DIR): New const. * tools/Makefile.am (libexec_PROGRAMS): Add gpg-pair-tool. (gpg_pair_tool_SOURCES, gpg_pair_tool_CFLAGS) (gpg_pair_tool_LDADD): New. * tools/gpg-pair-tool.c: New. -- This is a first try on a protocol to pair two devices so that they can agree on a shared secret to exchange secret keys. The idea is that if you want to sync your secret keys to another machine (e.g. from desktop to mobile) you have physical access to both devices and thus a pairing protocol allows to authenitcate the connection using a short string. See the source for a protocol description. How to test: $ gpg-pair-tool -va --homedir . --initiate >msg.commit $ gpg-pair-tool -va --homedir 2ndhome --respond \ msg.dhpart1 $ gpg-pair-tool -va --homedir . --respond \ msg.dhpart2 $ gpg-pair-tool -va --homedir 2ndhome --respond \ msg.confirm Now set the SAS as printed by the responder into SAS and run $ gpg-pair-tool -va --homedir . --respond --sas $SAS --- configure.ac | 1 + tools/Makefile.am | 10 +- tools/gpg-pair-tool.c | 2020 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 2030 insertions(+), 1 deletion(-) create mode 100644 tools/gpg-pair-tool.c diff --git a/configure.ac b/configure.ac index 78a03c420..03f3af9eb 100644 --- a/configure.ac +++ b/configure.ac @@ -506,6 +506,7 @@ AH_BOTTOM([ #endif #define GNUPG_PRIVATE_KEYS_DIR "private-keys-v1.d" #define GNUPG_OPENPGP_REVOC_DIR "openpgp-revocs.d" +#define GNUPG_CACHE_DIR "cache.d" #define GNUPG_DEF_COPYRIGHT_LINE \ "Copyright (C) 2018 Free Software Foundation, Inc." diff --git a/tools/Makefile.am b/tools/Makefile.am index 0c828a7bd..a15427622 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -51,7 +51,7 @@ else gpg_wks_server = endif -libexec_PROGRAMS = gpg-wks-client +libexec_PROGRAMS = gpg-wks-client gpg-pair-tool bin_PROGRAMS = gpgconf gpg-connect-agent ${symcryptrun} if !HAVE_W32_SYSTEM @@ -173,6 +173,14 @@ gpg_wks_client_LDADD = $(libcommon) \ $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(LIBINTL) $(LIBICONV) +gpg_pair_tool_SOURCES = \ + gpg-pair-tool.c + +gpg_pair_tool_CFLAGS = $(GPG_ERROR_CFLAGS) $(INCICONV) +gpg_pair_tool_LDADD = $(libcommon) \ + $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBINTL) $(LIBICONV) + # Make sure that all libs are build before we use them. This is # important for things like make -j2. diff --git a/tools/gpg-pair-tool.c b/tools/gpg-pair-tool.c new file mode 100644 index 000000000..a86bd8e3c --- /dev/null +++ b/tools/gpg-pair-tool.c @@ -0,0 +1,2020 @@ +/* gpg-pair-tool.c - The tool to run the pairing protocol. + * Copyright (C) 2018 g10 Code GmbH + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see . + */ + +/* Protocol: + * + * Initiator Responder + * | | + * | COMMIT | + * |-------------------->| + * | | + * | DHPART1 | + * |<--------------------| + * | | + * | DHPART2 | + * |-------------------->| + * | | + * | CONFIRM | + * |<--------------------| + * | | + * + * The initiator creates a keypar (PKi,SKi) and sends this COMMIT + * message to the responder: + * + * 7 byte Magic, value: "GPG-pa1" + * 1 byte MessageType, value 1 (COMMIT) + * 8 byte SessionId, value: 8 random bytes + * 1 byte Realm, value 1 + * 2 byte reserved, value 0 + * 5 byte ExpireTime, value: seconds since Epoch as an unsigned int. + * 32 byte Hash(PKi) + * + * The initiator also needs to locally store the sessionid, the realm, + * the expiration time, the keypair and a hash of the entire message + * sent. + * + * The responder checks that the received message has not expired and + * stores sessionid, realm, expiretime and the Hash(PKi). The + * Responder then creates and locally stores its own keypair (PKr,SKr) + * and sends the DHPART1 message back: + * + * 7 byte Magic, value: "GPG-pa1" + * 1 byte MessageType, value 2 (DHPART1) + * 8 byte SessionId from COMMIT message + * 32 byte PKr + * 32 byte Hash(Hash(COMMIT) || DHPART1[0..47]) + * + * Note that Hash(COMMIT) is the hash over the entire received COMMIT + * message. DHPART1[0..47] are the first 48 bytes of the created + * DHPART1 message. + * + * The Initiator receives the DHPART1 message and checks that the hash + * matches. Although this hash is easily malleable it is later in the + * protocol used to assert the integrity of all messages. The + * Initiator then computes the shared master secret from its SKi and + * the received PKr. Using this master secret several keys are + * derived: + * + * - HMACi-key using the label "GPG-pa1-HMACi-key". + * - SYMx-key using the label "GPG-pa1-SYMx-key" + * + * For details on the KDF see the implementation of the function kdf. + * The master secret is stored securily in the local state. The + * DHPART2 message is then created and send to the Responder: + * + * 7 byte Magic, value: "GPG-pa1" + * 1 byte MessageType, value 3 (DHPART2) + * 8 byte SessionId from COMMIT message + * 32 byte PKi + * 32 byte MAC(HMACi-key, Hash(DHPART1) || DHPART2[0..47] || SYMx-key) + * + * The Responder receives the DHPART2 message and checks that the hash + * of the received PKi matches the Hash(PKi) value as received earlier + * with the COMMIT message. The Responder now also computes the + * shared master secret from its SKr and the recived PKi and derives + * the keys: + * + * - HMACi-key using the label "GPG-pa1-HMACi-key". + * - HMACr-key using the label "GPG-pa1-HMACr-key". + * - SYMx-key using the label "GPG-pa1-SYMx-key" + * - SAS using the label "GPG-pa1-SAS" + * + * With these keys the MAC from the received DHPART2 message is + * checked. On success a SAS is displayed to the user and a CONFIRM + * message send back: + * + * 7 byte Magic, value: "GPG-pa1" + * 1 byte MessageType, value 4 (CONFIRM) + * 8 byte SessionId from COMMIT message + * 32 byte MAC(HMACr-key, Hash(DHPART2) || CONFIRM[0..15] || SYMx-key) + * + * The Initiator receives this CONFIRM message, gets the master shared + * secrey from its local state and derives the keys. It checks the + * the MAC in the received CONFIRM message and ask the user to enter + * the SAS as displayed by the responder. Iff the SAS matches the + * master key is flagged as confirmed and the Initiator may now use a + * derived key to send encrypted data to the Responder. + * + * In case the Responder also needs to send encrypted data we need to + * introduce another final message to tell the responder that the + * Initiator validated the SAS. + * + * TODO: Encrypt the state files using a key stored in gpg-agent's cache. + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../common/util.h" +#include "../common/status.h" +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "../common/init.h" +#include "../common/name-value.h" + + +/* Constants to identify the commands and options. */ +enum cmd_and_opt_values + { + aNull = 0, + + oQuiet = 'q', + oVerbose = 'v', + oOutput = 'o', + oArmor = 'a', + + aInitiate = 400, + aRespond = 401, + aGet = 402, + aCleanup = 403, + + oDebug = 500, + oStatusFD, + oHomedir, + oSAS, + + oDummy + }; + + +/* The list of commands and options. */ +static gpgrt_opt_t opts[] = { + ARGPARSE_group (300, ("@Commands:\n ")), + + ARGPARSE_c (aInitiate, "initiate", N_("initiate a pairing request")), + ARGPARSE_c (aRespond, "respond", N_("respond to a pairing request")), + ARGPARSE_c (aGet, "get", N_("return the keys")), + ARGPARSE_c (aCleanup, "cleanup", N_("remove expired states etc.")), + + ARGPARSE_group (301, ("@\nOptions:\n ")), + + ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), + ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), + ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")), + ARGPARSE_s_s (oSAS, "sas", N_("|SAS|the SAS as shown by the peer")), + ARGPARSE_s_s (oDebug, "debug", "@"), + ARGPARSE_s_s (oOutput, "output", N_("|FILE|write the request to FILE")), + ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), + + ARGPARSE_s_s (oHomedir, "homedir", "@"), + + ARGPARSE_end () +}; + + +/* We keep all global options in the structure OPT. */ +static struct +{ + int verbose; + unsigned int debug; + int quiet; + int armor; + const char *output; + estream_t statusfp; + unsigned int ttl; + const char *sas; +} opt; + + +/* Debug values and macros. */ +#define DBG_MESSAGE_VALUE 2 /* Debug the messages. */ +#define DBG_CRYPTO_VALUE 4 /* Debug low level crypto. */ +#define DBG_MEMORY_VALUE 32 /* Debug memory allocation stuff. */ + +#define DBG_MESSAGE (opt.debug & DBG_MESSAGE_VALUE) +#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) + + +/* The list of supported debug flags. */ +static struct debug_flags_s debug_flags [] = + { + { DBG_MESSAGE_VALUE, "message" }, + { DBG_CRYPTO_VALUE , "crypto" }, + { DBG_MEMORY_VALUE , "memory" }, + { 0, NULL } + }; + + +/* The directory name below the cache dir to store paring states. */ +#define PAIRING_STATE_DIR "state" + +/* Message types. */ +#define MSG_TYPE_COMMIT 1 +#define MSG_TYPE_DHPART1 2 +#define MSG_TYPE_DHPART2 3 +#define MSG_TYPE_CONFIRM 4 + + +/* Realm values. */ +#define REALM_STANDARD 1 + + + + +/* Local prototypes. */ +static void wrong_args (const char *text) GPGRT_ATTR_NORETURN; +static void xnvc_set_printf (nvc_t nvc, const char *name, const char *format, + ...) GPGRT_ATTR_PRINTF(3,4); +static void *hash_data (void *result, size_t resultsize, + ...) GPGRT_ATTR_SENTINEL(0); +static void *hmac_data (void *result, size_t resultsize, + const unsigned char *key, size_t keylen, + ...) GPGRT_ATTR_SENTINEL(0); + + +static gpg_error_t command_initiate (void); +static gpg_error_t command_respond (void); +static gpg_error_t command_cleanup (void); +static gpg_error_t command_get (const char *sessionidstr); + + + + +/* Print usage information and provide strings for help. */ +static const char * +my_strusage( int level ) +{ + const char *p; + + switch (level) + { + case 9: p = "LGPL-2.1-or-later"; break; + case 11: p = "gpg-pair-tool"; break; + case 12: p = "@GNUPG@"; break; + case 13: p = VERSION; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; + + case 1: + case 40: + p = ("Usage: gpg-pair-tool [command] [options] [args] (-h for help)"); + break; + case 41: + p = ("Syntax: gpg-pair-tool [command] [options] [args]\n" + "Client to run the pairing protocol\n"); + break; + + default: p = NULL; break; + } + return p; +} + + +static void +wrong_args (const char *text) +{ + es_fprintf (es_stderr, _("usage: %s [options] %s\n"), strusage (11), text); + exit (2); +} + + +/* Set the status FD. */ +static void +set_status_fd (int fd) +{ + static int last_fd = -1; + + if (fd != -1 && last_fd == fd) + return; + + if (opt.statusfp && opt.statusfp != es_stdout && opt.statusfp != es_stderr) + es_fclose (opt.statusfp); + opt.statusfp = NULL; + if (fd == -1) + return; + + if (fd == 1) + opt.statusfp = es_stdout; + else if (fd == 2) + opt.statusfp = es_stderr; + else + opt.statusfp = es_fdopen (fd, "w"); + if (!opt.statusfp) + { + log_fatal ("can't open fd %d for status output: %s\n", + fd, gpg_strerror (gpg_error_from_syserror ())); + } + last_fd = fd; +} + + +/* Write a status line with code NO followed by the outout of the + * printf style FORMAT. The caller needs to make sure that LFs and + * CRs are not printed. */ +static void +write_status (int no, const char *format, ...) +{ + va_list arg_ptr; + + if (!opt.statusfp) + return; /* Not enabled. */ + + es_fputs ("[GNUPG:] ", opt.statusfp); + es_fputs (get_status_string (no), opt.statusfp); + if (format) + { + es_putc (' ', opt.statusfp); + va_start (arg_ptr, format); + es_vfprintf (opt.statusfp, format, arg_ptr); + va_end (arg_ptr); + } + es_putc ('\n', opt.statusfp); +} + + + +/* gpg-pair-tool main. */ +int +main (int argc, char **argv) +{ + gpg_error_t err; + gpgrt_argparse_t pargs = { &argc, &argv }; + enum cmd_and_opt_values cmd = 0; + + opt.ttl = 8*3600; /* Default to 8 hours. */ + + gnupg_reopen_std ("gpg-pair-tool"); + gpgrt_set_strusage (my_strusage); + log_set_prefix ("gpg-pair-tool", GPGRT_LOG_WITH_PREFIX); + + /* Make sure that our subsystems are ready. */ + i18n_init(); + init_common_subsystems (&argc, &argv); + + /* Parse the command line. */ + while (gpgrt_argparse (NULL, &pargs, opts)) + { + switch (pargs.r_opt) + { + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + case oArmor: opt.armor = 1; break; + + case oDebug: + if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags)) + { + pargs.r_opt = ARGPARSE_INVALID_ARG; + pargs.err = ARGPARSE_PRINT_ERROR; + } + break; + + case oOutput: + opt.output = pargs.r.ret_str; + break; + + case oStatusFD: + set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); + break; + + case oHomedir: + gnupg_set_homedir (pargs.r.ret_str); + break; + + case oSAS: + opt.sas = pargs.r.ret_str; + break; + + case aInitiate: + case aRespond: + case aGet: + case aCleanup: + if (cmd && cmd != pargs.r_opt) + log_error (_("conflicting commands\n")); + else + cmd = pargs.r_opt; + break; + + default: pargs.err = ARGPARSE_PRINT_WARNING; break; + } + } + + /* 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]); + } + gpgrt_argparse (NULL, &pargs, NULL); /* Free internal memory. */ + + if (opt.sas) + { + if (strlen (opt.sas) != 11 + || !digitp (opt.sas+0) || !digitp (opt.sas+1) || !digitp (opt.sas+2) + || opt.sas[3] != '-' + || !digitp (opt.sas+4) || !digitp (opt.sas+5) || !digitp (opt.sas+6) + || opt.sas[7] != '-' + || !digitp (opt.sas+8) || !digitp (opt.sas+9) || !digitp (opt.sas+10)) + log_error ("invalid formatted SAS\n"); + } + + /* Stop if any error, inclduing ARGPARSE_PRINT_WARNING, occurred. */ + if (log_get_errorcount (0)) + exit (2); + + if (DBG_CRYPTO) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1|2); + + + /* Now run the requested command. */ + switch (cmd) + { + case aInitiate: + if (argc) + wrong_args ("--initiate"); + err = command_initiate (); + break; + + case aRespond: + if (argc) + wrong_args ("--respond"); + err = command_respond (); + break; + + case aGet: + if (argc > 1) + wrong_args ("--respond [sessionid]"); + err = command_get (argc? *argv:NULL); + break; + + case aCleanup: + if (argc) + wrong_args ("--cleanup"); + err = command_cleanup (); + break; + + default: + gpgrt_usage (1); + err = 0; + break; + } + + if (err) + write_status (STATUS_FAILURE, "- %u", err); + else if (log_get_errorcount (0)) + write_status (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL); + else + write_status (STATUS_SUCCESS, NULL); + return log_get_errorcount (0)? 1:0; +} + + + +/* Wrapper around nvc_new which terminates in the error case. */ +static nvc_t +xnvc_new (void) +{ + nvc_t c = nvc_new (); + if (!c) + log_fatal ("error creating NVC object: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + return c; +} + +/* Wrapper around nvc_set which terminates in the error case. */ +static void +xnvc_set (nvc_t nvc, const char *name, const char *value) +{ + gpg_error_t err = nvc_set (nvc, name, value); + if (err) + log_fatal ("error updating NVC object: %s\n", gpg_strerror (err)); +} + +/* Call vnc_set with (BUFFER, BUFLEN) converted to a hex string as + * value. Terminates in the error case. */ +static void +xnvc_set_hex (nvc_t nvc, const char *name, const void *buffer, size_t buflen) +{ + char *hex; + + hex = bin2hex (buffer, buflen, NULL); + if (!hex) + xoutofcore (); + strlwr (hex); + xnvc_set (nvc, name, hex); + xfree (hex); +} + +/* Call nvc_set with a value created from the string generated using + * the printf style FORMAT. Terminates in the error case. */ +static void +xnvc_set_printf (nvc_t nvc, const char *name, const char *format, ...) +{ + va_list arg_ptr; + char *buffer; + + va_start (arg_ptr, format); + if (gpgrt_vasprintf (&buffer, format, arg_ptr) < 0) + log_fatal ("estream_asprintf failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + va_end (arg_ptr); + xnvc_set (nvc, name, buffer); + xfree (buffer); +} + + +/* Return the string for the first entry in NVC with NAME. If NAME is + * missing, an empty string is returned. The returned string is a + * pointer into NVC. */ +static const char * +xnvc_get_string (nvc_t nvc, const char *name) +{ + nve_t item; + + if (!nvc) + return ""; + item = nvc_lookup (nvc, name); + if (!item) + return ""; + return nve_value (item); +} + + + +/* Return a string for MSGTYPE. */ +const char * +msgtypestr (int msgtype) +{ + switch (msgtype) + { + case MSG_TYPE_COMMIT: return "Commit"; + case MSG_TYPE_DHPART1: return "DHPart1"; + case MSG_TYPE_DHPART2: return "DHPart2"; + case MSG_TYPE_CONFIRM: return "Confirm"; + } + return "?"; +} + + +/* Private to {get,set}_session_id(). */ +static struct { + int initialized; + unsigned char sessid[8]; +} session_id; + + +/* Return the 8 octet session. */ +static unsigned char * +get_session_id (void) +{ + if (!session_id.initialized) + { + session_id.initialized = 1; + gcry_create_nonce (session_id.sessid, sizeof session_id.sessid); + } + + return session_id.sessid; +} + +static void +set_session_id (const void *sessid, size_t len) +{ + log_assert (!session_id.initialized); + if (len > sizeof session_id.sessid) + len = sizeof session_id.sessid; + memcpy (session_id.sessid, sessid, len); + if (len < sizeof session_id.sessid) + memset (session_id.sessid+len, 0, sizeof session_id.sessid - len); + session_id.initialized = 1; +} + +/* Return a string with the hexified session id. */ +static const char * +get_session_id_hex (void) +{ + static char hexstr[16+1]; + + bin2hex (get_session_id (), 8, hexstr); + strlwr (hexstr); + return hexstr; +} + + +/* Return a fixed string with the directory used to store the state of + * pairings. On error a diagnostic is printed but the file name is + * returned anyway. It is expected that the expected failure of the + * following open is responsible for error handling. */ +static const char * +get_pairing_statedir (void) +{ + static char *fname; + gpg_error_t err = 0; + char *tmpstr; + struct stat statbuf; + + if (fname) + return fname; + + fname = make_filename (gnupg_homedir (), GNUPG_CACHE_DIR, NULL); + if (stat (fname, &statbuf) && errno == ENOENT) + { + if (gnupg_mkdir (fname, "-rwx")) + { + err = gpg_error_from_syserror (); + log_error (_("can't create directory '%s': %s\n"), + fname, gpg_strerror (err) ); + } + else if (!opt.quiet) + log_info (_("directory '%s' created\n"), fname); + } + + tmpstr = make_filename (fname, PAIRING_STATE_DIR, NULL); + xfree (fname); + fname = tmpstr; + if (stat (fname, &statbuf) && errno == ENOENT) + { + if (gnupg_mkdir (fname, "-rwx")) + { + if (!err) + { + err = gpg_error_from_syserror (); + log_error (_("can't create directory '%s': %s\n"), + fname, gpg_strerror (err) ); + } + } + else if (!opt.quiet) + log_info (_("directory '%s' created\n"), fname); + } + + return fname; +} + + +/* Open the pairing state file. SESSIONID is a 8 byte buffer with the + * session-id. If CREATE_FLAG is set the file is created and will + * always return a valid stream. If CREATE_FLAG is not set the file + * is opened for reading and writing. If the file does not exist NULL + * is return; in all other error cases the process is terminated. If + * R_FNAME is not NULL the name of the file is stored there and the + * caller needs to free it. */ +static estream_t +open_pairing_state (const unsigned char *sessionid, int create_flag, + char **r_fname) +{ + gpg_error_t err; + char *fname, *tmpstr; + estream_t fp; + + /* The filename is the session id with a "pa1" suffix. Note that + * the state dir may eventually be used for other purposes as well + * and thus the suffix identifies that the file belongs to this + * tool. We use lowercase file names for no real reason. */ + tmpstr = bin2hex (sessionid, 8, NULL); + if (!tmpstr) + xoutofcore (); + strlwr (tmpstr); + fname = xstrconcat (tmpstr, ".pa1", NULL); + xfree (tmpstr); + tmpstr = make_filename (get_pairing_statedir (), fname, NULL); + xfree (fname); + fname = tmpstr; + + fp = es_fopen (fname, create_flag? "wbx,mode=-rw": "rb+,mode=-rw"); + if (!fp) + { + err = gpg_error_from_syserror (); + if (create_flag) + { + /* We should always be able to create a file. Also we use a + * 64 bit session id, it is theoretically possible that such + * a session already exists. However, that is rare enough + * and thus the fatal error message should still be okay. */ + log_fatal ("can't create '%s': %s\n", fname, gpg_strerror (err)); + } + else if (gpg_err_code (err) == GPG_ERR_ENOENT) + { + /* That is an expected error; return NULL. */ + } + else + { + log_fatal ("can't open '%s': %s\n", fname, gpg_strerror (err)); + } + } + + if (r_fname) + *r_fname = fname; + else + xfree (fname); + + return fp; +} + + +/* Write the state to a possible new state file. */ +static void +write_state (nvc_t state, int create_flag) +{ + gpg_error_t err; + char *fname = NULL; + estream_t fp; + + fp = open_pairing_state (get_session_id (), create_flag, &fname); + log_assert (fp); + + err = nvc_write (state, fp); + if (err) + { + es_fclose (fp); + gnupg_remove (fname); + log_fatal ("error writing '%s': %s\n", fname, gpg_strerror (err)); + } + + /* If we did not create the file, we need to truncate the file. */ + if (!create_flag && ftruncate (es_fileno (fp), es_ftello (fp))) + { + err = gpg_error_from_syserror (); + log_fatal ("error truncating '%s': %s\n", fname, gpg_strerror (err)); + } + if (es_ferror (fp) || es_fclose (fp)) + { + err = gpg_error_from_syserror (); + es_fclose (fp); + gnupg_remove (fname); + log_fatal ("error writing '%s': %s\n", fname, gpg_strerror (err)); + } +} + + +/* Read the state into a newly allocated state object and store that + * at R_STATE. If no state is available GPG_ERR_NOT_FOUND is returned + * and as with all errors NULL is tored at R_STATE. SESSIONID is an + * input with the 8 session id. */ +static gpg_error_t +read_state (nvc_t *r_state) +{ + gpg_error_t err; + char *fname = NULL; + estream_t fp; + nvc_t state = NULL; + nve_t item; + const char *value; + unsigned long expire; + + *r_state = NULL; + + fp = open_pairing_state (get_session_id (), 0, &fname); + if (!fp) + return gpg_error (GPG_ERR_NOT_FOUND); + + err = nvc_parse (&state, NULL, fp); + if (err) + { + log_info ("failed to parse state file '%s': %s\n", + fname, gpg_strerror (err)); + goto leave; + } + + /* Check whether the state already expired. */ + item = nvc_lookup (state, "Expires:"); + if (!item) + { + log_info ("invalid state file '%s': %s\n", + fname, "field 'expire' not found"); + goto leave; + } + value = nve_value (item); + if (!value || !(expire = strtoul (value, NULL, 10))) + { + log_info ("invalid state file '%s': %s\n", + fname, "field 'expire' has an invalid value"); + goto leave; + } + if (expire <= gnupg_get_time ()) + { + es_fclose (fp); + fp = NULL; + if (gnupg_remove (fname)) + { + err = gpg_error_from_syserror (); + log_info ("failed to delete state file '%s': %s\n", + fname, gpg_strerror (err)); + } + else if (opt.verbose) + log_info ("state file '%s' deleted\n", fname); + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + + *r_state = state; + state = NULL; + + leave: + nvc_release (state); + es_fclose (fp); + return err; +} + + +/* Send (MSG,MSGLEN) to the output device. */ +static void +send_message (const unsigned char *msg, size_t msglen) +{ + gpg_error_t err; + + if (opt.verbose) + log_info ("session %s: sending %s message\n", + get_session_id_hex (), msgtypestr (msg[7])); + + if (DBG_MESSAGE) + log_printhex (msg, msglen, "send msg(%s):", msgtypestr (msg[7])); + + /* FIXME: For now only stdout. */ + if (opt.armor) + { + gpgrt_b64state_t state; + + state = gpgrt_b64enc_start (es_stdout, ""); + if (!state) + log_fatal ("error setting up base64 encoder: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + err = gpgrt_b64enc_write (state, msg, msglen); + if (!err) + err = gpgrt_b64enc_finish (state); + if (err) + log_fatal ("error writing base64 to stdout: %s\n", gpg_strerror (err)); + } + else + { + if (es_fwrite (msg, msglen, 1, es_stdout) != 1) + log_fatal ("error writing to stdout: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + } + es_fputc ('\n', es_stdout); +} + + +/* Read a message from stdin and store it at the address (R_MSG, + * R_MSGLEN). This function detects armoring and removes it. On + * error NULL is stored at R_MSG, a diagnostic printed and an error + * code returned. The returned message has a proper message type and + * an appropriate length. The message type is stored at R_MSGTYPE and + * if a state is availabale it is stored at R_STATE. */ +static gpg_error_t +read_message (unsigned char **r_msg, size_t *r_msglen, int *r_msgtype, + nvc_t *r_state) +{ + gpg_error_t err; + unsigned char msg[128]; /* max msg size is 80 but 107 with base64. */ + size_t msglen; + size_t reqlen; + + *r_msg = NULL; + *r_state = NULL; + + es_setvbuf (es_stdin, NULL, _IONBF, 0); + es_set_binary (es_stdin); + + if (es_read (es_stdin, msg, sizeof msg, &msglen)) + { + err = gpg_error_from_syserror (); + log_error ("error reading from message: %s\n", gpg_strerror (err)); + return err; + } + + if (msglen > 4 && !memcmp (msg, "R1BH", 4)) + { + /* This is base64 of the first 3 bytes. */ + gpgrt_b64state_t state = gpgrt_b64dec_start (NULL); + if (!state) + log_fatal ("error setting up base64 decoder: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + err = gpgrt_b64dec_proc (state, msg, msglen, &msglen); + gpgrt_b64dec_finish (state); + if (err) + { + log_error ("error decoding message: %s\n", gpg_strerror (err)); + return err; + } + } + + if (msglen < 16 || memcmp (msg, "GPG-pa1", 7)) + { + log_error ("error parsing message: %s\n", + msglen? "invalid header":"empty message"); + return gpg_error (GPG_ERR_INV_RESPONSE); + } + switch (msg[7]) + { + case MSG_TYPE_COMMIT: reqlen = 56; break; + case MSG_TYPE_DHPART1: reqlen = 80; break; + case MSG_TYPE_DHPART2: reqlen = 80; break; + case MSG_TYPE_CONFIRM: reqlen = 48; break; + + default: + log_error ("error parsing message: %s\n", "invalid message type"); + return gpg_error (GPG_ERR_INV_RESPONSE); + } + if (msglen < reqlen) + { + log_error ("error parsing message: %s\n", "message too short"); + return gpg_error (GPG_ERR_INV_RESPONSE); + } + + if (DBG_MESSAGE) + log_printhex (msg, msglen, "recv msg(%s):", msgtypestr (msg[7])); + + /* Note that we ignore any garbage at the end of a message. */ + msglen = reqlen; + + set_session_id (msg+8, 8); + + if (opt.verbose) + log_info ("session %s: received %s message\n", + get_session_id_hex (), msgtypestr (msg[7])); + + /* Read the state. */ + err = read_state (r_state); + if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) + return err; + + *r_msg = xmalloc (msglen); + memcpy (*r_msg, msg, msglen); + *r_msglen = msglen; + *r_msgtype = msg[7]; + return err; +} + + +/* Display the Short Authentication String (SAS). If WAIT is true the + * function waits until the user has entered the SAS as seen at the + * peer. + * + * To construct the SAS we take the 4 most significant octets of HASH, + * interpret them as a 32 bit big endian unsigned integer, divide that + * integer by 10^9 and take the remainder. The remainder is displayed + * as 3 groups of 3 decimal digits delimited by a hyphens. This gives + * a search space of close to 2^30 and is still easy to compare. + */ +static gpg_error_t +display_sas (const unsigned char *hash, size_t hashlen, int wait) +{ + gpg_error_t err; + unsigned long sas = 0; + char sasbuf[12]; + + log_assert (hashlen >= 4); + + sas |= (unsigned long)hash[20] << 24; + sas |= (unsigned long)hash[21] << 16; + sas |= (unsigned long)hash[22] << 8; + sas |= (unsigned long)hash[23]; + sas %= 1000000000ul; + snprintf (sasbuf, sizeof sasbuf, "%09lu", sas); + memmove (sasbuf+8, sasbuf+6, 3); + memmove (sasbuf+4, sasbuf+3, 3); + sasbuf[3] = sasbuf[7] = '-'; + sasbuf[11] = 0; + + if (wait) + log_info ("Please check the SAS:\n"); + else + log_info ("Please note the SAS:\n"); + log_info ("\n"); + log_info (" %s\n", sasbuf); + log_info ("\n"); + + if (wait) + { + if (!opt.sas || strcmp (sasbuf, opt.sas)) + err = gpg_error (GPG_ERR_NOT_CONFIRMED); + else + log_info ("SAS confirmed\n"); + } + + if (err) + log_info ("checking SAS failed: %s\n", gpg_strerror (err)); + return err; +} + + + +static gpg_error_t +create_dh_keypair (unsigned char *dh_secret, size_t dh_secret_len, + unsigned char *dh_public, size_t dh_public_len) +{ + gpg_error_t err; + gcry_sexp_t sexp; + gcry_sexp_t s_keypair; + gcry_buffer_t secret; + gcry_buffer_t public; + unsigned char publicbuf[33]; + + /* We need a temporary buffer for the public key. Check the length + * for the later memcpy. */ + if (dh_public_len < 32) + return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); + + secret.size = dh_secret_len; + secret.data = dh_secret; + secret.off = 0; + public.size = sizeof publicbuf; + public.data = publicbuf; + public.off = 0; + + err = gcry_sexp_build (&sexp, NULL, + "(genkey(ecc(curve Curve25519)(flags djb-tweak)))"); + if (err) + return err; + err = gcry_pk_genkey (&s_keypair, sexp); + gcry_sexp_release (sexp); + if (err) + return err; + err = gcry_sexp_extract_param (s_keypair, "key-data!private-key", + "&dq", &secret, &public, NULL); + gcry_sexp_release (s_keypair); + if (err) + return err; + + /* Gcrypt prepends a 0x40 indicator - remove that. */ + if (public.len == 33) + { + public.len = 32; + memmove (public.data, publicbuf+1, 32); + } + memcpy (dh_public, public.data, public.len); + + if (DBG_CRYPTO) + { + log_printhex (secret.data, secret.len, "DH secret:"); + log_printhex (public.data, public.len, "DH public:"); + } + + return 0; +} + + +/* SHA256 the data given as varargs tuples of (const void*, size_t) + * and store the result in RESULT. The end of the list is indicated + * by a NULL element in a tuple. RESULTLEN gives the length of the + * RESULT buffer which must be at least 32. Note that the second item + * of the tuple is the length and it is a size_t. */ +static void * +hash_data (void *result, size_t resultsize, ...) +{ + va_list arg_ptr; + gpg_error_t err; + gcry_md_hd_t hd; + const void *data; + size_t datalen; + + log_assert (resultsize >= 32); + + err = gcry_md_open (&hd, GCRY_MD_SHA256, 0); + if (err) + log_fatal ("error creating a Hash handle: %s\n", gpg_strerror (err)); + /* log_printhex ("", 0, "Hash-256:"); */ + + va_start (arg_ptr, resultsize); + while ((data = va_arg (arg_ptr, const void *))) + { + datalen = va_arg (arg_ptr, size_t); + /* log_printhex (data, datalen, " data:"); */ + gcry_md_write (hd, data, datalen); + } + va_end (arg_ptr); + + memcpy (result, gcry_md_read (hd, 0), 32); + /* log_printhex (result, 32, " result:"); */ + + gcry_md_close (hd); + + return result; +} + + +/* HMAC-SHA256 the data given as varargs tuples of (const void*, + * size_t) using (KEYLEN,KEY) and store the result in RESULT. The end + * of the list is indicated by a NULL element in a tuple. RESULTLEN + * gives the length of the RESULT buffer which must be at least 32. + * Note that the second item of the tuple is the length and it is a + * size_t. */ +static void * +hmac_data (void *result, size_t resultsize, + const unsigned char *key, size_t keylen, ...) +{ + va_list arg_ptr; + gpg_error_t err; + gcry_mac_hd_t hd; + const void *data; + size_t datalen; + + log_assert (resultsize >= 32); + + err = gcry_mac_open (&hd, GCRY_MAC_HMAC_SHA256, 0, NULL); + if (err) + log_fatal ("error creating a MAC handle: %s\n", gpg_strerror (err)); + err = gcry_mac_setkey (hd, key, keylen); + if (err) + log_fatal ("error setting the MAC key: %s\n", gpg_strerror (err)); + /* log_printhex (key, keylen, "HMAC-key:"); */ + + va_start (arg_ptr, keylen); + while ((data = va_arg (arg_ptr, const void *))) + { + datalen = va_arg (arg_ptr, size_t); + /* log_printhex (data, datalen, " data:"); */ + err = gcry_mac_write (hd, data, datalen); + if (err) + log_fatal ("error writing to the MAC handle: %s\n", gpg_strerror (err)); + } + va_end (arg_ptr); + + err = gcry_mac_read (hd, result, &resultsize); + if (err || resultsize != 32) + log_fatal ("error reading MAC value: %s\n", gpg_strerror (err)); + /* log_printhex (result, resultsize, " result:"); */ + + gcry_mac_close (hd); + + return result; +} + + +/* Key derivation function: + * + * FIXME(doc) + */ +static void +kdf (unsigned char *result, size_t resultlen, + const unsigned char *master, size_t masterlen, + const unsigned char *sessionid, size_t sessionidlen, + const unsigned char *expire, size_t expirelen, + const char *label) +{ + log_assert (masterlen == 32 && sessionidlen == 8 && expirelen == 5); + log_assert (*label); + log_assert (resultlen == 32); + + hmac_data (result, resultlen, master, masterlen, + "\x00\x00\x00\x01", (size_t)4, /* Counter=1*/ + label, strlen (label) + 1, /* Label, 0x00 */ + sessionid, sessionidlen, /* Context */ + expire, expirelen, /* Context */ + "\x00\x00\x01\x00", (size_t)4, /* L=256 */ + NULL); +} + + +static gpg_error_t +compute_master_secret (unsigned char *master, size_t masterlen, + const unsigned char *sk_a, size_t sk_a_len, + const unsigned char *pk_b, size_t pk_b_len) +{ + gpg_error_t err; + gcry_sexp_t s_sk_a = NULL; + gcry_sexp_t s_pk_b = NULL; + gcry_sexp_t s_shared = NULL; + gcry_sexp_t s_tmp; + const char *s; + size_t n; + + log_assert (masterlen == 32); + + err = gcry_sexp_build (&s_sk_a, NULL, "%b", (int)sk_a_len, sk_a); + if (!err) + err = gcry_sexp_build (&s_pk_b, NULL, + "(public-key(ecdh(curve Curve25519)" + " (flags djb-tweak)(q%b)))", + (int)pk_b_len, pk_b); + if (err) + { + log_error ("error building S-expression: %s\n", gpg_strerror (err)); + goto leave; + } + + err = gcry_pk_encrypt (&s_shared, s_sk_a, s_pk_b); + if (err) + { + log_error ("error computing DH: %s\n", gpg_strerror (err)); + goto leave; + } + /* gcry_log_debugsxp ("sk_a", s_sk_a); */ + /* gcry_log_debugsxp ("pk_b", s_pk_b); */ + /* gcry_log_debugsxp ("shared", s_shared); */ + + s_tmp = gcry_sexp_find_token (s_shared, "s", 0); + if (!s_tmp || !(s = gcry_sexp_nth_data (s_tmp, 1, &n)) + || n != 33 || s[0] != 0x40) + { + err = gpg_error (GPG_ERR_INTERNAL); + log_error ("error computing DH: %s\n", gpg_strerror (err)); + goto leave; + } + memcpy (master, s+1, 32); + + + leave: + gcry_sexp_release (s_sk_a); + gcry_sexp_release (s_pk_b); + gcry_sexp_release (s_shared); + return err; +} + + +/* We are the Initiator: Create the commit message. This function + * sends the COMMIT message and writes STATE. */ +static gpg_error_t +make_msg_commit (nvc_t state) +{ + gpg_error_t err; + uint64_t now, expire; + unsigned char secret[32]; + unsigned char public[32]; + unsigned char *newmsg; + size_t newmsglen; + unsigned char tmphash[32]; + + err = create_dh_keypair (secret, sizeof secret, public, sizeof public ); + if (err) + log_error ("creating DH keypair failed: %s\n", gpg_strerror (err)); + + now = gnupg_get_time (); + expire = now + opt.ttl; + + newmsglen = 7+1+8+1+2+5+32; + newmsg = xmalloc (newmsglen); + memcpy (newmsg+0, "GPG-pa1", 7); + newmsg[7] = MSG_TYPE_COMMIT; + memcpy (newmsg+8, get_session_id (), 8); + newmsg[16] = REALM_STANDARD; + newmsg[17] = 0; + newmsg[18] = 0; + newmsg[19] = expire >> 32; + newmsg[20] = expire >> 24; + newmsg[21] = expire >> 16; + newmsg[22] = expire >> 8; + newmsg[23] = expire; + gcry_md_hash_buffer (GCRY_MD_SHA256, newmsg+24, public, 32); + + /* Create the state file. */ + xnvc_set (state, "State:", "Commit-sent"); + xnvc_set_printf (state, "Created:", "%llu", (unsigned long long)now); + xnvc_set_printf (state, "Expires:", "%llu", (unsigned long long)expire); + xnvc_set_hex (state, "DH-PKi:", public, 32); + xnvc_set_hex (state, "DH-SKi:", secret, 32); + gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, newmsg, newmsglen); + xnvc_set_hex (state, "Hash-Commit:", tmphash, 32); + + /* Write the state. Note that we need to create it. The state + * updating should in theory be done atomically with send_message. + * However, we can't assure that the message will actually be + * delivered and thus it doesn't matter whether we have an already + * update state when we later fail in send_message. */ + write_state (state, 1); + + /* Write the message. */ + send_message (newmsg, newmsglen); + + xfree (newmsg); + return err; +} + + +/* We are the Responder: Process a commit message in (MSG,MSGLEN) + * which has already been validated to have a correct header and + * message type. Sends the DHPart1 message and writes STATE. */ +static gpg_error_t +proc_msg_commit (nvc_t state, const unsigned char *msg, size_t msglen) +{ + gpg_error_t err; + uint64_t now, expire; + unsigned char tmphash[32]; + unsigned char secret[32]; + unsigned char public[32]; + unsigned char *newmsg = NULL; + size_t newmsglen; + + log_assert (msglen >= 56); + now = gnupg_get_time (); + + /* Check that the message has not expired. */ + expire = (uint64_t)msg[19] << 32; + expire |= (uint64_t)msg[20] << 24; + expire |= (uint64_t)msg[21] << 16; + expire |= (uint64_t)msg[22] << 8; + expire |= (uint64_t)msg[23]; + if (expire < now) + { + log_error ("received %s message is too old\n", + msgtypestr (MSG_TYPE_COMMIT)); + err = gpg_error (GPG_ERR_TOO_OLD); + goto leave; + } + + /* Create the response. */ + err = create_dh_keypair (secret, sizeof secret, public, sizeof public ); + if (err) + { + log_error ("creating DH keypair failed: %s\n", gpg_strerror (err)); + goto leave; + } + + newmsglen = 7+1+8+32+32; + newmsg = xmalloc (newmsglen); + memcpy (newmsg+0, "GPG-pa1", 7); + newmsg[7] = MSG_TYPE_DHPART1; + memcpy (newmsg+8, msg + 8, 8); /* SessionID. */ + memcpy (newmsg+16, public, 32); /* PKr */ + /* Hash(Hash(Commit) || DHPart1[0..47]) */ + gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, msg, msglen); + hash_data (newmsg+48, 32, + tmphash, sizeof tmphash, + newmsg, (size_t)48, + NULL); + + /* Update the state. */ + xnvc_set (state, "State:", "DHPart1-sent"); + xnvc_set_printf (state, "Created:", "%llu", (unsigned long long)now); + xnvc_set_printf (state, "Expires:", "%llu", (unsigned long long)expire); + xnvc_set_hex (state, "Hash-PKi:", msg+24, 32); + xnvc_set_hex (state, "DH-PKr:", public, 32); + xnvc_set_hex (state, "DH-SKr:", secret, 32); + gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, newmsg, newmsglen); + xnvc_set_hex (state, "Hash-DHPart1:", tmphash, 32); + + /* Write the state. Note that we need to create it. */ + write_state (state, 1); + + /* Write the message. */ + send_message (newmsg, newmsglen); + + leave: + xfree (newmsg); + return err; +} + + +/* We are the Initiator: Process a DHPART1 message in (MSG,MSGLEN) + * which has already been validated to have a correct header and + * message type. Sends the DHPart2 message and writes STATE. */ +static gpg_error_t +proc_msg_dhpart1 (nvc_t state, const unsigned char *msg, size_t msglen) +{ + gpg_error_t err; + unsigned char hash[32]; + unsigned char tmphash[32]; + unsigned char pki[32]; + unsigned char pkr[32]; + unsigned char ski[32]; + unsigned char master[32]; + uint64_t expire; + unsigned char expirebuf[5]; + unsigned char hmacikey[32]; + unsigned char symxkey[32]; + unsigned char *newmsg = NULL; + size_t newmsglen; + + log_assert (msglen >= 80); + + /* Check that the message includes the Hash(Commit). */ + if (hex2bin (xnvc_get_string (state, "Hash-Commit:"), hash, sizeof hash) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no or garbled 'Hash-Commit' in our state file\n"); + goto leave; + } + hash_data (tmphash, 32, + hash, sizeof hash, + msg, (size_t)48, + NULL); + if (memcmp (msg+48, tmphash, 32)) + { + err = gpg_error (GPG_ERR_BAD_DATA); + log_error ("manipulation of received %s message detected: %s\n", + msgtypestr (MSG_TYPE_DHPART1), "Bad Hash"); + goto leave; + } + /* Check that the received PKr is different from our PKi and copy + * PKr into PKR. */ + if (hex2bin (xnvc_get_string (state, "DH-PKi:"), pki, sizeof pki) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no or garbled 'DH-PKi' in our state file\n"); + goto leave; + } + if (!memcmp (msg+16, pki, 32)) + { + /* This can only happen if the state file leaked to the + * responder. */ + err = gpg_error (GPG_ERR_BAD_DATA); + log_error ("received our own public key PKi instead of PKr\n"); + goto leave; + } + memcpy (pkr, msg+16, 32); + + /* Put the expire value into a buffer. */ + expire = string_to_u64 (xnvc_get_string (state, "Expires:")); + if (!expire) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no 'Expire' in our state file\n"); + goto leave; + } + expirebuf[0] = expire >> 32; + expirebuf[1] = expire >> 24; + expirebuf[2] = expire >> 16; + expirebuf[3] = expire >> 8; + expirebuf[4] = expire; + + /* Get our secret from the state. */ + if (hex2bin (xnvc_get_string (state, "DH-SKi:"), ski, sizeof ski) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no or garbled 'DH-SKi' in our state file\n"); + goto leave; + } + + /* Compute the shared secrets. */ + err = compute_master_secret (master, sizeof master, + ski, sizeof ski, pkr, sizeof pkr); + if (err) + { + log_error ("creating DH keypair failed: %s\n", gpg_strerror (err)); + goto leave; + } + + kdf (hmacikey, sizeof hmacikey, + master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, + "GPG-pa1-HMACi-key"); + kdf (symxkey, sizeof symxkey, + master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, + "GPG-pa1-SYMx-key"); + + + /* Create the response. */ + newmsglen = 7+1+8+32+32; + newmsg = xmalloc (newmsglen); + memcpy (newmsg+0, "GPG-pa1", 7); + newmsg[7] = MSG_TYPE_DHPART2; + memcpy (newmsg+8, msg + 8, 8); /* SessionID. */ + memcpy (newmsg+16, pki, 32); /* PKi */ + /* MAC(HMACi-key, Hash(DHPART1) || DHPART2[0..47] || SYMx-key) */ + gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, msg, msglen); + hmac_data (newmsg+48, 32, hmacikey, sizeof hmacikey, + tmphash, sizeof tmphash, + newmsg, (size_t)48, + symxkey, sizeof symxkey, + NULL); + + /* Update the state. */ + xnvc_set (state, "State:", "DHPart2-sent"); + xnvc_set_hex (state, "DH-Master:", master, sizeof master); + gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, newmsg, newmsglen); + xnvc_set_hex (state, "Hash-DHPart2:", tmphash, 32); + + /* Write the state. */ + write_state (state, 0); + + /* Write the message. */ + send_message (newmsg, newmsglen); + + leave: + xfree (newmsg); + return err; +} + + +/* We are the Responder: Process a DHPART2 message in (MSG,MSGLEN) + * which has already been validated to have a correct header and + * message type. Sends the CONFIRM message and writes STATE. */ +static gpg_error_t +proc_msg_dhpart2 (nvc_t state, const unsigned char *msg, size_t msglen) +{ + gpg_error_t err; + unsigned char hash[32]; + unsigned char tmphash[32]; + uint64_t expire; + unsigned char expirebuf[5]; + unsigned char pki[32]; + unsigned char pkr[32]; + unsigned char skr[32]; + unsigned char master[32]; + unsigned char hmacikey[32]; + unsigned char hmacrkey[32]; + unsigned char symxkey[32]; + unsigned char sas[32]; + unsigned char *newmsg = NULL; + size_t newmsglen; + + log_assert (msglen >= 80); + + /* Check that the PKi in the message matches the Hash(Pki) received + * with the Commit message. */ + memcpy (pki, msg + 16, 32); + gcry_md_hash_buffer (GCRY_MD_SHA256, hash, pki, 32); + if (hex2bin (xnvc_get_string (state, "Hash-PKi:"), + tmphash, sizeof tmphash) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no or garbled 'Hash-PKi' in our state file\n"); + goto leave; + } + if (memcmp (hash, tmphash, 32)) + { + err = gpg_error (GPG_ERR_BAD_DATA); + log_error ("Initiator sent a different key in %s than announced in %s\n", + msgtypestr (MSG_TYPE_DHPART2), + msgtypestr (MSG_TYPE_COMMIT)); + goto leave; + } + /* Check that the received PKi is different from our PKr. */ + if (hex2bin (xnvc_get_string (state, "DH-PKr:"), pkr, sizeof pkr) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no or garbled 'DH-PKr' in our state file\n"); + goto leave; + } + if (!memcmp (pkr, pki, 32)) + { + err = gpg_error (GPG_ERR_BAD_DATA); + log_error ("Initiator sent our own PKr back\n"); + goto leave; + } + + /* Put the expire value into a buffer. */ + expire = string_to_u64 (xnvc_get_string (state, "Expires:")); + if (!expire) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no 'Expire' in our state file\n"); + goto leave; + } + expirebuf[0] = expire >> 32; + expirebuf[1] = expire >> 24; + expirebuf[2] = expire >> 16; + expirebuf[3] = expire >> 8; + expirebuf[4] = expire; + + /* Get our secret from the state. */ + if (hex2bin (xnvc_get_string (state, "DH-SKr:"), skr, sizeof skr) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no or garbled 'DH-SKr' in our state file\n"); + goto leave; + } + + /* Compute the shared secrets. */ + err = compute_master_secret (master, sizeof master, + skr, sizeof skr, pki, sizeof pki); + if (err) + { + log_error ("creating DH keypair failed: %s\n", gpg_strerror (err)); + goto leave; + } + + kdf (hmacikey, sizeof hmacikey, + master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, + "GPG-pa1-HMACi-key"); + kdf (hmacrkey, sizeof hmacrkey, + master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, + "GPG-pa1-HMACr-key"); + kdf (symxkey, sizeof symxkey, + master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, + "GPG-pa1-SYMx-key"); + kdf (sas, sizeof sas, + master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, + "GPG-pa1-SAS"); + + /* Check the MAC from the message which is + * MAC(HMACi-key, Hash(DHPART1) || DHPART2[0..47] || SYMx-key). + * For that we need to fetch the stored hash from the state. */ + if (hex2bin (xnvc_get_string (state, "Hash-DHPart1:"), + tmphash, sizeof tmphash) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no or garbled 'Hash-DHPart1' in our state file\n"); + goto leave; + } + hmac_data (hash, 32, hmacikey, sizeof hmacikey, + tmphash, sizeof tmphash, + msg, 48, + symxkey, sizeof symxkey, + NULL); + if (memcmp (msg+48, hash, 32)) + { + err = gpg_error (GPG_ERR_BAD_DATA); + log_error ("manipulation of received %s message detected: %s\n", + msgtypestr (MSG_TYPE_DHPART2), "Bad MAC"); + goto leave; + } + + /* Create the response. */ + newmsglen = 7+1+8+32; + newmsg = xmalloc (newmsglen); + memcpy (newmsg+0, "GPG-pa1", 7); + newmsg[7] = MSG_TYPE_CONFIRM; + memcpy (newmsg+8, msg + 8, 8); /* SessionID. */ + /* MAC(HMACr-key, Hash(DHPART2) || CONFIRM[0..15] || SYMx-key) */ + gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, msg, msglen); + hmac_data (newmsg+16, 32, hmacrkey, sizeof hmacrkey, + tmphash, sizeof tmphash, + newmsg, (size_t)16, + symxkey, sizeof symxkey, + NULL); + + /* Update the state. */ + xnvc_set (state, "State:", "Confirm-sent"); + xnvc_set_hex (state, "DH-Master:", master, sizeof master); + + /* Write the state. */ + write_state (state, 0); + + /* Write the message. */ + send_message (newmsg, newmsglen); + + display_sas (sas, sizeof sas, 0); + + + leave: + xfree (newmsg); + return err; +} + + +/* We are the Initiator: Process a CONFIRM message in (MSG,MSGLEN) + * which has already been validated to have a correct header and + * message type. Does not send anything back. */ +static gpg_error_t +proc_msg_confirm (nvc_t state, const unsigned char *msg, size_t msglen) +{ + gpg_error_t err; + unsigned char hash[32]; + unsigned char tmphash[32]; + unsigned char master[32]; + uint64_t expire; + unsigned char expirebuf[5]; + unsigned char hmacrkey[32]; + unsigned char symxkey[32]; + unsigned char sas[32]; + + log_assert (msglen >= 48); + + /* Put the expire value into a buffer. */ + expire = string_to_u64 (xnvc_get_string (state, "Expires:")); + if (!expire) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no 'Expire' in our state file\n"); + goto leave; + } + expirebuf[0] = expire >> 32; + expirebuf[1] = expire >> 24; + expirebuf[2] = expire >> 16; + expirebuf[3] = expire >> 8; + expirebuf[4] = expire; + + /* Get the master secret. */ + if (hex2bin (xnvc_get_string (state, "DH-Master:"),master,sizeof master) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no or garbled 'DH-Master' in our state file\n"); + goto leave; + } + + kdf (hmacrkey, sizeof hmacrkey, + master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, + "GPG-pa1-HMACr-key"); + kdf (symxkey, sizeof symxkey, + master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, + "GPG-pa1-SYMx-key"); + kdf (sas, sizeof sas, + master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, + "GPG-pa1-SAS"); + + /* Check the MAC from the message which is */ + /* MAC(HMACr-key, Hash(DHPART2) || CONFIRM[0..15] || SYMx-key). */ + if (hex2bin (xnvc_get_string (state, "Hash-DHPart2:"), + tmphash, sizeof tmphash) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no or garbled 'Hash-DHPart2' in our state file\n"); + goto leave; + } + hmac_data (hash, 32, hmacrkey, sizeof hmacrkey, + tmphash, sizeof tmphash, + msg, (size_t)16, + symxkey, sizeof symxkey, + NULL); + if (!memcmp (msg+48, hash, 32)) + { + err = gpg_error (GPG_ERR_BAD_DATA); + log_error ("manipulation of received %s message detected: %s\n", + msgtypestr (MSG_TYPE_CONFIRM), "Bad MAC"); + goto leave; + } + + + err = display_sas (sas, sizeof sas, 1); + if (err) + goto leave; + + /* Update the state. */ + xnvc_set (state, "State:", "Confirmed"); + + /* Write the state. */ + write_state (state, 0); + + leave: + return err; +} + + + +/* Expire old state files. This loops over all state files and remove + * those which are expired. */ +static void +expire_old_states (void) +{ + gpg_error_t err = 0; + const char *dirname; + DIR *dir = NULL; + struct dirent *dir_entry; + char *fname = NULL; + estream_t fp = NULL; + nvc_t nvc = NULL; + nve_t item; + const char *value; + unsigned long expire; + unsigned long now = gnupg_get_time (); + + dirname = get_pairing_statedir (); + dir = opendir (dirname); + if (!dir) + { + err = gpg_error_from_syserror (); + goto leave; + } + + while ((dir_entry = readdir (dir))) + { + if (strlen (dir_entry->d_name) != 16+4 + || strcmp (dir_entry->d_name + 16, ".pa1")) + continue; + + xfree (fname); + fname = make_filename (dirname, dir_entry->d_name, NULL); + es_fclose (fp); + fp = es_fopen (fname, "rb"); + if (!fp) + { + err = gpg_error_from_syserror (); + if (gpg_err_code (err) != GPG_ERR_ENOENT) + log_info ("failed to open state file '%s': %s\n", + fname, gpg_strerror (err)); + continue; + } + nvc_release (nvc); + + /* NB.: The following is similar to code in read_state. */ + err = nvc_parse (&nvc, NULL, fp); + if (err) + { + log_info ("failed to parse state file '%s': %s\n", + fname, gpg_strerror (err)); + continue; /* Skip */ + } + item = nvc_lookup (nvc, "Expires:"); + if (!item) + { + log_info ("invalid state file '%s': %s\n", + fname, "field 'expire' not found"); + continue; /* Skip */ + } + value = nve_value (item); + if (!value || !(expire = strtoul (value, NULL, 10))) + { + log_info ("invalid state file '%s': %s\n", + fname, "field 'expire' has an invalid value"); + continue; /* Skip */ + } + + if (expire <= now) + { + es_fclose (fp); + fp = NULL; + if (gnupg_remove (fname)) + { + err = gpg_error_from_syserror (); + log_info ("failed to delete state file '%s': %s\n", + fname, gpg_strerror (err)); + } + else if (opt.verbose) + log_info ("state file '%s' deleted\n", fname); + } + } + + leave: + if (err) + log_error ("expiring old states in '%s' failed: %s\n", + dirname, gpg_strerror (err)); + if (dir) + closedir (dir); + es_fclose (fp); + xfree (fname); +} + + + +/* Initiate a pairing. The output needs to be conveyed to the + * peer */ +static gpg_error_t +command_initiate (void) +{ + gpg_error_t err; + nvc_t state; + + state = xnvc_new (); + xnvc_set (state, "Version:", "GPG-pa1"); + xnvc_set_hex (state, "Session:", get_session_id (), 8); + xnvc_set (state, "Role:", "Initiator"); + + err = make_msg_commit (state); + + nvc_release (state); + return err; +} + + + +/* Helper for command_respond(). */ +static gpg_error_t +expect_state (int msgtype, const char *statestr, const char *expected) +{ + if (strcmp (statestr, expected)) + { + log_error ("received %s message in %s state (should be %s)\n", + msgtypestr (msgtype), statestr, expected); + return gpg_error (GPG_ERR_INV_RESPONSE); + } + return 0; +} + +/* Respond to a pairing intiation. This is used by the peer and later + * by the original responder. Depending on the state the output needs + * to be conveyed to the peer. */ +static gpg_error_t +command_respond (void) +{ + gpg_error_t err; + unsigned char *msg; + size_t msglen; + int msgtype; + nvc_t state; + const char *rolestr; + const char *statestr; + + err = read_message (&msg, &msglen, &msgtype, &state); + if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) + goto leave; + rolestr = xnvc_get_string (state, "Role:"); + statestr = xnvc_get_string (state, "State:"); + if (DBG_MESSAGE) + { + if (!state) + log_debug ("no state available\n"); + else + log_debug ("we are %s, our current state is %s\n", rolestr, statestr); + log_debug ("got message of type %s (%d)\n", + msgtypestr (msgtype), msgtype); + } + + if (!state) + { + if (msgtype == MSG_TYPE_COMMIT) + { + state = xnvc_new (); + xnvc_set (state, "Version:", "GPG-pa1"); + xnvc_set_hex (state, "Session:", get_session_id (), 8); + xnvc_set (state, "Role:", "Responder"); + err = proc_msg_commit (state, msg, msglen); + } + else + { + log_error ("%s message expected but got %s\n", + msgtypestr (MSG_TYPE_COMMIT), msgtypestr (msgtype)); + if (msgtype == MSG_TYPE_DHPART1) + log_info ("the pairing probably took too long and timed out\n"); + err = gpg_error (GPG_ERR_INV_RESPONSE); + goto leave; + } + } + else if (!strcmp (rolestr, "Initiator")) + { + if (msgtype == MSG_TYPE_DHPART1) + { + if (!(err = expect_state (msgtype, statestr, "Commit-sent"))) + err = proc_msg_dhpart1 (state, msg, msglen); + } + else if (msgtype == MSG_TYPE_CONFIRM) + { + if (!(err = expect_state (msgtype, statestr, "DHPart2-sent"))) + err = proc_msg_confirm (state, msg, msglen); + } + else + { + log_error ("%s message not expected by Initiator\n", + msgtypestr (msgtype)); + err = gpg_error (GPG_ERR_INV_RESPONSE); + goto leave; + } + } + else if (!strcmp (rolestr, "Responder")) + { + if (msgtype == MSG_TYPE_DHPART2) + { + if (!(err = expect_state (msgtype, statestr, "DHPart1-sent"))) + err = proc_msg_dhpart2 (state, msg, msglen); + } + else + { + log_error ("%s message not expected by Responder\n", + msgtypestr (msgtype)); + err = gpg_error (GPG_ERR_INV_RESPONSE); + goto leave; + } + } + else + log_fatal ("invalid role '%s' in state file\n", rolestr); + + + leave: + xfree (msg); + nvc_release (state); + return err; +} + + + +/* Return the keys for SESSIONIDSTR or the last one if it is NULL. + * Two keys are returned: The first is the one for sending encrypted + * data and the second one for decrypting received data. The keys are + * always returned hex encoded and both are terminated by a LF. */ +static gpg_error_t +command_get (const char *sessionidstr) +{ + gpg_error_t err; + unsigned char sessid[8]; + nvc_t state; + + if (!sessionidstr) + { + log_error ("calling without session-id is not yet implemented\n"); + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + goto leave; + } + if (hex2bin (sessionidstr, sessid, sizeof sessid) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("invalid session id given\n"); + goto leave; + } + set_session_id (sessid, sizeof sessid); + err = read_state (&state); + if (err) + { + log_error ("reading state of session %s failed: %s\n", + sessionidstr, gpg_strerror (err)); + goto leave; + } + + leave: + return err; +} + + + +/* Cleanup command. */ +static gpg_error_t +command_cleanup (void) +{ + expire_old_states (); + return 0; +} From 625ced6e672daa892d334323cce6b3d42a6f929f Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 6 Sep 2018 11:41:13 +0900 Subject: [PATCH 002/235] Fix use of strncpy, which is actually good to use memcpy. * common/ssh-utils.c (get_fingerprint): Use memcpy. * g10/build-packet.c (string_to_notation): Use memcpy. Signed-off-by: NIIBE Yutaka --- common/ssh-utils.c | 2 +- g10/build-packet.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/ssh-utils.c b/common/ssh-utils.c index 38d6e8aa2..013b28e5b 100644 --- a/common/ssh-utils.c +++ b/common/ssh-utils.c @@ -247,7 +247,7 @@ get_fingerprint (gcry_sexp_t key, int algo, goto leave; } - strncpy (*r_fpr, algo_name, strlen (algo_name)); + memcpy (*r_fpr, algo_name, strlen (algo_name)); fpr = (char *) *r_fpr + strlen (algo_name); *fpr++ = ':'; diff --git a/g10/build-packet.c b/g10/build-packet.c index b4e03d007..3c500d34f 100644 --- a/g10/build-packet.c +++ b/g10/build-packet.c @@ -1297,7 +1297,7 @@ string_to_notation(const char *string,int is_utf8) } notation->name=xmalloc((s-string)+1); - strncpy(notation->name,string,s-string); + memcpy(notation->name,string,s-string); notation->name[s-string]='\0'; if(!saw_at && !opt.expert) From 99c17b970bc0ca7e0cff7fe031c6f9feb05af3ff Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 6 Sep 2018 14:53:35 +0900 Subject: [PATCH 003/235] gpgscm: Suppress warnings for GCC > 6. * tests/gpgscm/scheme.c (CASE): Use unused attribute for GCC > 6. (FALLTHROUGH): New for fallthrough. (Eval_Cycle): Use FALLTHROUGH. Remove not-needed comment of fallthrough. -- Since GCC combines C preprocessor macro expansion, the fallthrough comment doesn't work well to suppress warnings for -Wimplicit-fallthrough, near the macro CASE. To handle this problem, we use GCC's extension of unused label and fallthrough attributes. Signed-off-by: NIIBE Yutaka --- tests/gpgscm/scheme.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/tests/gpgscm/scheme.c b/tests/gpgscm/scheme.c index 4384841a7..feb313349 100644 --- a/tests/gpgscm/scheme.c +++ b/tests/gpgscm/scheme.c @@ -2990,13 +2990,23 @@ _Error_1(scheme *sc, const char *s, pointer a) { /* Define a label OP and emit a case statement for OP. For use in the * dispatch function. The slightly peculiar goto that is never * executed avoids warnings about unused labels. */ +#if __GNUC__ > 6 +#define CASE(OP) OP: __attribute__((unused)); case OP +#else #define CASE(OP) case OP: if (0) goto OP; OP +#endif #else /* USE_THREADED_CODE */ #define s_thread_to(sc, a) s_goto(sc, a) #define CASE(OP) case OP #endif /* USE_THREADED_CODE */ +#if __GNUC__ > 6 +#define FALLTHROUGH __attribute__ ((fallthrough)) +#else +#define FALLTHROUGH /* fallthrough */ +#endif + /* Return to the previous frame on the dump stack, setting the current * value to A. */ #define s_return(sc, a) s_goto(sc, _s_return(sc, a, 0)) @@ -3557,7 +3567,7 @@ Eval_Cycle(scheme *sc, enum scheme_opcodes op) { putstr(sc,"\nEval: "); s_thread_to(sc,OP_P0LIST); } - /* fall through */ + FALLTHROUGH; CASE(OP_REAL_EVAL): #endif if (is_symbol(sc->code)) { /* symbol */ @@ -3635,7 +3645,7 @@ Eval_Cycle(scheme *sc, enum scheme_opcodes op) { free_cons(sc, sc->args, &callsite, &sc->args); sc->code = car(sc->args); sc->args = cdr(sc->args); - /* Fallthrough. */ + FALLTHROUGH; CASE(OP_APPLY): /* apply 'code' to 'args' */ #if USE_TRACING @@ -3646,7 +3656,7 @@ Eval_Cycle(scheme *sc, enum scheme_opcodes op) { putstr(sc,"\nApply to: "); s_thread_to(sc,OP_P0LIST); } - /* fall through */ + FALLTHROUGH; CASE(OP_REAL_APPLY): #endif #if USE_HISTORY @@ -3727,12 +3737,11 @@ Eval_Cycle(scheme *sc, enum scheme_opcodes op) { s_thread_to(sc,OP_APPLY); } } - /* Fallthrough. */ #else CASE(OP_LAMBDA): /* lambda */ sc->value = sc->code; - /* Fallthrough. */ #endif + FALLTHROUGH; CASE(OP_LAMBDA1): gc_disable(sc, 1); @@ -4655,13 +4664,9 @@ Eval_Cycle(scheme *sc, enum scheme_opcodes op) { CASE(OP_NULLP): /* null? */ s_retbool(car(sc->args) == sc->NIL); CASE(OP_NUMEQ): /* = */ - /* Fallthrough. */ CASE(OP_LESS): /* < */ - /* Fallthrough. */ CASE(OP_GRE): /* > */ - /* Fallthrough. */ CASE(OP_LEQ): /* <= */ - /* Fallthrough. */ CASE(OP_GEQ): /* >= */ switch(op) { case OP_NUMEQ: comp_func=num_eq; break; @@ -4750,9 +4755,7 @@ Eval_Cycle(scheme *sc, enum scheme_opcodes op) { s_return(sc,sc->value); CASE(OP_WRITE): /* write */ - /* Fallthrough. */ CASE(OP_DISPLAY): /* display */ - /* Fallthrough. */ CASE(OP_WRITE_CHAR): /* write-char */ if(is_pair(cdr(sc->args))) { if(cadr(sc->args)!=sc->outport) { @@ -4900,9 +4903,7 @@ Eval_Cycle(scheme *sc, enum scheme_opcodes op) { s_return(sc,sc->outport); CASE(OP_OPEN_INFILE): /* open-input-file */ - /* Fallthrough. */ CASE(OP_OPEN_OUTFILE): /* open-output-file */ - /* Fallthrough. */ CASE(OP_OPEN_INOUTFILE): /* open-input-output-file */ { int prop=0; pointer p; @@ -4922,7 +4923,6 @@ Eval_Cycle(scheme *sc, enum scheme_opcodes op) { #if USE_STRING_PORTS CASE(OP_OPEN_INSTRING): /* open-input-string */ - /* Fallthrough. */ CASE(OP_OPEN_INOUTSTRING): /* open-input-output-string */ { int prop=0; pointer p; @@ -5003,7 +5003,6 @@ Eval_Cycle(scheme *sc, enum scheme_opcodes op) { s_thread_to(sc,OP_READ_INTERNAL); CASE(OP_READ_CHAR): /* read-char */ - /* Fallthrough. */ CASE(OP_PEEK_CHAR): /* peek-char */ { int c; if(is_pair(sc->args)) { From 7c96cc67e108f3a9514a4222ffac2f9f9a2ab19e Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Fri, 7 Sep 2018 13:01:52 +0900 Subject: [PATCH 004/235] g10: Fix memory leak. * g10/import.c (read_block): Call free_packet to skip the packet. -- Reported-by: Philippe Antoine GnuPG-bug-id: 3916 Signed-off-by: NIIBE Yutaka --- g10/import.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/g10/import.c b/g10/import.c index 73f795cd9..dbf600079 100644 --- a/g10/import.c +++ b/g10/import.c @@ -924,6 +924,8 @@ read_block( IOBUF a, int with_meta, add_kbnode (root, new_kbnode (pkt)); pkt = xmalloc (sizeof *pkt); } + else + free_packet (pkt, &parsectx); init_packet(pkt); break; } From 04b56eff118ec34432c368b87e724bce1ac683f9 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sat, 29 Oct 2016 01:25:05 -0400 Subject: [PATCH 005/235] dirmngr: hkp: Avoid potential race condition when some hosts die. * dirmngr/ks-engine-hkp.c (select_random_host): Use atomic pass through the host table instead of risking out-of-bounds write. -- Multiple threads may write to hosttable[x]->dead while select_random_host() is running. For example, a housekeeping thread might clear the ->dead bit on some entries, or another connection to dirmngr might manually mark a host as alive. If one or more hosts are resurrected between the two loops over a given table in select_random_host(), then the allocation of tbl might not be large enough, resulting in a write past the end of tbl on the second loop. This change collapses the two loops into a single loop to avoid this discrepancy: each host's "dead" bit is now only checked once. As Werner points out, this isn't currently strictly necessary, since npth will not switch threads unless a blocking system call is made, and no blocking system call is made in these two loops. However, in a subsequent change in this series, we will call a function in this loop, and that function may sometimes write(2), or call other functions, which may themselves block. Keeping this as a single-pass loop avoids the need to keep track of what might block and what might not. GnuPG-bug-id: 2836 Signed-off-by: Daniel Kahn Gillmor --- dirmngr/ks-engine-hkp.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c index 630309991..49a57ebcf 100644 --- a/dirmngr/ks-engine-hkp.c +++ b/dirmngr/ks-engine-hkp.c @@ -221,29 +221,26 @@ host_in_pool_p (hostinfo_t hi, int tblidx) static int select_random_host (hostinfo_t hi) { - int *tbl; - size_t tblsize; + int *tbl = NULL; + size_t tblsize = 0; int pidx, idx; /* We create a new table so that we randomly select only from currently alive hosts. */ - for (idx = 0, tblsize = 0; + for (idx = 0; idx < hi->pool_len && (pidx = hi->pool[idx]) != -1; idx++) if (hosttable[pidx] && !hosttable[pidx]->dead) - tblsize++; + { + tblsize++; + tbl = xtryrealloc(tbl, tblsize * sizeof *tbl); + if (!tbl) + return -1; /* memory allocation failed! */ + tbl[tblsize-1] = pidx; + } if (!tblsize) return -1; /* No hosts. */ - tbl = xtrymalloc (tblsize * sizeof *tbl); - if (!tbl) - return -1; - for (idx = 0, tblsize = 0; - idx < hi->pool_len && (pidx = hi->pool[idx]) != -1; - idx++) - if (hosttable[pidx] && !hosttable[pidx]->dead) - tbl[tblsize++] = pidx; - if (tblsize == 1) /* Save a get_uint_nonce. */ pidx = tbl[0]; else From bee65edfbc8cc2c369e5941cc9d1a01a0519b388 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 7 Sep 2018 11:48:18 +0200 Subject: [PATCH 006/235] dirmngr: Emit SOURCE status also on NO_DATA. * dirmngr/ks-engine-hkp.c (ks_hkp_search): Send SOURCE status also on NO DATA error. (ks_hkp_get): Ditto. * g10/call-dirmngr.c (gpg_dirmngr_ks_search): Print "data source" info also on error. (gpg_dirmngr_ks_get): Ditto. -- If a keyserver does not return any data it can be useful to know which keyserver out of the pool answered. Signed-off-by: Werner Koch --- dirmngr/ks-engine-hkp.c | 12 ++++++++++-- g10/call-dirmngr.c | 13 ++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c index 49a57ebcf..3c7a8a019 100644 --- a/dirmngr/ks-engine-hkp.c +++ b/dirmngr/ks-engine-hkp.c @@ -1491,7 +1491,11 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, goto again; } if (err) - goto leave; + { + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + dirmngr_status (ctrl, "SOURCE", hostport, NULL); + goto leave; + } err = dirmngr_status (ctrl, "SOURCE", hostport, NULL); if (err) @@ -1626,7 +1630,11 @@ ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp) goto again; } if (err) - goto leave; + { + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + dirmngr_status (ctrl, "SOURCE", hostport, NULL); + goto leave; + } err = dirmngr_status (ctrl, "SOURCE", hostport, NULL); if (err) diff --git a/g10/call-dirmngr.c b/g10/call-dirmngr.c index 11663b9b1..388da3d63 100644 --- a/g10/call-dirmngr.c +++ b/g10/call-dirmngr.c @@ -608,6 +608,12 @@ gpg_dirmngr_ks_search (ctrl_t ctrl, const char *searchstr, NULL, NULL, ks_status_cb, &stparm); if (!err) err = cb (cb_value, 0, NULL); /* Send EOF. */ + else if (parm.stparm->source) + { + /* Error but we received a SOURCE status. Tell via callback but + * ignore errors. */ + parm.data_cb (parm.data_cb_value, 1, parm.stparm->source); + } xfree (get_membuf (&parm.saveddata, NULL)); xfree (parm.helpbuf); @@ -650,6 +656,7 @@ ks_get_data_cb (void *opaque, const void *data, size_t datalen) If R_SOURCE is not NULL the source of the data is stored as a malloced string there. If a source is not known NULL is stored. + Note that this may even be returned after an error. If there are too many patterns the function returns an error. That could be fixed by issuing several search commands or by @@ -737,13 +744,13 @@ gpg_dirmngr_ks_get (ctrl_t ctrl, char **pattern, *r_fp = parm.memfp; parm.memfp = NULL; - if (r_source) + + leave: + if (r_source && stparm.source) { *r_source = stparm.source; stparm.source = NULL; } - - leave: es_fclose (parm.memfp); xfree (stparm.source); xfree (line); From adce73b86fd49d5bbb8884231a26cc7533d400e2 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Mon, 10 Sep 2018 09:16:50 +0900 Subject: [PATCH 007/235] agent: Fix error code check from npth_mutex_init. * agent/call-pinentry.c (initialize_module_call_pinentry): It's an error when npth_mutex_init returns non-zero. -- Actually, initialize_module_call_pinentry is only called once from main. So, this bug had no harm and having the static variable INITIALIZED is not needed. Signed-off-by: NIIBE Yutaka --- agent/call-pinentry.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index af4eb06f2..048443ab3 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -98,11 +98,15 @@ void initialize_module_call_pinentry (void) { static int initialized; + int err; if (!initialized) { - if (npth_mutex_init (&entry_lock, NULL)) - initialized = 1; + err = npth_mutex_init (&entry_lock, NULL); + if (err) + log_fatal ("error initializing mutex: %s\n", strerror (err)); + + initialized = 1; } } From f80346f42df4bdc7d0a9741c3922129aceae4f81 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Mon, 10 Sep 2018 13:44:47 +0900 Subject: [PATCH 008/235] common: Use iobuf_get_noeof to avoid undefined behaviors. * common/iobuf.c (block_filter): Use iobuf_get_noeof. -- When singed integer has negative value, left shift computation is undefined in C. GnuPG-bug-id: 4093 Reported-by: Philippe Antoine Signed-off-by: NIIBE Yutaka --- common/iobuf.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/iobuf.c b/common/iobuf.c index 02c9b491c..8f52f7f5c 100644 --- a/common/iobuf.c +++ b/common/iobuf.c @@ -878,9 +878,9 @@ block_filter (void *opaque, int control, iobuf_t chain, byte * buffer, } else if (c == 255) { - a->size = (size_t)iobuf_get (chain) << 24; - a->size |= iobuf_get (chain) << 16; - a->size |= iobuf_get (chain) << 8; + a->size = iobuf_get_noeof (chain) << 24; + a->size |= iobuf_get_noeof (chain) << 16; + a->size |= iobuf_get_noeof (chain) << 8; if ((c = iobuf_get (chain)) == -1) { log_error ("block_filter: invalid 4 byte length\n"); From 995aded58724a1a07704493b311be5222b3f82a2 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 11 Sep 2018 13:54:49 +0900 Subject: [PATCH 009/235] dirmngr: Serialize access to hosttable. * dirmngr/dirmngr.h (ks_hkp_init): New. * dirmngr/dirmngr.c (main): Call ks_hkp_init. * dirmngr/ks-engine-hkp.c (ks_hkp_init): New. (ks_hkp_mark_host): Serialize access to hosttable. (ks_hkp_print_hosttable, make_host_part): Likewise. (ks_hkp_housekeeping, ks_hkp_reload): Likewise. -- Signed-off-by: NIIBE Yutaka --- dirmngr/dirmngr.c | 5 ++++ dirmngr/dirmngr.h | 2 +- dirmngr/ks-engine-hkp.c | 65 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index 6fdfe36c2..1696be578 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -1143,6 +1143,7 @@ main (int argc, char **argv) thread_init (); cert_cache_init (hkp_cacert_filenames); crl_cache_init (); + ks_hkp_init (); http_register_netactivity_cb (netactivity_action); start_command_handler (ASSUAN_INVALID_FD, 0); shutdown_reaper (); @@ -1178,6 +1179,7 @@ main (int argc, char **argv) thread_init (); cert_cache_init (hkp_cacert_filenames); crl_cache_init (); + ks_hkp_init (); http_register_netactivity_cb (netactivity_action); handle_connections (3); shutdown_reaper (); @@ -1399,6 +1401,7 @@ main (int argc, char **argv) thread_init (); cert_cache_init (hkp_cacert_filenames); crl_cache_init (); + ks_hkp_init (); http_register_netactivity_cb (netactivity_action); handle_connections (fd); shutdown_reaper (); @@ -1421,6 +1424,7 @@ main (int argc, char **argv) thread_init (); cert_cache_init (hkp_cacert_filenames); crl_cache_init (); + ks_hkp_init (); if (!argc) rc = crl_cache_load (&ctrlbuf, NULL); else @@ -1444,6 +1448,7 @@ main (int argc, char **argv) thread_init (); cert_cache_init (hkp_cacert_filenames); crl_cache_init (); + ks_hkp_init (); rc = crl_fetch (&ctrlbuf, argv[0], &reader); if (rc) log_error (_("fetching CRL from '%s' failed: %s\n"), diff --git a/dirmngr/dirmngr.h b/dirmngr/dirmngr.h index edaf46394..9c26c09e6 100644 --- a/dirmngr/dirmngr.h +++ b/dirmngr/dirmngr.h @@ -218,7 +218,7 @@ int dirmngr_use_tor (void); /*-- Various housekeeping functions. --*/ void ks_hkp_housekeeping (time_t curtime); void ks_hkp_reload (void); - +void ks_hkp_init (void); /*-- server.c --*/ ldap_server_t get_ldapservers_from_ctrl (ctrl_t ctrl); diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c index 3c7a8a019..9c234ec44 100644 --- a/dirmngr/ks-engine-hkp.c +++ b/dirmngr/ks-engine-hkp.c @@ -35,6 +35,7 @@ # include #endif /*!HAVE_W32_SYSTEM*/ +#include #include "dirmngr.h" #include "misc.h" #include "../common/userids.h" @@ -108,6 +109,8 @@ struct hostinfo_s resolved from a pool name and its allocated size.*/ static hostinfo_t *hosttable; static int hosttable_size; +/* A mutex used to serialize access to the hosttable. */ +static npth_mutex_t hosttable_lock; /* The number of host slots we initially allocate for HOSTTABLE. */ #define INITIAL_HOSTTABLE_SIZE 50 @@ -753,9 +756,15 @@ ks_hkp_mark_host (ctrl_t ctrl, const char *name, int alive) if (!name || !*name || !strcmp (name, "localhost")) return 0; + if (npth_mutex_lock (&hosttable_lock)) + log_fatal ("failed to acquire mutex\n"); + idx = find_hostinfo (name); if (idx == -1) - return gpg_error (GPG_ERR_NOT_FOUND); + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } hi = hosttable[idx]; if (alive && hi->dead) @@ -814,6 +823,10 @@ ks_hkp_mark_host (ctrl_t ctrl, const char *name, int alive) } } + leave: + if (npth_mutex_unlock (&hosttable_lock)) + log_fatal ("failed to release mutex\n"); + return err; } @@ -834,7 +847,9 @@ ks_hkp_print_hosttable (ctrl_t ctrl) if (err) return err; - /* FIXME: We need a lock for the hosttable. */ + if (npth_mutex_lock (&hosttable_lock)) + log_fatal ("failed to acquire mutex\n"); + curtime = gnupg_get_time (); for (idx=0; idx < hosttable_size; idx++) if ((hi=hosttable[idx])) @@ -927,12 +942,12 @@ ks_hkp_print_hosttable (ctrl_t ctrl) diedstr? ")":"" ); xfree (died); if (err) - return err; + goto leave; if (hi->cname) err = ks_printf_help (ctrl, " . %s", hi->cname); if (err) - return err; + goto leave; if (hi->pool) { @@ -947,14 +962,21 @@ ks_hkp_print_hosttable (ctrl_t ctrl) put_membuf( &mb, "", 1); p = get_membuf (&mb, NULL); if (!p) - return gpg_error_from_syserror (); + { + err = gpg_error_from_syserror (); + goto leave; + } err = ks_print_help (ctrl, p); xfree (p); if (err) - return err; + goto leave; } } - return 0; + + leave: + if (npth_mutex_unlock (&hosttable_lock)) + log_fatal ("failed to release mutex\n"); + return err; } @@ -1023,9 +1045,16 @@ make_host_part (ctrl_t ctrl, protocol = KS_PROTOCOL_HKP; } + if (npth_mutex_lock (&hosttable_lock)) + log_fatal ("failed to acquire mutex\n"); + portstr[0] = 0; err = map_host (ctrl, host, srvtag, force_reselect, protocol, &hostname, portstr, r_httpflags, r_httphost); + + if (npth_mutex_unlock (&hosttable_lock)) + log_fatal ("failed to release mutex\n"); + if (err) return err; @@ -1099,6 +1128,9 @@ ks_hkp_housekeeping (time_t curtime) int idx; hostinfo_t hi; + if (npth_mutex_lock (&hosttable_lock)) + log_fatal ("failed to acquire mutex\n"); + for (idx=0; idx < hosttable_size; idx++) { hi = hosttable[idx]; @@ -1115,6 +1147,9 @@ ks_hkp_housekeeping (time_t curtime) log_info ("resurrected host '%s'", hi->name); } } + + if (npth_mutex_unlock (&hosttable_lock)) + log_fatal ("failed to release mutex\n"); } @@ -1126,6 +1161,9 @@ ks_hkp_reload (void) int idx, count; hostinfo_t hi; + if (npth_mutex_lock (&hosttable_lock)) + log_fatal ("failed to acquire mutex\n"); + for (idx=count=0; idx < hosttable_size; idx++) { hi = hosttable[idx]; @@ -1139,6 +1177,9 @@ ks_hkp_reload (void) } if (count) log_info ("number of resurrected hosts: %d", count); + + if (npth_mutex_unlock (&hosttable_lock)) + log_fatal ("failed to release mutex\n"); } @@ -1754,3 +1795,13 @@ ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, const void *data, size_t datalen) xfree (httphost); return err; } + +void +ks_hkp_init (void) +{ + int err; + + err = npth_mutex_init (&hosttable_lock, NULL); + if (err) + log_fatal ("error initializing mutex: %s\n", strerror (err)); +} From 69bab1cba07a8259b85a7911c2824724667803a4 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 11 Sep 2018 14:04:37 +0900 Subject: [PATCH 010/235] Revert "dirmngr: hkp: Avoid potential race condition when some hosts die." This reverts commit 04b56eff118ec34432c368b87e724bce1ac683f9. -- Now the access to hosttable is serialized correctly. --- dirmngr/ks-engine-hkp.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c index 9c234ec44..31fa77284 100644 --- a/dirmngr/ks-engine-hkp.c +++ b/dirmngr/ks-engine-hkp.c @@ -224,26 +224,29 @@ host_in_pool_p (hostinfo_t hi, int tblidx) static int select_random_host (hostinfo_t hi) { - int *tbl = NULL; - size_t tblsize = 0; + int *tbl; + size_t tblsize; int pidx, idx; /* We create a new table so that we randomly select only from currently alive hosts. */ - for (idx = 0; + for (idx = 0, tblsize = 0; idx < hi->pool_len && (pidx = hi->pool[idx]) != -1; idx++) if (hosttable[pidx] && !hosttable[pidx]->dead) - { - tblsize++; - tbl = xtryrealloc(tbl, tblsize * sizeof *tbl); - if (!tbl) - return -1; /* memory allocation failed! */ - tbl[tblsize-1] = pidx; - } + tblsize++; if (!tblsize) return -1; /* No hosts. */ + tbl = xtrymalloc (tblsize * sizeof *tbl); + if (!tbl) + return -1; + for (idx = 0, tblsize = 0; + idx < hi->pool_len && (pidx = hi->pool[idx]) != -1; + idx++) + if (hosttable[pidx] && !hosttable[pidx]->dead) + tbl[tblsize++] = pidx; + if (tblsize == 1) /* Save a get_uint_nonce. */ pidx = tbl[0]; else From 64c5c45e2aa4a12d939680b9d51c8b26d61c5e9d Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Fri, 14 Sep 2018 07:55:20 +0900 Subject: [PATCH 011/235] g10: Fix memory leak in enum_secret_keys. * g10/skclist.c (enum_secret_keys): Don't forget to call free_public_key in the error return paths. -- Reported-by: Philippe Antoine GnuPG-bug-id: 4140 Signed-off-by: NIIBE Yutaka --- g10/skclist.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/g10/skclist.c b/g10/skclist.c index fd747fb2b..d23354968 100644 --- a/g10/skclist.c +++ b/g10/skclist.c @@ -345,7 +345,11 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) /* Make a new context. */ c = xtrycalloc (1, sizeof *c); if (!c) - return gpg_error_from_syserror (); + { + err = gpg_error_from_syserror (); + free_public_key (sk); + return err; + } *context = c; } @@ -363,7 +367,10 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) } if (c->eof) - return gpg_error (GPG_ERR_EOF); + { + free_public_key (sk); + return gpg_error (GPG_ERR_EOF); + } for (;;) { @@ -475,6 +482,7 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) default: /* No more names to check - stop. */ c->eof = 1; + free_public_key (sk); return gpg_error (GPG_ERR_EOF); } } From 2eb481e8cc1c37378cf68a06557c4a7a625d315c Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Fri, 14 Sep 2018 08:02:16 +0900 Subject: [PATCH 012/235] g10: Fix memory leak (more). * g10/skclist.c (enum_secret_keys): Free SERIALNO on update. -- Signed-off-by: NIIBE Yutaka --- g10/skclist.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/g10/skclist.c b/g10/skclist.c index d23354968..11c57e575 100644 --- a/g10/skclist.c +++ b/g10/skclist.c @@ -380,6 +380,8 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) /* Loop over the list of secret keys. */ do { + char *serialno; + name = NULL; keyblock = NULL; switch (c->state) @@ -415,8 +417,6 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) case 4: /* Get next item from card list. */ if (c->sl) { - char *serialno; - err = agent_scd_serialno (&serialno, c->sl->d); if (err) { @@ -444,9 +444,13 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) } else { - if (c->serialno) - /* Select the original card again. */ - agent_scd_serialno (&c->serialno, c->serialno); + serialno = c->serialno; + if (serialno) + { + /* Select the original card again. */ + agent_scd_serialno (&c->serialno, serialno); + xfree (serialno); + } c->state++; } break; From 60c880bda5c9b821fd2968cf89c38c37be3c1a7b Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Fri, 14 Sep 2018 08:11:45 +0900 Subject: [PATCH 013/235] g10: Fix another memory leak. * g10/skclist.c (enum_secret_keys): Use SK_LIST instead of pubkey_t. -- The use of pubkey_t was wrong. The use is just a list of keys, not with keyblock. With SK_LIST, release_sk_list releases memory by free_public_key. Signed-off-by: NIIBE Yutaka --- g10/skclist.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/g10/skclist.c b/g10/skclist.c index 11c57e575..c9c41d0d9 100644 --- a/g10/skclist.c +++ b/g10/skclist.c @@ -337,7 +337,7 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) kbnode_t keyblock; kbnode_t node; getkey_ctx_t ctx; - pubkey_t results; + SK_LIST results; } *c = *context; if (!c) @@ -358,7 +358,7 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) /* Free the context. */ xfree (c->serialno); free_strlist (c->card_list); - pubkeys_free (c->results); + release_sk_list (c->results); release_kbnode (c->keyblock); getkey_end (ctrl, c->ctx); xfree (c); @@ -516,7 +516,7 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) if (c->node->pkt->pkttype == PKT_PUBLIC_KEY || c->node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { - pubkey_t r; + SK_LIST r; /* Skip this candidate if it's already enumerated. */ for (r = c->results; r; r = r->next) @@ -537,7 +537,6 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) } r->pk = sk; - r->keyblock = NULL; r->next = c->results; c->results = r; From fe8b6339542f3b1228b5fd56fc710ea3b07a3a2b Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 18 Sep 2018 09:34:00 +0900 Subject: [PATCH 014/235] g10: Fix memory leak for --card-status. * g10/card-util.c (card_status): Release memory of serial number. Signed-off-by: NIIBE Yutaka --- g10/card-util.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/g10/card-util.c b/g10/card-util.c index e9c0120a1..779e9d8e7 100644 --- a/g10/card-util.c +++ b/g10/card-util.c @@ -674,7 +674,7 @@ card_status (ctrl_t ctrl, estream_t fp, const char *serialno) { int err; strlist_t card_list, sl; - char *serialno0; + char *serialno0, *serialno1; int all_cards = 0; if (serialno == NULL) @@ -700,8 +700,6 @@ card_status (ctrl_t ctrl, estream_t fp, const char *serialno) for (sl = card_list; sl; sl = sl->next) { - char *serialno1; - if (!all_cards && strcmp (serialno, sl->d)) continue; @@ -722,7 +720,8 @@ card_status (ctrl_t ctrl, estream_t fp, const char *serialno) } /* Select the original card again. */ - err = agent_scd_serialno (&serialno0, serialno0); + err = agent_scd_serialno (&serialno1, serialno0); + xfree (serialno1); leave: xfree (serialno0); From 0cb65564e022fface5ada4de8e0c2c4c3d0ac8ad Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 27 Sep 2018 16:45:27 +0900 Subject: [PATCH 015/235] g10,scd: Support UIF changing command. * g10/card-util.c (uif, cmdUIF): New. (card_edit): Add call to uif by cmdUIF. * scd/app-openpgp.c (do_getattr): Support UIF-1, UIF-2, and UIF-3. (do_setattr): Likewise. (do_learn_status): Learn UIF-1, UIF-2, and UIF-3. -- GnuPG-bug-id: 4158 Signed-off-by: NIIBE Yutaka --- g10/card-util.c | 35 +++++++++++++++++++++++++++++++++-- scd/app-openpgp.c | 18 +++++++++++++++--- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/g10/card-util.c b/g10/card-util.c index 779e9d8e7..b95efa8ba 100644 --- a/g10/card-util.c +++ b/g10/card-util.c @@ -2109,6 +2109,28 @@ kdf_setup (const char *args) leave: agent_release_card_info (&info); } + +static void +uif (int arg_number, const char *arg_rest) +{ + gpg_error_t err; + char name[100]; + unsigned char data[2]; + + snprintf (name, sizeof name, "UIF-%d", arg_number); + if ( !strcmp (arg_rest, "off") ) + data[0] = 0x00; + else if ( !strcmp (arg_rest, "on") ) + data[0] = 0x01; + else if ( !strcmp (arg_rest, "permanent") ) + data[0] = 0x02; + + data[1] = 0x20; + + err = agent_scd_setattr (name, data, 2, NULL); + if (err) + log_error (_("error for setup UIF: %s\n"), gpg_strerror (err)); +} /* Data used by the command parser. This needs to be outside of the function scope to allow readline based command completion. */ @@ -2119,7 +2141,7 @@ enum cmdids cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSEX, cmdCAFPR, cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET, cmdKDFSETUP, - cmdKEYATTR, + cmdKEYATTR, cmdUIF, cmdINVCMD }; @@ -2151,10 +2173,11 @@ static struct { "generate", cmdGENERATE, 1, N_("generate new keys")}, { "passwd" , cmdPASSWD, 0, N_("menu to change or unblock the PIN")}, { "verify" , cmdVERIFY, 0, N_("verify the PIN and list all data")}, - { "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code") }, + { "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code")}, { "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")}, { "kdf-setup", cmdKDFSETUP, 1, N_("setup KDF for PIN authentication")}, { "key-attr", cmdKEYATTR, 1, N_("change the key attribute")}, + { "uif", cmdUIF, 1, N_("change the User Interaction Flag")}, /* Note, that we do not announce these command yet. */ { "privatedo", cmdPRIVATEDO, 0, NULL }, { "readcert", cmdREADCERT, 0, NULL }, @@ -2446,6 +2469,14 @@ card_edit (ctrl_t ctrl, strlist_t commands) key_attr (); break; + case cmdUIF: + if ( arg_number < 1 || arg_number > 3 ) + tty_printf ("usage: uif N [on|off|permanent]\n" + " 1 <= N <= 3\n"); + else + uif (arg_number, arg_rest); + break; + case cmdQUIT: goto leave; diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index c17452555..8df9fab0a 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -119,8 +119,11 @@ static struct { { 0x0104, 0, 0, 0, 0, 0, 0, 2, "Private DO 4"}, { 0x7F21, 1, 0, 1, 0, 0, 0, 1, "Cardholder certificate"}, /* V3.0 */ - { 0x7F74, 0, 0, 1, 0, 0, 0, 0, "General Feature Management"}, + { 0x7F74, 0, 0x6E, 1, 0, 0, 0, 0, "General Feature Management"}, { 0x00D5, 0, 0, 1, 0, 0, 0, 0, "AES key data"}, + { 0x00D6, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for Signature"}, + { 0x00D7, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for decryption"}, + { 0x00D8, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for authentication"}, { 0x00F9, 0, 0, 1, 0, 0, 0, 0, "KDF data object"}, { 0 } }; @@ -985,6 +988,9 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name) { "PRIVATE-DO-4", 0x0104 }, { "$AUTHKEYID", 0x0000, -3 }, { "$DISPSERIALNO",0x0000, -4 }, + { "UIF-1", 0x00D6, 0 }, + { "UIF-2", 0x00D7, 0 }, + { "UIF-3", 0x00D8, 0 }, { "KDF", 0x00F9 }, { NULL, 0 } }; @@ -1827,6 +1833,9 @@ do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) do_getattr (app, ctrl, "CA-FPR"); do_getattr (app, ctrl, "CHV-STATUS"); do_getattr (app, ctrl, "SIG-COUNTER"); + do_getattr (app, ctrl, "UIF-1"); + do_getattr (app, ctrl, "UIF-2"); + do_getattr (app, ctrl, "UIF-3"); if (app->app_local->extcap.private_dos) { do_getattr (app, ctrl, "PRIVATE-DO-1"); @@ -2459,6 +2468,9 @@ do_setattr (app_t app, const char *name, { "SM-KEY-MAC", 0x00D2, 3, 0, 1 }, { "KEY-ATTR", 0, 0, 3, 1 }, { "AESKEY", 0x00D5, 3, 0, 1 }, + { "UIF-1", 0x00D6, 3, 0, 1 }, + { "UIF-2", 0x00D7, 3, 0, 1 }, + { "UIF-3", 0x00D8, 3, 0, 1 }, { "KDF", 0x00F9, 3, 4, 1 }, { NULL, 0 } }; @@ -2839,10 +2851,10 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */ } else - { + { rc = pin2hash_if_kdf (app, chvno, oldpinvalue, &pinlen0); if (!rc) - rc = pin2hash_if_kdf (app, chvno, pinvalue, &pinlen); + rc = pin2hash_if_kdf (app, chvno, pinvalue, &pinlen); if (!rc) rc = iso7816_change_reference_data (app->slot, 0x80 + chvno, oldpinvalue, pinlen0, From 50b02dba2060a8969da47b18d9c0ecdccbd30db4 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 2 Oct 2018 14:22:24 +0900 Subject: [PATCH 016/235] common: Fix gnupg_reopen_std. * common/sysutils.c (gnupg_reopen_std): Use fcntl instead of fstat. -- When gpg was invoked by a Perl web application on FreeBSD, fstat in gnupg_reopen_std failed with EBADF. Using fcntl, which is considered lighter than fstat, it works fine. Since uur purpose is to check if file descriptor is valid or not, lighter operation is better. Reported-by: Marcin Gryszkalis Signed-off-by: NIIBE Yutaka --- common/sysutils.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/common/sysutils.c b/common/sysutils.c index 55a7ee9ec..0a3dc2eaf 100644 --- a/common/sysutils.c +++ b/common/sysutils.c @@ -551,14 +551,13 @@ gnupg_tmpfile (void) void gnupg_reopen_std (const char *pgmname) { -#if defined(HAVE_STAT) && !defined(HAVE_W32_SYSTEM) - struct stat statbuf; +#ifdef F_GETFD int did_stdin = 0; int did_stdout = 0; int did_stderr = 0; FILE *complain; - if (fstat (STDIN_FILENO, &statbuf) == -1 && errno ==EBADF) + if (fcntl (STDIN_FILENO, F_GETFD) == -1 && errno ==EBADF) { if (open ("/dev/null",O_RDONLY) == STDIN_FILENO) did_stdin = 1; @@ -566,7 +565,7 @@ gnupg_reopen_std (const char *pgmname) did_stdin = 2; } - if (fstat (STDOUT_FILENO, &statbuf) == -1 && errno == EBADF) + if (fcntl (STDOUT_FILENO, F_GETFD) == -1 && errno == EBADF) { if (open ("/dev/null",O_WRONLY) == STDOUT_FILENO) did_stdout = 1; @@ -574,7 +573,7 @@ gnupg_reopen_std (const char *pgmname) did_stdout = 2; } - if (fstat (STDERR_FILENO, &statbuf)==-1 && errno==EBADF) + if (fcntl (STDERR_FILENO, F_GETFD)==-1 && errno==EBADF) { if (open ("/dev/null", O_WRONLY) == STDERR_FILENO) did_stderr = 1; @@ -607,7 +606,7 @@ gnupg_reopen_std (const char *pgmname) if (did_stdin == 2 || did_stdout == 2 || did_stderr == 2) exit (3); -#else /* !(HAVE_STAT && !HAVE_W32_SYSTEM) */ +#else /* !F_GETFD */ (void)pgmname; #endif } From 8e83493dae426fe36a0e0081198b10db1e103ff1 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 2 Oct 2018 11:02:08 +0200 Subject: [PATCH 017/235] gpg: New options import-drop-uids and export-drop-uids. * g10/options.h (IMPORT_DROP_UIDS): New. (EXPORT_DROP_UIDS): New. * g10/import.c (parse_import_options): Add option "import-drop-uids". (import_one): Don't bail out with that options and no uids found. Also remove all uids. (remove_all_uids): New. * g10/export.c (parse_export_options): Add option "export-drop-uids". (do_export_one_keyblock): Implement option. -- These options are required for experiments with changes to the keyserver infrastructure. Signed-off-by: Werner Koch --- doc/gpg.texi | 10 +++++++++ g10/export.c | 29 +++++++++++++++++++++---- g10/import.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++----- g10/misc.c | 2 ++ g10/options.h | 2 ++ 5 files changed, 94 insertions(+), 9 deletions(-) diff --git a/doc/gpg.texi b/doc/gpg.texi index 7f55cc7e3..843e91c5c 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -2342,6 +2342,11 @@ opposite meaning. The options are: on the keyring. This option is the same as running the @option{--edit-key} command "clean" after import. Defaults to no. + @item import-drop-uids + Do not import any user ids or their binding signatures. This option + can be used to update only the subkeys or other non-user id related + information. + @item repair-keys. After import, fix various problems with the keys. For example, this reorders signatures, and strips duplicate signatures. Defaults to yes. @@ -2506,6 +2511,11 @@ opposite meaning. The options are: running the @option{--edit-key} command "minimize" before export except that the local copy of the key is not modified. Defaults to no. + @item export-drop-uids + Do no export any user id or attribute packets or their associates + signatures. Note that due to missing user ids the resulting output is + not strictly RFC-4880 compliant. + @item export-pka Instead of outputting the key material output PKA records suitable to put into DNS zone files. An ORIGIN line is printed before each diff --git a/g10/export.c b/g10/export.c index e94e959fb..b140e28cb 100644 --- a/g10/export.c +++ b/g10/export.c @@ -97,7 +97,7 @@ cleanup_export_globals (void) } -/* Option parser for export options. See parse_options fro +/* Option parser for export options. See parse_options for details. */ int parse_export_options(char *str,unsigned int *options,int noisy) @@ -114,6 +114,8 @@ parse_export_options(char *str,unsigned int *options,int noisy) N_("remove unusable parts from key during export")}, {"export-minimal",EXPORT_MINIMAL|EXPORT_CLEAN,NULL, N_("remove as much as possible from key during export")}, + {"export-drop-uids", EXPORT_DROP_UIDS, NULL, + N_("Do not export user id or attribute packets")}, {"export-pka", EXPORT_PKA_FORMAT, NULL, NULL }, {"export-dane", EXPORT_DANE_FORMAT, NULL, NULL }, @@ -136,14 +138,20 @@ parse_export_options(char *str,unsigned int *options,int noisy) int rc; rc = parse_options (str, options, export_opts, noisy); - if (rc && (*options & EXPORT_BACKUP)) + if (!rc) + return 0; + + /* Alter other options we want or don't want for restore. */ + if ((*options & EXPORT_BACKUP)) { - /* Alter other options we want or don't want for restore. */ *options |= (EXPORT_LOCAL_SIGS | EXPORT_ATTRIBUTES | EXPORT_SENSITIVE_REVKEYS); *options &= ~(EXPORT_CLEAN | EXPORT_MINIMAL | EXPORT_PKA_FORMAT | EXPORT_DANE_FORMAT); } + /* Dropping uids also means to drop attributes. */ + if ((*options & EXPORT_DROP_UIDS)) + *options &= ~(EXPORT_ATTRIBUTES); return rc; } @@ -1575,7 +1583,7 @@ do_export_one_keyblock (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, if (node->pkt->pkttype == PKT_COMMENT) continue; - /* Skip ring trust packets - they should not ne here anyway. */ + /* Skip ring trust packets - they should not be here anyway. */ if (node->pkt->pkttype == PKT_RING_TRUST) continue; @@ -1650,6 +1658,19 @@ do_export_one_keyblock (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, } } + /* Don't export user ids (and attributes)? This is not RFC-4880 + * compliant but we allow it anyway. */ + if ((options & EXPORT_DROP_UIDS) + && node->pkt->pkttype == PKT_USER_ID) + { + /* Skip until we get to something that is not a user id (or + * attrib) or a signature on it. */ + while (kbctx->next && kbctx->next->pkt->pkttype == PKT_SIGNATURE) + kbctx = kbctx->next; + + continue; + } + /* Don't export attribs? */ if (!(options & EXPORT_ATTRIBUTES) && node->pkt->pkttype == PKT_USER_ID diff --git a/g10/import.c b/g10/import.c index dbf600079..1f334dcb8 100644 --- a/g10/import.c +++ b/g10/import.c @@ -121,6 +121,7 @@ static int chk_self_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, static int delete_inv_parts (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, unsigned int options); static int any_uid_left (kbnode_t keyblock); +static int remove_all_uids (kbnode_t *keyblock); static int merge_blocks (ctrl_t ctrl, unsigned int options, kbnode_t keyblock_orig, kbnode_t keyblock, u32 *keyid, @@ -181,6 +182,9 @@ parse_import_options(char *str,unsigned int *options,int noisy) {"import-minimal",IMPORT_MINIMAL|IMPORT_CLEAN,NULL, N_("remove as much as possible from key after import")}, + {"import-drop-uids", IMPORT_DROP_UIDS, NULL, + N_("Do not import user id or attribute packets")}, + {"import-export", IMPORT_EXPORT, NULL, N_("run import filters and export key immediately")}, @@ -1728,7 +1732,9 @@ import_one (ctrl_t ctrl, } - if (!uidnode ) + /* Unless import-drop-uids has been requested we don't allow import + * of a key without UIDs. */ + if (!uidnode && !(options & IMPORT_DROP_UIDS)) { if (!silent) log_error( _("key %s: no user ID\n"), keystr_from_pk(pk)); @@ -1755,7 +1761,11 @@ import_one (ctrl_t ctrl, return 0; } - collapse_uids(&keyblock); + /* Remove or collapse the user ids. */ + if ((options & IMPORT_DROP_UIDS)) + remove_all_uids (&keyblock); + else + collapse_uids (&keyblock); /* Clean the key that we're about to import, to cut down on things that we have to clean later. This has no practical impact on the @@ -1802,7 +1812,10 @@ import_one (ctrl_t ctrl, } } - if (!delete_inv_parts (ctrl, keyblock, keyid, options ) ) + /* Delete invalid parts and without the drop otions bail out if + * there are no user ids. */ + if (!delete_inv_parts (ctrl, keyblock, keyid, options) + && !(options & IMPORT_DROP_UIDS) ) { if (!silent) { @@ -3417,14 +3430,51 @@ any_uid_left (kbnode_t keyblock) -/**************** +/* Delete all user ids from KEYBLOCK. + * Returns: True if the keyblock has changed. */ +static int +remove_all_uids (kbnode_t *keyblock) +{ + kbnode_t node; + int any = 0; + + for (node = *keyblock; node; node = node->next) + { + if (is_deleted_kbnode (node)) + continue; + + if (node->pkt->pkttype != PKT_USER_ID) + continue; + + /* We are at the first user id. Delete everything up to the + * first subkey. */ + for (; node; node = node->next) + { + if (is_deleted_kbnode (node)) + continue; + + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY) + break; + delete_kbnode (node); + any = 1; + } + break; /* All done. */ + } + + commit_kbnode (keyblock); + return any; +} + + +/* * It may happen that the imported keyblock has duplicated user IDs. * We check this here and collapse those user IDs together with their * sigs into one. * Returns: True if the keyblock has changed. */ int -collapse_uids( kbnode_t *keyblock ) +collapse_uids (kbnode_t *keyblock) { kbnode_t uid1; int any=0; diff --git a/g10/misc.c b/g10/misc.c index d7a3ee3f2..89b21e257 100644 --- a/g10/misc.c +++ b/g10/misc.c @@ -1521,6 +1521,8 @@ optlen(const char *s) return strlen(s); } + +/* Note: This function returns true on success. */ int parse_options(char *str,unsigned int *options, struct parse_options *opts,int noisy) diff --git a/g10/options.h b/g10/options.h index 7defbda76..faaf53503 100644 --- a/g10/options.h +++ b/g10/options.h @@ -360,6 +360,7 @@ EXTERN_UNLESS_MAIN_MODULE int memory_stat_debug_mode; #define IMPORT_RESTORE (1<<10) #define IMPORT_REPAIR_KEYS (1<<11) #define IMPORT_DRY_RUN (1<<12) +#define IMPORT_DROP_UIDS (1<<13) #define EXPORT_LOCAL_SIGS (1<<0) #define EXPORT_ATTRIBUTES (1<<1) @@ -370,6 +371,7 @@ EXTERN_UNLESS_MAIN_MODULE int memory_stat_debug_mode; #define EXPORT_PKA_FORMAT (1<<6) #define EXPORT_DANE_FORMAT (1<<7) #define EXPORT_BACKUP (1<<10) +#define EXPORT_DROP_UIDS (1<<13) #define LIST_SHOW_PHOTOS (1<<0) #define LIST_SHOW_POLICY_URLS (1<<1) From 3c2ffd27f36dfe77005aa01005145904761d8743 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 4 Oct 2018 09:57:03 +0200 Subject: [PATCH 018/235] gpg: Add new card vendor -- --- g10/card-util.c | 1 + 1 file changed, 1 insertion(+) diff --git a/g10/card-util.c b/g10/card-util.c index b95efa8ba..111dfbf2c 100644 --- a/g10/card-util.c +++ b/g10/card-util.c @@ -216,6 +216,7 @@ get_manufacturer (unsigned int no) case 0x1337: return "Warsaw Hackerspace"; case 0x2342: return "warpzone"; /* hackerspace Muenster. */ + case 0x4354: return "Confidential Technologies"; /* cotech.de */ case 0x63AF: return "Trustica"; case 0xBD0E: return "Paranoidlabs"; case 0xF517: return "FSIJ"; From 79f165d7a8bcc26972712bb0f0cc554d5c3d4e42 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 8 Oct 2018 15:38:37 +0200 Subject: [PATCH 019/235] gpg: Make --skip-hidden-recipients work again. * g10/pubkey-enc.c (get_session_key): Take care of opt.skip_hidden_recipients. -- This was lost due to Fixes-commit: ce2f71760155b71a71418fe145a557c99bd52290 GnuPG-bug-id: 4169 Signed-off-by: Werner Koch --- g10/pubkey-enc.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/g10/pubkey-enc.c b/g10/pubkey-enc.c index 32b1ed08b..ad0a77e59 100644 --- a/g10/pubkey-enc.c +++ b/g10/pubkey-enc.c @@ -110,6 +110,16 @@ get_session_key (ctrl_t ctrl, struct pubkey_enc_list *list, DEK *dek) continue; } + /* FIXME: The list needs to be sorted so that we try the keys in + * an appropriate order. For example: + * - On-disk keys w/o protection + * - On-disk keys with a cached passphrase + * - On-card keys of an active card + * - On-disk keys with protection + * - On-card keys from cards which are not plugged it. Here a + * cancel-all button should stop aksing for other cards. + * Without any anonymous keys the sorting can be skipped. + */ for (k = list; k; k = k->next) { if (!(k->pubkey_algo == PUBKEY_ALGO_ELGAMAL_E @@ -129,6 +139,9 @@ get_session_key (ctrl_t ctrl, struct pubkey_enc_list *list, DEK *dek) if (!k->keyid[0] && !k->keyid[1]) { + if (opt.skip_hidden_recipients) + continue; + if (!opt.quiet) log_info (_("anonymous recipient; trying secret key %s ...\n"), keystr (keyid)); From b6275f3bda8edff34274c5b921508567f491ab9c Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 8 Oct 2018 16:14:17 +0200 Subject: [PATCH 020/235] gpg: Fix extra check for sign usage of a data signature. * g10/sig-check.c (check_signature_end_simple): -- Obviously we should not ignore a back signature here. Fixes-commit: 214b0077264e35c079e854a8b6374704aea45cd5 GnuPG-bug-id: 4014 Signed-off-by: Werner Koch --- g10/sig-check.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/g10/sig-check.c b/g10/sig-check.c index 0ec384347..9c32d4d12 100644 --- a/g10/sig-check.c +++ b/g10/sig-check.c @@ -480,7 +480,8 @@ check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig, } /* For data signatures check that the key has sign usage. */ - if (IS_SIG (sig) && !(pk->pubkey_usage & PUBKEY_USAGE_SIG)) + if (!IS_BACK_SIG (sig) && IS_SIG (sig) + && !(pk->pubkey_usage & PUBKEY_USAGE_SIG)) { rc = gpg_error (GPG_ERR_WRONG_KEY_USAGE); if (!opt.quiet) From 150a33df41944d764621f037038683f3d605aa3f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 10 Oct 2018 11:46:16 +0200 Subject: [PATCH 021/235] gpg: Don't take the a TOFU trust model from the trustdb, * g10/tdbio.c (tdbio_update_version_record): Never store a TOFU model. (create_version_record): Don't init as TOFU. (tdbio_db_matches_options): Don't indicate a change in case TOFU is stored in an old trustdb file. -- This change allows to switch between a tofu and pgp or tofu+pgp trust model without an auto rebuild of the trustdb. This also requires that the tofu trust model is requested on the command line. If TOFU will ever be the default we need to tweak the model detection via TM_AUTO by also looking into the TOFU data base, GnuPG-bug-id: 4134 --- doc/gpg.texi | 3 ++- g10/tdbio.c | 29 +++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/doc/gpg.texi b/doc/gpg.texi index 843e91c5c..ba1df4bfb 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -1724,7 +1724,8 @@ Set what trust model GnuPG should follow. The models are: @opindex trust-model:auto Select the trust model depending on whatever the internal trust database says. This is the default model if such a database already - exists. + exists. Note that a tofu trust model is not considered here and + must be enabled explicitly. @end table @item --auto-key-locate @var{mechanisms} diff --git a/g10/tdbio.c b/g10/tdbio.c index fed0cf5ab..8f7530621 100644 --- a/g10/tdbio.c +++ b/g10/tdbio.c @@ -562,6 +562,12 @@ tdbio_update_version_record (ctrl_t ctrl) { TRUSTREC rec; int rc; + int opt_tm; + + /* Never store a TOFU trust model in the trustdb. Use PGP instead. */ + opt_tm = opt.trust_model; + if (opt_tm == TM_TOFU || opt_tm == TM_TOFU_PGP) + opt_tm = TM_PGP; memset (&rec, 0, sizeof rec); @@ -572,7 +578,7 @@ tdbio_update_version_record (ctrl_t ctrl) rec.r.ver.marginals = opt.marginals_needed; rec.r.ver.completes = opt.completes_needed; rec.r.ver.cert_depth = opt.max_cert_depth; - rec.r.ver.trust_model = opt.trust_model; + rec.r.ver.trust_model = opt_tm; rec.r.ver.min_cert_level = opt.min_cert_level; rc = tdbio_write_record (ctrl, &rec); } @@ -591,6 +597,12 @@ create_version_record (ctrl_t ctrl) { TRUSTREC rec; int rc; + int opt_tm; + + /* Never store a TOFU trust model in the trustdb. Use PGP instead. */ + opt_tm = opt.trust_model; + if (opt_tm == TM_TOFU || opt_tm == TM_TOFU_PGP) + opt_tm = TM_PGP; memset (&rec, 0, sizeof rec); rec.r.ver.version = 3; @@ -598,8 +610,8 @@ create_version_record (ctrl_t ctrl) rec.r.ver.marginals = opt.marginals_needed; rec.r.ver.completes = opt.completes_needed; rec.r.ver.cert_depth = opt.max_cert_depth; - if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC) - rec.r.ver.trust_model = opt.trust_model; + if (opt_tm == TM_PGP || opt_tm == TM_CLASSIC) + rec.r.ver.trust_model = opt_tm; else rec.r.ver.trust_model = TM_PGP; rec.r.ver.min_cert_level = opt.min_cert_level; @@ -883,16 +895,25 @@ tdbio_db_matches_options() { TRUSTREC vr; int rc; + int opt_tm, tm; rc = tdbio_read_record (0, &vr, RECTYPE_VER); if( rc ) log_fatal( _("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc) ); + /* Consider tofu and pgp the same. */ + tm = vr.r.ver.trust_model; + if (tm == TM_TOFU || tm == TM_TOFU_PGP) + tm = TM_PGP; + opt_tm = opt.trust_model; + if (opt_tm == TM_TOFU || opt_tm == TM_TOFU_PGP) + opt_tm = TM_PGP; + yes_no = vr.r.ver.marginals == opt.marginals_needed && vr.r.ver.completes == opt.completes_needed && vr.r.ver.cert_depth == opt.max_cert_depth - && vr.r.ver.trust_model == opt.trust_model + && tm == opt_tm && vr.r.ver.min_cert_level == opt.min_cert_level; } From 827529339a4854886dbb5625238e7e01013efdcd Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 11 Oct 2018 13:37:24 +0900 Subject: [PATCH 022/235] agent: Support --ack option for POPUPPINPADPROMPT. * agent/divert-scd.c (getpin_cb): Support --ack option. -- We are now introducing "acknowledge button" feature to scdaemon, so that we can support OpenPGPcard User Interaction Flag. We will (re)use the mechanism of POPUPPINPADPROMPT for this. Perhaps, we will change the name of POPUPPINPADPROMPT, since it will be no longer for PINPAD only. Signed-off-by: NIIBE Yutaka --- agent/divert-scd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/divert-scd.c b/agent/divert-scd.c index b85b490c1..ad9b9ee2f 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -276,7 +276,7 @@ getpin_cb (void *opaque, const char *desc_text, const char *info, } else if (maxbuf == 1) /* Open the pinentry. */ { - if (info) + if (info && strcmp (info, "--ack") != 0) { char *desc, *desc2; From 7a5a4c4cac8709f7c413e94cd0b40f4123baa1e5 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 11 Oct 2018 15:41:49 +0900 Subject: [PATCH 023/235] scd: Support "acknowledge button" feature. * scd/apdu.c (set_prompt_cb): New member function. (set_prompt_cb_ccid_reader): New function. (open_ccid_reader): Initialize with set_prompt_cb_ccid_reader. (apdu_set_prompt_cb): New. * scd/app.c (lock_app, unlock_app): Add call to apdu_set_prompt_cb. * ccid-driver.c (ccid_set_prompt_cb): New. (bulk_in): Call ->prompt_cb when timer extension. * scd/command.c (popup_prompt): New. Signed-off-by: NIIBE Yutaka --- scd/apdu.c | 34 ++++++++++++++++++++++++++++++++++ scd/apdu.h | 1 + scd/app.c | 2 ++ scd/ccid-driver.c | 30 +++++++++++++++++++++++++++++- scd/ccid-driver.h | 2 ++ scd/command.c | 28 ++++++++++++++++++++++++++++ scd/scdaemon.h | 1 + 7 files changed, 97 insertions(+), 1 deletion(-) diff --git a/scd/apdu.c b/scd/apdu.c index ce7f41f61..c0b1bbea4 100644 --- a/scd/apdu.c +++ b/scd/apdu.c @@ -105,6 +105,7 @@ struct reader_table_s { int (*check_pinpad)(int, int, pininfo_t *); void (*dump_status_reader)(int); int (*set_progress_cb)(int, gcry_handler_progress_t, void*); + int (*set_prompt_cb)(int, void (*) (void *, int), void*); int (*pinpad_verify)(int, int, int, int, int, pininfo_t *); int (*pinpad_modify)(int, int, int, int, int, pininfo_t *); @@ -444,6 +445,7 @@ new_reader_slot (void) reader_table[reader].check_pinpad = check_pcsc_pinpad; reader_table[reader].dump_status_reader = NULL; reader_table[reader].set_progress_cb = NULL; + reader_table[reader].set_prompt_cb = NULL; reader_table[reader].pinpad_verify = pcsc_pinpad_verify; reader_table[reader].pinpad_modify = pcsc_pinpad_modify; @@ -1404,6 +1406,14 @@ set_progress_cb_ccid_reader (int slot, gcry_handler_progress_t cb, void *cb_arg) return ccid_set_progress_cb (slotp->ccid.handle, cb, cb_arg); } +static int +set_prompt_cb_ccid_reader (int slot, void (*cb) (void *, int ), void *cb_arg) +{ + reader_table_t slotp = reader_table + slot; + + return ccid_set_prompt_cb (slotp->ccid.handle, cb, cb_arg); +} + static int get_status_ccid (int slot, unsigned int *status, int on_wire) @@ -1543,6 +1553,7 @@ open_ccid_reader (struct dev_list *dl) reader_table[slot].check_pinpad = check_ccid_pinpad; reader_table[slot].dump_status_reader = dump_ccid_reader_status; reader_table[slot].set_progress_cb = set_progress_cb_ccid_reader; + reader_table[slot].set_prompt_cb = set_prompt_cb_ccid_reader; reader_table[slot].pinpad_verify = ccid_pinpad_operation; reader_table[slot].pinpad_modify = ccid_pinpad_operation; /* Our CCID reader code does not support T=0 at all, thus reset the @@ -2382,6 +2393,29 @@ apdu_set_progress_cb (int slot, gcry_handler_progress_t cb, void *cb_arg) } +int +apdu_set_prompt_cb (int slot, void (*cb) (void *, int), void *cb_arg) +{ + int sw; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if (reader_table[slot].set_prompt_cb) + { + sw = lock_slot (slot); + if (!sw) + { + sw = reader_table[slot].set_prompt_cb (slot, cb, cb_arg); + unlock_slot (slot); + } + } + else + sw = 0; + return sw; +} + + /* Do a reset for the card in reader at SLOT. */ int apdu_reset (int slot) diff --git a/scd/apdu.h b/scd/apdu.h index 8a0d4bda8..8621ddc41 100644 --- a/scd/apdu.h +++ b/scd/apdu.h @@ -117,6 +117,7 @@ int apdu_connect (int slot); int apdu_disconnect (int slot); int apdu_set_progress_cb (int slot, gcry_handler_progress_t cb, void *cb_arg); +int apdu_set_prompt_cb (int slot, void (*cb) (void *, int), void *cb_arg); int apdu_reset (int slot); int apdu_get_status (int slot, int hang, unsigned int *status); diff --git a/scd/app.c b/scd/app.c index f3f1205f8..a82db26cd 100644 --- a/scd/app.c +++ b/scd/app.c @@ -67,6 +67,7 @@ lock_app (app_t app, ctrl_t ctrl) } apdu_set_progress_cb (app->slot, print_progress_line, ctrl); + apdu_set_prompt_cb (app->slot, popup_prompt, ctrl); return 0; } @@ -76,6 +77,7 @@ static void unlock_app (app_t app) { apdu_set_progress_cb (app->slot, NULL, NULL); + apdu_set_prompt_cb (app->slot, NULL, NULL); if (npth_mutex_unlock (&app->lock)) { diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c index ae40f0118..6b0833b2c 100644 --- a/scd/ccid-driver.c +++ b/scd/ccid-driver.c @@ -255,6 +255,9 @@ struct ccid_driver_s void (*progress_cb)(void *, const char *, int, int, int); void *progress_cb_arg; + void (*prompt_cb)(void *, int); + void *prompt_cb_arg; + unsigned char intr_buf[64]; struct libusb_transfer *transfer; }; @@ -1802,6 +1805,19 @@ ccid_set_progress_cb (ccid_driver_t handle, } +int +ccid_set_prompt_cb (ccid_driver_t handle, + void (*cb)(void *, int), void *cb_arg) +{ + if (!handle) + return CCID_DRIVER_ERR_INV_VALUE; + + handle->prompt_cb = cb; + handle->prompt_cb_arg = cb_arg; + return 0; +} + + /* Close the reader HANDLE. */ int ccid_close_reader (ccid_driver_t handle) @@ -1930,6 +1946,7 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, { int rc; int msglen; + int notified = 0; /* Fixme: The next line for the current Valgrind without support for USB IOCTLs. */ @@ -1982,14 +1999,25 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, we got the expected message type. This is in particular required for the Cherry keyboard which sends a time extension request for each key hit. */ - if ( !(buffer[7] & 0x03) && (buffer[7] & 0xC0) == 0x80) + if (!(buffer[7] & 0x03) && (buffer[7] & 0xC0) == 0x80) { /* Card present and active, time extension requested. */ DEBUGOUT_2 ("time extension requested (%02X,%02X)\n", buffer[7], buffer[8]); + + /* Gnuk enhancement to prompt user input by ack button */ + if (buffer[8] == 0xff && !notified) + { + notified = 1; + handle->prompt_cb (handle->prompt_cb_arg, 1); + } + goto retry; } + if (notified) + handle->prompt_cb (handle->prompt_cb_arg, 0); + if (buffer[0] != expected_type && buffer[0] != RDR_to_PC_SlotStatus) { DEBUGOUT_1 ("unexpected bulk-in msg type (%02x)\n", buffer[0]); diff --git a/scd/ccid-driver.h b/scd/ccid-driver.h index c31c25fa7..1550b3eba 100644 --- a/scd/ccid-driver.h +++ b/scd/ccid-driver.h @@ -126,6 +126,8 @@ int ccid_open_reader (const char *spec_reader_name, int ccid_set_progress_cb (ccid_driver_t handle, void (*cb)(void *, const char *, int, int, int), void *cb_arg); +int ccid_set_prompt_cb (ccid_driver_t handle, void (*cb)(void *, int), + void *cb_arg); int ccid_shutdown_reader (ccid_driver_t handle); int ccid_close_reader (ccid_driver_t handle); int ccid_get_atr (ccid_driver_t handle, diff --git a/scd/command.c b/scd/command.c index 66d9fb971..0a9654693 100644 --- a/scd/command.c +++ b/scd/command.c @@ -1898,6 +1898,34 @@ send_status_direct (ctrl_t ctrl, const char *keyword, const char *args) } +void +popup_prompt (void *opaque, int on) +{ + ctrl_t ctrl = opaque; + + if (ctrl) + { + assuan_context_t ctx = ctrl->server_local->assuan_ctx; + + if (ctx) + { + const char *cmd; + gpg_error_t err; + unsigned char *value; + size_t valuelen; + + if (on) + cmd = "POPUPPINPADPROMPT --ack"; + else + cmd = "DISMISSPINPADPROMPT"; + err = assuan_inquire (ctx, cmd, &value, &valuelen, 100); + if (!err) + xfree (value); + } + } +} + + /* Helper to send the clients a status change notification. */ void send_client_notifications (app_t app, int removal) diff --git a/scd/scdaemon.h b/scd/scdaemon.h index 4797f3df0..238e6a8fd 100644 --- a/scd/scdaemon.h +++ b/scd/scdaemon.h @@ -123,6 +123,7 @@ int scd_command_handler (ctrl_t, int); void send_status_info (ctrl_t ctrl, const char *keyword, ...) GPGRT_ATTR_SENTINEL(1); void send_status_direct (ctrl_t ctrl, const char *keyword, const char *args); +void popup_prompt (void *opaque, int on); void send_client_notifications (app_t app, int removal); void scd_kick_the_loop (void); int get_active_connection_count (void); From 4ed941ff26783c4fabfe2079029f8e436eb7e340 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Fri, 12 Oct 2018 11:36:59 +0900 Subject: [PATCH 024/235] agent: Fix message for ACK button. * agent/divert-scd.c (getpin_cb): Display correct message. Signed-off-by: NIIBE Yutaka --- agent/divert-scd.c | 50 +++++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/agent/divert-scd.c b/agent/divert-scd.c index ad9b9ee2f..baa1cc3eb 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -276,27 +276,49 @@ getpin_cb (void *opaque, const char *desc_text, const char *info, } else if (maxbuf == 1) /* Open the pinentry. */ { - if (info && strcmp (info, "--ack") != 0) + if (info) { - char *desc, *desc2; + char *desc; + const char *desc2; - if ( asprintf (&desc, - L_("%s%%0A%%0AUse the reader's pinpad for input."), - info) < 0 ) + if (!strcmp (info, "--ack")) + { + desc2 = L_("Push ACK button on card/token."); + + if (desc_text) + { + desc = strconcat (desc_text, + has_percent0A_suffix (desc_text) + ? "%0A" : "%0A%0A", + desc2, NULL); + desc2 = NULL; + } + else + desc = NULL; + } + else + { + desc2 = NULL; + + if (desc_text) + desc = strconcat (desc_text, + has_percent0A_suffix (desc_text) + ? "%0A" : "%0A%0A", + info, "%0A%0A", + L_("Use the reader's pinpad for input."), + NULL); + else + desc = strconcat (info, "%0A%0A", + L_("Use the reader's pinpad for input."), + NULL); + } + + if (!desc2 && !desc) rc = gpg_error_from_syserror (); else { - /* Prepend DESC_TEXT to INFO. */ - if (desc_text) - desc2 = strconcat (desc_text, - has_percent0A_suffix (desc_text) - ? "%0A" : "%0A%0A", - desc, NULL); - else - desc2 = NULL; rc = agent_popup_message_start (ctrl, desc2? desc2:desc, NULL); - xfree (desc2); xfree (desc); } } From 78f542e1f4495195db2e668f9cd41657fb1afc77 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Mon, 15 Oct 2018 11:10:15 +0900 Subject: [PATCH 025/235] scd: Fix signing authentication status. * scd/app-openpgp.c (do_sign): Clear DID_CHV1 after signing. -- We have a corner case: In "not forced" situation and authenticated, and it is changed to "forced", card implementaiton can actually accept signing, but GnuPG requires authentication, because it is "forced". GnuPG-bug-id: 4177 Signed-off-by: NIIBE Yutaka --- scd/app-openpgp.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index 8df9fab0a..87804f593 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -4393,7 +4393,7 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, log_info (_("signatures created so far: %lu\n"), sigcount); /* Check CHV if needed. */ - if (!app->did_chv1 || app->force_chv1 ) + if (!app->did_chv1 || app->force_chv1) { char *pinvalue; int pinlen; @@ -4441,6 +4441,9 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, } rc = iso7816_compute_ds (app->slot, exmode, data, datalen, le_value, outdata, outdatalen); + if (!rc && app->force_chv1) + app->did_chv1 = 0; + return rc; } From f03928b16c4fb00077d22d8ec141575ef6d26913 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 15 Oct 2018 11:32:19 +0200 Subject: [PATCH 026/235] tools: Replace duplicated code in mime-maker. * tools/rfc822parse.c (HEADER_NAME_CHARS): New. Taken from mime-maker.c. (rfc822_valid_header_name_p): New. Based on code from mime-maker.c. (rfc822_capitalize_header_name): New. Copied from mime-maker.c. (capitalize_header_name): Remove. Replace calls by new func. (my_toupper, my_strcasecmp): New. * tools/mime-maker.c: Include rfc822parse.h. (HEADER_NAME_CHARS, capitalize_header_name): Remove. (add_header): Replace check and capitalization by new functions. -- This is a straightforward change with two minor chnages: - In rfc822parse.c the capitalization handles MIME-Version special. - The check in mime-maker bow detects a zero-length name as invalid. my_toupper and my_strcasecmp are introduced to allow standalone use of that file. Signed-off-by: Werner Koch --- tools/mime-maker.c | 47 ++------------------ tools/rfc822parse.c | 105 +++++++++++++++++++++++++++++++++++--------- tools/rfc822parse.h | 2 + 3 files changed, 91 insertions(+), 63 deletions(-) diff --git a/tools/mime-maker.c b/tools/mime-maker.c index 0edc14d78..91eab8258 100644 --- a/tools/mime-maker.c +++ b/tools/mime-maker.c @@ -25,14 +25,10 @@ #include "../common/util.h" #include "../common/zb32.h" +#include "rfc822parse.h" #include "mime-maker.h" -/* All valid characters in a header name. */ -#define HEADER_NAME_CHARS ("abcdefghijklmnopqrstuvwxyz" \ - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ - "-01234567890") - /* An object to store an header. Also used for a list of headers. */ struct header_s { @@ -269,38 +265,6 @@ ensure_part (mime_maker_t ctx, part_t *r_parent) } -/* Transform a header name into a standard capitalized format. - * "Content-Type". Conversion stops at the colon. */ -static void -capitalize_header_name (char *name) -{ - unsigned char *p = name; - int first = 1; - - /* Special cases first. */ - if (!ascii_strcasecmp (name, "MIME-Version")) - { - strcpy (name, "MIME-Version"); - return; - } - - /* Regular cases. */ - for (; *p && *p != ':'; p++) - { - if (*p == '-') - first = 1; - else if (first) - { - if (*p >= 'a' && *p <= 'z') - *p = *p - 'a' + 'A'; - first = 0; - } - else if (*p >= 'A' && *p <= 'Z') - *p = *p - 'A' + 'a'; - } -} - - /* Check whether a header with NAME has already been set into PART. * NAME must be in canonical capitalized format. Return true or * false. */ @@ -344,17 +308,14 @@ add_header (part_t part, const char *name, const char *value) memcpy (hdr->name, name, namelen); hdr->name[namelen] = 0; - /* Check that the header name is valid. We allow all lower and - * uppercase letters and, except for the first character, digits and - * the dash. */ - if (strspn (hdr->name, HEADER_NAME_CHARS) != namelen - || strchr ("-0123456789", *hdr->name)) + /* Check that the header name is valid. */ + if (!rfc822_valid_header_name_p (hdr->name)) { xfree (hdr); return gpg_error (GPG_ERR_INV_NAME); } - capitalize_header_name (hdr->name); + rfc822_capitalize_header_name (hdr->name); hdr->value = xtrystrdup (value); if (!hdr->value) { diff --git a/tools/rfc822parse.c b/tools/rfc822parse.c index e8cdb0215..0a4e2bc94 100644 --- a/tools/rfc822parse.c +++ b/tools/rfc822parse.c @@ -41,6 +41,12 @@ #include "rfc822parse.h" +/* All valid characters in a header name. */ +#define HEADER_NAME_CHARS ("abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "-01234567890") + + enum token_type { tSPACE, @@ -131,28 +137,31 @@ lowercase_string (unsigned char *string) *string = *string - 'A' + 'a'; } -/* Transform a header name into a standard capitalized format; i.e - "Content-Type". Conversion stops at the colon. As usual we don't - use the localized versions of ctype.h. - */ -static void -capitalize_header_name (unsigned char *name) -{ - int first = 1; - for (; *name && *name != ':'; name++) - if (*name == '-') - first = 1; - else if (first) - { - if (*name >= 'a' && *name <= 'z') - *name = *name - 'a' + 'A'; - first = 0; - } - else if (*name >= 'A' && *name <= 'Z') - *name = *name - 'A' + 'a'; +static int +my_toupper (int c) +{ + if (c >= 'a' && c <= 'z') + c &= ~0x20; + return c; } +/* This is the same as ascii_strcasecmp. */ +static int +my_strcasecmp (const char *a, const char *b) +{ + if (a == b) + return 0; + + for (; *a && *b; a++, b++) + { + if (*a != *b && my_toupper(*a) != my_toupper(*b)) + break; + } + return *a == *b? 0 : (my_toupper (*a) - my_toupper (*b)); +} + + #ifndef HAVE_STPCPY static char * my_stpcpy (char *a,const char *b) @@ -228,6 +237,62 @@ release_handle_data (rfc822parse_t msg) } +/* Check that the header name is valid. We allow all lower and + * uppercase letters and, except for the first character, digits and + * the dash. The check stops at the first colon or at string end. + * Returns true if the name is valid. */ +int +rfc822_valid_header_name_p (const char *name) +{ + const char *s; + size_t namelen; + + if ((s=strchr (name, ':'))) + namelen = s - name; + else + namelen = strlen (name); + + if (!namelen + || strspn (name, HEADER_NAME_CHARS) != namelen + || strchr ("-0123456789", *name)) + return 0; + return 1; +} + + +/* Transform a header NAME into a standard capitalized format. + * Conversion stops at the colon. */ +void +rfc822_capitalize_header_name (char *name) +{ + unsigned char *p = name; + int first = 1; + + /* Special cases first. */ + if (!my_strcasecmp (name, "MIME-Version")) + { + strcpy (name, "MIME-Version"); + return; + } + + /* Regular cases. */ + for (; *p && *p != ':'; p++) + { + if (*p == '-') + first = 1; + else if (first) + { + if (*p >= 'a' && *p <= 'z') + *p = *p - 'a' + 'A'; + first = 0; + } + else if (*p >= 'A' && *p <= 'Z') + *p = *p - 'A' + 'a'; + } +} + + + /* Create a new parsing context for an entire rfc822 message and return it. CB and CB_VALUE may be given to callback for certain events. NULL is returned on error with errno set appropriately. */ @@ -432,7 +497,7 @@ insert_header (rfc822parse_t msg, const unsigned char *line, size_t length) /* Transform a field name into canonical format. */ if (!hdr->cont && strchr (line, ':')) - capitalize_header_name (hdr->line); + rfc822_capitalize_header_name (hdr->line); *msg->current_part->hdr_lines_tail = hdr; msg->current_part->hdr_lines_tail = &hdr->next; diff --git a/tools/rfc822parse.h b/tools/rfc822parse.h index 177d8271a..e2f2bedac 100644 --- a/tools/rfc822parse.h +++ b/tools/rfc822parse.h @@ -48,6 +48,8 @@ typedef int (*rfc822parse_cb_t) (void *opaque, rfc822parse_event_t event, rfc822parse_t msg); +int rfc822_valid_header_name_p (const char *name); +void rfc822_capitalize_header_name (char *name); rfc822parse_t rfc822parse_open (rfc822parse_cb_t cb, void *opaque_value); From 0a7f446c189201ca6e527af08b44da756b343209 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 22 Oct 2018 14:23:11 +0200 Subject: [PATCH 027/235] dirmngr: In verbose mode print the OCSP responder id. * dirmngr/ocsp.c (ocsp_isvalid): Print the responder id. Signed-off-by: Werner Koch --- dirmngr/ocsp.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/dirmngr/ocsp.c b/dirmngr/ocsp.c index ca28960e4..966eda304 100644 --- a/dirmngr/ocsp.c +++ b/dirmngr/ocsp.c @@ -653,6 +653,33 @@ ocsp_isvalid (ctrl_t ctrl, ksba_cert_t cert, const char *cert_fpr, if (err) goto leave; + /* It is sometimes useful to know the responder ID. */ + if (opt.verbose) + { + char *resp_name; + ksba_sexp_t resp_keyid; + + err = ksba_ocsp_get_responder_id (ocsp, &resp_name, &resp_keyid); + if (err) + log_info (_("error getting responder ID: %s\n"), gpg_strerror (err)); + else + { + log_info ("responder id: "); + if (resp_name) + log_printf ("'/%s' ", resp_name); + if (resp_keyid) + { + log_printf ("{"); + dump_serial (resp_keyid); + log_printf ("} "); + } + log_printf ("\n"); + } + ksba_free (resp_name); + ksba_free (resp_keyid); + err = 0; + } + /* We got a useful answer, check that the answer has a valid signature. */ sigval = ksba_ocsp_get_sig_val (ocsp, produced_at); if (!sigval || !*produced_at) From 68b8096b6617cdad09c99d7eda2035176807e78f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 22 Oct 2018 17:24:58 +0200 Subject: [PATCH 028/235] agent: Fix build regression for Windows. * agent/command-ssh.c (get_client_info): Turn client_uid into an int. Fix setting of it in case of a failed getsocketopt. * agent/command.c (start_command_handler): Fix setting of the pid and uid for Windows. -- Fixes-commit: 28aa6890588cc108639951bb4bef03ac17743046 which obviously was only added to master. Signed-off-by: Werner Koch --- agent/command-ssh.c | 18 ++++++++++-------- agent/command.c | 5 +++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/agent/command-ssh.c b/agent/command-ssh.c index df63ed713..ff1f0a5bc 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -3625,7 +3625,7 @@ static void get_client_info (int fd, struct peer_info_s *out) { pid_t client_pid = (pid_t)(-1); - uid_t client_uid = (uid_t)-1; + int client_uid = -1; #ifdef SO_PEERCRED { @@ -3640,10 +3640,10 @@ get_client_info (int fd, struct peer_info_s *out) { #if defined (HAVE_STRUCT_SOCKPEERCRED_PID) || defined (HAVE_STRUCT_UCRED_PID) client_pid = cr.pid; - client_uid = cr.uid; + client_uid = (int)cr.uid; #elif defined (HAVE_STRUCT_UCRED_CR_PID) client_pid = cr.cr_pid; - client_pid = cr.cr_uid; + client_uid = (int)cr.cr_uid; #else #error "Unknown SO_PEERCRED struct" #endif @@ -3660,7 +3660,7 @@ get_client_info (int fd, struct peer_info_s *out) len = sizeof (struct xucred); if (!getsockopt (fd, SOL_LOCAL, LOCAL_PEERCRED, &cr, &len)) - client_uid = cr.cr_uid; + client_uid = (int)cr.cr_uid; } #endif } @@ -3670,8 +3670,10 @@ get_client_info (int fd, struct peer_info_s *out) socklen_t unpl = sizeof unp; if (getsockopt (fd, 0, LOCAL_PEEREID, &unp, &unpl) != -1) - client_pid = unp.unp_pid; - client_uid = unp.unp_euid; + { + client_pid = unp.unp_pid; + client_uid = (int)unp.unp_euid; + } } #elif defined (HAVE_GETPEERUCRED) { @@ -3680,7 +3682,7 @@ get_client_info (int fd, struct peer_info_s *out) if (getpeerucred (fd, &ucred) != -1) { client_pid = ucred_getpid (ucred); - client_uid = ucred_geteuid (ucred); + client_uid = (int)ucred_geteuid (ucred); ucred_free (ucred); } } @@ -3689,7 +3691,7 @@ get_client_info (int fd, struct peer_info_s *out) #endif out->pid = (client_pid == (pid_t)(-1)? 0 : (unsigned long)client_pid); - out->uid = (int)client_uid; + out->uid = client_uid; } diff --git a/agent/command.c b/agent/command.c index 925d1f780..087175335 100644 --- a/agent/command.c +++ b/agent/command.c @@ -3588,8 +3588,13 @@ start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd) } else { +#ifdef HAVE_W32_SYSTEM + pid = assuan_get_pid (ctx); + ctrl->client_uid = -1; +#else pid = client_creds->pid; ctrl->client_uid = client_creds->uid; +#endif } ctrl->client_pid = (pid == ASSUAN_INVALID_PID)? 0 : (unsigned long)pid; ctrl->server_local->connect_from_self = (pid == getpid ()); From 256a280c51f9ea862e4bfb0bb530c2a96f9088f9 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 22 Oct 2018 20:13:08 +0200 Subject: [PATCH 029/235] dirmngr: Prepare for updated WKD specs with ?l= param * dirmngr/server.c (proc_wkd_get): Tack the raw local address to the request. -- We append the raw non-canonicalized local address part to the hash. Servers who serve the requests from static files will ignore the parameters and a test with posteo shows that also services using a database ignore the parameter. The general idea is that service providers may use their own canonicalization rules. The problem is that we currently filter the returned key for the full mail address and thus we will never see a key if the service did a different canonicalization than we. So consider this to be an experiment. Signed-off-by: Werner Koch --- dirmngr/server.c | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/dirmngr/server.c b/dirmngr/server.c index 2519fd601..05a530bce 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -957,19 +957,28 @@ proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) } else { - uri = strconcat ("https://", - domain, - portstr, - "/.well-known/openpgpkey/hu/", - encodedhash, - NULL); - no_log = 1; - if (uri) + char *escapedmbox; + + escapedmbox = http_escape_string (mbox, "%;?&="); + if (escapedmbox) { - err = dirmngr_status_printf (ctrl, "SOURCE", "https://%s%s", - domain, portstr); - if (err) - goto leave; + uri = strconcat ("https://", + domain, + portstr, + "/.well-known/openpgpkey/hu/", + encodedhash, + "?l=", + escapedmbox, + NULL); + xfree (escapedmbox); + no_log = 1; + if (uri) + { + err = dirmngr_status_printf (ctrl, "SOURCE", "https://%s%s", + domain, portstr); + if (err) + goto leave; + } } } if (!uri) From 3b88bceb4dc31e076da80987ce67260c62bc7a93 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 24 Oct 2018 16:00:20 +0200 Subject: [PATCH 030/235] indent: Modernize g10/sign.c -- --- g10/sign.c | 1654 +++++++++++++++++++++++++++------------------------- 1 file changed, 846 insertions(+), 808 deletions(-) diff --git a/g10/sign.c b/g10/sign.c index 581a08f5b..83f455122 100644 --- a/g10/sign.c +++ b/g10/sign.c @@ -51,7 +51,8 @@ static int recipient_digest_algo=0; -/**************** + +/* * Create notations and other stuff. It is assumed that the stings in * STRLIST are already checked to contain only printable data and have * a valid NAME=VALUE format. @@ -574,129 +575,135 @@ print_status_sig_created (PKT_public_key *pk, PKT_signature *sig, int what) * Loop over the secret certificates in SK_LIST and build the one pass * signature packets. OpenPGP says that the data should be bracket by * the onepass-sig and signature-packet; so we build these onepass - * packet here in reverse order + * packet here in reverse order. */ static int write_onepass_sig_packets (SK_LIST sk_list, IOBUF out, int sigclass ) { - int skcount; - SK_LIST sk_rover; + int skcount; + SK_LIST sk_rover; - for (skcount=0, sk_rover=sk_list; sk_rover; sk_rover = sk_rover->next) - skcount++; + for (skcount=0, sk_rover=sk_list; sk_rover; sk_rover = sk_rover->next) + skcount++; - for (; skcount; skcount--) { - PKT_public_key *pk; - PKT_onepass_sig *ops; - PACKET pkt; - int i, rc; + for (; skcount; skcount--) + { + PKT_public_key *pk; + PKT_onepass_sig *ops; + PACKET pkt; + int i, rc; - for (i=0, sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) { - if (++i == skcount) - break; - } + for (i=0, sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) + if (++i == skcount) + break; - pk = sk_rover->pk; - ops = xmalloc_clear (sizeof *ops); - ops->sig_class = sigclass; - ops->digest_algo = hash_for (pk); - ops->pubkey_algo = pk->pubkey_algo; - keyid_from_pk (pk, ops->keyid); - ops->last = (skcount == 1); + pk = sk_rover->pk; + ops = xmalloc_clear (sizeof *ops); + ops->sig_class = sigclass; + ops->digest_algo = hash_for (pk); + ops->pubkey_algo = pk->pubkey_algo; + keyid_from_pk (pk, ops->keyid); + ops->last = (skcount == 1); - init_packet(&pkt); - pkt.pkttype = PKT_ONEPASS_SIG; - pkt.pkt.onepass_sig = ops; - rc = build_packet (out, &pkt); - free_packet (&pkt, NULL); - if (rc) { - log_error ("build onepass_sig packet failed: %s\n", - gpg_strerror (rc)); - return rc; + init_packet (&pkt); + pkt.pkttype = PKT_ONEPASS_SIG; + pkt.pkt.onepass_sig = ops; + rc = build_packet (out, &pkt); + free_packet (&pkt, NULL); + if (rc) + { + log_error ("build onepass_sig packet failed: %s\n", + gpg_strerror (rc)); + return rc; } } - return 0; + return 0; } + /* * Helper to write the plaintext (literal data) packet */ static int write_plaintext_packet (IOBUF out, IOBUF inp, const char *fname, int ptmode) { - PKT_plaintext *pt = NULL; - u32 filesize; - int rc = 0; + PKT_plaintext *pt = NULL; + u32 filesize; + int rc = 0; - if (!opt.no_literal) - pt=setup_plaintext_name(fname,inp); + if (!opt.no_literal) + pt = setup_plaintext_name (fname, inp); - /* try to calculate the length of the data */ - if ( !iobuf_is_pipe_filename (fname) && *fname ) - { - off_t tmpsize; - int overflow; + /* Try to calculate the length of the data. */ + if ( !iobuf_is_pipe_filename (fname) && *fname) + { + off_t tmpsize; + int overflow; - if( !(tmpsize = iobuf_get_filelength(inp, &overflow)) - && !overflow && opt.verbose) - log_info (_("WARNING: '%s' is an empty file\n"), fname); + if (!(tmpsize = iobuf_get_filelength (inp, &overflow)) + && !overflow && opt.verbose) + log_info (_("WARNING: '%s' is an empty file\n"), fname); - /* We can't encode the length of very large files because - OpenPGP uses only 32 bit for file sizes. So if the size of - a file is larger than 2^32 minus some bytes for packet - headers, we switch to partial length encoding. */ - if ( tmpsize < (IOBUF_FILELENGTH_LIMIT - 65536) ) - filesize = tmpsize; - else - filesize = 0; + /* We can't encode the length of very large files because + * OpenPGP uses only 32 bit for file sizes. So if the size of a + * file is larger than 2^32 minus some bytes for packet headers, + * we switch to partial length encoding. */ + if (tmpsize < (IOBUF_FILELENGTH_LIMIT - 65536)) + filesize = tmpsize; + else + filesize = 0; - /* Because the text_filter modifies the length of the - * data, it is not possible to know the used length - * without a double read of the file - to avoid that - * we simple use partial length packets. */ - if ( ptmode == 't' || ptmode == 'u' || ptmode == 'm') - filesize = 0; - } - else - filesize = opt.set_filesize? opt.set_filesize : 0; /* stdin */ - - if (!opt.no_literal) { - PACKET pkt; - - /* Note that PT has been initialized above in no_literal mode. */ - pt->timestamp = make_timestamp (); - pt->mode = ptmode; - pt->len = filesize; - pt->new_ctb = !pt->len; - pt->buf = inp; - init_packet(&pkt); - pkt.pkttype = PKT_PLAINTEXT; - pkt.pkt.plaintext = pt; - /*cfx.datalen = filesize? calc_packet_length( &pkt ) : 0;*/ - if( (rc = build_packet (out, &pkt)) ) - log_error ("build_packet(PLAINTEXT) failed: %s\n", - gpg_strerror (rc) ); - pt->buf = NULL; - free_packet (&pkt, NULL); + /* Because the text_filter modifies the length of the + * data, it is not possible to know the used length + * without a double read of the file - to avoid that + * we simple use partial length packets. */ + if (ptmode == 't' || ptmode == 'u' || ptmode == 'm') + filesize = 0; } - else { - byte copy_buffer[4096]; - int bytes_copied; + else + filesize = opt.set_filesize? opt.set_filesize : 0; /* stdin */ - while ((bytes_copied = iobuf_read(inp, copy_buffer, 4096)) != -1) - if ( (rc=iobuf_write(out, copy_buffer, bytes_copied)) ) { - log_error ("copying input to output failed: %s\n", - gpg_strerror (rc)); - break; - } - wipememory(copy_buffer,4096); /* burn buffer */ + if (!opt.no_literal) + { + PACKET pkt; + + /* Note that PT has been initialized above in no_literal mode. */ + pt->timestamp = make_timestamp (); + pt->mode = ptmode; + pt->len = filesize; + pt->new_ctb = !pt->len; + pt->buf = inp; + init_packet (&pkt); + pkt.pkttype = PKT_PLAINTEXT; + pkt.pkt.plaintext = pt; + /*cfx.datalen = filesize? calc_packet_length( &pkt ) : 0;*/ + if ((rc = build_packet (out, &pkt))) + log_error ("build_packet(PLAINTEXT) failed: %s\n", + gpg_strerror (rc) ); + pt->buf = NULL; + free_packet (&pkt, NULL); } - /* fixme: it seems that we never freed pt/pkt */ + else + { + byte copy_buffer[4096]; + int bytes_copied; - return rc; + while ((bytes_copied = iobuf_read (inp, copy_buffer, 4096)) != -1) + if ((rc = iobuf_write (out, copy_buffer, bytes_copied))) + { + log_error ("copying input to output failed: %s\n", + gpg_strerror (rc)); + break; + } + wipememory (copy_buffer, 4096); /* burn buffer */ + } + /* fixme: it seems that we never freed pt/pkt */ + + return rc; } + /* * Write the signatures from the SK_LIST to OUT. HASH must be a non-finalized * hash which will not be changes here. @@ -782,7 +789,7 @@ write_signature_packets (ctrl_t ctrl, } -/**************** +/* * Sign the files whose names are in FILENAME. * If DETACHED has the value true, * make a detached signature. If FILENAMES->d is NULL read from stdin @@ -798,62 +805,65 @@ int sign_file (ctrl_t ctrl, strlist_t filenames, int detached, strlist_t locusr, int encryptflag, strlist_t remusr, const char *outfile ) { - const char *fname; - armor_filter_context_t *afx; - compress_filter_context_t zfx; - md_filter_context_t mfx; - text_filter_context_t tfx; - progress_filter_context_t *pfx; - encrypt_filter_context_t efx; - IOBUF inp = NULL, out = NULL; - PACKET pkt; - int rc = 0; - PK_LIST pk_list = NULL; - SK_LIST sk_list = NULL; - SK_LIST sk_rover = NULL; - int multifile = 0; - u32 duration=0; + const char *fname; + armor_filter_context_t *afx; + compress_filter_context_t zfx; + md_filter_context_t mfx; + text_filter_context_t tfx; + progress_filter_context_t *pfx; + encrypt_filter_context_t efx; + iobuf_t inp = NULL; + iobuf_t out = NULL; + PACKET pkt; + int rc = 0; + PK_LIST pk_list = NULL; + SK_LIST sk_list = NULL; + SK_LIST sk_rover = NULL; + int multifile = 0; + u32 duration=0; - pfx = new_progress_context (); - afx = new_armor_context (); - memset( &zfx, 0, sizeof zfx); - memset( &mfx, 0, sizeof mfx); - memset( &efx, 0, sizeof efx); - efx.ctrl = ctrl; - init_packet( &pkt ); + pfx = new_progress_context (); + afx = new_armor_context (); + memset (&zfx, 0, sizeof zfx); + memset (&mfx, 0, sizeof mfx); + memset (&efx, 0, sizeof efx); + efx.ctrl = ctrl; + init_packet (&pkt); - if( filenames ) { - fname = filenames->d; - multifile = !!filenames->next; + if (filenames) + { + fname = filenames->d; + multifile = !!filenames->next; } - else - fname = NULL; + else + fname = NULL; - if( fname && filenames->next && (!detached || encryptflag) ) - log_bug("multiple files can only be detached signed"); + if (fname && filenames->next && (!detached || encryptflag)) + log_bug ("multiple files can only be detached signed"); - if(encryptflag==2 - && (rc=setup_symkey(&efx.symkey_s2k,&efx.symkey_dek))) - goto leave; + if (encryptflag == 2 + && (rc = setup_symkey (&efx.symkey_s2k, &efx.symkey_dek))) + goto leave; - if (opt.ask_sig_expire && !opt.batch) - duration = ask_expire_interval(1,opt.def_sig_expire); - else - duration = parse_expire_string(opt.def_sig_expire); + if (opt.ask_sig_expire && !opt.batch) + duration = ask_expire_interval(1,opt.def_sig_expire); + else + duration = parse_expire_string(opt.def_sig_expire); - /* Note: In the old non-agent version the following call used to - unprotect the secret key. This is now done on demand by the agent. */ - if( (rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG )) ) - goto leave; + /* Note: In the old non-agent version the following call used to + * unprotect the secret key. This is now done on demand by the agent. */ + if ((rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG ))) + goto leave; - if (encryptflag - && (rc=build_pk_list (ctrl, remusr, &pk_list))) - goto leave; + if (encryptflag + && (rc = build_pk_list (ctrl, remusr, &pk_list))) + goto leave; - /* prepare iobufs */ - if( multifile ) /* have list of filenames */ - inp = NULL; /* we do it later */ - else { + /* Prepare iobufs. */ + if (multifile) /* have list of filenames */ + inp = NULL; /* we do it later */ + else + { inp = iobuf_open(fname); if (inp && is_secured_file (iobuf_get_fd (inp))) { @@ -861,407 +871,435 @@ sign_file (ctrl_t ctrl, strlist_t filenames, int detached, strlist_t locusr, inp = NULL; gpg_err_set_errno (EPERM); } - if( !inp ) + if (!inp) { rc = gpg_error_from_syserror (); log_error (_("can't open '%s': %s\n"), fname? fname: "[stdin]", - strerror(errno) ); + strerror (errno)); goto leave; } - handle_progress (pfx, inp, fname); + handle_progress (pfx, inp, fname); } - if( outfile ) { - if (is_secured_filename ( outfile )) { - out = NULL; - gpg_err_set_errno (EPERM); + if (outfile) + { + if (is_secured_filename (outfile)) + { + out = NULL; + gpg_err_set_errno (EPERM); } - else - out = iobuf_create (outfile, 0); - if( !out ) - { - rc = gpg_error_from_syserror (); - log_error(_("can't create '%s': %s\n"), outfile, strerror(errno) ); - goto leave; - } - else if( opt.verbose ) - log_info(_("writing to '%s'\n"), outfile ); + else + out = iobuf_create (outfile, 0); + if (!out) + { + rc = gpg_error_from_syserror (); + log_error (_("can't create '%s': %s\n"), outfile, gpg_strerror (rc)); + goto leave; + } + else if (opt.verbose) + log_info (_("writing to '%s'\n"), outfile); } - else if( (rc = open_outfile (-1, fname, - opt.armor? 1: detached? 2:0, 0, &out))) - goto leave; - - /* prepare to calculate the MD over the input */ - if( opt.textmode && !outfile && !multifile ) - { - memset( &tfx, 0, sizeof tfx); - iobuf_push_filter( inp, text_filter, &tfx ); - } - - if ( gcry_md_open (&mfx.md, 0, 0) ) - BUG (); - if (DBG_HASHING) - gcry_md_debug (mfx.md, "sign"); - - /* If we're encrypting and signing, it is reasonable to pick the - hash algorithm to use out of the recipient key prefs. This is - best effort only, as in a DSA2 and smartcard world there are - cases where we cannot please everyone with a single hash (DSA2 - wants >160 and smartcards want =160). In the future this could - be more complex with different hashes for each sk, but the - current design requires a single hash for all SKs. */ - if(pk_list) - { - if(opt.def_digest_algo) - { - if(!opt.expert && - select_algo_from_prefs(pk_list,PREFTYPE_HASH, - opt.def_digest_algo, - NULL)!=opt.def_digest_algo) - log_info(_("WARNING: forcing digest algorithm %s (%d)" - " violates recipient preferences\n"), - gcry_md_algo_name (opt.def_digest_algo), - opt.def_digest_algo ); - } - else - { - int algo, smartcard=0; - union pref_hint hint; - - hint.digest_length = 0; - - /* Of course, if the recipient asks for something - unreasonable (like the wrong hash for a DSA key) then - don't do it. Check all sk's - if any are DSA or live - on a smartcard, then the hash has restrictions and we - may not be able to give the recipient what they want. - For DSA, pass a hint for the largest q we have. Note - that this means that a q>160 key will override a q=160 - key and force the use of truncation for the q=160 key. - The alternative would be to ignore the recipient prefs - completely and get a different hash for each DSA key in - hash_for(). The override behavior here is more or less - reasonable as it is under the control of the user which - keys they sign with for a given message and the fact - that the message with multiple signatures won't be - usable on an implementation that doesn't understand - DSA2 anyway. */ - - for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) - { - if (sk_rover->pk->pubkey_algo == PUBKEY_ALGO_DSA - || sk_rover->pk->pubkey_algo == PUBKEY_ALGO_ECDSA) - { - int temp_hashlen = (gcry_mpi_get_nbits - (sk_rover->pk->pkey[1])); - - if (sk_rover->pk->pubkey_algo == PUBKEY_ALGO_ECDSA) - temp_hashlen = ecdsa_qbits_from_Q (temp_hashlen); - temp_hashlen = (temp_hashlen+7)/8; - - /* Pick a hash that is large enough for our - largest q */ - - if (hint.digest_lengthpk->is_protected */ - /* && sk_rover->pk->protect.s2k.mode == 1002) */ - /* smartcard = 1; */ - } - - /* Current smartcards only do 160-bit hashes. If we have - to have a >160-bit hash, then we can't use the - recipient prefs as we'd need both =160 and >160 at the - same time and recipient prefs currently require a - single hash for all signatures. All this may well have - to change as the cards add algorithms. */ - - if (!smartcard || (smartcard && hint.digest_length==20)) - if ( (algo= - select_algo_from_prefs(pk_list,PREFTYPE_HASH,-1,&hint)) > 0) - recipient_digest_algo=algo; - } - } - - for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) - gcry_md_enable (mfx.md, hash_for (sk_rover->pk)); - - if( !multifile ) - iobuf_push_filter( inp, md_filter, &mfx ); - - if( detached && !encryptflag) - afx->what = 2; - - if( opt.armor && !outfile ) - push_armor_filter (afx, out); - - if( encryptflag ) { - efx.pk_list = pk_list; - /* fixme: set efx.cfx.datalen if known */ - iobuf_push_filter( out, encrypt_filter, &efx ); + else if ((rc = open_outfile (-1, fname, + opt.armor? 1 : detached? 2 : 0, 0, &out))) + { + goto leave; } - if (opt.compress_algo && !outfile && !detached) - { - int compr_algo=opt.compress_algo; - - /* If not forced by user */ - if(compr_algo==-1) - { - /* If we're not encrypting, then select_algo_from_prefs - will fail and we'll end up with the default. If we are - encrypting, select_algo_from_prefs cannot fail since - there is an assumed preference for uncompressed data. - Still, if it did fail, we'll also end up with the - default. */ - - if((compr_algo= - select_algo_from_prefs(pk_list,PREFTYPE_ZIP,-1,NULL))==-1) - compr_algo=default_compress_algo(); - } - else if(!opt.expert && pk_list - && select_algo_from_prefs(pk_list,PREFTYPE_ZIP, - compr_algo,NULL)!=compr_algo) - log_info(_("WARNING: forcing compression algorithm %s (%d)" - " violates recipient preferences\n"), - compress_algo_to_string(compr_algo),compr_algo); - - /* algo 0 means no compression */ - if( compr_algo ) - push_compress_filter(out,&zfx,compr_algo); - } - - /* Write the one-pass signature packets if needed */ - if (!detached) { - rc = write_onepass_sig_packets (sk_list, out, - opt.textmode && !outfile ? 0x01:0x00); - if (rc) - goto leave; + /* Prepare to calculate the MD over the input. */ + if (opt.textmode && !outfile && !multifile) + { + memset (&tfx, 0, sizeof tfx); + iobuf_push_filter (inp, text_filter, &tfx); } - write_status_begin_signing (mfx.md); + if (gcry_md_open (&mfx.md, 0, 0)) + BUG (); + if (DBG_HASHING) + gcry_md_debug (mfx.md, "sign"); - /* Setup the inner packet. */ - if( detached ) { - if( multifile ) { - strlist_t sl; + /* If we're encrypting and signing, it is reasonable to pick the + * hash algorithm to use out of the recipient key prefs. This is + * best effort only, as in a DSA2 and smartcard world there are + * cases where we cannot please everyone with a single hash (DSA2 + * wants >160 and smartcards want =160). In the future this could + * be more complex with different hashes for each sk, but the + * current design requires a single hash for all SKs. */ + if (pk_list) + { + if (opt.def_digest_algo) + { + if (!opt.expert + && select_algo_from_prefs (pk_list,PREFTYPE_HASH, + opt.def_digest_algo, + NULL) != opt.def_digest_algo) + { + log_info (_("WARNING: forcing digest algorithm %s (%d)" + " violates recipient preferences\n"), + gcry_md_algo_name (opt.def_digest_algo), + opt.def_digest_algo); + } + } + else + { + int algo; + int smartcard=0; + union pref_hint hint; - if( opt.verbose ) - log_info(_("signing:") ); - /* must walk reverse trough this list */ - for( sl = strlist_last(filenames); sl; - sl = strlist_prev( filenames, sl ) ) { - inp = iobuf_open(sl->d); - if (inp && is_secured_file (iobuf_get_fd (inp))) - { - iobuf_close (inp); - inp = NULL; - gpg_err_set_errno (EPERM); - } - if( !inp ) - { - rc = gpg_error_from_syserror (); - log_error(_("can't open '%s': %s\n"), - sl->d,strerror(errno)); - goto leave; - } - handle_progress (pfx, inp, sl->d); - if( opt.verbose ) - log_printf (" '%s'", sl->d ); - if(opt.textmode) - { - memset( &tfx, 0, sizeof tfx); - iobuf_push_filter( inp, text_filter, &tfx ); - } - iobuf_push_filter( inp, md_filter, &mfx ); - while( iobuf_get(inp) != -1 ) - ; - iobuf_close(inp); inp = NULL; - } - if( opt.verbose ) - log_printf ("\n"); - } - else { - /* read, so that the filter can calculate the digest */ - while( iobuf_get(inp) != -1 ) - ; - } - } - else { - rc = write_plaintext_packet (out, inp, fname, - opt.textmode && !outfile ? - (opt.mimemode? 'm':'t'):'b'); + hint.digest_length = 0; + + /* Of course, if the recipient asks for something + * unreasonable (like the wrong hash for a DSA key) then + * don't do it. Check all sk's - if any are DSA or live + * on a smartcard, then the hash has restrictions and we + * may not be able to give the recipient what they want. + * For DSA, pass a hint for the largest q we have. Note + * that this means that a q>160 key will override a q=160 + * key and force the use of truncation for the q=160 key. + * The alternative would be to ignore the recipient prefs + * completely and get a different hash for each DSA key in + * hash_for(). The override behavior here is more or less + * reasonable as it is under the control of the user which + * keys they sign with for a given message and the fact + * that the message with multiple signatures won't be + * usable on an implementation that doesn't understand + * DSA2 anyway. */ + for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) + { + if (sk_rover->pk->pubkey_algo == PUBKEY_ALGO_DSA + || sk_rover->pk->pubkey_algo == PUBKEY_ALGO_ECDSA) + { + int temp_hashlen = gcry_mpi_get_nbits (sk_rover->pk->pkey[1]); + + if (sk_rover->pk->pubkey_algo == PUBKEY_ALGO_ECDSA) + temp_hashlen = ecdsa_qbits_from_Q (temp_hashlen); + + temp_hashlen = (temp_hashlen+7)/8; + + /* Pick a hash that is large enough for our largest Q */ + if (hint.digest_length < temp_hashlen) + hint.digest_length = temp_hashlen; + } + /* FIXME: need to check gpg-agent for this. */ + /* else if (sk_rover->pk->is_protected */ + /* && sk_rover->pk->protect.s2k.mode == 1002) */ + /* smartcard = 1; */ + } + + /* Current smartcards only do 160-bit hashes. If we have + * to have a >160-bit hash, then we can't use the + * recipient prefs as we'd need both =160 and >160 at the + * same time and recipient prefs currently require a + * single hash for all signatures. All this may well have + * to change as the cards add algorithms. */ + if ((!smartcard || (smartcard && hint.digest_length==20)) + && ((algo = select_algo_from_prefs (pk_list, PREFTYPE_HASH, + -1, &hint)) > 0)) + { + recipient_digest_algo = algo; + } + } } - /* catch errors from above */ - if (rc) - goto leave; + for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) + gcry_md_enable (mfx.md, hash_for (sk_rover->pk)); - /* write the signatures */ - rc = write_signature_packets (ctrl, sk_list, out, mfx.md, - opt.textmode && !outfile? 0x01 : 0x00, - 0, duration, detached ? 'D':'S', NULL); - if( rc ) + if (!multifile) + iobuf_push_filter (inp, md_filter, &mfx); + + if (detached && !encryptflag) + afx->what = 2; + + if (opt.armor && !outfile) + push_armor_filter (afx, out); + + if (encryptflag) + { + efx.pk_list = pk_list; + /* fixme: set efx.cfx.datalen if known */ + iobuf_push_filter (out, encrypt_filter, &efx); + } + + if (opt.compress_algo && !outfile && !detached) + { + int compr_algo = opt.compress_algo; + + /* If not forced by user */ + if (compr_algo==-1) + { + /* If we're not encrypting, then select_algo_from_prefs + * will fail and we'll end up with the default. If we are + * encrypting, select_algo_from_prefs cannot fail since + * there is an assumed preference for uncompressed data. + * Still, if it did fail, we'll also end up with the + * default. */ + if ((compr_algo = select_algo_from_prefs (pk_list, PREFTYPE_ZIP, + -1, NULL)) == -1) + { + compr_algo = default_compress_algo(); + } + } + else if (!opt.expert && pk_list + && select_algo_from_prefs (pk_list, PREFTYPE_ZIP, + compr_algo, NULL) != compr_algo) + { + log_info (_("WARNING: forcing compression algorithm %s (%d)" + " violates recipient preferences\n"), + compress_algo_to_string (compr_algo), compr_algo); + } + + /* Algo 0 means no compression. */ + if (compr_algo) + push_compress_filter (out, &zfx, compr_algo); + } + + /* Write the one-pass signature packets if needed */ + if (!detached) + { + rc = write_onepass_sig_packets (sk_list, out, + opt.textmode && !outfile ? 0x01:0x00); + if (rc) goto leave; - - - leave: - if( rc ) - iobuf_cancel(out); - else { - iobuf_close(out); - if (encryptflag) - write_status( STATUS_END_ENCRYPTION ); } - iobuf_close(inp); - gcry_md_close ( mfx.md ); - release_sk_list( sk_list ); - release_pk_list( pk_list ); - recipient_digest_algo=0; - release_progress_context (pfx); - release_armor_context (afx); - return rc; + + write_status_begin_signing (mfx.md); + + /* Setup the inner packet. */ + if (detached) + { + if (multifile) + { + strlist_t sl; + + if (opt.verbose) + log_info (_("signing:") ); + /* Must walk reverse trough this list. */ + for (sl = strlist_last(filenames); + sl; + sl = strlist_prev( filenames, sl)) + { + inp = iobuf_open (sl->d); + if (inp && is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + gpg_err_set_errno (EPERM); + } + if (!inp) + { + rc = gpg_error_from_syserror (); + log_error (_("can't open '%s': %s\n"), + sl->d, gpg_strerror (rc)); + goto leave; + } + handle_progress (pfx, inp, sl->d); + if (opt.verbose) + log_printf (" '%s'", sl->d ); + if (opt.textmode) + { + memset (&tfx, 0, sizeof tfx); + iobuf_push_filter (inp, text_filter, &tfx); + } + iobuf_push_filter (inp, md_filter, &mfx); + while (iobuf_get (inp) != -1) + ; + iobuf_close (inp); + inp = NULL; + } + if (opt.verbose) + log_printf ("\n"); + } + else + { + /* Read, so that the filter can calculate the digest. */ + while (iobuf_get(inp) != -1) + ; + } + } + else + { + rc = write_plaintext_packet (out, inp, fname, + (opt.textmode && !outfile) ? + (opt.mimemode? 'm' : 't') : 'b'); + } + + /* Catch errors from above. */ + if (rc) + goto leave; + + /* Write the signatures. */ + rc = write_signature_packets (ctrl, sk_list, out, mfx.md, + opt.textmode && !outfile? 0x01 : 0x00, + 0, duration, detached ? 'D':'S', NULL); + if (rc) + goto leave; + + + leave: + if (rc) + iobuf_cancel (out); + else + { + iobuf_close (out); + if (encryptflag) + write_status (STATUS_END_ENCRYPTION); + } + iobuf_close (inp); + gcry_md_close (mfx.md); + release_sk_list (sk_list); + release_pk_list (pk_list); + recipient_digest_algo = 0; + release_progress_context (pfx); + release_armor_context (afx); + return rc; } - -/**************** - * make a clear signature. note that opt.armor is not needed +/* + * Make a clear signature. Note that opt.armor is not needed. */ int clearsign_file (ctrl_t ctrl, - const char *fname, strlist_t locusr, const char *outfile ) + const char *fname, strlist_t locusr, const char *outfile) { - armor_filter_context_t *afx; - progress_filter_context_t *pfx; - gcry_md_hd_t textmd = NULL; - IOBUF inp = NULL, out = NULL; - PACKET pkt; - int rc = 0; - SK_LIST sk_list = NULL; - SK_LIST sk_rover = NULL; - u32 duration=0; + armor_filter_context_t *afx; + progress_filter_context_t *pfx; + gcry_md_hd_t textmd = NULL; + iobuf_t inp = NULL; + iobuf_t out = NULL; + PACKET pkt; + int rc = 0; + SK_LIST sk_list = NULL; + SK_LIST sk_rover = NULL; + u32 duration = 0; - pfx = new_progress_context (); - afx = new_armor_context (); - init_packet( &pkt ); + pfx = new_progress_context (); + afx = new_armor_context (); + init_packet( &pkt ); - if (opt.ask_sig_expire && !opt.batch) - duration = ask_expire_interval (1,opt.def_sig_expire); - else - duration = parse_expire_string (opt.def_sig_expire); + if (opt.ask_sig_expire && !opt.batch) + duration = ask_expire_interval (1, opt.def_sig_expire); + else + duration = parse_expire_string (opt.def_sig_expire); - /* Note: In the old non-agent version the following call used to - unprotect the secret key. This is now done on demand by the agent. */ - if( (rc=build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG )) ) - goto leave; - - /* prepare iobufs */ - inp = iobuf_open(fname); - if (inp && is_secured_file (iobuf_get_fd (inp))) - { - iobuf_close (inp); - inp = NULL; - gpg_err_set_errno (EPERM); - } - if( !inp ) { - rc = gpg_error_from_syserror (); - log_error (_("can't open '%s': %s\n"), - fname? fname: "[stdin]", strerror(errno) ); - goto leave; - } - handle_progress (pfx, inp, fname); - - if( outfile ) { - if (is_secured_filename (outfile) ) { - outfile = NULL; - gpg_err_set_errno (EPERM); - } - else - out = iobuf_create (outfile, 0); - if( !out ) - { - rc = gpg_error_from_syserror (); - log_error(_("can't create '%s': %s\n"), outfile, strerror(errno) ); - goto leave; - } - else if( opt.verbose ) - log_info(_("writing to '%s'\n"), outfile ); - } - else if ((rc = open_outfile (-1, fname, 1, 0, &out))) - goto leave; - - iobuf_writestr(out, "-----BEGIN PGP SIGNED MESSAGE-----" LF ); + /* Note: In the old non-agent version the following call used to + * unprotect the secret key. This is now done on demand by the agent. */ + if ((rc=build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG))) + goto leave; + /* Prepare iobufs. */ + inp = iobuf_open (fname); + if (inp && is_secured_file (iobuf_get_fd (inp))) { - const char *s; - int any = 0; - byte hashs_seen[256]; + iobuf_close (inp); + inp = NULL; + gpg_err_set_errno (EPERM); + } + if (!inp) + { + rc = gpg_error_from_syserror (); + log_error (_("can't open '%s': %s\n"), + fname? fname: "[stdin]", gpg_strerror (rc)); + goto leave; + } + handle_progress (pfx, inp, fname); - memset( hashs_seen, 0, sizeof hashs_seen ); - iobuf_writestr(out, "Hash: " ); - for( sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) { - int i = hash_for (sk_rover->pk); + if (outfile) + { + if (is_secured_filename (outfile)) + { + outfile = NULL; + gpg_err_set_errno (EPERM); + } + else + out = iobuf_create (outfile, 0); - if( !hashs_seen[ i & 0xff ] ) { - s = gcry_md_algo_name ( i ); - if( s ) { - hashs_seen[ i & 0xff ] = 1; - if( any ) - iobuf_put(out, ',' ); - iobuf_writestr(out, s ); - any = 1; - } - } - } - log_assert(any); - iobuf_writestr(out, LF ); + if (!out) + { + rc = gpg_error_from_syserror (); + log_error (_("can't create '%s': %s\n"), outfile, gpg_strerror (rc)); + goto leave; + } + else if (opt.verbose) + log_info (_("writing to '%s'\n"), outfile); + + } + else if ((rc = open_outfile (-1, fname, 1, 0, &out))) + { + goto leave; } - if( opt.not_dash_escaped ) - iobuf_writestr( out, - "NotDashEscaped: You need "GPG_NAME - " to verify this message" LF ); - iobuf_writestr(out, LF ); + iobuf_writestr (out, "-----BEGIN PGP SIGNED MESSAGE-----" LF); - if ( gcry_md_open (&textmd, 0, 0) ) - BUG (); + { + const char *s; + int any = 0; + byte hashs_seen[256]; + + memset (hashs_seen, 0, sizeof hashs_seen); + iobuf_writestr (out, "Hash: " ); for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) - gcry_md_enable (textmd, hash_for(sk_rover->pk)); + { + int i = hash_for (sk_rover->pk); - if ( DBG_HASHING ) - gcry_md_debug ( textmd, "clearsign" ); + if (!hashs_seen[ i & 0xff ]) + { + s = gcry_md_algo_name (i); + if (s) + { + hashs_seen[ i & 0xff ] = 1; + if (any) + iobuf_put (out, ','); + iobuf_writestr (out, s); + any = 1; + } + } + } + log_assert (any); + iobuf_writestr (out, LF); + } - copy_clearsig_text (out, inp, textmd, !opt.not_dash_escaped, - opt.escape_from); - /* fixme: check for read errors */ + if (opt.not_dash_escaped) + iobuf_writestr (out, + "NotDashEscaped: You need "GPG_NAME + " to verify this message" LF); + iobuf_writestr (out, LF ); - /* now write the armor */ - afx->what = 2; - push_armor_filter (afx, out); + if (gcry_md_open (&textmd, 0, 0)) + BUG (); + for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) + gcry_md_enable (textmd, hash_for(sk_rover->pk)); - /* Write the signatures. */ - rc = write_signature_packets (ctrl, sk_list, out, textmd, 0x01, 0, - duration, 'C', NULL); - if( rc ) - goto leave; + if (DBG_HASHING) + gcry_md_debug (textmd, "clearsign"); - leave: - if( rc ) - iobuf_cancel(out); - else - iobuf_close(out); - iobuf_close(inp); - gcry_md_close ( textmd ); - release_sk_list( sk_list ); - release_progress_context (pfx); - release_armor_context (afx); - return rc; + copy_clearsig_text (out, inp, textmd, !opt.not_dash_escaped, opt.escape_from); + /* fixme: check for read errors */ + + /* Now write the armor. */ + afx->what = 2; + push_armor_filter (afx, out); + + /* Write the signatures. */ + rc = write_signature_packets (ctrl, sk_list, out, textmd, 0x01, 0, + duration, 'C', NULL); + if (rc) + goto leave; + + leave: + if (rc) + iobuf_cancel (out); + else + iobuf_close (out); + iobuf_close (inp); + gcry_md_close (textmd); + release_sk_list (sk_list); + release_progress_context (pfx); + release_armor_context (afx); + return rc; } + /* * Sign and conventionally encrypt the given file. * FIXME: Far too much code is duplicated - revamp the whole file. @@ -1269,175 +1307,179 @@ clearsign_file (ctrl_t ctrl, int sign_symencrypt_file (ctrl_t ctrl, const char *fname, strlist_t locusr) { - armor_filter_context_t *afx; - progress_filter_context_t *pfx; - compress_filter_context_t zfx; - md_filter_context_t mfx; - text_filter_context_t tfx; - cipher_filter_context_t cfx; - IOBUF inp = NULL, out = NULL; - PACKET pkt; - STRING2KEY *s2k = NULL; - int rc = 0; - SK_LIST sk_list = NULL; - SK_LIST sk_rover = NULL; - int algo; - u32 duration=0; - int canceled; + armor_filter_context_t *afx; + progress_filter_context_t *pfx; + compress_filter_context_t zfx; + md_filter_context_t mfx; + text_filter_context_t tfx; + cipher_filter_context_t cfx; + iobuf_t inp = NULL; + iobuf_t out = NULL; + PACKET pkt; + STRING2KEY *s2k = NULL; + int rc = 0; + SK_LIST sk_list = NULL; + SK_LIST sk_rover = NULL; + int algo; + u32 duration = 0; + int canceled; - pfx = new_progress_context (); - afx = new_armor_context (); - memset( &zfx, 0, sizeof zfx); - memset( &mfx, 0, sizeof mfx); - memset( &tfx, 0, sizeof tfx); - memset( &cfx, 0, sizeof cfx); - init_packet( &pkt ); + pfx = new_progress_context (); + afx = new_armor_context (); + memset (&zfx, 0, sizeof zfx); + memset (&mfx, 0, sizeof mfx); + memset (&tfx, 0, sizeof tfx); + memset (&cfx, 0, sizeof cfx); + init_packet (&pkt); - if (opt.ask_sig_expire && !opt.batch) - duration = ask_expire_interval (1, opt.def_sig_expire); - else - duration = parse_expire_string (opt.def_sig_expire); + if (opt.ask_sig_expire && !opt.batch) + duration = ask_expire_interval (1, opt.def_sig_expire); + else + duration = parse_expire_string (opt.def_sig_expire); - /* Note: In the old non-agent version the following call used to - unprotect the secret key. This is now done on demand by the agent. */ - rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG); - if (rc) - goto leave; + /* Note: In the old non-agent version the following call used to + * unprotect the secret key. This is now done on demand by the agent. */ + rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG); + if (rc) + goto leave; - /* prepare iobufs */ - inp = iobuf_open(fname); - if (inp && is_secured_file (iobuf_get_fd (inp))) - { - iobuf_close (inp); - inp = NULL; - gpg_err_set_errno (EPERM); - } - if( !inp ) { - rc = gpg_error_from_syserror (); - log_error (_("can't open '%s': %s\n"), - fname? fname: "[stdin]", strerror(errno) ); - goto leave; - } - handle_progress (pfx, inp, fname); - - /* prepare key */ - s2k = xmalloc_clear( sizeof *s2k ); - s2k->mode = opt.s2k_mode; - s2k->hash_algo = S2K_DIGEST_ALGO; - - algo = default_cipher_algo(); - cfx.dek = passphrase_to_dek (algo, s2k, 1, 1, NULL, &canceled); - - if (!cfx.dek || !cfx.dek->keylen) { - rc = gpg_error (canceled?GPG_ERR_CANCELED:GPG_ERR_BAD_PASSPHRASE); - log_error(_("error creating passphrase: %s\n"), gpg_strerror (rc) ); - goto leave; - } - - cfx.dek->use_aead = use_aead (NULL, cfx.dek->algo); - if (!cfx.dek->use_aead) - cfx.dek->use_mdc = !!use_mdc (NULL, cfx.dek->algo); - - if (!opt.quiet || !opt.batch) - log_info (_("%s.%s encryption will be used\n"), - openpgp_cipher_algo_name (algo), - cfx.dek->use_aead? openpgp_aead_algo_name (cfx.dek->use_aead) - /**/ : "CFB"); - - /* now create the outfile */ - rc = open_outfile (-1, fname, opt.armor? 1:0, 0, &out); - if (rc) - goto leave; - - /* prepare to calculate the MD over the input */ - if (opt.textmode) - iobuf_push_filter (inp, text_filter, &tfx); - if ( gcry_md_open (&mfx.md, 0, 0) ) - BUG (); - if ( DBG_HASHING ) - gcry_md_debug (mfx.md, "symc-sign"); - - for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) - gcry_md_enable (mfx.md, hash_for (sk_rover->pk)); - - iobuf_push_filter (inp, md_filter, &mfx); - - /* Push armor output filter */ - if (opt.armor) - push_armor_filter (afx, out); - - /* Write the symmetric key packet */ - /*(current filters: armor)*/ + /* Prepare iobufs. */ + inp = iobuf_open (fname); + if (inp && is_secured_file (iobuf_get_fd (inp))) { - PKT_symkey_enc *enc = xmalloc_clear( sizeof *enc ); - enc->version = 4; - enc->cipher_algo = cfx.dek->algo; - enc->s2k = *s2k; - pkt.pkttype = PKT_SYMKEY_ENC; - pkt.pkt.symkey_enc = enc; - if( (rc = build_packet( out, &pkt )) ) - log_error("build symkey packet failed: %s\n", gpg_strerror (rc) ); - xfree(enc); + iobuf_close (inp); + inp = NULL; + gpg_err_set_errno (EPERM); } - - /* Push the encryption filter */ - iobuf_push_filter (out, - cfx.dek->use_aead? cipher_filter_aead - /**/ : cipher_filter_cfb, - &cfx); - - /* Push the compress filter */ - if (default_compress_algo()) - { - if (cfx.dek && (cfx.dek->use_mdc || cfx.dek->use_aead)) - zfx.new_ctb = 1; - push_compress_filter (out, &zfx,default_compress_algo() ); - } - - /* Write the one-pass signature packets */ - /*(current filters: zip - encrypt - armor)*/ - rc = write_onepass_sig_packets (sk_list, out, - opt.textmode? 0x01:0x00); - if (rc) + if (!inp) + { + rc = gpg_error_from_syserror (); + log_error (_("can't open '%s': %s\n"), + fname? fname: "[stdin]", gpg_strerror (rc)); goto leave; - - write_status_begin_signing (mfx.md); - - /* Pipe data through all filters; i.e. write the signed stuff */ - /*(current filters: zip - encrypt - armor)*/ - rc = write_plaintext_packet (out, inp, fname, - opt.textmode ? (opt.mimemode?'m':'t'):'b'); - if (rc) - goto leave; - - /* Write the signatures */ - /*(current filters: zip - encrypt - armor)*/ - rc = write_signature_packets (ctrl, sk_list, out, mfx.md, - opt.textmode? 0x01 : 0x00, - 0, duration, 'S', NULL); - if( rc ) - goto leave; - - - leave: - if( rc ) - iobuf_cancel(out); - else { - iobuf_close(out); - write_status( STATUS_END_ENCRYPTION ); } - iobuf_close(inp); - release_sk_list( sk_list ); - gcry_md_close( mfx.md ); - xfree(cfx.dek); - xfree(s2k); - release_progress_context (pfx); - release_armor_context (afx); - return rc; + handle_progress (pfx, inp, fname); + + /* Prepare key. */ + s2k = xmalloc_clear (sizeof *s2k); + s2k->mode = opt.s2k_mode; + s2k->hash_algo = S2K_DIGEST_ALGO; + + algo = default_cipher_algo (); + cfx.dek = passphrase_to_dek (algo, s2k, 1, 1, NULL, &canceled); + + if (!cfx.dek || !cfx.dek->keylen) + { + rc = gpg_error (canceled?GPG_ERR_CANCELED:GPG_ERR_BAD_PASSPHRASE); + log_error (_("error creating passphrase: %s\n"), gpg_strerror (rc)); + goto leave; + } + + cfx.dek->use_aead = use_aead (NULL, cfx.dek->algo); + if (!cfx.dek->use_aead) + cfx.dek->use_mdc = !!use_mdc (NULL, cfx.dek->algo); + + if (!opt.quiet || !opt.batch) + log_info (_("%s.%s encryption will be used\n"), + openpgp_cipher_algo_name (algo), + cfx.dek->use_aead? openpgp_aead_algo_name (cfx.dek->use_aead) + /**/ : "CFB"); + + /* Now create the outfile. */ + rc = open_outfile (-1, fname, opt.armor? 1:0, 0, &out); + if (rc) + goto leave; + + /* Prepare to calculate the MD over the input. */ + if (opt.textmode) + iobuf_push_filter (inp, text_filter, &tfx); + if (gcry_md_open (&mfx.md, 0, 0)) + BUG (); + if (DBG_HASHING) + gcry_md_debug (mfx.md, "symc-sign"); + + for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) + gcry_md_enable (mfx.md, hash_for (sk_rover->pk)); + + iobuf_push_filter (inp, md_filter, &mfx); + + /* Push armor output filter */ + if (opt.armor) + push_armor_filter (afx, out); + + /* Write the symmetric key packet */ + /* (current filters: armor)*/ + { + PKT_symkey_enc *enc = xmalloc_clear( sizeof *enc ); + + enc->version = 4; + enc->cipher_algo = cfx.dek->algo; + enc->s2k = *s2k; + pkt.pkttype = PKT_SYMKEY_ENC; + pkt.pkt.symkey_enc = enc; + if ((rc = build_packet (out, &pkt))) + log_error ("build symkey packet failed: %s\n", gpg_strerror (rc)); + xfree (enc); + } + + /* Push the encryption filter */ + iobuf_push_filter (out, + cfx.dek->use_aead? cipher_filter_aead + /**/ : cipher_filter_cfb, + &cfx); + + /* Push the compress filter */ + if (default_compress_algo()) + { + if (cfx.dek && (cfx.dek->use_mdc || cfx.dek->use_aead)) + zfx.new_ctb = 1; + push_compress_filter (out, &zfx,default_compress_algo() ); + } + + /* Write the one-pass signature packets */ + /* (current filters: zip - encrypt - armor) */ + rc = write_onepass_sig_packets (sk_list, out, opt.textmode? 0x01:0x00); + if (rc) + goto leave; + + write_status_begin_signing (mfx.md); + + /* Pipe data through all filters; i.e. write the signed stuff. */ + /* (current filters: zip - encrypt - armor) */ + rc = write_plaintext_packet (out, inp, fname, + opt.textmode ? (opt.mimemode?'m':'t'):'b'); + if (rc) + goto leave; + + /* Write the signatures. */ + /* (current filters: zip - encrypt - armor) */ + rc = write_signature_packets (ctrl, sk_list, out, mfx.md, + opt.textmode? 0x01 : 0x00, + 0, duration, 'S', NULL); + if (rc) + goto leave; + + + leave: + if (rc) + iobuf_cancel (out); + else + { + iobuf_close (out); + write_status (STATUS_END_ENCRYPTION); + } + iobuf_close (inp); + release_sk_list (sk_list); + gcry_md_close (mfx.md); + xfree (cfx.dek); + xfree (s2k); + release_progress_context (pfx); + release_armor_context (afx); + return rc; } -/**************** +/* * Create a v4 signature in *RET_SIG. * * PK is the primary key to sign (required for all sigs) @@ -1472,105 +1514,101 @@ make_keysig_packet (ctrl_t ctrl, int (*mksubpkt)(PKT_signature *, void *), void *opaque, const char *cache_nonce) { - PKT_signature *sig; - int rc=0; - int sigversion; - gcry_md_hd_t md; + PKT_signature *sig; + int rc = 0; + int sigversion; + gcry_md_hd_t md; - log_assert ((sigclass >= 0x10 && sigclass <= 0x13) || sigclass == 0x1F - || sigclass == 0x20 || sigclass == 0x18 || sigclass == 0x19 - || sigclass == 0x30 || sigclass == 0x28 ); + log_assert ((sigclass >= 0x10 && sigclass <= 0x13) || sigclass == 0x1F + || sigclass == 0x20 || sigclass == 0x18 || sigclass == 0x19 + || sigclass == 0x30 || sigclass == 0x28 ); - sigversion = 4; - if (sigversion < pksk->version) - sigversion = pksk->version; + sigversion = 4; + if (sigversion < pksk->version) + sigversion = pksk->version; - if( !digest_algo ) - { - /* Basically, this means use SHA1 always unless the user - specified something (use whatever they said), or it's DSA - (use the best match). They still can't pick an - inappropriate hash for DSA or the signature will fail. - Note that this still allows the caller of - make_keysig_packet to override the user setting if it - must. */ + if (!digest_algo) + { + /* Basically, this means use SHA1 always unless the user + * specified something (use whatever they said), or it's DSA + * (use the best match). They still can't pick an inappropriate + * hash for DSA or the signature will fail. Note that this + * still allows the caller of make_keysig_packet to override the + * user setting if it must. */ - if(opt.cert_digest_algo) - digest_algo=opt.cert_digest_algo; - else if(pksk->pubkey_algo == PUBKEY_ALGO_DSA) - digest_algo = match_dsa_hash (gcry_mpi_get_nbits (pksk->pkey[1])/8); - else if (pksk->pubkey_algo == PUBKEY_ALGO_ECDSA - || pksk->pubkey_algo == PUBKEY_ALGO_EDDSA) - { - if (openpgp_oid_is_ed25519 (pksk->pkey[0])) - digest_algo = DIGEST_ALGO_SHA256; - else - digest_algo = match_dsa_hash - (ecdsa_qbits_from_Q (gcry_mpi_get_nbits (pksk->pkey[1]))/8); - } - else - digest_algo = DEFAULT_DIGEST_ALGO; - } - - if ( gcry_md_open (&md, digest_algo, 0 ) ) - BUG (); - - /* Hash the public key certificate. */ - hash_public_key( md, pk ); - - if( sigclass == 0x18 || sigclass == 0x19 || sigclass == 0x28 ) - { - /* hash the subkey binding/backsig/revocation */ - hash_public_key( md, subpk ); - } - else if( sigclass != 0x1F && sigclass != 0x20 ) - { - /* hash the user id */ - hash_uid (md, sigversion, uid); - } - /* and make the signature packet */ - sig = xmalloc_clear( sizeof *sig ); - sig->version = sigversion; - sig->flags.exportable=1; - sig->flags.revocable=1; - keyid_from_pk (pksk, sig->keyid); - sig->pubkey_algo = pksk->pubkey_algo; - sig->digest_algo = digest_algo; - if(timestamp) - sig->timestamp=timestamp; - else - sig->timestamp=make_timestamp(); - if(duration) - sig->expiredate=sig->timestamp+duration; - sig->sig_class = sigclass; - - build_sig_subpkt_from_sig (sig, pksk); - mk_notation_policy_etc (sig, pk, pksk); - - /* Crucial that the call to mksubpkt comes LAST before the calls - to finalize the sig as that makes it possible for the mksubpkt - function to get a reliable pointer to the subpacket area. */ - if (mksubpkt) - rc = (*mksubpkt)( sig, opaque ); - - if( !rc ) { - hash_sigversion_to_magic (md, sig); - gcry_md_final (md); - - rc = complete_sig (ctrl, sig, pksk, md, cache_nonce); + if (opt.cert_digest_algo) + digest_algo = opt.cert_digest_algo; + else if (pksk->pubkey_algo == PUBKEY_ALGO_DSA) + digest_algo = match_dsa_hash (gcry_mpi_get_nbits (pksk->pkey[1])/8); + else if (pksk->pubkey_algo == PUBKEY_ALGO_ECDSA + || pksk->pubkey_algo == PUBKEY_ALGO_EDDSA) + { + if (openpgp_oid_is_ed25519 (pksk->pkey[0])) + digest_algo = DIGEST_ALGO_SHA256; + else + digest_algo = match_dsa_hash + (ecdsa_qbits_from_Q (gcry_mpi_get_nbits (pksk->pkey[1]))/8); + } + else + digest_algo = DEFAULT_DIGEST_ALGO; } - gcry_md_close (md); - if( rc ) - free_seckey_enc( sig ); - else - *ret_sig = sig; - return rc; + if (gcry_md_open (&md, digest_algo, 0)) + BUG (); + + /* Hash the public key certificate. */ + hash_public_key (md, pk); + + if (sigclass == 0x18 || sigclass == 0x19 || sigclass == 0x28) + { + /* Hash the subkey binding/backsig/revocation. */ + hash_public_key (md, subpk); + } + else if (sigclass != 0x1F && sigclass != 0x20) + { + /* Hash the user id. */ + hash_uid (md, sigversion, uid); + } + /* Make the signature packet. */ + sig = xmalloc_clear (sizeof *sig); + sig->version = sigversion; + sig->flags.exportable = 1; + sig->flags.revocable = 1; + keyid_from_pk (pksk, sig->keyid); + sig->pubkey_algo = pksk->pubkey_algo; + sig->digest_algo = digest_algo; + sig->timestamp = timestamp? timestamp : make_timestamp (); + if (duration) + sig->expiredate = sig->timestamp + duration; + sig->sig_class = sigclass; + + build_sig_subpkt_from_sig (sig, pksk); + mk_notation_policy_etc (sig, pk, pksk); + + /* Crucial that the call to mksubpkt comes LAST before the calls + * to finalize the sig as that makes it possible for the mksubpkt + * function to get a reliable pointer to the subpacket area. */ + if (mksubpkt) + rc = (*mksubpkt)(sig, opaque); + + if (!rc) + { + hash_sigversion_to_magic (md, sig); + gcry_md_final (md); + rc = complete_sig (ctrl, sig, pksk, md, cache_nonce); + } + + gcry_md_close (md); + if (rc) + free_seckey_enc (sig); + else + *ret_sig = sig; + return rc; } -/**************** +/* * Create a new signature packet based on an existing one. * Only user ID signatures are supported for now. * PK is the public key to work on. @@ -1589,82 +1627,82 @@ update_keysig_packet (ctrl_t ctrl, int (*mksubpkt)(PKT_signature *, void *), void *opaque) { - PKT_signature *sig; - gpg_error_t rc = 0; - int digest_algo; - gcry_md_hd_t md; + PKT_signature *sig; + gpg_error_t rc = 0; + int digest_algo; + gcry_md_hd_t md; - if ((!orig_sig || !pk || !pksk) - || (orig_sig->sig_class >= 0x10 && orig_sig->sig_class <= 0x13 && !uid) - || (orig_sig->sig_class == 0x18 && !subpk)) - return GPG_ERR_GENERAL; + if ((!orig_sig || !pk || !pksk) + || (orig_sig->sig_class >= 0x10 && orig_sig->sig_class <= 0x13 && !uid) + || (orig_sig->sig_class == 0x18 && !subpk)) + return GPG_ERR_GENERAL; - if ( opt.cert_digest_algo ) - digest_algo = opt.cert_digest_algo; - else - digest_algo = orig_sig->digest_algo; + if (opt.cert_digest_algo) + digest_algo = opt.cert_digest_algo; + else + digest_algo = orig_sig->digest_algo; - if ( gcry_md_open (&md, digest_algo, 0 ) ) - BUG (); + if (gcry_md_open (&md, digest_algo, 0)) + BUG (); - /* Hash the public key certificate and the user id. */ - hash_public_key( md, pk ); + /* Hash the public key certificate and the user id. */ + hash_public_key (md, pk); - if( orig_sig->sig_class == 0x18 ) - hash_public_key( md, subpk ); - else - hash_uid (md, orig_sig->version, uid); + if (orig_sig->sig_class == 0x18) + hash_public_key (md, subpk); + else + hash_uid (md, orig_sig->version, uid); - /* create a new signature packet */ - sig = copy_signature (NULL, orig_sig); + /* Create a new signature packet. */ + sig = copy_signature (NULL, orig_sig); - sig->digest_algo=digest_algo; + sig->digest_algo = digest_algo; - /* We need to create a new timestamp so that new sig expiration - calculations are done correctly... */ - sig->timestamp=make_timestamp(); + /* We need to create a new timestamp so that new sig expiration + * calculations are done correctly... */ + sig->timestamp = make_timestamp(); - /* ... but we won't make a timestamp earlier than the existing - one. */ + /* ... but we won't make a timestamp earlier than the existing + * one. */ + { + int tmout = 0; + while (sig->timestamp <= orig_sig->timestamp) + { + if (++tmout > 5 && !opt.ignore_time_conflict) + { + rc = gpg_error (GPG_ERR_TIME_CONFLICT); + goto leave; + } + gnupg_sleep (1); + sig->timestamp = make_timestamp(); + } + } + + /* Note that already expired sigs will remain expired (with a + * duration of 1) since build-packet.c:build_sig_subpkt_from_sig + * detects this case. */ + + /* Put the updated timestamp into the sig. Note that this will + * automagically lower any sig expiration dates to correctly + * correspond to the differences in the timestamps (i.e. the + * duration will shrink). */ + build_sig_subpkt_from_sig (sig, pksk); + + if (mksubpkt) + rc = (*mksubpkt)(sig, opaque); + + if (!rc) { - int tmout = 0; - while(sig->timestamp<=orig_sig->timestamp) - { - if (++tmout > 5 && !opt.ignore_time_conflict) - { - rc = gpg_error (GPG_ERR_TIME_CONFLICT); - goto leave; - } - gnupg_sleep (1); - sig->timestamp=make_timestamp(); - } - } - - /* Note that already expired sigs will remain expired (with a - duration of 1) since build-packet.c:build_sig_subpkt_from_sig - detects this case. */ - - /* Put the updated timestamp into the sig. Note that this will - automagically lower any sig expiration dates to correctly - correspond to the differences in the timestamps (i.e. the - duration will shrink). */ - build_sig_subpkt_from_sig (sig, pksk); - - if (mksubpkt) - rc = (*mksubpkt)(sig, opaque); - - if (!rc) { - hash_sigversion_to_magic (md, sig); - gcry_md_final (md); - - rc = complete_sig (ctrl, sig, pksk, md, NULL); + hash_sigversion_to_magic (md, sig); + gcry_md_final (md); + rc = complete_sig (ctrl, sig, pksk, md, NULL); } leave: - gcry_md_close (md); - if( rc ) - free_seckey_enc (sig); - else - *ret_sig = sig; - return rc; + gcry_md_close (md); + if (rc) + free_seckey_enc (sig); + else + *ret_sig = sig; + return rc; } From 64a1e86fc06d89c980a196c61d2b6d77d167565e Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 24 Oct 2018 16:18:27 +0200 Subject: [PATCH 031/235] gpg: Unfinished support for v5 signatures. * g10/parse-packet.c (parse_signature): Allow for v5 signatures. * g10/sig-check.c (check_signature_end_simple): Support the 64bit v5 byte count. * g10/sign.c (hash_sigversion_to_magic): Ditto. (write_signature_packets): Request v5 sig for v5 keys. Remove useless condition. (make_keysig_packet): Request v5 sig for v5 keys. Signed-off-by: Werner Koch --- g10/build-packet.c | 2 +- g10/parse-packet.c | 16 ++++++++-------- g10/sig-check.c | 29 ++++++++++++++++++---------- g10/sign.c | 47 +++++++++++++++++++++++++--------------------- 4 files changed, 54 insertions(+), 40 deletions(-) diff --git a/g10/build-packet.c b/g10/build-packet.c index 3c500d34f..dd4ad54bf 100644 --- a/g10/build-packet.c +++ b/g10/build-packet.c @@ -1536,7 +1536,7 @@ do_signature( IOBUF out, int ctb, PKT_signature *sig ) else iobuf_put( a, sig->version ); if ( sig->version < 4 ) - iobuf_put (a, 5 ); /* Constant */ + iobuf_put (a, 5 ); /* Constant used by pre-v4 signatures. */ iobuf_put (a, sig->sig_class ); if ( sig->version < 4 ) { diff --git a/g10/parse-packet.c b/g10/parse-packet.c index 92c65294a..78413e49a 100644 --- a/g10/parse-packet.c +++ b/g10/parse-packet.c @@ -1932,7 +1932,7 @@ parse_signature (IOBUF inp, int pkttype, unsigned long pktlen, { int md5_len = 0; unsigned n; - int is_v4 = 0; + int is_v4or5 = 0; int rc = 0; int i, ndata; @@ -1945,8 +1945,8 @@ parse_signature (IOBUF inp, int pkttype, unsigned long pktlen, } sig->version = iobuf_get_noeof (inp); pktlen--; - if (sig->version == 4) - is_v4 = 1; + if (sig->version == 4 || sig->version == 5) + is_v4or5 = 1; else if (sig->version != 2 && sig->version != 3) { log_error ("packet(%d) with unknown version %d\n", @@ -1957,7 +1957,7 @@ parse_signature (IOBUF inp, int pkttype, unsigned long pktlen, goto leave; } - if (!is_v4) + if (!is_v4or5) { if (pktlen == 0) goto underflow; @@ -1968,7 +1968,7 @@ parse_signature (IOBUF inp, int pkttype, unsigned long pktlen, goto underflow; sig->sig_class = iobuf_get_noeof (inp); pktlen--; - if (!is_v4) + if (!is_v4or5) { if (pktlen < 12) goto underflow; @@ -1987,7 +1987,7 @@ parse_signature (IOBUF inp, int pkttype, unsigned long pktlen, pktlen--; sig->flags.exportable = 1; sig->flags.revocable = 1; - if (is_v4) /* Read subpackets. */ + if (is_v4or5) /* Read subpackets. */ { if (pktlen < 2) goto underflow; @@ -2058,7 +2058,7 @@ parse_signature (IOBUF inp, int pkttype, unsigned long pktlen, sig->digest_start[1] = iobuf_get_noeof (inp); pktlen--; - if (is_v4 && sig->pubkey_algo) /* Extract required information. */ + if (is_v4or5 && sig->pubkey_algo) /* Extract required information. */ { const byte *p; size_t len; @@ -2159,7 +2159,7 @@ parse_signature (IOBUF inp, int pkttype, unsigned long pktlen, (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) + if (is_v4or5) { parse_sig_subpkt (sig->hashed, SIGSUBPKT_LIST_HASHED, NULL); parse_sig_subpkt (sig->unhashed, SIGSUBPKT_LIST_UNHASHED, NULL); diff --git a/g10/sig-check.c b/g10/sig-check.c index 9c32d4d12..6d7f1afbd 100644 --- a/g10/sig-check.c +++ b/g10/sig-check.c @@ -510,7 +510,8 @@ check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig, } else { - byte buf[6]; + byte buf[10]; + int i; size_t n; gcry_md_putc (digest, sig->pubkey_algo); gcry_md_putc (digest, sig->digest_algo); @@ -531,13 +532,21 @@ check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig, n = 6; } /* add some magic per Section 5.2.4 of RFC 4880. */ - buf[0] = sig->version; - buf[1] = 0xff; - buf[2] = n >> 24; - buf[3] = n >> 16; - buf[4] = n >> 8; - buf[5] = n; - gcry_md_write( digest, buf, 6 ); + i = 0; + buf[i++] = sig->version; + buf[i++] = 0xff; + if (sig->version >= 5) + { + buf[i++] = 0; + buf[i++] = 0; + buf[i++] = 0; + buf[i++] = 0; + } + buf[i++] = n >> 24; + buf[i++] = n >> 16; + buf[i++] = n >> 8; + buf[i++] = n; + gcry_md_write (digest, buf, i); } gcry_md_final( digest ); @@ -572,7 +581,7 @@ hash_uid_packet (PKT_user_id *uid, gcry_md_hd_t md, PKT_signature *sig ) { if (uid->attrib_data) { - if (sig->version >=4) + if (sig->version >= 4) { byte buf[5]; buf[0] = 0xd1; /* packet of type 17 */ @@ -586,7 +595,7 @@ hash_uid_packet (PKT_user_id *uid, gcry_md_hd_t md, PKT_signature *sig ) } else { - if (sig->version >=4) + if (sig->version >= 4) { byte buf[5]; buf[0] = 0xb4; /* indicates a userid packet */ diff --git a/g10/sign.c b/g10/sign.c index 83f455122..213fd0145 100644 --- a/g10/sign.c +++ b/g10/sign.c @@ -220,7 +220,8 @@ hash_uid (gcry_md_hd_t md, int sigversion, const PKT_user_id *uid) static void hash_sigversion_to_magic (gcry_md_hd_t md, const PKT_signature *sig) { - byte buf[6]; + byte buf[10]; + int i; size_t n; gcry_md_putc (md, sig->version); @@ -242,13 +243,21 @@ hash_sigversion_to_magic (gcry_md_hd_t md, const PKT_signature *sig) n = 6; } /* Add some magic. */ - buf[0] = sig->version; - buf[1] = 0xff; - buf[2] = n >> 24; /* (n is only 16 bit, so this is always 0) */ - buf[3] = n >> 16; - buf[4] = n >> 8; - buf[5] = n; - gcry_md_write (md, buf, 6); + i = 0; + buf[i++] = sig->version; + buf[i++] = 0xff; + if (sig->version >= 5) + { + buf[i++] = 0; + buf[i++] = 0; + buf[i++] = 0; + buf[i++] = 0; + } + buf[i++] = n >> 24; /* (n is only 16 bit, so this is always 0) */ + buf[i++] = n >> 16; + buf[i++] = n >> 8; + buf[i++] = n; + gcry_md_write (md, buf, i); } @@ -731,11 +740,10 @@ write_signature_packets (ctrl_t ctrl, if (!sig) return gpg_error_from_syserror (); - if (duration || opt.sig_policy_url - || opt.sig_notations || opt.sig_keyserver_url) - sig->version = 4; + if (pk->version >= 5) + sig->version = 5; /* Required for v5 keys. */ else - sig->version = pk->version; + sig->version = 4; /*Required. */ keyid_from_pk (pk, sig->keyid); sig->digest_algo = hash_for (pk); @@ -751,12 +759,8 @@ write_signature_packets (ctrl_t ctrl, if (gcry_md_copy (&md, hash)) BUG (); - if (sig->version >= 4) - { - build_sig_subpkt_from_sig (sig, pk); - mk_notation_policy_etc (sig, NULL, pk); - } - + build_sig_subpkt_from_sig (sig, pk); + mk_notation_policy_etc (sig, NULL, pk); hash_sigversion_to_magic (md, sig); gcry_md_final (md); @@ -1523,9 +1527,10 @@ make_keysig_packet (ctrl_t ctrl, || sigclass == 0x20 || sigclass == 0x18 || sigclass == 0x19 || sigclass == 0x30 || sigclass == 0x28 ); - sigversion = 4; - if (sigversion < pksk->version) - sigversion = pksk->version; + if (pksk->version >= 5) + sigversion = 5; + else + sigversion = 4; if (!digest_algo) { From 793fd8d876777c24c4d5072301fa530333d6e1d9 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 24 Oct 2018 19:55:19 +0200 Subject: [PATCH 032/235] sm: Use the correct string in an error message. * sm/gpgsm.c (main): Fix error message. -- GnuPG-bug-id: 4219 --- sm/gpgsm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sm/gpgsm.c b/sm/gpgsm.c index b0547876a..072b30d35 100644 --- a/sm/gpgsm.c +++ b/sm/gpgsm.c @@ -1666,7 +1666,7 @@ main ( int argc, char **argv) || cmd == aClearsign, opt.extra_digest_algo)) log_error (_("digest algorithm '%s' may not be used in %s mode\n"), - forced_digest_algo, + extra_digest_algo, gnupg_compliance_option_string (opt.compliance)); if (log_get_errorcount(0)) From 378719f25fe00d46393541f4a4f79e04484c3000 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 24 Oct 2018 20:04:52 +0200 Subject: [PATCH 033/235] tools: Fix FILE memory leak in gpg-connect-agent. * tools/gpg-connect-agent.c (do_open): dup the fileno and close the stream. GnuPG-bug-id: 4220 Signed-off-by: Werner Koch --- tools/gpg-connect-agent.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/gpg-connect-agent.c b/tools/gpg-connect-agent.c index 00482a32e..7eb7ffa3a 100644 --- a/tools/gpg-connect-agent.c +++ b/tools/gpg-connect-agent.c @@ -983,7 +983,7 @@ do_open (char *line) name, mode, strerror (errno)); return; } - fd = fileno (fp); + fd = dup (fileno (fp)); if (fd >= 0 && fd < DIM (open_fd_table)) { open_fd_table[fd].inuse = 1; @@ -1030,8 +1030,10 @@ do_open (char *line) else { log_error ("can't put fd %d into table\n", fd); - close (fd); + if (fd != -1) + close (fd); /* Table was full. */ } + fclose (fp); } From 7385e1babf6eef586c79ad23f8e541aaf608c4e5 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 24 Oct 2018 20:11:33 +0200 Subject: [PATCH 034/235] ssh: Fix possible infinite loop in case of an read error. * agent/command-ssh.c (ssh_handler_add_identity): Handle other errors than EOF. -- GnuPG-bug-id: 4221 Signed-off-by: Werner Koch --- agent/command-ssh.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/agent/command-ssh.c b/agent/command-ssh.c index ff1f0a5bc..86fd092ff 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -3249,9 +3249,10 @@ ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response) while (1) { err = stream_read_byte (request, &b); - if (gpg_err_code (err) == GPG_ERR_EOF) - { - err = 0; + if (err) + { + if (gpg_err_code (err) == GPG_ERR_EOF) + err = 0; break; } From 2bdc4b6ed97770ed15ec6c5afa02c2e44568a3bc Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 24 Oct 2018 20:16:26 +0200 Subject: [PATCH 035/235] agent: Fix possible release of unitialize var in a genkey error case. * agent/command.c (cmd_genkey): Initialize 'value'. -- GnuPG-bug-id: 4222 Signed-off-by: Werner Koch --- agent/command.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/command.c b/agent/command.c index 087175335..7fbf1de1c 100644 --- a/agent/command.c +++ b/agent/command.c @@ -887,7 +887,7 @@ cmd_genkey (assuan_context_t ctx, char *line) ctrl_t ctrl = assuan_get_pointer (ctx); int rc; int no_protection; - unsigned char *value; + unsigned char *value = NULL; size_t valuelen; unsigned char *newpasswd = NULL; membuf_t outbuf; From bafcf7095159493a656382997f8b0d0bb11a20e8 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 24 Oct 2018 20:22:17 +0200 Subject: [PATCH 036/235] agent: Fix possible uninitalized use of CTX in simple_pwquery. * common/simple-pwquery.c (agent_open): Clear CTX even on early error. -- GnuPG-bug-id: 4223 Signed-off-by: Werner Koch --- common/simple-pwquery.c | 1 + 1 file changed, 1 insertion(+) diff --git a/common/simple-pwquery.c b/common/simple-pwquery.c index e7f4af341..b8ada4214 100644 --- a/common/simple-pwquery.c +++ b/common/simple-pwquery.c @@ -246,6 +246,7 @@ agent_open (assuan_context_t *ctx) #ifdef SPWQ_USE_LOGGING log_error (_("no gpg-agent running in this session\n")); #endif + *ctx = NULL; return SPWQ_NO_AGENT; } From ef540d1af0649ebf1add190d0ab095e957658b7e Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 24 Oct 2018 14:39:56 -0400 Subject: [PATCH 037/235] doc: fix spelling mistakes Signed-off-by: Daniel Kahn Gillmor --- doc/DETAILS | 12 ++++++------ doc/HACKING | 6 +++--- doc/faq.org | 2 +- doc/tools.texi | 6 +++--- doc/wks.texi | 4 ++-- doc/yat2m.c | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/doc/DETAILS b/doc/DETAILS index eb6d7dd4b..74a63ef00 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -59,7 +59,7 @@ described here. - uat :: User attribute (same as user id except for field 10). - sig :: Signature - rev :: Revocation signature - - rvs :: Recocation signature (standalone) [since 2.2.9] + - rvs :: Revocation signature (standalone) [since 2.2.9] - fpr :: Fingerprint (fingerprint is in field 10) - pkd :: Public key data [*] - grp :: Keygrip @@ -126,7 +126,7 @@ described here. *** Field 4 - Public key algorithm The values here are those from the OpenPGP specs or if they are - greather than 255 the algorithm ids as used by Libgcrypt. + greater than 255 the algorithm ids as used by Libgcrypt. *** Field 5 - KeyID @@ -544,7 +544,7 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: *** DECRYPTION_KEY This line is emitted when a public key decryption succeeded in providing a session key. is the hexified fingerprint of the - actual key used for descryption. is the fingerprint of the + actual key used for decryption. is the fingerprint of the primary key. is the letter with the ownertrust; this is in general a 'u' which stands for ultimately trusted. *** DECRYPTION_INFO [] @@ -700,7 +700,7 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: - 0 :: No specific reason given - 1 :: Not Found - - 2 :: Ambigious specification + - 2 :: Ambiguous specification - 3 :: Wrong key usage - 4 :: Key revoked - 5 :: Key expired @@ -1016,7 +1016,7 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: - 2 :: bad PIN *** SC_OP_SUCCESS - A smart card operaion succeeded. This status is only printed for + A smart card operation succeeded. This status is only printed for certain operation and is mostly useful to check whether a PIN change really worked. @@ -1073,7 +1073,7 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: Deleting a key failed. Reason codes are: - 1 :: No such key - 2 :: Must delete secret key first - - 3 :: Ambigious specification + - 3 :: Ambiguous specification - 4 :: Key is stored on a smartcard. *** PROGRESS [] diff --git a/doc/HACKING b/doc/HACKING index 17c58269b..4781bf62c 100644 --- a/doc/HACKING +++ b/doc/HACKING @@ -150,7 +150,7 @@ Note that such a comment will be removed if the git commit option if ( 42 == foo ) #+end_src this is harder to read and modern compilers are pretty good in - detecing accidential assignments. It is also suggested not to + detecing accidental assignments. It is also suggested not to compare to 0 or NULL but to test the value direct or with a '!'; this makes it easier to see that a boolean test is done. - We use our own printf style functions like =es_printf=, and @@ -342,7 +342,7 @@ Note that such a comment will be removed if the git commit option - g10/main.h :: Prototypes and some constants - g10/mainproc.c :: Message processing - g10/armor.c :: Ascii armor filter - - g10/mdfilter.c :: Filter to calculate hashs + - g10/mdfilter.c :: Filter to calculate hashes - g10/textfilter.c :: Filter to handle CR/LF and trailing white space - g10/cipher.c :: En-/Decryption filter - g10/misc.c :: Utility functions @@ -395,7 +395,7 @@ The *secure versions allocate memory in the secure memory. That is, swapping out of this memory is avoided and is gets overwritten on free. Use this for passphrases, session keys and other sensitive material. This memory set aside for secure memory is linited to a few -k. In general the function don't print a memeory message and +k. In general the function don't print a memory message and terminate the process if there is not enough memory available. The "try" versions of the functions return NULL instead. diff --git a/doc/faq.org b/doc/faq.org index ddbeafaf8..2f873e600 100644 --- a/doc/faq.org +++ b/doc/faq.org @@ -1096,7 +1096,7 @@ update this FAQ in the next month. See the section "Changes" for recent updates As of 1.0.3, keys generated with gpg are created with preferences to TWOFISH (and AES since 1.0.4) and that also means that they have the capability to use the new MDC encryption method. This will go into - OpenPGP soon, and is also suppoted by PGP 7. This new method avoids + OpenPGP soon, and is also supported by PGP 7. This new method avoids a (not so new) attack on all email encryption systems. This in turn means that pre-1.0.3 gpg binaries have problems with diff --git a/doc/tools.texi b/doc/tools.texi index 7becf67e2..6256c05ed 100644 --- a/doc/tools.texi +++ b/doc/tools.texi @@ -1561,7 +1561,7 @@ string @code{true} or @code{yes}. The evaluation is done by passing /subst /let i 3 /while $i - /echo loop couter is $i + /echo loop counter is $i /let i $@{- $i 1@} /end @end smallexample @@ -1962,7 +1962,7 @@ Extract all files from an encrypted archive. @item --sign @itemx -s -Make a signed archive from the given files and directories. Thsi can +Make a signed archive from the given files and directories. This can be combined with option @option{--encrypt} to create a signed and then encrypted archive. @@ -2031,7 +2031,7 @@ linefeed to separate file names. @item --openpgp @opindex openpgp -This option has no effect becuase OpenPGP encryption and signing is +This option has no effect because OpenPGP encryption and signing is the default. @item --cms diff --git a/doc/wks.texi b/doc/wks.texi index 4508ae2a1..51b86ca4a 100644 --- a/doc/wks.texi +++ b/doc/wks.texi @@ -61,7 +61,7 @@ Service provider. This is usuallay done to upload a key into a Web Key Directory. With the @option{--supported} command the caller can test whether a -site supports the Web Key Service. The argument is an arbitray +site supports the Web Key Service. The argument is an arbitrary address in the to be tested domain. For example @file{foo@@example.net}. The command returns success if the Web Key Service is supported. The operation is silent; to get diagnostic @@ -206,7 +206,7 @@ mail is processed. Commonly this command is used with the option @option{--send} to directly send the crerated mails back. See below for an installation example. -The command @option{--cron} is used for regualr cleanup tasks. For +The command @option{--cron} is used for regular cleanup tasks. For example non-confirmed requested should be removed after their expire time. It is best to run this command once a day from a cronjob. diff --git a/doc/yat2m.c b/doc/yat2m.c index c7bec338f..be0ef17fd 100644 --- a/doc/yat2m.c +++ b/doc/yat2m.c @@ -55,7 +55,7 @@ .B whateever you want @end ifset - alternativly a special comment may be used: + alternatively a special comment may be used: @c man:.B whatever you want @@ -704,7 +704,7 @@ write_th (FILE *fp) /* Process the texinfo command COMMAND (without the leading @) and - write output if needed to FP. REST is the remainer of the line + write output if needed to FP. REST is the remainder of the line which should either point to an opening brace or to a white space. The function returns the number of characters already processed from REST. LEN is the usable length of REST. TABLE_LEVEL is used to @@ -1197,7 +1197,7 @@ parse_file (const char *fname, FILE *fp, char **section_name, int in_pause) if (*p == '@' && !strncmp (p+1, "item", 4)) item_indent = p - line; /* Set a new indent level. */ else if (p - line < item_indent) - item_indent = 0; /* Switch off indention. */ + item_indent = 0; /* Switch off indentation. */ if (item_indent) { From 54eb375ff14e2a93cea70eab35719be4d25f51ca Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 24 Oct 2018 15:56:18 -0400 Subject: [PATCH 038/235] all: fix spelling and typos Signed-off-by: Daniel Kahn Gillmor --- agent/command-ssh.c | 2 +- agent/divert-scd.c | 2 +- agent/gpg-agent.c | 6 +++--- agent/learncard.c | 2 +- agent/protect.c | 2 +- common/argparse.c | 6 +++--- common/convert.c | 2 +- common/dotlock.c | 2 +- common/iobuf.c | 4 ++-- common/openpgp-oid.c | 4 ++-- common/percent.c | 2 +- common/sexputil.c | 2 +- common/simple-pwquery.c | 2 +- common/stringhelp.c | 2 +- common/t-exechelp.c | 2 +- dirmngr/cdblib.c | 6 +++--- dirmngr/crlcache.c | 4 ++-- dirmngr/dirmngr.c | 8 ++++---- dirmngr/dirmngr_ldap.c | 4 ++-- dirmngr/dns-stuff.c | 2 +- dirmngr/dns.c | 4 ++-- dirmngr/http-ntbtls.c | 2 +- dirmngr/ks-action.c | 2 +- dirmngr/ks-engine-hkp.c | 2 +- dirmngr/ldap.c | 2 +- dirmngr/misc.c | 2 +- dirmngr/ocsp.c | 6 +++--- dirmngr/t-http.c | 2 +- dirmngr/workqueue.c | 2 +- g10/armor.c | 2 +- g10/call-dirmngr.c | 2 +- g10/card-util.c | 2 +- g10/cpr.c | 2 +- g10/decrypt-data.c | 2 +- g10/encrypt.c | 4 ++-- g10/export.c | 2 +- g10/getkey.c | 4 ++-- g10/gpg.c | 6 +++--- g10/import.c | 2 +- g10/key-clean.c | 4 ++-- g10/keygen.c | 2 +- g10/mainproc.c | 2 +- g10/misc.c | 4 ++-- g10/parse-packet.c | 4 ++-- g10/seskey.c | 4 ++-- g10/sign.c | 2 +- g10/tdbio.c | 6 +++--- g10/trustdb.c | 2 +- g10/verify.c | 2 +- g13/call-syshelp.c | 2 +- g13/mountinfo.c | 2 +- g13/runner.c | 2 +- kbx/keybox-blob.c | 2 +- kbx/keybox-errors.c | 2 +- kbx/keybox-search.c | 2 +- scd/apdu.c | 2 +- scd/app-dinsig.c | 2 +- scd/app-geldkarte.c | 2 +- scd/app-openpgp.c | 2 +- scd/ccid-driver.c | 6 +++--- scd/iso7816.c | 4 ++-- scd/scdaemon.c | 2 +- sm/call-dirmngr.c | 2 +- sm/certchain.c | 8 ++++---- sm/certlist.c | 4 ++-- sm/certreqgen-ui.c | 2 +- sm/gpgsm.c | 2 +- sm/keydb.c | 2 +- sm/minip12.c | 6 +++--- sm/qualified.c | 2 +- tests/asschk.c | 10 +++++----- tools/gpg-check-pattern.c | 2 +- tools/gpgconf.c | 2 +- tools/mime-parser.c | 2 +- tools/no-libgcrypt.c | 2 +- tools/rfc822parse.c | 10 +++++----- tools/wks-util.c | 2 +- 77 files changed, 120 insertions(+), 120 deletions(-) diff --git a/agent/command-ssh.c b/agent/command-ssh.c index 86fd092ff..8a4150539 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -2751,7 +2751,7 @@ data_hash (unsigned char *data, size_t data_n, allow the use of signature algorithms that implement the hashing internally (e.g. Ed25519). On success the created signature is stored in ssh format at R_SIG and it's size at R_SIGLEN; the caller - must use es_free to releaase this memory. */ + must use es_free to release this memory. */ static gpg_error_t data_sign (ctrl_t ctrl, ssh_key_type_spec_t *spec, const void *hash, size_t hashlen, diff --git a/agent/divert-scd.c b/agent/divert-scd.c index baa1cc3eb..b9781547d 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -195,7 +195,7 @@ has_percent0A_suffix (const char *string) string with the passphrase, the buffer may optionally be padded with arbitrary characters. - If DESC_TEXT is not NULL it can be used as further informtion shown + If DESC_TEXT is not NULL it can be used as further information shown atop of the INFO message. INFO gets displayed as part of a generic string. However if the diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 1fdc94d0f..911064cdc 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -851,7 +851,7 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) case oLogFile: if (!reread) - return 0; /* not handeld */ + return 0; /* not handled */ if (!current_logfile || !pargs->r.ret_str || strcmp (current_logfile, pargs->r.ret_str)) { @@ -1768,7 +1768,7 @@ main (int argc, char **argv ) /* 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. */ + * we can avoid the regular stat calls. */ if (!argc) reliable_homedir_inotify = 1; } @@ -2108,7 +2108,7 @@ get_agent_scd_notify_event (void) GetCurrentProcess(), &h2, EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0)) { - log_error ("setting syncronize for scd notify event failed: %s\n", + log_error ("setting synchronize for scd notify event failed: %s\n", w32_strerror (-1) ); CloseHandle (h); } diff --git a/agent/learncard.c b/agent/learncard.c index abe1dd0bf..f3219ed8f 100644 --- a/agent/learncard.c +++ b/agent/learncard.c @@ -40,7 +40,7 @@ struct keypair_info_s char hexgrip[1]; /* The keygrip (i.e. a hash over the public key parameters) formatted as a hex string. Allocated somewhat large to also act as - memeory for the above ID field. */ + memory for the above ID field. */ }; typedef struct keypair_info_s *KEYPAIR_INFO; diff --git a/agent/protect.c b/agent/protect.c index 16ae715e1..c7bd30b68 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -1109,7 +1109,7 @@ agent_unprotect (ctrl_t ctrl, if (!protect_info[infidx].algo) return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); - /* See wether we have a protected-at timestamp. */ + /* See whether we have a protected-at timestamp. */ protect_list = s; /* Save for later. */ if (protected_at) { diff --git a/common/argparse.c b/common/argparse.c index 331998bb2..db0b7e079 100644 --- a/common/argparse.c +++ b/common/argparse.c @@ -408,7 +408,7 @@ static void store_alias( ARGPARSE_ARGS *arg, char *name, char *value ) { /* TODO: replace this dummy function with a rea one - * and fix the probelms IRIX has with (ALIAS_DEV)arg.. + * and fix the problems IRIX has with (ALIAS_DEV)arg.. * used as lvalue */ (void)arg; @@ -439,7 +439,7 @@ ignore_invalid_option_p (ARGPARSE_ARGS *arg, const char *keyword) /* Add the keywords up to the next LF to the list of to be ignored options. After returning FP will either be at EOF or the next - character read wll be the first of a new line. The function + character read will be the first of a new line. The function returns 0 on success or true on malloc failure. */ static int ignore_invalid_option_add (ARGPARSE_ARGS *arg, FILE *fp) @@ -1280,7 +1280,7 @@ long_opt_strlen( ARGPARSE_OPTS *o ) * this option * - a description,ine which starts with a '@' and is followed by * any other characters is printed as is; this may be used for examples - * ans such. + * and such. * - A description which starts with a '|' outputs the string between this * bar and the next one as arguments of the long option. */ diff --git a/common/convert.c b/common/convert.c index 6d03adc3d..40fb4eecf 100644 --- a/common/convert.c +++ b/common/convert.c @@ -177,7 +177,7 @@ bin2hexcolon (const void *buffer, size_t length, char *stringbuf) string or a white space character. The function makes sure that the resulting string in BUFFER is terminated by a Nul byte. Note that the returned string may include embedded Nul bytes; the extra - Nul byte at the end is used to make sure tha the result can always + Nul byte at the end is used to make sure that the result can always be used as a C-string. BUFSIZE is the available length of BUFFER; if the converted result diff --git a/common/dotlock.c b/common/dotlock.c index 5227bb64e..1bc31d8a6 100644 --- a/common/dotlock.c +++ b/common/dotlock.c @@ -140,7 +140,7 @@ you pass (0) instead of (-1) the function does not wait in case the file is already locked but returns -1 and sets ERRNO to EACCES. Any other positive value for the second parameter is considered a - timeout valuie in milliseconds. + timeout value in milliseconds. To release the lock you call: diff --git a/common/iobuf.c b/common/iobuf.c index 8f52f7f5c..18a458e0a 100644 --- a/common/iobuf.c +++ b/common/iobuf.c @@ -68,8 +68,8 @@ /*-- End configurable part. --*/ -/* The size of the iobuffers. This can be chnages using the - * iobuf_set_buffer_size fucntion. */ +/* The size of the iobuffers. This can be changed using the + * iobuf_set_buffer_size function. */ static unsigned int iobuf_buffer_size = DEFAULT_IOBUF_BUFFER_SIZE; diff --git a/common/openpgp-oid.c b/common/openpgp-oid.c index d800e7d57..86885e0aa 100644 --- a/common/openpgp-oid.c +++ b/common/openpgp-oid.c @@ -184,7 +184,7 @@ openpgp_oid_from_str (const char *string, gcry_mpi_t *r_mpi) } -/* Return a malloced string represenation of the OID in the opaque MPI +/* Return a malloced string representation of the OID in the opaque MPI A. In case of an error NULL is returned and ERRNO is set. */ char * openpgp_oid_to_str (gcry_mpi_t a) @@ -221,7 +221,7 @@ openpgp_oid_to_str (gcry_mpi_t a) /* To calculate the length of the string we can safely assume an upper limit of 3 decimal characters per byte. Two extra bytes - account for the special first octect */ + account for the special first octet */ string = p = xtrymalloc (length*(1+3)+2+1); if (!string) return NULL; diff --git a/common/percent.c b/common/percent.c index eeb026fbe..7b817684d 100644 --- a/common/percent.c +++ b/common/percent.c @@ -42,7 +42,7 @@ failure. Note that we also escape the quote character to work around a bug - in the mingw32 runtime which does not correcty handle command line + in the mingw32 runtime which does not correctly handle command line quoting. We correctly double the quote mark when calling a program (i.e. gpg-protect-tool), but the pre-main code does not notice the double quote as an escaped quote. We do this also on POSIX systems diff --git a/common/sexputil.c b/common/sexputil.c index f30790aa1..02e52d0ed 100644 --- a/common/sexputil.c +++ b/common/sexputil.c @@ -303,7 +303,7 @@ make_simple_sexp_from_hexstr (const char *line, size_t *nscanned) for (; n > 1; n -=2, s += 2) *p++ = xtoi_2 (s); *p++ = ')'; - *p = 0; /* (Not really neaded.) */ + *p = 0; /* (Not really needed.) */ return buf; } diff --git a/common/simple-pwquery.c b/common/simple-pwquery.c index b8ada4214..7688c846d 100644 --- a/common/simple-pwquery.c +++ b/common/simple-pwquery.c @@ -75,7 +75,7 @@ /* Name of the socket to be used. This is a kludge to keep on using - the existsing code despite that we only support a standard socket. */ + the existing code despite that we only support a standard socket. */ static char *default_gpg_agent_info; diff --git a/common/stringhelp.c b/common/stringhelp.c index 0abac8ae5..751e5711f 100644 --- a/common/stringhelp.c +++ b/common/stringhelp.c @@ -1400,7 +1400,7 @@ parse_version_number (const char *s, int *number) /* This function breaks up the complete string-representation of the - version number S, which is of the following struture: .[.]. The major, minor, and micro number components will be stored in *MAJOR, *MINOR and *MICRO. If MICRO is not given 0 is used instead. diff --git a/common/t-exechelp.c b/common/t-exechelp.c index cf967fcc7..3bf082bbb 100644 --- a/common/t-exechelp.c +++ b/common/t-exechelp.c @@ -131,7 +131,7 @@ test_close_all_fds (void) free (array); /* Now let's check the realloc we use. We do this and the next - tests only if we are allowed to open enought descriptors. */ + tests only if we are allowed to open enough descriptors. */ if (get_max_fds () > 32) { int except[] = { 20, 23, 24, -1 }; diff --git a/dirmngr/cdblib.c b/dirmngr/cdblib.c index 827399f7e..c40126396 100644 --- a/dirmngr/cdblib.c +++ b/dirmngr/cdblib.c @@ -19,7 +19,7 @@ length, meaning that corresponding hash table is empty. Right after toc section, data section follows without any - alingment. It consists of series of records, each is a key length, + alignment. It consists of series of records, each is a key length, value (data) length, key and value. Again, key and value length are 4-byte unsigned integers. Each next record follows previous without any special alignment. @@ -52,7 +52,7 @@ beginning of a table). When hash value in question is found in hash table, look to key of corresponding record, comparing it with key in question. If them of the same length and equals to each - other, then record is found, overwise, repeat with next hash table + other, then record is found, otherwise, repeat with next hash table slot. Note that there may be several records with the same key. */ @@ -245,7 +245,7 @@ cdb_find(struct cdb *cdbp, const void *key, cdbi_t klen) pos = cdb_unpack(htp); /* htab position */ if (n > (cdbp->cdb_fsize >> 3) /* overflow of httodo ? */ || pos > cdbp->cdb_fsize /* htab start within file ? */ - || httodo > cdbp->cdb_fsize - pos) /* entrie htab within file ? */ + || httodo > cdbp->cdb_fsize - pos) /* htab entry within file ? */ { gpg_err_set_errno (EPROTO); return -1; diff --git a/dirmngr/crlcache.c b/dirmngr/crlcache.c index fbe3beea1..a2867be54 100644 --- a/dirmngr/crlcache.c +++ b/dirmngr/crlcache.c @@ -1782,7 +1782,7 @@ crl_parse_insert (ctrl_t ctrl, ksba_crl_t crl, ksba_sexp_t keyid; /* We need to look for the issuer only after having read - all items. The issuer itselfs comes before the items + all items. The issuer itself comes before the items but the optional authorityKeyIdentifier comes after the items. */ err = ksba_crl_get_issuer (crl, &crlissuer); @@ -1907,7 +1907,7 @@ get_crl_number (ksba_crl_t crl) /* Return the authorityKeyIdentifier or NULL if it is not available. - The issuer name may consists of several parts - they are delimted by + The issuer name may consists of several parts - they are delimited by 0x01. */ static char * get_auth_key_id (ksba_crl_t crl, char **serialno) diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index 1696be578..80fb13476 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -338,7 +338,7 @@ static int active_connections; * thread to run background network tasks. */ static int network_activity_seen; -/* A list of filenames registred with --hkp-cacert. */ +/* A list of filenames registered with --hkp-cacert. */ static strlist_t hkp_cacert_filenames; @@ -411,7 +411,7 @@ my_strusage( int level ) /* Callback from libksba to hash a provided buffer. Our current implementation does only allow SHA-1 for hashing. This may be - extended by mapping the name, testing for algorithm availibility + extended by mapping the name, testing for algorithm availability and adjust the length checks accordingly. */ static gpg_error_t my_ksba_hash_buffer (void *arg, const char *oid, @@ -520,7 +520,7 @@ set_tor_mode (void) { if (dirmngr_use_tor ()) { - /* Enable Tor mode and when called again force a new curcuit + /* Enable Tor mode and when called again force a new circuit * (e.g. on SIGHUP). */ enable_dns_tormode (1); if (assuan_sock_set_flag (ASSUAN_INVALID_FD, "tor-mode", 1)) @@ -752,7 +752,7 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) } -/* This fucntion is called after option parsing to adjust some values +/* This function is called after option parsing to adjust some values * and call option setup functions. */ static void post_option_parsing (void) diff --git a/dirmngr/dirmngr_ldap.c b/dirmngr/dirmngr_ldap.c index 8452c3ba0..dd7e4bda5 100644 --- a/dirmngr/dirmngr_ldap.c +++ b/dirmngr/dirmngr_ldap.c @@ -417,9 +417,9 @@ set_timeout (my_opt_t myopt) sec_attr.nLength = sizeof sec_attr; sec_attr.bInheritHandle = FALSE; - /* Create a manual resetable timer. */ + /* Create a manual resettable timer. */ timer = CreateWaitableTimer (NULL, TRUE, NULL); - /* Intially set the timer. */ + /* Initially set the timer. */ SetWaitableTimer (timer, &due_time, 0, NULL, NULL, 0); if (CreateThread (&sec_attr, 0, alarm_thread, timer, 0, &tid)) diff --git a/dirmngr/dns-stuff.c b/dirmngr/dns-stuff.c index 09b17c0fb..f86ccb0ae 100644 --- a/dirmngr/dns-stuff.c +++ b/dirmngr/dns-stuff.c @@ -151,7 +151,7 @@ static char tor_socks_password[20]; #ifdef USE_LIBDNS -/* Libdns gobal data. */ +/* Libdns global data. */ struct libdns_s { struct dns_resolv_conf *resolv_conf; diff --git a/dirmngr/dns.c b/dirmngr/dns.c index 77f83f437..596e81fc9 100644 --- a/dirmngr/dns.c +++ b/dirmngr/dns.c @@ -1832,7 +1832,7 @@ static void dns_p_free(struct dns_packet *P) { } /* dns_p_free() */ -/* convience routine to free any existing packet before storing new packet */ +/* convenience routine to free any existing packet before storing new packet */ static struct dns_packet *dns_p_setptr(struct dns_packet **dst, struct dns_packet *src) { dns_p_free(*dst); @@ -7634,7 +7634,7 @@ retry: goto udp_connect_retry; } else if (error == ECONNREFUSED) /* Error for previous socket operation may - be reserverd asynchronously. */ + be reserverd(?) asynchronously. */ goto udp_connect_retry; if (error) diff --git a/dirmngr/http-ntbtls.c b/dirmngr/http-ntbtls.c index ed4cdd496..924b8b25f 100644 --- a/dirmngr/http-ntbtls.c +++ b/dirmngr/http-ntbtls.c @@ -55,7 +55,7 @@ gnupg_http_tls_verify_cb (void *opaque, log_assert (ctrl && ctrl->magic == SERVER_CONTROL_MAGIC); log_assert (!ntbtls_check_context (tls)); - /* Get the peer's certs fron ntbtls. */ + /* Get the peer's certs from ntbtls. */ for (idx = 0; (cert = ntbtls_x509_get_peer_cert (tls, idx)); idx++) { diff --git a/dirmngr/ks-action.c b/dirmngr/ks-action.c index c1ecafb58..3651ca7db 100644 --- a/dirmngr/ks-action.c +++ b/dirmngr/ks-action.c @@ -88,7 +88,7 @@ ks_action_help (ctrl_t ctrl, const char *url) return err; } - /* Call all engines to give them a chance to print a help sting. */ + /* Call all engines to give them a chance to print a help string. */ err = ks_hkp_help (ctrl, parsed_uri); if (!err) err = ks_http_help (ctrl, parsed_uri); diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c index 31fa77284..790a8b1c5 100644 --- a/dirmngr/ks-engine-hkp.c +++ b/dirmngr/ks-engine-hkp.c @@ -1381,7 +1381,7 @@ handle_send_request_error (ctrl_t ctrl, gpg_error_t err, const char *request, int retry = 0; /* Fixme: Should we disable all hosts of a protocol family if a - * request for an address of that familiy returned ENETDOWN? */ + * request for an address of that family returned ENETDOWN? */ switch (gpg_err_code (err)) { diff --git a/dirmngr/ldap.c b/dirmngr/ldap.c index cb3c0b763..a04bb97a2 100644 --- a/dirmngr/ldap.c +++ b/dirmngr/ldap.c @@ -388,7 +388,7 @@ parse_one_pattern (const char *pattern) } /* Take the string STRING and escape it according to the URL rules. - Retun a newly allocated string. */ + Return a newly allocated string. */ static char * escape4url (const char *string) { diff --git a/dirmngr/misc.c b/dirmngr/misc.c index 1270b834d..9ad60d59f 100644 --- a/dirmngr/misc.c +++ b/dirmngr/misc.c @@ -637,7 +637,7 @@ armor_data (char **r_string, const void *data, size_t datalen) } -/* Copy all data from IN to OUT. OUT may be NULL to use this fucntion +/* Copy all data from IN to OUT. OUT may be NULL to use this function * as a dummy reader. */ gpg_error_t copy_stream (estream_t in, estream_t out) diff --git a/dirmngr/ocsp.c b/dirmngr/ocsp.c index 966eda304..79c252d87 100644 --- a/dirmngr/ocsp.c +++ b/dirmngr/ocsp.c @@ -343,7 +343,7 @@ validate_responder_cert (ctrl_t ctrl, ksba_cert_t cert, Note, that in theory we could simply ask the client via an inquire to validate a certificate but this might involve - calling DirMngr again recursivly - we can't do that as of now + calling DirMngr again recursively - we can't do that as of now (neither DirMngr nor gpgsm have the ability for concurrent access to DirMngr. */ @@ -391,7 +391,7 @@ check_signature_core (ctrl_t ctrl, ksba_cert_t cert, gcry_sexp_t s_sig, } -/* Check the signature of an OCSP repsonse. OCSP is the context, +/* Check the signature of an OCSP response. OCSP is the context, S_SIG the signature value and MD the handle of the hash we used for the response. This function automagically finds the correct public key. If SIGNER_FPR_LIST is not NULL, the default OCSP reponder has been @@ -788,7 +788,7 @@ ocsp_isvalid (ctrl_t ctrl, ksba_cert_t cert, const char *cert_fpr, err = gpg_error (GPG_ERR_TIME_CONFLICT); } - /* Check that we are not beyound NEXT_UPDATE (plus some extra time). */ + /* Check that we are not beyond NEXT_UPDATE (plus some extra time). */ if (*next_update) { gnupg_copy_time (tmp_time, next_update); diff --git a/dirmngr/t-http.c b/dirmngr/t-http.c index 2fc0a465c..8b32613b6 100644 --- a/dirmngr/t-http.c +++ b/dirmngr/t-http.c @@ -137,7 +137,7 @@ my_http_tls_verify_cb (void *opaque, (void)session; (void)http_flags; - /* Get the peer's certs fron ntbtls. */ + /* Get the peer's certs from ntbtls. */ for (idx = 0; (cert = ntbtls_x509_get_peer_cert (tls_context, idx)); idx++) { diff --git a/dirmngr/workqueue.c b/dirmngr/workqueue.c index 2cb8573e8..a47cdebc8 100644 --- a/dirmngr/workqueue.c +++ b/dirmngr/workqueue.c @@ -116,7 +116,7 @@ workqueue_add_task (wqtask_t func, const char *args, unsigned int session_id, /* Run the task described by ITEM. ITEM must have been detached from - * the workqueue; its ownership is transferred to this fucntion. */ + * the workqueue; its ownership is transferred to this function. */ static void run_a_task (ctrl_t ctrl, wqitem_t item) { diff --git a/g10/armor.c b/g10/armor.c index cc8096862..98b870ab9 100644 --- a/g10/armor.c +++ b/g10/armor.c @@ -1,4 +1,4 @@ -/* armor.c - Armor flter +/* armor.c - Armor filter * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, * 2007 Free Software Foundation, Inc. * diff --git a/g10/call-dirmngr.c b/g10/call-dirmngr.c index 388da3d63..8f83c087f 100644 --- a/g10/call-dirmngr.c +++ b/g10/call-dirmngr.c @@ -1083,7 +1083,7 @@ ks_put_inq_cb (void *opaque, const char *line) /* Send a key to the configured server. {DATA,DATLEN} contains the key in OpenPGP binary transport format. If KEYBLOCK is not NULL it - has the internal representaion of that key; this is for example + has the internal representation of that key; this is for example used to convey meta data to LDAP keyservers. */ gpg_error_t gpg_dirmngr_ks_put (ctrl_t ctrl, void *data, size_t datalen, kbnode_t keyblock) diff --git a/g10/card-util.c b/g10/card-util.c index 111dfbf2c..234abcbab 100644 --- a/g10/card-util.c +++ b/g10/card-util.c @@ -648,7 +648,7 @@ current_card_status (ctrl_t ctrl, estream_t fp, info.fpr3len? info.fpr3 : NULL); thefprlen = (info.fpr1len? info.fpr1len : info.fpr2len? info.fpr2len : info.fpr3len? info.fpr3len : 0); - /* If the fingerprint is all 0xff, the key has no asssociated + /* If the fingerprint is all 0xff, the key has no associated OpenPGP certificate. */ if ( thefpr && !fpr_is_ff (thefpr, thefprlen) && !get_pubkey_byfprint (ctrl, pk, &keyblock, thefpr, thefprlen)) diff --git a/g10/cpr.c b/g10/cpr.c index 435442636..ed68b3f15 100644 --- a/g10/cpr.c +++ b/g10/cpr.c @@ -187,7 +187,7 @@ write_status_text (int no, const char *text) } -/* Write a status line with code NO followed by the outout of the +/* Write a status line with code NO followed by the output of the * printf style FORMAT. The caller needs to make sure that LFs and * CRs are not printed. */ void diff --git a/g10/decrypt-data.c b/g10/decrypt-data.c index a3151b5ed..3951fa794 100644 --- a/g10/decrypt-data.c +++ b/g10/decrypt-data.c @@ -42,7 +42,7 @@ static int decode_filter ( void *opaque, int control, IOBUF a, /* Our context object. */ struct decode_filter_context_s { - /* Recounter (max value is 2). We need it becuase we do not know + /* Recounter (max value is 2). We need it because we do not know * whether the iobuf or the outer control code frees this object * first. */ int refcount; diff --git a/g10/encrypt.c b/g10/encrypt.c index 04a9ab214..972d13c7c 100644 --- a/g10/encrypt.c +++ b/g10/encrypt.c @@ -732,7 +732,7 @@ encrypt_crypt (ctrl_t ctrl, int filefd, const char *filename, /* In case 3DES has been selected, print a warning if any key does not have a preference for AES. This should help to - indentify why encrypting to several recipients falls back to + identify why encrypting to several recipients falls back to 3DES. */ if (opt.verbose && cfx.dek->algo == CIPHER_ALGO_3DES) warn_missing_aes_from_pklist (pk_list); @@ -1003,7 +1003,7 @@ encrypt_filter (void *opaque, int control, /* In case 3DES has been selected, print a warning if any key does not have a preference for AES. This - should help to indentify why encrypting to several + should help to identify why encrypting to several recipients falls back to 3DES. */ if (opt.verbose && efx->cfx.dek->algo == CIPHER_ALGO_3DES) diff --git a/g10/export.c b/g10/export.c index b140e28cb..9477b7526 100644 --- a/g10/export.c +++ b/g10/export.c @@ -1179,7 +1179,7 @@ print_status_exported (PKT_public_key *pk) * passphrase-protected. Otherwise, store secret key material in the * clear. * - * CACHE_NONCE_ADDR is used to share nonce for multple key retrievals. + * CACHE_NONCE_ADDR is used to share nonce for multiple key retrievals. */ gpg_error_t receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd, diff --git a/g10/getkey.c b/g10/getkey.c index ea2dee21d..75ce9cb38 100644 --- a/g10/getkey.c +++ b/g10/getkey.c @@ -60,7 +60,7 @@ struct getkey_ctx_s search or not. A search that is exact requires that a key or subkey meet all of the specified criteria. A search that is not exact allows selecting a different key or subkey from the - keyblock that matched the critera. Further, an exact search + keyblock that matched the criteria. Further, an exact search returns the key or subkey that matched whereas a non-exact search typically returns the primary key. See finish_lookup for details. */ @@ -3364,7 +3364,7 @@ merge_selfsigs (ctrl_t ctrl, kbnode_t keyblock) * * 1. No requested usage and no primary key requested * Examples for this case are that we have a keyID to be used - * for decrytion or verification. + * for decryption or verification. * 2. No usage but primary key requested * This is the case for all functions which work on an * entire keyblock, e.g. for editing or listing diff --git a/g10/gpg.c b/g10/gpg.c index f04a3400a..aeb9c6fc0 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -3891,7 +3891,7 @@ main (int argc, char **argv) keygen_set_std_prefs(pers_compress_list,PREFTYPE_ZIP)) log_error(_("invalid personal compress preferences\n")); - /* Check chunk size. Please fix also the man page if you chnage + /* Check chunk size. Please fix also the man page if you change * the default. The limits are given by the specs. */ if (!opt.chunk_size) opt.chunk_size = 27; /* Default to the suggested max of 128 MiB. */ @@ -4875,9 +4875,9 @@ main (int argc, char **argv) while( endless || count ) { byte *p; - /* Wee need a multiple of 3, so that in case of + /* We need a multiple of 3, so that in case of armored output we get a correct string. No - linefolding is done, as it is best to levae this to + linefolding is done, as it is best to leave this to other tools */ size_t n = !endless && count < 99? count : 99; diff --git a/g10/import.c b/g10/import.c index 1f334dcb8..6fdd53e85 100644 --- a/g10/import.c +++ b/g10/import.c @@ -1812,7 +1812,7 @@ import_one (ctrl_t ctrl, } } - /* Delete invalid parts and without the drop otions bail out if + /* Delete invalid parts and without the drop option bail out if * there are no user ids. */ if (!delete_inv_parts (ctrl, keyblock, keyid, options) && !(options & IMPORT_DROP_UIDS) ) diff --git a/g10/key-clean.c b/g10/key-clean.c index f66a0dbb4..d701a6665 100644 --- a/g10/key-clean.c +++ b/g10/key-clean.c @@ -500,7 +500,7 @@ clean_one_subkey_dupsigs (ctrl_t ctrl, kbnode_t subkeynode) log_debug ("\tchecking subkey %08lX for dupsigs\n", (ulong) keyid_from_pk (pk, NULL)); - /* First check that the choosen flag has been set. Note that we + /* First check that the chosen flag has been set. Note that we * only look at plain signatures so to keep all revocation * signatures which may carry important information. */ for (node = subkeynode->next; @@ -519,7 +519,7 @@ clean_one_subkey_dupsigs (ctrl_t ctrl, kbnode_t subkeynode) } if (!any_choosen) - return 0; /* Ooops no choosen flag set - we can't decide. */ + return 0; /* Ooops no chosen flag set - we can't decide. */ for (node = subkeynode->next; node && !(node->pkt->pkttype == PKT_PUBLIC_SUBKEY diff --git a/g10/keygen.c b/g10/keygen.c index 817704040..145b871b0 100644 --- a/g10/keygen.c +++ b/g10/keygen.c @@ -450,7 +450,7 @@ keygen_set_std_prefs (const char *string,int personal) } /* In case we have no compress algo at all, declare that - we prefer no compresssion. */ + we prefer no compression. */ if (!any_compress) strcat(dummy_string,"Z0 "); diff --git a/g10/mainproc.c b/g10/mainproc.c index 6ec15894d..5b7bc9555 100644 --- a/g10/mainproc.c +++ b/g10/mainproc.c @@ -811,7 +811,7 @@ proc_plaintext( CTX c, PACKET *pkt ) int any, clearsig, rc; kbnode_t n; - /* This is a literal data packet. Bumb a counter for later checks. */ + /* This is a literal data packet. Bump a counter for later checks. */ literals_seen++; if (pt->namelen == 8 && !memcmp( pt->name, "_CONSOLE", 8)) diff --git a/g10/misc.c b/g10/misc.c index 89b21e257..f129e8327 100644 --- a/g10/misc.c +++ b/g10/misc.c @@ -549,7 +549,7 @@ openpgp_cipher_blocklen (cipher_algo_t algo) /**************** * Wrapper around the libgcrypt function with additional checks on - * the OpenPGP contraints for the algo ID. + * the OpenPGP constraints for the algo ID. */ int openpgp_cipher_test_algo (cipher_algo_t algo) @@ -712,7 +712,7 @@ openpgp_pk_test_algo2 (pubkey_algo_t algo, unsigned int use) #endif case PUBKEY_ALGO_ELGAMAL: - /* Dont't allow type 20 keys unless in rfc2440 mode. */ + /* Don't allow type 20 keys unless in rfc2440 mode. */ if (RFC2440) ga = GCRY_PK_ELG; break; diff --git a/g10/parse-packet.c b/g10/parse-packet.c index 78413e49a..c0f2ca12e 100644 --- a/g10/parse-packet.c +++ b/g10/parse-packet.c @@ -2351,7 +2351,7 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, { log_error ("packet(%d) too large\n", pkttype); if (list_mode) - es_fputs (":key packet: [too larget]\n", listfp); + es_fputs (":key packet: [too large]\n", listfp); err = gpg_error (GPG_ERR_INV_PACKET); goto leave; } @@ -3180,7 +3180,7 @@ parse_plaintext (IOBUF inp, int pkttype, unsigned long pktlen, 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 + * reading from uninitialized memory. This case may be triggred by * corrupted packets. */ for (; i < namelen; i++) pt->name[i] = 0; diff --git a/g10/seskey.c b/g10/seskey.c index 15490179d..fb71ad5cd 100644 --- a/g10/seskey.c +++ b/g10/seskey.c @@ -95,7 +95,7 @@ encode_session_key (int openpgp_pk_algo, DEK *dek, unsigned int nbits) output be a multiple of 8 bytes. */ if (openpgp_pk_algo == PUBKEY_ALGO_ECDH) { - /* Pad to 8 byte granulatiry; the padding byte is the number of + /* Pad to 8 byte granularity; the padding byte is the number of * padded bytes. * * A DEK(k bytes) CSUM(2 bytes) 0x 0x 0x 0x ... 0x @@ -143,7 +143,7 @@ encode_session_key (int openpgp_pk_algo, DEK *dek, unsigned int nbits) * * 0 2 RND(i bytes) 0 A DEK(k bytes) CSUM(2 bytes) * - * (But how can we store the leading 0 - the external representaion + * (But how can we store the leading 0 - the external representation * of MPIs doesn't allow leading zeroes =:-) * * RND are (at least 1) non-zero random bytes. diff --git a/g10/sign.c b/g10/sign.c index 213fd0145..df888136f 100644 --- a/g10/sign.c +++ b/g10/sign.c @@ -53,7 +53,7 @@ static int recipient_digest_algo=0; /* - * Create notations and other stuff. It is assumed that the stings in + * Create notations and other stuff. It is assumed that the strings in * STRLIST are already checked to contain only printable data and have * a valid NAME=VALUE format. */ diff --git a/g10/tdbio.c b/g10/tdbio.c index 8f7530621..b6c38bd24 100644 --- a/g10/tdbio.c +++ b/g10/tdbio.c @@ -106,7 +106,7 @@ struct cmp_xdir_struct static char *db_name; /* The handle for locking the trustdb file and a counter to record how - * often this lock has been taken. That counter is required becuase + * often this lock has been taken. That counter is required because * dotlock does not implemen recursive locks. */ static dotlock_t lockhandle; static unsigned int is_locked; @@ -589,7 +589,7 @@ tdbio_update_version_record (ctrl_t ctrl) /* * Create and write the trustdb version record. - * This is called with the writelock activ. + * This is called with the writelock active. * Returns: 0 on success or an error code. */ static int @@ -1139,7 +1139,7 @@ upd_hashtable (ctrl_t ctrl, ulong table, byte *key, int keylen, ulong newrecnum) if (rec.r.hlst.next) { - /* read the next reord of the list. */ + /* read the next record of the list. */ rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST); if (rc) { diff --git a/g10/trustdb.c b/g10/trustdb.c index 8ef6db542..c46dc50c8 100644 --- a/g10/trustdb.c +++ b/g10/trustdb.c @@ -1506,7 +1506,7 @@ store_validation_status (ctrl_t ctrl, int depth, /* Returns a sanitized copy of the regexp (which might be "", but not NULL). */ #ifndef DISABLE_REGEX -/* Operator charactors except '.' and backslash. +/* Operator characters except '.' and backslash. See regex(7) on BSD. */ #define REGEXP_OPERATOR_CHARS "^[$()|*+?{" diff --git a/g10/verify.c b/g10/verify.c index caeb1a244..73ac4bad8 100644 --- a/g10/verify.c +++ b/g10/verify.c @@ -69,7 +69,7 @@ verify_signatures (ctrl_t ctrl, int nfiles, char **files ) * we can do it is by reading one byte from stdin and then unget * it; the problem here is that we may be reading from the * terminal (which could be detected using isatty() but won't work - * when under contol of a pty using program (e.g. expect)) and + * when under control of a pty using program (e.g. expect)) and * might get us in trouble when stdin is used for another purpose * (--passphrase-fd 0). So we have to break with the behaviour * prior to gpg 1.0.4 by assuming that case 3 is a normal diff --git a/g13/call-syshelp.c b/g13/call-syshelp.c index b160ba32d..a69573bd1 100644 --- a/g13/call-syshelp.c +++ b/g13/call-syshelp.c @@ -174,7 +174,7 @@ call_syshelp_release (ctrl_t ctrl) -/* Staus callback for call_syshelp_find_device. */ +/* Status callback for call_syshelp_find_device. */ static gpg_error_t finddevice_status_cb (void *opaque, const char *line) { diff --git a/g13/mountinfo.c b/g13/mountinfo.c index ed898b836..50cc153fa 100644 --- a/g13/mountinfo.c +++ b/g13/mountinfo.c @@ -117,7 +117,7 @@ mountinfo_del_mount (const char *container, const char *mountpoint, size_t idx; mtab_t m; - /* If a container or mountpint is givem search the RID via the + /* If a container or mountpint is given search the RID via the standard find function. */ if (container || mountpoint) { diff --git a/g13/runner.c b/g13/runner.c index 138269d21..b08d99030 100644 --- a/g13/runner.c +++ b/g13/runner.c @@ -278,7 +278,7 @@ runner_set_pid (runner_t runner, pid_t pid) } -/* Register the engine handler fucntions HANDLER and HANDLER_CLEANUP +/* Register the engine handler functions HANDLER and HANDLER_CLEANUP and its private HANDLER_DATA with RUNNER. */ void runner_set_handler (runner_t runner, diff --git a/kbx/keybox-blob.c b/kbx/keybox-blob.c index 687421219..817253590 100644 --- a/kbx/keybox-blob.c +++ b/kbx/keybox-blob.c @@ -616,7 +616,7 @@ create_blob_header (KEYBOXBLOB blob, int blobtype, int as_ephemeral) if (blobtype == KEYBOX_BLOBTYPE_X509) { /* We don't want to point to ASN.1 encoded UserIDs (DNs) but to - the utf-8 string represenation of them */ + the utf-8 string representation of them */ for (i=0; i < blob->nuids; i++ ) { if (blob->uids[i].name) diff --git a/kbx/keybox-errors.c b/kbx/keybox-errors.c index ce2b498d0..cb5a092d9 100644 --- a/kbx/keybox-errors.c +++ b/kbx/keybox-errors.c @@ -8,7 +8,7 @@ * keybox_strerror: * @err: Error code * - * This function returns a textual representaion of the given + * This function returns a textual representation of the given * errorcode. If this is an unknown value, a string with the value * is returned (Beware: it is hold in a static buffer). * diff --git a/kbx/keybox-search.c b/kbx/keybox-search.c index e309cce98..0a050ff9b 100644 --- a/kbx/keybox-search.c +++ b/kbx/keybox-search.c @@ -1069,7 +1069,7 @@ keybox_search (KEYBOX_HANDLE hd, KEYBOX_SEARCH_DESC *desc, size_t ndesc, /* Return the last found keyblock. Returns 0 on success and stores a - * new iobuf at R_IOBUF. R_UID_NO and R_PK_NO are used to retun the + * new iobuf at R_IOBUF. R_UID_NO and R_PK_NO are used to return the * number of the key or user id which was matched the search criteria; * if not known they are set to 0. */ gpg_error_t diff --git a/scd/apdu.c b/scd/apdu.c index c0b1bbea4..f3e2a12e7 100644 --- a/scd/apdu.c +++ b/scd/apdu.c @@ -2646,7 +2646,7 @@ send_apdu (int slot, unsigned char *apdu, size_t apdulen, } -/* Core APDU tranceiver function. Parameters are described at +/* Core APDU transceiver function. Parameters are described at apdu_send_le with the exception of PININFO which indicates pinpad related operations if not NULL. If EXTENDED_MODE is not 0 command chaining or extended length will be used according to these diff --git a/scd/app-dinsig.c b/scd/app-dinsig.c index bea285687..983bed6e1 100644 --- a/scd/app-dinsig.c +++ b/scd/app-dinsig.c @@ -415,7 +415,7 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, && indatalen != (15+20) && indatalen != (19+32)) return gpg_error (GPG_ERR_INV_VALUE); - /* Check that the provided ID is vaid. This is not really needed + /* Check that the provided ID is valid. This is not really needed but we do it to enforce correct usage by the caller. */ if (strncmp (keyidstr, "DINSIG.", 7) ) return gpg_error (GPG_ERR_INV_ID); diff --git a/scd/app-geldkarte.c b/scd/app-geldkarte.c index 510beb550..85bcedc4f 100644 --- a/scd/app-geldkarte.c +++ b/scd/app-geldkarte.c @@ -254,7 +254,7 @@ copy_bcd (const unsigned char *string, size_t length) } -/* Convert the BCD number at STING of LENGTH into an integer and store +/* Convert the BCD number at STRING of LENGTH into an integer and store that at RESULT. Return 0 on success. */ static gpg_error_t bcd_to_int (const unsigned char *string, size_t length, int *result) diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index 87804f593..63265e48a 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -643,7 +643,7 @@ count_bits (const unsigned char *a, size_t len) Where FLAGS is a plain hexadecimal number representing flag values. The lsb is here the rightmost bit. Defined flags bits are: - Bit 0 = CHV1 and CHV2 are not syncronized + Bit 0 = CHV1 and CHV2 are not synchronized Bit 1 = CHV2 has been set to the default PIN of "123456" (this implies that bit 0 is also set). diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c index 6b0833b2c..ff82ef38b 100644 --- a/scd/ccid-driver.c +++ b/scd/ccid-driver.c @@ -1937,7 +1937,7 @@ bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen, is the sequence number used to send the request and EXPECTED_TYPE the type of message we expect. Does checks on the ccid header. TIMEOUT is the timeout value in ms. NO_DEBUG may be set to - avoid debug messages in case of no error; this can be overriden + avoid debug messages in case of no error; this can be overridden with a glibal debug level of at least 3. Returns 0 on success. */ static int bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, @@ -2956,7 +2956,7 @@ ccid_transceive_apdu_level (ccid_driver_t handle, bit 3 unused bit 2..0 Source Node Address (SAD) - If node adresses are not used, SAD and DAD should be set to 0 on + If node addresses are not used, SAD and DAD should be set to 0 on the first block sent to the card. If they are used they should have different values (0 for one is okay); that first block sets up the addresses of the nodes. @@ -3298,7 +3298,7 @@ ccid_transceive (ccid_driver_t handle, /* Wait time extension request. */ unsigned char bwi = tpdu[3]; - /* Check if it's unsual value which can't be expressed in ATR. */ + /* Check if it's unusual value which can't be expressed in ATR. */ if (bwi > 15) wait_more = 1; diff --git a/scd/iso7816.c b/scd/iso7816.c index 29208c254..01faca5b4 100644 --- a/scd/iso7816.c +++ b/scd/iso7816.c @@ -427,7 +427,7 @@ iso7816_put_data_odd (int slot, int extended_mode, int tag, /* Manage Security Environment. This is a weird operation and there is no easy abstraction for it. Furthermore, some card seem to have - a different interpreation of 7816-8 and thus we resort to let the + a different interpretation of 7816-8 and thus we resort to let the caller decide what to do. */ gpg_error_t iso7816_manage_security_env (int slot, int p1, int p2, @@ -445,7 +445,7 @@ iso7816_manage_security_env (int slot, int p1, int p2, /* Perform the security operation COMPUTE DIGITAL SIGANTURE. On - success 0 is returned and the data is availavle in a newly + success 0 is returned and the data is available in a newly allocated buffer stored at RESULT with its length stored at RESULTLEN. For LE see do_generate_keypair. */ gpg_error_t diff --git a/scd/scdaemon.c b/scd/scdaemon.c index 8f8a02619..507108db0 100644 --- a/scd/scdaemon.c +++ b/scd/scdaemon.c @@ -1069,7 +1069,7 @@ handle_signal (int signo) /* Create a name for the socket. We 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. - Retunrs: Pointer to an allcoated string with the absolute name of + Returns: Pointer to an allcoated string with the absolute name of the socket used. */ static char * create_socket_name (char *standard_name) diff --git a/sm/call-dirmngr.c b/sm/call-dirmngr.c index 3a38bca50..bff7dd652 100644 --- a/sm/call-dirmngr.c +++ b/sm/call-dirmngr.c @@ -474,7 +474,7 @@ isvalid_status_cb (void *opaque, const char *line) { parm->seen++; if (!*s || !unhexify_fpr (s, parm->fpr)) - parm->seen++; /* Bumb it to indicate an error. */ + parm->seen++; /* Bump it to indicate an error. */ } return 0; } diff --git a/sm/certchain.c b/sm/certchain.c index 4e18caf55..708e9371c 100644 --- a/sm/certchain.c +++ b/sm/certchain.c @@ -669,7 +669,7 @@ find_up (ctrl_t ctrl, KEYDB_HANDLE kh, log_debug (" found via authid and sn+issuer\n"); /* In case of an error, try to get the certificate from the - dirmngr. That is done by trying to put that certifcate + dirmngr. That is done by trying to put that certificate into the ephemeral DB and let the code below do the actual retrieve. Thus there is no error checking. Skipped in find_next mode as usual. */ @@ -908,7 +908,7 @@ is_root_cert (ksba_cert_t cert, const char *issuerdn, const char *subjectdn) { if (gpg_err_code (err) == GPG_ERR_NO_DATA) return 1; /* Yes. Without a authorityKeyIdentifier this needs - to be the Root certifcate (our trust anchor). */ + to be the Root certificate (our trust anchor). */ log_error ("error getting authorityKeyIdentifier: %s\n", gpg_strerror (err)); return 0; /* Well, it is broken anyway. Return No. */ @@ -1103,7 +1103,7 @@ check_validity_period (ksba_isotime_t current_time, } /* This is a variant of check_validity_period used with the chain - model. The dextra contraint here is that notBefore and notAfter + model. The extra constraint here is that notBefore and notAfter must exists and if the additional argument CHECK_TIME is given this time is used to check the validity period of SUBJECT_CERT. */ static gpg_error_t @@ -1553,7 +1553,7 @@ do_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime_arg, if (rc) goto leave; - break; /* Okay: a self-signed certicate is an end-point. */ + break; /* Okay: a self-signed certificate is an end-point. */ } /* End is_root. */ diff --git a/sm/certlist.c b/sm/certlist.c index c9e275e9d..12a492518 100644 --- a/sm/certlist.c +++ b/sm/certlist.c @@ -395,7 +395,7 @@ gpgsm_add_to_certlist (ctrl_t ctrl, const char *name, int secret, Further we ignore them if they are due to an identical certificate (which may happen if a - certificate is accidential duplicated in the + certificate is accidentally duplicated in the keybox). */ if (!keydb_get_cert (kh, &cert2)) { @@ -533,7 +533,7 @@ gpgsm_find_cert (ctrl_t ctrl, } /* If we don't have the KEYID filter we need to check for - ambiguous search results. Note, that it is somehwat + ambiguous search results. Note, that it is somewhat reasonable to assume that a specification of a KEYID won't lead to ambiguous names. */ if (!rc && !keyid) diff --git a/sm/certreqgen-ui.c b/sm/certreqgen-ui.c index 4f8a1ac9d..f64baf365 100644 --- a/sm/certreqgen-ui.c +++ b/sm/certreqgen-ui.c @@ -86,7 +86,7 @@ store_mb_lines (membuf_t *mb, membuf_t *lines) } -/* Chech whether we have a key for the key with HEXGRIP. Returns NULL +/* Check whether we have a key for the key with HEXGRIP. Returns NULL if not or a string describing the type of the key (RSA, ELG, DSA, etc..). */ static const char * diff --git a/sm/gpgsm.c b/sm/gpgsm.c index 072b30d35..2f9e5bfd2 100644 --- a/sm/gpgsm.c +++ b/sm/gpgsm.c @@ -1571,7 +1571,7 @@ main ( int argc, char **argv) set_debug (); - /* Although we always use gpgsm_exit, we better install a regualr + /* Although we always use gpgsm_exit, we better install a regular exit handler so that at least the secure memory gets wiped out. */ if (atexit (emergency_cleanup)) diff --git a/sm/keydb.c b/sm/keydb.c index d85679a3b..a6ea9f77f 100644 --- a/sm/keydb.c +++ b/sm/keydb.c @@ -106,7 +106,7 @@ try_make_homedir (const char *fname) /* Handle the creation of a keybox if it does not yet exist. Take - into acount that other processes might have the keybox already + into account that other processes might have the keybox already locked. This lock check does not work if the directory itself is not yet available. If R_CREATED is not NULL it will be set to true if the function created a new keybox. */ diff --git a/sm/minip12.c b/sm/minip12.c index f066892a0..76ce07376 100644 --- a/sm/minip12.c +++ b/sm/minip12.c @@ -853,7 +853,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length, if (ti.class == ASNCONTEXT && ti.tag == 0 && ti.is_constructed && ti.ndef) { /* Mozilla exported certs now come with single byte chunks of - octect strings. (Mozilla Firefox 1.0.4). Arghh. */ + octet strings. (Mozilla Firefox 1.0.4). Arghh. */ where = "cram-rc2or3des-ciphertext"; cram_buffer = cram_octet_string ( p, &n, &consumed); if (!cram_buffer) @@ -1210,7 +1210,7 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset, if (ti.is_constructed && ti.ndef) { /* Mozilla exported certs now come with single byte chunks of - octect strings. (Mozilla Firefox 1.0.4). Arghh. */ + octet strings. (Mozilla Firefox 1.0.4). Arghh. */ where = "cram-data.outersegs"; cram_buffer = cram_octet_string ( p, &n, &consumed); if (!cram_buffer) @@ -1550,7 +1550,7 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, if (ti.is_constructed && ti.ndef) { /* Mozilla exported certs now come with single byte chunks of - octect strings. (Mozilla Firefox 1.0.4). Arghh. */ + octet strings. (Mozilla Firefox 1.0.4). Arghh. */ where = "cram-bags"; cram_buffer = cram_octet_string ( p, &n, NULL); if (!cram_buffer) diff --git a/sm/qualified.c b/sm/qualified.c index 6a7b47306..70d03aed3 100644 --- a/sm/qualified.c +++ b/sm/qualified.c @@ -140,7 +140,7 @@ read_list (char *key, char *country, int *lnr) Returns: 0 if the certificate is included. GPG_ERR_NOT_FOUND if it is not in the list or any other error (e.g. if no list of qualified signatures is available. If COUNTRY has not been passed - as NULL a string witha maximum length of 2 will be copied into it; + as NULL a string with a maximum length of 2 will be copied into it; thus the caller needs to provide a buffer of length 3. */ gpg_error_t gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert, char *country) diff --git a/tests/asschk.c b/tests/asschk.c index 65828e5b2..c77fd7c23 100644 --- a/tests/asschk.c +++ b/tests/asschk.c @@ -30,7 +30,7 @@ expanded once and non existing macros expand to the empty string. A macro is dereferenced by prefixing its name with a dollar sign; the end of the name is currently indicated by a white space, a - dollar sign or a slash. To use a dollor sign verbatim, double it. + dollar sign or a slash. To use a dollar sign verbatim, double it. A macro is assigned by prefixing a statement with the macro name and an equal sign. The value is assigned verbatim if it does not @@ -47,7 +47,7 @@ [ =] [] - If NAME is not specifed but the statement returns a value it is + If NAME is not specified but the statement returns a value it is assigned to the name "?" so that it can be referenced using "$?". The following commands are implemented: @@ -274,7 +274,7 @@ writen (int fd, const char *buffer, size_t length) type and store that in recv_type. The function terminates on a communication error. Returns a pointer into the inputline to the first byte of the arguments. The parsing is very strict to match - exaclty what we want to send. */ + exactly what we want to send. */ static char * read_assuan (int fd) { @@ -397,7 +397,7 @@ write_assuan (int fd, const char *line) /* Start the server with path PGMNAME and connect its stdout and strerr to a newly created pipes; the file descriptors are then - store in the gloabl variables SERVER_SEND_FD and + store in the global variables SERVER_SEND_FD and SERVER_RECV_FD. The initial handcheck is performed.*/ static void start_server (const char *pgmname) @@ -468,7 +468,7 @@ start_server (const char *pgmname) -/* Script intepreter. */ +/* Script interpreter. */ static void unset_var (const char *name) diff --git a/tools/gpg-check-pattern.c b/tools/gpg-check-pattern.c index 4db8f3706..1f015e9e7 100644 --- a/tools/gpg-check-pattern.c +++ b/tools/gpg-check-pattern.c @@ -91,7 +91,7 @@ static struct enum { PAT_NULL, /* Indicates end of the array. */ PAT_STRING, /* The pattern is a simple string. */ - PAT_REGEX /* The pattern is an extended regualr expression. */ + PAT_REGEX /* The pattern is an extended regualar expression. */ }; diff --git a/tools/gpgconf.c b/tools/gpgconf.c index 59085d8b5..df3ae8cab 100644 --- a/tools/gpgconf.c +++ b/tools/gpgconf.c @@ -823,7 +823,7 @@ main (int argc, char **argv) ; else if (rmdir (socketdir)) { - /* If the director is not empty we first try to delet + /* If the director is not empty we first try to delete * socket files. */ err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_ENOTEMPTY diff --git a/tools/mime-parser.c b/tools/mime-parser.c index a151dc65e..98f27105c 100644 --- a/tools/mime-parser.c +++ b/tools/mime-parser.c @@ -50,7 +50,7 @@ struct mime_parser_context_s { void *cookie; /* Cookie passed to all callbacks. */ - /* The callback to announce the transation from header to body. */ + /* The callback to announce the transition from header to body. */ gpg_error_t (*t2body) (void *cookie, int level); /* The callback to announce a new part. */ diff --git a/tools/no-libgcrypt.c b/tools/no-libgcrypt.c index 873996889..3b577567a 100644 --- a/tools/no-libgcrypt.c +++ b/tools/no-libgcrypt.c @@ -114,7 +114,7 @@ gcry_free (void *a) /* We need this dummy because exechelp.c uses gcry_control to - terminate the secure memeory. */ + terminate the secure memory. */ gcry_error_t gcry_control (enum gcry_ctl_cmds cmd, ...) { diff --git a/tools/rfc822parse.c b/tools/rfc822parse.c index 0a4e2bc94..f1e95bd34 100644 --- a/tools/rfc822parse.c +++ b/tools/rfc822parse.c @@ -96,7 +96,7 @@ struct rfc822parse_context void *callback_value; int callback_error; int in_body; - int in_preamble; /* Wether we are before the first boundary. */ + int in_preamble; /* Whether we are before the first boundary. */ part_t parts; /* The tree of parts. */ part_t current_part; /* Whom we are processing (points into parts). */ const char *boundary; /* Current boundary. */ @@ -176,7 +176,7 @@ my_stpcpy (char *a,const char *b) #endif -/* If a callback has been registerd, call it for the event of type +/* If a callback has been registered, call it for the event of type EVENT. */ static int do_callback (rfc822parse_t msg, rfc822parse_event_t event) @@ -643,7 +643,7 @@ rfc822parse_get_field (rfc822parse_t msg, const char *name, int which, /**************** * Enumerate all header. Caller has to provide the address of a pointer - * which has to be initialzed to NULL, the caller should then never change this + * which has to be initialized to NULL, the caller should then never change this * pointer until he has closed the enumeration by passing again the address * of the pointer but with msg set to NULL. * The function returns pointers to all the header lines or NULL when @@ -681,7 +681,7 @@ rfc822parse_enum_header_lines (rfc822parse_t msg, void **context) * >0 : Retrieve the n-th field * RPREV may be used to return the predecessor of the returned field; - * which may be NULL for the very first one. It has to be initialzed + * which may be NULL for the very first one. It has to be initialized * to either NULL in which case the search start at the first header line, * or it may point to a headerline, where the search should start */ @@ -1078,7 +1078,7 @@ is_parameter (TOKEN t) parse context is valid; NULL is returned in case that attr is not defined in the header, a missing value is reppresented by an empty string. - With LOWER_VALUE set to true, a matching field valuebe be + With LOWER_VALUE set to true, a matching field value will be lowercased. Note, that ATTR should be lowercase. diff --git a/tools/wks-util.c b/tools/wks-util.c index 3fd824c1a..862cd33a3 100644 --- a/tools/wks-util.c +++ b/tools/wks-util.c @@ -65,7 +65,7 @@ wks_set_status_fd (int fd) } -/* Write a status line with code NO followed by the outout of the +/* Write a status line with code NO followed by the output of the * printf style FORMAT. The caller needs to make sure that LFs and * CRs are not printed. */ void From 0240345728a84d8f235ce05889e83963e52742eb Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 25 Oct 2018 16:20:20 +0900 Subject: [PATCH 039/235] g10,scd: Improve UIF support. * g10/call-agent.c (learn_status_cb): Parse "bt" flag. * g10/call-agent.h: New member field "bt". * g10/card-util.c (uif): Limit its access only when it is supported. * scd/app-openpgp.c (do_setattr): Allow access to UIF objects only when there is a button. Signed-off-by: NIIBE Yutaka --- g10/call-agent.c | 2 ++ g10/call-agent.h | 1 + g10/card-util.c | 21 +++++++++++++++++++++ scd/app-openpgp.c | 9 ++++++--- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/g10/call-agent.c b/g10/call-agent.c index 755f2e30b..e9ea82e4f 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -609,6 +609,8 @@ learn_status_cb (void *opaque, const char *line) parm->extcap.ki = abool; else if (!strcmp (p, "aac")) parm->extcap.aac = abool; + else if (!strcmp (p, "bt")) + parm->extcap.bt = abool; else if (!strcmp (p, "kdf")) parm->extcap.kdf = abool; else if (!strcmp (p, "si")) diff --git a/g10/call-agent.h b/g10/call-agent.h index 59e4ff486..1055b5ef9 100644 --- a/g10/call-agent.h +++ b/g10/call-agent.h @@ -69,6 +69,7 @@ struct agent_card_info_s unsigned int ki:1; /* Key import available. */ unsigned int aac:1; /* Algorithm attributes are changeable. */ unsigned int kdf:1; /* KDF object to support PIN hashing available. */ + unsigned int bt:1; /* Button for confirmation available. */ } extcap; unsigned int status_indicator; }; diff --git a/g10/card-util.c b/g10/card-util.c index 234abcbab..5205798b6 100644 --- a/g10/card-util.c +++ b/g10/card-util.c @@ -2114,10 +2114,31 @@ kdf_setup (const char *args) static void uif (int arg_number, const char *arg_rest) { + struct agent_card_info_s info; + int feature_available; gpg_error_t err; char name[100]; unsigned char data[2]; + memset (&info, 0, sizeof info); + + err = agent_scd_getattr ("EXTCAP", &info); + if (err) + { + log_error (_("error getting card info: %s\n"), gpg_strerror (err)); + return; + } + + feature_available = info.extcap.bt; + agent_release_card_info (&info); + + if (!feature_available) + { + log_error (_("This command is not supported by this card\n")); + tty_printf ("\n"); + return; + } + snprintf (name, sizeof name, "UIF-%d", arg_number); if ( !strcmp (arg_rest, "off") ) data[0] = 0x00; diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index 63265e48a..789b72f00 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -2468,9 +2468,9 @@ do_setattr (app_t app, const char *name, { "SM-KEY-MAC", 0x00D2, 3, 0, 1 }, { "KEY-ATTR", 0, 0, 3, 1 }, { "AESKEY", 0x00D5, 3, 0, 1 }, - { "UIF-1", 0x00D6, 3, 0, 1 }, - { "UIF-2", 0x00D7, 3, 0, 1 }, - { "UIF-3", 0x00D8, 3, 0, 1 }, + { "UIF-1", 0x00D6, 3, 5, 1 }, + { "UIF-2", 0x00D7, 3, 5, 1 }, + { "UIF-3", 0x00D8, 3, 5, 1 }, { "KDF", 0x00F9, 3, 4, 1 }, { NULL, 0 } }; @@ -2483,6 +2483,9 @@ do_setattr (app_t app, const char *name, if (table[idx].need_v2 && !app->app_local->extcap.is_v2) return gpg_error (GPG_ERR_NOT_SUPPORTED); /* Not yet supported. */ + if (table[idx].special == 5 && app->app_local->extcap.has_button == 0) + return gpg_error (GPG_ERR_INV_OBJ); + if (table[idx].special == 3) return change_keyattr_from_string (app, pincb, pincb_arg, value, valuelen); From 2b57a8159cdc3b212a4efc68787b40cafcd91ebe Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 25 Oct 2018 17:21:52 +0200 Subject: [PATCH 040/235] dirmngr: Fix out of scope use of a var in the keyserver LDAP code. * dirmngr/ks-engine-ldap.c (extract_attributes): Don't use a variabale out of scope and cleanup the entire pgpKeySize block. -- GnuPG-bug-id: 4229 Signed-off-by: Werner Koch --- dirmngr/ks-engine-ldap.c | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/dirmngr/ks-engine-ldap.c b/dirmngr/ks-engine-ldap.c index f50ba50d0..d94bd5e25 100644 --- a/dirmngr/ks-engine-ldap.c +++ b/dirmngr/ks-engine-ldap.c @@ -1694,26 +1694,16 @@ extract_attributes (LDAPMod ***modlist, char *line) if (is_pub || is_sub) { - char *size = fields[2]; - int val = atoi (size); - size = NULL; + char padded[6]; + int val; - if (val > 0) - { - /* We zero pad this on the left to make PGP happy. */ - char padded[6]; - if (val < 99999 && val > 0) - { - snprintf (padded, sizeof padded, "%05u", val); - size = padded; - } - } - - if (size) - { - if (is_pub || is_sub) - modlist_add (modlist, "pgpKeySize", size); - } + val = atoi (fields[2]); + if (val < 99999 && val > 0) + { + /* We zero pad this on the left to make PGP happy. */ + snprintf (padded, sizeof padded, "%05u", val); + modlist_add (modlist, "pgpKeySize", padded); + } } if (is_pub) @@ -1736,10 +1726,7 @@ extract_attributes (LDAPMod ***modlist, char *line) } if (algo) - { - if (is_pub) - modlist_add (modlist, "pgpKeyType", algo); - } + modlist_add (modlist, "pgpKeyType", algo); } if (is_pub || is_sub || is_sig) From 11e9b704b5054708b0b307013fde053701c39df4 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 25 Oct 2018 18:26:34 +0200 Subject: [PATCH 041/235] speedo: Sign the windows installer with a timestamp. -- --- build-aux/speedo.mk | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/build-aux/speedo.mk b/build-aux/speedo.mk index d9b4a7515..1a96e3b57 100644 --- a/build-aux/speedo.mk +++ b/build-aux/speedo.mk @@ -157,8 +157,9 @@ 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 +# The Authenticode key and cert chain used to sign the Windows installer AUTHENTICODE_KEY=${HOME}/.gnupg/g10code-authenticode-key.p12 +AUTHENTICODE_CERTS=${HOME}/.gnupg/g10code-authenticode-certs.pem # Directory names. @@ -1266,8 +1267,11 @@ sign-installer: 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" ;\ + osslsigncode sign -certs $(AUTHENTICODE_CERTS)\ + -pkcs12 $(AUTHENTICODE_KEY) -askpass \ + -ts "http://timestamp.globalsign.com/scripts/timstamp.dll" \ + -h sha256 -n GnuPG -i https://gnupg.org \ + -in "PLAY/inst/$$exefile" -out "../../$$exefile" ;\ exefile="../../$$exefile" ;\ $(call MKSWDB_commands,$${exefile},$${reldate}); \ echo "speedo: /*" ;\ From b39ece7d35401302879062d9d4bec25b1249ae7e Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 25 Oct 2018 09:46:23 -0400 Subject: [PATCH 042/235] headers: fix spelling Signed-off-by: Daniel Kahn Gillmor --- common/audit.h | 2 +- common/sexp-parse.h | 2 +- dirmngr/cdb.h | 2 +- g10/key-clean.h | 2 +- g10/packet.h | 2 +- sm/gpgsm.h | 2 +- tools/gpgtar.h | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/common/audit.h b/common/audit.h index 4ef2645da..05f39533d 100644 --- a/common/audit.h +++ b/common/audit.h @@ -185,7 +185,7 @@ typedef enum if no real recipient has been given. */ AUDIT_SESSION_KEY, /* string */ - /* Mark the creation or availibility of the session key. The + /* Mark the creation or availability of the session key. The parameter is the algorithm ID. */ AUDIT_ENCRYPTED_TO, /* cert, err */ diff --git a/common/sexp-parse.h b/common/sexp-parse.h index 4f77f1430..0403d65f5 100644 --- a/common/sexp-parse.h +++ b/common/sexp-parse.h @@ -105,7 +105,7 @@ smatch (unsigned char const **buf, size_t buflen, const char *token) } /* Format VALUE for use as the length indicatior of an S-expression. - The caller needs to provide a buffer HELP_BUFFER wth a length of + The caller needs to provide a buffer HELP_BUFFER with a length of HELP_BUFLEN. The return value is a pointer into HELP_BUFFER with the formatted length string. The colon and a trailing nul are appended. HELP_BUFLEN must be at least 3 - a more useful value is diff --git a/dirmngr/cdb.h b/dirmngr/cdb.h index 0c0d2702a..5d46f6956 100644 --- a/dirmngr/cdb.h +++ b/dirmngr/cdb.h @@ -85,7 +85,7 @@ int cdb_make_put(struct cdb_make *cdbmp, const void *key, cdbi_t klen, const void *val, cdbi_t vlen, int flag); -#define CDB_PUT_ADD 0 /* add unconditionnaly, like cdb_make_add() */ +#define CDB_PUT_ADD 0 /* add unconditionally, like cdb_make_add() */ #define CDB_PUT_REPLACE 1 /* replace: do not place to index OLD record */ #define CDB_PUT_INSERT 2 /* add only if not already exists */ #define CDB_PUT_WARN 3 /* add unconditionally but ret. 1 if exists */ diff --git a/g10/key-clean.h b/g10/key-clean.h index a0fb76950..c4f164928 100644 --- a/g10/key-clean.h +++ b/g10/key-clean.h @@ -23,7 +23,7 @@ #include "gpg.h" -/* No explict cleaning. */ +/* No explicit cleaning. */ #define KEY_CLEAN_NONE 0 /* Remove only invalid subkeys (ie. missing key-bindings) */ #define KEY_CLEAN_INVALID 1 diff --git a/g10/packet.h b/g10/packet.h index 6e1438be6..d088bf432 100644 --- a/g10/packet.h +++ b/g10/packet.h @@ -244,7 +244,7 @@ typedef struct const byte *trust_regexp; struct revocation_key *revkey; int numrevkeys; - int help_counter; /* Used internally bu some fucntions. */ + int help_counter; /* Used internally bu some functions. */ pka_info_t *pka_info; /* Malloced PKA data or NULL if not available. See also flags.pka_tried. */ char *signers_uid; /* Malloced value of the SIGNERS_UID diff --git a/sm/gpgsm.h b/sm/gpgsm.h index d3fbde515..7a5e4917d 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -76,7 +76,7 @@ struct const char *protect_tool_program; char *outfile; /* name of output file */ - int with_key_data;/* include raw key in the column delimted output */ + int with_key_data;/* include raw key in the column delimited output */ int fingerprint; /* list fingerprints in all key listings */ diff --git a/tools/gpgtar.h b/tools/gpgtar.h index 8cbe80bbb..28d3d88b1 100644 --- a/tools/gpgtar.h +++ b/tools/gpgtar.h @@ -88,7 +88,7 @@ typedef enum } typeflag_t; -/* The internal represenation of a TAR header. */ +/* The internal representation of a TAR header. */ struct tar_header_s; typedef struct tar_header_s *tar_header_t; struct tar_header_s From a7c5d65eb50355274c1b5b047c02c653f518900a Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 25 Oct 2018 16:52:58 -0400 Subject: [PATCH 043/235] all: fix more spelling errors --- Makefile.am | 2 +- NEWS | 26 +++++++++++++------------- agent/keyformat.txt | 6 +++--- build-aux/speedo.mk | 6 +++--- build-aux/texinfo.tex | 4 ++-- common/mkerrors | 2 +- common/mkerrtok | 2 +- configure.ac | 2 +- doc/Notes | 6 +++--- doc/dirmngr.texi | 2 +- doc/gpg.texi | 12 ++++++------ g10/import.c | 2 +- kbx/mkerrors | 2 +- m4/gpg-error.m4 | 2 +- m4/ksba.m4 | 2 +- m4/libgcrypt.m4 | 2 +- m4/ntbtls.m4 | 2 +- sm/certchain.c | 2 +- tools/gpg-check-pattern.c | 2 +- 19 files changed, 43 insertions(+), 43 deletions(-) diff --git a/Makefile.am b/Makefile.am index 680fe1be1..b59e9e3ec 100644 --- a/Makefile.am +++ b/Makefile.am @@ -189,7 +189,7 @@ release: $(MAKE) -f $(RELEASE_NAME)/build-aux/speedo.mk w32-release ;\ echo "/* Build finished at $$(date -uIseconds) */" ;\ echo "/*" ;\ - echo " * Please run the final step interactivly:" ;\ + echo " * Please run the final step interactively:" ;\ echo " * make sign-release" ;\ echo " */" ;\ ) 2>&1 | tee "$(RELEASE_NAME).buildlog" diff --git a/NEWS b/NEWS index 27f78b14c..245cc7044 100644 --- a/NEWS +++ b/NEWS @@ -98,7 +98,7 @@ Noteworthy changes in version 2.3.0 (unreleased) * dirmngr: Fallback to CRL if no default OCSP responder is configured. * dirmngr: Implement CRL fetching via https. Here a redirection to - http is explictly allowed. + http is explicitly allowed. * dirmngr: Make LDAP searching and CRL fetching work under Windows. This stopped working with 2.1. [#3937] @@ -959,7 +959,7 @@ Noteworthy changes in version 2.1.11 (2016-01-26) * gpg: Emit PROGRESS status lines during key generation. - * gpg: Don't check for ambigious or non-matching key specification in + * gpg: Don't check for ambiguous or non-matching key specification in the config file or given to --encrypt-to. This feature will return in 2.3.x. @@ -986,7 +986,7 @@ Noteworthy changes in version 2.1.11 (2016-01-26) * dirmmgr: All configured keyservers are now searched. * dirmngr: Install CA certificate for hkps.pool.sks-keyservers.net. - Use this certiticate even if --hkp-cacert is not used. + Use this certificate even if --hkp-cacert is not used. * gpgtar: Add actual encryption code. gpgtar does now fully replace gpg-zip. @@ -1020,7 +1020,7 @@ Noteworthy changes in version 2.1.10 (2015-12-04) * gpg: New option --only-sign-text-ids to exclude photo IDs from key signing. - * gpg: Check for ambigious or non-matching key specification in the + * gpg: Check for ambiguous or non-matching key specification in the config file or given to --encrypt-to. * gpg: Show the used card reader with --card-status. @@ -1310,7 +1310,7 @@ Noteworthy changes in version 2.1.1 (2014-12-16) * gpg: Fixed regression in --refresh-keys. - * gpg: Fixed regresion in %g and %p codes for --sig-notation. + * gpg: Fixed regression in %g and %p codes for --sig-notation. * gpg: Fixed best matching hash algo detection for ECDSA and EdDSA. @@ -1390,7 +1390,7 @@ Noteworthy changes in version 2.1.0 (2014-11-06) * gpg: Default keyring is now created with a .kbx suffix. - * gpg: Add a shortcut to the key capabilies menu (e.g. "=e" sets the + * gpg: Add a shortcut to the key capabilities menu (e.g. "=e" sets the encryption capabilities). * gpg: Fixed obsolete options parsing. @@ -1582,7 +1582,7 @@ Noteworthy changes in version 2.1.0 (2014-11-06) * scdaemon: Does not anymore block after changing a card (regression fix). - * tools: gpg-connect-agent does now proberly display the help output + * tools: gpg-connect-agent does now properly display the help output for "SCD HELP" commands. @@ -1707,7 +1707,7 @@ Noteworthy changes in version 2.0.13 (2009-09-04) * Add hack to the internal CCID driver to allow the use of some Omnikey based card readers with 2048 bit keys. - * GPG now repeatly asks the user to insert the requested OpenPGP + * GPG now repeatedly asks the user to insert the requested OpenPGP card. This can be disabled with --limit-card-insert-tries=1. * Minor bug fixes. @@ -1833,7 +1833,7 @@ Noteworthy changes in version 2.0.9 (2008-03-26) * Extended the PKITS framework. - * Fixed a bug in the ambigious name detection. + * Fixed a bug in the ambiguous name detection. * Fixed possible memory corruption while importing OpenPGP keys (bug introduced with 2.0.8). [CVE-2008-1530] @@ -2383,7 +2383,7 @@ Noteworthy changes in version 1.9.2 (2003-11-17) command but from the menu provided by the new --card-edit command. * PINs are now properly cached and there are only 2 PINs visible. - The 3rd PIN (CHV2) is internally syncronized with the regular PIN. + The 3rd PIN (CHV2) is internally synchronized with the regular PIN. * All kind of other internal stuff. @@ -3087,7 +3087,7 @@ Noteworthy changes in version 1.0.1 (1999-12-16) * Fixed some minor bugs and the problem with conventional encrypted packets which did use the gpg v3 partial length headers. - * Add Indonesian and Portugese translations. + * Add Indonesian and Portuguese translations. * Fixed a bug with symmetric-only encryption using the non-default 3DES. The option --emulate-3des-s2k-bug may be used to decrypt documents @@ -3190,7 +3190,7 @@ Noteworthy changes in version 0.9.8 (1999-06-26) * New option --with-key-data to list the public key parameters. New option -N to insert notations and a --set-policy-url. - A couple of other options to allow reseting of options. + A couple of other options to allow resetting of options. * Better support for HPUX. @@ -3669,7 +3669,7 @@ Noteworthy changes in version 0.2.19 (1998-05-29) Noteworthy changes in version 0.2.18 (1998-05-15) ------------------------------------ - * Splitted cipher/random.c, add new option "--disable-dev-random" + * Split cipher/random.c, add new option "--disable-dev-random" to configure to support the development of a random source for other systems. Prepared sourcefiles rand-unix.c, rand-w32.c and rand-dummy.c (which is used to allow compilation on systems diff --git a/agent/keyformat.txt b/agent/keyformat.txt index 2e48b346e..c7426db9d 100644 --- a/agent/keyformat.txt +++ b/agent/keyformat.txt @@ -234,7 +234,7 @@ The currently defined protection modes are: (csum n) (protection PROTTYPE PROTALGO IV S2KMODE S2KHASH S2KSALT S2KCOUNT))) - Note that the public key paramaters in SKEY are duplicated and + Note that the public key parameters 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: @@ -359,8 +359,8 @@ 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 +optional. The order of the "key" lists and the order of the "value" +lists must 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 diff --git a/build-aux/speedo.mk b/build-aux/speedo.mk index 1a96e3b57..5ec582357 100644 --- a/build-aux/speedo.mk +++ b/build-aux/speedo.mk @@ -971,7 +971,7 @@ else endif @touch $(stampdir)/stamp-$(1)-01-configure -# Note that unpack has no 64 bit version becuase it is just the source. +# Note that unpack has no 64 bit version because it is just the source. # Fixme: We should use templates to create the standard and w64 # version of these rules. $(stampdir)/stamp-w64-$(1)-01-configure: $(stampdir)/stamp-$(1)-00-unpack @@ -1143,7 +1143,7 @@ all-speedo: $(stampdir)/stamp-final report-speedo: $(addprefix report-,$(speedo_build_list)) -# Just to check if we catched all stamps. +# Just to check if we caught all stamps. clean-stamps: $(RM) -fR $(stampdir) @@ -1287,7 +1287,7 @@ endif # -# Check availibility of standard tools +# Check availability of standard tools # check-tools: diff --git a/build-aux/texinfo.tex b/build-aux/texinfo.tex index 5a17f9793..9e1184868 100644 --- a/build-aux/texinfo.tex +++ b/build-aux/texinfo.tex @@ -3,7 +3,7 @@ % Load plain if necessary, i.e., if running under initex. \expandafter\ifx\csname fmtname\endcsname\relax\input plain\fi % -\def\texinfoversion{2007-05-03.09} +\def\texinfoversion{2018-10-25.16} % % Copyright (C) 1985, 1986, 1988, 1990, 1991, 1992, 1993, 1994, 1995, % 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, @@ -4598,7 +4598,7 @@ end \chardef\maxseclevel = 3 % % A numbered section within an unnumbered changes to unnumbered too. -% To achive this, remember the "biggest" unnum. sec. we are currently in: +% To achieve this, remember the "biggest" unnum. sec. we are currently in: \chardef\unmlevel = \maxseclevel % % Trace whether the current chapter is an appendix or not: diff --git a/common/mkerrors b/common/mkerrors index 138d3c1d1..2a6960ab6 100755 --- a/common/mkerrors +++ b/common/mkerrors @@ -30,7 +30,7 @@ cat < * How to mark a CA certificate as trusted. @@ -57,7 +57,7 @@ or In general you should first import the root certificates and then down to the end user certificate. You may put all into one file and gpgsm -will do the right thing in this case independend of the order. +will do the right thing in this case independent of the order. While verifying a signature, all included certificates are automagically imported. @@ -82,7 +82,7 @@ you get an output like: uid:::::::::CN=Werner Koch,OU=test,O=g10 Code,C=de:: uid::::::::::: -This should be familar to advanced gpg-users; see doc/DETAILS in gpg +This should be familiar to advanced gpg-users; see doc/DETAILS in gpg 1.3 (CVS HEAD) for a description of the records. The value in the "grp" tagged record is the so called keygrip and you should find a file ~/.gnupg/private-keys-v1.d/C92DB9CFD588ADE846BE3AC4E7A2E1B11A4A2ADB.key diff --git a/doc/dirmngr.texi b/doc/dirmngr.texi index 76be5286c..f5910a884 100644 --- a/doc/dirmngr.texi +++ b/doc/dirmngr.texi @@ -1096,7 +1096,7 @@ as a binary blob. @c In the end the same fucntionality is used, albeit hidden by a couple @c of indirection and argument and result code mangling. It furthere @c ingetrages OCSP checking depending on options are the way it is -@c called. GPGSM still uses this command but might eventuall switch over +@c called. GPGSM still uses this command but might eventually switch over @c to CHECKCRL and CHECKOCSP so that ISVALID can be retired. @c @c diff --git a/doc/gpg.texi b/doc/gpg.texi index ba1df4bfb..dedb8cc42 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -624,9 +624,9 @@ fingerprint (preferred) or their keyid. @end table -@c ******************************************* -@c ******* KEY MANGEMENT COMMANDS ********** -@c ******************************************* +@c ******************************************** +@c ******* KEY MANAGEMENT COMMANDS ********** +@c ******************************************** @node OpenPGP Key Management @subsection How to manage your keys @@ -2623,7 +2623,7 @@ These options are obsolete and have no effect since GnuPG 2.1. @item --force-aead @opindex force-aead Force the use of AEAD encryption over MDC encryption. AEAD is a -modern and faster way to do authenticated encrytion than the old MDC +modern and faster way to do authenticated encryption than the old MDC method. See also options @option{--aead-algo} and @option{--chunk-size}. @@ -2779,7 +2779,7 @@ This option is obsolete; it is handled as an alias for @option{--pgp7} @item --pgp7 @opindex pgp7 -Set up all options to be as PGP 7 compliant as possible. This allowd +Set up all options to be as PGP 7 compliant as possible. This allowed the ciphers IDEA, 3DES, CAST5,AES128, AES192, AES256, and TWOFISH., the hashes MD5, SHA1 and RIPEMD160, and the compression algorithms none and ZIP. This option implies @option{--escape-from-lines} and @@ -3051,7 +3051,7 @@ same thing. @opindex aead-algo Specify that the AEAD algorithm @var{name} is to be used. This is useful for symmetric encryption where no key preference are available -to select the AEAD algorithm. Runing @command{@gpgname} with option +to select the AEAD algorithm. Running @command{@gpgname} with option @option{--version} shows the available AEAD algorithms. In general, you do not want to use this option as it allows you to violate the OpenPGP standard. The option @option{--personal-aead-preferences} is diff --git a/g10/import.c b/g10/import.c index 6fdd53e85..23258a0a7 100644 --- a/g10/import.c +++ b/g10/import.c @@ -3073,7 +3073,7 @@ chk_self_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, int *non_self) kbnode_t bsnode = NULL; /* Subkey binding signature node. */ u32 bsdate = 0; /* Timestamp of that node. */ kbnode_t rsnode = NULL; /* Subkey recocation signature node. */ - u32 rsdate = 0; /* Timestamp of tha node. */ + u32 rsdate = 0; /* Timestamp of that node. */ PKT_signature *sig; int rc; kbnode_t n; diff --git a/kbx/mkerrors b/kbx/mkerrors index 629485ae0..c0bca108c 100755 --- a/kbx/mkerrors +++ b/kbx/mkerrors @@ -29,7 +29,7 @@ cat < 0)) { /* Note that we don't need a case for the root certificate - because its own consitency has already been checked. */ + because its own consistency has already been checked. */ do_list(opt.ignore_expiration?0:1, listmode, listfp, depth == 0 ? _("signature not created during lifetime of certificate") : diff --git a/tools/gpg-check-pattern.c b/tools/gpg-check-pattern.c index 1f015e9e7..dee5d5d47 100644 --- a/tools/gpg-check-pattern.c +++ b/tools/gpg-check-pattern.c @@ -91,7 +91,7 @@ static struct enum { PAT_NULL, /* Indicates end of the array. */ PAT_STRING, /* The pattern is a simple string. */ - PAT_REGEX /* The pattern is an extended regualar expression. */ + PAT_REGEX /* The pattern is an extended regular expression. */ }; From 4249e9a2bf028f007d1ddaac730f636e5c6da20f Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Fri, 26 Oct 2018 12:38:43 +0900 Subject: [PATCH 044/235] kbx: Increase size of field for fingerprint. * kbx/keybox-search-desc.h (fpr): Increase the size. -- In the function keydb_search_fpr in g10/keydb.c, it is copied using MAX_FINGERPRINT_LEN. So, more size is required. Fixes-commit: ecbbafb88d920e713439b6b1b8e1b41a6f8d0e38 Signed-off-by: NIIBE Yutaka --- kbx/keybox-search-desc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kbx/keybox-search-desc.h b/kbx/keybox-search-desc.h index 6298994e9..c75bfa4c7 100644 --- a/kbx/keybox-search-desc.h +++ b/kbx/keybox-search-desc.h @@ -69,7 +69,7 @@ struct keydb_search_desc int snlen; /* -1 := sn is a hex string */ union { const char *name; - unsigned char fpr[24]; + unsigned char fpr[32]; u32 kid[2]; /* Note that this is in native endianness. */ unsigned char grip[20]; } u; From f248416bc9792e80bb0785302058131de49d7639 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 26 Oct 2018 14:44:32 +0200 Subject: [PATCH 045/235] wkd: Add option --directory to the server. * tools/gpg-wks-server.c (opts): Add '--directory', (main): Explain how to set correct permissions. (command_list_domains): Create an empty policy file and remove the warning for an empty policy file. -- Note that a policy file is meanwhile required and thus is is useful to create it. Signed-off-by: Werner Koch --- doc/wks.texi | 29 ++++++++++++++++++----------- tools/gpg-wks-server.c | 29 ++++++++++++++++++----------- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/doc/wks.texi b/doc/wks.texi index 51b86ca4a..bd2b8d502 100644 --- a/doc/wks.texi +++ b/doc/wks.texi @@ -215,9 +215,9 @@ Further it creates missing directories for the configuration and prints warnings pertaining to problems in the configuration. The command @option{--check-key} (or just @option{--check}) checks -whether a key with the given user-id is installed. The process return -success in this case; to also print a diagnostic, use option -@option{-v}. If the key is not installed a diagnostics is printed and +whether a key with the given user-id is installed. The process returns +success in this case; to also print a diagnostic use the option +@option{-v}. If the key is not installed a diagnostic is printed and the process returns failure; to suppress the diagnostic, use option @option{-q}. More than one user-id can be given; see also option @option{with-file}. @@ -243,6 +243,12 @@ The command @option{--revoke-key} is not yet functional. @table @gnupgtabopt +@item -C @var{dir} +@itemx --directory @var{dir} +@opindex directory +Use @var{dir} as top level directory for domains. The default is +@file{/var/lib/gnupg/wks}. + @item --from @var{mailaddr} @opindex from Use @var{mailaddr} as the default sender address. @@ -256,21 +262,22 @@ Add the mail header "@var{name}: @var{value}" to all outgoing mails. Directly send created mails using the @command{sendmail} command. Requires installation of that command. -@item --output @var{file} -@itemx -o +@item -o @var{file} +@itemx --output @var{file} @opindex output Write the created mail also to @var{file}. Note that the value @code{-} for @var{file} would write it to stdout. @item --with-dir @opindex with-dir -Also print the directory name for each domain listed by command -@option{--list-domains}. +When used with the command @option{--list-domains} print for each +installed domain the domain name and its directory name. @item --with-file @opindex with-file -With command @option{--check-key} print for each user-id, the address, -'i' for installed key or 'n' for not installed key, and the filename. +When used with the command @option{--check-key} print for each user-id, +the address, 'i' for installed key or 'n' for not installed key, and +the filename. @item --verbose @opindex verbose @@ -316,7 +323,7 @@ Finally run $ gpg-wks-server --list-domains @end example -to create the required sub-directories with the permission set +to create the required sub-directories with the permissions set correctly. For each domain a submission address needs to be configured. All service mails are directed to that address. It can be the same address for all configured domains, for example: @@ -326,7 +333,7 @@ be the same address for all configured domains, for example: $ echo key-submission@@example.net >submission-address @end example -The protocol requires that the key to be published is sent with an +The protocol requires that the key to be published is send with an encrypted mail to the service. Thus you need to create a key for the submission address: diff --git a/tools/gpg-wks-server.c b/tools/gpg-wks-server.c index a5881557f..24b331262 100644 --- a/tools/gpg-wks-server.c +++ b/tools/gpg-wks-server.c @@ -58,6 +58,7 @@ enum cmd_and_opt_values oQuiet = 'q', oVerbose = 'v', oOutput = 'o', + oDirectory = 'C', oDebug = 500, @@ -108,6 +109,7 @@ static ARGPARSE_OPTS opts[] = { ARGPARSE_s_s (oGpgProgram, "gpg", "@"), ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"), ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"), + ARGPARSE_s_s (oDirectory, "directory", "|DIR|use DIR as top directory"), ARGPARSE_s_s (oFrom, "from", "|ADDR|use ADDR as the default sender"), ARGPARSE_s_s (oHeader, "header" , "|NAME=VALUE|add \"NAME: VALUE\" as header to all mails"), @@ -225,6 +227,9 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break; + case oDirectory: + opt.directory = pargs->r.ret_str; + break; case oFrom: opt.default_from = pargs->r.ret_str; break; @@ -350,6 +355,7 @@ main (int argc, char **argv) { log_error ("directory '%s' has too relaxed permissions\n", opt.directory); + log_info ("Fix by running: chmod o-rw '%s'\n", opt.directory); exit (2); } } @@ -1667,7 +1673,7 @@ command_receive_cb (void *opaque, const char *mediatype, -/* Return a list of all configured domains. ECh list element is the +/* Return a list of all configured domains. Each list element is the * top directory for the domain. To figure out the actual domain * name strrchr(name, '/') can be used. */ static gpg_error_t @@ -1946,7 +1952,17 @@ command_list_domains (void) if (!fp) { err = gpg_error_from_syserror (); - if (gpg_err_code (err) != GPG_ERR_ENOENT) + if (gpg_err_code (err) == GPG_ERR_ENOENT) + { + fp = es_fopen (fname, "w"); + if (!fp) + log_error ("domain %s: can't create policy file: %s\n", + domain, gpg_strerror (err)); + else + es_fclose (fp); + fp = NULL; + } + else log_error ("domain %s: error in policy file: %s\n", domain, gpg_strerror (err)); } @@ -1955,17 +1971,8 @@ command_list_domains (void) struct policy_flags_s policy; err = wks_parse_policy (&policy, fp, 0); es_fclose (fp); - if (!err) - { - struct policy_flags_s empty_policy; - memset (&empty_policy, 0, sizeof empty_policy); - if (!memcmp (&empty_policy, &policy, sizeof policy)) - log_error ("domain %s: empty policy file\n", domain); - } wks_free_policy (&policy); } - - } err = 0; From b83fed64f8051279a8f36e024c1f12f7f13c4716 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 26 Oct 2018 14:54:52 +0200 Subject: [PATCH 046/235] build: By default build wks-tools on all Unix platforms. Signed-off-by: Werner Koch --- configure.ac | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index f0e696618..795daa400 100644 --- a/configure.ac +++ b/configure.ac @@ -129,7 +129,10 @@ GNUPG_BUILD_PROGRAM(symcryptrun, no) # We use gpgtar to unpack test data, hence we always build it. If the # user opts out, we simply don't install it. GNUPG_BUILD_PROGRAM(gpgtar, yes) -GNUPG_BUILD_PROGRAM(wks-tools, no) +# We also install the gpg-wks-server tool by default but disable it +# later for platforms where it can't be build. +GNUPG_BUILD_PROGRAM(wks-tools, yes) + AC_SUBST(PACKAGE) AC_SUBST(PACKAGE_GT) @@ -681,6 +684,7 @@ case "${host}" in try_gettext="no" use_simple_gettext=yes mmap_needed=no + build_wks_tools=no ;; i?86-emx-os2 | i?86-*-os2*emx ) # OS/2 with the EMX environment @@ -688,6 +692,7 @@ case "${host}" in AC_DEFINE(HAVE_DRIVE_LETTERS) have_dosish_system=yes try_gettext="no" + build_wks_tools=no ;; i?86-*-msdosdjgpp*) @@ -696,6 +701,7 @@ case "${host}" in AC_DEFINE(HAVE_DRIVE_LETTERS) have_dosish_system=yes try_gettext="no" + build_wks_tools=no ;; *-*-hpux*) @@ -726,6 +732,7 @@ case "${host}" in # Android is fully utf-8 and we do not want to use iconv to # keeps things simple require_iconv=no + build_wks_tools=no ;; *-apple-darwin*) AC_DEFINE(_DARWIN_C_SOURCE, 900000L, From fd7aee6a97134fdf48a496841241f8ae25736e61 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 31 Oct 2018 08:20:37 +0900 Subject: [PATCH 047/235] build: Update *.m4 from libraries. * m4/gpg-error.m4: Update from master. * m4/ksba.m4: Ditto. * m4/libassuan.m4: Ditto. * m4/libgcrypt.m4: Ditto. * m4/npth.m4: Ditto. * m4/ntbtls.m4: Ditto. -- Signed-off-by: NIIBE Yutaka --- m4/gpg-error.m4 | 64 ++++++++++++++++------ m4/ksba.m4 | 69 +++++++++++++++++------- m4/libassuan.m4 | 137 +++++++++++++++++++++++------------------------- m4/libgcrypt.m4 | 42 +++++++++++---- m4/npth.m4 | 56 ++++++++++++++------ m4/ntbtls.m4 | 40 ++++++++++---- 6 files changed, 267 insertions(+), 141 deletions(-) diff --git a/m4/gpg-error.m4 b/m4/gpg-error.m4 index 8ba24b71a..0964a268f 100644 --- a/m4/gpg-error.m4 +++ b/m4/gpg-error.m4 @@ -1,5 +1,5 @@ # gpg-error.m4 - autoconf macro to detect libgpg-error. -# Copyright (C) 2002, 2003, 2004, 2011, 2014 g10 Code GmbH +# Copyright (C) 2002, 2003, 2004, 2011, 2014, 2018 g10 Code GmbH # # This file is free software; as a special exception the author gives # unlimited permission to copy and/or distribute it, with or without @@ -9,7 +9,7 @@ # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # -# Last-changed: 2014-10-02 +# Last-changed: 2018-10-29 dnl AM_PATH_GPG_ERROR([MINIMUM-VERSION, @@ -17,7 +17,7 @@ dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]]) dnl dnl Test for libgpg-error and define GPG_ERROR_CFLAGS, GPG_ERROR_LIBS, dnl GPG_ERROR_MT_CFLAGS, and GPG_ERROR_MT_LIBS. The _MT_ variants are -dnl used for programs requiring real multi thread support. +dnl used for programs requireing real multi thread support. dnl dnl If a prefix option is not used, the config script is first dnl searched in $SYSROOT/bin and then along $PATH. If the used @@ -61,16 +61,28 @@ AC_DEFUN([AM_PATH_GPG_ERROR], fi AC_PATH_PROG(GPG_ERROR_CONFIG, gpg-error-config, no) - min_gpg_error_version=ifelse([$1], ,0.0,$1) - AC_MSG_CHECKING(for GPG Error - version >= $min_gpg_error_version) + min_gpg_error_version=ifelse([$1], ,1.33,$1) ok=no - if test "$GPG_ERROR_CONFIG" != "no" \ - && test -f "$GPG_ERROR_CONFIG" ; then + + use_gpgrt_config="" + if test "$GPG_ERROR_CONFIG" = "no"; then + AC_PATH_PROG(GPGRT_CONFIG, gpgrt-config, no) + if test "$GPGRT_CONFIG" != "no"; then + GPGRT_CONFIG="$GPGRT_CONFIG --prefix=$prefix --exec-prefix=$exec_prefix --libdir=$libdir" + if $GPGRT_CONFIG gpg-error >/dev/null 2>&1; then + GPG_ERROR_CONFIG="$GPGRT_CONFIG gpg-error" + use_gpgrt_config=yes + gpg_error_config_version=`$GPG_ERROR_CONFIG --modversion` + fi + fi + else + gpg_error_config_version=`$GPG_ERROR_CONFIG --version` + fi + if test "$GPG_ERROR_CONFIG" != "no"; then req_major=`echo $min_gpg_error_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)/\1/'` req_minor=`echo $min_gpg_error_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)/\2/'` - gpg_error_config_version=`$GPG_ERROR_CONFIG $gpg_error_config_args --version` major=`echo $gpg_error_config_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'` minor=`echo $gpg_error_config_version | \ @@ -84,23 +96,45 @@ AC_DEFUN([AM_PATH_GPG_ERROR], fi fi fi + if test -z "$GPGRT_CONFIG"; then + if test "$major" -gt 1 -o "$major" -eq 1 -a "$minor" -ge 33; then + AC_PATH_PROG(GPGRT_CONFIG, gpgrt-config, no) + if test "$GPGRT_CONFIG" != "no"; then + GPGRT_CONFIG="$GPGRT_CONFIG --prefix=$prefix --exec-prefix=$exec_prefix --libdir=$libdir" + GPG_ERROR_CONFIG="$GPGRT_CONFIG gpg-error" + use_gpgrt_config=yes + fi + fi + fi fi + AC_MSG_CHECKING(for GPG Error - version >= $min_gpg_error_version) if test $ok = yes; then - GPG_ERROR_CFLAGS=`$GPG_ERROR_CONFIG $gpg_error_config_args --cflags` - GPG_ERROR_LIBS=`$GPG_ERROR_CONFIG $gpg_error_config_args --libs` - GPG_ERROR_MT_CFLAGS=`$GPG_ERROR_CONFIG $gpg_error_config_args --mt --cflags 2>/dev/null` - GPG_ERROR_MT_LIBS=`$GPG_ERROR_CONFIG $gpg_error_config_args --mt --libs 2>/dev/null` + GPG_ERROR_CFLAGS=`$GPG_ERROR_CONFIG --cflags` + GPG_ERROR_LIBS=`$GPG_ERROR_CONFIG --libs` + if test -z "$use_gpgrt_config"; then + GPG_ERROR_MT_CFLAGS=`$GPG_ERROR_CONFIG --mt --cflags 2>/dev/null` + GPG_ERROR_MT_LIBS=`$GPG_ERROR_CONFIG --mt --libs 2>/dev/null` + else + GPG_ERROR_MT_CFLAGS=`$GPG_ERROR_CONFIG --variable=mtcflags 2>/dev/null` + GPG_ERROR_MT_CFLAGS="$GPG_ERROR_CFLAGS${GPG_ERROR_CFLAGS:+ }$GPG_ERROR_MT_CFLAGS" + GPG_ERROR_MT_LIBS=`$GPG_ERROR_CONFIG --variable=mtlibs 2>/dev/null` + GPG_ERROR_MT_LIBS="$GPG_ERROR_LIBS${GPG_ERROR_LIBS:+ }$GPG_ERROR_MT_LIBS" + fi AC_MSG_RESULT([yes ($gpg_error_config_version)]) ifelse([$2], , :, [$2]) - gpg_error_config_host=`$GPG_ERROR_CONFIG $gpg_error_config_args --host 2>/dev/null || echo none` + if test -z "$use_gpgrt_config"; then + gpg_error_config_host=`$GPG_ERROR_CONFIG --host 2>/dev/null || echo none` + else + gpg_error_config_host=`$GPG_ERROR_CONFIG --variable=host 2>/dev/null || echo none` + fi if test x"$gpg_error_config_host" != xnone ; then if test x"$gpg_error_config_host" != x"$host" ; then AC_MSG_WARN([[ *** -*** The config script $GPG_ERROR_CONFIG was +*** The config script "$GPG_ERROR_CONFIG" was *** built for $gpg_error_config_host and thus may not match the *** used host $host. -*** You may want to use the configure option --with-gpg-error-prefix +*** You may want to use the configure option --with-libgpg-error-prefix *** to specify a matching config script or use \$SYSROOT. ***]]) gpg_config_script_warn="$gpg_config_script_warn libgpg-error" diff --git a/m4/ksba.m4 b/m4/ksba.m4 index 8cb49a391..ee4b96256 100644 --- a/m4/ksba.m4 +++ b/m4/ksba.m4 @@ -1,5 +1,5 @@ # ksba.m4 - autoconf macro to detect ksba -# Copyright (C) 2002 g10 Code GmbH +# Copyright (C) 2002, 2018 g10 Code GmbH # # This file is free software; as a special exception the author gives # unlimited permission to copy and/or distribute it, with or without @@ -13,27 +13,46 @@ dnl AM_PATH_KSBA([MINIMUM-VERSION, dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]]) dnl Test for libksba and define KSBA_CFLAGS and KSBA_LIBS -dnl MINIMUM-VERSION is a string with the version number optionally prefixed +dnl MINIMUN-VERSION is a string with the version number optionalliy prefixed dnl with the API version to also check the API compatibility. Example: -dnl a MINIMUM-VERSION of 1:1.0.7 won't pass the test unless the installed +dnl a MINIMUN-VERSION of 1:1.0.7 won't pass the test unless the installed dnl version of libksba is at least 1.0.7 *and* the API number is 1. Using -dnl this feature prevents building against newer versions of libksba +dnl this features allows to prevent build against newer versions of libksba dnl with a changed API. dnl AC_DEFUN([AM_PATH_KSBA], -[AC_REQUIRE([AC_CANONICAL_HOST]) - AC_ARG_WITH(ksba-prefix, - AC_HELP_STRING([--with-ksba-prefix=PFX], - [prefix where KSBA is installed (optional)]), +[ AC_REQUIRE([AC_CANONICAL_HOST]) + dnl --with-libksba-prefix=PFX is the preferred name for this option, + dnl since that is consistent with how our three siblings use the directory/ + dnl package name in --with-$dir_name-prefix=PFX. + AC_ARG_WITH(libksba-prefix, + AC_HELP_STRING([--with-libksba-prefix=PFX], + [prefix where KSBA is installed (optional)]), ksba_config_prefix="$withval", ksba_config_prefix="") + + dnl Accept --with-ksba-prefix and make it work the same as + dnl --with-libksba-prefix above, for backwards compatibility, + dnl but do not document this old, inconsistently-named option. + AC_ARG_WITH(ksba-prefix,, + ksba_config_prefix="$withval", ksba_config_prefix="") + if test x$ksba_config_prefix != x ; then - ksba_config_args="$ksba_config_args --prefix=$ksba_config_prefix" - if test x${KSBA_CONFIG+set} != xset ; then - KSBA_CONFIG=$ksba_config_prefix/bin/ksba-config - fi + if test x${KSBA_CONFIG+set} != xset ; then + KSBA_CONFIG=$ksba_config_prefix/bin/ksba-config + fi + fi + + use_gpgrt_config="" + if test x"$KSBA_CONFIG" = x -a x"$GPGRT_CONFIG" != x -a "$GPGRT_CONFIG" != "no"; then + if $GPGRT_CONFIG ksba --exists; then + KSBA_CONFIG="$GPGRT_CONFIG ksba" + use_gpgrt_config=yes + fi + fi + if test -z "$use_gpgrt_config"; then + AC_PATH_PROG(KSBA_CONFIG, ksba-config, no) fi - AC_PATH_PROG(KSBA_CONFIG, ksba-config, no) tmp=ifelse([$1], ,1:1.0.0,$1) if echo "$tmp" | grep ':' >/dev/null 2>/dev/null ; then req_ksba_api=`echo "$tmp" | sed 's/\(.*\):\(.*\)/\1/'` @@ -52,7 +71,11 @@ AC_DEFUN([AM_PATH_KSBA], sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\2/'` req_micro=`echo $min_ksba_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\3/'` - ksba_config_version=`$KSBA_CONFIG $ksba_config_args --version` + if test -z "$use_gpgrt_config"; then + ksba_config_version=`$KSBA_CONFIG --version` + else + ksba_config_version=`$KSBA_CONFIG --modversion` + fi major=`echo $ksba_config_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'` minor=`echo $ksba_config_version | \ @@ -84,7 +107,11 @@ AC_DEFUN([AM_PATH_KSBA], # Even if we have a recent libksba, we should check that the # API is compatible. if test "$req_ksba_api" -gt 0 ; then - tmp=`$KSBA_CONFIG --api-version 2>/dev/null || echo 0` + if test -z "$use_gpgrt_config"; then + tmp=`$KSBA_CONFIG --api-version 2>/dev/null || echo 0` + else + tmp=`$KSBA_CONFIG --variable=api_version 2>/dev/null || echo 0` + fi if test "$tmp" -gt 0 ; then AC_MSG_CHECKING([KSBA API version]) if test "$req_ksba_api" -eq "$tmp" ; then @@ -97,15 +124,19 @@ AC_DEFUN([AM_PATH_KSBA], fi fi if test $ok = yes; then - KSBA_CFLAGS=`$KSBA_CONFIG $ksba_config_args --cflags` - KSBA_LIBS=`$KSBA_CONFIG $ksba_config_args --libs` + KSBA_CFLAGS=`$KSBA_CONFIG --cflags` + KSBA_LIBS=`$KSBA_CONFIG --libs` ifelse([$2], , :, [$2]) - libksba_config_host=`$LIBKSBA_CONFIG $ksba_config_args --host 2>/dev/null || echo none` + if test -z "$use_gpgrt_config"; then + libksba_config_host=`$KSBA_CONFIG --host 2>/dev/null || echo none` + else + libksba_config_host=`$KSBA_CONFIG --variable=host 2>/dev/null || echo none` + fi if test x"$libksba_config_host" != xnone ; then if test x"$libksba_config_host" != x"$host" ; then AC_MSG_WARN([[ *** -*** The config script $LIBKSBA_CONFIG was +*** The config script "$KSBA_CONFIG" was *** built for $libksba_config_host and thus may not match the *** used host $host. *** You may want to use the configure option --with-libksba-prefix diff --git a/m4/libassuan.m4 b/m4/libassuan.m4 index 004eee392..52058ea85 100644 --- a/m4/libassuan.m4 +++ b/m4/libassuan.m4 @@ -1,5 +1,5 @@ dnl Autoconf macros for libassuan -dnl Copyright (C) 2002, 2003 Free Software Foundation, Inc. +dnl Copyright (C) 2002, 2003, 2011 Free Software Foundation, Inc. dnl dnl This file is free software; as a special exception the author gives dnl unlimited permission to copy and/or distribute it, with or without @@ -8,23 +8,34 @@ dnl dnl This file is distributed in the hope that it will be useful, but dnl WITHOUT ANY WARRANTY, to the extent permitted by law; without even the dnl implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +dnl SPDX-License-Identifier: FSFULLR dnl dnl Common code used for libassuan detection [internal] dnl Returns ok set to yes or no. dnl AC_DEFUN([_AM_PATH_LIBASSUAN_COMMON], -[ AC_ARG_WITH(libassuan-prefix, +[ AC_REQUIRE([AC_CANONICAL_HOST]) + AC_ARG_WITH(libassuan-prefix, AC_HELP_STRING([--with-libassuan-prefix=PFX], [prefix where LIBASSUAN is installed (optional)]), libassuan_config_prefix="$withval", libassuan_config_prefix="") if test x$libassuan_config_prefix != x ; then - libassuan_config_args="$libassuan_config_args --prefix=$libassuan_config_prefix" if test x${LIBASSUAN_CONFIG+set} != xset ; then LIBASSUAN_CONFIG=$libassuan_config_prefix/bin/libassuan-config fi fi - AC_PATH_PROG(LIBASSUAN_CONFIG, libassuan-config, no) + + use_gpgrt_config="" + if test x"${LIBASSUAN_CONFIG}" = x -a x"$GPGRT_CONFIG" != x -a "$GPGRT_CONFIG" != "no"; then + if $GPGRT_CONFIG libassuan --exists; then + LIBASSUAN_CONFIG="$GPGRT_CONFIG libassuan" + use_gpgrt_config=yes + fi + fi + if test -z "$use_gpgrt_config"; then + AC_PATH_PROG(LIBASSUAN_CONFIG, libassuan-config, no) + fi tmp=ifelse([$1], ,1:0.9.2,$1) if echo "$tmp" | grep ':' >/dev/null 2>/dev/null ; then @@ -35,55 +46,60 @@ AC_DEFUN([_AM_PATH_LIBASSUAN_COMMON], min_libassuan_version="$tmp" fi - if test "$LIBASSUAN_CONFIG" != "no" ; then - libassuan_version=`$LIBASSUAN_CONFIG --version` - fi - libassuan_version_major=`echo $libassuan_version | \ - sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'` - libassuan_version_minor=`echo $libassuan_version | \ - sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\2/'` - libassuan_version_micro=`echo $libassuan_version | \ - sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\3/'` - - AC_MSG_CHECKING(for LIBASSUAN ifelse([$2], ,,[$2 ])- version >= $min_libassuan_version) + AC_MSG_CHECKING(for LIBASSUAN - version >= $min_libassuan_version) ok=no - if test "$LIBASSUAN_CONFIG" != "no" ; then - ifelse([$2], ,,[if `$LIBASSUAN_CONFIG --thread=$2 2> /dev/null` ; then]) + if test "$LIBASSUAN_CONFIG" != "no"; then req_major=`echo $min_libassuan_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\1/'` req_minor=`echo $min_libassuan_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\2/'` req_micro=`echo $min_libassuan_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\3/'` - if test "$libassuan_version_major" -gt "$req_major"; then + + if test -z "$use_gpgrt_config"; then + libassuan_config_version=`$LIBASSUAN_CONFIG --version` + else + libassuan_config_version=`$LIBASSUAN_CONFIG --modversion` + fi + major=`echo $libassuan_config_version | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'` + minor=`echo $libassuan_config_version | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\2/'` + micro=`echo $libassuan_config_version | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\3/'` + + if test "$major" -gt "$req_major"; then ok=yes - else - if test "$libassuan_version_major" -eq "$req_major"; then - if test "$libassuan_version_minor" -gt "$req_minor"; then + else + if test "$major" -eq "$req_major"; then + if test "$minor" -gt "$req_minor"; then ok=yes else - if test "$libassuan_version_minor" -eq "$req_minor"; then - if test "$libassuan_version_micro" -ge "$req_micro"; then + if test "$minor" -eq "$req_minor"; then + if test "$micro" -ge "$req_micro"; then ok=yes fi fi fi fi fi - ifelse([$2], ,,[fi]) fi if test $ok = yes; then - AC_MSG_RESULT([yes ($libassuan_version)]) + AC_MSG_RESULT([yes ($libassuan_config_version)]) else AC_MSG_RESULT(no) fi if test $ok = yes; then if test "$req_libassuan_api" -gt 0 ; then - tmp=`$LIBASSUAN_CONFIG --api-version 2>/dev/null || echo 0` + if test -z "$use_gpgrt_config"; then + tmp=`$LIBASSUAN_CONFIG --api-version 2>/dev/null || echo 0` + else + tmp=`$LIBASSUAN_CONFIG --variable=api_version 2>/dev/null || echo 0` + fi if test "$tmp" -gt 0 ; then - AC_MSG_CHECKING([LIBASSUAN ifelse([$2], ,,[$2 ])API version]) + AC_MSG_CHECKING([LIBASSUAN API version]) if test "$req_libassuan_api" -eq "$tmp" ; then AC_MSG_RESULT(okay) else @@ -94,6 +110,27 @@ AC_DEFUN([_AM_PATH_LIBASSUAN_COMMON], fi fi + if test $ok = yes; then + if test x"$host" != x ; then + if test -z "$use_gpgrt_config"; then + libassuan_config_host=`$LIBASSUAN_CONFIG --host 2>/dev/null || echo none` + else + libassuan_config_host=`$LIBASSUAN_CONFIG --variable=host 2>/dev/null || echo none` + fi + if test x"$libassuan_config_host" != xnone ; then + if test x"$libassuan_config_host" != x"$host" ; then + AC_MSG_WARN([[ +*** +*** The config script "$LIBASSUAN_CONFIG" was +*** built for $libassuan_config_host and thus may not match the +*** used host $host. +*** You may want to use the configure option --with-libassuan-prefix +*** to specify a matching config script. +***]]) + fi + fi + fi + fi ]) dnl AM_CHECK_LIBASSUAN([MINIMUM-VERSION, @@ -120,8 +157,8 @@ dnl AC_DEFUN([AM_PATH_LIBASSUAN], [ _AM_PATH_LIBASSUAN_COMMON($1) if test $ok = yes; then - LIBASSUAN_CFLAGS=`$LIBASSUAN_CONFIG $libassuan_config_args --cflags` - LIBASSUAN_LIBS=`$LIBASSUAN_CONFIG $libassuan_config_args --libs` + LIBASSUAN_CFLAGS=`$LIBASSUAN_CONFIG --cflags` + LIBASSUAN_LIBS=`$LIBASSUAN_CONFIG --libs` ifelse([$2], , :, [$2]) else LIBASSUAN_CFLAGS="" @@ -131,45 +168,3 @@ AC_DEFUN([AM_PATH_LIBASSUAN], AC_SUBST(LIBASSUAN_CFLAGS) AC_SUBST(LIBASSUAN_LIBS) ]) - - -dnl AM_PATH_LIBASSUAN_PTH([MINIMUM-VERSION, -dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]]) -dnl Test for libassuan and define LIBASSUAN_PTH_CFLAGS and LIBASSUAN_PTH_LIBS -dnl -AC_DEFUN([AM_PATH_LIBASSUAN_PTH], -[ _AM_PATH_LIBASSUAN_COMMON($1,pth) - if test $ok = yes; then - LIBASSUAN_PTH_CFLAGS=`$LIBASSUAN_CONFIG $libassuan_config_args --thread=pth --cflags` - LIBASSUAN_PTH_LIBS=`$LIBASSUAN_CONFIG $libassuan_config_args --thread=pth --libs` - ifelse([$2], , :, [$2]) - else - LIBASSUAN_PTH_CFLAGS="" - LIBASSUAN_PTH_LIBS="" - ifelse([$3], , :, [$3]) - fi - AC_SUBST(LIBASSUAN_PTH_CFLAGS) - AC_SUBST(LIBASSUAN_PTH_LIBS) -]) - - -dnl AM_PATH_LIBASSUAN_PTHREAD([MINIMUM-VERSION, -dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]]) -dnl Test for libassuan and define LIBASSUAN_PTHREAD_CFLAGS -dnl and LIBASSUAN_PTHREAD_LIBS -dnl -AC_DEFUN([AM_PATH_LIBASSUAN_PTHREAD], -[ _AM_PATH_LIBASSUAN_COMMON($1,pthread) - if test $ok = yes; then - LIBASSUAN_PTHREAD_CFLAGS=`$LIBASSUAN_CONFIG $libassuan_config_args --thread=pthread --cflags` - LIBASSUAN_PTHREAD_LIBS=`$LIBASSUAN_CONFIG $libassuan_config_args --thread=pthread --libs` - ifelse([$2], , :, [$2]) - else - LIBASSUAN_PTHREAD_CFLAGS="" - LIBASSUAN_PTHREAD_LIBS="" - ifelse([$3], , :, [$3]) - fi - AC_SUBST(LIBASSUAN_PTHREAD_CFLAGS) - AC_SUBST(LIBASSUAN_PTHREAD_LIBS) -]) - diff --git a/m4/libgcrypt.m4 b/m4/libgcrypt.m4 index dd167f814..2646e96c5 100644 --- a/m4/libgcrypt.m4 +++ b/m4/libgcrypt.m4 @@ -1,5 +1,5 @@ # libgcrypt.m4 - Autoconf macros to detect libgcrypt -# Copyright (C) 2002, 2003, 2004, 2011, 2014 g10 Code GmbH +# Copyright (C) 2002, 2003, 2004, 2011, 2014, 2018 g10 Code GmbH # # This file is free software; as a special exception the author gives # unlimited permission to copy and/or distribute it, with or without @@ -9,17 +9,17 @@ # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # -# Last-changed: 2014-10-02 +# Last-changed: 2018-10-29 dnl AM_PATH_LIBGCRYPT([MINIMUM-VERSION, dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]]) dnl Test for libgcrypt and define LIBGCRYPT_CFLAGS and LIBGCRYPT_LIBS. -dnl MINIMUM-VERSION is a string with the version number optionally prefixed +dnl MINIMUN-VERSION is a string with the version number optionalliy prefixed dnl with the API version to also check the API compatibility. Example: -dnl a MINIMUM-VERSION of 1:1.2.5 won't pass the test unless the installed +dnl a MINIMUN-VERSION of 1:1.2.5 won't pass the test unless the installed dnl version of libgcrypt is at least 1.2.5 *and* the API number is 1. Using -dnl this feature prevents building against newer versions of libgcrypt +dnl this features allows to prevent build against newer versions of libgcrypt dnl with a changed API. dnl dnl If a prefix option is not used, the config script is first @@ -52,7 +52,17 @@ AC_DEFUN([AM_PATH_LIBGCRYPT], fi fi - AC_PATH_PROG(LIBGCRYPT_CONFIG, libgcrypt-config, no) + use_gpgrt_config="" + if test x"${LIBGCRYPT_CONFIG}" = x -a x"$GPGRT_CONFIG" != x -a "$GPGRT_CONFIG" != "no"; then + if $GPGRT_CONFIG libgcrypt --exists; then + LIBGCRYPT_CONFIG="$GPGRT_CONFIG libgcrypt" + use_gpgrt_config=yes + fi + fi + if test -z "$use_gpgrt_config"; then + AC_PATH_PROG(LIBGCRYPT_CONFIG, libgcrypt-config, no) + fi + tmp=ifelse([$1], ,1:1.2.0,$1) if echo "$tmp" | grep ':' >/dev/null 2>/dev/null ; then req_libgcrypt_api=`echo "$tmp" | sed 's/\(.*\):\(.*\)/\1/'` @@ -71,7 +81,11 @@ AC_DEFUN([AM_PATH_LIBGCRYPT], sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\2/'` req_micro=`echo $min_libgcrypt_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\3/'` - libgcrypt_config_version=`$LIBGCRYPT_CONFIG --version` + if test -z "$use_gpgrt_config"; then + libgcrypt_config_version=`$LIBGCRYPT_CONFIG --version` + else + libgcrypt_config_version=`$LIBGCRYPT_CONFIG --modversion` + fi major=`echo $libgcrypt_config_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'` minor=`echo $libgcrypt_config_version | \ @@ -103,7 +117,11 @@ AC_DEFUN([AM_PATH_LIBGCRYPT], # If we have a recent libgcrypt, we should also check that the # API is compatible if test "$req_libgcrypt_api" -gt 0 ; then - tmp=`$LIBGCRYPT_CONFIG --api-version 2>/dev/null || echo 0` + if test -z "$use_gpgrt_config"; then + tmp=`$LIBGCRYPT_CONFIG --api-version 2>/dev/null || echo 0` + else + tmp=`$LIBGCRYPT_CONFIG --variable=api_version 2>/dev/null || echo 0` + fi if test "$tmp" -gt 0 ; then AC_MSG_CHECKING([LIBGCRYPT API version]) if test "$req_libgcrypt_api" -eq "$tmp" ; then @@ -119,12 +137,16 @@ AC_DEFUN([AM_PATH_LIBGCRYPT], LIBGCRYPT_CFLAGS=`$LIBGCRYPT_CONFIG --cflags` LIBGCRYPT_LIBS=`$LIBGCRYPT_CONFIG --libs` ifelse([$2], , :, [$2]) - libgcrypt_config_host=`$LIBGCRYPT_CONFIG --host 2>/dev/null || echo none` + if test -z "$use_gpgrt_config"; then + libgcrypt_config_host=`$LIBGCRYPT_CONFIG --host 2>/dev/null || echo none` + else + libgcrypt_config_host=`$LIBGCRYPT_CONFIG --variable=host 2>/dev/null || echo none` + fi if test x"$libgcrypt_config_host" != xnone ; then if test x"$libgcrypt_config_host" != x"$host" ; then AC_MSG_WARN([[ *** -*** The config script $LIBGCRYPT_CONFIG was +*** The config script "$LIBGCRYPT_CONFIG" was *** built for $libgcrypt_config_host and thus may not match the *** used host $host. *** You may want to use the configure option --with-libgcrypt-prefix diff --git a/m4/npth.m4 b/m4/npth.m4 index 17c264491..7296cbc12 100644 --- a/m4/npth.m4 +++ b/m4/npth.m4 @@ -1,5 +1,5 @@ # npth.m4 - autoconf macro to detect NPTH. -# Copyright (C) 2002, 2003, 2004, 2011 g10 Code GmbH +# Copyright (C) 2002, 2003, 2004, 2011, 2018 g10 Code GmbH # # This file is free software; as a special exception the author gives # unlimited permission to copy and/or distribute it, with or without @@ -17,10 +17,24 @@ AC_DEFUN([_AM_PATH_NPTH_CONFIG], if test "x$npth_config_prefix" != x ; then NPTH_CONFIG="$npth_config_prefix/bin/npth-config" fi - AC_PATH_PROG(NPTH_CONFIG, npth-config, no) + + use_gpgrt_config="" + if test x"$NPTH_CONFIG" = x -a x"$GPGRT_CONFIG" != x -a "$GPGRT_CONFIG" != "no"; then + if $GPGRT_CONFIG npth --exists; then + NPTH_CONFIG="$GPGRT_CONFIG npth" + use_gpgrt_config=yes + fi + fi + if test -z "$use_gpgrt_config"; then + AC_PATH_PROG(NPTH_CONFIG, npth-config, no) + fi if test "$NPTH_CONFIG" != "no" ; then - npth_version=`$NPTH_CONFIG --version` + if test -z "$use_gpgrt_config"; then + npth_version=`$NPTH_CONFIG --version` + else + npth_version=`$NPTH_CONFIG --modversion` + fi fi npth_version_major=`echo $npth_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'` @@ -70,31 +84,39 @@ AC_DEFUN([AM_PATH_NPTH], AC_MSG_RESULT(no) fi if test $ok = yes; then - # If we have a recent NPTH, we should also check that the - # API is compatible. - if test "$req_npth_api" -gt 0 ; then + # If we have a recent NPTH, we should also check that the + # API is compatible. + if test "$req_npth_api" -gt 0 ; then + if test -z "$use_gpgrt_config"; then tmp=`$NPTH_CONFIG --api-version 2>/dev/null || echo 0` - if test "$tmp" -gt 0 ; then - AC_MSG_CHECKING([NPTH API version]) - if test "$req_npth_api" -eq "$tmp" ; then - AC_MSG_RESULT([okay]) - else - ok=no - AC_MSG_RESULT([does not match. want=$req_npth_api got=$tmp]) - fi + else + tmp=`$NPTH_CONFIG --variable=api_version 2>/dev/null || echo 0` + fi + if test "$tmp" -gt 0 ; then + AC_MSG_CHECKING([NPTH API version]) + if test "$req_npth_api" -eq "$tmp" ; then + AC_MSG_RESULT([okay]) + else + ok=no + AC_MSG_RESULT([does not match. want=$req_npth_api got=$tmp]) fi - fi + fi + fi fi if test $ok = yes; then NPTH_CFLAGS=`$NPTH_CONFIG --cflags` NPTH_LIBS=`$NPTH_CONFIG --libs` ifelse([$2], , :, [$2]) - npth_config_host=`$NPTH_CONFIG --host 2>/dev/null || echo none` + if test -z "$use_gpgrt_config"; then + npth_config_host=`$NPTH_CONFIG --host 2>/dev/null || echo none` + else + npth_config_host=`$NPTH_CONFIG --variable=host 2>/dev/null || echo none` + fi if test x"$npth_config_host" != xnone ; then if test x"$npth_config_host" != x"$host" ; then AC_MSG_WARN([[ *** -*** The config script $NPTH_CONFIG was +*** The config script "$NPTH_CONFIG" was *** built for $npth_config_host and thus may not match the *** used host $host. *** You may want to use the configure option --with-npth-prefix diff --git a/m4/ntbtls.m4 b/m4/ntbtls.m4 index 4a4b5da30..c4e07dc56 100644 --- a/m4/ntbtls.m4 +++ b/m4/ntbtls.m4 @@ -14,11 +14,11 @@ dnl AM_PATH_NTBTLS([MINIMUM-VERSION, dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]]) dnl dnl Test for NTBTLS and define NTBTLS_CFLAGS and NTBTLS_LIBS. -dnl MINIMUM-VERSION is a string with the version number optionally prefixed +dnl MINIMUN-VERSION is a string with the version number optionalliy prefixed dnl with the API version to also check the API compatibility. Example: -dnl a MINIMUM-VERSION of 1:1.2.5 won't pass the test unless the installed -dnl version of ntbtls is at least 1.2.5 *and* the API number is 1. Using -dnl this feature prevents building against newer versions of ntbtls +dnl a MINIMUN-VERSION of 1:1.2.5 won't pass the test unless the installed +dnl version of libgcrypt is at least 1.2.5 *and* the API number is 1. Using +dnl this features allows to prevent build against newer versions of libgcrypt dnl with a changed API. dnl AC_DEFUN([AM_PATH_NTBTLS], @@ -46,7 +46,17 @@ AC_DEFUN([AM_PATH_NTBTLS], fi fi - AC_PATH_PROG(NTBTLS_CONFIG, ntbtls-config, no) + use_gpgrt_config="" + if test x"${NTBTLS_CONFIG}" = x -a x"$GPGRT_CONFIG" != x -a "$GPGRT_CONFIG" != "no"; then + if $GPGRT_CONFIG ntbtls --exists; then + NTBTLS_CONFIG="$GPGRT_CONFIG ntbtls" + use_gpgrt_config=yes + fi + fi + if test -z "$use_gpgrt_config"; then + AC_PATH_PROG(NTBTLS_CONFIG, ntbtls-config, no) + fi + tmp=ifelse([$1], ,1:1.0.0,$1) if echo "$tmp" | grep ':' >/dev/null 2>/dev/null ; then req_ntbtls_api=`echo "$tmp" | sed 's/\(.*\):\(.*\)/\1/'` @@ -65,7 +75,11 @@ AC_DEFUN([AM_PATH_NTBTLS], sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\2/'` req_micro=`echo $min_ntbtls_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\3/'` - ntbtls_config_version=`$NTBTLS_CONFIG --version` + if test -z "$use_gpgrt_config"; then + ntbtls_config_version=`$NTBTLS_CONFIG --version` + else + ntbtls_config_version=`$NTBTLS_CONFIG --modversion` + fi major=`echo $ntbtls_config_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'` minor=`echo $ntbtls_config_version | \ @@ -97,7 +111,11 @@ AC_DEFUN([AM_PATH_NTBTLS], # If we have a recent ntbtls, we should also check that the # API is compatible if test "$req_ntbtls_api" -gt 0 ; then - tmp=`$NTBTLS_CONFIG --api-version 2>/dev/null || echo 0` + if test -z "$use_gpgrt_config"; then + tmp=`$NTBTLS_CONFIG --api-version 2>/dev/null || echo 0` + else + tmp=`$NTBTLS_CONFIG --variable=api_version 2>/dev/null || echo 0` + fi if test "$tmp" -gt 0 ; then AC_MSG_CHECKING([NTBTLS API version]) if test "$req_ntbtls_api" -eq "$tmp" ; then @@ -113,12 +131,16 @@ AC_DEFUN([AM_PATH_NTBTLS], NTBTLS_CFLAGS=`$NTBTLS_CONFIG --cflags` NTBTLS_LIBS=`$NTBTLS_CONFIG --libs` ifelse([$2], , :, [$2]) - ntbtls_config_host=`$NTBTLS_CONFIG --host 2>/dev/null || echo none` + if test -z "$use_gpgrt_config"; then + ntbtls_config_host=`$NTBTLS_CONFIG --host 2>/dev/null || echo none` + else + ntbtls_config_host=`$NTBTLS_CONFIG --variable=host 2>/dev/null || echo none` + fi if test x"$ntbtls_config_host" != xnone ; then if test x"$ntbtls_config_host" != x"$host" ; then AC_MSG_WARN([[ *** -*** The config script $NTBTLS_CONFIG was +*** The config script "$NTBTLS_CONFIG" was *** built for $ntbtls_config_host and thus may not match the *** used host $host. *** You may want to use the configure option --with-ntbtls-prefix From 8e84efbe35633275e260712ba38a505e3f9d0898 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Fri, 2 Nov 2018 13:06:43 +0900 Subject: [PATCH 048/235] build: Update *.m4 from libraries. * m4/gpg-error.m4: Update from master. * m4/ksba.m4: Ditto. * m4/libassuan.m4: Ditto. * m4/libgcrypt.m4: Ditto. * m4/npth.m4: Ditto. * m4/ntbtls.m4: Ditto. -- Do it again today. Signed-off-by: NIIBE Yutaka --- m4/gpg-error.m4 | 59 ++++++++++++++++++++++++++++++++++++++----------- m4/ksba.m4 | 1 + m4/libassuan.m4 | 1 + m4/libgcrypt.m4 | 3 ++- m4/npth.m4 | 1 + m4/ntbtls.m4 | 1 + 6 files changed, 52 insertions(+), 14 deletions(-) diff --git a/m4/gpg-error.m4 b/m4/gpg-error.m4 index 0964a268f..a9d572fb5 100644 --- a/m4/gpg-error.m4 +++ b/m4/gpg-error.m4 @@ -9,7 +9,7 @@ # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # -# Last-changed: 2018-10-29 +# Last-changed: 2018-11-02 dnl AM_PATH_GPG_ERROR([MINIMUM-VERSION, @@ -64,15 +64,42 @@ AC_DEFUN([AM_PATH_GPG_ERROR], min_gpg_error_version=ifelse([$1], ,1.33,$1) ok=no - use_gpgrt_config="" - if test "$GPG_ERROR_CONFIG" = "no"; then + if test "$prefix" = NONE ; then + prefix_option_expanded=/usr/local + else + prefix_option_expanded="$prefix" + fi + if test "$exec_prefix" = NONE ; then + exec_prefix_option_expanded=$prefix_option_expanded + else + exec_prefix_option_expanded=$(prefix=$prefix_option_expanded eval echo $exec_prefix) + fi + libdir_option_expanded=$(prefix=$prefix_option_expanded exec_prefix=$exec_prefix_option_expanded eval echo $libdir) + + if test -f $libdir_option_expanded/pkgconfig/gpg-error.pc; then + gpgrt_libdir=$libdir_option_expanded + else + if crt1_path=$(${CC:-cc} -print-file-name=crt1.o 2>/dev/null); then + if possible_libdir=$(cd ${crt1_path%/*} && pwd 2>/dev/null); then + if test -f $possible_libdir/pkgconfig/gpg-error.pc; then + gpgrt_libdir=$possible_libdir + fi + fi + fi + fi + + if test "$GPG_ERROR_CONFIG" = "no" -a -n "$gpgrt_libdir"; then AC_PATH_PROG(GPGRT_CONFIG, gpgrt-config, no) - if test "$GPGRT_CONFIG" != "no"; then - GPGRT_CONFIG="$GPGRT_CONFIG --prefix=$prefix --exec-prefix=$exec_prefix --libdir=$libdir" + if test "$GPGRT_CONFIG" = "no"; then + unset GPGRT_CONFIG + else + GPGRT_CONFIG="$GPGRT_CONFIG --libdir=$gpgrt_libdir" if $GPGRT_CONFIG gpg-error >/dev/null 2>&1; then GPG_ERROR_CONFIG="$GPGRT_CONFIG gpg-error" - use_gpgrt_config=yes + AC_MSG_NOTICE([Use gpgrt-config with $gpgrt_libdir as gpg-error-config]) gpg_error_config_version=`$GPG_ERROR_CONFIG --modversion` + else + unset GPGRT_CONFIG fi fi else @@ -96,13 +123,19 @@ AC_DEFUN([AM_PATH_GPG_ERROR], fi fi fi - if test -z "$GPGRT_CONFIG"; then + if test -z "$GPGRT_CONFIG" -a -n "$gpgrt_libdir"; then if test "$major" -gt 1 -o "$major" -eq 1 -a "$minor" -ge 33; then AC_PATH_PROG(GPGRT_CONFIG, gpgrt-config, no) - if test "$GPGRT_CONFIG" != "no"; then - GPGRT_CONFIG="$GPGRT_CONFIG --prefix=$prefix --exec-prefix=$exec_prefix --libdir=$libdir" - GPG_ERROR_CONFIG="$GPGRT_CONFIG gpg-error" - use_gpgrt_config=yes + if test "$GPGRT_CONFIG" = "no"; then + unset GPGRT_CONFIG + else + GPGRT_CONFIG="$GPGRT_CONFIG --libdir=$gpgrt_libdir" + if $GPGRT_CONFIG gpg-error >/dev/null 2>&1; then + GPG_ERROR_CONFIG="$GPGRT_CONFIG gpg-error" + AC_MSG_NOTICE([Use gpgrt-config with $gpgrt_libdir as gpg-error-config]) + else + unset GPGRT_CONFIG + fi fi fi fi @@ -111,7 +144,7 @@ AC_DEFUN([AM_PATH_GPG_ERROR], if test $ok = yes; then GPG_ERROR_CFLAGS=`$GPG_ERROR_CONFIG --cflags` GPG_ERROR_LIBS=`$GPG_ERROR_CONFIG --libs` - if test -z "$use_gpgrt_config"; then + if test -z "$GPGRT_CONFIG"; then GPG_ERROR_MT_CFLAGS=`$GPG_ERROR_CONFIG --mt --cflags 2>/dev/null` GPG_ERROR_MT_LIBS=`$GPG_ERROR_CONFIG --mt --libs 2>/dev/null` else @@ -122,7 +155,7 @@ AC_DEFUN([AM_PATH_GPG_ERROR], fi AC_MSG_RESULT([yes ($gpg_error_config_version)]) ifelse([$2], , :, [$2]) - if test -z "$use_gpgrt_config"; then + if test -z "$GPGRT_CONFIG"; then gpg_error_config_host=`$GPG_ERROR_CONFIG --host 2>/dev/null || echo none` else gpg_error_config_host=`$GPG_ERROR_CONFIG --variable=host 2>/dev/null || echo none` diff --git a/m4/ksba.m4 b/m4/ksba.m4 index ee4b96256..ad8de4f32 100644 --- a/m4/ksba.m4 +++ b/m4/ksba.m4 @@ -46,6 +46,7 @@ AC_DEFUN([AM_PATH_KSBA], if test x"$KSBA_CONFIG" = x -a x"$GPGRT_CONFIG" != x -a "$GPGRT_CONFIG" != "no"; then if $GPGRT_CONFIG ksba --exists; then KSBA_CONFIG="$GPGRT_CONFIG ksba" + AC_MSG_NOTICE([Use gpgrt-config as ksba-config]) use_gpgrt_config=yes fi fi diff --git a/m4/libassuan.m4 b/m4/libassuan.m4 index 52058ea85..4af2d04f5 100644 --- a/m4/libassuan.m4 +++ b/m4/libassuan.m4 @@ -30,6 +30,7 @@ AC_DEFUN([_AM_PATH_LIBASSUAN_COMMON], if test x"${LIBASSUAN_CONFIG}" = x -a x"$GPGRT_CONFIG" != x -a "$GPGRT_CONFIG" != "no"; then if $GPGRT_CONFIG libassuan --exists; then LIBASSUAN_CONFIG="$GPGRT_CONFIG libassuan" + AC_MSG_NOTICE([Use gpgrt-config as libassuan-config]) use_gpgrt_config=yes fi fi diff --git a/m4/libgcrypt.m4 b/m4/libgcrypt.m4 index 2646e96c5..40ea01c2c 100644 --- a/m4/libgcrypt.m4 +++ b/m4/libgcrypt.m4 @@ -9,7 +9,7 @@ # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # -# Last-changed: 2018-10-29 +# Last-changed: 2018-11-02 dnl AM_PATH_LIBGCRYPT([MINIMUM-VERSION, @@ -56,6 +56,7 @@ AC_DEFUN([AM_PATH_LIBGCRYPT], if test x"${LIBGCRYPT_CONFIG}" = x -a x"$GPGRT_CONFIG" != x -a "$GPGRT_CONFIG" != "no"; then if $GPGRT_CONFIG libgcrypt --exists; then LIBGCRYPT_CONFIG="$GPGRT_CONFIG libgcrypt" + AC_MSG_NOTICE([Use gpgrt-config as libgcrypt-config]) use_gpgrt_config=yes fi fi diff --git a/m4/npth.m4 b/m4/npth.m4 index 7296cbc12..7d3e73f00 100644 --- a/m4/npth.m4 +++ b/m4/npth.m4 @@ -22,6 +22,7 @@ AC_DEFUN([_AM_PATH_NPTH_CONFIG], if test x"$NPTH_CONFIG" = x -a x"$GPGRT_CONFIG" != x -a "$GPGRT_CONFIG" != "no"; then if $GPGRT_CONFIG npth --exists; then NPTH_CONFIG="$GPGRT_CONFIG npth" + AC_MSG_NOTICE([Use gpgrt-config as npth-config]) use_gpgrt_config=yes fi fi diff --git a/m4/ntbtls.m4 b/m4/ntbtls.m4 index c4e07dc56..7bbd5699c 100644 --- a/m4/ntbtls.m4 +++ b/m4/ntbtls.m4 @@ -50,6 +50,7 @@ AC_DEFUN([AM_PATH_NTBTLS], if test x"${NTBTLS_CONFIG}" = x -a x"$GPGRT_CONFIG" != x -a "$GPGRT_CONFIG" != "no"; then if $GPGRT_CONFIG ntbtls --exists; then NTBTLS_CONFIG="$GPGRT_CONFIG ntbtls" + AC_MSG_NOTICE([Use gpgrt-config as ntbtls-config]) use_gpgrt_config=yes fi fi From a3a5a2451924640588e5ecc03a1d4ba6a6ba94a5 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 5 Nov 2018 08:59:13 +0100 Subject: [PATCH 049/235] dirmngr: Fix LDAP port parsing. * dirmngr/misc.c (host_and_port_from_url): Fix bad port parsing and a segv for a missing slash after the host name. -- Reportted-by: Tomas Mraz GnuPG-bug-id: 4230 Signed-off-by: Werner Koch --- dirmngr/misc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dirmngr/misc.c b/dirmngr/misc.c index 9ad60d59f..9cedf911c 100644 --- a/dirmngr/misc.c +++ b/dirmngr/misc.c @@ -515,7 +515,7 @@ host_and_port_from_url (const char *url, int *port) if ((p = strchr (buf, '/'))) *p++ = 0; strlwr (buf); - if ((p = strchr (p, ':'))) + if ((p = strchr (buf, ':'))) { *p++ = 0; *port = atoi (p); From d7323bb2d957fbeb8192c0ecbd99b1d14d302912 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 5 Nov 2018 12:47:44 +0100 Subject: [PATCH 050/235] speedo: Remove obsolete configure option of gpgme. * build-aux/speedo.mk (speedo_pkg_gpgme_configure): Remove --disable-w32-qt option. -- This option is obsolete since GPGME 1.7 (in 2016) Signed-off-by: Werner Koch --- build-aux/speedo.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build-aux/speedo.mk b/build-aux/speedo.mk index 5ec582357..d0f97f3db 100644 --- a/build-aux/speedo.mk +++ b/build-aux/speedo.mk @@ -521,12 +521,12 @@ 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 \ + --enable-static --enable-w32-glib \ --with-gpg-error-prefix=$(idir) \ LDFLAGS=-L$(idir)/lib else speedo_pkg_gpgme_configure = \ - --disable-static --disable-w32-glib --disable-w32-qt \ + --disable-static --disable-w32-glib \ --with-gpg-error-prefix=$(idir) \ LDFLAGS=-L$(idir)/lib endif From e3a1e80d13487c9336640a99b2f6d385d7d6f55c Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 5 Nov 2018 20:58:27 +0100 Subject: [PATCH 051/235] wks: New option --with-colons for gpg-wks-client. * tools/gpg-wks.h (opt): Add field with_colons. * tools/gpg-wks-client.c (oWithColons): New const. (opts, parse_arguments): Add option --with-colons. (main): Change aSupported to take several domains in --with-colons mode. (command_send): Factor policy getting code out to ... (get_policy_and_sa): New function. (command_supported): Make use of new function. -- In addition to this the --create command now also supports a submission address only in the policy file. That means the submission-address file is not anymore required and can be replaced by the policy file. Signed-off-by: Werner Koch --- doc/wks.texi | 41 ++++++- tools/gpg-wks-client.c | 254 ++++++++++++++++++++++++++++++----------- tools/gpg-wks.h | 1 + tools/wks-util.c | 2 +- 4 files changed, 230 insertions(+), 68 deletions(-) diff --git a/doc/wks.texi b/doc/wks.texi index bd2b8d502..03d748255 100644 --- a/doc/wks.texi +++ b/doc/wks.texi @@ -65,7 +65,8 @@ site supports the Web Key Service. The argument is an arbitrary address in the to be tested domain. For example @file{foo@@example.net}. The command returns success if the Web Key Service is supported. The operation is silent; to get diagnostic -output use the option @option{--verbose}. +output use the option @option{--verbose}. See option +@option{--with-colons} for a variant of this command. With the @option{--check} command the caller can test whether a key exists for a supplied mail address. The command returns success if a @@ -109,6 +110,44 @@ $(gpgconf --list-dirs libexecdir)/gpg-wks-client --check foo@@example.net Directly send created mails using the @command{sendmail} command. Requires installation of that command. +@item --with-colons +@opindex with-colons +This option has currently only an effect on the @option{--supported} +command. If it is used all arguimenst on the command line are taken +as domain names and tested for WKD support. The output format is one +line per domain with colon delimited fields. The currently specified +fields are (future versions may specify additional fields): + +@table @asis + + @item 1 - domain + This is the domain name. Although quoting is not required for valid + domain names this field is specified to be quoted in standard C + manner. + + @item 2 - WKD + If the value is true the domain supports the Web Key Directory. + + @item 3 - WKS + If the value is true the domain supports the Web Key Service + protocol to upload keys to the directory. + + @item 4 - error-code + This may contain an gpg-error code to describe certain + failures. Use @samp{gpg-error CODE} to explain the code. + + @item 5 - protocol-version + The minimum protocol version supported by the server. + + @item 6 - auth-submit + The auth-submit flag from the policy file of the server. + + @item 7 - mailbox-only + The mailbox-only flag from the policy file of the server. +@end table + + + @item --output @var{file} @itemx -o @opindex output diff --git a/tools/gpg-wks-client.c b/tools/gpg-wks-client.c index 73945ff30..bf6b119e0 100644 --- a/tools/gpg-wks-client.c +++ b/tools/gpg-wks-client.c @@ -61,6 +61,7 @@ enum cmd_and_opt_values oSend, oFakeSubmissionAddr, oStatusFD, + oWithColons, oDummy }; @@ -90,6 +91,7 @@ static ARGPARSE_OPTS opts[] = { ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"), ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"), ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), + ARGPARSE_s_n (oWithColons, "with-colons", "@"), ARGPARSE_s_s (oFakeSubmissionAddr, "fake-submission-addr", "@"), @@ -204,6 +206,9 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) case oStatusFD: wks_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1)); break; + case oWithColons: + opt.with_colons = 1; + break; case aSupported: case aCreate: @@ -271,11 +276,20 @@ main (int argc, char **argv) switch (cmd) { case aSupported: - if (argc != 1) - wrong_args ("--supported USER-ID"); - err = command_supported (argv[0]); - if (err && gpg_err_code (err) != GPG_ERR_FALSE) - log_error ("checking support failed: %s\n", gpg_strerror (err)); + if (opt.with_colons) + { + for (; argc; argc--, argv++) + command_supported (*argv); + err = 0; + } + else + { + if (argc != 1) + wrong_args ("--supported DOMAIN"); + err = command_supported (argv[0]); + if (err && gpg_err_code (err) != GPG_ERR_FALSE) + log_error ("checking support failed: %s\n", gpg_strerror (err)); + } break; case aCreate: @@ -471,6 +485,134 @@ decrypt_stream (estream_t *r_output, struct decrypt_stream_parm_s *decinfo, } +/* Return the submission address for the address or just the domain in + * ADDRSPEC. The submission address is stored as a malloced string at + * R_SUBMISSION_ADDRESS. At R_POLICY the policy flags of the domain + * are stored. The caller needs to free them with wks_free_policy. + * The function returns an error code on failure to find a submission + * address or policy file. Note: The function may store NULL at + * R_SUBMISSION_ADDRESS but return success to indicate that the web + * key directory is supported but not the web key service. As per WKD + * specs a policy file is always required and will thus be return on + * success. */ +static gpg_error_t +get_policy_and_sa (const char *addrspec, int silent, + policy_flags_t *r_policy, char **r_submission_address) +{ + gpg_error_t err; + estream_t mbuf = NULL; + const char *domain; + const char *s; + policy_flags_t policy = NULL; + char *submission_to = NULL; + + *r_submission_address = NULL; + *r_policy = NULL; + + domain = strchr (addrspec, '@'); + if (domain) + domain++; + + if (opt.with_colons) + { + s = domain? domain : addrspec; + es_write_sanitized (es_stdout, s, strlen (s), ":", NULL); + es_putc (':', es_stdout); + } + + /* We first try to get the submission address from the policy file + * (this is the new method). If both are available we check that + * they match and print a warning if not. In the latter case we + * keep on using the one from the submission-address file. */ + err = wkd_get_policy_flags (addrspec, &mbuf); + if (err && gpg_err_code (err) != GPG_ERR_NO_DATA + && gpg_err_code (err) != GPG_ERR_NO_NAME) + { + if (!opt.with_colons) + log_error ("error reading policy flags for '%s': %s\n", + domain, gpg_strerror (err)); + goto leave; + } + if (!mbuf) + { + if (!opt.with_colons) + log_error ("provider for '%s' does NOT support the Web Key Directory\n", + addrspec); + err = gpg_error (GPG_ERR_FALSE); + goto leave; + } + + policy = xtrycalloc (1, sizeof *policy); + if (!policy) + err = gpg_error_from_syserror (); + else + err = wks_parse_policy (policy, mbuf, 1); + es_fclose (mbuf); + mbuf = NULL; + if (err) + goto leave; + + err = wkd_get_submission_address (addrspec, &submission_to); + if (err && !policy->submission_address) + { + if (!silent && !opt.with_colons) + log_error (_("error looking up submission address for domain '%s'" + ": %s\n"), domain, gpg_strerror (err)); + if (!silent && gpg_err_code (err) == GPG_ERR_NO_DATA && !opt.with_colons) + log_error (_("this domain probably doesn't support WKS.\n")); + goto leave; + } + + if (submission_to && policy->submission_address + && ascii_strcasecmp (submission_to, policy->submission_address)) + log_info ("Warning: different submission addresses (sa=%s, po=%s)\n", + submission_to, policy->submission_address); + + if (!submission_to && policy->submission_address) + { + submission_to = xtrystrdup (policy->submission_address); + if (!submission_to) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + leave: + *r_submission_address = submission_to; + submission_to = NULL; + *r_policy = policy; + policy = NULL; + + if (opt.with_colons) + { + if (*r_policy && !*r_submission_address) + es_fprintf (es_stdout, "1:0::"); + else if (*r_policy && *r_submission_address) + es_fprintf (es_stdout, "1:1::"); + else if (err && !(gpg_err_code (err) == GPG_ERR_FALSE + || gpg_err_code (err) == GPG_ERR_NO_DATA + || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST)) + es_fprintf (es_stdout, "0:0:%d:", err); + else + es_fprintf (es_stdout, "0:0::"); + if (*r_policy) + { + es_fprintf (es_stdout, "%u:%u:%u:", + (*r_policy)->protocol_version, + (*r_policy)->auth_submit, + (*r_policy)->mailbox_only); + } + es_putc ('\n', es_stdout); + } + + xfree (submission_to); + wks_free_policy (policy); + xfree (policy); + es_fclose (mbuf); + return err; +} + /* Check whether the provider supports the WKS protocol. */ @@ -480,6 +622,7 @@ command_supported (char *userid) gpg_error_t err; char *addrspec = NULL; char *submission_to = NULL; + policy_flags_t policy = NULL; if (!strchr (userid, '@')) { @@ -497,24 +640,41 @@ command_supported (char *userid) } /* Get the submission address. */ - err = wkd_get_submission_address (addrspec, &submission_to); - if (err) + err = get_policy_and_sa (addrspec, 1, &policy, &submission_to); + if (err || !submission_to) { - if (gpg_err_code (err) == GPG_ERR_NO_DATA - || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST) + if (!submission_to + || gpg_err_code (err) == GPG_ERR_FALSE + || gpg_err_code (err) == GPG_ERR_NO_DATA + || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST + ) { - if (opt.verbose) - log_info ("provider for '%s' does NOT support WKS (%s)\n", - addrspec, gpg_strerror (err)); + /* FALSE is returned if we already figured out that even the + * Web Key Directory is not supported and thus printed an + * error message. */ + if (opt.verbose && gpg_err_code (err) != GPG_ERR_FALSE + && !opt.with_colons) + { + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + log_info ("provider for '%s' does NOT support WKS\n", + addrspec); + else + log_info ("provider for '%s' does NOT support WKS (%s)\n", + addrspec, gpg_strerror (err)); + } err = gpg_error (GPG_ERR_FALSE); - log_inc_errorcount (); + if (!opt.with_colons) + log_inc_errorcount (); } goto leave; } - if (opt.verbose) + + if (opt.verbose && !opt.with_colons) log_info ("provider for '%s' supports WKS\n", addrspec); leave: + wks_free_policy (policy); + xfree (policy); xfree (submission_to); xfree (addrspec); return err; @@ -628,7 +788,7 @@ command_send (const char *fingerprint, const char *userid) estream_t keyenc = NULL; char *submission_to = NULL; mime_maker_t mime = NULL; - struct policy_flags_s policy; + policy_flags_t policy = NULL; int no_encrypt = 0; int posteo_hack = 0; const char *domain; @@ -636,8 +796,6 @@ command_send (const char *fingerprint, const char *userid) uidinfo_list_t uid, thisuid; time_t thistime; - memset (&policy, 0, sizeof policy); - if (classify_user_id (fingerprint, &desc, 1) || !(desc.mode == KEYDB_SEARCH_MODE_FPR || desc.mode == KEYDB_SEARCH_MODE_FPR20)) @@ -665,62 +823,26 @@ command_send (const char *fingerprint, const char *userid) /* Get the submission address. */ if (fake_submission_addr) { + policy = xcalloc (1, sizeof *policy); submission_to = xstrdup (fake_submission_addr); err = 0; } else { - /* We first try to get the submission address from the policy - * file (this is the new method). If both are available we - * check that they match and print a warning if not. In the - * latter case we keep on using the one from the - * submission-address file. */ - estream_t mbuf; - - err = wkd_get_policy_flags (addrspec, &mbuf); - if (err && gpg_err_code (err) != GPG_ERR_NO_DATA) - { - log_error ("error reading policy flags for '%s': %s\n", - domain, gpg_strerror (err)); - goto leave; - } - if (mbuf) - { - err = wks_parse_policy (&policy, mbuf, 1); - es_fclose (mbuf); - if (err) - goto leave; - } - - err = wkd_get_submission_address (addrspec, &submission_to); - if (err && !policy.submission_address) - { - log_error (_("error looking up submission address for domain '%s'" - ": %s\n"), domain, gpg_strerror (err)); - if (gpg_err_code (err) == GPG_ERR_NO_DATA) - log_error (_("this domain probably doesn't support WKS.\n")); - goto leave; - } - - if (submission_to && policy.submission_address - && ascii_strcasecmp (submission_to, policy.submission_address)) - log_info ("Warning: different submission addresses (sa=%s, po=%s)\n", - submission_to, policy.submission_address); - + err = get_policy_and_sa (addrspec, 0, &policy, &submission_to); + if (err) + goto leave; if (!submission_to) { - submission_to = xtrystrdup (policy.submission_address); - if (!submission_to) - { - err = gpg_error_from_syserror (); - goto leave; - } + log_error (_("this domain probably doesn't support WKS.\n")); + err = gpg_error (GPG_ERR_NO_DATA); + goto leave; } } log_info ("submitting request to '%s'\n", submission_to); - if (policy.auth_submit) + if (policy->auth_submit) log_info ("no confirmation required for '%s'\n", addrspec); /* In case the key has several uids with the same addr-spec we will @@ -738,8 +860,7 @@ command_send (const char *fingerprint, const char *userid) { if (!uid->mbox) continue; /* Should not happen anyway. */ - if (policy.mailbox_only - && ascii_strcasecmp (uid->uid, uid->mbox)) + if (policy->mailbox_only && ascii_strcasecmp (uid->uid, uid->mbox)) continue; /* UID has more than just the mailbox. */ if (uid->created > thistime) { @@ -770,7 +891,7 @@ command_send (const char *fingerprint, const char *userid) key = newkey; } - if (policy.mailbox_only + if (policy->mailbox_only && (!thisuid->mbox || ascii_strcasecmp (thisuid->uid, thisuid->mbox))) { log_info ("Warning: policy requires 'mailbox-only'" @@ -791,7 +912,7 @@ command_send (const char *fingerprint, const char *userid) /* Hack to support posteo but let them disable this by setting the * new policy-version flag. */ - if (policy.protocol_version < 3 + if (policy->protocol_version < 3 && !ascii_strcasecmp (domain, "posteo.de")) { log_info ("Warning: Using draft-1 method for domain '%s'\n", domain); @@ -908,7 +1029,8 @@ command_send (const char *fingerprint, const char *userid) free_uidinfo_list (uidlist); es_fclose (keyenc); es_fclose (key); - wks_free_policy (&policy); + wks_free_policy (policy); + xfree (policy); xfree (addrspec); return err; } diff --git a/tools/gpg-wks.h b/tools/gpg-wks.h index 1b91b6504..fba73f085 100644 --- a/tools/gpg-wks.h +++ b/tools/gpg-wks.h @@ -36,6 +36,7 @@ struct unsigned int debug; int quiet; int use_sendmail; + int with_colons; const char *output; const char *gpg_program; const char *directory; diff --git a/tools/wks-util.c b/tools/wks-util.c index 862cd33a3..729098a02 100644 --- a/tools/wks-util.c +++ b/tools/wks-util.c @@ -556,7 +556,7 @@ wks_send_mime (mime_maker_t mime) /* Parse the policy flags by reading them from STREAM and storing them - * into FLAGS. If IGNORE_UNKNOWN is iset unknown keywords are + * into FLAGS. If IGNORE_UNKNOWN is set unknown keywords are * ignored. */ gpg_error_t wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown) From 01b77ebbb71d47ba276d3a1af9595fdcd9b48f5f Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 6 Nov 2018 15:28:43 +0900 Subject: [PATCH 052/235] g10: Fix print_keygrip for smartcard. * g10/card-util.c (print_keygrip): Use tty_fprintf. -- Reported-by: Joey Pabalinas Signed-off-by: NIIBE Yutaka --- g10/card-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/g10/card-util.c b/g10/card-util.c index 5205798b6..a1a099d85 100644 --- a/g10/card-util.c +++ b/g10/card-util.c @@ -276,7 +276,7 @@ print_keygrip (estream_t fp, const unsigned char *grp) { tty_fprintf (fp, " keygrip ....: "); for (i=0; i < 20 ; i++, grp++) - es_fprintf (fp, "%02X", *grp); + tty_fprintf (fp, "%02X", *grp); tty_fprintf (fp, "\n"); } } From 7fc3decc2e038be905d47701c7ce196ed86a725b Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 8 Nov 2018 12:14:23 +0900 Subject: [PATCH 053/235] g10: Fix log_debug formatting. * g10/cipher-aead.c (do_flush): No cast is correct. * g10/decrypt-data.c (aead_underflow): No cast needed. Use "%j" for uint64_t for chunklen. Signed-off-by: NIIBE Yutaka --- g10/cipher-aead.c | 2 +- g10/decrypt-data.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/g10/cipher-aead.c b/g10/cipher-aead.c index f9a996c80..b14b85444 100644 --- a/g10/cipher-aead.c +++ b/g10/cipher-aead.c @@ -278,7 +278,7 @@ do_flush (cipher_filter_context_t *cfx, iobuf_t a, byte *buf, size_t size) if (DBG_FILTER) log_debug ("chunksize %ju reached;" " cur buflen=%zu using %zu of %zu\n", - (uintmax_t)cfx->chunksize, (uintmax_t)cfx->buflen, + cfx->chunksize, cfx->buflen, n1, n); n = n1; } diff --git a/g10/decrypt-data.c b/g10/decrypt-data.c index 3951fa794..61e01129c 100644 --- a/g10/decrypt-data.c +++ b/g10/decrypt-data.c @@ -647,7 +647,7 @@ aead_underflow (decode_filter_ctx_t dfx, iobuf_t a, byte *buf, size_t *ret_len) * case when a chunk ends within the buffer. */ if (DBG_FILTER) log_debug ("decrypt: chunklen=%ju total=%ju size=%zu len=%zu%s\n", - (uintmax_t)dfx->chunklen, (uintmax_t)dfx->total, size, len, + dfx->chunklen, dfx->total, size, len, dfx->eof_seen? " eof":""); while (len && dfx->chunklen + len >= dfx->chunksize) @@ -683,7 +683,7 @@ aead_underflow (decode_filter_ctx_t dfx, iobuf_t a, byte *buf, size_t *ret_len) len -= n; if (DBG_FILTER) - log_debug ("ndecrypted: %zu (nchunk=%zu) bytes left: %zu at off=%zu\n", + log_debug ("ndecrypted: %zu (nchunk=%ju) bytes left: %zu at off=%zu\n", totallen, dfx->chunklen, len, off); /* Check the tag. */ @@ -765,7 +765,7 @@ aead_underflow (decode_filter_ctx_t dfx, iobuf_t a, byte *buf, size_t *ret_len) dfx->chunklen += len; dfx->total += len; if (DBG_FILTER) - log_debug ("ndecrypted: %zu (nchunk=%zu)\n", totallen, dfx->chunklen); + log_debug ("ndecrypted: %zu (nchunk=%ju)\n", totallen, dfx->chunklen); } if (dfx->eof_seen) From 69930f6884a934207f7aa523cf6d2b8e22dfe666 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 8 Nov 2018 20:52:38 +0900 Subject: [PATCH 054/235] gpgcompose: Fix --sk-esk. * g10/gpgcompose.c (sk_esk): Copy the result content correctly. Don't forget to free the result. -- Fixes-commit: 0131d4369a81a51bf7bb328cc81a3bb082ed1a94 Signed-off-by: NIIBE Yutaka --- g10/gpgcompose.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/g10/gpgcompose.c b/g10/gpgcompose.c index b3f7ecdce..9b6901599 100644 --- a/g10/gpgcompose.c +++ b/g10/gpgcompose.c @@ -2281,16 +2281,27 @@ sk_esk (const char *option, int argc, char *argv[], void *cookie) /* Encrypt the session key using the s2k specifier. */ { DEK *sesdekp = &sesdek; + void *enckey; + size_t enckeylen; /* Now encrypt the session key (or rather, the algorithm used to - encrypt the SKESK plus the session key) using ENCKEY. */ - err = encrypt_seskey (&s2kdek, 0, &sesdekp, - (void**)&ske->seskey, (size_t *)&ske->seskeylen); + encrypt the SKESK plus the session key) using S2KDEK. */ + err = encrypt_seskey (&s2kdek, 0, &sesdekp, &enckey, &enckeylen); + if (err) log_fatal ("encrypt_seskey failed: %s\n", gpg_strerror (err)); + if (enckeylen - 1 > sesdek.keylen) + log_fatal ("key size is too big: %z\n", enckeylen); + else + { + ske->seskeylen = (byte)enckeylen; + memcpy (ske->seskey, enckey, enckeylen); + } + /* Save the session key for later. */ session_key = sesdek; + xfree (enckey); } pkt.pkttype = PKT_SYMKEY_ENC; From e2b9095de35ac4d402b077d5484b4131700a9925 Mon Sep 17 00:00:00 2001 From: Jussi Kivilinna Date: Thu, 8 Nov 2018 21:31:12 +0200 Subject: [PATCH 055/235] g10/decrypt-data: use fill_buffer in more places * g10/decrypt-data.c (mdc_decode_filter, decode_filter): Use fill_buffer. -- Signed-off-by: Jussi Kivilinna --- g10/decrypt-data.c | 82 +++------------------------------------------- 1 file changed, 5 insertions(+), 77 deletions(-) diff --git a/g10/decrypt-data.c b/g10/decrypt-data.c index 61e01129c..a3c9b800b 100644 --- a/g10/decrypt-data.c +++ b/g10/decrypt-data.c @@ -873,7 +873,6 @@ mdc_decode_filter (void *opaque, int control, IOBUF a, decode_filter_ctx_t dfx = opaque; size_t n, size = *ret_len; int rc = 0; - int c; /* Note: We need to distinguish between a partial and a fixed length packet. The first is the usual case as created by GPG. However @@ -894,25 +893,7 @@ mdc_decode_filter (void *opaque, int control, IOBUF a, log_assert (size > 44); /* Our code requires at least this size. */ /* Get at least 22 bytes and put it ahead in the buffer. */ - if (dfx->partial) - { - for (n=22; n < 44; n++) - { - if ( (c = iobuf_get(a)) == -1 ) - break; - buf[n] = c; - } - } - else - { - for (n=22; n < 44 && dfx->length; n++, dfx->length--) - { - c = iobuf_get (a); - if (c == -1) - break; /* Premature EOF. */ - buf[n] = c; - } - } + n = fill_buffer (dfx, a, buf, 44, 22); if (n == 44) { /* We have enough stuff - flush the holdback buffer. */ @@ -923,37 +904,11 @@ mdc_decode_filter (void *opaque, int control, IOBUF a, } else { - memcpy (buf, dfx->holdback, 22); } + /* Fill up the buffer. */ - if (dfx->partial) - { - for (; n < size; n++ ) - { - if ( (c = iobuf_get(a)) == -1 ) - { - dfx->eof_seen = 1; /* Normal EOF. */ - break; - } - buf[n] = c; - } - } - else - { - for (; n < size && dfx->length; n++, dfx->length--) - { - c = iobuf_get(a); - if (c == -1) - { - dfx->eof_seen = 3; /* Premature EOF. */ - break; - } - buf[n] = c; - } - if (!dfx->length) - dfx->eof_seen = 1; /* Normal EOF. */ - } + n = fill_buffer (dfx, a, buf, size, n); /* Move the trailing 22 bytes back to the holdback buffer. We have at least 44 bytes thus a memmove is not needed. */ @@ -1008,7 +963,7 @@ decode_filter( void *opaque, int control, IOBUF a, byte *buf, size_t *ret_len) decode_filter_ctx_t fc = opaque; size_t size = *ret_len; size_t n; - int c, rc = 0; + int rc = 0; if ( control == IOBUFCTRL_UNDERFLOW && fc->eof_seen ) @@ -1020,34 +975,7 @@ decode_filter( void *opaque, int control, IOBUF a, byte *buf, size_t *ret_len) { log_assert (a); - if (fc->partial) - { - for (n=0; n < size; n++ ) - { - c = iobuf_get(a); - if (c == -1) - { - fc->eof_seen = 1; /* Normal EOF. */ - break; - } - buf[n] = c; - } - } - else - { - for (n=0; n < size && fc->length; n++, fc->length--) - { - c = iobuf_get(a); - if (c == -1) - { - fc->eof_seen = 3; /* Premature EOF. */ - break; - } - buf[n] = c; - } - if (!fc->length) - fc->eof_seen = 1; /* Normal EOF. */ - } + n = fill_buffer (fc, a, buf, size, 0); if (n) { if (fc->cipher_hd) From 5d6c080522e1666943b75c99124fb69b985b6941 Mon Sep 17 00:00:00 2001 From: Jussi Kivilinna Date: Thu, 8 Nov 2018 21:31:12 +0200 Subject: [PATCH 056/235] g10/decrypt-data: use iobuf_read for higher performance * g10/decrypt-data.c (fill_buffer): Use iobuf_read instead of iobuf_get for reading data. -- This patch reduces iobuf_read per byte processing overhead and speeds up decryption. Benchmark results below, tested on Intel Core i7-4790K (turbo off). Encrypted 2 GiB through pipe to ramfs file using AES128. Decrypt ramfs file out through pipe to /dev/null. before patch-set ---------------- gpg process no-armor: user time pipe transfer rate encrypt-aead: 1.02 1.0 GB/s decrypt-aead: 10.8 185 MB/s encrypt-cfb: 4.8 342 MB/s decrypt-cfb: 12.7 157 MB/s gpg process armor: user time pipe transfer rate encrypt-aead: 13.8 140 MB/s decrypt-aead: 30.6 68 MB/s encrypt-cfb: 17.4 114 MB/s decrypt-cfb: 32.6 64 MB/s after (decrypt opt) ------------------- gpg process no-armor: user time pipe transfer rate decrypt-aead: 7.3 263 MB/s decrypt-cfb: 9.3 211 MB/s gpg process armor: user time pipe transfer rate decrypt-aead: 27.0 77 MB/s decrypt-cfb: 29.0 72 MB/s Note: decryption results are much slower than encryption because of extra SHA1 & RIPEMD160 hashing. GnuPG-bug-id: 3786 Signed-off-by: Jussi Kivilinna --- g10/decrypt-data.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/g10/decrypt-data.c b/g10/decrypt-data.c index a3c9b800b..4d9dc86d9 100644 --- a/g10/decrypt-data.c +++ b/g10/decrypt-data.c @@ -551,31 +551,42 @@ fill_buffer (decode_filter_ctx_t dfx, iobuf_t stream, byte *buffer, size_t nbytes, size_t offset) { size_t nread = offset; - int c; + size_t curr; + int ret; if (dfx->partial) { - for (; nread < nbytes; nread++ ) + while (nread < nbytes) { - if ((c = iobuf_get (stream)) == -1) + curr = nbytes - nread; + + ret = iobuf_read (stream, &buffer[nread], curr); + if (ret == -1) { dfx->eof_seen = 1; /* Normal EOF. */ break; } - buffer[nread] = c; + + nread += ret; } } else { - for (; nread < nbytes && dfx->length; nread++, dfx->length--) + while (nread < nbytes && dfx->length) { - c = iobuf_get (stream); - if (c == -1) + curr = nbytes - nread; + if (curr > dfx->length) + curr = dfx->length; + + ret = iobuf_read (stream, &buffer[nread], curr); + if (ret == -1) { dfx->eof_seen = 3; /* Premature EOF. */ break; } - buffer[nread] = c; + + nread += ret; + dfx->length -= ret; } if (!dfx->length) dfx->eof_seen = 1; /* Normal EOF. */ From a571bb8df52d6f2727876e086790dd037c9948ad Mon Sep 17 00:00:00 2001 From: Jussi Kivilinna Date: Thu, 8 Nov 2018 21:31:12 +0200 Subject: [PATCH 057/235] g10/armor: fix eof checks in radix64_read * g10/armor.c (radix64_read): Check EOF with '!afx->buffer_len' instead of 'c == -1', as 'c' is never set to this value. -- Signed-off-by: Jussi Kivilinna --- g10/armor.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/g10/armor.c b/g10/armor.c index 98b870ab9..1b02a231f 100644 --- a/g10/armor.c +++ b/g10/armor.c @@ -793,7 +793,7 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, byte *buf, size_t size ) { byte val; - int c=0, c2; /*init c because gcc is not clever enough for the continue*/ + int c, c2; int checkcrc=0; int rc = 0; size_t n = 0; @@ -911,7 +911,7 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, continue; break; } - if( c == -1 ) + if( !afx->buffer_len ) log_error(_("premature eof (no CRC)\n")); else { u32 mycrc = 0; @@ -945,7 +945,7 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, if( !afx->buffer_len ) break; /* eof */ } while( ++idx < 4 ); - if( c == -1 ) { + if( !afx->buffer_len ) { log_info(_("premature eof (in CRC)\n")); rc = invalid_crc(); } From 47424881b27d4b3bae2925265b2008cda0c2933f Mon Sep 17 00:00:00 2001 From: Jussi Kivilinna Date: Thu, 8 Nov 2018 21:31:12 +0200 Subject: [PATCH 058/235] g10/armor: remove unused unarmor_pump code * g10/armor.c (unarmor_state_e, unarmor_pump_s, unarmor_pump_new) (unarmor_pump_release, unarmor_pump): Remove. * g10/filter.h (UnarmorPump, unarmor_pump_new, unarmor_pump_release) (unarmor_pump): Remove. -- Signed-off-by: Jussi Kivilinna --- g10/armor.c | 218 --------------------------------------------------- g10/filter.h | 5 -- 2 files changed, 223 deletions(-) diff --git a/g10/armor.c b/g10/armor.c index 1b02a231f..714282f45 100644 --- a/g10/armor.c +++ b/g10/armor.c @@ -1350,221 +1350,3 @@ make_radix64_string( const byte *data, size_t len ) *p = 0; return buffer; } - - -/*********************************************** - * For the pipemode command we can't use the armor filter for various - * reasons, so we use this new unarmor_pump stuff to remove the armor - */ - -enum unarmor_state_e { - STA_init = 0, - STA_bypass, - STA_wait_newline, - STA_wait_dash, - STA_first_dash, - STA_compare_header, - STA_found_header_wait_newline, - STA_skip_header_lines, - STA_skip_header_lines_non_ws, - STA_read_data, - STA_wait_crc, - STA_read_crc, - STA_ready -}; - -struct unarmor_pump_s { - enum unarmor_state_e state; - byte val; - int checkcrc; - int pos; /* counts from 0..3 */ - u32 crc; - u32 mycrc; /* the one store in the data */ -}; - - - -UnarmorPump -unarmor_pump_new (void) -{ - UnarmorPump x; - - if( !is_initialized ) - initialize(); - x = xmalloc_clear (sizeof *x); - return x; -} - -void -unarmor_pump_release (UnarmorPump x) -{ - xfree (x); -} - -/* - * Get the next character from the ascii armor taken from the IOBUF - * created earlier by unarmor_pump_new(). - * Return: c = Character - * 256 = ignore this value - * -1 = End of current armor - * -2 = Premature EOF (not used) - * -3 = Invalid armor - */ -int -unarmor_pump (UnarmorPump x, int c) -{ - int rval = 256; /* default is to ignore the return value */ - - switch (x->state) { - case STA_init: - { - byte tmp[2]; - tmp[0] = c; - tmp[1] = 0; - if ( is_armored (tmp) ) - x->state = c == '-'? STA_first_dash : STA_wait_newline; - else { - x->state = STA_bypass; - return c; - } - } - break; - case STA_bypass: - return c; /* return here to avoid crc calculation */ - case STA_wait_newline: - if (c == '\n') - x->state = STA_wait_dash; - break; - case STA_wait_dash: - x->state = c == '-'? STA_first_dash : STA_wait_newline; - break; - case STA_first_dash: /* just need for initialization */ - x->pos = 0; - x->state = STA_compare_header; /* fall through */ - case STA_compare_header: - if ( "-----BEGIN PGP SIGNATURE-----"[++x->pos] == c ) { - if ( x->pos == 28 ) - x->state = STA_found_header_wait_newline; - } - else - x->state = c == '\n'? STA_wait_dash : STA_wait_newline; - break; - case STA_found_header_wait_newline: - /* to make CR,LF issues easier we simply allow for white space - behind the 5 dashes */ - if ( c == '\n' ) - x->state = STA_skip_header_lines; - else if ( c != '\r' && c != ' ' && c != '\t' ) - x->state = STA_wait_dash; /* garbage after the header line */ - break; - case STA_skip_header_lines: - /* i.e. wait for one empty line */ - if ( c == '\n' ) { - x->state = STA_read_data; - x->crc = CRCINIT; - x->val = 0; - x->pos = 0; - } - else if ( c != '\r' && c != ' ' && c != '\t' ) - x->state = STA_skip_header_lines_non_ws; - break; - case STA_skip_header_lines_non_ws: - /* like above but we already encountered non white space */ - if ( c == '\n' ) - x->state = STA_skip_header_lines; - break; - case STA_read_data: - /* fixme: we don't check for the trailing dash lines but rely - * on the armor stop characters */ - if( c == '\n' || c == ' ' || c == '\r' || c == '\t' ) - break; /* skip all kind of white space */ - - if( c == '=' ) { /* pad character: stop */ - if( x->pos == 1 ) /* in this case val has some value */ - rval = x->val; - x->state = STA_wait_crc; - break; - } - - { - int c2; - if( (c = asctobin[(c2=c)]) == 255 ) { - log_error(_("invalid radix64 character %02X skipped\n"), c2); - break; - } - } - - switch(x->pos) { - case 0: - x->val = c << 2; - break; - case 1: - x->val |= (c>>4)&3; - rval = x->val; - x->val = (c<<4)&0xf0; - break; - case 2: - x->val |= (c>>2)&15; - rval = x->val; - x->val = (c<<6)&0xc0; - break; - case 3: - x->val |= c&0x3f; - rval = x->val; - break; - } - x->pos = (x->pos+1) % 4; - break; - case STA_wait_crc: - if( c == '\n' || c == ' ' || c == '\r' || c == '\t' || c == '=' ) - break; /* skip ws and pad characters */ - /* assume that we are at the next line */ - x->state = STA_read_crc; - x->pos = 0; - x->mycrc = 0; /* fall through */ - case STA_read_crc: - if( (c = asctobin[c]) == 255 ) { - rval = -1; /* ready */ - if( x->crc != x->mycrc ) { - log_info (_("CRC error; %06lX - %06lX\n"), - (ulong)x->crc, (ulong)x->mycrc); - if ( invalid_crc() ) - rval = -3; - } - x->state = STA_ready; /* not sure whether this is correct */ - break; - } - - switch(x->pos) { - case 0: - x->val = c << 2; - break; - case 1: - x->val |= (c>>4)&3; - x->mycrc |= x->val << 16; - x->val = (c<<4)&0xf0; - break; - case 2: - x->val |= (c>>2)&15; - x->mycrc |= x->val << 8; - x->val = (c<<6)&0xc0; - break; - case 3: - x->val |= c&0x3f; - x->mycrc |= x->val; - break; - } - x->pos = (x->pos+1) % 4; - break; - case STA_ready: - rval = -1; - break; - } - - if ( !(rval & ~255) ) { /* compute the CRC */ - x->crc = (x->crc << 8) ^ crc_table[((x->crc >> 16)&0xff) ^ rval]; - x->crc &= 0x00ffffff; - } - - return rval; -} diff --git a/g10/filter.h b/g10/filter.h index 6daf273fa..fa1f5a24f 100644 --- a/g10/filter.h +++ b/g10/filter.h @@ -69,8 +69,6 @@ typedef struct { int pending_lf; /* used together with faked */ } armor_filter_context_t; -struct unarmor_pump_s; -typedef struct unarmor_pump_s *UnarmorPump; struct compress_filter_context_s { @@ -172,9 +170,6 @@ armor_filter_context_t *new_armor_context (void); void release_armor_context (armor_filter_context_t *afx); int push_armor_filter (armor_filter_context_t *afx, iobuf_t iobuf); int use_armor_filter( iobuf_t a ); -UnarmorPump unarmor_pump_new (void); -void unarmor_pump_release (UnarmorPump x); -int unarmor_pump (UnarmorPump x, int c); /*-- compress.c --*/ gpg_error_t push_compress_filter (iobuf_t out, compress_filter_context_t *zfx, From 2b5718c1f76851160115f455c3a9383b04521347 Mon Sep 17 00:00:00 2001 From: Jussi Kivilinna Date: Thu, 8 Nov 2018 21:31:12 +0200 Subject: [PATCH 059/235] common/iobuf: optimize iobuf_read_line * common/iobuf.c (iobuf_read_line): Add fast path for finding '\n' character in buffer. -- This patch reduce per byte overhead in iobuf_read_line by avoiding using iobuf_get when possible and use memchr to find '\n'. This speeds armored decryption. Benchmark results below, tested on Intel Core i7-4790K (turbo off). Encrypted 2 GiB through pipe to ramfs file using AES128. Decrypt ramfs file out through pipe to /dev/null. before patch-set ---------------- gpg process armor: user time pipe transfer rate encrypt-aead: 13.8 140 MB/s decrypt-aead: 30.6 68 MB/s encrypt-cfb: 17.4 114 MB/s decrypt-cfb: 32.6 64 MB/s after (decrypt+iobuf opt) ------------------------- gpg process armor: user time pipe transfer rate decrypt-aead: 22.5 92 MB/s decrypt-cfb: 24.4 85 MB/s Signed-off-by: Jussi Kivilinna --- common/iobuf.c | 48 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/common/iobuf.c b/common/iobuf.c index 18a458e0a..5eeba8fe6 100644 --- a/common/iobuf.c +++ b/common/iobuf.c @@ -2610,12 +2610,50 @@ iobuf_read_line (iobuf_t a, byte ** addr_of_buffer, } p = buffer; - while ((c = iobuf_get (a)) != -1) + while (1) { - *p++ = c; - nbytes++; - if (c == '\n') - break; + if (!a->nofast && a->d.start < a->d.len && nbytes < length - 1) + /* Fast path for finding '\n' by using standard C library's optimized + memchr. */ + { + unsigned size = a->d.len - a->d.start; + byte *newline_pos; + + if (size > length - 1 - nbytes) + size = length - 1 - nbytes; + + newline_pos = memchr (a->d.buf + a->d.start, '\n', size); + if (newline_pos) + { + /* Found newline, copy buffer and return. */ + size = (newline_pos - (a->d.buf + a->d.start)) + 1; + memcpy (p, a->d.buf + a->d.start, size); + p += size; + nbytes += size; + a->d.start += size; + a->nbytes += size; + break; + } + else + { + /* No newline, copy buffer and continue. */ + memcpy (p, a->d.buf + a->d.start, size); + p += size; + nbytes += size; + a->d.start += size; + a->nbytes += size; + } + } + else + { + c = iobuf_readbyte (a); + if (c == -1) + break; + *p++ = c; + nbytes++; + if (c == '\n') + break; + } if (nbytes == length - 1) /* We don't have enough space to add a \n and a \0. Increase From e486d4f0259f27906d2c2869cc01b3aa31aaa0a6 Mon Sep 17 00:00:00 2001 From: Jussi Kivilinna Date: Thu, 8 Nov 2018 21:31:12 +0200 Subject: [PATCH 060/235] g10/armor: use libgcrypt's CRC24 implementation * g10/armor.c (CRCINIT, CRCPOLY, CRCUPDATE, crc_table): Remove. (new_armor_context): Open libgcrypt CRC24 context. (release_armor_context): Close CRC24 context. (initialize): Remove CRC table generation. (get_afx_crc): New. (check_input, fake_packet, radix64_read, armor_filter): Update to use CRC24 context. * g10/filter.h (armor_filter_context_t): Replace crc intermediate value with libgcrypt md context pointer. -- This patch changes armor filter to use optimized CRC24 implementation from libgcrypt to speed up encryption and decryption. Benchmark results below, tested on Intel Core i7-4790K (turbo off). Encrypted 2 GiB through pipe to ramfs file using AES128. Decrypt ramfs file out through pipe to /dev/null. before patch-set ---------------- gpg process armor: user time pipe transfer rate encrypt-aead: 13.8 140 MB/s decrypt-aead: 30.6 68 MB/s encrypt-cfb: 17.4 114 MB/s decrypt-cfb: 32.6 64 MB/s after (decrypt+iobuf+crc opt) ----------------------------- gpg process armor: user time pipe transfer rate encrypt-aead: 8.7 211 MB/s decrypt-aead: 17.6 116 MB/s encrypt-cfb: 12.6 153 MB/s decrypt-cfb: 19.6 105 MB/s Signed-off-by: Jussi Kivilinna --- g10/armor.c | 97 +++++++++++++++++++++++++++------------------------- g10/filter.h | 2 +- 2 files changed, 51 insertions(+), 48 deletions(-) diff --git a/g10/armor.c b/g10/armor.c index 714282f45..9da135a2b 100644 --- a/g10/armor.c +++ b/g10/armor.c @@ -37,13 +37,6 @@ #define MAX_LINELEN 20000 -#define CRCINIT 0xB704CE -#define CRCPOLY 0X864CFB -#define CRCUPDATE(a,c) do { \ - a = ((a) << 8) ^ crc_table[((a)&0xff >> 16) ^ (c)]; \ - a &= 0x00ffffff; \ - } while(0) -static u32 crc_table[256]; static byte bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; @@ -121,9 +114,22 @@ armor_filter_context_t * new_armor_context (void) { armor_filter_context_t *afx; + gpg_error_t err; afx = xcalloc (1, sizeof *afx); - afx->refcount = 1; + if (afx) + { + err = gcry_md_open (&afx->crc_md, GCRY_MD_CRC24_RFC2440, 0); + if (err != 0) + { + log_error ("gcry_md_open failed for GCRY_MD_CRC24_RFC2440: %s", + gpg_strerror (err)); + xfree (afx); + return NULL; + } + + afx->refcount = 1; + } return afx; } @@ -138,6 +144,7 @@ release_armor_context (armor_filter_context_t *afx) log_assert (afx->refcount); if ( --afx->refcount ) return; + gcry_md_close (afx->crc_md); xfree (afx); } @@ -161,25 +168,8 @@ push_armor_filter (armor_filter_context_t *afx, iobuf_t iobuf) static void initialize(void) { - int i, j; - u32 t; + u32 i; byte *s; - - /* init the crc lookup table */ - crc_table[0] = 0; - for(i=j=0; j < 128; j++ ) { - t = crc_table[j]; - if( t & 0x00800000 ) { - t <<= 1; - crc_table[i++] = t ^ CRCPOLY; - crc_table[i++] = t; - } - else { - t <<= 1; - crc_table[i++] = t; - crc_table[i++] = t ^ CRCPOLY; - } - } /* build the helptable for radix64 to bin conversion */ for(i=0; i < 256; i++ ) asctobin[i] = 255; /* used to detect invalid characters */ @@ -190,6 +180,24 @@ initialize(void) } +static inline u32 +get_afx_crc (armor_filter_context_t *afx) +{ + const byte *crc_buf; + u32 crc; + + crc_buf = gcry_md_read (afx->crc_md, GCRY_MD_CRC24_RFC2440); + + crc = crc_buf[0]; + crc <<= 8; + crc |= crc_buf[1]; + crc <<= 8; + crc |= crc_buf[2]; + + return crc; +} + + /* * Check whether this is an armored file. See also * parse-packet.c for details on this code. @@ -592,7 +600,7 @@ check_input( armor_filter_context_t *afx, IOBUF a ) afx->faked = 1; else { afx->inp_checked = 1; - afx->crc = CRCINIT; + gcry_md_reset (afx->crc_md); afx->idx = 0; afx->radbuf[0] = 0; } @@ -768,7 +776,7 @@ fake_packet( armor_filter_context_t *afx, IOBUF a, } } afx->inp_checked = 1; - afx->crc = CRCINIT; + gcry_md_reset (afx->crc_md); afx->idx = 0; afx->radbuf[0] = 0; } @@ -797,10 +805,8 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, int checkcrc=0; int rc = 0; size_t n = 0; - int idx, i, onlypad=0; - u32 crc; + int idx, onlypad=0; - crc = afx->crc; idx = afx->idx; val = afx->radbuf[0]; for( n=0; n < size; ) { @@ -881,14 +887,14 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, idx = (idx+1) % 4; } - for(i=0; i < n; i++ ) - crc = (crc << 8) ^ crc_table[((crc >> 16)&0xff) ^ buf[i]]; - crc &= 0x00ffffff; - afx->crc = crc; afx->idx = idx; afx->radbuf[0] = val; + if( n ) + gcry_md_write (afx->crc_md, buf, n); + if( checkcrc ) { + gcry_md_final (afx->crc_md); afx->any_data = 1; afx->inp_checked=0; afx->faked = 0; @@ -957,10 +963,10 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, log_info(_("malformed CRC\n")); rc = invalid_crc(); } - else if( mycrc != afx->crc ) { - log_info (_("CRC error; %06lX - %06lX\n"), - (ulong)afx->crc, (ulong)mycrc); - rc = invalid_crc(); + else if( mycrc != get_afx_crc (afx) ) { + log_info (_("CRC error; %06lX - %06lX\n"), + (ulong)get_afx_crc (afx), (ulong)mycrc); + rc = invalid_crc(); } else { rc = 0; @@ -1188,18 +1194,15 @@ armor_filter( void *opaque, int control, afx->status++; afx->idx = 0; afx->idx2 = 0; - afx->crc = CRCINIT; - + gcry_md_reset (afx->crc_md); } - crc = afx->crc; idx = afx->idx; idx2 = afx->idx2; for(i=0; i < idx; i++ ) radbuf[i] = afx->radbuf[i]; - for(i=0; i < size; i++ ) - crc = (crc << 8) ^ crc_table[((crc >> 16)&0xff) ^ buf[i]]; - crc &= 0x00ffffff; + if( size ) + gcry_md_write (afx->crc_md, buf, size); for( ; size; buf++, size-- ) { radbuf[idx++] = *buf; @@ -1224,7 +1227,6 @@ armor_filter( void *opaque, int control, afx->radbuf[i] = radbuf[i]; afx->idx = idx; afx->idx2 = idx2; - afx->crc = crc; } else if( control == IOBUFCTRL_INIT ) { @@ -1250,7 +1252,8 @@ armor_filter( void *opaque, int control, if( afx->cancel ) ; else if( afx->status ) { /* pad, write cecksum, and bottom line */ - crc = afx->crc; + gcry_md_final (afx->crc_md); + crc = get_afx_crc (afx); idx = afx->idx; idx2 = afx->idx2; if( idx ) { diff --git a/g10/filter.h b/g10/filter.h index fa1f5a24f..b2ef3828f 100644 --- a/g10/filter.h +++ b/g10/filter.h @@ -61,7 +61,7 @@ typedef struct { byte radbuf[4]; int idx, idx2; - u32 crc; + gcry_md_hd_t crc_md; int status; /* an internal state flag */ int cancel; From e8142cc69a2ae5a5d0a238bc9f88841067359af8 Mon Sep 17 00:00:00 2001 From: Jussi Kivilinna Date: Thu, 8 Nov 2018 21:31:12 +0200 Subject: [PATCH 061/235] g10/armor: optimize binary to radix64 conversion * g10/armor.c (bintoasc): Change to read-only. (initialize): Use const pointer for 'bintoasc'. (armor_output_buf_as_radix64): New function for faster binary to radix64 conversion. (armor_filter): Use new conversion function. -- This patch adds faster binary to radix64 conversion to speed up armored encryption. Benchmark results below, tested on Intel Core i7-4790K (turbo off). Encrypted 2 GiB through pipe to ramfs file using AES128. Decrypt ramfs file out through pipe to /dev/null. before patch-set ---------------- gpg process armor: user time pipe transfer rate encrypt-aead: 13.8 140 MB/s decrypt-aead: 30.6 68 MB/s encrypt-cfb: 17.4 114 MB/s decrypt-cfb: 32.6 64 MB/s after (decrypt+iobuf+crc+radix64 opt) ------------------------------------- gpg process armor: user time pipe transfer rate encrypt-aead: 2.7 523 MB/s encrypt-cfb: 6.7 264 MB/s Signed-off-by: Jussi Kivilinna --- g10/armor.c | 160 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 125 insertions(+), 35 deletions(-) diff --git a/g10/armor.c b/g10/armor.c index 9da135a2b..95293d91c 100644 --- a/g10/armor.c +++ b/g10/armor.c @@ -37,9 +37,9 @@ #define MAX_LINELEN 20000 -static byte bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; +static const byte bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; static byte asctobin[256]; /* runtime initialized */ static int is_initialized; @@ -169,7 +169,8 @@ static void initialize(void) { u32 i; - byte *s; + const byte *s; + /* build the helptable for radix64 to bin conversion */ for(i=0; i < 256; i++ ) asctobin[i] = 255; /* used to detect invalid characters */ @@ -1003,6 +1004,121 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, return rc; } +static void +armor_output_buf_as_radix64 (armor_filter_context_t *afx, IOBUF a, + byte *buf, size_t size) +{ + byte radbuf[sizeof (afx->radbuf)]; + byte outbuf[64 + sizeof (afx->eol)]; + unsigned int eollen = strlen (afx->eol); + u32 in, in2; + int idx, idx2; + int i; + + idx = afx->idx; + idx2 = afx->idx2; + memcpy (radbuf, afx->radbuf, sizeof (afx->radbuf)); + + if (size && (idx || idx2)) + { + /* preload eol to outbuf buffer */ + memcpy (outbuf + 4, afx->eol, sizeof (afx->eol)); + + for (; size && (idx || idx2); buf++, size--) + { + radbuf[idx++] = *buf; + if (idx > 2) + { + idx = 0; + in = (u32)radbuf[0] << (2 * 8); + in |= (u32)radbuf[1] << (1 * 8); + in |= (u32)radbuf[2] << (0 * 8); + outbuf[0] = bintoasc[(in >> 18) & 077]; + outbuf[1] = bintoasc[(in >> 12) & 077]; + outbuf[2] = bintoasc[(in >> 6) & 077]; + outbuf[3] = bintoasc[(in >> 0) & 077]; + if (++idx2 >= (64/4)) + { /* pgp doesn't like 72 here */ + idx2=0; + iobuf_write (a, outbuf, 4 + eollen); + } + else + { + iobuf_write (a, outbuf, 4); + } + } + } + } + + if (size >= (64/4)*3) + { + /* preload eol to outbuf buffer */ + memcpy (outbuf + 64, afx->eol, sizeof(afx->eol)); + + do + { + /* idx and idx2 == 0 */ + + for (i = 0; i < (64/8); i++) + { + in = (u32)buf[0] << (2 * 8); + in |= (u32)buf[1] << (1 * 8); + in |= (u32)buf[2] << (0 * 8); + in2 = (u32)buf[3] << (2 * 8); + in2 |= (u32)buf[4] << (1 * 8); + in2 |= (u32)buf[5] << (0 * 8); + outbuf[i*8+0] = bintoasc[(in >> 18) & 077]; + outbuf[i*8+1] = bintoasc[(in >> 12) & 077]; + outbuf[i*8+2] = bintoasc[(in >> 6) & 077]; + outbuf[i*8+3] = bintoasc[(in >> 0) & 077]; + outbuf[i*8+4] = bintoasc[(in2 >> 18) & 077]; + outbuf[i*8+5] = bintoasc[(in2 >> 12) & 077]; + outbuf[i*8+6] = bintoasc[(in2 >> 6) & 077]; + outbuf[i*8+7] = bintoasc[(in2 >> 0) & 077]; + buf+=6; + size-=6; + } + + /* pgp doesn't like 72 here */ + iobuf_write (a, outbuf, 64 + eollen); + } + while (size >= (64/4)*3); + + /* restore eol for tail handling */ + if (size) + memcpy (outbuf + 4, afx->eol, sizeof (afx->eol)); + } + + for (; size; buf++, size--) + { + radbuf[idx++] = *buf; + if (idx > 2) + { + idx = 0; + in = (u32)radbuf[0] << (2 * 8); + in |= (u32)radbuf[1] << (1 * 8); + in |= (u32)radbuf[2] << (0 * 8); + outbuf[0] = bintoasc[(in >> 18) & 077]; + outbuf[1] = bintoasc[(in >> 12) & 077]; + outbuf[2] = bintoasc[(in >> 6) & 077]; + outbuf[3] = bintoasc[(in >> 0) & 077]; + if (++idx2 >= (64/4)) + { /* pgp doesn't like 72 here */ + idx2=0; + iobuf_write (a, outbuf, 4 + eollen); + } + else + { + iobuf_write (a, outbuf, 4); + } + } + } + + memcpy (afx->radbuf, radbuf, sizeof (afx->radbuf)); + afx->idx = idx; + afx->idx2 = idx2; +} + /**************** * This filter is used to handle the armor stuff */ @@ -1012,7 +1128,7 @@ armor_filter( void *opaque, int control, { size_t size = *ret_len; armor_filter_context_t *afx = opaque; - int rc=0, i, c; + int rc=0, c; byte radbuf[3]; int idx, idx2; size_t n=0; @@ -1196,37 +1312,11 @@ armor_filter( void *opaque, int control, afx->idx2 = 0; gcry_md_reset (afx->crc_md); } - idx = afx->idx; - idx2 = afx->idx2; - for(i=0; i < idx; i++ ) - radbuf[i] = afx->radbuf[i]; - if( size ) - gcry_md_write (afx->crc_md, buf, size); - - for( ; size; buf++, size-- ) { - radbuf[idx++] = *buf; - if( idx > 2 ) { - idx = 0; - c = bintoasc[(*radbuf >> 2) & 077]; - iobuf_put(a, c); - c = bintoasc[(((*radbuf<<4)&060)|((radbuf[1] >> 4)&017))&077]; - iobuf_put(a, c); - c = bintoasc[(((radbuf[1]<<2)&074)|((radbuf[2]>>6)&03))&077]; - iobuf_put(a, c); - c = bintoasc[radbuf[2]&077]; - iobuf_put(a, c); - if( ++idx2 >= (64/4) ) - { /* pgp doesn't like 72 here */ - iobuf_writestr(a,afx->eol); - idx2=0; - } - } - } - for(i=0; i < idx; i++ ) - afx->radbuf[i] = radbuf[i]; - afx->idx = idx; - afx->idx2 = idx2; + if( size ) { + gcry_md_write (afx->crc_md, buf, size); + armor_output_buf_as_radix64 (afx, a, buf, size); + } } else if( control == IOBUFCTRL_INIT ) { From 643ec7c642dc75191e712963d2bb460ac247e09b Mon Sep 17 00:00:00 2001 From: Jussi Kivilinna Date: Thu, 8 Nov 2018 21:31:12 +0200 Subject: [PATCH 062/235] g10/armor: optimize radix64 to binary conversion * g10/armor.c (asctobin): Larger look-up table for fast path. (initialize): Update 'asctobin' initialization. (radix64_read): Add fast path for radix64 to binary conversion. -- This patch adds fast path for radix64 to binary conversion in armored decryption. Benchmark results below, tested on Intel Core i7-4790K (turbo off). Encrypted 2 GiB through pipe to ramfs file using AES128. Decrypt ramfs file out through pipe to /dev/null. before patch-set ---------------- gpg process armor: user time pipe transfer rate encrypt-aead: 13.8 140 MB/s decrypt-aead: 30.6 68 MB/s encrypt-cfb: 17.4 114 MB/s decrypt-cfb: 32.6 64 MB/s after (decrypt+iobuf+crc+radix64 opt) ------------------------------------- gpg process armor: user time pipe transfer rate decrypt-aead: 9.8 200 MB/s decrypt-cfb: 11.9 168 MB/s Signed-off-by: Jussi Kivilinna --- g10/armor.c | 166 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 141 insertions(+), 25 deletions(-) diff --git a/g10/armor.c b/g10/armor.c index 95293d91c..972766503 100644 --- a/g10/armor.c +++ b/g10/armor.c @@ -40,7 +40,7 @@ static const byte bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; -static byte asctobin[256]; /* runtime initialized */ +static u32 asctobin[4][256]; /* runtime initialized */ static int is_initialized; @@ -171,11 +171,16 @@ initialize(void) u32 i; const byte *s; - /* build the helptable for radix64 to bin conversion */ - for(i=0; i < 256; i++ ) - asctobin[i] = 255; /* used to detect invalid characters */ + /* Build the helptable for radix64 to bin conversion. Value 0xffffffff is + used to detect invalid characters. */ + memset (asctobin, 0xff, sizeof(asctobin)); for(s=bintoasc,i=0; *s; s++,i++ ) - asctobin[*s] = i; + { + asctobin[0][*s] = i << (0 * 6); + asctobin[1][*s] = i << (1 * 6); + asctobin[2][*s] = i << (2 * 6); + asctobin[3][*s] = i << (3 * 6); + } is_initialized=1; } @@ -802,11 +807,13 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, byte *buf, size_t size ) { byte val; - int c, c2; + int c; + u32 binc; int checkcrc=0; int rc = 0; size_t n = 0; - int idx, onlypad=0; + int idx, onlypad=0; + int skip_fast = 0; idx = afx->idx; val = afx->radbuf[0]; @@ -827,6 +834,122 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, } again: + binc = asctobin[0][c]; + + if( binc != 0xffffffffUL ) + { + if( idx == 0 && skip_fast == 0 + && afx->buffer_pos + (16 - 1) < afx->buffer_len + && n + 12 < size) + { + /* Fast path for radix64 to binary conversion. */ + u32 b0,b1,b2,b3; + + /* Speculatively load 15 more input bytes. */ + b0 = binc << (3 * 6); + b0 |= asctobin[2][afx->buffer[afx->buffer_pos + 0]]; + b0 |= asctobin[1][afx->buffer[afx->buffer_pos + 1]]; + b0 |= asctobin[0][afx->buffer[afx->buffer_pos + 2]]; + b1 = asctobin[3][afx->buffer[afx->buffer_pos + 3]]; + b1 |= asctobin[2][afx->buffer[afx->buffer_pos + 4]]; + b1 |= asctobin[1][afx->buffer[afx->buffer_pos + 5]]; + b1 |= asctobin[0][afx->buffer[afx->buffer_pos + 6]]; + b2 = asctobin[3][afx->buffer[afx->buffer_pos + 7]]; + b2 |= asctobin[2][afx->buffer[afx->buffer_pos + 8]]; + b2 |= asctobin[1][afx->buffer[afx->buffer_pos + 9]]; + b2 |= asctobin[0][afx->buffer[afx->buffer_pos + 10]]; + b3 = asctobin[3][afx->buffer[afx->buffer_pos + 11]]; + b3 |= asctobin[2][afx->buffer[afx->buffer_pos + 12]]; + b3 |= asctobin[1][afx->buffer[afx->buffer_pos + 13]]; + b3 |= asctobin[0][afx->buffer[afx->buffer_pos + 14]]; + + /* Check if any of the input bytes were invalid. */ + if( (b0 | b1 | b2 | b3) != 0xffffffffUL ) + { + /* All 16 bytes are valid. */ + buf[n + 0] = b0 >> (2 * 8); + buf[n + 1] = b0 >> (1 * 8); + buf[n + 2] = b0 >> (0 * 8); + buf[n + 3] = b1 >> (2 * 8); + buf[n + 4] = b1 >> (1 * 8); + buf[n + 5] = b1 >> (0 * 8); + buf[n + 6] = b2 >> (2 * 8); + buf[n + 7] = b2 >> (1 * 8); + buf[n + 8] = b2 >> (0 * 8); + buf[n + 9] = b3 >> (2 * 8); + buf[n + 10] = b3 >> (1 * 8); + buf[n + 11] = b3 >> (0 * 8); + afx->buffer_pos += 16 - 1; + n += 12; + continue; + } + else if( b0 == 0xffffffffUL ) + { + /* byte[1..3] have invalid character(s). Switch to slow + path. */ + skip_fast = 1; + } + else if( b1 == 0xffffffffUL ) + { + /* byte[4..7] have invalid character(s), first 4 bytes are + valid. */ + buf[n + 0] = b0 >> (2 * 8); + buf[n + 1] = b0 >> (1 * 8); + buf[n + 2] = b0 >> (0 * 8); + afx->buffer_pos += 4 - 1; + n += 3; + skip_fast = 1; + continue; + } + else if( b2 == 0xffffffffUL ) + { + /* byte[8..11] have invalid character(s), first 8 bytes are + valid. */ + buf[n + 0] = b0 >> (2 * 8); + buf[n + 1] = b0 >> (1 * 8); + buf[n + 2] = b0 >> (0 * 8); + buf[n + 3] = b1 >> (2 * 8); + buf[n + 4] = b1 >> (1 * 8); + buf[n + 5] = b1 >> (0 * 8); + afx->buffer_pos += 8 - 1; + n += 6; + skip_fast = 1; + continue; + } + else /*if( b3 == 0xffffffffUL )*/ + { + /* byte[12..15] have invalid character(s), first 12 bytes + are valid. */ + buf[n + 0] = b0 >> (2 * 8); + buf[n + 1] = b0 >> (1 * 8); + buf[n + 2] = b0 >> (0 * 8); + buf[n + 3] = b1 >> (2 * 8); + buf[n + 4] = b1 >> (1 * 8); + buf[n + 5] = b1 >> (0 * 8); + buf[n + 6] = b2 >> (2 * 8); + buf[n + 7] = b2 >> (1 * 8); + buf[n + 8] = b2 >> (0 * 8); + afx->buffer_pos += 12 - 1; + n += 9; + skip_fast = 1; + continue; + } + } + + switch(idx) + { + case 0: val = binc << 2; break; + case 1: val |= (binc>>4)&3; buf[n++]=val;val=(binc<<4)&0xf0;break; + case 2: val |= (binc>>2)&15; buf[n++]=val;val=(binc<<6)&0xc0;break; + case 3: val |= binc&0x3f; buf[n++] = val; break; + } + idx = (idx+1) % 4; + + continue; + } + + skip_fast = 0; + if( c == '\n' || c == ' ' || c == '\r' || c == '\t' ) continue; else if( c == '=' ) { /* pad character: stop */ @@ -857,10 +980,10 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, if (afx->buffer_pos + 6 < afx->buffer_len && afx->buffer[afx->buffer_pos + 0] == '3' && afx->buffer[afx->buffer_pos + 1] == 'D' - && asctobin[afx->buffer[afx->buffer_pos + 2]] != 255 - && asctobin[afx->buffer[afx->buffer_pos + 3]] != 255 - && asctobin[afx->buffer[afx->buffer_pos + 4]] != 255 - && asctobin[afx->buffer[afx->buffer_pos + 5]] != 255 + && asctobin[0][afx->buffer[afx->buffer_pos + 2]] != 0xffffffffUL + && asctobin[0][afx->buffer[afx->buffer_pos + 3]] != 0xffffffffUL + && asctobin[0][afx->buffer[afx->buffer_pos + 4]] != 0xffffffffUL + && asctobin[0][afx->buffer[afx->buffer_pos + 5]] != 0xffffffffUL && afx->buffer[afx->buffer_pos + 6] == '\n') { afx->buffer_pos += 2; @@ -875,17 +998,10 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, checkcrc++; break; } - else if( (c = asctobin[(c2=c)]) == 255 ) { - log_error(_("invalid radix64 character %02X skipped\n"), c2); + else { + log_error(_("invalid radix64 character %02X skipped\n"), c); continue; } - switch(idx) { - case 0: val = c << 2; break; - case 1: val |= (c>>4)&3; buf[n++]=val;val=(c<<4)&0xf0;break; - case 2: val |= (c>>2)&15; buf[n++]=val;val=(c<<6)&0xc0;break; - case 3: val |= c&0x3f; buf[n++] = val; break; - } - idx = (idx+1) % 4; } afx->idx = idx; @@ -924,13 +1040,13 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, u32 mycrc = 0; idx = 0; do { - if( (c = asctobin[c]) == 255 ) + if( (binc = asctobin[0][c]) == 0xffffffffUL ) break; switch(idx) { - case 0: val = c << 2; break; - case 1: val |= (c>>4)&3; mycrc |= val << 16;val=(c<<4)&0xf0;break; - case 2: val |= (c>>2)&15; mycrc |= val << 8;val=(c<<6)&0xc0;break; - case 3: val |= c&0x3f; mycrc |= val; break; + case 0: val = binc << 2; break; + case 1: val |= (binc>>4)&3; mycrc |= val << 16;val=(binc<<4)&0xf0;break; + case 2: val |= (binc>>2)&15; mycrc |= val << 8;val=(binc<<6)&0xc0;break; + case 3: val |= binc&0x3f; mycrc |= val; break; } for(;;) { if( afx->buffer_pos < afx->buffer_len ) From b46382dd47731231ff49b59c486110a25e08e985 Mon Sep 17 00:00:00 2001 From: Jussi Kivilinna Date: Fri, 9 Nov 2018 18:07:38 +0200 Subject: [PATCH 063/235] g10/mainproc: avoid extra hash contexts when decrypting AEAD input * g10/mainproc.c (mainproc_context): New member 'seen_pkt_encrypted_aead'. (release_list): Clear 'seen_pkt_encrypted_aead'. (proc_encrypted): Set 'seen_pkt_encrypted_aead'. (have_seen_pkt_encrypted_aead): New. (proc_plaintext): Do not enable extra hash contexts when decryption AEAD input. -- Signed-off-by: Jussi Kivilinna --- g10/mainproc.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/g10/mainproc.c b/g10/mainproc.c index 5b7bc9555..7eceb7e0b 100644 --- a/g10/mainproc.c +++ b/g10/mainproc.c @@ -86,6 +86,7 @@ struct mainproc_context int trustletter; /* Temporary usage in list_node. */ ulong symkeys; /* Number of symmetrically encrypted session keys. */ struct pubkey_enc_list *pkenc_list; /* List of encryption packets. */ + int seen_pkt_encrypted_aead; /* PKT_ENCRYPTED_AEAD packet seen. */ struct { unsigned int sig_seen:1; /* Set to true if a signature packet has been seen. */ @@ -137,6 +138,7 @@ release_list( CTX c ) c->any.data = 0; c->any.uncompress_failed = 0; c->last_was_session_key = 0; + c->seen_pkt_encrypted_aead = 0; xfree (c->dek); c->dek = NULL; } @@ -536,6 +538,9 @@ proc_encrypted (CTX c, PACKET *pkt) int result = 0; int early_plaintext = literals_seen; + if (pkt->pkttype == PKT_ENCRYPTED_AEAD) + c->seen_pkt_encrypted_aead = 1; + if (early_plaintext) { log_info (_("WARNING: multiple plaintexts seen\n")); @@ -704,7 +709,6 @@ proc_encrypted (CTX c, PACKET *pkt) } - if (!result) result = decrypt_data (c->ctrl, c, pkt->pkt.encrypted, c->dek ); @@ -804,6 +808,21 @@ proc_encrypted (CTX c, PACKET *pkt) } +static int +have_seen_pkt_encrypted_aead( CTX c ) +{ + CTX cc; + + for (cc = c; cc; cc = cc->anchor) + { + if (cc->seen_pkt_encrypted_aead) + return 1; + } + + return 0; +} + + static void proc_plaintext( CTX c, PACKET *pkt ) { @@ -874,7 +893,7 @@ proc_plaintext( CTX c, PACKET *pkt ) } } - if (!any && !opt.skip_verify) + if (!any && !opt.skip_verify && !have_seen_pkt_encrypted_aead(c)) { /* This is for the old GPG LITERAL+SIG case. It's not legal according to 2440, so hopefully it won't come up that often. From b3095c95ef9d2d76b49a6ad1b946fca590380dc9 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Sun, 11 Nov 2018 12:01:42 +0100 Subject: [PATCH 064/235] common: Add --filter option to t-mbox-util. * common/t-mbox-util.c (run_filter): New. (main): Add option parser. --- common/t-mbox-util.c | 98 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 4 deletions(-) diff --git a/common/t-mbox-util.c b/common/t-mbox-util.c index fb1ac12e0..e9cf41215 100644 --- a/common/t-mbox-util.c +++ b/common/t-mbox-util.c @@ -25,6 +25,9 @@ #include "util.h" #include "mbox-util.h" +#define PGM "t-mbox-util" + + #define pass() do { ; } while(0) #define fail(a) do { fprintf (stderr, "%s:%d: test %d failed\n",\ __FILE__,__LINE__, (a)); \ @@ -32,6 +35,10 @@ } while(0) +static int verbose; +static int debug; + + static void run_mbox_test (void) { @@ -143,14 +150,97 @@ run_dns_test (void) } +static void +run_filter (void) +{ + char buf[4096]; + int c; + char *p, *mbox; + unsigned int count1 = 0; + unsigned int count2 = 0; + + while (fgets (buf, sizeof buf, stdin)) + { + p = strchr (buf, '\n'); + if (p) + *p = 0; + else + { + /* Skip to the end of the line. */ + while ((c = getc (stdin)) != EOF && c != '\n') + ; + } + count1++; + trim_spaces (buf); + mbox = mailbox_from_userid (buf); + if (mbox) + { + printf ("%s\n", mbox); + xfree (mbox); + count2++; + } + } + if (verbose) + fprintf (stderr, PGM ": lines=%u mboxes=%u\n", count1, count2); +} + + int main (int argc, char **argv) { - (void)argc; - (void)argv; + int last_argc = -1; + int opt_filter = 0; - run_mbox_test (); - run_dns_test (); + if (argc) + { argc--; argv++; } + while (argc && last_argc != argc ) + { + last_argc = argc; + if (!strcmp (*argv, "--")) + { + argc--; argv++; + break; + } + else if (!strcmp (*argv, "--help")) + { + fputs ("usage: " PGM " [FILE]\n" + "Options:\n" + " --verbose Print timings etc.\n" + " --debug Flyswatter\n" + " --filter Filter mboxes from input lines\n" + , stdout); + exit (0); + } + else if (!strcmp (*argv, "--verbose")) + { + verbose++; + argc--; argv++; + } + else if (!strcmp (*argv, "--debug")) + { + verbose += 2; + debug++; + argc--; argv++; + } + else if (!strcmp (*argv, "--filter")) + { + opt_filter = 1; + argc--; argv++; + } + else if (!strncmp (*argv, "--", 2)) + { + fprintf (stderr, PGM ": unknown option '%s'\n", *argv); + exit (1); + } + } + + if (opt_filter) + run_filter (); + else + { + run_mbox_test (); + run_dns_test (); + } return 0; } From bbed4746edcd20946ef9f90d25a69c151238de0a Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Sun, 11 Nov 2018 12:20:34 +0100 Subject: [PATCH 065/235] gpg: Fix format string in gpgcompose.c -- For size_t use "%zu" For ssize_t use "%zd" Signed-off-by: Werner Koch --- g10/gpgcompose.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/g10/gpgcompose.c b/g10/gpgcompose.c index 9b6901599..6f573ce46 100644 --- a/g10/gpgcompose.c +++ b/g10/gpgcompose.c @@ -2292,7 +2292,7 @@ sk_esk (const char *option, int argc, char *argv[], void *cookie) log_fatal ("encrypt_seskey failed: %s\n", gpg_strerror (err)); if (enckeylen - 1 > sesdek.keylen) - log_fatal ("key size is too big: %z\n", enckeylen); + log_fatal ("key size is too big: %zu\n", enckeylen); else { ske->seskeylen = (byte)enckeylen; From 6b9f772914624cc673ba26d49b6e3adc32dd7e0a Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 12 Nov 2018 07:44:33 +0100 Subject: [PATCH 066/235] common: Prepare for parsing mail sub-addresses. * common/mbox-util.c (mailbox_from_userid): Add arg subaddress and implement. Change all callers to pass false for it. * common/t-mbox-util.c (run_mbox_no_sub_test): New. (run_filter): Add arg no_sub. (main): Call new test and add option --no-sub. -- Some stats: In the about 5300000 keys on the SKS servers we found 3055 unique mailboxes with a '+' in it. After removing leading and trailing '+' as well as multiple '+' (e.g. "c++" or "foo+bar+baz") 2697 were left which seem to be valid sub-addresses. To filter mailboxes out from a line delimited list with user-ids (e.g. an SQL output), the command t-mbox-util --verbose --filter can be used; to output w/o sub-addresses add --no-sub. GnuPG-bug-id: 4200 Signed-off-by: Werner Koch --- common/mbox-util.c | 32 ++++++++++++-- common/mbox-util.h | 2 +- common/t-mbox-util.c | 95 ++++++++++++++++++++++++++++++++++++++++-- dirmngr/server.c | 4 +- g10/export.c | 2 +- g10/getkey.c | 2 +- g10/gpg.c | 2 +- g10/import.c | 2 +- g10/keylist.c | 2 +- g10/keyserver.c | 2 +- g10/sign.c | 3 +- g10/tofu.c | 2 +- g10/trustdb.c | 2 +- tools/gpg-wks-client.c | 8 ++-- tools/gpg-wks-server.c | 4 +- tools/wks-util.c | 2 +- 16 files changed, 139 insertions(+), 27 deletions(-) diff --git a/common/mbox-util.c b/common/mbox-util.c index 76255ba38..a9086a3f5 100644 --- a/common/mbox-util.c +++ b/common/mbox-util.c @@ -173,11 +173,12 @@ is_valid_mailbox (const char *name) /* Return the mailbox (local-part@domain) form a standard user id. - All plain ASCII characters in the result are converted to - lowercase. Caller must free the result. Returns NULL if no valid - mailbox was found (or we are out of memory). */ + * All plain ASCII characters in the result are converted to + * lowercase. If SUBADDRESS is 1, '+' denoted sub-addresses are not + * included in the result. Caller must free the result. Returns NULL + * if no valid mailbox was found (or we are out of memory). */ char * -mailbox_from_userid (const char *userid) +mailbox_from_userid (const char *userid, int subaddress) { const char *s, *s_end; size_t len; @@ -226,6 +227,29 @@ mailbox_from_userid (const char *userid) else errno = EINVAL; + if (result && subaddress == 1) + { + char *atsign, *plus; + + if ((atsign = strchr (result, '@'))) + { + /* We consider a subaddress only if there is a single '+' + * in the local part and the '+' is not the first or last + * character. */ + *atsign = 0; + if ((plus = strchr (result, '+')) + && !strchr (plus+1, '+') + && result != plus + && plus[1] ) + { + *atsign = '@'; + memmove (plus, atsign, strlen (atsign)+1); + } + else + *atsign = '@'; + } + } + return result? ascii_strlwr (result): NULL; } diff --git a/common/mbox-util.h b/common/mbox-util.h index 7355ceef5..10ff2c4a0 100644 --- a/common/mbox-util.h +++ b/common/mbox-util.h @@ -22,7 +22,7 @@ int has_invalid_email_chars (const void *buffer, size_t length); int is_valid_mailbox (const char *name); int is_valid_mailbox_mem (const void *buffer, size_t length); -char *mailbox_from_userid (const char *userid); +char *mailbox_from_userid (const char *userid, int subaddress); int is_valid_user_id (const char *uid); int is_valid_domain_name (const char *string); diff --git a/common/t-mbox-util.c b/common/t-mbox-util.c index e9cf41215..ae717f96f 100644 --- a/common/t-mbox-util.c +++ b/common/t-mbox-util.c @@ -83,7 +83,86 @@ run_mbox_test (void) for (idx=0; testtbl[idx].userid; idx++) { - char *mbox = mailbox_from_userid (testtbl[idx].userid); + char *mbox = mailbox_from_userid (testtbl[idx].userid, 0); + + if (!testtbl[idx].mbox) + { + if (mbox) + fail (idx); + } + else if (!mbox) + fail (idx); + else if (strcmp (mbox, testtbl[idx].mbox)) + fail (idx); + + xfree (mbox); + } +} + + +static void +run_mbox_no_sub_test (void) +{ + static struct + { + const char *userid; + const char *mbox; + } testtbl[] = + { + { "foo+bar@example.org", "foo@example.org" }, + { "Werner Koch ", "wk@gnupg.org" }, + { "", "wk@gnupg.org" }, + { "wk@gnupg.org", "wk@gnupg.org" }, + { "wk@gnupg.org ", NULL }, + { " wk@gnupg.org", NULL }, + { "Werner Koch (test) ", "wk@gnupg.org" }, + { "Werner Koch (test)", "wk@gnupg.org" }, + { "Werner Koch ", NULL }, + { "Werner Koch ", NULL }, + { "", "foo@example.org" }, + { "", "foo.@example.org" }, + { "<.foo.@example.org>", ".foo.@example.org" }, + { "", "foo..@example.org" }, + { "", "foo..bar@example.org" }, + { "", NULL }, + { "", NULL }, + { "", NULL }, + { "<@example.org>", NULL }, + { "", NULL }, + { "<@foo@example.org>", NULL }, + { " ()", "foo@example.org" }, + { " ()", "fo()o@example.org" }, + { " ()", "fo()o@example.org" }, + { "fo()o@example.org", NULL}, + { "Mr. Foo ", "foo@example.org"}, + { "foo+bar@example.org", "foo@example.org" }, + { "foo++bar@example.org", "foo++bar@example.org" }, + { "foo++@example.org", "foo++@example.org" }, + { "foo+@example.org", "foo+@example.org" }, + { "+foo@example.org", "+foo@example.org" }, + { "++foo@example.org", "++foo@example.org" }, + { "+foo+@example.org", "+foo+@example.org" }, + { "+@example.org", "+@example.org" }, + { "++@example.org", "++@example.org" }, + { "foo+b@example.org", "foo@example.org" }, + { "foo+ba@example.org", "foo@example.org" }, + { "foo+bar@example.org", "foo@example.org" }, + { "foo+barb@example.org", "foo@example.org" }, + { "foo+barba@example.org", "foo@example.org" }, + { "f+b@example.org", "f@example.org" }, + { "fo+b@example.org", "fo@example.org" }, + + { NULL, NULL } + }; + int idx; + + for (idx=0; testtbl[idx].userid; idx++) + { + char *mbox = mailbox_from_userid (testtbl[idx].userid, 1); if (!testtbl[idx].mbox) { @@ -151,7 +230,7 @@ run_dns_test (void) static void -run_filter (void) +run_filter (int no_sub) { char buf[4096]; int c; @@ -172,7 +251,7 @@ run_filter (void) } count1++; trim_spaces (buf); - mbox = mailbox_from_userid (buf); + mbox = mailbox_from_userid (buf, no_sub); if (mbox) { printf ("%s\n", mbox); @@ -190,6 +269,7 @@ main (int argc, char **argv) { int last_argc = -1; int opt_filter = 0; + int opt_no_sub = 0; if (argc) { argc--; argv++; } @@ -208,6 +288,7 @@ main (int argc, char **argv) " --verbose Print timings etc.\n" " --debug Flyswatter\n" " --filter Filter mboxes from input lines\n" + " --no-sub Ignore '+'-sub-addresses\n" , stdout); exit (0); } @@ -227,6 +308,11 @@ main (int argc, char **argv) opt_filter = 1; argc--; argv++; } + else if (!strcmp (*argv, "--no-sub")) + { + opt_no_sub = 1; + argc--; argv++; + } else if (!strncmp (*argv, "--", 2)) { fprintf (stderr, PGM ": unknown option '%s'\n", *argv); @@ -235,10 +321,11 @@ main (int argc, char **argv) } if (opt_filter) - run_filter (); + run_filter (opt_no_sub); else { run_mbox_test (); + run_mbox_no_sub_test (); run_dns_test (); } diff --git a/dirmngr/server.c b/dirmngr/server.c index 05a530bce..a21e1abb6 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -731,7 +731,7 @@ cmd_dns_cert (assuan_context_t ctx, char *line) /* We lowercase ascii characters but the DANE I-D does not allow this. FIXME: Check after the release of the RFC whether to change this. */ - mbox = mailbox_from_userid (line); + mbox = mailbox_from_userid (line, 0); if (!mbox || !(domain = strchr (mbox, '@'))) { err = set_error (GPG_ERR_INV_USER_ID, "no mailbox in user id"); @@ -855,7 +855,7 @@ proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) line = skip_options (line); is_wkd_query = !(opt_policy_flags || opt_submission_addr); - mbox = mailbox_from_userid (line); + mbox = mailbox_from_userid (line, 0); if (!mbox || !(domain = strchr (mbox, '@'))) { err = set_error (GPG_ERR_INV_USER_ID, "no mailbox in user id"); diff --git a/g10/export.c b/g10/export.c index 9477b7526..d53be99fe 100644 --- a/g10/export.c +++ b/g10/export.c @@ -1469,7 +1469,7 @@ print_pka_or_dane_records (iobuf_t out, kbnode_t keyblock, PKT_public_key *pk, continue; xfree (mbox); - mbox = mailbox_from_userid (uid->name); + mbox = mailbox_from_userid (uid->name, 0); if (!mbox) continue; diff --git a/g10/getkey.c b/g10/getkey.c index 75ce9cb38..c776a6100 100644 --- a/g10/getkey.c +++ b/g10/getkey.c @@ -1373,7 +1373,7 @@ pubkey_cmp (ctrl_t ctrl, const char *name, struct pubkey_cmp_cookie *old, n; n = find_next_kbnode (n, PKT_USER_ID)) { PKT_user_id *uid = n->pkt->pkt.user_id; - char *mbox = mailbox_from_userid (uid->name); + char *mbox = mailbox_from_userid (uid->name, 0); int match = mbox ? strcasecmp (name, mbox) == 0 : 0; xfree (mbox); diff --git a/g10/gpg.c b/g10/gpg.c index aeb9c6fc0..110289243 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -3130,7 +3130,7 @@ main (int argc, char **argv) break; case oSender: { - char *mbox = mailbox_from_userid (pargs.r.ret_str); + char *mbox = mailbox_from_userid (pargs.r.ret_str, 0); if (!mbox) log_error (_("\"%s\" is not a proper mail address\n"), pargs.r.ret_str); diff --git a/g10/import.c b/g10/import.c index 23258a0a7..8ea5144b5 100644 --- a/g10/import.c +++ b/g10/import.c @@ -1264,7 +1264,7 @@ impex_filter_getval (void *cookie, const char *propname) { if (!uid->mbox) { - uid->mbox = mailbox_from_userid (uid->name); + uid->mbox = mailbox_from_userid (uid->name, 0); } result = uid->mbox; } diff --git a/g10/keylist.c b/g10/keylist.c index 8b7da76ee..793f7dacd 100644 --- a/g10/keylist.c +++ b/g10/keylist.c @@ -1020,7 +1020,7 @@ list_keyblock_print (ctrl_t ctrl, kbnode_t keyblock, int secret, int fpr, char *mbox, *hash, *p; char hashbuf[32]; - mbox = mailbox_from_userid (uid->name); + mbox = mailbox_from_userid (uid->name, 0); if (mbox && (p = strchr (mbox, '@'))) { *p++ = 0; diff --git a/g10/keyserver.c b/g10/keyserver.c index a8c222d3f..44870a610 100644 --- a/g10/keyserver.c +++ b/g10/keyserver.c @@ -2053,7 +2053,7 @@ keyserver_import_wkd (ctrl_t ctrl, const char *name, int quick, /* We want to work on the mbox. That is what dirmngr will do anyway * and we need the mbox for the import filter anyway. */ - mbox = mailbox_from_userid (name); + mbox = mailbox_from_userid (name, 0); if (!mbox) { err = gpg_error_from_syserror (); diff --git a/g10/sign.c b/g10/sign.c index df888136f..b2d1c1826 100644 --- a/g10/sign.c +++ b/g10/sign.c @@ -153,7 +153,8 @@ mk_notation_policy_etc (PKT_signature *sig, char *mbox; /* For now we use the uid which was used to locate the key. */ - if (pksk->user_id && (mbox = mailbox_from_userid (pksk->user_id->name))) + if (pksk->user_id + && (mbox = mailbox_from_userid (pksk->user_id->name, 0))) { if (DBG_LOOKUP) log_debug ("setting Signer's UID to '%s'\n", mbox); diff --git a/g10/tofu.c b/g10/tofu.c index 762b19b7a..44f354512 100644 --- a/g10/tofu.c +++ b/g10/tofu.c @@ -3292,7 +3292,7 @@ show_warning (const char *fingerprint, strlist_t user_id_list) static char * email_from_user_id (const char *user_id) { - char *email = mailbox_from_userid (user_id); + char *email = mailbox_from_userid (user_id, 0); if (! email) { /* Hmm, no email address was provided or we are out of core. Just diff --git a/g10/trustdb.c b/g10/trustdb.c index c46dc50c8..a230a6c03 100644 --- a/g10/trustdb.c +++ b/g10/trustdb.c @@ -1131,7 +1131,7 @@ tdb_get_validity_core (ctrl_t ctrl, if (sig && sig->signers_uid) /* Make sure the UID matches. */ { - char *email = mailbox_from_userid (user_id->name); + char *email = mailbox_from_userid (user_id->name, 0); if (!email || !*email || strcmp (sig->signers_uid, email) != 0) { if (DBG_TRUST) diff --git a/tools/gpg-wks-client.c b/tools/gpg-wks-client.c index bf6b119e0..0be5ea89b 100644 --- a/tools/gpg-wks-client.c +++ b/tools/gpg-wks-client.c @@ -627,11 +627,11 @@ command_supported (char *userid) if (!strchr (userid, '@')) { char *tmp = xstrconcat ("foo@", userid, NULL); - addrspec = mailbox_from_userid (tmp); + addrspec = mailbox_from_userid (tmp, 0); xfree (tmp); } else - addrspec = mailbox_from_userid (userid); + addrspec = mailbox_from_userid (userid, 0); if (!addrspec) { log_error (_("\"%s\" is not a proper mail address\n"), userid); @@ -694,7 +694,7 @@ command_check (char *userid) uidinfo_list_t sl; int found = 0; - addrspec = mailbox_from_userid (userid); + addrspec = mailbox_from_userid (userid, 0); if (!addrspec) { log_error (_("\"%s\" is not a proper mail address\n"), userid); @@ -805,7 +805,7 @@ command_send (const char *fingerprint, const char *userid) goto leave; } - addrspec = mailbox_from_userid (userid); + addrspec = mailbox_from_userid (userid, 0); if (!addrspec) { log_error (_("\"%s\" is not a proper mail address\n"), userid); diff --git a/tools/gpg-wks-server.c b/tools/gpg-wks-server.c index 24b331262..1b533124a 100644 --- a/tools/gpg-wks-server.c +++ b/tools/gpg-wks-server.c @@ -2020,7 +2020,7 @@ command_install_key (const char *fname, const char *userid) char *huname = NULL; int any; - addrspec = mailbox_from_userid (userid); + addrspec = mailbox_from_userid (userid, 0); if (!addrspec) { log_error ("\"%s\" is not a proper mail address\n", userid); @@ -2153,7 +2153,7 @@ fname_from_userid (const char *userid, char **r_fname, char **r_addrspec) if (r_addrspec) *r_addrspec = NULL; - addrspec = mailbox_from_userid (userid); + addrspec = mailbox_from_userid (userid, 0); if (!addrspec) { if (opt.verbose) diff --git a/tools/wks-util.c b/tools/wks-util.c index 729098a02..cf80a25bc 100644 --- a/tools/wks-util.c +++ b/tools/wks-util.c @@ -104,7 +104,7 @@ append_to_uidinfo_list (uidinfo_list_t *list, const char *uid, time_t created) strcpy (sl->uid, uid); sl->created = created; - sl->mbox = mailbox_from_userid (uid); + sl->mbox = mailbox_from_userid (uid, 0); sl->next = NULL; if (!*list) *list = sl; From 678e4706ee614a6b7e543e2a80072d75405dd4db Mon Sep 17 00:00:00 2001 From: Andre Heinecke Date: Wed, 24 Oct 2018 10:40:42 +0200 Subject: [PATCH 067/235] dirmngr: Add FLUSHCRLs command Summary: * dirmngr/crlcache.c (crl_cache_flush): Also deinit the cache. * dirmngr/server.c (hlp_flushcrls, cmd_flushcrls): New. (register_commands): Add FLUSHCRLS. -- This allows it to flush the CRL cache of a running dirmngr server. This can be useful to debug / analyze CRL issues. GnuPG-Bug-Id: T3967 Differential Revision: https://dev.gnupg.org/D469 Signed-off-by: Andre Heinecke (cherry picked from commit 00321a025f90990a71b60b4689ede1f38fbde347) --- dirmngr/crlcache.c | 4 +++- dirmngr/server.c | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/dirmngr/crlcache.c b/dirmngr/crlcache.c index a2867be54..c9e5ca68f 100644 --- a/dirmngr/crlcache.c +++ b/dirmngr/crlcache.c @@ -1250,13 +1250,15 @@ crl_cache_deinit (void) } -/* Delete the cache from disk. Return 0 on success.*/ +/* Delete the cache from disk and memory. Return 0 on success.*/ int crl_cache_flush (void) { int rc; + crl_cache_deinit (); rc = cleanup_cache_dir (0)? -1 : 0; + crl_cache_init (); return rc; } diff --git a/dirmngr/server.c b/dirmngr/server.c index a21e1abb6..ac2562031 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -2689,6 +2689,20 @@ cmd_reloaddirmngr (assuan_context_t ctx, char *line) } +static const char hlp_flushcrls[] = + "FLUSHCRLS\n" + "\n" + "Remove all cached CRLs from memory and\n" + "the file system."; +static gpg_error_t +cmd_flushcrls (assuan_context_t ctx, char *line) +{ + (void)line; + + return leave_cmd (ctx, crl_cache_flush () ? GPG_ERR_GENERAL : 0); +} + + /* Tell the assuan library about our commands. */ static int @@ -2719,6 +2733,7 @@ register_commands (assuan_context_t ctx) { "LOADSWDB", cmd_loadswdb, hlp_loadswdb }, { "KILLDIRMNGR",cmd_killdirmngr,hlp_killdirmngr }, { "RELOADDIRMNGR",cmd_reloaddirmngr,hlp_reloaddirmngr }, + { "FLUSHCRLS", cmd_flushcrls, hlp_flushcrls }, { NULL, NULL } }; int i, j, rc; From d58fe697acefd435ec01503dae574d6c99dfedae Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 13 Nov 2018 11:37:37 +0900 Subject: [PATCH 068/235] build: Update libgcrypt.m4 and ntbtls.m4. * m4/libgcrypt.m4: Update from master. * m4/ntbtls.m4: Update from master. Signed-off-by: NIIBE Yutaka --- m4/libgcrypt.m4 | 29 +++++++++++++++-------------- m4/ntbtls.m4 | 29 ++++++++++++++++------------- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/m4/libgcrypt.m4 b/m4/libgcrypt.m4 index 40ea01c2c..37dfbea24 100644 --- a/m4/libgcrypt.m4 +++ b/m4/libgcrypt.m4 @@ -9,7 +9,7 @@ # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # -# Last-changed: 2018-11-02 +# Last-changed: 2018-11-13 dnl AM_PATH_LIBGCRYPT([MINIMUM-VERSION, @@ -36,19 +36,6 @@ AC_DEFUN([AM_PATH_LIBGCRYPT], if test x"${LIBGCRYPT_CONFIG}" = x ; then if test x"${libgcrypt_config_prefix}" != x ; then LIBGCRYPT_CONFIG="${libgcrypt_config_prefix}/bin/libgcrypt-config" - else - case "${SYSROOT}" in - /*) - if test -x "${SYSROOT}/bin/libgcrypt-config" ; then - LIBGCRYPT_CONFIG="${SYSROOT}/bin/libgcrypt-config" - fi - ;; - '') - ;; - *) - AC_MSG_WARN([Ignoring \$SYSROOT as it is not an absolute path.]) - ;; - esac fi fi @@ -61,6 +48,20 @@ AC_DEFUN([AM_PATH_LIBGCRYPT], fi fi if test -z "$use_gpgrt_config"; then + if test x"${LIBGCRYPT_CONFIG}" = x ; then + case "${SYSROOT}" in + /*) + if test -x "${SYSROOT}/bin/libgcrypt-config" ; then + LIBGCRYPT_CONFIG="${SYSROOT}/bin/libgcrypt-config" + fi + ;; + '') + ;; + *) + AC_MSG_WARN([Ignoring \$SYSROOT as it is not an absolute path.]) + ;; + esac + fi AC_PATH_PROG(LIBGCRYPT_CONFIG, libgcrypt-config, no) fi diff --git a/m4/ntbtls.m4 b/m4/ntbtls.m4 index 7bbd5699c..18b43d939 100644 --- a/m4/ntbtls.m4 +++ b/m4/ntbtls.m4 @@ -8,6 +8,8 @@ dnl dnl This file is distributed in the hope that it will be useful, but dnl WITHOUT ANY WARRANTY, to the extent permitted by law; without even the dnl implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +dnl +dnl Last-changed: 2018-11-13 dnl AM_PATH_NTBTLS([MINIMUM-VERSION, @@ -30,19 +32,6 @@ AC_DEFUN([AM_PATH_NTBTLS], if test x"${NTBTLS_CONFIG}" = x ; then if test x"${ntbtls_config_prefix}" != x ; then NTBTLS_CONFIG="${ntbtls_config_prefix}/bin/ntbtls-config" - else - case "${SYSROOT}" in - /*) - if test -x "${SYSROOT}/bin/ntbtls-config" ; then - NTBTLS_CONFIG="${SYSROOT}/bin/ntbtls-config" - fi - ;; - '') - ;; - *) - AC_MSG_WARN([Ignoring \$SYSROOT as it is not an absolute path.]) - ;; - esac fi fi @@ -55,6 +44,20 @@ AC_DEFUN([AM_PATH_NTBTLS], fi fi if test -z "$use_gpgrt_config"; then + if test x"${NTBTLS_CONFIG}" = x ; then + case "${SYSROOT}" in + /*) + if test -x "${SYSROOT}/bin/ntbtls-config" ; then + NTBTLS_CONFIG="${SYSROOT}/bin/ntbtls-config" + fi + ;; + '') + ;; + *) + AC_MSG_WARN([Ignoring \$SYSROOT as it is not an absolute path.]) + ;; + esac + fi AC_PATH_PROG(NTBTLS_CONFIG, ntbtls-config, no) fi From b3a70b67f33d3fd0c68dfe474285fb2b8b4ab134 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 12 Nov 2018 18:13:31 +0100 Subject: [PATCH 069/235] po: Clarify a translator's note. -- --- agent/call-pinentry.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index 048443ab3..3716e11bb 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -501,14 +501,16 @@ start_pinentry (ctrl_t ctrl) { /* Provide a few default strings for use by the pinentries. This - may help a pinentry to avoid implementing localization code. */ + * may help a pinentry to avoid implementing localization code. + * Note that gpg-agent has been set to utf-8 so that the strings + * are in the expected encoding. */ static const struct { const char *key, *value; int what; } tbl[] = { - /* TRANSLATORS: These are labels for buttons etc used in - Pinentries. An underscore indicates that the next letter - should be used as an accelerator. Double the underscore for - a literal one. The actual to be translated text starts after - the second vertical bar. Note that gpg-agent has been set to - utf-8 so that the strings are in the expected encoding. */ + /* TRANSLATORS: These are labels for buttons etc as used in + * Pinentries. In your translation copy the text before the + * second vertical bar verbatim; translate only the following + * text. An underscore indicates that the next letter should be + * used as an accelerator. Double the underscore to have + * pinentry display a literal underscore. */ { "ok", N_("|pinentry-label|_OK") }, { "cancel", N_("|pinentry-label|_Cancel") }, { "yes", N_("|pinentry-label|_Yes") }, From 914fa3be22bf8848a97a7dd405a040d6ef31e2fd Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 13 Nov 2018 11:35:39 +0100 Subject: [PATCH 070/235] dirmngr: Support the new WKD draft with the openpgpkey subdomain. * dirmngr/server.c (proc_wkd_get): Implement new openpgpkey subdomain method. Signed-off-by: Werner Koch --- dirmngr/dns-stuff.c | 21 ++++++++------- dirmngr/domaininfo.c | 2 +- dirmngr/server.c | 64 ++++++++++++++++++++++++++++++++++++++------ 3 files changed, 68 insertions(+), 19 deletions(-) diff --git a/dirmngr/dns-stuff.c b/dirmngr/dns-stuff.c index f86ccb0ae..7aa07c716 100644 --- a/dirmngr/dns-stuff.c +++ b/dirmngr/dns-stuff.c @@ -1056,16 +1056,17 @@ resolve_name_standard (ctrl_t ctrl, const char *name, unsigned short port, /* This a wrapper around getaddrinfo with slightly different semantics. - NAME is the name to resolve. - PORT is the requested port or 0. - WANT_FAMILY is either 0 (AF_UNSPEC), AF_INET6, or AF_INET4. - WANT_SOCKETTYPE is either SOCK_STREAM or SOCK_DGRAM. - - On success the result is stored in a linked list with the head - stored at the address R_AI; the caller must call gpg_addrinfo_free - on this. If R_CANONNAME is not NULL the official name of the host - is stored there as a malloced string; if that name is not available - NULL is stored. */ + * NAME is the name to resolve. + * PORT is the requested port or 0. + * WANT_FAMILY is either 0 (AF_UNSPEC), AF_INET6, or AF_INET4. + * WANT_SOCKETTYPE is either 0 for any socket type + * or SOCK_STREAM or SOCK_DGRAM. + * + * On success the result is stored in a linked list with the head + * stored at the address R_AI; the caller must call free_dns_addrinfo + * on this. If R_CANONNAME is not NULL the official name of the host + * is stored there as a malloced string; if that name is not available + * NULL is stored. */ gpg_error_t resolve_dns_name (ctrl_t ctrl, const char *name, unsigned short port, int want_family, int want_socktype, diff --git a/dirmngr/domaininfo.c b/dirmngr/domaininfo.c index a2effffef..f6263b06d 100644 --- a/dirmngr/domaininfo.c +++ b/dirmngr/domaininfo.c @@ -119,7 +119,7 @@ domaininfo_print_stats (void) } -/* Return true if DOMAIN definitely does not support WKD. Noet that +/* Return true if DOMAIN definitely does not support WKD. Note that * DOMAIN is expected to be lowercase. */ int domaininfo_is_wkd_not_supported (const char *domain) diff --git a/dirmngr/server.c b/dirmngr/server.c index ac2562031..4a242539b 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -837,8 +837,11 @@ proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) gpg_error_t err = 0; char *mbox = NULL; char *domainbuf = NULL; - char *domain; /* Points to mbox or domainbuf. */ - char *domain_orig;/* Points to mbox. */ + char *domain; /* Points to mbox or domainbuf. This is used to + * connect to the host. */ + char *domain_orig;/* Points to mbox. This is the used for the + * query; i.e. the domain part of the + * addrspec. */ char sha1buf[20]; char *uri = NULL; char *encodedhash = NULL; @@ -847,6 +850,7 @@ proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) int is_wkd_query; /* True if this is a real WKD query. */ int no_log = 0; char portstr[20] = { 0 }; + int subdomain_mode = 0; opt_submission_addr = has_option (line, "--submission-address"); opt_policy_flags = has_option (line, "--policy-flags"); @@ -864,7 +868,8 @@ proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) *domain++ = 0; domain_orig = domain; - /* First check whether we already know that the domain does not + + /* Let's check whether we already know that the domain does not * support WKD. */ if (is_wkd_query) { @@ -875,8 +880,41 @@ proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) } } - /* Check for SRV records. */ - if (1) + + /* First try the new "openpgp" subdomain. We check that the domain + * is valid because it is later used as an unescaped filename part + * of the URI. */ + if (is_valid_domain_name (domain_orig)) + { + dns_addrinfo_t aibuf; + + domainbuf = strconcat ( "openpgpkey.", domain_orig, NULL); + if (!domainbuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* FIXME: We should put a cache into dns-stuff because the same + * query (with a different port and socket type, though) will be + * done later by http function. */ + err = resolve_dns_name (ctrl, domainbuf, 0, 0, 0, &aibuf, NULL); + if (err) + { + err = 0; + xfree (domainbuf); + domainbuf = NULL; + } + else /* Got a subdomain. */ + { + free_dns_addrinfo (aibuf); + subdomain_mode = 1; + domain = domainbuf; + } + } + + /* Check for SRV records unless we have a subdomain. */ + if (!subdomain_mode) { struct srventry *srvs; unsigned int srvscount; @@ -931,6 +969,7 @@ proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) xfree (srvs); } + /* Prepare the hash of the local part. */ gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, mbox, strlen (mbox)); encodedhash = zb32_encode (sha1buf, 8*20); if (!encodedhash) @@ -944,7 +983,10 @@ proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) uri = strconcat ("https://", domain, portstr, - "/.well-known/openpgpkey/submission-address", + "/.well-known/openpgpkey/", + subdomain_mode? domain_orig : "", + subdomain_mode? "/" : "", + "submission-address", NULL); } else if (opt_policy_flags) @@ -952,7 +994,10 @@ proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) uri = strconcat ("https://", domain, portstr, - "/.well-known/openpgpkey/policy", + "/.well-known/openpgpkey/", + subdomain_mode? domain_orig : "", + subdomain_mode? "/" : "", + "policy", NULL); } else @@ -965,7 +1010,10 @@ proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) uri = strconcat ("https://", domain, portstr, - "/.well-known/openpgpkey/hu/", + "/.well-known/openpgpkey/", + subdomain_mode? domain_orig : "", + subdomain_mode? "/" : "", + "hu/", encodedhash, "?l=", escapedmbox, From 804a77edd9472d44606641b7772550521e1ba271 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 14 Nov 2018 10:45:15 +0900 Subject: [PATCH 071/235] agent: Simplify agent_popup_message_stop. * agent/call-pinentry.c (agent_popup_message_stop): Just kill it. -- By checking if it's alive or not, we can lower a risk of sending SIGINT to a wrong process on unusual condition when PID is re-used to a different process. That's true, however, since it's alive usually, simply sending SIGINT is enough here. Note that here is a race condition for detecting if process is active or not; A process can die just after being detected alive. Moreover, when the process of pinentry accidentally died already, it should have caused return of assuan_transact and the thread of popup_message_thread likely already set popup_finished=1. Signed-off-by: NIIBE Yutaka --- agent/call-pinentry.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index 3716e11bb..38c01e26f 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -1543,14 +1543,6 @@ agent_popup_message_stop (ctrl_t ctrl) TerminateProcess (process, 1); } #else - else if (pid && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) ) - { /* The daemon already died. No need to send a kill. However - because we already waited for the process, we need to tell - assuan that it should not wait again (done by - unlock_pinentry). */ - if (rc == pid) - assuan_set_flag (entry_ctx, ASSUAN_NO_WAITPID, 1); - } else if (pid > 0) kill (pid, SIGINT); #endif From 8b8ea802ca071c911158cab0203245a16a69125c Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 14 Nov 2018 13:17:49 +0100 Subject: [PATCH 072/235] Remove the gpg-zip script. * tools/gpg-zip.in: Remove. * m4/tar-ustar.m4: Remove. -- Note that the script was even not anymore installed. See also GnuPG-bug-id: 4252 Signed-off-by: Werner Koch --- configure.ac | 2 - m4/Makefile.am | 2 +- m4/tar-ustar.m4 | 43 -------------- tools/Makefile.am | 5 -- tools/gpg-zip.in | 148 ---------------------------------------------- 5 files changed, 1 insertion(+), 199 deletions(-) delete mode 100644 m4/tar-ustar.m4 delete mode 100644 tools/gpg-zip.in diff --git a/configure.ac b/configure.ac index 795daa400..780a5e20f 100644 --- a/configure.ac +++ b/configure.ac @@ -625,7 +625,6 @@ AC_ARG_VAR(YAT2M, [tool to convert texi to man pages]) AM_CONDITIONAL(HAVE_YAT2M, test -n "$ac_cv_path_YAT2M") AC_ISC_POSIX AC_SYS_LARGEFILE -GNUPG_CHECK_USTAR # We need to compile and run a program on the build machine. A @@ -2050,7 +2049,6 @@ agent/Makefile scd/Makefile g13/Makefile dirmngr/Makefile -tools/gpg-zip tools/Makefile doc/Makefile tests/Makefile diff --git a/m4/Makefile.am b/m4/Makefile.am index 3232413a5..250a4ac2c 100644 --- a/m4/Makefile.am +++ b/m4/Makefile.am @@ -1,6 +1,6 @@ EXTRA_DIST = intl.m4 intldir.m4 glibc2.m4 lock.m4 visibility.m4 intmax.m4 longdouble.m4 printf-posix.m4 signed.m4 size_max.m4 wchar_t.m4 wint_t.m4 xsize.m4 codeset.m4 gettext.m4 glibc21.m4 iconv.m4 intdiv0.m4 inttypes.m4 inttypes_h.m4 inttypes-pri.m4 isc-posix.m4 lcmessage.m4 lib-ld.m4 lib-link.m4 lib-prefix.m4 progtest.m4 stdint_h.m4 uintmax_t.m4 -EXTRA_DIST += ldap.m4 libcurl.m4 libusb.m4 tar-ustar.m4 readline.m4 pkg.m4 +EXTRA_DIST += ldap.m4 libcurl.m4 libusb.m4 readline.m4 pkg.m4 EXTRA_DIST += gnupg-pth.m4 diff --git a/m4/tar-ustar.m4 b/m4/tar-ustar.m4 deleted file mode 100644 index 4ae9e63aa..000000000 --- a/m4/tar-ustar.m4 +++ /dev/null @@ -1,43 +0,0 @@ -dnl Check for a tar program that speaks ustar format -dnl Copyright (C) 2005, 2006 Free Software Foundation, Inc. -dnl -dnl This file is free software, distributed under the terms of the GNU -dnl General Public License. As a special exception to the GNU General -dnl Public License, this file may be distributed as part of a program -dnl that contains a configuration script generated by Autoconf, under -dnl the same distribution terms as the rest of that program. - -AC_DEFUN([GNUPG_CHECK_USTAR], -[ - AC_ARG_WITH(tar, - AC_HELP_STRING([--with-tar=PATH],[look for a tar program in PATH]), - [_do_tar=$withval]) - - if test x$_do_tar != xno ; then - - if test x$_do_tar = x ; then - AC_PATH_PROG(TAR,"tar") - _mytar=$ac_cv_path_TAR - fi - - # Check if our tar is ustar format. If so, it's good. TODO: Add some - # code to check various options, etc, to try and create ustar - # format. - - if test x$_mytar != x ; then - AC_MSG_CHECKING([whether $_mytar speaks USTAR]) - echo hithere > conftest.txt - $_mytar -cf - conftest.txt | (dd skip=257 bs=1 count=5 2>/dev/null || cat) | grep ustar > /dev/null - _tar_bad=$? - rm conftest.txt - - if test x$_tar_bad = x0 ; then - AC_MSG_RESULT([yes]) - else - AC_MSG_RESULT([no]) - fi - fi - fi - - AM_CONDITIONAL(HAVE_USTAR, test x$_tar_bad = x0) -])dnl diff --git a/tools/Makefile.am b/tools/Makefile.am index 0c828a7bd..cedcbf402 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -34,11 +34,6 @@ AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(LIBASSUAN_CFLAGS) sbin_SCRIPTS = addgnupghome applygnupgdefaults -if HAVE_USTAR -# bin_SCRIPTS += gpg-zip -noinst_SCRIPTS = gpg-zip -endif - if BUILD_SYMCRYPTRUN symcryptrun = symcryptrun else diff --git a/tools/gpg-zip.in b/tools/gpg-zip.in deleted file mode 100644 index 48c4766b1..000000000 --- a/tools/gpg-zip.in +++ /dev/null @@ -1,148 +0,0 @@ -#!/bin/sh - -# gpg-archive - gpg-ized tar using the same format as PGP's PGP Zip. -# 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 . -# Despite the name, PGP Zip format is actually an OpenPGP-wrapped tar -# file. To be compatible with PGP itself, this must be a USTAR format -# tar file. Unclear on whether there is a distinction here between -# the GNU or POSIX variant of USTAR. - -VERSION=@VERSION@ -TAR=@TAR@ -GPG=gpg - -usage="\ -Usage: gpg-zip [--help] [--version] [--encrypt] [--decrypt] [--symmetric] - [--list-archive] [--output FILE] [--gpg GPG] [--gpg-args ARGS] - [--tar TAR] [--tar-args ARGS] filename1 [filename2, ...] - directory1 [directory2, ...] - -Encrypt or sign files into an archive." - -tar_verbose_opt="v" - -while test $# -gt 0 ; do - case $1 in - -h | --help | --h*) - echo "$usage" - exit 0 - ;; - --list-archive) - list=yes - create=no - unpack=no - shift - ;; - --encrypt | -e) - gpg_args="$gpg_args --encrypt" - list=no - create=yes - unpack=no - shift - ;; - --decrypt | -d) - gpg_args="$gpg_args --decrypt" - list=no - create=no - unpack=yes - shift - ;; - --symmetric | -c) - gpg_args="$gpg_args --symmetric" - list=no - create=yes - unpack=no - shift - ;; - --sign | -s) - gpg_args="$gpg_args --sign" - list=no - create=yes - unpack=no - shift - ;; - --recipient | -r) - gpg_args="$gpg_args --recipient $2" - shift - shift - ;; - --local-user | -u) - gpg_args="$gpg_args --local-user $2" - shift - shift - ;; - --output | -o) - gpg_args="$gpg_args --output $2" - shift - shift - ;; - --version) - echo "gpg-zip (GnuPG) $VERSION" - exit 0 - ;; - --gpg) - GPG=$2 - shift - shift - ;; - --gpg-args) - gpg_args="$gpg_args $2" - shift - shift - ;; - --tar) - TAR=$2 - shift - shift - ;; - --tar-args) - tar_args="$tar_args $2" - shift - shift - ;; - --quiet) - tar_verbose_opt="" - shift - ;; - --) - shift - break - ;; - -*) - echo "$usage" 1>&2 - exit 1 - ;; - *) - break - ;; - esac -done - -if test x$create = xyes ; then -# echo "$TAR $tar_args -cf - "$@" | $GPG --set-filename x.tar $gpg_args" 1>&2 - $TAR $tar_args -cf - "$@" | $GPG --set-filename x.tar $gpg_args -elif test x$list = xyes ; then -# echo "cat \"$1\" | $GPG $gpg_args | $TAR $tar_args -tf -" 1>&2 - cat "$1" | $GPG $gpg_args | $TAR $tar_args -tf - -elif test x$unpack = xyes ; then -# echo "cat \"$1\" | $GPG $gpg_args | $TAR $tar_args -xvf -" 1>&2 - cat "$1" | $GPG $gpg_args | $TAR $tar_args -x${tar_verbose_opt}f - -else - echo "$usage" 1>&2 - exit 1 -fi From a5542a4a702c2210facf58a98bc8d3d16089b6ab Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 15 Nov 2018 12:19:02 +0900 Subject: [PATCH 073/235] card: Display if KDF is enabled or not. * g10/call-agent.h (kdf_do_enabled): New field. * g10/call-agent.c (learn_status_cb): Set kdf_do_enabled if available. * g10/card-util.c (current_card_status): Inform the availability. Signed-off-by: NIIBE Yutaka --- g10/call-agent.c | 4 ++++ g10/call-agent.h | 5 +++-- g10/card-util.c | 5 +++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/g10/call-agent.c b/g10/call-agent.c index e9ea82e4f..2dbacf4ef 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -707,6 +707,10 @@ learn_status_cb (void *opaque, const char *line) xfree (parm->private_do[no]); parm->private_do[no] = unescape_status_string (line); } + else if (keywordlen == 3 && !memcmp (keyword, "KDF", 3)) + { + parm->kdf_do_enabled = 1; + } return 0; } diff --git a/g10/call-agent.h b/g10/call-agent.h index 1055b5ef9..1d232f7be 100644 --- a/g10/call-agent.h +++ b/g10/call-agent.h @@ -72,6 +72,7 @@ struct agent_card_info_s unsigned int bt:1; /* Button for confirmation available. */ } extcap; unsigned int status_indicator; + int kdf_do_enabled; /* Card has a KDF object */ }; @@ -193,14 +194,14 @@ gpg_error_t agent_keywrap_key (ctrl_t ctrl, int forexport, gpg_error_t agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr, const void *key, size_t keylen, int unattended, int force, - u32 *keyid, u32 *mainkeyid, int pubkey_algo); + u32 *keyid, u32 *mainkeyid, int pubkey_algo); /* Receive a key from the agent. */ gpg_error_t agent_export_key (ctrl_t ctrl, const char *keygrip, const char *desc, int openpgp_protected, char **cache_nonce_addr, unsigned char **r_result, size_t *r_resultlen, - u32 *keyid, u32 *mainkeyid, int pubkey_algo); + u32 *keyid, u32 *mainkeyid, int pubkey_algo); /* Delete a key from the agent. */ gpg_error_t agent_delete_key (ctrl_t ctrl, const char *hexkeygrip, diff --git a/g10/card-util.c b/g10/card-util.c index a1a099d85..bfaac3bd7 100644 --- a/g10/card-util.c +++ b/g10/card-util.c @@ -618,6 +618,11 @@ current_card_status (ctrl_t ctrl, estream_t fp, tty_fprintf (fp, "PIN retry counter : %d %d %d\n", info.chvretry[0], info.chvretry[1], info.chvretry[2]); tty_fprintf (fp, "Signature counter : %lu\n", info.sig_counter); + if (info.extcap.kdf) + { + tty_fprintf (fp, "KDF setting ......: %s\n", + info.kdf_do_enabled ? "on" : "off"); + } tty_fprintf (fp, "Signature key ....:"); print_shax_fpr (fp, info.fpr1len? info.fpr1:NULL, info.fpr1len); if (info.fpr1len && info.fpr1time) From 05d163aebc04db109ec5e004eb04a4b3796f6421 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 15 Nov 2018 13:31:12 +0900 Subject: [PATCH 074/235] scd: Make "learn" report about KDF data object. * scd/app-openpgp.c (do_learn_status): Report KDF attr. * g10/card-util.c (current_card_status): Output KDF for with_colons. Signed-off-by: NIIBE Yutaka --- g10/card-util.c | 4 ++++ scd/app-openpgp.c | 11 ++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/g10/card-util.c b/g10/card-util.c index bfaac3bd7..8de061aa4 100644 --- a/g10/card-util.c +++ b/g10/card-util.c @@ -512,6 +512,10 @@ current_card_status (ctrl_t ctrl, estream_t fp, es_fprintf (fp, "pinretry:%d:%d:%d:\n", info.chvretry[0], info.chvretry[1], info.chvretry[2]); es_fprintf (fp, "sigcount:%lu:::\n", info.sig_counter); + if (info.extcap.kdf) + { + es_fprintf (fp, "kdf:%s:\n", info.kdf_do_enabled ? "on" : "off"); + } for (i=0; i < 4; i++) { diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index 789b72f00..9948c0bd9 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -1833,9 +1833,14 @@ do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) do_getattr (app, ctrl, "CA-FPR"); do_getattr (app, ctrl, "CHV-STATUS"); do_getattr (app, ctrl, "SIG-COUNTER"); - do_getattr (app, ctrl, "UIF-1"); - do_getattr (app, ctrl, "UIF-2"); - do_getattr (app, ctrl, "UIF-3"); + if (app->app_local->extcap.kdf_do) + do_getattr (app, ctrl, "KDF"); + if (app->app_local->extcap.has_button) + { + do_getattr (app, ctrl, "UIF-1"); + do_getattr (app, ctrl, "UIF-2"); + do_getattr (app, ctrl, "UIF-3"); + } if (app->app_local->extcap.private_dos) { do_getattr (app, ctrl, "PRIVATE-DO-1"); From e955ca245ea08e68ae2397f1583c8728d72acbd8 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 15 Nov 2018 13:57:31 +0900 Subject: [PATCH 075/235] card: Display UIF setting. * g10/call-agent.h (agent_card_info_s): Add UIF fields. * g10/call-agent.c (learn_status_cb): Put UIF DOs info. * g10/card-util.c (current_card_status): Output for UIF. Signed-off-by: NIIBE Yutaka --- g10/call-agent.c | 11 +++++++++++ g10/call-agent.h | 3 ++- g10/card-util.c | 11 +++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/g10/call-agent.c b/g10/call-agent.c index 2dbacf4ef..11011aea4 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -711,6 +711,17 @@ learn_status_cb (void *opaque, const char *line) { parm->kdf_do_enabled = 1; } + else if (keywordlen == 5 && !memcmp (keyword, "UIF-", 4) + && strchr("123", keyword[4])) + { + unsigned char *data; + int no = keyword[4] - '1'; + + log_assert (no >= 0 && no <= 2); + data = unescape_status_string (line); + parm->uif[no] = (data[0] != 0xff); + xfree (data); + } return 0; } diff --git a/g10/call-agent.h b/g10/call-agent.h index 1d232f7be..8ea8ffea6 100644 --- a/g10/call-agent.h +++ b/g10/call-agent.h @@ -72,7 +72,8 @@ struct agent_card_info_s unsigned int bt:1; /* Button for confirmation available. */ } extcap; unsigned int status_indicator; - int kdf_do_enabled; /* Card has a KDF object */ + int kdf_do_enabled; /* True if card has a KDF object. */ + int uif[3]; /* True if User Interaction Flag is on. */ }; diff --git a/g10/card-util.c b/g10/card-util.c index 8de061aa4..eca248433 100644 --- a/g10/card-util.c +++ b/g10/card-util.c @@ -516,6 +516,11 @@ current_card_status (ctrl_t ctrl, estream_t fp, { es_fprintf (fp, "kdf:%s:\n", info.kdf_do_enabled ? "on" : "off"); } + if (info.extcap.bt) + { + es_fprintf (fp, "uif:%d:%d:%d:\n", + info.uif[0], info.uif[1], info.uif[2]); + } for (i=0; i < 4; i++) { @@ -627,6 +632,12 @@ current_card_status (ctrl_t ctrl, estream_t fp, tty_fprintf (fp, "KDF setting ......: %s\n", info.kdf_do_enabled ? "on" : "off"); } + if (info.extcap.bt) + { + tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n", + info.uif[0] ? "on" : "off", info.uif[1] ? "on" : "off", + info.uif[2] ? "on" : "off"); + } tty_fprintf (fp, "Signature key ....:"); print_shax_fpr (fp, info.fpr1len? info.fpr1:NULL, info.fpr1len); if (info.fpr1len && info.fpr1time) From c8f79cec743c60b03db4c6d1e901a42e41faa1bc Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 16 Nov 2018 08:30:47 +0100 Subject: [PATCH 076/235] doc: Add NEWS item from recent 2.2 releases. -- --- NEWS | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 9 deletions(-) diff --git a/NEWS b/NEWS index 245cc7044..cf096b66e 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,66 @@ Noteworthy changes in version 2.3.0 (unreleased) ------------------------------------------------ + Changes also found in 2.2.11: + + * gpgsm: Fix CRL loading when intermediate certicates are not yet + trusted. + + * gpgsm: Fix an error message about the digest algo. [#4219] + + * gpg: Fix a wrong warning due to new sign usage check introduced + with 2.2.9. [#4014] + + * gpg: Print the "data source" even for an unsuccessful keyserver + query. + + * gpg: Do not store the TOFU trust model in the trustdb. This + allows to enable or disable a TOFO model without triggering a + trustdb rebuild. [#4134] + + * scd: Fix cases of "Bad PIN" after using "forcesig". [#4177] + + * agent: Fix possible hang in the ssh handler. [#4221] + + * dirmngr: Tack the unmodified mail address to a WKD request. See + commit a2bd4a64e5b057f291a60a9499f881dd47745e2f for details. + + * dirmngr: Tweak diagnostic about missing LDAP server file. + + * dirmngr: In verbose mode print the OCSP responder id. + + * dirmngr: Fix parsing of the LDAP port. [#4230] + + * wks: Add option --directory/-C to the server. Always build the + server on Unix systems. + + * wks: Add option --with-colons to the client. Support sites which + use the policy file instead of the submission-address file. + + * Fix EBADF when gpg et al. are called by broken CGI scripts. + + * Fix some minor memory leaks and bugs. + + Release-info: https://dev.gnupg.org/T4233 + See-also: gnupg-announce/2018q4/000432.html + + Changes also found in 2.2.10: + + * gpg: Refresh expired keys originating from the WKD. [#2917] + + * gpg: Use a 256 KiB limit for a WKD imported key. + + * gpg: New option --known-notation. [#4060] + + * scd: Add support for the Trustica Cryptoucan reader. + + * agent: Speed up starting during on-demand launching. [#3490] + + * dirmngr: Validate SRV records in WKD queries. + + Release-info: https://dev.gnupg.org/T4112 + See-also: gnupg-announce/2018q3/000428.html + Changes also found in 2.2.9: * dirmngr: Fix recursive resolver mode and other bugs in the libdns @@ -317,15 +377,17 @@ Noteworthy changes in version 2.3.0 (unreleased) Release dates of 2.2.x versions: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Version 2.2.1 (2017-09-19) - Version 2.2.2 (2017-11-07) - Version 2.2.3 (2017-11-20) - Version 2.2.4 (2017-12-20) - Version 2.2.5 (2018-02-22) - Version 2.2.6 (2018-04-09) - Version 2.2.7 (2018-05-02) - Version 2.2.8 (2018-06-08) - Version 2.2.9 (2018-07-12) + Version 2.2.1 (2017-09-19) + Version 2.2.2 (2017-11-07) + Version 2.2.3 (2017-11-20) + Version 2.2.4 (2017-12-20) + Version 2.2.5 (2018-02-22) + Version 2.2.6 (2018-04-09) + Version 2.2.7 (2018-05-02) + Version 2.2.8 (2018-06-08) + Version 2.2.9 (2018-07-12) + Version 2.2.10 (2018-08-30) + Version 2.2.11 (2018-11-06) Noteworthy changes in version 2.2.0 (2017-08-28) From 1e700961ddf4c54ec5a03a697fe00d7eb606fdff Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 16 Nov 2018 09:19:10 +0100 Subject: [PATCH 077/235] gpg: Start using OCB mode by default with Libgcrypt 1.9. * g10/main.h (GCRYPT_VERSION_NUMBER): Fix type in condition. -- GnuPG-bug-id: 4259 Signed-off-by: Werner Koch --- g10/main.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/g10/main.h b/g10/main.h index 768f7cfb8..86f8589b2 100644 --- a/g10/main.h +++ b/g10/main.h @@ -41,7 +41,9 @@ # define DEFAULT_CIPHER_ALGO CIPHER_ALGO_3DES #endif -#if GCRYPT_VERSION_NUMBER < 0x019000 +/* We will start using OCB mode by default only if the yet to be + * released libgcrypt 1.9 is used. */ +#if GCRYPT_VERSION_NUMBER < 0x010900 # define DEFAULT_AEAD_ALGO AEAD_ALGO_OCB #else # define DEFAULT_AEAD_ALGO AEAD_ALGO_EAX From e5c3a6999a374813134a9e68744444c25c3017f6 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 21 Nov 2018 09:20:56 +0100 Subject: [PATCH 078/235] doc: Clarify use of clear and nodefault in the AKL. -- --- doc/gpg.texi | 4 +++- g10/gpg.c | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/gpg.texi b/doc/gpg.texi index dedb8cc42..58672c9e0 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -1783,7 +1783,9 @@ list. The default is "local,wkd". @item clear Clear all defined mechanisms. This is useful to override - mechanisms given in a config file. + mechanisms given in a config file. Note that a @code{nodefault} in + @var{mechanisms} will also be cleared unless it is given after the + @code{clear}. @end table diff --git a/g10/gpg.c b/g10/gpg.c index 110289243..9f96ef407 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -3554,7 +3554,7 @@ main (int argc, char **argv) case oAutoKeyLocate: if (default_akl) { - /* This is the first time --aito-key-locate is seen. + /* This is the first time --auto-key-locate is seen. * We need to reset the default akl. */ default_akl = 0; release_akl(); From fa1b1eaa4241ff3f0634c8bdf8591cbc7c464144 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 22 Nov 2018 22:27:56 +0100 Subject: [PATCH 079/235] dirmngr: Avoid possible CSRF attacks via http redirects. * dirmngr/http.h (parsed_uri_s): Add fields off_host and off_path. (http_redir_info_t): New. * dirmngr/http.c (do_parse_uri): Set new fields. (same_host_p): New. (http_prepare_redirect): New. * dirmngr/t-http-basic.c: New test. * dirmngr/ks-engine-hkp.c (send_request): Use http_prepare_redirect instead of the open code. * dirmngr/ks-engine-http.c (ks_http_fetch): Ditto. -- With this change a http query will not follow a redirect unless the Location header gives the same host. If the host is different only the host and port is taken from the Location header and the original path and query parts are kept. Signed-off-by: Werner Koch --- dirmngr/Makefile.am | 11 ++- dirmngr/http.c | 171 ++++++++++++++++++++++++++++++++- dirmngr/http.h | 21 +++++ dirmngr/ks-engine-hkp.c | 56 ++++------- dirmngr/ks-engine-http.c | 66 ++++--------- dirmngr/t-http-basic.c | 199 +++++++++++++++++++++++++++++++++++++++ dirmngr/t-http.c | 4 +- 7 files changed, 434 insertions(+), 94 deletions(-) create mode 100644 dirmngr/t-http-basic.c diff --git a/dirmngr/Makefile.am b/dirmngr/Makefile.am index 22b8c1a3a..098e711e9 100644 --- a/dirmngr/Makefile.am +++ b/dirmngr/Makefile.am @@ -120,7 +120,7 @@ t_common_ldadd = $(libcommon) $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ $(NTBTLS_LIBS) $(LIBGNUTLS_LIBS) \ $(DNSLIBS) $(LIBINTL) $(LIBICONV) -module_tests = +module_tests = t-http-basic if USE_LDAP module_tests += t-ldap-parse-uri @@ -151,6 +151,15 @@ t_http_CFLAGS = -DWITHOUT_NPTH=1 $(USE_C99_CFLAGS) \ t_http_LDADD = $(t_common_ldadd) \ $(NTBTLS_LIBS) $(KSBA_LIBS) $(LIBGNUTLS_LIBS) $(DNSLIBS) +t_http_basic_SOURCES = $(t_common_src) t-http-basic.c http.c \ + dns-stuff.c http-common.c +t_http_basic_CFLAGS = -DWITHOUT_NPTH=1 $(USE_C99_CFLAGS) \ + $(LIBGCRYPT_CFLAGS) $(NTBTLS_CFLAGS) $(LIBGNUTLS_CFLAGS) \ + $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) $(KSBA_CFLAGS) +t_http_basic_LDADD = $(t_common_ldadd) \ + $(NTBTLS_LIBS) $(KSBA_LIBS) $(LIBGNUTLS_LIBS) $(DNSLIBS) + + t_ldap_parse_uri_SOURCES = \ t-ldap-parse-uri.c ldap-parse-uri.c ldap-parse-uri.h \ http.c http-common.c dns-stuff.c \ diff --git a/dirmngr/http.c b/dirmngr/http.c index 5fb7eed04..6c183b2ec 100644 --- a/dirmngr/http.c +++ b/dirmngr/http.c @@ -1350,6 +1350,8 @@ do_parse_uri (parsed_uri_t uri, int only_local_part, uri->v6lit = 0; uri->onion = 0; uri->explicit_port = 0; + uri->off_host = 0; + uri->off_path = 0; /* A quick validity check. */ if (strspn (p, VALID_URI_CHARS) != n) @@ -1393,7 +1395,19 @@ do_parse_uri (parsed_uri_t uri, int only_local_part, { p += 2; if ((p2 = strchr (p, '/'))) - *p2++ = 0; + { + if (p2 - uri->buffer > 10000) + return GPG_ERR_BAD_URI; + uri->off_path = p2 - uri->buffer; + *p2++ = 0; + } + else + { + n = (p - uri->buffer) + strlen (p); + if (n > 10000) + return GPG_ERR_BAD_URI; + uri->off_path = n; + } /* Check for username/password encoding */ if ((p3 = strchr (p, '@'))) @@ -1412,11 +1426,19 @@ do_parse_uri (parsed_uri_t uri, int only_local_part, *p3++ = '\0'; /* worst case, uri->host should have length 0, points to \0 */ uri->host = p + 1; + if (p - uri->buffer > 10000) + return GPG_ERR_BAD_URI; + uri->off_host = (p + 1) - uri->buffer; uri->v6lit = 1; p = p3; } else - uri->host = p; + { + uri->host = p; + if (p - uri->buffer > 10000) + return GPG_ERR_BAD_URI; + uri->off_host = p - uri->buffer; + } if ((p3 = strchr (p, ':'))) { @@ -3496,3 +3518,148 @@ uri_query_lookup (parsed_uri_t uri, const char *key) return NULL; } + + +/* Return true if both URI point to the same host. */ +static int +same_host_p (parsed_uri_t a, parsed_uri_t b) +{ + return a->host && b->host && !ascii_strcasecmp (a->host, b->host); +} + + +/* Prepare a new URL for a HTTP redirect. INFO has flags controlling + * the operaion, STATUS_CODE is used for diagnostics, LOCATION is the + * value of the "Location" header, and R_URL reveives the new URL on + * success or NULL or error. Note that INFO->ORIG_URL is + * required. */ +gpg_error_t +http_prepare_redirect (http_redir_info_t *info, unsigned int status_code, + const char *location, char **r_url) +{ + gpg_error_t err; + parsed_uri_t locuri; + parsed_uri_t origuri; + char *newurl; + char *p; + + *r_url = NULL; + + if (!info || !info->orig_url) + return gpg_error (GPG_ERR_INV_ARG); + + if (!info->silent) + log_info (_("URL '%s' redirected to '%s' (%u)\n"), + info->orig_url, location? location:"[none]", status_code); + + if (!info->redirects_left) + { + if (!info->silent) + log_error (_("too many redirections\n")); + return gpg_error (GPG_ERR_NO_DATA); + } + info->redirects_left--; + + if (!location || !*location) + return gpg_error (GPG_ERR_NO_DATA); + + err = http_parse_uri (&locuri, location, 0); + if (err) + return err; + + /* Make sure that an onion address only redirects to another + * onion address, or that a https address only redirects to a + * https address. */ + if (info->orig_onion && !locuri->onion) + { + http_release_parsed_uri (locuri); + return gpg_error (GPG_ERR_FORBIDDEN); + } + if (!info->allow_downgrade && info->orig_https && !locuri->use_tls) + { + http_release_parsed_uri (locuri); + return gpg_error (GPG_ERR_FORBIDDEN); + } + + if (info->trust_location) + { + /* We trust the Location - return it verbatim. */ + http_release_parsed_uri (locuri); + newurl = xtrystrdup (location); + if (!newurl) + { + err = gpg_error_from_syserror (); + http_release_parsed_uri (locuri); + return err; + } + } + else if ((err = http_parse_uri (&origuri, info->orig_url, 0))) + { + http_release_parsed_uri (locuri); + return err; + } + else if (same_host_p (origuri, locuri)) + { + /* The host is the same and thus we can take the location + * verbatim. */ + http_release_parsed_uri (origuri); + http_release_parsed_uri (locuri); + newurl = xtrystrdup (location); + if (!newurl) + { + err = gpg_error_from_syserror (); + http_release_parsed_uri (locuri); + return err; + } + } + else + { + /* We take only the host and port from the URL given in the + * Location. This limits the effects of redirection attacks by + * rogue hosts returning an URL to servers in the client's own + * network. We don't even include the userinfo because they + * should be considered similar to the path and query parts. + */ + if (!(locuri->off_path - locuri->off_host)) + { + http_release_parsed_uri (origuri); + http_release_parsed_uri (locuri); + return gpg_error (GPG_ERR_BAD_URI); + } + if (!(origuri->off_path - origuri->off_host)) + { + http_release_parsed_uri (origuri); + http_release_parsed_uri (locuri); + return gpg_error (GPG_ERR_BAD_URI); + } + + newurl = xtrymalloc (strlen (origuri->original) + + (locuri->off_path - locuri->off_host) + 1); + if (!newurl) + { + err = gpg_error_from_syserror (); + http_release_parsed_uri (origuri); + http_release_parsed_uri (locuri); + return err; + } + /* Build new URL from + * uriguri: scheme userinfo ---- ---- path rest + * locuri: ------ -------- host port ---- ---- + */ + p = newurl; + memcpy (p, origuri->original, origuri->off_host); + p += origuri->off_host; + memcpy (p, locuri->original + locuri->off_host, + (locuri->off_path - locuri->off_host)); + p += locuri->off_path - locuri->off_host; + strcpy (p, origuri->original + origuri->off_path); + + http_release_parsed_uri (origuri); + http_release_parsed_uri (locuri); + if (!info->silent) + log_info (_("redirection changed to '%s'\n"), newurl); + } + + *r_url = newurl; + return 0; +} diff --git a/dirmngr/http.h b/dirmngr/http.h index a86abbee7..4755b9248 100644 --- a/dirmngr/http.h +++ b/dirmngr/http.h @@ -58,6 +58,8 @@ struct parsed_uri_s char *auth; /* username/password for basic auth. */ char *host; /* Host (converted to lowercase). */ unsigned short port; /* Port (always set if the host is set). */ + unsigned short off_host; /* Offset to the HOST respective PATH parts */ + unsigned short off_path; /* in the original URI buffer. */ char *path; /* Path. */ uri_tuple_t params; /* ";xxxxx" */ uri_tuple_t query; /* "?xxx=yyy" */ @@ -100,6 +102,21 @@ typedef struct http_session_s *http_session_t; struct http_context_s; typedef struct http_context_s *http_t; +/* An object used to track redirection infos. */ +struct http_redir_info_s +{ + unsigned int redirects_left; /* Number of still possible redirects. */ + const char *orig_url; /* The original requested URL. */ + unsigned int orig_onion:1; /* Original request was an onion address. */ + unsigned int orig_https:1; /* Original request was a http address. */ + unsigned int silent:1; /* No diagnostics. */ + unsigned int allow_downgrade:1;/* Allow a downgrade from https to http. */ + unsigned int trust_location:1; /* Trust the received Location header. */ +}; +typedef struct http_redir_info_s http_redir_info_t; + + + /* A TLS verify callback function. */ typedef gpg_error_t (*http_verify_cb_t) (void *opaque, http_t http, @@ -176,5 +193,9 @@ gpg_error_t http_verify_server_credentials (http_session_t sess); char *http_escape_string (const char *string, const char *specials); char *http_escape_data (const void *data, size_t datalen, const char *specials); +gpg_error_t http_prepare_redirect (http_redir_info_t *info, + unsigned int status_code, + const char *location, char **r_url); + #endif /*GNUPG_COMMON_HTTP_H*/ diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c index 790a8b1c5..1b14a2e6a 100644 --- a/dirmngr/ks-engine-hkp.c +++ b/dirmngr/ks-engine-hkp.c @@ -1201,18 +1201,21 @@ send_request (ctrl_t ctrl, const char *request, const char *hostportstr, gpg_error_t err; http_session_t session = NULL; http_t http = NULL; - int redirects_left = MAX_REDIRECTS; + http_redir_info_t redirinfo = { MAX_REDIRECTS }; estream_t fp = NULL; char *request_buffer = NULL; parsed_uri_t uri = NULL; - int is_onion; *r_fp = NULL; err = http_parse_uri (&uri, request, 0); if (err) goto leave; - is_onion = uri->onion; + redirinfo.orig_url = request; + redirinfo.orig_onion = uri->onion; + redirinfo.allow_downgrade = 1; + /* FIXME: I am not sure whey we allow a downgrade for hkp requests. + * Needs at least an explanation here.. */ err = http_session_new (&session, httphost, ((ctrl->http_no_crl? HTTP_FLAG_NO_CRL : 0) @@ -1293,45 +1296,18 @@ send_request (ctrl_t ctrl, const char *request, const char *hostportstr, case 302: case 307: { - const char *s = http_get_header (http, "Location"); + xfree (request_buffer); + err = http_prepare_redirect (&redirinfo, http_get_status_code (http), + http_get_header (http, "Location"), + &request_buffer); + if (err) + goto leave; - log_info (_("URL '%s' redirected to '%s' (%u)\n"), - request, s?s:"[none]", http_get_status_code (http)); - if (s && *s && redirects_left-- ) - { - if (is_onion) - { - /* Make sure that an onion address only redirects to - * another onion address. */ - http_release_parsed_uri (uri); - uri = NULL; - err = http_parse_uri (&uri, s, 0); - if (err) - goto leave; - - if (! uri->onion) - { - err = gpg_error (GPG_ERR_FORBIDDEN); - goto leave; - } - } - - xfree (request_buffer); - request_buffer = xtrystrdup (s); - if (request_buffer) - { - request = request_buffer; - http_close (http, 0); - http = NULL; - goto once_more; - } - err = gpg_error_from_syserror (); - } - else - err = gpg_error (GPG_ERR_NO_DATA); - log_error (_("too many redirections\n")); + request = request_buffer; + http_close (http, 0); + http = NULL; } - goto leave; + goto once_more; case 501: err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); diff --git a/dirmngr/ks-engine-http.c b/dirmngr/ks-engine-http.c index 9e6b9e1f5..0f3e2db4a 100644 --- a/dirmngr/ks-engine-http.c +++ b/dirmngr/ks-engine-http.c @@ -74,17 +74,18 @@ ks_http_fetch (ctrl_t ctrl, const char *url, unsigned int flags, http_session_t session = NULL; unsigned int session_flags; http_t http = NULL; - int redirects_left = MAX_REDIRECTS; + http_redir_info_t redirinfo = { MAX_REDIRECTS }; estream_t fp = NULL; char *request_buffer = NULL; parsed_uri_t uri = NULL; - int is_onion, is_https; err = http_parse_uri (&uri, url, 0); if (err) goto leave; - is_onion = uri->onion; - is_https = uri->use_tls; + redirinfo.orig_url = url; + redirinfo.orig_onion = uri->onion; + redirinfo.orig_https = uri->use_tls; + redirinfo.allow_downgrade = !!(flags & KS_HTTP_FETCH_ALLOW_DOWNGRADE); /* By default we only use the system provided certificates with this * fetch command. */ @@ -158,53 +159,20 @@ ks_http_fetch (ctrl_t ctrl, const char *url, unsigned int flags, case 302: case 307: { - const char *s = http_get_header (http, "Location"); + xfree (request_buffer); + err = http_prepare_redirect (&redirinfo, http_get_status_code (http), + http_get_header (http, "Location"), + &request_buffer); + if (err) + goto leave; - log_info (_("URL '%s' redirected to '%s' (%u)\n"), - url, s?s:"[none]", http_get_status_code (http)); - if (s && *s && redirects_left-- ) - { - if (is_onion || is_https) - { - /* Make sure that an onion address only redirects to - * another onion address, or that a https address - * only redirects to a https address. */ - http_release_parsed_uri (uri); - uri = NULL; - err = http_parse_uri (&uri, s, 0); - if (err) - goto leave; - - if (is_onion && !uri->onion) - { - err = gpg_error (GPG_ERR_FORBIDDEN); - goto leave; - } - if (!(flags & KS_HTTP_FETCH_ALLOW_DOWNGRADE) - && is_https && !uri->use_tls) - { - err = gpg_error (GPG_ERR_FORBIDDEN); - goto leave; - } - } - - xfree (request_buffer); - request_buffer = xtrystrdup (s); - if (request_buffer) - { - url = request_buffer; - http_close (http, 0); - http = NULL; - http_session_release (session); - goto once_more; - } - err = gpg_error_from_syserror (); - } - else - err = gpg_error (GPG_ERR_NO_DATA); - log_error (_("too many redirections\n")); + url = request_buffer; + http_close (http, 0); + http = NULL; + http_session_release (session); + session = NULL; } - goto leave; + goto once_more; default: log_error (_("error accessing '%s': http status %u\n"), diff --git a/dirmngr/t-http-basic.c b/dirmngr/t-http-basic.c new file mode 100644 index 000000000..edf82efb9 --- /dev/null +++ b/dirmngr/t-http-basic.c @@ -0,0 +1,199 @@ +/* t-http-basic.c - Basic regression tests for http.c + * Copyright (C) 2018 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 . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include + +#include "../common/util.h" +#include "t-support.h" +#include "http.h" + +#define PGM "t-http-basic" + + +static void +test_http_prepare_redirect (void) +{ + static struct { + const char *url; + const char *location; + const char *expect_url; + gpg_error_t expect_err; + } tests[] = { + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + NULL, + "", + GPG_ERR_NO_DATA + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "", + "", + GPG_ERR_NO_DATA + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "foo//bla", + "", + GPG_ERR_BAD_URI + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://foo.gnupg.org:8080/.not-so-well-known/openpgpkey/hu/12345678", + "http://foo.gnupg.org:8080/.well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http:///.no-so-well-known/openpgpkey/hu/12345678", + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + GPG_ERR_BAD_URI + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org:8080/.not-so-well-known/openpgpkey/hu/12345678", + "http://gnupg.org:8080/.not-so-well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org:8/.not-so-well-known/openpgpkey/hu/12345678", + "http://gnupg.org:8/.not-so-well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org:/.no-so-well-known/openpgpkey/hu/12345678", + "http://gnupg.org:/.no-so-well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org/", + "http://gnupg.org/", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.net", + "http://gnupg.net/.well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org", + "http://gnupg.org", + "http://gnupg.org", + 0 + }, + { + "http://gnupg.org", + "http://foo.gnupg.org", + "http://foo.gnupg.org", + 0 + }, + { + "http://gnupg.org/", + "http://foo.gnupg.org", + "http://foo.gnupg.org/", + 0 + }, + { + "http://gnupg.org", + "http://foo.gnupg.org/", + "http://foo.gnupg.org", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org/something-else", + "http://gnupg.org/something-else", + 0 + }, + }; + int tidx; + http_redir_info_t ri; + gpg_error_t err; + char *newurl; + + err = http_prepare_redirect (NULL, 301, tests[0].location, &newurl); + if (gpg_err_code (err) != GPG_ERR_INV_ARG) + fail (0); + memset (&ri, 0, sizeof ri); + err = http_prepare_redirect (&ri, 301, tests[0].location, &newurl); + if (gpg_err_code (err) != GPG_ERR_INV_ARG) + fail (0); + memset (&ri, 0, sizeof ri); + ri.silent = 1; + ri.orig_url = "http://example.org"; + err = http_prepare_redirect (&ri, 301, tests[0].location, &newurl); + if (gpg_err_code (err) != GPG_ERR_NO_DATA) + fail (0); + + for (tidx = 0; tidx < DIM (tests); tidx++) + { + memset (&ri, 0, sizeof ri); + ri.silent = 1; + ri.redirects_left = 1; + ri.orig_url = tests[tidx].url; + + err = http_prepare_redirect (&ri, 301, tests[tidx].location, &newurl); + if (err && newurl) + fail (tidx); + if (err && gpg_err_code (err) != tests[tidx].expect_err) + fail (tidx); + if (err) + continue; + if (!newurl) + fail (tidx); + if (strcmp (tests[tidx].expect_url, newurl)) + { + fprintf (stderr, "want: '%s'\n", tests[tidx].expect_url); + fprintf (stderr, "got : '%s'\n", newurl); + fail (tidx); + } + + xfree (newurl); + } +} + + +int +main (int argc, char **argv) +{ + (void)argc; + (void)argv; + + test_http_prepare_redirect (); + + return 0; +} diff --git a/dirmngr/t-http.c b/dirmngr/t-http.c index 8b32613b6..70d7f3fac 100644 --- a/dirmngr/t-http.c +++ b/dirmngr/t-http.c @@ -394,9 +394,9 @@ main (int argc, char **argv) else { printf ("Auth : %s\n", uri->auth? uri->auth:"[none]"); - printf ("Host : %s\n", uri->host); + printf ("Host : %s (off=%hu)\n", uri->host, uri->off_host); printf ("Port : %u\n", uri->port); - printf ("Path : %s\n", uri->path); + printf ("Path : %s (off=%hu)\n", uri->path, uri->off_path); for (r = uri->params; r; r = r->next) { printf ("Params: %s", r->name); From f45d6124696cc92ccdec09841b8182679c377997 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Mon, 26 Nov 2018 10:37:02 +0900 Subject: [PATCH 080/235] agent: Clean up SCDaemon management. * agent/call-scd.c (struct scd_local_s): Remove ctrl_backlink. (start_scd): Don't assign to the field. (agent_scd_check_aliveness): Fix typo in comment. Signed-off-by: NIIBE Yutaka --- agent/call-scd.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/agent/call-scd.c b/agent/call-scd.c index 51d9abd70..954be0238 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -54,12 +54,6 @@ struct scd_local_s SCD_LOCAL_LIST (see below). */ struct scd_local_s *next_local; - /* We need to get back to the ctrl object actually referencing this - structure. This is really an awkward way of enumerating the local - contexts. A much cleaner way would be to keep a global list of - ctrl objects to enumerate them. */ - ctrl_t ctrl_backlink; - assuan_context_t ctx; /* NULL or session context for the SCdaemon used with this connection. */ int locked; /* This flag is used to assert proper use of @@ -218,7 +212,6 @@ start_scd (ctrl_t ctrl) ctrl->scd_local = xtrycalloc (1, sizeof *ctrl->scd_local); if (!ctrl->scd_local) return gpg_error_from_syserror (); - ctrl->scd_local->ctrl_backlink = ctrl; ctrl->scd_local->next_local = scd_local_list; scd_local_list = ctrl->scd_local; } @@ -497,7 +490,7 @@ agent_scd_check_aliveness (void) now but take care that it won't do another wait. Also cleanup all other connections and release their resources. The next use will start a new daemon then. - Due to the use of the START_SCD_LOCAL we are sure that + Due to the use of the START_SCD_LOCK we are sure that none of these context are actually in use. */ struct scd_local_s *sl; From 9fb3f0f3f79e74166cce8e0781e97043f25890cc Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Mon, 26 Nov 2018 11:05:28 +0900 Subject: [PATCH 081/235] agent: Defer calling assuan_release when it's still in use. * agent/call-scd.c (struct scd_local_s): Remove LOCK, introduce IN_USE and INVALID flags. (unlock_scd): Call assuan_release when CTX is invalid. (start_scd): Set IN_USE. (agent_scd_check_aliveness): Don't call assuan_release when it's in use. Signed-off-by: NIIBE Yutaka --- agent/call-scd.c | 62 +++++++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/agent/call-scd.c b/agent/call-scd.c index 954be0238..592ddb939 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -54,11 +54,10 @@ struct scd_local_s SCD_LOCAL_LIST (see below). */ struct scd_local_s *next_local; - assuan_context_t ctx; /* NULL or session context for the SCdaemon - used with this connection. */ - int locked; /* This flag is used to assert proper use of - start_scd and unlock_scd. */ - + assuan_context_t ctx; /* NULL or session context for the SCdaemon + used with this connection. */ + unsigned int in_use: 1; /* CTX is in use. */ + unsigned int invalid:1; /* CTX is invalid, should be released. */ }; @@ -132,7 +131,7 @@ initialize_module_call_scd (void) { err = npth_mutex_init (&start_scd_lock, NULL); if (err) - log_fatal ("error initializing mutex: %s\n", strerror (err)); + log_fatal ("error initializing mutex: %s\n", strerror (err)); initialized = 1; } } @@ -162,14 +161,37 @@ agent_scd_dump_state (void) static int unlock_scd (ctrl_t ctrl, int rc) { - if (ctrl->scd_local->locked != 1) + int err; + + if (ctrl->scd_local->in_use == 0) { - log_error ("unlock_scd: invalid lock count (%d)\n", - ctrl->scd_local->locked); + log_error ("unlock_scd: CTX is not in use\n"); if (!rc) rc = gpg_error (GPG_ERR_INTERNAL); } - ctrl->scd_local->locked = 0; + ctrl->scd_local->in_use = 0; + if (ctrl->scd_local->invalid) + { + err = npth_mutex_lock (&start_scd_lock); + if (err) + { + log_error ("failed to acquire the start_scd lock: %s\n", + strerror (err)); + return gpg_error (GPG_ERR_INTERNAL); + } + + assuan_release (ctrl->scd_local->ctx); + ctrl->scd_local->ctx = NULL; + + err = npth_mutex_unlock (&start_scd_lock); + if (err) + { + log_error ("failed to release the start_scd lock: %s\n", + strerror (err)); + return gpg_error (GPG_ERR_INTERNAL); + } + } + ctrl->scd_local->invalid = 0; return rc; } @@ -216,15 +238,12 @@ start_scd (ctrl_t ctrl) scd_local_list = ctrl->scd_local; } - - /* Assert that the lock count is as expected. */ - if (ctrl->scd_local->locked) + if (ctrl->scd_local->in_use) { - log_error ("start_scd: invalid lock count (%d)\n", - ctrl->scd_local->locked); + log_error ("start_scd: CTX is in use\n"); return gpg_error (GPG_ERR_INTERNAL); } - ctrl->scd_local->locked++; + ctrl->scd_local->in_use = 1; if (ctrl->scd_local->ctx) return 0; /* Okay, the context is fine. We used to test for an @@ -344,7 +363,7 @@ start_scd (ctrl_t ctrl) detached flag so that under Windows SCDAEMON does not show up a new window. */ rc = assuan_pipe_connect (ctx, opt.scdaemon_program, argv, - no_close_list, atfork_cb, NULL, + no_close_list, atfork_cb, NULL, ASSUAN_PIPE_CONNECT_DETACHED); if (rc) { @@ -414,7 +433,7 @@ start_scd (ctrl_t ctrl) { unlock_scd (ctrl, err); if (ctx) - assuan_release (ctx); + assuan_release (ctx); } else { @@ -495,14 +514,13 @@ agent_scd_check_aliveness (void) struct scd_local_s *sl; assuan_set_flag (primary_scd_ctx, ASSUAN_NO_WAITPID, 1); - assuan_release (primary_scd_ctx); for (sl=scd_local_list; sl; sl = sl->next_local) { - if (sl->ctx) + sl->invalid = 1; + if (!sl->in_use && sl->ctx) { - if (sl->ctx != primary_scd_ctx) - assuan_release (sl->ctx); + assuan_release (sl->ctx); sl->ctx = NULL; } } From 40c7923ea881881a48de8f303b0870ec5910e13d Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Mon, 26 Nov 2018 12:07:36 +0900 Subject: [PATCH 082/235] agent: Have a thread to wait for the child process of scdaemon. * agent/call-scd.c (wait_child_thread): New. (start_scd): Create a thread for wait_child_thread. (agent_scd_check_aliveness): Remove. Signed-off-by: NIIBE Yutaka --- agent/agent.h | 1 - agent/call-scd.c | 182 ++++++++++++++++++++++++---------------------- agent/gpg-agent.c | 3 - 3 files changed, 97 insertions(+), 89 deletions(-) diff --git a/agent/agent.h b/agent/agent.h index 9baf59601..8a3e5c6b6 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -546,7 +546,6 @@ int divert_writekey (ctrl_t ctrl, int force, const char *serialno, void initialize_module_call_scd (void); void agent_scd_dump_state (void); int agent_scd_check_running (void); -void agent_scd_check_aliveness (void); int agent_reset_scd (ctrl_t ctrl); int agent_card_learn (ctrl_t ctrl, void (*kpinfo_cb)(void*, const char *), diff --git a/agent/call-scd.c b/agent/call-scd.c index 592ddb939..c6d062a02 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -207,6 +207,85 @@ atfork_cb (void *opaque, int where) } +static void * +wait_child_thread (void *arg) +{ + int err; + struct scd_local_s *sl; + +#ifdef HAVE_W32_SYSTEM + HANDLE pid = (HANDLE)arg; + + npth_unprotect (); + WaitForSingleObject ((HANDLE)pid, INFINITE); + npth_protect (); +#else + int wstatus; + pid_t pid = (pid_t)(uintptr_t)arg; + + again: + npth_unprotect (); + err = waitpid (pid, &wstatus, 0); + npth_protect (); + + if (err < 0) + { + if (errno == EINTR) + goto again; + log_error ("waitpid failed: %s\n", strerror (errno)); + return NULL; + } + else + { + if (WIFEXITED (wstatus)) + log_info ("scdaemon finished (status %d)\n", WEXITSTATUS (wstatus)); + else if (WIFSIGNALED (wstatus)) + log_info ("scdaemon killed by signal %d\n", WTERMSIG (wstatus)); + else + { + if (WIFSTOPPED (wstatus)) + log_info ("scdaemon stopped by signal %d\n", WSTOPSIG (wstatus)); + goto again; + } + } +#endif + + err = npth_mutex_lock (&start_scd_lock); + if (err) + { + log_error ("failed to acquire the start_scd lock: %s\n", + strerror (err)); + } + else + { + assuan_set_flag (primary_scd_ctx, ASSUAN_NO_WAITPID, 1); + + for (sl = scd_local_list; sl; sl = sl->next_local) + { + sl->invalid = 1; + if (!sl->in_use && sl->ctx) + { + assuan_release (sl->ctx); + sl->ctx = NULL; + } + } + + primary_scd_ctx = NULL; + primary_scd_ctx_reusable = 0; + + xfree (socket_name); + socket_name = NULL; + + err = npth_mutex_unlock (&start_scd_lock); + if (err) + log_error ("failed to release the start_scd lock while" + " doing the aliveness check: %s\n", strerror (err)); + } + + return NULL; +} + + /* Fork off the SCdaemon if this has not already been done. Lock the daemon and make sure that a proper context has been setup in CTRL. This function might also lock the daemon, which means that the @@ -427,6 +506,24 @@ start_scd (ctrl_t ctrl) primary_scd_ctx = ctx; primary_scd_ctx_reusable = 0; + { + npth_t thread; + npth_attr_t tattr; + pid_t pid; + + pid = assuan_get_pid (primary_scd_ctx); + err = npth_attr_init (&tattr); + if (!err) + { + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + err = npth_create (&thread, &tattr, wait_child_thread, + (void *)(uintptr_t)pid); + if (err) + log_error ("error spawning wait_child_thread: %s\n", strerror (err)); + npth_attr_destroy (&tattr); + } + } + leave: xfree (abs_homedir); if (err) @@ -456,91 +553,6 @@ agent_scd_check_running (void) } -/* Check whether the Scdaemon is still alive and clean it up if not. */ -void -agent_scd_check_aliveness (void) -{ - pid_t pid; -#ifdef HAVE_W32_SYSTEM - DWORD rc; -#else - int rc; -#endif - struct timespec abstime; - int err; - - if (!primary_scd_ctx) - return; /* No scdaemon running. */ - - /* This is not a critical function so we use a short timeout while - acquiring the lock. */ - npth_clock_gettime (&abstime); - abstime.tv_sec += 1; - err = npth_mutex_timedlock (&start_scd_lock, &abstime); - if (err) - { - if (err == ETIMEDOUT) - { - if (opt.verbose > 1) - log_info ("failed to acquire the start_scd lock while" - " doing an aliveness check: %s\n", strerror (err)); - } - else - log_error ("failed to acquire the start_scd lock while" - " doing an aliveness check: %s\n", strerror (err)); - return; - } - - if (primary_scd_ctx) - { - pid = assuan_get_pid (primary_scd_ctx); -#ifdef HAVE_W32_SYSTEM - /* If we have a PID we disconnect if either GetExitProcessCode - fails or if ir returns the exit code of the scdaemon. 259 is - the error code for STILL_ALIVE. */ - if (pid != (pid_t)(void*)(-1) && pid - && (!GetExitCodeProcess ((HANDLE)pid, &rc) || rc != 259)) -#else - if (pid != (pid_t)(-1) && pid - && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) ) -#endif - { - /* Okay, scdaemon died. Disconnect the primary connection - now but take care that it won't do another wait. Also - cleanup all other connections and release their - resources. The next use will start a new daemon then. - Due to the use of the START_SCD_LOCK we are sure that - none of these context are actually in use. */ - struct scd_local_s *sl; - - assuan_set_flag (primary_scd_ctx, ASSUAN_NO_WAITPID, 1); - - for (sl=scd_local_list; sl; sl = sl->next_local) - { - sl->invalid = 1; - if (!sl->in_use && sl->ctx) - { - assuan_release (sl->ctx); - sl->ctx = NULL; - } - } - - primary_scd_ctx = NULL; - primary_scd_ctx_reusable = 0; - - xfree (socket_name); - socket_name = NULL; - } - } - - err = npth_mutex_unlock (&start_scd_lock); - if (err) - log_error ("failed to release the start_scd lock while" - " doing the aliveness check: %s\n", strerror (err)); -} - - - /* Reset the SCD if it has been used. Actually it is not a reset but a cleanup of resources used by the current connection. */ int diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 911064cdc..de6947d4e 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -2370,9 +2370,6 @@ handle_tick (void) 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 From f12fcd907903742bde3ebb7ffef244c92d6d1611 Mon Sep 17 00:00:00 2001 From: Andre Heinecke Date: Mon, 26 Nov 2018 13:05:26 +0100 Subject: [PATCH 083/235] w32: Fix linkage of gpg-pair-tool * tools/Makefile.am (gpg_pair_tool_LDADD): Add W32SOCKLIBS. -- This is required because parts of libcommon depend on ws2_32. --- tools/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Makefile.am b/tools/Makefile.am index 196bf5210..e4fd81c5d 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -174,7 +174,7 @@ gpg_pair_tool_SOURCES = \ gpg_pair_tool_CFLAGS = $(GPG_ERROR_CFLAGS) $(INCICONV) gpg_pair_tool_LDADD = $(libcommon) \ $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ - $(LIBINTL) $(LIBICONV) + $(LIBINTL) $(LIBICONV) $(W32SOCKLIBS) # Make sure that all libs are build before we use them. This is From 483e63f9b5faead819ddd28308902ef43bf298ab Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 27 Nov 2018 11:08:51 +0900 Subject: [PATCH 084/235] agent: Better serialization for scdaemon access. * agent/call-scd.c (unlock_scd): Move lock before accessing IN_USE. (wait_child_thread): Add log_info for Windows, and fixed log_error message. -- The old code is still valid with cooperate threads, but this is better. Signed-off-by: NIIBE Yutaka --- agent/call-scd.c | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/agent/call-scd.c b/agent/call-scd.c index c6d062a02..796e7d86e 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -169,29 +169,25 @@ unlock_scd (ctrl_t ctrl, int rc) if (!rc) rc = gpg_error (GPG_ERR_INTERNAL); } + err = npth_mutex_lock (&start_scd_lock); + if (err) + { + log_error ("failed to acquire the start_scd lock: %s\n", strerror (err)); + return gpg_error (GPG_ERR_INTERNAL); + } ctrl->scd_local->in_use = 0; if (ctrl->scd_local->invalid) { - err = npth_mutex_lock (&start_scd_lock); - if (err) - { - log_error ("failed to acquire the start_scd lock: %s\n", - strerror (err)); - return gpg_error (GPG_ERR_INTERNAL); - } - assuan_release (ctrl->scd_local->ctx); ctrl->scd_local->ctx = NULL; - - err = npth_mutex_unlock (&start_scd_lock); - if (err) - { - log_error ("failed to release the start_scd lock: %s\n", - strerror (err)); - return gpg_error (GPG_ERR_INTERNAL); - } + ctrl->scd_local->invalid = 0; + } + err = npth_mutex_unlock (&start_scd_lock); + if (err) + { + log_error ("failed to release the start_scd lock: %s\n", strerror (err)); + return gpg_error (GPG_ERR_INTERNAL); } - ctrl->scd_local->invalid = 0; return rc; } @@ -219,6 +215,7 @@ wait_child_thread (void *arg) npth_unprotect (); WaitForSingleObject ((HANDLE)pid, INFINITE); npth_protect (); + log_info ("scdaemon finished\n"); #else int wstatus; pid_t pid = (pid_t)(uintptr_t)arg; @@ -278,8 +275,8 @@ wait_child_thread (void *arg) err = npth_mutex_unlock (&start_scd_lock); if (err) - log_error ("failed to release the start_scd lock while" - " doing the aliveness check: %s\n", strerror (err)); + log_error ("failed to release the start_scd lock after waitpid", + strerror (err)); } return NULL; From 47106ac435e891ced67f0ad9bb6b2ee12098c880 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 28 Nov 2018 14:59:44 +0900 Subject: [PATCH 085/235] scd: Serialize opening device by select_application. * scd/app.c (app_new_register): Don't lock APP_LIST_LOCK here. (select_application): Lock with APP_LIST_LOCK earlier. -- What we want to do here is to serialize the call of select_application. In the old code, it was possible that a call of select_application was blocked internally, and then another call of select_application entered. We can have a dedicated lock for call of select_application, but it is easier to re-use APP_LIST_LOCK. Signed-off-by: NIIBE Yutaka --- scd/app.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scd/app.c b/scd/app.c index a82db26cd..d16300efa 100644 --- a/scd/app.c +++ b/scd/app.c @@ -299,11 +299,8 @@ app_new_register (int slot, ctrl_t ctrl, const char *name, } app->periodical_check_needed = periodical_check_needed; - - npth_mutex_lock (&app_list_lock); app->next = app_top; app_top = app; - npth_mutex_unlock (&app_list_lock); unlock_app (app); return 0; } @@ -322,6 +319,8 @@ select_application (ctrl_t ctrl, const char *name, app_t *r_app, *r_app = NULL; + npth_mutex_lock (&app_list_lock); + if (scan || !app_top) { struct dev_list *l; @@ -330,7 +329,10 @@ select_application (ctrl_t ctrl, const char *name, app_t *r_app, /* Scan the devices to find new device(s). */ err = apdu_dev_list_start (opt.reader_port, &l); if (err) - return err; + { + npth_mutex_unlock (&app_list_lock); + return err; + } while (1) { @@ -365,7 +367,6 @@ select_application (ctrl_t ctrl, const char *name, app_t *r_app, scd_kick_the_loop (); } - npth_mutex_lock (&app_list_lock); for (a = app_top; a; a = a->next) { lock_app (a, ctrl); From cd64af003d4b6b46b69dbd575f73d53359ae0bcc Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 30 Nov 2018 12:35:37 +0100 Subject: [PATCH 086/235] gpg: Improve error message about failed keygrip computation. * g10/keyid.c (keygrip_from_pk): Print the fingerprint on failure. Signed-off-by: Werner Koch --- g10/keyid.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/g10/keyid.c b/g10/keyid.c index a9034ee46..3694c26cc 100644 --- a/g10/keyid.c +++ b/g10/keyid.c @@ -975,7 +975,12 @@ keygrip_from_pk (PKT_public_key *pk, unsigned char *array) if (!gcry_pk_get_keygrip (s_pkey, array)) { - log_info ("error computing keygrip\n"); + char *hexfpr; + + hexfpr = hexfingerprint (pk, NULL, 0); + log_info ("error computing keygrip (fpr=%s)\n", hexfpr); + xfree (hexfpr); + memset (array, 0, 20); err = gpg_error (GPG_ERR_GENERAL); } From 3a90efb7cf13532cc82b45c11a7abdadfe0c81f1 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 30 Nov 2018 12:38:51 +0100 Subject: [PATCH 087/235] scd: Add strerror to new error message. * agent/call-scd.c (wait_child_thread): Add %s. Signed-off-by: Werner Koch --- agent/call-scd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/call-scd.c b/agent/call-scd.c index 796e7d86e..d0d4794a4 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -275,7 +275,7 @@ wait_child_thread (void *arg) err = npth_mutex_unlock (&start_scd_lock); if (err) - log_error ("failed to release the start_scd lock after waitpid", + log_error ("failed to release the start_scd lock after waitpid: %s\n", strerror (err)); } From 2a650772b4e1c78a4fd20bc88433930e5551fe9c Mon Sep 17 00:00:00 2001 From: Jussi Kivilinna Date: Sat, 1 Dec 2018 13:43:09 +0200 Subject: [PATCH 088/235] common/mischelp: use platform memory zeroing function for wipememory * common/mischelp.h (wipememory): Replace macro with function prototype. (wipememory2): Remove. * common/mischelp.c (wipememory): New. * configure.ac (AC_CHECK_FUNCS): Check for 'explicit_bzero'. -- In new wipememory function, memory is cleared through platform provided secure memory zeroing function, SecureZeroMemory or explicit_bzero. If none of these is available, memset is called through volatile function pointer to so that compiler won't optimize away the call. Signed-off-by: Jussi Kivilinna --- common/mischelp.c | 16 ++++++++++++++++ common/mischelp.h | 12 +++--------- configure.ac | 20 ++++++++++---------- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/common/mischelp.c b/common/mischelp.c index 75ba60714..81dd501f8 100644 --- a/common/mischelp.c +++ b/common/mischelp.c @@ -49,6 +49,22 @@ #include "mischelp.h" +void +wipememory (void *ptr, size_t len) +{ +#if defined(HAVE_W32_SYSTEM) && defined(SecureZeroMemory) + SecureZeroMemory (ptr, len); +#elif defined(HAVE_EXPLICIT_BZERO) + explicit_bzero (ptr, len); +#else + /* Prevent compiler from optimizing away the call to memset by accessing + memset through volatile pointer. */ + static void *(*volatile memset_ptr)(void *, int, size_t) = (void *)memset; + memset_ptr (ptr, 0, len); +#endif +} + + /* Check whether the files NAME1 and NAME2 are identical. This is for example achieved by comparing the inode numbers of the files. */ int diff --git a/common/mischelp.h b/common/mischelp.h index 18ec96edf..bdee5a443 100644 --- a/common/mischelp.h +++ b/common/mischelp.h @@ -47,15 +47,9 @@ time_t timegm (struct tm *tm); #define DIM(v) (sizeof(v)/sizeof((v)[0])) #define DIMof(type,member) DIM(((type *)0)->member) -/* To avoid that a compiler optimizes certain memset calls away, these - macros may be used instead. */ -#define wipememory2(_ptr,_set,_len) do { \ - volatile char *_vptr=(volatile char *)(_ptr); \ - size_t _vlen=(_len); \ - while(_vlen) { *_vptr=(_set); _vptr++; _vlen--; } \ - } while(0) -#define wipememory(_ptr,_len) wipememory2(_ptr,0,_len) - +/* To avoid that a compiler optimizes certain memset calls away, + wipememory function may be used instead. */ +void wipememory(void *ptr, size_t len); /* Include hacks which are mainly required for Slowaris. */ #ifdef GNUPG_COMMON_NEED_AFLOCAL diff --git a/configure.ac b/configure.ac index 9d3eb41f0..89ef9398e 100644 --- a/configure.ac +++ b/configure.ac @@ -1400,16 +1400,16 @@ AC_FUNC_FSEEKO AC_FUNC_VPRINTF AC_FUNC_FORK AC_CHECK_FUNCS([atexit canonicalize_file_name clock_gettime ctermid \ - fcntl flockfile fsync ftello ftruncate funlockfile \ - getaddrinfo getenv getpagesize getpwnam getpwuid \ - getrlimit getrusage gettimeofday gmtime_r \ - inet_ntop inet_pton isascii lstat \ - memicmp memmove memrchr mmap nl_langinfo pipe \ - raise rand setenv setlocale setrlimit sigaction \ - sigprocmask stat stpcpy strcasecmp strerror strftime \ - stricmp strlwr strncasecmp strpbrk strsep \ - strtol strtoul strtoull tcgetattr timegm times \ - ttyname unsetenv wait4 waitpid ]) + explicit_bzero fcntl flockfile fsync ftello \ + ftruncate funlockfile getaddrinfo getenv getpagesize \ + getpwnam getpwuid getrlimit getrusage gettimeofday \ + gmtime_r inet_ntop inet_pton isascii lstat memicmp \ + memmove memrchr mmap nl_langinfo pipe raise rand \ + setenv setlocale setrlimit sigaction sigprocmask \ + stat stpcpy strcasecmp strerror strftime stricmp \ + strlwr strncasecmp strpbrk strsep strtol strtoul \ + strtoull tcgetattr timegm times ttyname unsetenv \ + wait4 waitpid ]) # On some systems (e.g. Solaris) nanosleep requires linking to librl. # Given that we use nanosleep only as an optimization over a select From 654e353d9b20f10fa275e7ae10cc50480654f079 Mon Sep 17 00:00:00 2001 From: Jussi Kivilinna Date: Sat, 1 Dec 2018 13:43:10 +0200 Subject: [PATCH 089/235] common/iobuf: fix memory wiping in iobuf_copy * common/iobuf.c (iobuf_copy): Wipe used area of buffer instead of first sizeof(char*) bytes. -- Signed-off-by: Jussi Kivilinna --- common/iobuf.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/common/iobuf.c b/common/iobuf.c index 5eeba8fe6..05944255f 100644 --- a/common/iobuf.c +++ b/common/iobuf.c @@ -2262,6 +2262,7 @@ iobuf_copy (iobuf_t dest, iobuf_t source) size_t nread; size_t nwrote = 0; + size_t max_read = 0; int err; assert (source->use == IOBUF_INPUT || source->use == IOBUF_INPUT_TEMP); @@ -2278,6 +2279,9 @@ iobuf_copy (iobuf_t dest, iobuf_t source) /* EOF. */ break; + if (nread > max_read) + max_read = nread; + err = iobuf_write (dest, temp, nread); if (err) break; @@ -2285,7 +2289,8 @@ iobuf_copy (iobuf_t dest, iobuf_t source) } /* Burn the buffer. */ - wipememory (temp, sizeof (temp)); + if (max_read) + wipememory (temp, max_read); xfree (temp); return nwrote; From 73e74de0e33bbb76300f96a4174024779047df06 Mon Sep 17 00:00:00 2001 From: Jussi Kivilinna Date: Sat, 1 Dec 2018 13:43:10 +0200 Subject: [PATCH 090/235] g10/mainproc: disable hash contexts when --skip-verify is used * g10/mainproc.c (proc_plaintext): Do not enable hash contexts when opt.skip_verify is set. -- Signed-off-by: Jussi Kivilinna --- g10/mainproc.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/g10/mainproc.c b/g10/mainproc.c index 7eceb7e0b..dce3f3799 100644 --- a/g10/mainproc.c +++ b/g10/mainproc.c @@ -862,7 +862,10 @@ proc_plaintext( CTX c, PACKET *pkt ) /* The onepass signature case. */ if (n->pkt->pkt.onepass_sig->digest_algo) { - gcry_md_enable (c->mfx.md, n->pkt->pkt.onepass_sig->digest_algo); + if (!opt.skip_verify) + gcry_md_enable (c->mfx.md, + n->pkt->pkt.onepass_sig->digest_algo); + any = 1; } } @@ -880,7 +883,8 @@ proc_plaintext( CTX c, PACKET *pkt ) * documents. */ clearsig = (*data == 0x01); for (data++, datalen--; datalen; datalen--, data++) - gcry_md_enable (c->mfx.md, *data); + if (!opt.skip_verify) + gcry_md_enable (c->mfx.md, *data); any = 1; break; /* Stop here as one-pass signature packets are not expected. */ @@ -888,7 +892,8 @@ proc_plaintext( CTX c, PACKET *pkt ) else if (n->pkt->pkttype == PKT_SIGNATURE) { /* The SIG+LITERAL case that PGP used to use. */ - gcry_md_enable ( c->mfx.md, n->pkt->pkt.signature->digest_algo ); + if (!opt.skip_verify) + gcry_md_enable (c->mfx.md, n->pkt->pkt.signature->digest_algo); any = 1; } } From 802b23289cc9b43a56e5032c2681eb21d4014784 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 4 Dec 2018 12:32:01 +0900 Subject: [PATCH 091/235] build: Remove --with-*-prefix from configure_opts. * autogen.rc (configure_opts): Remove --with-*-prefix. -- It seems that we haven't done cross-build for amd64 for a while, we now use nPth instead of Pth. Signed-off-by: NIIBE Yutaka --- autogen.rc | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/autogen.rc b/autogen.rc index aa565911a..c80dd4801 100644 --- a/autogen.rc +++ b/autogen.rc @@ -17,25 +17,15 @@ esac case "$myhost" in w32) configure_opts=" - --with-gpg-error-prefix=@SYSROOT@ - --with-ksba-prefix=@SYSROOT@ - --with-libgcrypt-prefix=@SYSROOT@ - --with-libassuan-prefix=@SYSROOT@ --with-zlib=@SYSROOT@ --with-regex=@SYSROOT@ - --with-npth-prefix=@SYSROOT@ --disable-g13 " ;; amd64) configure_opts=" - --with-gpg-error-prefix=@SYSROOT@ - --with-ksba-prefix=@SYSROOT@ - --with-libgcrypt-prefix=@SYSROOT@ - --with-libassuan-prefix=@SYSROOT@ --with-zlib=/usr/x86_64-linux-gnu/usr - --with-pth-prefix=/usr/x86_64-linux-gnu/usr " ;; esac From 99094c992c20dd22971beb3527cfda109cd1df89 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 4 Dec 2018 09:45:42 +0100 Subject: [PATCH 092/235] wks: Move a few server functions to wks-util. * tools/gpg-wks-server.c (write_to_file): Move to ... * tools/wks-util.c: here. * tools/gpg-wks-server.c (compute_hu_fname): Move to ... * tools/wks-util.c (wks_compute_hu_fname): here. * tools/gpg-wks-server.c (fname_from_userid): Move to ... * tools/wks-util.c (wks_fname_from_userid): here. * tools/gpg-wks-server.c (command_install_key): Move to ... * tools/wks-util.c (wks_cmd_install_key): here and change caller. * tools/gpg-wks-server.c (command_remove_key): Move to ... * tools/wks-util.c (wks_cmd_remove_key): here and change callers. Signed-off-by: Werner Koch --- tools/gpg-wks-server.c | 314 +---------------------------------------- tools/gpg-wks.h | 7 + tools/wks-util.c | 305 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 317 insertions(+), 309 deletions(-) diff --git a/tools/gpg-wks-server.c b/tools/gpg-wks-server.c index 1b533124a..eae93b374 100644 --- a/tools/gpg-wks-server.c +++ b/tools/gpg-wks-server.c @@ -157,8 +157,6 @@ static gpg_error_t command_receive_cb (void *opaque, const char *mediatype, estream_t fp, unsigned int flags); static gpg_error_t command_list_domains (void); -static gpg_error_t command_install_key (const char *fname, const char *userid); -static gpg_error_t command_remove_key (const char *mailaddr); static gpg_error_t command_revoke_key (const char *mailaddr); static gpg_error_t command_check_key (const char *mailaddr); static gpg_error_t command_cron (void); @@ -385,13 +383,13 @@ main (int argc, char **argv) case aInstallKey: if (argc != 2) wrong_args ("--install-key FILE USER-ID"); - err = command_install_key (*argv, argv[1]); + err = wks_cmd_install_key (*argv, argv[1]); break; case aRemoveKey: if (argc != 1) wrong_args ("--remove-key USER-ID"); - err = command_remove_key (*argv); + err = wks_cmd_remove_key (*argv); break; case aRevokeKey: @@ -1346,81 +1344,6 @@ send_congratulation_message (const char *mbox, const char *keyfile) } -/* Write the content of SRC to the new file FNAME. */ -static gpg_error_t -write_to_file (estream_t src, const char *fname) -{ - gpg_error_t err; - estream_t dst; - char buffer[4096]; - size_t nread, written; - - dst = es_fopen (fname, "wb"); - if (!dst) - return gpg_error_from_syserror (); - - do - { - nread = es_fread (buffer, 1, sizeof buffer, src); - if (!nread) - break; - written = es_fwrite (buffer, 1, nread, dst); - if (written != nread) - break; - } - while (!es_feof (src) && !es_ferror (src) && !es_ferror (dst)); - if (!es_feof (src) || es_ferror (src) || es_ferror (dst)) - { - err = gpg_error_from_syserror (); - es_fclose (dst); - gnupg_remove (fname); - return err; - } - - if (es_fclose (dst)) - { - err = gpg_error_from_syserror (); - log_error ("error closing '%s': %s\n", fname, gpg_strerror (err)); - return err; - } - - return 0; -} - - -/* Compute the the full file name for the key with ADDRSPEC and return - * it at R_FNAME. */ -static gpg_error_t -compute_hu_fname (char **r_fname, const char *addrspec) -{ - gpg_error_t err; - char *hash; - const char *domain; - char sha1buf[20]; - - *r_fname = NULL; - - domain = strchr (addrspec, '@'); - if (!domain || !domain[1] || domain == addrspec) - return gpg_error (GPG_ERR_INV_ARG); - domain++; - - gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, addrspec, domain - addrspec - 1); - hash = zb32_encode (sha1buf, 8*20); - if (!hash) - return gpg_error_from_syserror (); - - *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL); - if (!*r_fname) - err = gpg_error_from_syserror (); - else - err = 0; - - xfree (hash); - return err; -} - - /* Check that we have send a request with NONCE and publish the key. */ static gpg_error_t check_and_publish (server_ctx_t ctx, const char *address, const char *nonce) @@ -1495,7 +1418,7 @@ check_and_publish (server_ctx_t ctx, const char *address, const char *nonce) } /* Hash user ID and create filename. */ - err = compute_hu_fname (&fnewname, address); + err = wks_compute_hu_fname (&fnewname, address); if (err) goto leave; @@ -2004,195 +1927,6 @@ command_cron (void) } -/* Install a single key into the WKD by reading FNAME and extracting - * USERID. */ -static gpg_error_t -command_install_key (const char *fname, const char *userid) -{ - gpg_error_t err; - KEYDB_SEARCH_DESC desc; - estream_t fp = NULL; - char *addrspec = NULL; - char *fpr = NULL; - uidinfo_list_t uidlist = NULL; - uidinfo_list_t uid, thisuid; - time_t thistime; - char *huname = NULL; - int any; - - addrspec = mailbox_from_userid (userid, 0); - if (!addrspec) - { - log_error ("\"%s\" is not a proper mail address\n", userid); - err = gpg_error (GPG_ERR_INV_USER_ID); - goto leave; - } - - if (!classify_user_id (fname, &desc, 1) - && (desc.mode == KEYDB_SEARCH_MODE_FPR - || desc.mode == KEYDB_SEARCH_MODE_FPR20)) - { - /* FNAME looks like a fingerprint. Get the key from the - * standard keyring. */ - err = wks_get_key (&fp, fname, addrspec, 0); - if (err) - { - log_error ("error getting key '%s' (uid='%s'): %s\n", - fname, addrspec, gpg_strerror (err)); - goto leave; - } - } - else /* Take it from the file */ - { - fp = es_fopen (fname, "rb"); - if (!fp) - { - err = gpg_error_from_syserror (); - log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); - goto leave; - } - } - - /* List the key so that we can figure out the newest UID with the - * requested addrspec. */ - err = wks_list_key (fp, &fpr, &uidlist); - if (err) - { - log_error ("error parsing key: %s\n", gpg_strerror (err)); - err = gpg_error (GPG_ERR_NO_PUBKEY); - goto leave; - } - thistime = 0; - thisuid = NULL; - any = 0; - for (uid = uidlist; uid; uid = uid->next) - { - if (!uid->mbox) - continue; /* Should not happen anyway. */ - if (ascii_strcasecmp (uid->mbox, addrspec)) - continue; /* Not the requested addrspec. */ - any = 1; - if (uid->created > thistime) - { - thistime = uid->created; - thisuid = uid; - } - } - if (!thisuid) - thisuid = uidlist; /* This is the case for a missing timestamp. */ - if (!any) - { - log_error ("public key in '%s' has no mail address '%s'\n", - fname, addrspec); - err = gpg_error (GPG_ERR_INV_USER_ID); - goto leave; - } - - if (opt.verbose) - log_info ("using key with user id '%s'\n", thisuid->uid); - - { - estream_t fp2; - - es_rewind (fp); - err = wks_filter_uid (&fp2, fp, thisuid->uid, 1); - if (err) - { - log_error ("error filtering key: %s\n", gpg_strerror (err)); - err = gpg_error (GPG_ERR_NO_PUBKEY); - goto leave; - } - es_fclose (fp); - fp = fp2; - } - - /* Hash user ID and create filename. */ - err = compute_hu_fname (&huname, addrspec); - if (err) - goto leave; - - /* Publish. */ - err = write_to_file (fp, huname); - if (err) - { - log_error ("copying key to '%s' failed: %s\n", huname,gpg_strerror (err)); - goto leave; - } - - /* Make sure it is world readable. */ - if (gnupg_chmod (huname, "-rwxr--r--")) - log_error ("can't set permissions of '%s': %s\n", - huname, gpg_strerror (gpg_err_code_from_syserror())); - - if (!opt.quiet) - log_info ("key %s published for '%s'\n", fpr, addrspec); - - leave: - xfree (huname); - free_uidinfo_list (uidlist); - xfree (fpr); - xfree (addrspec); - es_fclose (fp); - return err; -} - - -/* Return the filename and optionally the addrspec for USERID at - * R_FNAME and R_ADDRSPEC. R_ADDRSPEC might also be set on error. */ -static gpg_error_t -fname_from_userid (const char *userid, char **r_fname, char **r_addrspec) -{ - gpg_error_t err; - char *addrspec = NULL; - const char *domain; - char *hash = NULL; - const char *s; - char shaxbuf[32]; /* Used for SHA-1 and SHA-256 */ - - *r_fname = NULL; - if (r_addrspec) - *r_addrspec = NULL; - - addrspec = mailbox_from_userid (userid, 0); - if (!addrspec) - { - if (opt.verbose) - log_info ("\"%s\" is not a proper mail address\n", userid); - err = gpg_error (GPG_ERR_INV_USER_ID); - goto leave; - } - - domain = strchr (addrspec, '@'); - log_assert (domain); - domain++; - - /* Hash user ID and create filename. */ - s = strchr (addrspec, '@'); - log_assert (s); - gcry_md_hash_buffer (GCRY_MD_SHA1, shaxbuf, addrspec, s - addrspec); - hash = zb32_encode (shaxbuf, 8*20); - if (!hash) - { - err = gpg_error_from_syserror (); - goto leave; - } - - *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL); - if (!*r_fname) - err = gpg_error_from_syserror (); - else - err = 0; - - leave: - if (r_addrspec && addrspec) - *r_addrspec = addrspec; - else - xfree (addrspec); - xfree (hash); - return err; -} - - /* Check whether the key with USER_ID is installed. */ static gpg_error_t command_check_key (const char *userid) @@ -2201,7 +1935,7 @@ command_check_key (const char *userid) char *addrspec = NULL; char *fname = NULL; - err = fname_from_userid (userid, &fname, &addrspec); + err = wks_fname_from_userid (userid, &fname, &addrspec); if (err) goto leave; @@ -2236,49 +1970,11 @@ command_check_key (const char *userid) } -/* Remove the key with mail address in USERID. */ -static gpg_error_t -command_remove_key (const char *userid) -{ - gpg_error_t err; - char *addrspec = NULL; - char *fname = NULL; - - err = fname_from_userid (userid, &fname, &addrspec); - if (err) - goto leave; - - if (gnupg_remove (fname)) - { - err = gpg_error_from_syserror (); - if (gpg_err_code (err) == GPG_ERR_ENOENT) - { - if (!opt.quiet) - log_info ("key for '%s' is not installed\n", addrspec); - log_inc_errorcount (); - err = 0; - } - else - log_error ("error removing '%s': %s\n", fname, gpg_strerror (err)); - goto leave; - } - - if (opt.verbose) - log_info ("key for '%s' removed\n", addrspec); - err = 0; - - leave: - xfree (fname); - xfree (addrspec); - return err; -} - - /* Revoke the key with mail address MAILADDR. */ static gpg_error_t command_revoke_key (const char *mailaddr) { /* Remove should be different from removing but we have not yet * defined a suitable way to do this. */ - return command_remove_key (mailaddr); + return wks_cmd_remove_key (mailaddr); } diff --git a/tools/gpg-wks.h b/tools/gpg-wks.h index fba73f085..e36943090 100644 --- a/tools/gpg-wks.h +++ b/tools/gpg-wks.h @@ -98,6 +98,13 @@ gpg_error_t wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown); void wks_free_policy (policy_flags_t policy); +gpg_error_t wks_fname_from_userid (const char *userid, + char **r_fname, char **r_addrspec); +gpg_error_t wks_compute_hu_fname (char **r_fname, const char *addrspec); +gpg_error_t wks_cmd_install_key (const char *fname, const char *userid); +gpg_error_t wks_cmd_remove_key (const char *userid); + + /*-- wks-receive.c --*/ /* Flag values for the receive callback. */ diff --git a/tools/wks-util.c b/tools/wks-util.c index cf80a25bc..604194376 100644 --- a/tools/wks-util.c +++ b/tools/wks-util.c @@ -24,7 +24,10 @@ #include "../common/status.h" #include "../common/ccparray.h" #include "../common/exectool.h" +#include "../common/zb32.h" +#include "../common/userids.h" #include "../common/mbox-util.h" +#include "../common/sysutils.h" #include "mime-maker.h" #include "send-mail.h" #include "gpg-wks.h" @@ -699,3 +702,305 @@ wks_free_policy (policy_flags_t policy) memset (policy, 0, sizeof *policy); } } + + +/* Write the content of SRC to the new file FNAME. */ +static gpg_error_t +write_to_file (estream_t src, const char *fname) +{ + gpg_error_t err; + estream_t dst; + char buffer[4096]; + size_t nread, written; + + dst = es_fopen (fname, "wb"); + if (!dst) + return gpg_error_from_syserror (); + + do + { + nread = es_fread (buffer, 1, sizeof buffer, src); + if (!nread) + break; + written = es_fwrite (buffer, 1, nread, dst); + if (written != nread) + break; + } + while (!es_feof (src) && !es_ferror (src) && !es_ferror (dst)); + if (!es_feof (src) || es_ferror (src) || es_ferror (dst)) + { + err = gpg_error_from_syserror (); + es_fclose (dst); + gnupg_remove (fname); + return err; + } + + if (es_fclose (dst)) + { + err = gpg_error_from_syserror (); + log_error ("error closing '%s': %s\n", fname, gpg_strerror (err)); + return err; + } + + return 0; +} + + +/* Return the filename and optionally the addrspec for USERID at + * R_FNAME and R_ADDRSPEC. R_ADDRSPEC might also be set on error. */ +gpg_error_t +wks_fname_from_userid (const char *userid, char **r_fname, char **r_addrspec) +{ + gpg_error_t err; + char *addrspec = NULL; + const char *domain; + char *hash = NULL; + const char *s; + char shaxbuf[32]; /* Used for SHA-1 and SHA-256 */ + + *r_fname = NULL; + if (r_addrspec) + *r_addrspec = NULL; + + addrspec = mailbox_from_userid (userid, 0); + if (!addrspec) + { + if (opt.verbose) + log_info ("\"%s\" is not a proper mail address\n", userid); + err = gpg_error (GPG_ERR_INV_USER_ID); + goto leave; + } + + domain = strchr (addrspec, '@'); + log_assert (domain); + domain++; + + /* Hash user ID and create filename. */ + s = strchr (addrspec, '@'); + log_assert (s); + gcry_md_hash_buffer (GCRY_MD_SHA1, shaxbuf, addrspec, s - addrspec); + hash = zb32_encode (shaxbuf, 8*20); + if (!hash) + { + err = gpg_error_from_syserror (); + goto leave; + } + + *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL); + if (!*r_fname) + err = gpg_error_from_syserror (); + else + err = 0; + + leave: + if (r_addrspec && addrspec) + *r_addrspec = addrspec; + else + xfree (addrspec); + xfree (hash); + return err; +} + + +/* Compute the the full file name for the key with ADDRSPEC and return + * it at R_FNAME. */ +gpg_error_t +wks_compute_hu_fname (char **r_fname, const char *addrspec) +{ + gpg_error_t err; + char *hash; + const char *domain; + char sha1buf[20]; + + *r_fname = NULL; + + domain = strchr (addrspec, '@'); + if (!domain || !domain[1] || domain == addrspec) + return gpg_error (GPG_ERR_INV_ARG); + domain++; + + gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, addrspec, domain - addrspec - 1); + hash = zb32_encode (sha1buf, 8*20); + if (!hash) + return gpg_error_from_syserror (); + + *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL); + if (!*r_fname) + err = gpg_error_from_syserror (); + else + err = 0; + + xfree (hash); + return err; +} + + +/* Install a single key into the WKD by reading FNAME and extracting + * USERID. */ +gpg_error_t +wks_cmd_install_key (const char *fname, const char *userid) +{ + gpg_error_t err; + KEYDB_SEARCH_DESC desc; + estream_t fp = NULL; + char *addrspec = NULL; + char *fpr = NULL; + uidinfo_list_t uidlist = NULL; + uidinfo_list_t uid, thisuid; + time_t thistime; + char *huname = NULL; + int any; + + addrspec = mailbox_from_userid (userid, 0); + if (!addrspec) + { + log_error ("\"%s\" is not a proper mail address\n", userid); + err = gpg_error (GPG_ERR_INV_USER_ID); + goto leave; + } + + if (!classify_user_id (fname, &desc, 1) + && (desc.mode == KEYDB_SEARCH_MODE_FPR + || desc.mode == KEYDB_SEARCH_MODE_FPR20)) + { + /* FNAME looks like a fingerprint. Get the key from the + * standard keyring. */ + err = wks_get_key (&fp, fname, addrspec, 0); + if (err) + { + log_error ("error getting key '%s' (uid='%s'): %s\n", + fname, addrspec, gpg_strerror (err)); + goto leave; + } + } + else /* Take it from the file */ + { + fp = es_fopen (fname, "rb"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + } + + /* List the key so that we can figure out the newest UID with the + * requested addrspec. */ + err = wks_list_key (fp, &fpr, &uidlist); + if (err) + { + log_error ("error parsing key: %s\n", gpg_strerror (err)); + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + thistime = 0; + thisuid = NULL; + any = 0; + for (uid = uidlist; uid; uid = uid->next) + { + if (!uid->mbox) + continue; /* Should not happen anyway. */ + if (ascii_strcasecmp (uid->mbox, addrspec)) + continue; /* Not the requested addrspec. */ + any = 1; + if (uid->created > thistime) + { + thistime = uid->created; + thisuid = uid; + } + } + if (!thisuid) + thisuid = uidlist; /* This is the case for a missing timestamp. */ + if (!any) + { + log_error ("public key in '%s' has no mail address '%s'\n", + fname, addrspec); + err = gpg_error (GPG_ERR_INV_USER_ID); + goto leave; + } + + if (opt.verbose) + log_info ("using key with user id '%s'\n", thisuid->uid); + + { + estream_t fp2; + + es_rewind (fp); + err = wks_filter_uid (&fp2, fp, thisuid->uid, 1); + if (err) + { + log_error ("error filtering key: %s\n", gpg_strerror (err)); + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + es_fclose (fp); + fp = fp2; + } + + /* Hash user ID and create filename. */ + err = wks_compute_hu_fname (&huname, addrspec); + if (err) + goto leave; + + /* Publish. */ + err = write_to_file (fp, huname); + if (err) + { + log_error ("copying key to '%s' failed: %s\n", huname,gpg_strerror (err)); + goto leave; + } + + /* Make sure it is world readable. */ + if (gnupg_chmod (huname, "-rwxr--r--")) + log_error ("can't set permissions of '%s': %s\n", + huname, gpg_strerror (gpg_err_code_from_syserror())); + + if (!opt.quiet) + log_info ("key %s published for '%s'\n", fpr, addrspec); + + leave: + xfree (huname); + free_uidinfo_list (uidlist); + xfree (fpr); + xfree (addrspec); + es_fclose (fp); + return err; +} + + +/* Remove the key with mail address in USERID. */ +gpg_error_t +wks_cmd_remove_key (const char *userid) +{ + gpg_error_t err; + char *addrspec = NULL; + char *fname = NULL; + + err = wks_fname_from_userid (userid, &fname, &addrspec); + if (err) + goto leave; + + if (gnupg_remove (fname)) + { + err = gpg_error_from_syserror (); + if (gpg_err_code (err) == GPG_ERR_ENOENT) + { + if (!opt.quiet) + log_info ("key for '%s' is not installed\n", addrspec); + log_inc_errorcount (); + err = 0; + } + else + log_error ("error removing '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + + if (opt.verbose) + log_info ("key for '%s' removed\n", addrspec); + err = 0; + + leave: + xfree (fname); + xfree (addrspec); + return err; +} From 602b1909632925d5a2e0778c102d66109795c627 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 4 Dec 2018 10:31:42 +0100 Subject: [PATCH 093/235] wks: Add new commands --install-key and --remove-key to the client. * tools/gpg-wks-client.c (aInstallKey, aRemoveKey, oDirectory): New. (opts): Add "--install-key", "--remove-key" and "-C". (parse_arguments): Parse them. (main): Check that the given directory exists. Implement the new commands. -- These commands maybe useful to prepare a WKD directory on a non-Unix box using the standard wks client. Signed-off-by: Werner Koch --- doc/wks.texi | 15 ++++++++++++ tools/gpg-wks-client.c | 53 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/doc/wks.texi b/doc/wks.texi index 03d748255..021fe5bbb 100644 --- a/doc/wks.texi +++ b/doc/wks.texi @@ -90,6 +90,14 @@ decrypted MIME message. The result of these commands are another mail which can be send in the same way as the mail created with @option{--create}. +The command @option{--install-key} manually installs a key into a +local directory (see option @option{-C}) reflecting the structure of a +WKD. The arguments are a file with the keyblock and the user-id to +install. If the first argument resembles a fingerprint the key is +taken from the current keyring; to force the use of a file, prefix the +first argument with "./". The command @option{--remove-key} removes a +key from that directory, its only argument is a user-id. + @command{gpg-wks-client} is not commonly invoked directly and thus it is not installed in the bin directory. Here is an example how it can be invoked manually to check for a Web Key Directory entry for @@ -161,6 +169,13 @@ This program returns only the status messages SUCCESS or FAILURE which are helpful when the caller uses a double fork approach and can't easily get the return code of the process. +@item -C @var{dir} +@itemx --directory @var{dir} +@opindex directory +Use @var{dir} as top level directory for the commands +@option{--install-key} and @option{--remove-key}. The default is +@file{openpgpkey}. + @item --verbose @opindex verbose Enable extra informational output. diff --git a/tools/gpg-wks-client.c b/tools/gpg-wks-client.c index 0be5ea89b..5535a6812 100644 --- a/tools/gpg-wks-client.c +++ b/tools/gpg-wks-client.c @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include "../common/util.h" #include "../common/status.h" @@ -48,6 +50,7 @@ enum cmd_and_opt_values oQuiet = 'q', oVerbose = 'v', oOutput = 'o', + oDirectory = 'C', oDebug = 500, @@ -56,6 +59,8 @@ enum cmd_and_opt_values aCreate, aReceive, aRead, + aInstallKey, + aRemoveKey, oGpgProgram, oSend, @@ -81,6 +86,10 @@ static ARGPARSE_OPTS opts[] = { ("receive a MIME confirmation request")), ARGPARSE_c (aRead, "read", ("receive a plain text confirmation request")), + ARGPARSE_c (aInstallKey, "install-key", + "install a key into a directory"), + ARGPARSE_c (aRemoveKey, "remove-key", + "remove a key from a directory"), ARGPARSE_group (301, ("@\nOptions:\n ")), @@ -92,6 +101,7 @@ static ARGPARSE_OPTS opts[] = { ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"), ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), ARGPARSE_s_n (oWithColons, "with-colons", "@"), + ARGPARSE_s_s (oDirectory, "directory", "@"), ARGPARSE_s_s (oFakeSubmissionAddr, "fake-submission-addr", "@"), @@ -194,6 +204,9 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break; + case oDirectory: + opt.directory = pargs->r.ret_str; + break; case oSend: opt.use_sendmail = 1; break; @@ -215,6 +228,8 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) case aReceive: case aRead: case aCheck: + case aInstallKey: + case aRemoveKey: cmd = pargs->r_opt; break; @@ -269,9 +284,34 @@ main (int argc, char **argv) if (!opt.gpg_program) opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); + if (!opt.directory) + opt.directory = "openpgpkey"; + /* Tell call-dirmngr what options we want. */ set_dirmngr_options (opt.verbose, (opt.debug & DBG_IPC_VALUE), 1); + + /* Check that the top directory exists. */ + if (cmd == aInstallKey || cmd == aRemoveKey) + { + struct stat sb; + + if (stat (opt.directory, &sb)) + { + err = gpg_error_from_syserror (); + log_error ("error accessing directory '%s': %s\n", + opt.directory, gpg_strerror (err)); + goto leave; + } + if (!S_ISDIR(sb.st_mode)) + { + log_error ("error accessing directory '%s': %s\n", + opt.directory, "not a directory"); + err = gpg_error (GPG_ERR_ENOENT); + goto leave; + } + } + /* Run the selected command. */ switch (cmd) { @@ -322,12 +362,25 @@ main (int argc, char **argv) err = command_check (argv[0]); break; + case aInstallKey: + if (argc != 2) + wrong_args ("--install-key FILE|FINGERPRINT USER-ID"); + err = wks_cmd_install_key (*argv, argv[1]); + break; + + case aRemoveKey: + if (argc != 1) + wrong_args ("--remove-key USER-ID"); + err = wks_cmd_remove_key (*argv); + break; + default: usage (1); err = 0; break; } + leave: if (err) wks_write_status (STATUS_FAILURE, "- %u", err); else if (log_get_errorcount (0)) From 73e5b0ec9b9ba5e04e55f8c42d81e23df7c3afe0 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 4 Dec 2018 11:37:54 +0100 Subject: [PATCH 094/235] wks: Create sub-directories * tools/wks-util.c (wks_compute_hu_fname): Stat and create directory if needed. Signed-off-by: Werner Koch --- tools/wks-util.c | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/tools/wks-util.c b/tools/wks-util.c index 604194376..c0ea06a29 100644 --- a/tools/wks-util.c +++ b/tools/wks-util.c @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include "../common/util.h" #include "../common/status.h" @@ -811,6 +813,8 @@ wks_compute_hu_fname (char **r_fname, const char *addrspec) char *hash; const char *domain; char sha1buf[20]; + char *fname; + struct stat sb; *r_fname = NULL; @@ -824,12 +828,28 @@ wks_compute_hu_fname (char **r_fname, const char *addrspec) if (!hash) return gpg_error_from_syserror (); - *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL); - if (!*r_fname) - err = gpg_error_from_syserror (); - else - err = 0; + /* Try to create missing directories below opt.directory. */ + fname = make_filename_try (opt.directory, domain, NULL); + if (fname && stat (fname, &sb) + && gpg_err_code_from_syserror () == GPG_ERR_ENOENT) + if (!gnupg_mkdir (fname, "-rwxr--r--") && opt.verbose) + log_info ("directory '%s' created\n", fname); + xfree (fname); + fname = make_filename_try (opt.directory, domain, "hu", NULL); + if (fname && stat (fname, &sb) + && gpg_err_code_from_syserror () == GPG_ERR_ENOENT) + if (!gnupg_mkdir (fname, "-rwxr--r--") && opt.verbose) + log_info ("directory '%s' created\n", fname); + xfree (fname); + /* Create the filename. */ + fname = make_filename_try (opt.directory, domain, "hu", hash, NULL); + err = fname? 0 : gpg_error_from_syserror (); + + if (err) + xfree (fname); + else + *r_fname = fname; /* Okay. */ xfree (hash); return err; } From 0e8bf204791ebfd0c9a8e4b49fbadf998ec62e49 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 4 Dec 2018 12:32:28 +0100 Subject: [PATCH 095/235] gpg: New list-option "show-only-fpr-mbox". * g10/gpg.c (parse_list_options): Add option "show-only-fpr-mbox". * g10/options.h (LIST_SHOW_ONLY_FPR_MBOX): New. * g10/keylist.c (list_keyblock_simple): New. (list_keyblock): Call it. (list_all): Do not print the keyring name in LIST_SHOW_ONLY_FPR_MBOX mode. Signed-off-by: Werner Koch --- doc/gpg.texi | 4 ++++ g10/gpg.c | 2 ++ g10/keylist.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++- g10/options.h | 1 + 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/doc/gpg.texi b/doc/gpg.texi index 58672c9e0..a5c172b2c 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -1328,6 +1328,10 @@ give the opposite meaning. The options are: meaningful when using @option{--with-colons} along with @option{--check-signatures}. + @item show-only-fpr-mbox + @opindex list-options:show-only-fpr-mbox + For each valid user-id which also has a valid mail address print + only the fingerprint and the mail address. @end table @item --verify-options @var{parameters} diff --git a/g10/gpg.c b/g10/gpg.c index 9f96ef407..ddf8c86eb 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -2009,6 +2009,8 @@ parse_list_options(char *str) N_("show expiration dates during signature listings")}, {"show-sig-subpackets",LIST_SHOW_SIG_SUBPACKETS,NULL, NULL}, + {"show-only-fpr-mbox",LIST_SHOW_ONLY_FPR_MBOX, NULL, + NULL}, {NULL,0,NULL,NULL} }; diff --git a/g10/keylist.c b/g10/keylist.c index 793f7dacd..9a2166308 100644 --- a/g10/keylist.c +++ b/g10/keylist.c @@ -541,7 +541,7 @@ list_all (ctrl_t ctrl, int secret, int mark_secret) ; /* Secret key listing requested but this isn't one. */ else { - if (!opt.with_colons) + if (!opt.with_colons && !(opt.list_options & LIST_SHOW_ONLY_FPR_MBOX)) { resname = keydb_get_resource_name (hd); if (lastresname != resname) @@ -1254,6 +1254,57 @@ list_keyblock_print (ctrl_t ctrl, kbnode_t keyblock, int secret, int fpr, xfree (hexgrip); } + +/* Do a simple key listing printing only the fingerprint and the mail + * address of valid keys. */ +static void +list_keyblock_simple (ctrl_t ctrl, kbnode_t keyblock) +{ + gpg_err_code_t ec; + kbnode_t kbctx; + kbnode_t node; + char hexfpr[2*MAX_FINGERPRINT_LEN+1]; + char *mbox; + + (void)ctrl; + + node = find_kbnode (keyblock, PKT_PUBLIC_KEY); + if (!node) + { + log_error ("Oops; key lost!\n"); + dump_kbnode (keyblock); + return; + } + hexfingerprint (node->pkt->pkt.public_key, hexfpr, sizeof hexfpr); + + for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));) + { + if (node->pkt->pkttype == PKT_USER_ID) + { + PKT_user_id *uid = node->pkt->pkt.user_id; + + if (uid->attrib_data) + continue; + + if (uid->flags.expired || uid->flags.revoked) + continue; + + mbox = mailbox_from_userid (uid->name, 0); + if (!mbox) + { + ec = gpg_err_code_from_syserror (); + if (ec != GPG_ERR_EINVAL) + log_error ("error getting mailbox from user-id: %s\n", + gpg_strerror (ec)); + continue; + } + es_fprintf (es_stdout, "%s %s\n", hexfpr, mbox); + xfree (mbox); + } + } +} + + void print_revokers (estream_t fp, PKT_public_key * pk) { @@ -1807,6 +1858,12 @@ list_keyblock (ctrl_t ctrl, if (opt.with_colons) list_keyblock_colon (ctrl, keyblock, secret, has_secret); + else if ((opt.list_options & LIST_SHOW_ONLY_FPR_MBOX)) + { + if (!listctx->no_validity) + check_trustdb_stale (ctrl); + list_keyblock_simple (ctrl, keyblock); + } else list_keyblock_print (ctrl, keyblock, secret, fpr, listctx); diff --git a/g10/options.h b/g10/options.h index faaf53503..8adf09f08 100644 --- a/g10/options.h +++ b/g10/options.h @@ -386,6 +386,7 @@ EXTERN_UNLESS_MAIN_MODULE int memory_stat_debug_mode; #define LIST_SHOW_SIG_EXPIRE (1<<9) #define LIST_SHOW_SIG_SUBPACKETS (1<<10) #define LIST_SHOW_USAGE (1<<11) +#define LIST_SHOW_ONLY_FPR_MBOX (1<<12) #define VERIFY_SHOW_PHOTOS (1<<0) #define VERIFY_SHOW_POLICY_URLS (1<<1) From ba46a359b9d6549b74ec8401ea39bad434d87564 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 4 Dec 2018 15:27:19 +0100 Subject: [PATCH 096/235] wks: Allow reading of --install-key arguments from stdin. * tools/wks-util.c (install_key_from_spec_file): New. (wks_cmd_install_key): Call it. * tools/gpg-wks-client.c (main): Allow --install-key w/o arguments. * tools/gpg-wks-server.c (main): Ditto. Signed-off-by: Werner Koch --- doc/wks.texi | 11 ++++++-- tools/gpg-wks-client.c | 9 ++++-- tools/gpg-wks-server.c | 9 ++++-- tools/wks-util.c | 62 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 81 insertions(+), 10 deletions(-) diff --git a/doc/wks.texi b/doc/wks.texi index 021fe5bbb..a0b2a34b9 100644 --- a/doc/wks.texi +++ b/doc/wks.texi @@ -95,8 +95,11 @@ local directory (see option @option{-C}) reflecting the structure of a WKD. The arguments are a file with the keyblock and the user-id to install. If the first argument resembles a fingerprint the key is taken from the current keyring; to force the use of a file, prefix the -first argument with "./". The command @option{--remove-key} removes a -key from that directory, its only argument is a user-id. +first argument with "./". If no arguments are given the parameters +are read from stdin; the expected format are lines with the +fingerprint and the mailbox separated by a space. The command +@option{--remove-key} removes a key from that directory, its only +argument is a user-id. @command{gpg-wks-client} is not commonly invoked directly and thus it is not installed in the bin directory. Here is an example how it can @@ -280,7 +283,9 @@ The command @option{--install-key} manually installs a key into the WKD. The arguments are a file with the keyblock and the user-id to install. If the first argument resembles a fingerprint the key is taken from the current keyring; to force the use of a file, prefix the -first argument with "./". +first argument with "./". If no arguments are given the parameters +are read from stdin; the expected format are lines with the +fingerprint and the mailbox separated by a space. The command @option{--remove-key} uninstalls a key from the WKD. The process returns success in this case; to also print a diagnostic, use diff --git a/tools/gpg-wks-client.c b/tools/gpg-wks-client.c index 5535a6812..0dee1a2a1 100644 --- a/tools/gpg-wks-client.c +++ b/tools/gpg-wks-client.c @@ -363,9 +363,12 @@ main (int argc, char **argv) break; case aInstallKey: - if (argc != 2) - wrong_args ("--install-key FILE|FINGERPRINT USER-ID"); - err = wks_cmd_install_key (*argv, argv[1]); + if (!argc) + err = wks_cmd_install_key (NULL, NULL); + else if (argc == 2) + err = wks_cmd_install_key (*argv, argv[1]); + else + wrong_args ("--install-key [FILE|FINGERPRINT USER-ID]"); break; case aRemoveKey: diff --git a/tools/gpg-wks-server.c b/tools/gpg-wks-server.c index eae93b374..1a0ba8f4f 100644 --- a/tools/gpg-wks-server.c +++ b/tools/gpg-wks-server.c @@ -381,9 +381,12 @@ main (int argc, char **argv) break; case aInstallKey: - if (argc != 2) - wrong_args ("--install-key FILE USER-ID"); - err = wks_cmd_install_key (*argv, argv[1]); + if (!argc) + err = wks_cmd_install_key (NULL, NULL); + else if (argc == 2) + err = wks_cmd_install_key (*argv, argv[1]); + else + wrong_args ("--install-key [FILE|FINGERPRINT USER-ID]"); break; case aRemoveKey: diff --git a/tools/wks-util.c b/tools/wks-util.c index c0ea06a29..73cced9c2 100644 --- a/tools/wks-util.c +++ b/tools/wks-util.c @@ -855,8 +855,65 @@ wks_compute_hu_fname (char **r_fname, const char *addrspec) } + +/* Helper form wks_cmd_install_key. */ +static gpg_error_t +install_key_from_spec_file (const char *fname) +{ + gpg_error_t err; + estream_t fp; + char *line = NULL; + size_t linelen = 0; + char *fields[2]; + unsigned int lnr = 0; + + if (!fname || !strcmp (fname, "")) + fp = es_stdin; + else + fp = es_fopen (fname, "rb"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + + while (es_getline (&line, &linelen, fp) >= 0) + { + lnr++; + trim_spaces (line); + log_debug ("got line='%s'\n", line); + if (!*line || *line == '#') + continue; + if (split_fields (line, fields, DIM(fields)) < 2) + { + log_error ("error reading '%s': syntax error at line %u\n", + fname, lnr); + continue; + } + err = wks_cmd_install_key (fields[0], fields[1]); + if (err) + goto leave; + } + if (es_ferror (fp)) + { + err = gpg_error_from_syserror (); + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + + leave: + if (fp != es_stdin) + es_fclose (fp); + es_free (line); + return err; +} + + /* Install a single key into the WKD by reading FNAME and extracting - * USERID. */ + * USERID. If USERID is NULL FNAME is expected to be a list of fpr + * mbox lines and for each line the respective key will be + * installed. */ gpg_error_t wks_cmd_install_key (const char *fname, const char *userid) { @@ -871,6 +928,9 @@ wks_cmd_install_key (const char *fname, const char *userid) char *huname = NULL; int any; + if (!userid) + return install_key_from_spec_file (fname); + addrspec = mailbox_from_userid (userid, 0); if (!addrspec) { From c6e2ee020784de63edfa83c76095e086eae49eef Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 4 Dec 2018 15:43:19 +0100 Subject: [PATCH 097/235] gpg: Prepare revocation keys for use with v5 keys. * g10/packet.h (struct revocation_key): Add field 'fprlen'. * g10/parse-packet.c (parse_revkeys): Set fprlen and allow for v5 keys. Also fix reading of unitialized data at place where MAX_FINGERPRINT_LEN is used. * g10/revoke.c (gen_desig_revoke): Allow for v5 keys and use fprlen. Do an explicit compare to avoid reading unitialized data. * g10/sig-check.c (check_revocation_keys): Use the fprlen. * g10/getkey.c (merge_selfsigs_main): Do an explicit copy to avoid reading unitialized data. * g10/import.c (revocation_present): Use fprlen. * g10/keyedit.c (show_key_with_all_names): Use fprlen. (menu_addrevoker): Use fprlen. Allow for v5 keys. * g10/keygen.c (keygen_add_revkey): Use fprlen. (parse_revocation_key): Allow for v5 keys. * g10/keyid.c (keyid_from_fingerprint): Allow for v5 keys. Print a better error message in case of bogus fingerprints. * g10/keylist.c (print_revokers): Use fprlen. -- The reading of uninitialized data is harmless but we better fix it to make valgrind happy. More serious was that we always passed MAX_FINGERPRINT_LEN but we will need to support 20 and 32 octet fingerprints and MAX_FINGERPRINT_LEN would be too large for a v4. Signed-off-by: Werner Koch --- g10/getkey.c | 20 ++++++++++++++++---- g10/import.c | 8 ++++---- g10/keyedit.c | 5 +++-- g10/keygen.c | 10 +++++++--- g10/keyid.c | 10 ++++++---- g10/keylist.c | 2 +- g10/packet.h | 2 ++ g10/parse-packet.c | 16 +++++++++------- g10/revoke.c | 29 ++++++++++++++++------------- g10/sig-check.c | 4 ++-- 10 files changed, 66 insertions(+), 40 deletions(-) diff --git a/g10/getkey.c b/g10/getkey.c index c776a6100..039a5edb5 100644 --- a/g10/getkey.c +++ b/g10/getkey.c @@ -2577,10 +2577,22 @@ merge_selfsigs_main (ctrl_t ctrl, kbnode_t keyblock, int *r_revoked, xrealloc (pk->revkey, sizeof (struct revocation_key) * (pk->numrevkeys + sig->numrevkeys)); - for (i = 0; i < sig->numrevkeys; i++) - memcpy (&pk->revkey[pk->numrevkeys++], - &sig->revkey[i], - sizeof (struct revocation_key)); + for (i = 0; i < sig->numrevkeys; i++, pk->numrevkeys++) + { + pk->revkey[pk->numrevkeys].class + = sig->revkey[i].class; + pk->revkey[pk->numrevkeys].algid + = sig->revkey[i].algid; + pk->revkey[pk->numrevkeys].fprlen + = sig->revkey[i].fprlen; + memcpy (pk->revkey[pk->numrevkeys].fpr, + sig->revkey[i].fpr, sig->revkey[i].fprlen); + memset (pk->revkey[pk->numrevkeys].fpr + + sig->revkey[i].fprlen, + 0, + sizeof (sig->revkey[i].fpr) + - sig->revkey[i].fprlen); + } } if (sig->timestamp >= sigdate) diff --git a/g10/import.c b/g10/import.c index 8ea5144b5..64cc4b093 100644 --- a/g10/import.c +++ b/g10/import.c @@ -3614,7 +3614,7 @@ revocation_present (ctrl_t ctrl, kbnode_t keyblock) u32 keyid[2]; keyid_from_fingerprint (ctrl, sig->revkey[idx].fpr, - MAX_FINGERPRINT_LEN, keyid); + sig->revkey[idx].fprlen, keyid); for(inode=keyblock->next;inode;inode=inode->next) { @@ -3634,7 +3634,7 @@ revocation_present (ctrl_t ctrl, kbnode_t keyblock) err = get_pubkey_byfprint_fast (NULL, sig->revkey[idx].fpr, - MAX_FINGERPRINT_LEN); + sig->revkey[idx].fprlen); if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY || gpg_err_code (err) == GPG_ERR_UNUSABLE_PUBKEY) { @@ -3650,13 +3650,13 @@ revocation_present (ctrl_t ctrl, kbnode_t keyblock) tempkeystr,keystr(keyid)); keyserver_import_fprint (ctrl, sig->revkey[idx].fpr, - MAX_FINGERPRINT_LEN, + sig->revkey[idx].fprlen, opt.keyserver, 0); /* Do we have it now? */ err = get_pubkey_byfprint_fast (NULL, sig->revkey[idx].fpr, - MAX_FINGERPRINT_LEN); + sig->revkey[idx].fprlen); } if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY diff --git a/g10/keyedit.c b/g10/keyedit.c index 9716ed9d6..63a54fa21 100644 --- a/g10/keyedit.c +++ b/g10/keyedit.c @@ -3524,7 +3524,7 @@ show_key_with_all_names (ctrl_t ctrl, estream_t fp, algo = gcry_pk_algo_name (pk->revkey[i].algid); keyid_from_fingerprint (ctrl, pk->revkey[i].fpr, - MAX_FINGERPRINT_LEN, r_keyid); + pk->revkey[i].fprlen, r_keyid); user = get_user_id_string_native (ctrl, r_keyid); tty_fprintf (fp, @@ -4309,13 +4309,14 @@ menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive) xfree (answer); fingerprint_from_pk (revoker_pk, revkey.fpr, &fprlen); - if (fprlen != 20) + if (fprlen != 20 && fprlen != 32) { log_error (_("cannot appoint a PGP 2.x style key as a " "designated revoker\n")); continue; } + revkey.fprlen = fprlen; revkey.class = 0x80; if (sensitive) revkey.class |= 0x40; diff --git a/g10/keygen.c b/g10/keygen.c index 145b871b0..61f839a9f 100644 --- a/g10/keygen.c +++ b/g10/keygen.c @@ -943,11 +943,13 @@ keygen_add_revkey (PKT_signature *sig, void *opaque) struct revocation_key *revkey = opaque; byte buf[2+MAX_FINGERPRINT_LEN]; + log_assert (revkey->fprlen <= MAX_FINGERPRINT_LEN); buf[0] = revkey->class; buf[1] = revkey->algid; - memcpy (&buf[2], revkey->fpr, MAX_FINGERPRINT_LEN); + memcpy (buf + 2, revkey->fpr, revkey->fprlen); + memset (buf + 2 + revkey->fprlen, 0, sizeof (revkey->fpr) - revkey->fprlen); - build_sig_subpkt (sig, SIGSUBPKT_REV_KEY, buf, 2+MAX_FINGERPRINT_LEN); + build_sig_subpkt (sig, SIGSUBPKT_REV_KEY, buf, 2+revkey->fprlen); /* All sigs with revocation keys set are nonrevocable. */ sig->flags.revocable = 0; @@ -3526,6 +3528,8 @@ parse_revocation_key (const char *fname, revkey.fpr[i]=c; } + if (i != 20 && i != 32) + goto fail; /* skip to the tag */ while(*pn && *pn!='s' && *pn!='S') @@ -3538,7 +3542,7 @@ parse_revocation_key (const char *fname, return 0; - fail: + fail: log_error("%s:%d: invalid revocation key\n", fname, r->lnr ); return -1; /* error */ } diff --git a/g10/keyid.c b/g10/keyid.c index 3694c26cc..e099c7d97 100644 --- a/g10/keyid.c +++ b/g10/keyid.c @@ -534,8 +534,9 @@ keyid_from_pk (PKT_public_key *pk, u32 *keyid) /* - * Get the keyid from the fingerprint. This function is simple for most - * keys, but has to do a keylookup for old stayle keys. + * Get the keyid from the fingerprint. This function is simple for + * most keys, but has to do a key lookup for old v3 keys where the + * keyid is not part of the fingerprint. */ u32 keyid_from_fingerprint (ctrl_t ctrl, const byte *fprint, @@ -546,7 +547,7 @@ keyid_from_fingerprint (ctrl_t ctrl, const byte *fprint, if( !keyid ) keyid = dummy_keyid; - if (fprint_len != 20) + if (fprint_len != 20 && fprint_len != 32) { /* This is special as we have to lookup the key first. */ PKT_public_key pk; @@ -556,7 +557,8 @@ keyid_from_fingerprint (ctrl_t ctrl, const byte *fprint, rc = get_pubkey_byfprint (ctrl, &pk, NULL, fprint, fprint_len); if( rc ) { - log_error("Oops: keyid_from_fingerprint: no pubkey\n"); + log_printhex (fprint, fprint_len, + "Oops: keyid_from_fingerprint: no pubkey; fpr:"); keyid[0] = 0; keyid[1] = 0; } diff --git a/g10/keylist.c b/g10/keylist.c index 9a2166308..92a8d4cbb 100644 --- a/g10/keylist.c +++ b/g10/keylist.c @@ -1321,7 +1321,7 @@ print_revokers (estream_t fp, PKT_public_key * pk) es_fprintf (fp, "rvk:::%d::::::", pk->revkey[i].algid); p = pk->revkey[i].fpr; - for (j = 0; j < 20; j++, p++) + for (j = 0; j < pk->revkey[i].fprlen; j++, p++) es_fprintf (fp, "%02X", *p); es_fprintf (fp, ":%02x%s:\n", pk->revkey[i].class, diff --git a/g10/packet.h b/g10/packet.h index d088bf432..1ec12d64b 100644 --- a/g10/packet.h +++ b/g10/packet.h @@ -180,6 +180,8 @@ struct revocation_key { byte class; /* The public-key algorithm ID. */ byte algid; + /* The length of the fingerprint. */ + byte fprlen; /* The fingerprint of the authorized key. */ byte fpr[MAX_FINGERPRINT_LEN]; }; diff --git a/g10/parse-packet.c b/g10/parse-packet.c index c0f2ca12e..21a26c786 100644 --- a/g10/parse-packet.c +++ b/g10/parse-packet.c @@ -1905,21 +1905,23 @@ parse_revkeys (PKT_signature * sig) 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. */ + /* Consider only valid packets. They must have a length of + * either 2+20 or 2+32 octets and bit 7 of the class octet must + * be set. */ + if ((len == 22 || len == 34) && (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); - + len -= 2; + sig->revkey[sig->numrevkeys].fprlen = len; + memcpy (sig->revkey[sig->numrevkeys].fpr, revkey+2, len); + memset (sig->revkey[sig->numrevkeys].fpr+len, 0, + sizeof (sig->revkey[sig->numrevkeys].fpr) - len); sig->numrevkeys++; } } diff --git a/g10/revoke.c b/g10/revoke.c index b778684b0..e8ce3544c 100644 --- a/g10/revoke.c +++ b/g10/revoke.c @@ -277,12 +277,12 @@ gen_desig_revoke (ctrl_t ctrl, const char *uname, strlist_t locusr) fingerprint_from_pk (list->pk, fpr, &fprlen); - /* Don't get involved with keys that don't have 160 - bit fingerprints */ - if(fprlen!=20) + /* Don't get involved with keys that don't have a v4 + * or v5 fingerprint */ + if (fprlen != 20 && fprlen != 32) continue; - if(memcmp(fpr,pk->revkey[i].fpr,20)==0) + if (!memcmp(fpr,pk->revkey[i].fpr, fprlen)) break; } @@ -295,7 +295,7 @@ gen_desig_revoke (ctrl_t ctrl, const char *uname, strlist_t locusr) { pk2 = xmalloc_clear (sizeof *pk2); rc = get_pubkey_byfprint (ctrl, pk2, NULL, - pk->revkey[i].fpr, MAX_FINGERPRINT_LEN); + pk->revkey[i].fpr, pk->revkey[i].fprlen); } /* We have the revocation key. */ @@ -388,15 +388,18 @@ gen_desig_revoke (ctrl_t ctrl, const char *uname, strlist_t locusr) for(j=0;jpkt->pkt.signature->numrevkeys;j++) { - if(pk->revkey[i].class== - signode->pkt->pkt.signature->revkey[j].class && - pk->revkey[i].algid== - signode->pkt->pkt.signature->revkey[j].algid && - memcmp(pk->revkey[i].fpr, - signode->pkt->pkt.signature->revkey[j].fpr, - MAX_FINGERPRINT_LEN)==0) + if (pk->revkey[i].class + == signode->pkt->pkt.signature->revkey[j].class + && pk->revkey[i].algid + == signode->pkt->pkt.signature->revkey[j].algid + && pk->revkey[i].fprlen + == signode->pkt->pkt.signature->revkey[j].fprlen + && !memcmp + (pk->revkey[i].fpr, + signode->pkt->pkt.signature->revkey[j].fpr, + pk->revkey[i].fprlen)) { - revkey=signode->pkt->pkt.signature; + revkey = signode->pkt->pkt.signature; break; } } diff --git a/g10/sig-check.c b/g10/sig-check.c index 6d7f1afbd..d02c68e33 100644 --- a/g10/sig-check.c +++ b/g10/sig-check.c @@ -716,8 +716,8 @@ check_revocation_keys (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig) /* The revoker's keyid. */ u32 keyid[2]; - keyid_from_fingerprint (ctrl, pk->revkey[i].fpr, - MAX_FINGERPRINT_LEN, keyid); + keyid_from_fingerprint (ctrl, pk->revkey[i].fpr, pk->revkey[i].fprlen, + keyid); if(keyid[0]==sig->keyid[0] && keyid[1]==sig->keyid[1]) /* The signature was generated by a designated revoker. From 0c36ec241d285545f286069843de4f663cd274a3 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 4 Dec 2018 16:00:49 +0100 Subject: [PATCH 098/235] wks: Fix filter expression syntax flaw. * tools/wks-util.c (wks_get_key, wks_filter_uid): The filter expression needs a space before the value. (install_key_from_spec_file): Replace es_getline by es_read_line and remove debug output. -- A value of starting with '<' was considered an invalid operator due to our tokenization method. Signed-off-by: Werner Koch --- tools/wks-util.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tools/wks-util.c b/tools/wks-util.c index 73cced9c2..1935d264a 100644 --- a/tools/wks-util.c +++ b/tools/wks-util.c @@ -192,7 +192,7 @@ wks_get_key (estream_t *r_key, const char *fingerprint, const char *addrspec, es_fputs ("Content-Type: application/pgp-keys\n" "\n", key); - filterexp = es_bsprintf ("keep-uid=%s=%s", exact? "uid":"mbox", addrspec); + filterexp = es_bsprintf ("keep-uid=%s= %s", exact? "uid":"mbox", addrspec); if (!filterexp) { err = gpg_error_from_syserror (); @@ -466,7 +466,7 @@ wks_filter_uid (estream_t *r_newkey, estream_t key, const char *uid, es_fputs ("Content-Type: application/pgp-keys\n" "\n", newkey); - filterexp = es_bsprintf ("keep-uid=uid=%s", uid); + filterexp = es_bsprintf ("keep-uid=uid= %s", uid); if (!filterexp) { err = gpg_error_from_syserror (); @@ -864,6 +864,7 @@ install_key_from_spec_file (const char *fname) estream_t fp; char *line = NULL; size_t linelen = 0; + size_t maxlen = 2048; char *fields[2]; unsigned int lnr = 0; @@ -878,11 +879,16 @@ install_key_from_spec_file (const char *fname) goto leave; } - while (es_getline (&line, &linelen, fp) >= 0) + while (es_read_line (fp, &line, &linelen, &maxlen) > 0) { + if (!maxlen) + { + err = gpg_error (GPG_ERR_LINE_TOO_LONG); + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } lnr++; trim_spaces (line); - log_debug ("got line='%s'\n", line); if (!*line || *line == '#') continue; if (split_fields (line, fields, DIM(fields)) < 2) From e154fba30ba0d5f29040a33f5c1b5c25b441b69f Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 5 Dec 2018 16:44:59 +0900 Subject: [PATCH 099/235] g10: Fix print_pubkey_info new line output. * g10/keylist.c (print_pubkey_info): Reverse the condition. -- This mistakes were introduced when replacing by estream. It resulted 'gpg --card-status' from a process with no controlling terminal fails. Fixes-commit: fb2ba98963beea249474f5d6d7345cf9b4b7f570 Signed-off-by: NIIBE Yutaka --- g10/keylist.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/g10/keylist.c b/g10/keylist.c index 92a8d4cbb..0b8aa88cd 100644 --- a/g10/keylist.c +++ b/g10/keylist.c @@ -212,7 +212,7 @@ print_pubkey_info (ctrl_t ctrl, estream_t fp, PKT_public_key *pk) else p = get_user_id_native (ctrl, keyid); - if (fp) + if (!fp) tty_printf ("\n"); tty_fprintf (fp, "%s %s/%s %s %s\n", pk->flags.primary? "pub":"sub", From 2c35e67e3475ec38ff49953d79bd0f734d6db542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomi=20Lepp=C3=A4nen?= Date: Tue, 11 Dec 2018 08:42:33 +0100 Subject: [PATCH 100/235] tools: Use POSIX compatible arguments for find * tools/addgnupghome (filelist): Remove bashism. --- tools/addgnupghome | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/addgnupghome b/tools/addgnupghome index e13c3cd01..718b2226c 100755 --- a/tools/addgnupghome +++ b/tools/addgnupghome @@ -107,7 +107,7 @@ if [ ! -d /etc/skel/.gnupg ]; then exit 1 fi cd "/etc/skel/.gnupg" || (error "error cd-ing to \`/etc/skel/.gnupg'"; exit 1) -filelist=$(find . \( -type f -or -type d \) -not -name '*~' -not -name . -print) +filelist=$(find . \( -type f -o -type d \) '!' -name '*~' '!' -name . -print) if ! umask 0077 ; then From e7252ae57f3c9da557f23295268f74dd25fee3a1 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 11 Dec 2018 12:29:58 +0100 Subject: [PATCH 101/235] gpg: In search-keys return "Not found" instead of "No Data". * g10/keyserver.c (keyserver_search): Check for NO_DATA. -- GnuPG-bug-id: 3830 Signed-off-by: Werner Koch --- g10/keyserver.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/g10/keyserver.c b/g10/keyserver.c index 44870a610..1ba94ed49 100644 --- a/g10/keyserver.c +++ b/g10/keyserver.c @@ -1528,7 +1528,7 @@ keyserver_search (ctrl_t ctrl, strlist_t tokens) err = gpg_dirmngr_ks_search (ctrl, searchstr, search_line_handler, &parm); - if (parm.not_found) + if (parm.not_found || gpg_err_code (err) == GPG_ERR_NO_DATA) { if (parm.searchstr_disp) log_info (_("key \"%s\" not found on keyserver\n"), @@ -1539,6 +1539,8 @@ keyserver_search (ctrl_t ctrl, strlist_t tokens) if (gpg_err_code (err) == GPG_ERR_NO_KEYSERVER) log_error (_("no keyserver known (use option --keyserver)\n")); + else if (gpg_err_code (err) == GPG_ERR_NO_DATA) + err = gpg_error (GPG_ERR_NOT_FOUND); else if (err) log_error ("error searching keyserver: %s\n", gpg_strerror (err)); From dc61f4ecea5c9815cb00aeb25439978337c1fd64 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 11 Dec 2018 13:24:21 +0100 Subject: [PATCH 102/235] dirmngr: New function http_status2string. * dirmngr/http.c (http_status2string): New. -- Right now only the standard 5xx codes. Signed-off-by: Werner Koch --- dirmngr/http.c | 24 ++++++++++++++++++++++++ dirmngr/http.h | 2 ++ 2 files changed, 26 insertions(+) diff --git a/dirmngr/http.c b/dirmngr/http.c index 6c183b2ec..eb7d99a1e 100644 --- a/dirmngr/http.c +++ b/dirmngr/http.c @@ -3663,3 +3663,27 @@ http_prepare_redirect (http_redir_info_t *info, unsigned int status_code, *r_url = newurl; return 0; } + + +/* Return string describing the http STATUS. Returns an empty string + * for an unknown status. */ +const char * +http_status2string (unsigned int status) +{ + switch (status) + { + case 500: return "Internal Server Error"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP version Not Supported"; + case 506: return "Variant Also Negation"; + case 507: return "Insufficient Storage"; + case 508: return "Loop Detected"; + case 510: return "Not Extended"; + case 511: return "Network Authentication Required"; + } + + return ""; +} diff --git a/dirmngr/http.h b/dirmngr/http.h index 4755b9248..492e86726 100644 --- a/dirmngr/http.h +++ b/dirmngr/http.h @@ -197,5 +197,7 @@ gpg_error_t http_prepare_redirect (http_redir_info_t *info, unsigned int status_code, const char *location, char **r_url); +const char *http_status2string (unsigned int status); + #endif /*GNUPG_COMMON_HTTP_H*/ From 05ef6282784495a77f4faf76c0de5bc85dfecf06 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 11 Dec 2018 13:39:41 +0100 Subject: [PATCH 103/235] dirmngr: Retry another server from the pool on 502, 503, 504. * dirmngr/ks-engine-hkp.c (handle_send_request_error): Add arg http_status and handle it. (ks_hkp_search): Get http_status froms end_request and pass on to handle_send_request_error. (ks_hkp_get): Ditto. (ks_hkp_put): Ditto. -- GnuPG-bug-id: 4175 Signed-off-by: Werner Koch --- dirmngr/ks-engine-hkp.c | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c index 1b14a2e6a..149de9425 100644 --- a/dirmngr/ks-engine-hkp.c +++ b/dirmngr/ks-engine-hkp.c @@ -1352,7 +1352,7 @@ send_request (ctrl_t ctrl, const char *request, const char *hostportstr, down to zero. */ static int handle_send_request_error (ctrl_t ctrl, gpg_error_t err, const char *request, - unsigned int *tries_left) + unsigned int http_status, unsigned int *tries_left) { int retry = 0; @@ -1395,6 +1395,27 @@ handle_send_request_error (ctrl_t ctrl, gpg_error_t err, const char *request, } break; + case GPG_ERR_NO_DATA: + { + switch (http_status) + { + case 502: /* Bad Gateway */ + log_info ("marking host dead due to a %u (%s)\n", + http_status, http_status2string (http_status)); + if (mark_host_dead (request) && *tries_left) + retry = 1; + break; + + case 503: /* Service Unavailable */ + case 504: /* Gateway Timeout */ + log_info ("selecting a different host due to a %u (%s)", + http_status, http_status2string (http_status)); + retry = 1; + break; + } + } + break; + default: break; } @@ -1423,6 +1444,7 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, int reselect; unsigned int httpflags; char *httphost = NULL; + unsigned int http_status; unsigned int tries = SEND_REQUEST_RETRIES; *r_fp = NULL; @@ -1504,12 +1526,14 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, /* Send the request. */ err = send_request (ctrl, request, hostport, httphost, httpflags, - NULL, NULL, &fp, r_http_status); - if (handle_send_request_error (ctrl, err, request, &tries)) + NULL, NULL, &fp, &http_status); + if (handle_send_request_error (ctrl, err, request, http_status, &tries)) { reselect = 1; goto again; } + if (r_http_status) + *r_http_status = http_status; if (err) { if (gpg_err_code (err) == GPG_ERR_NO_DATA) @@ -1571,6 +1595,7 @@ ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp) int reselect; char *httphost = NULL; unsigned int httpflags; + unsigned int http_status; unsigned int tries = SEND_REQUEST_RETRIES; *r_fp = NULL; @@ -1643,8 +1668,8 @@ ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp) /* Send the request. */ err = send_request (ctrl, request, hostport, httphost, httpflags, - NULL, NULL, &fp, NULL); - if (handle_send_request_error (ctrl, err, request, &tries)) + NULL, NULL, &fp, &http_status); + if (handle_send_request_error (ctrl, err, request, http_status, &tries)) { reselect = 1; goto again; @@ -1718,6 +1743,7 @@ ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, const void *data, size_t datalen) int reselect; char *httphost = NULL; unsigned int httpflags; + unsigned int http_status; unsigned int tries = SEND_REQUEST_RETRIES; parm.datastring = NULL; @@ -1756,8 +1782,8 @@ ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, const void *data, size_t datalen) /* Send the request. */ err = send_request (ctrl, request, hostport, httphost, 0, - put_post_cb, &parm, &fp, NULL); - if (handle_send_request_error (ctrl, err, request, &tries)) + put_post_cb, &parm, &fp, &http_status); + if (handle_send_request_error (ctrl, err, request, http_status, &tries)) { reselect = 1; goto again; From cbcc8c19541fe8407f3b6588fce1535c64cf6b25 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 11 Dec 2018 18:12:51 +0100 Subject: [PATCH 104/235] agent: Make the S2K calibration time runtime configurabe. * agent/protect.c (s2k_calibration_time): New file global var. (calibrate_s2k_count): Use it here. (get_calibrated_s2k_count): Replace function static var by ... (s2k_calibrated_count): new file global var. (set_s2k_calibration_time): New function. * agent/gpg-agent.c (oS2KCalibration): New const. (opts): New option --s2k-calibration. (parse_rereadable_options): Parse that option. -- Note that using an unrelistic high value (like 60000) takes quite some time for calibration. GnuPG-bug-id: 3399 Signed-off-by: Werner Koch --- agent/agent.h | 1 + agent/gpg-agent.c | 7 +++++++ agent/protect.c | 33 ++++++++++++++++++++++++++------- doc/gpg-agent.texi | 11 +++++++++-- 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/agent/agent.h b/agent/agent.h index 8a3e5c6b6..05080f1f2 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -488,6 +488,7 @@ gpg_error_t agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey, char **passphrase_addr); /*-- protect.c --*/ +void set_s2k_calibration_time (unsigned int milliseconds); unsigned long get_calibrated_s2k_count (void); unsigned long get_standard_s2k_count (void); unsigned char get_standard_s2k_count_rfc4880 (void); diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index de6947d4e..e1c0e2b0b 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -135,6 +135,7 @@ enum cmd_and_opt_values oDisableScdaemon, oDisableCheckOwnSocket, oS2KCount, + oS2KCalibration, oAutoExpandSecmem, oListenBacklog, @@ -253,6 +254,7 @@ static ARGPARSE_OPTS opts[] = { ARGPARSE_s_n (oEnableExtendedKeyFormat, "enable-extended-key-format", "@"), ARGPARSE_s_u (oS2KCount, "s2k-count", "@"), + ARGPARSE_s_u (oS2KCalibration, "s2k-calibration", "@"), ARGPARSE_op_u (oAutoExpandSecmem, "auto-expand-secmem", "@"), @@ -834,6 +836,7 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) /* Note: When changing the next line, change also gpgconf_list. */ opt.ssh_fingerprint_digest = GCRY_MD_MD5; opt.s2k_count = 0; + set_s2k_calibration_time (0); /* Set to default. */ return 1; } @@ -929,6 +932,10 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) opt.s2k_count = pargs->r.ret_ulong; break; + case oS2KCalibration: + set_s2k_calibration_time (pargs->r.ret_ulong); + break; + default: return 0; /* not handled */ } diff --git a/agent/protect.c b/agent/protect.c index c7bd30b68..f95527f78 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -71,6 +71,13 @@ static const struct { }; +/* The number of milliseconds we use in the S2K function and the + * calibrated count value. A count value of zero indicates that the + * calibration has not yet been done or needs to be done again. */ +static unsigned int s2k_calibration_time = AGENT_S2K_CALIBRATION; +static unsigned long s2k_calibrated_count; + + /* A helper object for time measurement. */ struct calibrate_time_s { @@ -175,11 +182,11 @@ calibrate_s2k_count (void) ms = calibrate_s2k_count_one (count); if (opt.verbose > 1) log_info ("S2K calibration: %lu -> %lums\n", count, ms); - if (ms > AGENT_S2K_CALIBRATION) + if (ms > s2k_calibration_time) break; } - count = (unsigned long)(((double)count / ms) * AGENT_S2K_CALIBRATION); + count = (unsigned long)(((double)count / ms) * s2k_calibration_time); count /= 1024; count *= 1024; if (count < 65536) @@ -195,18 +202,30 @@ calibrate_s2k_count (void) } +/* Set the calibration time. This may be called early at startup or + * at any time. Thus it should one set variables. */ +void +set_s2k_calibration_time (unsigned int milliseconds) +{ + if (!milliseconds) + milliseconds = AGENT_S2K_CALIBRATION; + else if (milliseconds > 60 * 1000) + milliseconds = 60 * 1000; /* Cap at 60 seconds. */ + s2k_calibration_time = milliseconds; + s2k_calibrated_count = 0; /* Force re-calibration. */ +} + + /* 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 (); + if (!s2k_calibrated_count) + s2k_calibrated_count = calibrate_s2k_count (); /* Enforce a lower limit. */ - return count < 65536 ? 65536 : count; + return s2k_calibrated_count < 65536 ? 65536 : s2k_calibrated_count; } diff --git a/doc/gpg-agent.texi b/doc/gpg-agent.texi index bcce03329..3997d2046 100644 --- a/doc/gpg-agent.texi +++ b/doc/gpg-agent.texi @@ -669,12 +669,19 @@ 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-calibration @var{milliseconds} +@opindex s2k-calibration +Change the default calibration time to @var{milliseconds}. The given +value is capped at 60 seconds; a value of 0 resets to the compiled-in +default. This option is re-read on a SIGHUP (or @code{gpgconf +--reload gpg-agent}) and the S2K count is then re-calibrated. + @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. +The auto-calibration computes a count which requires by default 100ms +to mangle a given passphrase. See also @option{--s2k-calibration}. To view the actually used iteration count and the milliseconds required for an S2K operation use: From ebf775eb16fef27bd1f27319a5483d04dcf95a9a Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 12 Dec 2018 10:25:34 +0900 Subject: [PATCH 105/235] card: Suppress error message by agent_scd_cardlist. * g10/call-agent.c (agent_scd_cardlist): Add FLAG_FOR_CARD_SUPPRESS_ERRORS. Signed-off-by: NIIBE Yutaka --- g10/call-agent.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/g10/call-agent.c b/g10/call-agent.c index 11011aea4..c958b84b7 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -1218,7 +1218,7 @@ agent_scd_cardlist (strlist_t *result) memset (&parm, 0, sizeof parm); *result = NULL; - err = start_agent (NULL, 1); + err = start_agent (NULL, 1 | FLAG_FOR_CARD_SUPPRESS_ERRORS); if (err) return err; From 40c307fa8d0e813f2aa57806f25b8b0063cc2be3 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 17 Dec 2018 18:46:26 +0100 Subject: [PATCH 106/235] Silence a few compiler warnings new with gcc 8. * dirmngr/dns.c: Include gpgrt.h. Silence -Warray-bounds also gcc. * tools/gpg-pair-tool.c (command_respond): Init two vars to silence gcc. Signed-off-by: Werner Koch --- dirmngr/dns.c | 17 ++++++++++++----- tools/gpg-pair-tool.c | 4 ++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/dirmngr/dns.c b/dirmngr/dns.c index 596e81fc9..210e9f49a 100644 --- a/dirmngr/dns.c +++ b/dirmngr/dns.c @@ -77,6 +77,7 @@ typedef int socket_fd_t; #include /* struct addrinfo */ #endif +#include "gpgrt.h" /* For GGPRT_GCC_VERSION */ #include "dns.h" @@ -7521,9 +7522,13 @@ static unsigned char *dns_so_tcp_recv_buffer(struct dns_socket *so) { } -#if defined __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warray-bounds" + +#if GPGRT_GCC_VERSION >= 80000 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Warray-bounds" +#elif defined __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Warray-bounds" #endif static int dns_so_tcp_send(struct dns_socket *so) { @@ -7589,8 +7594,10 @@ static int dns_so_tcp_recv(struct dns_socket *so) { return 0; } /* dns_so_tcp_recv() */ -#if __clang__ -#pragma clang diagnostic pop +#if GPGRT_GCC_VERSION >= 80000 +# pragma GCC diagnostic pop +#elif __clang__ +# pragma clang diagnostic pop #endif diff --git a/tools/gpg-pair-tool.c b/tools/gpg-pair-tool.c index a86bd8e3c..347b29d24 100644 --- a/tools/gpg-pair-tool.c +++ b/tools/gpg-pair-tool.c @@ -1885,8 +1885,8 @@ command_respond (void) { gpg_error_t err; unsigned char *msg; - size_t msglen; - int msgtype; + size_t msglen = 0; /* In case that read_message returns an error. */ + int msgtype = 0; /* ditto. */ nvc_t state; const char *rolestr; const char *statestr; From e6b7e0ff9990813ac9f11b2d9d92596d6379ebfe Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 18 Dec 2018 10:26:57 +0900 Subject: [PATCH 107/235] scd: Fix description string. * scd/app-openpgp.c (data_objects): Capitalize the word for usage. Signed-off-by: NIIBE Yutaka --- scd/app-openpgp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index 9948c0bd9..c87add14a 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -122,8 +122,8 @@ static struct { { 0x7F74, 0, 0x6E, 1, 0, 0, 0, 0, "General Feature Management"}, { 0x00D5, 0, 0, 1, 0, 0, 0, 0, "AES key data"}, { 0x00D6, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for Signature"}, - { 0x00D7, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for decryption"}, - { 0x00D8, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for authentication"}, + { 0x00D7, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for Decryption"}, + { 0x00D8, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for Authentication"}, { 0x00F9, 0, 0, 1, 0, 0, 0, 0, "KDF data object"}, { 0 } }; From 70a8db0333e3c22403b3647f8b5f924f6dace719 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 18 Dec 2018 08:21:03 +0100 Subject: [PATCH 108/235] wks: Do not use compression for the encrypted data. * tools/gpg-wks-client.c (encrypt_response): Add arg -z0. * tools/gpg-wks-server.c (encrypt_stream): Ditto. -- If for example a server was built without the development packages of the compression libraries installed, the server will not be able to decrypt a request. In theory this can't happen due to the preference system but it is just to easy to create the server's key using a different version of gpg and then use gpg-wks-server built differently. For the short messages we exchange compression is not really required and thus we better do without to make the system more robust. Signed-off-by: Werner Koch --- tools/gpg-wks-client.c | 1 + tools/gpg-wks-server.c | 1 + 2 files changed, 2 insertions(+) diff --git a/tools/gpg-wks-client.c b/tools/gpg-wks-client.c index 0dee1a2a1..e59d52d36 100644 --- a/tools/gpg-wks-client.c +++ b/tools/gpg-wks-client.c @@ -1151,6 +1151,7 @@ encrypt_response (estream_t *r_output, estream_t input, const char *addrspec, ccparray_put (&ccp, "--status-fd=2"); ccparray_put (&ccp, "--always-trust"); ccparray_put (&ccp, "--armor"); + ccparray_put (&ccp, "-z0"); /* No compression for improved robustness. */ if (fake_submission_addr) ccparray_put (&ccp, "--auto-key-locate=clear,local"); else diff --git a/tools/gpg-wks-server.c b/tools/gpg-wks-server.c index 1a0ba8f4f..f83ef6528 100644 --- a/tools/gpg-wks-server.c +++ b/tools/gpg-wks-server.c @@ -586,6 +586,7 @@ encrypt_stream (estream_t *r_output, estream_t input, const char *keyfile) ccparray_put (&ccp, "--always-trust"); ccparray_put (&ccp, "--no-keyring"); ccparray_put (&ccp, "--armor"); + ccparray_put (&ccp, "-z0"); /* No compression for improved robustness. */ ccparray_put (&ccp, "--recipient-file"); ccparray_put (&ccp, keyfile); ccparray_put (&ccp, "--encrypt"); From 3d766924b412b36fc9481803447b93f7fa68b8f6 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 2 Jan 2019 16:15:25 +0100 Subject: [PATCH 109/235] doc: Typo fix in code comment -- --- scd/app-openpgp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index c87add14a..f25a36062 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -2538,10 +2538,10 @@ do_setattr (app_t app, const char *name, } -/* Handle the WRITECERT command for OpenPGP. This rites the standard - certifciate to the card; CERTID needs to be set to "OPENPGP.3". - PINCB and PINCB_ARG are the usual arguments for the pinentry - callback. */ +/* Handle the WRITECERT command for OpenPGP. This writes the standard + * certificate to the card; CERTID needs to be set to "OPENPGP.3". + * PINCB and PINCB_ARG are the usual arguments for the pinentry + * callback. */ static gpg_error_t do_writecert (app_t app, ctrl_t ctrl, const char *certidstr, From cca2b87e79cda212a33c13efdd2b2830295d2efe Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 3 Jan 2019 15:17:04 +0100 Subject: [PATCH 110/235] scd: Support "READKEY --advanced" for all cards. * scd/command.c (cmd_readkey): Reformat for advanced mode. -- The --advanced option used to work only if the driver supported that but not if we extracted the public key from an x.509 certificate. This patch fixes that. Signed-off-by: Werner Koch --- scd/command.c | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/scd/command.c b/scd/command.c index 0a9654693..9df26117c 100644 --- a/scd/command.c +++ b/scd/command.c @@ -465,7 +465,7 @@ cmd_learn (assuan_context_t ctx, char *line) static const char hlp_readcert[] = - "READCERT |\n" + "READCERT ||\n" "\n" "Note, that this function may even be used on a locked card."; static gpg_error_t @@ -498,7 +498,7 @@ cmd_readcert (assuan_context_t ctx, char *line) static const char hlp_readkey[] = - "READKEY [--advanced] \n" + "READKEY [--advanced] |\n" "\n" "Return the public key for the given cert or key ID as a standard\n" "S-expression.\n" @@ -514,7 +514,7 @@ cmd_readkey (assuan_context_t ctx, char *line) unsigned char *cert = NULL; size_t ncert, n; ksba_cert_t kc = NULL; - ksba_sexp_t p; + ksba_sexp_t p = NULL; unsigned char *pk; size_t pklen; @@ -570,13 +570,36 @@ cmd_readkey (assuan_context_t ctx, char *line) rc = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } - n = gcry_sexp_canon_len (p, 0, NULL, NULL); - rc = assuan_send_data (ctx, p, n); - xfree (p); + if (advanced) + { + gcry_sexp_t s_key; + + rc = gcry_sexp_new (&s_key, (void*)p, n, 0); + if (rc) + goto leave; + + pklen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, NULL, 0); + pk = xtrymalloc (pklen); + if (!pk) + { + rc = gpg_error_from_syserror (); + goto leave; + } + log_assert (pklen); + + gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, pk, pklen); + gcry_sexp_release (s_key); + /* (One less to adjust for the trailing '\0') */ + rc = assuan_send_data (ctx, pk, pklen-1); + xfree (pk); + } + else + rc = assuan_send_data (ctx, p, n); leave: + xfree (p); ksba_cert_release (kc); xfree (cert); return rc; From 405feca2bdeeb620dc406667a702035a123ae848 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 3 Jan 2019 15:18:15 +0100 Subject: [PATCH 111/235] scd: Add two variants to the set of ISO7816 functions. * scd/iso7816.c (iso7816_select_application_ext): New. (iso7816_get_data_odd): New. Signed-off-by: Werner Koch --- scd/iso7816.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++ scd/iso7816.h | 7 +++++ 2 files changed, 86 insertions(+) diff --git a/scd/iso7816.c b/scd/iso7816.c index 01faca5b4..43c0bcd8e 100644 --- a/scd/iso7816.c +++ b/scd/iso7816.c @@ -138,6 +138,21 @@ iso7816_select_application (int slot, const char *aid, size_t aidlen, } +/* This is the same as iso7816_select_application but may return data + * at RESULT,RESULTLEN). */ +gpg_error_t +iso7816_select_application_ext (int slot, const char *aid, size_t aidlen, + unsigned int flags, + unsigned char **result, size_t *resultlen) +{ + int sw; + sw = apdu_send (slot, 0, 0x00, CMD_SELECT_FILE, 4, + (flags&1)? 0:0x0c, aidlen, aid, + result, resultlen); + return map_sw (sw); +} + + gpg_error_t iso7816_select_file (int slot, int tag, int is_dir) { @@ -396,6 +411,70 @@ iso7816_get_data (int slot, int extended_mode, int tag, } +/* Perform a GET DATA command requesting TAG and storing the result in + * a newly allocated buffer at the address passed by RESULT. Return + * the length of this data at the address of RESULTLEN. This variant + * is needed for long (3 octet) tags. */ +gpg_error_t +iso7816_get_data_odd (int slot, int extended_mode, unsigned int tag, + unsigned char **result, size_t *resultlen) +{ + int sw; + int le; + int datalen; + unsigned char data[5]; + + if (!result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + if (extended_mode > 0 && extended_mode < 256) + le = 65534; /* Not 65535 in case it is used as some special flag. */ + else if (extended_mode > 0) + le = extended_mode; + else + le = 256; + + data[0] = 0x5c; + if (tag <= 0xff) + { + data[1] = 1; + data[2] = tag; + datalen = 3; + } + else if (tag <= 0xffff) + { + data[1] = 2; + data[2] = (tag >> 8); + data[3] = tag; + datalen = 4; + } + else + { + data[1] = 3; + data[2] = (tag >> 16); + data[3] = (tag >> 8); + data[4] = tag; + datalen = 5; + } + + sw = apdu_send_le (slot, extended_mode, 0x00, CMD_GET_DATA + 1, + 0x3f, 0xff, datalen, data, le, + result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + /* Perform a PUT DATA command on card in SLOT. Write DATA of length DATALEN to TAG. EXTENDED_MODE controls whether extended length headers or command chaining is used instead of single length diff --git a/scd/iso7816.h b/scd/iso7816.h index 4c71bbd50..332fc0e81 100644 --- a/scd/iso7816.h +++ b/scd/iso7816.h @@ -51,6 +51,11 @@ gpg_error_t iso7816_map_sw (int sw); gpg_error_t iso7816_select_application (int slot, const char *aid, size_t aidlen, unsigned int flags); +gpg_error_t iso7816_select_application_ext (int slot, + const char *aid, size_t aidlen, + unsigned int flags, + unsigned char **result, + size_t *resultlen); gpg_error_t iso7816_select_file (int slot, int tag, int is_dir); gpg_error_t iso7816_select_path (int slot, const unsigned short *path, size_t pathlen); @@ -78,6 +83,8 @@ gpg_error_t iso7816_reset_retry_counter_with_rc (int slot, int chvno, size_t datalen); gpg_error_t iso7816_get_data (int slot, int extended_mode, int tag, unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_get_data_odd (int slot, int extended_mode, unsigned int tag, + unsigned char **result, size_t *resultlen); gpg_error_t iso7816_put_data (int slot, int extended_mode, int tag, const void *data, size_t datalen); gpg_error_t iso7816_put_data_odd (int slot, int extended_mode, int tag, From 5ab3bc422a5cc1a646c168b547f2b6538b3a4ffa Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Mon, 7 Jan 2019 14:08:51 +0900 Subject: [PATCH 112/235] scd: Fix for USB INTERRUPT transfer. * scd/ccid-driver.c (intr_cb): When LIBUSB_TRANSFER_NO_DEVICE, just handle this event as failure. -- It used to try another interrupt transfer request to make sure if it fails again. GnuPG-bug-id: 4308 Signed-off-by: NIIBE Yutaka --- scd/ccid-driver.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c index ff82ef38b..c165f6e30 100644 --- a/scd/ccid-driver.c +++ b/scd/ccid-driver.c @@ -1470,8 +1470,7 @@ intr_cb (struct libusb_transfer *transfer) DEBUGOUT_1 ("CCID: interrupt callback %d\n", transfer->status); - if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT - || transfer->status == LIBUSB_TRANSFER_NO_DEVICE) + if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT) { int err; From 6c000d4b78b836686e5a2789cc88a41e465e4400 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 8 Jan 2019 11:21:07 +0100 Subject: [PATCH 113/235] doc: Mark keyserver-options timeout and http-proxy as obsolete. -- --- doc/gpg.texi | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/doc/gpg.texi b/doc/gpg.texi index a5c172b2c..6b6c8cc54 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -1895,32 +1895,12 @@ are available for all keyserver types, some common options are: retrieving keys by subkey id. @item timeout - Tell the keyserver helper program how long (in seconds) to try and - perform a keyserver action before giving up. Note that performing - multiple actions at the same time uses this timeout value per action. - For example, when retrieving multiple keys via @option{--receive-keys}, the - timeout applies separately to each key retrieval, and not to the - @option{--receive-keys} command as a whole. Defaults to 30 seconds. - - @item http-proxy=@var{value} - This option is deprecated. - Set the proxy to use for HTTP and HKP keyservers. - This overrides any proxy defined in @file{dirmngr.conf}. - - @item verbose - This option has no more function since GnuPG 2.1. Use the - @code{dirmngr} configuration options instead. - - @item debug - This option has no more function since GnuPG 2.1. Use the - @code{dirmngr} configuration options instead. - - @item check-cert - This option has no more function since GnuPG 2.1. Use the - @code{dirmngr} configuration options instead. - + @itemx http-proxy=@var{value} + @itemx verbose + @itemx debug + @itemx check-cert @item ca-cert-file - This option has no more function since GnuPG 2.1. Use the + These options have no more function since GnuPG 2.1. Use the @code{dirmngr} configuration options instead. @end table From dafffa95b2317bcb80fff1fd6d2bc7b4e6b1e206 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 16 Jan 2019 10:27:21 +0900 Subject: [PATCH 114/235] gpg: Report STATUS_NO_SECKEY when it is examined. * g10/packet.h (struct pubkey_enc_list): Add result. * g10/mainproc.c (proc_pubkey_enc): Initialize ->result. (proc_encrypted): Report STATUS_NO_SECKEY status. * g10/pubkey-enc.c (get_session_key): Set ->result. -- This change is for GPGME compatibility. Before this change, gpgme/tests/json/t-json failed with t-decrypt-verify. Signed-off-by: NIIBE Yutaka --- g10/mainproc.c | 26 +++++++++++++------------- g10/packet.h | 1 + g10/pubkey-enc.c | 3 +++ 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/g10/mainproc.c b/g10/mainproc.c index dce3f3799..8c41088cc 100644 --- a/g10/mainproc.c +++ b/g10/mainproc.c @@ -481,6 +481,7 @@ proc_pubkey_enc (CTX c, PACKET *pkt) x->keyid[0] = enc->keyid[0]; x->keyid[1] = enc->keyid[1]; x->pubkey_algo = enc->pubkey_algo; + x->result = -1; x->data[0] = x->data[1] = NULL; if (enc->data[0]) { @@ -577,22 +578,21 @@ proc_encrypted (CTX c, PACKET *pkt) { c->dek = xmalloc_secure_clear (sizeof *c->dek); result = get_session_key (c->ctrl, c->pkenc_list, c->dek); - if (result == GPG_ERR_NO_SECKEY) + if (is_status_enabled ()) { - if (is_status_enabled ()) - { - struct pubkey_enc_list *list; + struct pubkey_enc_list *list; - for (list = c->pkenc_list; list; list = list->next) - { - char buf[20]; - snprintf (buf, sizeof buf, "%08lX%08lX", - (ulong)list->keyid[0], (ulong)list->keyid[1]); - write_status_text (STATUS_NO_SECKEY, buf); - } - } + for (list = c->pkenc_list; list; list = list->next) + if (list->result == GPG_ERR_NO_SECKEY) + { + char buf[20]; + snprintf (buf, sizeof buf, "%08lX%08lX", + (ulong)list->keyid[0], (ulong)list->keyid[1]); + write_status_text (STATUS_NO_SECKEY, buf); + } } - else if (result) + + if (result) { log_info (_("public key decryption failed: %s\n"), gpg_strerror (result)); diff --git a/g10/packet.h b/g10/packet.h index 1ec12d64b..78a57bacf 100644 --- a/g10/packet.h +++ b/g10/packet.h @@ -137,6 +137,7 @@ struct pubkey_enc_list struct pubkey_enc_list *next; u32 keyid[2]; int pubkey_algo; + int result; gcry_mpi_t data[PUBKEY_MAX_NENC]; }; diff --git a/g10/pubkey-enc.c b/g10/pubkey-enc.c index ad0a77e59..e0a6e8ae1 100644 --- a/g10/pubkey-enc.c +++ b/g10/pubkey-enc.c @@ -132,6 +132,8 @@ get_session_key (ctrl_t ctrl, struct pubkey_enc_list *list, DEK *dek) if (openpgp_pk_test_algo2 (k->pubkey_algo, PUBKEY_USAGE_ENC)) continue; + k->result = GPG_ERR_NO_SECKEY; + if (sk->pubkey_algo != k->pubkey_algo) continue; @@ -155,6 +157,7 @@ get_session_key (ctrl_t ctrl, struct pubkey_enc_list *list, DEK *dek) rc = get_it (ctrl, k, dek, sk, keyid); if (!rc) { + k->result = 0; if (!opt.quiet && !k->keyid[0] && !k->keyid[1]) log_info (_("okay, we are the anonymous recipient.\n")); search_for_secret_keys = 0; From d93797c8a7892fe26672c551017468e9f8099ef6 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 17 Jan 2019 15:42:33 +0100 Subject: [PATCH 115/235] ssh: Simplify the curve name lookup. * agent/command-ssh.c (struct ssh_key_type_spec): Add field alt_curve_name. (ssh_key_types): Add some alternate curve names. (ssh_identifier_from_curve_name): Lookup also bey alternative names and return the canonical name. (ssh_key_to_blob): Simplify the ECDSA case by using gcry_pk_get_curve instead of the explicit mapping. (ssh_receive_key): Likewise. Use ssh_identifier_from_curve_name to validate the curve name. Remove the reverse mapping because since GnuPG-2.2 Libgcrypt 1.7 is required. (ssh_handler_request_identities): Log an error message. -- This change will make it easier to support other curves, in particular those from tokens. Libgcrypt has a large list of alias names which we now use to to make the mapping more flexible. Signed-off-by: Werner Koch --- agent/command-ssh.c | 138 +++++++++++++++++--------------------------- 1 file changed, 54 insertions(+), 84 deletions(-) diff --git a/agent/command-ssh.c b/agent/command-ssh.c index 8a4150539..ebd28ab5a 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -195,9 +195,14 @@ struct ssh_key_type_spec algorithm. */ ssh_signature_encoder_t signature_encoder; - /* The name of the ECC curve or NULL. */ + /* The name of the ECC curve or NULL for non-ECC algos. This is the + * canonical name for the curve as specified by RFC-5656. */ const char *curve_name; + /* An alias for curve_name or NULL. Actually this is Libcgrypt's + * primary name of the curve. */ + const char *alt_curve_name; + /* The hash algorithm to be used with this key. 0 for using the default. */ int hash_algo; @@ -297,68 +302,71 @@ static const ssh_key_type_spec_t ssh_key_types[] = { "ssh-ed25519", "Ed25519", GCRY_PK_EDDSA, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_eddsa, - "Ed25519", 0, SPEC_FLAG_IS_EdDSA + "Ed25519", NULL, 0, SPEC_FLAG_IS_EdDSA }, { "ssh-rsa", "RSA", GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu", ssh_key_modifier_rsa, ssh_signature_encoder_rsa, - NULL, 0, SPEC_FLAG_USE_PKCS1V2 + NULL, NULL, 0, SPEC_FLAG_USE_PKCS1V2 }, { "ssh-dss", "DSA", GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx", NULL, ssh_signature_encoder_dsa, - NULL, 0, 0 + NULL, NULL, 0, 0 }, { "ecdsa-sha2-nistp256", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, - "nistp256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA + "nistp256", "NIST P-256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA }, { "ecdsa-sha2-nistp384", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, - "nistp384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA + "nistp384", "NIST P-384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA }, { "ecdsa-sha2-nistp521", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, - "nistp521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA + "nistp521", "NIST P-521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA }, { "ssh-ed25519-cert-v01@openssh.com", "Ed25519", GCRY_PK_EDDSA, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_eddsa, - "Ed25519", 0, SPEC_FLAG_IS_EdDSA | SPEC_FLAG_WITH_CERT + "Ed25519", NULL, 0, SPEC_FLAG_IS_EdDSA | SPEC_FLAG_WITH_CERT }, { "ssh-rsa-cert-v01@openssh.com", "RSA", GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu", ssh_key_modifier_rsa, ssh_signature_encoder_rsa, - NULL, 0, SPEC_FLAG_USE_PKCS1V2 | SPEC_FLAG_WITH_CERT + NULL, NULL, 0, SPEC_FLAG_USE_PKCS1V2 | SPEC_FLAG_WITH_CERT }, { "ssh-dss-cert-v01@openssh.com", "DSA", GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx", NULL, ssh_signature_encoder_dsa, - NULL, 0, SPEC_FLAG_WITH_CERT | SPEC_FLAG_WITH_CERT + NULL, NULL, 0, SPEC_FLAG_WITH_CERT | SPEC_FLAG_WITH_CERT }, { "ecdsa-sha2-nistp256-cert-v01@openssh.com", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, - "nistp256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT + "nistp256", "NIST P-256", GCRY_MD_SHA256, + SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT }, { "ecdsa-sha2-nistp384-cert-v01@openssh.com", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, - "nistp384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT + "nistp384", "NIST P-384", GCRY_MD_SHA384, + SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT }, { "ecdsa-sha2-nistp521-cert-v01@openssh.com", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, - "nistp521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT + "nistp521", "NIST P-521", GCRY_MD_SHA512, + SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT } }; @@ -389,16 +397,24 @@ realloc_secure (void *a, size_t n) /* Lookup the ssh-identifier for the ECC curve CURVE_NAME. Returns - NULL if not found. */ + * NULL if not found. If found the ssh indetifier is returned and a + * pointer to the canonical curve name as specified for ssh is stored + * at R_CANON_NAME. */ static const char * -ssh_identifier_from_curve_name (const char *curve_name) +ssh_identifier_from_curve_name (const char *curve_name, + const char **r_canon_name) { int i; for (i = 0; i < DIM (ssh_key_types); i++) if (ssh_key_types[i].curve_name - && !strcmp (ssh_key_types[i].curve_name, curve_name)) - return ssh_key_types[i].ssh_identifier; + && (!strcmp (ssh_key_types[i].curve_name, curve_name) + || (ssh_key_types[i].alt_curve_name + && !strcmp (ssh_key_types[i].alt_curve_name, curve_name)))) + { + *r_canon_name = ssh_key_types[i].curve_name; + return ssh_key_types[i].ssh_identifier; + } return NULL; } @@ -1849,7 +1865,6 @@ ssh_key_to_blob (gcry_sexp_t sexp, int with_secret, gpg_error_t err = 0; gcry_sexp_t value_list = NULL; gcry_sexp_t value_pair = NULL; - char *curve_name = NULL; estream_t stream = NULL; void *blob = NULL; size_t blob_size; @@ -1867,7 +1882,7 @@ ssh_key_to_blob (gcry_sexp_t sexp, int with_secret, goto out; } - /* Get the type of the key extpression. */ + /* Get the type of the key expression. */ data = gcry_sexp_nth_data (sexp, 0, &datalen); if (!data) { @@ -1898,49 +1913,17 @@ ssh_key_to_blob (gcry_sexp_t sexp, int with_secret, /* Write the ssh algorithm identifier. */ if ((key_spec.flags & SPEC_FLAG_IS_ECDSA)) { - /* Parse the "curve" parameter. We currently expect the curve - name for ECC and not the parameters of the curve. This can - easily be changed but then we need to find the curve name - from the parameters using gcry_pk_get_curve. */ - const char *mapped; - const char *sshname; + /* Map the curve name to the ssh name. */ + const char *name, *sshname, *canon_name; - gcry_sexp_release (value_pair); - value_pair = gcry_sexp_find_token (value_list, "curve", 5); - if (!value_pair) - { - err = gpg_error (GPG_ERR_INV_CURVE); - goto out; - } - curve_name = gcry_sexp_nth_string (value_pair, 1); - if (!curve_name) - { - err = gpg_error (GPG_ERR_INV_CURVE); /* (Or out of core.) */ - goto out; - } - - /* Fixme: The mapping should be done by using gcry_pk_get_curve - et al to iterate over all name aliases. */ - if (!strcmp (curve_name, "NIST P-256")) - mapped = "nistp256"; - else if (!strcmp (curve_name, "NIST P-384")) - mapped = "nistp384"; - else if (!strcmp (curve_name, "NIST P-521")) - mapped = "nistp521"; - else - mapped = NULL; - if (mapped) + name = gcry_pk_get_curve (sexp, 0, NULL); + if (!name) { - xfree (curve_name); - curve_name = xtrystrdup (mapped); - if (!curve_name) - { - err = gpg_error_from_syserror (); - goto out; - } + err = gpg_error (GPG_ERR_INV_CURVE); + goto out; } - sshname = ssh_identifier_from_curve_name (curve_name); + sshname = ssh_identifier_from_curve_name (name, &canon_name); if (!sshname) { err = gpg_error (GPG_ERR_UNKNOWN_CURVE); @@ -1949,7 +1932,7 @@ ssh_key_to_blob (gcry_sexp_t sexp, int with_secret, err = stream_write_cstring (stream, sshname); if (err) goto out; - err = stream_write_cstring (stream, curve_name); + err = stream_write_cstring (stream, canon_name); if (err) goto out; } @@ -2022,7 +2005,6 @@ ssh_key_to_blob (gcry_sexp_t sexp, int with_secret, out: gcry_sexp_release (value_list); gcry_sexp_release (value_pair); - xfree (curve_name); es_fclose (stream); es_free (blob); @@ -2081,7 +2063,7 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, ssh_key_type_spec_t spec; gcry_mpi_t *mpi_list = NULL; const char *elems; - char *curve_name = NULL; + const char *curve_name = NULL; err = stream_read_cstring (stream, &key_type); @@ -2204,34 +2186,19 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, * certificate. */ unsigned char *buffer; - const char *mapped; err = stream_read_string (cert? cert : stream, 0, &buffer, NULL); if (err) goto out; - curve_name = buffer; - /* Fixme: Check that curve_name matches the keytype. */ - /* Because Libgcrypt < 1.6 has no support for the "nistpNNN" - curve names, we need to translate them here to Libgcrypt's - native names. */ - if (!strcmp (curve_name, "nistp256")) - mapped = "NIST P-256"; - else if (!strcmp (curve_name, "nistp384")) - mapped = "NIST P-384"; - else if (!strcmp (curve_name, "nistp521")) - mapped = "NIST P-521"; - else - mapped = NULL; - if (mapped) + /* Get the canonical name. Should be the same as the read + * string but we use this mapping to validate that name. */ + if (!ssh_identifier_from_curve_name (buffer, &curve_name)) { - xfree (curve_name); - curve_name = xtrystrdup (mapped); - if (!curve_name) - { - err = gpg_error_from_syserror (); - goto out; - } + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + xfree (buffer); + goto out; } + xfree (buffer); err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list); if (err) @@ -2299,7 +2266,6 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, out: es_fclose (cert); mpint_list_free (mpi_list); - xfree (curve_name); xfree (key_type); xfree (comment); @@ -2647,6 +2613,8 @@ ssh_handler_request_identities (ctrl_t ctrl, continue; err = ssh_send_key_public (key_blobs, key_public, cardsn); + if (err && opt.verbose) + gcry_log_debugsxp ("pubkey", key_public); gcry_sexp_release (key_public); key_public = NULL; xfree (cardsn); @@ -2722,6 +2690,8 @@ ssh_handler_request_identities (ctrl_t ctrl, } else { + log_error ("ssh request identities failed: %s <%s>\n", + gpg_strerror (err), gpg_strsource (err)); ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); } From 70bb5c7931598590b1acfae90bf4657f5911d2d3 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Sun, 20 Jan 2019 11:41:23 +0100 Subject: [PATCH 116/235] scd: One new and one improved 7816 function. * scd/apdu.c (apdu_send_direct): New arg R_SW. * scd/command.c (cmd_apdu): Ditto. * scd/iso7816.c (iso7816_apdu_direct): New arg R_SW. (iso7816_general_authenticate): New. * scd/app-nks.c (get_chv_status, get_nks_version): Pass NULL for new arg. -- iso7816_general_authenticate will be used for the PIV card support. The new arg to iso7816_apdu_direct and apdu_send_direct allows to get the raw status word back without the need to handle an output buffer. Signed-off-by: Werner Koch --- scd/apdu.c | 30 +++++++++++++++-------- scd/apdu.h | 2 +- scd/app-nks.c | 4 +-- scd/command.c | 5 ++-- scd/iso7816.c | 67 ++++++++++++++++++++++++++++++++++++++++++--------- scd/iso7816.h | 9 ++++++- 6 files changed, 90 insertions(+), 27 deletions(-) diff --git a/scd/apdu.c b/scd/apdu.c index f3e2a12e7..816938ac5 100644 --- a/scd/apdu.c +++ b/scd/apdu.c @@ -3063,19 +3063,25 @@ apdu_send_simple (int slot, int extended_mode, /* This is a more generic version of the apdu sending routine. It - takes an already formatted APDU in APDUDATA or length APDUDATALEN - and returns with an APDU including the status word. With - HANDLE_MORE set to true this function will handle the MORE DATA - status and return all APDUs concatenated with one status word at - the end. If EXTENDED_LENGTH is != 0 extended lengths are allowed - with a max. result data length of EXTENDED_LENGTH bytes. The - function does not return a regular status word but 0 on success. - If the slot is locked, the function returns immediately with an - error. */ + * takes an already formatted APDU in APDUDATA or length APDUDATALEN + * and returns with an APDU including the status word. With + * HANDLE_MORE set to true this function will handle the MORE DATA + * status and return all APDUs concatenated with one status word at + * the end. If EXTENDED_LENGTH is != 0 extended lengths are allowed + * with a max. result data length of EXTENDED_LENGTH bytes. The + * function does not return a regular status word but 0 on success. + * If the slot is locked, the function returns immediately with an + * error. + * + * Out of historical reasons the function returns 0 on success and + * outs the status word at the end of the result to be able to get the + * status word in the case of a not provided RETBUF, R_SW can be used + * to store the SW. But note that R_SW qill only be set if the + * function returns 0. */ int apdu_send_direct (int slot, size_t extended_length, const unsigned char *apdudata, size_t apdudatalen, - int handle_more, + int handle_more, unsigned int *r_sw, unsigned char **retbuf, size_t *retbuflen) { #define SHORT_RESULT_BUFFER_SIZE 258 @@ -3282,9 +3288,13 @@ apdu_send_direct (int slot, size_t extended_length, (*retbuf)[(*retbuflen)++] = sw; } + if (r_sw) + *r_sw = sw; + if (DBG_CARD_IO && retbuf) log_printhex (*retbuf, *retbuflen, " dump: "); + return 0; } diff --git a/scd/apdu.h b/scd/apdu.h index 8621ddc41..1392aab71 100644 --- a/scd/apdu.h +++ b/scd/apdu.h @@ -138,7 +138,7 @@ int apdu_send_le (int slot, int extended_mode, unsigned char **retbuf, size_t *retbuflen); int apdu_send_direct (int slot, size_t extended_length, const unsigned char *apdudata, size_t apdudatalen, - int handle_more, + int handle_more, unsigned int *r_sw, unsigned char **retbuf, size_t *retbuflen); const char *apdu_get_reader_name (int slot); diff --git a/scd/app-nks.c b/scd/app-nks.c index 9e720f0b0..801ab904a 100644 --- a/scd/app-nks.c +++ b/scd/app-nks.c @@ -273,7 +273,7 @@ get_chv_status (app_t app, int sigg, int pwid) command[3] = pwid; if (apdu_send_direct (app->slot, 0, (unsigned char *)command, - 4, 0, &result, &resultlen)) + 4, 0, NULL, &result, &resultlen)) rc = -1; /* Error. */ else if (resultlen < 2) rc = -1; /* Error. */ @@ -1300,7 +1300,7 @@ get_nks_version (int slot) int type; if (iso7816_apdu_direct (slot, "\x80\xaa\x06\x00\x00", 5, 0, - &result, &resultlen)) + NULL, &result, &resultlen)) return 2; /* NKS 2 does not support this command. */ /* Example value: 04 11 19 22 21 6A 20 80 03 03 01 01 01 00 00 00 diff --git a/scd/command.c b/scd/command.c index 9df26117c..ea4ccbcda 100644 --- a/scd/command.c +++ b/scd/command.c @@ -333,7 +333,7 @@ static const char hlp_learn[] = "or a \"CANCEL\" to force the function to terminate with a Cancel\n" "error message.\n" "\n" - "With the option --keypairinfo only KEYPARIINFO lstatus lines are\n" + "With the option --keypairinfo only KEYPARIINFO status lines are\n" "returned.\n" "\n" "The response of this command is a list of status lines formatted as\n" @@ -346,6 +346,7 @@ static const char hlp_learn[] = " P15 = PKCS-15 structure used\n" " DINSIG = DIN SIG\n" " OPENPGP = OpenPGP card\n" + " PIV = PIV card\n" " NKS = NetKey card\n" "\n" "are implemented. These strings are aliases for the AID\n" @@ -1663,7 +1664,7 @@ cmd_apdu (assuan_context_t ctx, char *line) rc = apdu_send_direct (app->slot, exlen, apdu, apdulen, handle_more, - &result, &resultlen); + NULL, &result, &resultlen); if (rc) log_error ("apdu_send_direct failed: %s\n", gpg_strerror (rc)); else diff --git a/scd/iso7816.c b/scd/iso7816.c index 43c0bcd8e..c8a2138cb 100644 --- a/scd/iso7816.c +++ b/scd/iso7816.c @@ -50,6 +50,7 @@ #define CMD_PUT_DATA 0xDA #define CMD_MSE 0x22 #define CMD_PSO 0x2A +#define CMD_GENERAL_AUTHENTICATE 0x87 #define CMD_INTERNAL_AUTHENTICATE 0x88 #define CMD_GENERATE_KEYPAIR 0x47 #define CMD_GET_CHALLENGE 0x84 @@ -225,24 +226,28 @@ iso7816_list_directory (int slot, int list_dirs, internally. The return value is a gpg error code (i.e. a mapped status word). This is basically the same as apdu_send_direct but it maps the status word and does not return it in the result - buffer. */ + buffer. However, it R_SW is not NULL the status word is stored + R_SW for closer inspection. */ gpg_error_t iso7816_apdu_direct (int slot, const void *apdudata, size_t apdudatalen, - int handle_more, + int handle_more, unsigned int *r_sw, unsigned char **result, size_t *resultlen) { - int sw; + int sw, sw2; - if (!result || !resultlen) - return gpg_error (GPG_ERR_INV_VALUE); - *result = NULL; - *resultlen = 0; + if (result) + { + *result = NULL; + *resultlen = 0; + } sw = apdu_send_direct (slot, 0, apdudata, apdudatalen, handle_more, - result, resultlen); + &sw2, result, resultlen); if (!sw) { - if (*resultlen < 2) + if (!result) + sw = sw2; + else if (*resultlen < 2) sw = SW_HOST_GENERAL_ERROR; else { @@ -251,13 +256,15 @@ iso7816_apdu_direct (int slot, const void *apdudata, size_t apdudatalen, (*resultlen)--; } } - if (sw != SW_SUCCESS) + if (sw != SW_SUCCESS && result) { /* Make sure that pending buffers are released. */ xfree (*result); *result = NULL; *resultlen = 0; } + if (r_sw) + *r_sw = sw; return map_sw (sw); } @@ -621,7 +628,7 @@ iso7816_decipher (int slot, int extended_mode, } -/* For LE see do_generate_keypair. */ +/* For LE see do_generate_keypair. */ gpg_error_t iso7816_internal_authenticate (int slot, int extended_mode, const unsigned char *data, size_t datalen, @@ -658,6 +665,44 @@ iso7816_internal_authenticate (int slot, int extended_mode, } +/* For LE see do_generate_keypair. */ +gpg_error_t +iso7816_general_authenticate (int slot, int extended_mode, + int algoref, int keyref, + const unsigned char *data, size_t datalen, + int le, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!data || !datalen || !result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + if (!extended_mode) + le = 256; /* Ignore provided Le and use what apdu_send uses. */ + else if (le >= 0 && le < 256) + le = 256; + + sw = apdu_send_le (slot, extended_mode, + 0x00, CMD_GENERAL_AUTHENTICATE, algoref, keyref, + datalen, (const char*)data, + le, + result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + /* LE is the expected return length. This is usually 0 except if extended length mode is used and more than 256 byte will be returned. In that case a value of -1 uses a large default diff --git a/scd/iso7816.h b/scd/iso7816.h index 332fc0e81..4a366e6eb 100644 --- a/scd/iso7816.h +++ b/scd/iso7816.h @@ -63,7 +63,7 @@ gpg_error_t iso7816_list_directory (int slot, int list_dirs, unsigned char **result, size_t *resultlen); gpg_error_t iso7816_apdu_direct (int slot, const void *apdudata, size_t apdudatalen, - int handle_more, + int handle_more, unsigned int *r_sw, unsigned char **result, size_t *resultlen); gpg_error_t iso7816_check_pinpad (int slot, int command, pininfo_t *pininfo); @@ -104,6 +104,13 @@ gpg_error_t iso7816_internal_authenticate (int slot, int extended_mode, const unsigned char *data, size_t datalen, int le, unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_general_authenticate (int slot, int extended_mode, + int algoref, int keyref, + const unsigned char *data, + size_t datalen, + int le, + unsigned char **result, + size_t *resultlen); gpg_error_t iso7816_generate_keypair (int slot, int extended_mode, const char *data, size_t datalen, int le, From ec56996029d95d4bd26e1badfe207232270c6247 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Sun, 20 Jan 2019 11:45:57 +0100 Subject: [PATCH 117/235] scd: Add very basic support for PIV cards. * scd/app-piv.c: New. * scd/Makefile.am (card_apps): Add app-piv.c * scd/app.c (app_new_register): Try to get a Yubikey serial number. Detect the PIV application. (get_supported_applications): Add "piv". -- Right now this allows the use of the authentication key (9A) for SSH authentication. More support will follow soon. Tested with Yubikey-5. --- scd/Makefile.am | 3 +- scd/app-common.h | 3 + scd/app-piv.c | 1238 ++++++++++++++++++++++++++++++++++++++++++++++ scd/app.c | 58 +++ 4 files changed, 1301 insertions(+), 1 deletion(-) create mode 100644 scd/app-piv.c diff --git a/scd/Makefile.am b/scd/Makefile.am index cbd1f9f0f..0cc50dca8 100644 --- a/scd/Makefile.am +++ b/scd/Makefile.am @@ -33,7 +33,8 @@ AM_CFLAGS = $(LIBGCRYPT_CFLAGS) \ $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) -card_apps = app-openpgp.c app-nks.c app-dinsig.c app-p15.c app-geldkarte.c app-sc-hsm.c +card_apps = app-openpgp.c app-piv.c app-nks.c app-dinsig.c app-p15.c \ + app-geldkarte.c app-sc-hsm.c scdaemon_SOURCES = \ scdaemon.c scdaemon.h \ diff --git a/scd/app-common.h b/scd/app-common.h index 38e6cc609..ff583183a 100644 --- a/scd/app-common.h +++ b/scd/app-common.h @@ -206,5 +206,8 @@ gpg_error_t app_select_geldkarte (app_t app); /*-- app-sc-hsm.c --*/ gpg_error_t app_select_sc_hsm (app_t app); +/*-- app-piv.c --*/ +gpg_error_t app_select_piv (app_t app); + #endif /*GNUPG_SCD_APP_COMMON_H*/ diff --git a/scd/app-piv.c b/scd/app-piv.c new file mode 100644 index 000000000..9b4047753 --- /dev/null +++ b/scd/app-piv.c @@ -0,0 +1,1238 @@ +/* app-piv.c - The OpenPGP card application. + * Copyright (C) 2019 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 . + */ + +/* Some notes: + * - Specs for PIV are at http://dx.doi.org/10.6028/NIST.SP.800-73-4 + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "scdaemon.h" + +#include "../common/util.h" +#include "../common/i18n.h" +#include "iso7816.h" +#include "app-common.h" +#include "../common/tlv.h" +#include "../common/host2net.h" +#include "apdu.h" /* We use apdu_send_direct. */ + +#define PIV_ALGORITHM_3DES_ECB_0 0x00 +#define PIV_ALGORITHM_2DES_ECB 0x01 +#define PIV_ALGORITHM_2DES_CBC 0x02 +#define PIV_ALGORITHM_3DES_ECB 0x03 +#define PIV_ALGORITHM_3DES_CBC 0x04 +#define PIV_ALGORITHM_RSA 0x07 +#define PIV_ALGORITHM_AES128_ECB 0x08 +#define PIV_ALGORITHM_AES128_CBC 0x09 +#define PIV_ALGORITHM_AES192_ECB 0x0A +#define PIV_ALGORITHM_AES192_CBC 0x0B +#define PIV_ALGORITHM_AES256_ECB 0x0C +#define PIV_ALGORITHM_AES256_CBC 0x0D +#define PIV_ALGORITHM_ECC_P256 0x11 +#define PIV_ALGORITHM_ECC_P384 0x14 + + + +/* A table describing the DOs of a PIV card. */ +struct data_object_s +{ + unsigned int tag; + unsigned int mandatory:1; + unsigned int acr_contact:2; /* 0=always, 1=VCI, 2=PIN, 3=PINorOCC */ + unsigned int acr_contactless:2; /* 0=always, 1=VCI, 2=VCIandPIN, + 3=VCIand(PINorOCC) */ + unsigned int binary:1; /* Data is not human readable. */ + unsigned int dont_cache:1; /* Data item will not be cached. */ + unsigned int flush_on_error:1; /* Flush cached item on error. */ + unsigned int keypair:1; /* Has a public key for a keypair. */ + char keyref[3]; /* The key reference. */ + char *oidsuffix; /* Suffix of the OID, prefix is "2.16.840.1.101.3.7." */ + char *desc; /* Description of the DO. */ +}; +typedef struct data_object_s *data_object_t; +static struct data_object_s data_objects[] = { + { 0x5FC107, 1, 0,1, 1, 0,0, 0, "", "1.219.0", "Card Capability Container"}, + { 0x5FC102, 1, 0,0, 1, 0,0, 0, "", "2.48.0", "Cardholder Unique Id" }, + { 0x5FC105, 1, 0,1, 1, 0,0, 1, "9A", "2.1.1", "Cert PIV Authentication" }, + { 0x5FC103, 1, 2,2, 1, 0,0, 0, "", "2.96.16", "Cardholder Fingerprints" }, + { 0x5FC106, 1, 0,1, 1, 0,0, 0, "", "2.144.0", "Security Object" }, + { 0x5FC108, 1, 2,2, 1, 0,0, 0, "", "2.96.48", "Cardholder Facial Image" }, + { 0x5FC101, 1, 0,0, 1, 0,0, 1, "9E", "2.5.0", "Cert Card Authentication"}, + { 0x5FC10A, 0, 0,1, 1, 0,0, 1, "9C", "2.1.0", "Cert Digital Signature" }, + { 0x5FC10B, 0, 0,1, 1, 0,0, 1, "9D", "2.1.2", "Cert Key Management" }, + { 0x5FC109, 0, 3,3, 0, 0,0, 0, "", "2.48.1", "Printed Information" }, + { 0x7E, 0, 0,0, 1, 0,0, 0, "", "2.96.80", "Discovery Object" }, + { 0x5FC10C, 0, 0,1, 1, 0,0, 0, "", "2.96.96", "Key History Object" }, + { 0x5FC10D, 0, 0,1, 1, 0,0, 0, "82", "2.16.1", "Retired Cert Key Mgm 1" }, + { 0x5FC10E, 0, 0,1, 1, 0,0, 0, "83", "2.16.2", "Retired Cert Key Mgm 2" }, + { 0x5FC10F, 0, 0,1, 1, 0,0, 0, "84", "2.16.3", "Retired Cert Key Mgm 3" }, + { 0x5FC110, 0, 0,1, 1, 0,0, 0, "85", "2.16.4", "Retired Cert Key Mgm 4" }, + { 0x5FC111, 0, 0,1, 1, 0,0, 0, "86", "2.16.5", "Retired Cert Key Mgm 5" }, + { 0x5FC112, 0, 0,1, 1, 0,0, 0, "87", "2.16.6", "Retired Cert Key Mgm 6" }, + { 0x5FC113, 0, 0,1, 1, 0,0, 0, "88", "2.16.7", "Retired Cert Key Mgm 7" }, + { 0x5FC114, 0, 0,1, 1, 0,0, 0, "89", "2.16.8", "Retired Cert Key Mgm 8" }, + { 0x5FC115, 0, 0,1, 1, 0,0, 0, "8A", "2.16.9", "Retired Cert Key Mgm 9" }, + { 0x5FC116, 0, 0,1, 1, 0,0, 0, "8B", "2.16.10", "Retired Cert Key Mgm 10" }, + { 0x5FC117, 0, 0,1, 1, 0,0, 0, "8C", "2.16.11", "Retired Cert Key Mgm 11" }, + { 0x5FC118, 0, 0,1, 1, 0,0, 0, "8D", "2.16.12", "Retired Cert Key Mgm 12" }, + { 0x5FC119, 0, 0,1, 1, 0,0, 0, "8E", "2.16.13", "Retired Cert Key Mgm 13" }, + { 0x5FC11A, 0, 0,1, 1, 0,0, 0, "8F", "2.16.14", "Retired Cert Key Mgm 14" }, + { 0x5FC11B, 0, 0,1, 1, 0,0, 0, "90", "2.16.15", "Retired Cert Key Mgm 15" }, + { 0x5FC11C, 0, 0,1, 1, 0,0, 0, "91", "2.16.16", "Retired Cert Key Mgm 16" }, + { 0x5FC11D, 0, 0,1, 1, 0,0, 0, "92", "2.16.17", "Retired Cert Key Mgm 17" }, + { 0x5FC11E, 0, 0,1, 1, 0,0, 0, "93", "2.16.18", "Retired Cert Key Mgm 18" }, + { 0x5FC11F, 0, 0,1, 1, 0,0, 0, "94", "2.16.19", "Retired Cert Key Mgm 19" }, + { 0x5FC120, 0, 0,1, 1, 0,0, 0, "95", "2.16.20", "Retired Cert Key Mgm 20" }, + { 0x5FC121, 0, 2,2, 1, 0,0, 0, "", "2.16.21", "Cardholder Iris Images" }, + { 0x7F61, 0, 0,0, 1, 0,0, 0, "", "2.16.22", "BIT Group Template" }, + { 0x5FC122, 0, 0,0, 1, 0,0, 0, "", "2.16.23", "SM Cert Signer" }, + { 0x5FC123, 0, 3,3, 1, 0,0, 0, "", "2.16.24", "Pairing Code Ref Data" }, + { 0 } + /* Other key reference values without a tag: + * "00" Global PIN (not cleared by application switching) + * "04" PIV Secure Messaging Key + * "80" PIV Application PIN + * "81" PIN Unblocking Key + * "96" Primary Finger OCC + * "97" Secondary Finger OCC + * "98" Pairing Code + * "9B" PIV Card Application Administration Key + */ +}; + + +/* One cache item for DOs. */ +struct cache_s { + struct cache_s *next; + int tag; + size_t length; + unsigned char data[1]; +}; + + +/* Object with application specific data. */ +struct app_local_s { + /* A linked list with cached DOs. */ + struct cache_s *cache; + + /* Various flags. */ + struct + { + unsigned int dummy:1; + } flags; + +}; + + +/***** Local prototypes *****/ +static gpg_error_t get_keygrip_by_tag (app_t app, unsigned int tag, + char **r_keygripstr); + + + + + +/* Deconstructor. */ +static void +do_deinit (app_t app) +{ + if (app && app->app_local) + { + struct cache_s *c, *c2; + + for (c = app->app_local->cache; c; c = c2) + { + c2 = c->next; + xfree (c); + } + + xfree (app->app_local); + app->app_local = NULL; + } +} + + +/* Wrapper around iso7816_get_data which first tries to get the data + * from the cache. With GET_IMMEDIATE passed as true, the cache is + * bypassed. The tag-53 container is also removed. */ +static gpg_error_t +get_cached_data (app_t app, int tag, + unsigned char **result, size_t *resultlen, + int get_immediate) +{ + gpg_error_t err; + int i; + unsigned char *p; + const unsigned char *s; + size_t len, n; + struct cache_s *c; + + *result = NULL; + *resultlen = 0; + + if (!get_immediate) + { + for (c=app->app_local->cache; c; c = c->next) + if (c->tag == tag) + { + if(c->length) + { + p = xtrymalloc (c->length); + if (!p) + return gpg_error_from_syserror (); + memcpy (p, c->data, c->length); + *result = p; + } + + *resultlen = c->length; + + return 0; + } + } + + err = iso7816_get_data_odd (app->slot, 0, tag, &p, &len); + if (err) + return err; + + /* Unless the Discovery Object or the BIT Group Template is + * requested, remove the outer container. + * (SP800-73.4 Part 2, section 3.1.2) */ + if (tag == 0x7E || tag == 0x7F61) + ; + else if (len && *p == 0x53 && (s = find_tlv (p, len, 0x53, &n))) + { + memmove (p, s, n); + len = n; + } + + if (len) + *result = p; + *resultlen = len; + + /* Check whether we should cache this object. */ + if (get_immediate) + return 0; + + for (i=0; data_objects[i].tag; i++) + if (data_objects[i].tag == tag) + { + if (data_objects[i].dont_cache) + return 0; + break; + } + + /* Okay, cache it. */ + for (c=app->app_local->cache; c; c = c->next) + log_assert (c->tag != tag); + + c = xtrymalloc (sizeof *c + len); + if (c) + { + if (len) + memcpy (c->data, p, len); + else + xfree (p); + c->length = len; + c->tag = tag; + c->next = app->app_local->cache; + app->app_local->cache = c; + } + + return 0; +} + + +/* Get the DO identified by TAG from the card in SLOT and return a + * buffer with its content in RESULT and NBYTES. The return value is + * NULL if not found or a pointer which must be used to release the + * buffer holding value. */ +static void * +get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes, + int *r_err) +{ + gpg_error_t err; + int i; + unsigned char *buffer; + size_t buflen; + unsigned char *value; + size_t valuelen; + gpg_error_t dummyerr; + + if (!r_err) + r_err = &dummyerr; + + *result = NULL; + *nbytes = 0; + *r_err = 0; + for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++) + ; + + value = NULL; + err = gpg_error (GPG_ERR_ENOENT); + + if (!value) /* Not in a constructed DO, try simple. */ + { + err = get_cached_data (app, tag, &buffer, &buflen, + data_objects[i].dont_cache); + if (!err) + { + value = buffer; + valuelen = buflen; + } + } + + if (!err) + { + *nbytes = valuelen; + *result = value; + return buffer; + } + + *r_err = err; + return NULL; +} + + +static void +dump_all_do (int slot) +{ + gpg_error_t err; + int i; + unsigned char *buffer; + size_t buflen; + + for (i=0; data_objects[i].tag; i++) + { + /* We don't try extended length APDU because such large DO would + be pretty useless in a log file. */ + err = iso7816_get_data_odd (slot, 0, data_objects[i].tag, + &buffer, &buflen); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_ENOENT + && !data_objects[i].mandatory) + ; + else + log_info ("DO '%s' not available: %s\n", + data_objects[i].desc, gpg_strerror (err)); + } + else + { + if (data_objects[i].binary) + { + log_info ("DO '%s': ", data_objects[i].desc); + if (buflen > 16 && opt.verbose < 2) + { + log_printhex (buffer, 16, NULL); + log_printf ("[...]\n"); + } + else + log_printhex (buffer, buflen, ""); + } + else + log_info ("DO '%s': '%.*s'\n", + data_objects[i].desc, + (int)buflen, buffer); + + } + xfree (buffer); buffer = NULL; + } +} + + +/* Return an allocated string with the serial number in a format to be + * show to the user. With FAILMODE is true return NULL if such an + * abbreviated S/N is not available, else return the full serial + * number as a hex string. May return NULL on malloc problem. */ +static char * +get_dispserialno (app_t app, int failmode) +{ + char *result; + + if (app->serialno && app->serialnolen == 3+1+4 + && !memcmp (app->serialno, "\xff\x02\x00", 3)) + { + /* This is a 4 byte S/N of a Yubikey which seems to be printed + * on the token in decimal. Maybe they will print larger S/N + * also in decimal but we can't be sure, thus do it only for + * these 32 bit numbers. */ + unsigned long sn; + sn = app->serialno[4] * 16777216; + sn += app->serialno[5] * 65536; + sn += app->serialno[6] * 256; + sn += app->serialno[7]; + result = xtryasprintf ("yk-%lu", sn); + } + else if (failmode) + result = NULL; /* No Abbreviated S/N. */ + else + result = app_get_serialno (app); + + return result; +} + + +/* Implementation of the GETATTR command. This is similar to the + * LEARN command but returns only one value via status lines. */ +static gpg_error_t +do_getattr (app_t app, ctrl_t ctrl, const char *name) +{ + static struct { + const char *name; + int tag; + int special; + } table[] = { + { "SERIALNO", 0x0000, -1 }, + { "$AUTHKEYID", 0x0000, -2 }, /* Default key for ssh. */ + { "$DISPSERIALNO",0x0000, -3 } + }; + gpg_error_t err = 0; + int idx; + void *relptr; + unsigned char *value; + size_t valuelen; + + for (idx=0; (idx < DIM (table) + && ascii_strcasecmp (table[idx].name, name)); idx++) + ; + if (!(idx < DIM (table))) + err = gpg_error (GPG_ERR_INV_NAME); + else if (table[idx].special == -1) + { + char *serial = app_get_serialno (app); + + if (serial) + { + send_status_direct (ctrl, "SERIALNO", serial); + xfree (serial); + } + } + else if (table[idx].special == -2) + { + char const tmp[] = "PIV.9A"; /* Cert PIV Authenticate. */ + send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); + } + else if (table[idx].special == -3) + { + char *tmp = get_dispserialno (app, 1); + + if (tmp) + { + send_status_info (ctrl, table[idx].name, + tmp, strlen (tmp), + NULL, (size_t)0); + xfree (tmp); + } + else + err = gpg_error (GPG_ERR_INV_NAME); /* No Abbreviated S/N. */ + } + else + { + relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err); + if (relptr) + { + send_status_info (ctrl, table[idx].name, value, valuelen, NULL, 0); + xfree (relptr); + } + } + + return err; +} + + +/* Send the KEYPAIRINFO back. DOBJ describes the data object carrying + * the key. This is used by the LEARN command. */ +static gpg_error_t +send_keypair_and_cert_info (app_t app, ctrl_t ctrl, data_object_t dobj, + int only_keypair) +{ + gpg_error_t err = 0; + char *keygripstr = NULL; + char idbuf[50]; + + err = get_keygrip_by_tag (app, dobj->tag, &keygripstr); + if (err) + goto leave; + + snprintf (idbuf, sizeof idbuf, "PIV.%s", dobj->keyref); + send_status_info (ctrl, "KEYPAIRINFO", + keygripstr, strlen (keygripstr), + idbuf, strlen (idbuf), + NULL, (size_t)0); + if (!only_keypair) + { + /* All certificates are of type 100 (Regular X.509 Cert). */ + send_status_info (ctrl, "CERTINFO", + "100", 3, + idbuf, strlen (idbuf), + NULL, (size_t)0); + } + + leave: + xfree (keygripstr); + return err; +} + + +/* Handle the LEARN command for OpenPGP. */ +static gpg_error_t +do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) +{ + int i; + + (void)flags; + + for (i=0; data_objects[i].tag; i++) + if (data_objects[i].keypair) + send_keypair_and_cert_info (app, ctrl, data_objects + i, !!(flags & 1)); + + return 0; +} + + +/* Core of do-readcert which fetches the certificate based on the + * given tag and returns it in a freshly allocated buffer stored at + * R_CERT and the length of the certificate stored at R_CERTLEN. */ +static gpg_error_t +readcert_by_tag (app_t app, unsigned int tag, + unsigned char **r_cert, size_t *r_certlen) +{ + gpg_error_t err; + unsigned char *buffer; + size_t buflen; + void *relptr; + const unsigned char *s; + size_t n; + + *r_cert = NULL; + *r_certlen = 0; + + relptr = get_one_do (app, tag, &buffer, &buflen, NULL); + if (!relptr || !buflen) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + + s = find_tlv (buffer, buflen, 0x71, &n); + if (!s || n != 1) + { + log_error ("piv: no or invalid CertInfo in 0x%X\n", tag); + err = gpg_error (GPG_ERR_INV_CERT_OBJ); + goto leave; + } + if (*s == 0x01) + { + log_error ("piv: gzip compression not yet supported (tag 0x%X)\n", tag); + err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); + goto leave; + } + if (*s) + { + log_error ("piv: invalid CertInfo 0x%02x in 0x%X\n", *s, tag); + err = gpg_error (GPG_ERR_INV_CERT_OBJ); + goto leave; + } + + /* Note: We don't check that the LRC octet has a length of zero as + * required by the specs. */ + + /* Get the cert from the container. */ + s = find_tlv (buffer, buflen, 0x70, &n); + if (!s || !n) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + + if (!(*r_cert = xtrymalloc (n))) + { + err = gpg_error_from_syserror (); + goto leave; + } + + memcpy (*r_cert, s, n); + *r_certlen = n; + err = 0; + + leave: + xfree (relptr); + return err; +} + + +/* Get the keygrip of a key from the certificate stored at TAG. + * Caller must free the string at R_KEYGRIPSTR. */ +static gpg_error_t +get_keygrip_by_tag (app_t app, unsigned int tag, char **r_keygripstr) +{ + gpg_error_t err; + unsigned char *certbuf = NULL; + size_t certbuflen; + ksba_cert_t cert = NULL; + + *r_keygripstr = xtrymalloc (40+1); + if (!r_keygripstr) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* We need to get the public key from the certificate. */ + err = readcert_by_tag (app, tag, &certbuf, &certbuflen); + if (err) + goto leave; + + /* Compute the keygrip. */ + err = ksba_cert_new (&cert); + if (err) + goto leave; + err = ksba_cert_init_from_mem (cert, certbuf, certbuflen); + if (err) + goto leave; + err = app_help_get_keygrip_string (cert, *r_keygripstr); + + + leave: + ksba_cert_release (cert); + xfree (certbuf); + if (err) + { + xfree (*r_keygripstr); + *r_keygripstr = NULL; + } + return err; +} + + +/* Locate the data object from the given KEYREF. The KEYREF may also + * be the corresponding OID of the key object. Returns the data + * object or NULL if not found. */ +static data_object_t +find_dobj_by_keyref (app_t app, const char *keyref) +{ + int i; + + (void)app; + + if (!ascii_strncasecmp (keyref, "PIV.", 4)) + { + keyref += 4; + for (i=0; data_objects[i].tag; i++) + if (*data_objects[i].keyref + && !ascii_strcasecmp (keyref, data_objects[i].keyref)) + { + return data_objects + i; + } + } + else if (!strncmp (keyref, "2.16.840.1.101.3.7.", 19)) + { + keyref += 19; + for (i=0; data_objects[i].tag; i++) + if (*data_objects[i].keyref + && !strcmp (keyref, data_objects[i].oidsuffix)) + { + return data_objects + i; + } + } + + return NULL; +} + + +/* Read a certificate from the card and returned in a freshly + * allocated buffer stored at R_CERT and the length of the certificate + * stored at R_CERTLEN. CERTID is either the OID of the cert's + * container or of the form "PIV." */ +static gpg_error_t +do_readcert (app_t app, const char *certid, + unsigned char **r_cert, size_t *r_certlen) +{ + data_object_t dobj; + + *r_cert = NULL; + *r_certlen = 0; + + dobj = find_dobj_by_keyref (app, certid); + if (!dobj) + return gpg_error (GPG_ERR_INV_ID); + + return readcert_by_tag (app, dobj->tag, r_cert, r_certlen); +} + + +/* Given a data object DOBJ return the corresponding PIV algorithm and + * store it at R_ALGO. The algorithm is taken from the corresponding + * certificate or from a cache. */ +static gpg_error_t +get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_algo) +{ + gpg_error_t err; + unsigned char *certbuf = NULL; + size_t certbuflen; + ksba_cert_t cert = NULL; + ksba_sexp_t k_pkey = NULL; + gcry_sexp_t s_pkey = NULL; + gcry_sexp_t l1 = NULL; + char *algoname = NULL; + int algo; + size_t n; + const char *curve_name; + + *r_algo = 0; + + err = readcert_by_tag (app, dobj->tag, &certbuf, &certbuflen); + if (err) + goto leave; + + err = ksba_cert_new (&cert); + if (err) + goto leave; + + err = ksba_cert_init_from_mem (cert, certbuf, certbuflen); + if (err) + { + log_error ("piv: failed to parse the certificate %s: %s\n", + dobj->keyref, gpg_strerror (err)); + goto leave; + } + xfree (certbuf); + certbuf = NULL; + + k_pkey = ksba_cert_get_public_key (cert); + if (!k_pkey) + { + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + n = gcry_sexp_canon_len (k_pkey, 0, NULL, NULL); + err = gcry_sexp_new (&s_pkey, k_pkey, n, 0); + if (err) + goto leave; + + l1 = gcry_sexp_find_token (s_pkey, "public-key", 0); + if (!l1) + { + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + + { + gcry_sexp_t l_tmp = gcry_sexp_cadr (l1); + gcry_sexp_release (l1); + l1 = l_tmp; + } + algoname = gcry_sexp_nth_string (l1, 0); + if (!algoname) + { + err = gpg_error_from_syserror (); + goto leave; + } + + algo = gcry_pk_map_name (algoname); + switch (algo) + { + case GCRY_PK_RSA: + algo = PIV_ALGORITHM_RSA; + break; + + case GCRY_PK_ECC: + case GCRY_PK_ECDSA: + case GCRY_PK_ECDH: + curve_name = gcry_pk_get_curve (s_pkey, 0, NULL); + if (curve_name && !strcmp (curve_name, "NIST P-256")) + algo = PIV_ALGORITHM_ECC_P256; + else if (curve_name && !strcmp (curve_name, "NIST P-384")) + algo = PIV_ALGORITHM_ECC_P384; + else + { + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + log_error ("piv: certificate %s, curve '%s': %s\n", + dobj->keyref, curve_name, gpg_strerror (err)); + goto leave; + } + break; + + default: + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + log_error ("piv: certificate %s, pubkey algo '%s': %s\n", + dobj->keyref, algoname, gpg_strerror (err)); + goto leave; + } + *r_algo = algo; + + leave: + gcry_free (algoname); + gcry_sexp_release (l1); + gcry_sexp_release (s_pkey); + ksba_free (k_pkey); + xfree (certbuf); + return err; +} + + +/* Return an allocated string to be used as prompt. Returns NULL on + * malloc error. */ +static char * +make_prompt (app_t app, int remaining, const char *firstline) +{ + char *serial, *tmpbuf, *result; + + serial = get_dispserialno (app, 0); + if (!serial) + return NULL; + + /* TRANSLATORS: Put a \x1f right before a colon. This can be + * used by pinentry to nicely align the names and values. Keep + * the %s at the start and end of the string. */ + result = xtryasprintf (_("%s" + "Number\x1f: %s%%0A" + "Holder\x1f: %s" + "%s"), + "\x1e", + serial, + "Unknown", /* Fixme */ + ""); + xfree (serial); + + /* Append a "remaining attempts" info if needed. */ + if (remaining != -1 && remaining < 3) + { + char *rembuf; + + /* TRANSLATORS: This is the number of remaining attempts to + * enter a PIN. Use %%0A (double-percent,0A) for a linefeed. */ + rembuf = xtryasprintf (_("Remaining attempts: %d"), remaining); + if (rembuf) + { + tmpbuf = strconcat (firstline, "%0A%0A", result, + "%0A%0A", rembuf, NULL); + xfree (rembuf); + } + else + tmpbuf = NULL; + xfree (result); + result = tmpbuf; + } + else + { + tmpbuf = strconcat (firstline, "%0A%0A", result, NULL); + xfree (result); + result = tmpbuf; + } + + return result; +} + + + +/* Verify the Application PIN for use with data object DOBJ. */ +static gpg_error_t +verify_pin (app_t app, data_object_t dobj, + gpg_error_t (*pincb)(void*,const char *,char **), void *pincb_arg) +{ + gpg_error_t err; + unsigned char apdu[4]; + unsigned int sw; + int remaining; + char *prompt; + char *pinvalue = NULL; + unsigned int pinlen; + char pinbuffer[8]; + + /* First check whether a verify is at all needed. This is done with + * P1 being 0 and no Lc and command data send. */ + apdu[0] = 0x00; + apdu[1] = ISO7816_VERIFY; + apdu[2] = 0x00; + apdu[3] = 0x80; + if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL)) + { + /* No need to verification. */ + return 0; /* All fine. */ + } + if ((sw & 0xfff0) == 0x63C0) + remaining = (sw & 0x000f); /* PIN has REMAINING tries left. */ + else + remaining = -1; + + if (remaining != -1) + log_debug ("piv: PIN for %s has %d attempts left\n", + dobj->keyref, remaining); + + /* Ask for the PIN. */ + prompt = make_prompt (app, remaining, _("||Please enter your PIV PIN")); + err = pincb (pincb_arg, prompt, &pinvalue); + xfree (prompt); + prompt = NULL; + if (err) + { + log_info (_("PIN callback returned error: %s\n"), gpg_strerror (err)); + return err; + } + + pinlen = pinvalue? strlen (pinvalue) : 0; + if (pinlen < 6) + { + log_error (_("PIN for is too short;" + " minimum length is %d\n"), 6); + if (pinvalue) + wipememory (pinvalue, pinlen); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + if (pinlen > sizeof pinbuffer) + { + log_error (_("PIN for is too long;" + " maximum length is %d\n"), (int)sizeof pinbuffer); + wipememory (pinvalue, pinlen); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + if (strspn (pinvalue, "0123456789") != pinlen) + { + log_error (_("PIN has invalid characters; only digits are allowed\n")); + wipememory (pinvalue, pinlen); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + memcpy (pinbuffer, pinvalue, pinlen); + memset (pinbuffer + pinlen, 0xff, sizeof(pinbuffer) - pinlen); + wipememory (pinvalue, pinlen); + xfree (pinvalue); + + err = iso7816_verify (app->slot, 0x80, + pinbuffer, sizeof pinbuffer); + wipememory (pinbuffer, sizeof pinbuffer); + if (err) + log_error ("PIN verification failed: %s\n", gpg_strerror (err)); + + return err; +} + + +/* Compute a digital signature using the GENERAL AUTHENTICATE command + * on INDATA which is expected to be the raw message digest. The + * KEYIDSTR has the key reference or its OID (e.g. "PIV.9A"). The + * result is stored at (R_OUTDATA,R_OUTDATALEN); on error (NULL,0) is + * stored there and an error code returned. For ECDSA the result is + * the simple concatenation of R and S without any DER encoding. R + * and S are left extended with zeroes to make sure they have an equal + * length. + */ +static gpg_error_t +do_auth (app_t app, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata_arg, size_t indatalen, + unsigned char **r_outdata, size_t *r_outdatalen) +{ + const unsigned char *indata = indata_arg; + gpg_error_t err; + data_object_t dobj; + unsigned char tmpl[2+2+2+128]; + size_t tmpllen; + unsigned char *outdata = NULL; + size_t outdatalen; + const unsigned char *s; + size_t n; + int keyref, algo; + + if (!keyidstr || !*keyidstr) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + + /* Fixme: Shall we support the KEYID/FINGERPRINT syntax? Does it + * make sense for X.509 certs? */ + + dobj = find_dobj_by_keyref (app, keyidstr); + if (!dobj) + { + err = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + keyref = xtoi_2 (dobj->keyref); + + err = get_key_algorithm_by_dobj (app, dobj, &algo); + if (err) + goto leave; + + /* We need to remove the ASN.1 prefix from INDATA. We use TEMPL as + * a temporary buffer for the OID. */ + if (algo == PIV_ALGORITHM_ECC_P256) + { + tmpllen = sizeof tmpl; + err = gcry_md_get_asnoid (GCRY_MD_SHA256, &tmpl, &tmpllen); + if (err) + { + err = gpg_error (GPG_ERR_INTERNAL); + log_debug ("piv: no OID for hash algo %d\n", GCRY_MD_SHA256); + goto leave; + } + if (indatalen != tmpllen + 32 || memcmp (indata, tmpl, tmpllen)) + { + err = GPG_ERR_INV_VALUE; + log_error ("piv: bad formatted input for ECC-P256 auth\n"); + goto leave; + } + indata +=tmpllen; + indatalen -= tmpllen; + } + else if (algo == PIV_ALGORITHM_ECC_P384) + { + tmpllen = sizeof tmpl; + err = gcry_md_get_asnoid (GCRY_MD_SHA384, &tmpl, &tmpllen); + if (err) + { + err = gpg_error (GPG_ERR_INTERNAL); + log_debug ("piv: no OID for hash algo %d\n", GCRY_MD_SHA384); + goto leave; + } + if (indatalen != tmpllen + 48 || memcmp (indata, tmpl, tmpllen)) + { + err = GPG_ERR_INV_VALUE; + log_error ("piv: bad formatted input for ECC-P384 auth\n"); + goto leave; + } + indata += tmpllen; + indatalen -= tmpllen; + } + else if (algo == PIV_ALGORITHM_RSA) + { + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + log_error ("piv: FIXME: implement RSA authentication\n"); + goto leave; + } + else + { + err = gpg_error (GPG_ERR_INTERNAL); + log_debug ("piv: unknown PIV algo %d from helper function\n", algo); + goto leave; + } + + /* Because we don't have a dynamic template builder we make sure + * that we can encode all lengths in one octet. FIXME: Use add_tls + * from app-openpgp as a base for an strconcat like function. */ + if (indatalen >= 100) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + + /* Now verify the PIN. */ + err = verify_pin (app, dobj, pincb, pincb_arg); + if (err) + return err; + + /* Build the Dynamic Authentication Template. */ + tmpl[0] = 0x7c; + tmpl[1] = indatalen + 4; + tmpl[2] = 0x82; /* Response. */ + tmpl[3] = 0; /* Must be 0 to get the tag in the answer. */ + tmpl[4] = 0x81; /* Challenge. */ + tmpl[5] = indatalen; + memcpy (tmpl+6, indata, indatalen); + tmpllen = indatalen + 6; + + /* Note: the -1 requests command chaining. */ + err = iso7816_general_authenticate (app->slot, -1, + algo, keyref, + tmpl, (int)tmpllen, 0, + &outdata, &outdatalen); + if (err) + goto leave; + + /* Parse the response. */ + if (outdatalen && *outdata == 0x7c + && (s = find_tlv (outdata, outdatalen, 0x82, &n))) + { + const unsigned char *rval, *sval; + size_t rlen, rlenx, slen, slenx, resultlen; + char *result; + /* The result of an ECDSA signature is + * SEQUENCE { r INTEGER, s INTEGER } + * We re-pack that by concatenating R and S and making sure that + * both have the same length. We simplify parsing by using + * find_tlv and not a proper DER parser. */ + s = find_tlv (s, n, 0x30, &n); + if (!s) + goto bad_der; + rval = find_tlv (s, n, 0x02, &rlen); + if (!rval) + goto bad_der; + log_assert (n >= (rval-s)+rlen); + sval = find_tlv (rval+rlen, n-((rval-s)+rlen), 0x02, &slen); + if (!rval) + goto bad_der; + rlenx = slenx = 0; + if (rlen > slen) + slenx = rlen - slen; + else if (slen > rlen) + rlenx = slen - rlen; + + resultlen = rlen + rlenx + slen + slenx; + result = xtrycalloc (1, resultlen); + if (!result) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (result + rlenx, rval, rlen); + memcpy (result + rlenx + rlen + slenx, sval, slen); + xfree (outdata); + outdata = result; + outdatalen = resultlen; + } + else + { + bad_der: + err = gpg_error (GPG_ERR_CARD); + log_error ("piv: response does not contain a proper result\n"); + goto leave; + } + + leave: + if (err) + { + xfree (outdata); + *r_outdata = NULL; + *r_outdatalen = 0; + } + else + { + *r_outdata = outdata; + *r_outdatalen = outdatalen; + } + return err; +} + + +/* Select the PIV application on the card in SLOT. This function must + * be used before any other PIV application functions. */ +gpg_error_t +app_select_piv (app_t app) +{ + static char const aid[] = { 0xA0, 0x00, 0x00, 0x03, 0x08, /* RID=NIST */ + 0x00, 0x00, 0x10, 0x00 /* PIX=PIV */ }; + int slot = app->slot; + gpg_error_t err; + unsigned char *apt = NULL; + size_t aptlen; + const unsigned char *s; + size_t n; + + /* Note that we select using the AID without the 2 octet version + * number. This allows for better reporting of future specs. We + * need to use the use-zero-for-P2-flag. */ + err = iso7816_select_application_ext (slot, aid, sizeof aid, 0x0001, + &apt, &aptlen); + if (err) + goto leave; + + app->apptype = "PIV"; + app->did_chv1 = 0; + app->did_chv2 = 0; + app->did_chv3 = 0; + app->app_local = NULL; + + /* Check the Application Property Template. */ + if (opt.verbose) + { + /* We use a separate log_info to avoid the "DBG:" prefix. */ + log_info ("piv: APT="); + log_printhex (apt, aptlen, ""); + } + + s = find_tlv (apt, aptlen, 0x4F, &n); + if (!s || n != 6 || memcmp (s, aid+5, 4)) + { + /* The PIX does not match. */ + log_error ("piv: missing or invalid DO 0x4F in APT\n"); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + if (s[4] != 1 || s[5] != 0) + { + log_error ("piv: unknown PIV version %u.%u\n", s[4], s[5]); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + app->card_version = ((s[4] << 8) | s[5]); + + s = find_tlv (apt, aptlen, 0x79, &n); + if (!s || n < 7) + { + log_error ("piv: missing or invalid DO 0x79 in APT\n"); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + s = find_tlv (s, n, 0x4F, &n); + if (!s || n != 5 || memcmp (s, aid, 5)) + { + /* The RID does not match. */ + log_error ("piv: missing or invalid DO 0x79.4F in APT\n"); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + + app->app_local = xtrycalloc (1, sizeof *app->app_local); + if (!app->app_local) + { + err = gpg_error_from_syserror (); + goto leave; + } + + + /* FIXME: Parse the optional and conditional DOs in the APT. */ + + if (opt.verbose) + dump_all_do (slot); + + app->fnc.deinit = do_deinit; + app->fnc.learn_status = do_learn_status; + app->fnc.readcert = do_readcert; + app->fnc.readkey = NULL; + app->fnc.getattr = do_getattr; + /* app->fnc.setattr = do_setattr; */ + /* app->fnc.writecert = do_writecert; */ + /* app->fnc.writekey = do_writekey; */ + /* app->fnc.genkey = do_genkey; */ + /* app->fnc.sign = do_sign; */ + app->fnc.auth = do_auth; + /* app->fnc.decipher = do_decipher; */ + /* app->fnc.change_pin = do_change_pin; */ + /* app->fnc.check_pin = do_check_pin; */ + + +leave: + xfree (apt); + if (err) + do_deinit (app); + return err; +} diff --git a/scd/app.c b/scd/app.c index d16300efa..800c954b4 100644 --- a/scd/app.c +++ b/scd/app.c @@ -211,6 +211,60 @@ app_new_register (int slot, ctrl_t ctrl, const char *name, if (!want_undefined) { err = iso7816_select_file (slot, 0x3F00, 1); + if (gpg_err_code (err) == GPG_ERR_CARD) + { + /* Might be SW==0x7D00. Let's test whether it is a Yubikey + * by selecting its manager application and then reading the + * config. */ + static char const yk_aid[] = + { 0xA0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17 }; /*MGR*/ + unsigned char *buf; + size_t buflen; + const unsigned char *s0, *s1; + size_t n; + + if (!iso7816_select_application (slot, yk_aid, sizeof yk_aid, + 0x0001) + && !iso7816_apdu_direct (slot, "\x00\x1d\x00\x00\x00", 5, 0, + NULL, &buf, &buflen)) + { + if (opt.verbose) + { + log_info ("Yubico: config="); + log_printhex (buf, buflen, ""); + } + + /* We skip the first byte which seems to be the total + * length of the config data. */ + if (buflen > 1) + { + s0 = find_tlv (buf+1, buflen-1, 0x04, &n); /* Form factor */ + if (s0 && n == 1) + { + s1 = find_tlv (buf+1, buflen-1, 0x02, &n); /* Serial */ + if (s1 && n >= 4) + { + app->serialno = xtrymalloc (3 + 1 + n); + if (app->serialno) + { + app->serialnolen = 3 + 1 + n; + app->serialno[0] = 0xff; + app->serialno[1] = 0x02; + app->serialno[2] = 0x0; + app->serialno[3] = *s0; + memcpy (app->serialno + 4, s1, n); + /* Note that we do not clear the error + * so that no further serial number + * testing is done. After all we just + * set the serial number. */ + } + } + } + } + xfree (buf); + } + } + if (!err) err = iso7816_select_file (slot, 0x2F02, 0); if (!err) @@ -270,6 +324,8 @@ app_new_register (int slot, ctrl_t ctrl, const char *name, if (err && is_app_allowed ("openpgp") && (!name || !strcmp (name, "openpgp"))) err = app_select_openpgp (app); + if (err && is_app_allowed ("piv") && (!name || !strcmp (name, "piv"))) + err = app_select_piv (app); if (err && is_app_allowed ("nks") && (!name || !strcmp (name, "nks"))) err = app_select_nks (app); if (err && is_app_allowed ("p15") && (!name || !strcmp (name, "p15"))) @@ -409,6 +465,7 @@ get_supported_applications (void) { const char *list[] = { "openpgp", + "piv", "nks", "p15", "geldkarte", @@ -509,6 +566,7 @@ release_application (app_t app, int locked_already) FF 00 00 = For serial numbers starting with an FF FF 01 00 = Some german p15 cards return an empty serial number so the serial number from the EF(TokenInfo) is used instead. + FF 02 00 = Serial number from Yubikey config FF 7F 00 = No serialno. All other serial number not starting with FF are used as they are. From 29929e65521279eabc98a67c766fe485057405a9 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 21 Jan 2019 14:06:51 +0100 Subject: [PATCH 118/235] scd: Add option --clear to PASSWD. * scd/command.c (cmd_passwd): Add option --clear. (send_status_printf): New. * scd/app-common.h (APP_CHANGE_FLAG_CLEAR): New. * scd/app-nks.c (do_change_pin): Return an error if that option is used. * scd/app-openpgp.c (do_change_pin): Ditto. -- Card application may support this option to clear the PIN verification status of a specific PIN. Signed-off-by: Werner Koch --- po/Makevars | 1 + scd/app-common.h | 5 +++-- scd/app-nks.c | 3 +++ scd/app-openpgp.c | 4 ++++ scd/app.c | 2 +- scd/command.c | 34 +++++++++++++++++++++++++++++++--- scd/scdaemon.h | 3 +++ 7 files changed, 46 insertions(+), 6 deletions(-) diff --git a/po/Makevars b/po/Makevars index 90b0c5b83..20d6ae9d6 100644 --- a/po/Makevars +++ b/po/Makevars @@ -63,6 +63,7 @@ XGETTEXT_OPTIONS = \ --flag=write_status_printf:2:c-format \ --flag=kbxd_print_status:3:c-format \ --flag=gpgconf_write_status:2:c-format \ + --flag=send_status_printf:3:c-format \ --flag=wks_write_status:2:c-format diff --git a/scd/app-common.h b/scd/app-common.h index ff583183a..b1661b524 100644 --- a/scd/app-common.h +++ b/scd/app-common.h @@ -26,8 +26,9 @@ #include -#define APP_CHANGE_FLAG_RESET 1 -#define APP_CHANGE_FLAG_NULLPIN 2 +#define APP_CHANGE_FLAG_RESET 1 /* PIN Reset mode. */ +#define APP_CHANGE_FLAG_NULLPIN 2 /* NULL PIN mode. */ +#define APP_CHANGE_FLAG_CLEAR 4 /* Clear the given PIN. */ /* Bit flags set by the decipher function into R_INFO. */ #define APP_DECIPHER_INFO_NOPAD 1 /* Padding has been removed. */ diff --git a/scd/app-nks.c b/scd/app-nks.c index 801ab904a..0f38e7cd0 100644 --- a/scd/app-nks.c +++ b/scd/app-nks.c @@ -1169,6 +1169,9 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *pwidstr, if (!newdesc) return gpg_error (GPG_ERR_INV_ID); + if ((flags & APP_CHANGE_FLAG_CLEAR)) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = switch_application (app, is_sigg); if (err) return err; diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index f25a36062..fddc3b8cf 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -2602,10 +2602,14 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int pinlen = 0; (void)ctrl; + memset (&pininfo, 0, sizeof pininfo); pininfo.fixedlen = -1; pininfo.minlen = minlen; + if ((flags & APP_CHANGE_FLAG_CLEAR)) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + if (reset_mode && chvno == 3) { rc = gpg_error (GPG_ERR_INV_ID); diff --git a/scd/app.c b/scd/app.c index 800c954b4..8e0955538 100644 --- a/scd/app.c +++ b/scd/app.c @@ -997,7 +997,7 @@ app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, } -/* Perform a VERIFY operation without doing anything lese. This may +/* Perform a VERIFY operation without doing anything else. This may be used to initialize a the PIN cache for long lasting other operations. Its use is highly application dependent. */ gpg_error_t diff --git a/scd/command.c b/scd/command.c index ea4ccbcda..044831f01 100644 --- a/scd/command.c +++ b/scd/command.c @@ -1215,12 +1215,13 @@ cmd_random (assuan_context_t ctx, char *line) static const char hlp_passwd[] = - "PASSWD [--reset] [--nullpin] \n" + "PASSWD [--reset] [--nullpin] [--clear] \n" "\n" "Change the PIN or, if --reset is given, reset the retry counter of\n" "the card holder verification vector CHVNO. The option --nullpin is\n" - "used for TCOS cards to set the initial PIN. The format of CHVNO\n" - "depends on the card application."; + "used for TCOS cards to set the initial PIN. The option --clear clears\n" + "the security status associated with the PIN so that the PIN needs to\n" + "be presented again. The format of CHVNO depends on the card application."; static gpg_error_t cmd_passwd (assuan_context_t ctx, char *line) { @@ -1233,6 +1234,8 @@ cmd_passwd (assuan_context_t ctx, char *line) flags |= APP_CHANGE_FLAG_RESET; if (has_option (line, "--nullpin")) flags |= APP_CHANGE_FLAG_NULLPIN; + if (has_option (line, "--clear")) + flags |= APP_CHANGE_FLAG_CLEAR; line = skip_options (line); @@ -1243,6 +1246,11 @@ cmd_passwd (assuan_context_t ctx, char *line) line++; *line = 0; + /* Do not allow other flags aside of --clear. */ + if ((flags & APP_CHANGE_FLAG_CLEAR) && (flags & ~APP_CHANGE_FLAG_CLEAR)) + return set_error (GPG_ERR_UNSUPPORTED_OPERATION, + "--clear used with other options"); + if ((rc = open_card (ctrl))) return rc; @@ -1922,6 +1930,26 @@ send_status_direct (ctrl_t ctrl, const char *keyword, const char *args) } +/* This status functions expects a printf style format string. No + * filtering of the data is done instead the orintf formatted data is + * send using assuan_send_status. */ +gpg_error_t +send_status_printf (ctrl_t ctrl, const char *keyword, const char *format, ...) +{ + gpg_error_t err; + va_list arg_ptr; + assuan_context_t ctx; + + if (!ctrl || !ctrl->server_local || !(ctx = ctrl->server_local->assuan_ctx)) + return 0; + + va_start (arg_ptr, format); + err = vprint_assuan_status (ctx, keyword, format, arg_ptr); + va_end (arg_ptr); + return err; +} + + void popup_prompt (void *opaque, int on) { diff --git a/scd/scdaemon.h b/scd/scdaemon.h index 238e6a8fd..73589ade8 100644 --- a/scd/scdaemon.h +++ b/scd/scdaemon.h @@ -123,6 +123,9 @@ int scd_command_handler (ctrl_t, int); void send_status_info (ctrl_t ctrl, const char *keyword, ...) GPGRT_ATTR_SENTINEL(1); void send_status_direct (ctrl_t ctrl, const char *keyword, const char *args); +gpg_error_t send_status_printf (ctrl_t ctrl, const char *keyword, + const char *format, ...) GPGRT_ATTR_PRINTF(3,4); + void popup_prompt (void *opaque, int on); void send_client_notifications (app_t app, int removal); void scd_kick_the_loop (void); From fa9d703de5c70ae925e8ca6604073506f24d641a Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 21 Jan 2019 15:01:45 +0100 Subject: [PATCH 119/235] scd: Support CHV-STATUS and CHECKPIN for PIV. * scd/app-piv.c (parse_pin_keyref): New. (get_chv_status): New. (do_getattr): Add name CHV-STATUS. (verify_pin): Add arg keyref to support other PINs. (do_change_pin): New. Right now limited to --clear. (do_check_pin): New. (app_select_piv): Register new commands. Signed-off-by: Werner Koch --- scd/app-piv.c | 237 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 212 insertions(+), 25 deletions(-) diff --git a/scd/app-piv.c b/scd/app-piv.c index 9b4047753..011b55229 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -364,6 +364,25 @@ dump_all_do (int slot) } +/* Parse the key reference KEYREFSTR which is expected to hold a key + * reference for a PIN object. Return the one octet keyref or -1 for + * an invalid reference. */ +static int +parse_pin_keyref (const char *keyrefstr) +{ + if (!keyrefstr) + return -1; + else if (!ascii_strcasecmp (keyrefstr, "PIV.00")) + return 0x00; + else if (!ascii_strcasecmp (keyrefstr, "PIV.80")) + return 0x80; + else if (!ascii_strcasecmp (keyrefstr, "PIV.81")) + return 0x81; + else + return -1; +} + + /* Return an allocated string with the serial number in a format to be * show to the user. With FAILMODE is true return NULL if such an * abbreviated S/N is not available, else return the full serial @@ -396,6 +415,47 @@ get_dispserialno (app_t app, int failmode) } +/* The verify command can be used to retrieve the security status of + * the card. Given the PIN name (e.g. "PIV.80" for thge application + * pin, a status is returned: + * + * -1 = Error retrieving the data, + * -2 = No such PIN, + * -3 = PIN blocked, + * -5 = Verify still valid, + * n >= 0 = Number of verification attempts left. + */ +static int +get_chv_status (app_t app, const char *keyrefstr) +{ + unsigned char apdu[4]; + unsigned int sw; + int result; + int keyref; + + keyref = parse_pin_keyref (keyrefstr); + if (!keyrefstr) + return -1; + + apdu[0] = 0x00; + apdu[1] = ISO7816_VERIFY; + apdu[2] = 0x00; + apdu[3] = keyref; + if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL)) + result = -5; /* No need to verification. */ + else if (sw == 0x6a88) + result = -2; /* No such PIN. */ + else if (sw == 0x6983) + result = -3; /* PIN is blocked. */ + else if ((sw & 0xfff0) == 0x63C0) + result = (sw & 0x000f); + else + result = -1; /* Error. */ + + return result; +} + + /* Implementation of the GETATTR command. This is similar to the * LEARN command but returns only one value via status lines. */ static gpg_error_t @@ -408,7 +468,8 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name) } table[] = { { "SERIALNO", 0x0000, -1 }, { "$AUTHKEYID", 0x0000, -2 }, /* Default key for ssh. */ - { "$DISPSERIALNO",0x0000, -3 } + { "$DISPSERIALNO",0x0000, -3 }, + { "CHV-STATUS", 0x0000, -4 } }; gpg_error_t err = 0; int idx; @@ -450,6 +511,16 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name) else err = gpg_error (GPG_ERR_INV_NAME); /* No Abbreviated S/N. */ } + else if (table[idx].special == -4) /* CHV-STATUS */ + { + int tmp[3]; + + tmp[0] = get_chv_status (app, "PIV.00"); + tmp[1] = get_chv_status (app, "PIV.80"); + tmp[2] = get_chv_status (app, "PIV.81"); + err = send_status_printf (ctrl, table[idx].name, "%d %d %d", + tmp[0], tmp[1], tmp[2]); + } else { relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err); @@ -849,27 +920,28 @@ make_prompt (app_t app, int remaining, const char *firstline) } - -/* Verify the Application PIN for use with data object DOBJ. */ +/* Verify the Application PIN KEYREF. */ static gpg_error_t -verify_pin (app_t app, data_object_t dobj, +verify_pin (app_t app, int keyref, gpg_error_t (*pincb)(void*,const char *,char **), void *pincb_arg) { gpg_error_t err; unsigned char apdu[4]; unsigned int sw; int remaining; + const char *label; char *prompt; char *pinvalue = NULL; unsigned int pinlen; char pinbuffer[8]; + int minlen, maxlen, padding, onlydigits; /* First check whether a verify is at all needed. This is done with * P1 being 0 and no Lc and command data send. */ apdu[0] = 0x00; apdu[1] = ISO7816_VERIFY; apdu[2] = 0x00; - apdu[3] = 0x80; + apdu[3] = keyref; if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL)) { /* No need to verification. */ @@ -881,11 +953,46 @@ verify_pin (app_t app, data_object_t dobj, remaining = -1; if (remaining != -1) - log_debug ("piv: PIN for %s has %d attempts left\n", - dobj->keyref, remaining); + log_debug ("piv: PIN %2X has %d attempts left\n", keyref, remaining); + + switch (keyref) + { + case 0x00: + minlen = 6; + maxlen = 8; + padding = 1; + onlydigits = 1; + label = _("||Please enter the Global-PIN of your PIV card"); + break; + case 0x80: + minlen = 6; + maxlen = 8; + padding = 1; + onlydigits = 1; + label = _("||Please enter the PIN of your PIV card"); + break; + case 0x81: + minlen = 8; + maxlen = 8; + padding = 0; + onlydigits = 0; + label = _("||Please enter the Unblocking Key of your PIV card"); + break; + + case 0x96: + case 0x97: + case 0x98: + case 0x9B: + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + default: + return gpg_error (GPG_ERR_INV_ID); + } + log_assert (sizeof pinbuffer >= maxlen); + /* Ask for the PIN. */ - prompt = make_prompt (app, remaining, _("||Please enter your PIV PIN")); + prompt = make_prompt (app, remaining, label); err = pincb (pincb_arg, prompt, &pinvalue); xfree (prompt); prompt = NULL; @@ -896,24 +1003,22 @@ verify_pin (app_t app, data_object_t dobj, } pinlen = pinvalue? strlen (pinvalue) : 0; - if (pinlen < 6) + if (pinlen < minlen) { - log_error (_("PIN for is too short;" - " minimum length is %d\n"), 6); + log_error (_("PIN for is too short; minimum length is %d\n"), minlen); if (pinvalue) wipememory (pinvalue, pinlen); xfree (pinvalue); return gpg_error (GPG_ERR_BAD_PIN); } - if (pinlen > sizeof pinbuffer) + if (pinlen > maxlen) { - log_error (_("PIN for is too long;" - " maximum length is %d\n"), (int)sizeof pinbuffer); + log_error (_("PIN for is too long; maximum length is %d\n"), maxlen); wipememory (pinvalue, pinlen); xfree (pinvalue); return gpg_error (GPG_ERR_BAD_PIN); } - if (strspn (pinvalue, "0123456789") != pinlen) + if (onlydigits && strspn (pinvalue, "0123456789") != pinlen) { log_error (_("PIN has invalid characters; only digits are allowed\n")); wipememory (pinvalue, pinlen); @@ -921,20 +1026,102 @@ verify_pin (app_t app, data_object_t dobj, return gpg_error (GPG_ERR_BAD_PIN); } memcpy (pinbuffer, pinvalue, pinlen); - memset (pinbuffer + pinlen, 0xff, sizeof(pinbuffer) - pinlen); - wipememory (pinvalue, pinlen); + if (padding) + { + memset (pinbuffer + pinlen, 0xff, maxlen - pinlen); + wipememory (pinvalue, pinlen); + pinlen = maxlen; + } + else + wipememory (pinvalue, pinlen); xfree (pinvalue); - err = iso7816_verify (app->slot, 0x80, - pinbuffer, sizeof pinbuffer); - wipememory (pinbuffer, sizeof pinbuffer); + err = iso7816_verify (app->slot, keyref, pinbuffer, pinlen); + wipememory (pinbuffer, pinlen); if (err) - log_error ("PIN verification failed: %s\n", gpg_strerror (err)); + log_error ("PIN %02X verification failed: %s\n", keyref,gpg_strerror (err)); return err; } +/* Handle the PASSWD command. Valid values for PWIDSTR are + * key references related to PINs; in particular: + * PIV.00 - The Global PIN + * PIV.80 - The Application PIN + * PIV.81 - The PIN Unblocking key + * The supported flags are: + * APP_CHANGE_FLAG_CLEAR Clear the PIN verification state. + */ +static gpg_error_t +do_change_pin (app_t app, ctrl_t ctrl, const char *pwidstr, + unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + int keyref; + unsigned char apdu[4]; + + char *newpin = NULL; + char *oldpin = NULL; + size_t newpinlen; + size_t oldpinlen; + const char *newdesc; + int pwid; + pininfo_t pininfo; + + (void)ctrl; + + /* The minimum and maximum lengths are enforced by PIV. */ + memset (&pininfo, 0, sizeof pininfo); + pininfo.minlen = 6; + pininfo.maxlen = 8; + + keyref = parse_pin_keyref (pwidstr); + if (keyref == -1) + return gpg_error (GPG_ERR_INV_ID); + + if ((flags & ~APP_CHANGE_FLAG_CLEAR)) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + /* First see whether the special --clear mode has been requested. */ + if ((flags & APP_CHANGE_FLAG_CLEAR)) + { + apdu[0] = 0x00; + apdu[1] = ISO7816_VERIFY; + apdu[2] = 0xff; + apdu[3] = keyref; + err = iso7816_apdu_direct (app->slot, apdu, 4, 0, NULL, NULL, NULL); + goto leave; + } + + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + leave: + xfree (oldpin); + xfree (newpin); + return err; +} + + +/* Perform a simple verify operation for the PIN specified by PWIDSTR. + * For valid values see do_change_pin. */ +static gpg_error_t +do_check_pin (app_t app, const char *pwidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + int keyref; + + keyref = parse_pin_keyref (pwidstr); + if (keyref == -1) + return gpg_error (GPG_ERR_INV_ID); + + return verify_pin (app, keyref, pincb, pincb_arg); +} + + /* Compute a digital signature using the GENERAL AUTHENTICATE command * on INDATA which is expected to be the raw message digest. The * KEYIDSTR has the key reference or its OID (e.g. "PIV.9A"). The @@ -1045,8 +1232,8 @@ do_auth (app_t app, const char *keyidstr, goto leave; } - /* Now verify the PIN. */ - err = verify_pin (app, dobj, pincb, pincb_arg); + /* Now verify the Application PIN. */ + err = verify_pin (app, 0x80, pincb, pincb_arg); if (err) return err; @@ -1226,8 +1413,8 @@ app_select_piv (app_t app) /* app->fnc.sign = do_sign; */ app->fnc.auth = do_auth; /* app->fnc.decipher = do_decipher; */ - /* app->fnc.change_pin = do_change_pin; */ - /* app->fnc.check_pin = do_check_pin; */ + app->fnc.change_pin = do_change_pin; + app->fnc.check_pin = do_check_pin; leave: From 03cf23b43ec5fea8a355d3ba2200e86a8efc589b Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 22 Jan 2019 09:02:17 +0100 Subject: [PATCH 120/235] common: Add generic status print function. * common/status.c (gnupg_set_status_fd): New. (gnupg_status_printf): New. * po/Makevars (XGETTEXT_OPTIONS): Add gnupg-status_printf. -- Some of the extra tools take a --status-fd option to print certain status messages. A generic printf style print function thus makes sense. Signed-off-by: Werner Koch --- common/status.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++++ common/status.h | 4 ++++ po/Makevars | 1 + 3 files changed, 63 insertions(+) diff --git a/common/status.c b/common/status.c index 50afce496..269ffea5d 100644 --- a/common/status.c +++ b/common/status.c @@ -34,6 +34,10 @@ #include "status.h" #include "status-codes.h" +/* The stream to output the status information. Output is disabled if + * this is NULL. */ +static estream_t statusfp; + /* Return the status string for code NO. */ const char * @@ -47,6 +51,60 @@ get_status_string ( int no ) } +/* Set a global status FD. */ +void +gnupg_set_status_fd (int fd) +{ + static int last_fd = -1; + + if (fd != -1 && last_fd == fd) + return; + + if (statusfp && statusfp != es_stdout && statusfp != es_stderr) + es_fclose (statusfp); + statusfp = NULL; + if (fd == -1) + return; + + if (fd == 1) + statusfp = es_stdout; + else if (fd == 2) + statusfp = es_stderr; + else + statusfp = es_fdopen (fd, "w"); + if (!statusfp) + { + log_fatal ("can't open fd %d for status output: %s\n", + fd, gpg_strerror (gpg_error_from_syserror ())); + } + last_fd = fd; +} + + +/* Write a status line with code NO followed by the output of the + * printf style FORMAT. The caller needs to make sure that LFs and + * CRs are not printed. */ +void +gnupg_status_printf (int no, const char *format, ...) +{ + va_list arg_ptr; + + if (!statusfp) + return; /* Not enabled. */ + + es_fputs ("[GNUPG:] ", statusfp); + es_fputs (get_status_string (no), statusfp); + if (format) + { + es_putc (' ', statusfp); + va_start (arg_ptr, format); + es_vfprintf (statusfp, format, arg_ptr); + va_end (arg_ptr); + } + es_putc ('\n', statusfp); +} + + const char * get_inv_recpsgnr_code (gpg_error_t err) { diff --git a/common/status.h b/common/status.h index dc62f3629..aeab54202 100644 --- a/common/status.h +++ b/common/status.h @@ -163,6 +163,10 @@ enum const char *get_status_string (int code); +void gnupg_set_status_fd (int fd); +void gnupg_status_printf (int no, const char *format, + ...) GPGRT_ATTR_PRINTF(2,3); + const char *get_inv_recpsgnr_code (gpg_error_t err); diff --git a/po/Makevars b/po/Makevars index 20d6ae9d6..07778e055 100644 --- a/po/Makevars +++ b/po/Makevars @@ -61,6 +61,7 @@ XGETTEXT_OPTIONS = \ --flag=ks_printf_help:2:c-format \ --flag=print_further_info:1:c-format \ --flag=write_status_printf:2:c-format \ + --flag=gnupg_printf_status:2:c-format \ --flag=kbxd_print_status:3:c-format \ --flag=gpgconf_write_status:2:c-format \ --flag=send_status_printf:3:c-format \ From e6d613711a327d63511601dd42aeff34e09ec95a Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 22 Jan 2019 09:07:24 +0100 Subject: [PATCH 121/235] card-tool: Add skeleton for new tool * tools/gpg-card-tool.c: New. * tools/gpg-card-tool-w32info.rc: New. * tools/Makefile.am: Add new tool. -- To support more cards than the OpenPGP card it is useful to have a separate tool. It will have have the "gpg --card-edit" style interactive interface as well as direct command line options for all commands. In a first step the OpenPGP card will be supported, to allow its use as an alternative to the gpg command, and the forthcoming PIV card support. The tool can be though as a direct interface to scdaemon. Signed-off-by: Werner Koch --- tools/Makefile.am | 20 +- tools/gpg-card-tool-w32info.rc | 51 ++ tools/gpg-card-tool.c | 869 +++++++++++++++++++++++++++++ tools/gpg-connect-agent-w32info.rc | 2 +- 4 files changed, 936 insertions(+), 6 deletions(-) create mode 100644 tools/gpg-card-tool-w32info.rc create mode 100644 tools/gpg-card-tool.c diff --git a/tools/Makefile.am b/tools/Makefile.am index e4fd81c5d..4833bff5e 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -20,14 +20,17 @@ EXTRA_DIST = \ Manifest watchgnupg.c no-libgcrypt.c \ addgnupghome applygnupgdefaults \ lspgpot mail-signed-keys convert-from-106 sockprox.c \ - ccidmon.c ChangeLog-2011 gpg-connect-agent-w32info.rc - + ccidmon.c ChangeLog-2011 \ + gpg-connect-agent-w32info.rc \ + gpg-card-tool-w32info.rc AM_CPPFLAGS = include $(top_srcdir)/am/cmacros.am if HAVE_W32_SYSTEM -resource_objs += gpg-connect-agent-w32info.o +gpg_connect_agent_rc_objs = gpg-connect-agent-w32info.o +gpg_card_tool_rc_objs = gpg-card-tool-w32info.o +resource_objs += $(gpg_connect_agent_rc_objs) $(gpg_card_tool_rc_objs) endif AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(LIBASSUAN_CFLAGS) @@ -48,7 +51,7 @@ endif libexec_PROGRAMS = gpg-wks-client gpg-pair-tool -bin_PROGRAMS = gpgconf gpg-connect-agent ${symcryptrun} +bin_PROGRAMS = gpgconf gpg-connect-agent gpg-card-tool ${symcryptrun} if !HAVE_W32_SYSTEM bin_PROGRAMS += watchgnupg gpgparsemail ${gpg_wks_server} endif @@ -118,7 +121,14 @@ gpg_connect_agent_LDADD = ../common/libgpgrl.a $(common_libs) \ $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ $(GPG_ERROR_LIBS) \ $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ - $(resource_objs) + $(gpg_connect_agent_rc_objs) + +gpg_card_tool_SOURCES = gpg-card-tool.c +gpg_card_tool_LDADD = ../common/libgpgrl.a $(common_libs) \ + $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ + $(GPG_ERROR_LIBS) \ + $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ + $(gpg_card_tool_rc_objs) if !DISABLE_REGEX diff --git a/tools/gpg-card-tool-w32info.rc b/tools/gpg-card-tool-w32info.rc new file mode 100644 index 000000000..6937c3e34 --- /dev/null +++ b/tools/gpg-card-tool-w32info.rc @@ -0,0 +1,51 @@ +/* gpg-card-toolt-w32info.rc -*- c -*- + * Copyright (C) 2019 g10 Code GmbH + * + * This file is free software; as a special exception the author gives + * unlimited permission to copy and/or distribute it, with or without + * modifications, as long as this notice is preserved. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "afxres.h" +#include "../common/w32info-rc.h" + +1 ICON "../common/gnupg.ico" + +1 VERSIONINFO + FILEVERSION W32INFO_VI_FILEVERSION + PRODUCTVERSION W32INFO_VI_PRODUCTVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ + FILETYPE 0x1L /* VFT_APP (0x1) */ + FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ + BEGIN + VALUE "FileDescription", L"GnuPG\x2019s card tool \ +to the agent\0" + VALUE "InternalName", "gpg-card-tool\0" + VALUE "OriginalFilename", "gpg-card-tool.exe\0" + VALUE "ProductName", W32INFO_PRODUCTNAME + VALUE "ProductVersion", W32INFO_PRODUCTVERSION + VALUE "CompanyName", W32INFO_COMPANYNAME + VALUE "FileVersion", W32INFO_FILEVERSION + VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT + VALUE "Comments", W32INFO_COMMENTS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END + END diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c new file mode 100644 index 000000000..91f04108e --- /dev/null +++ b/tools/gpg-card-tool.c @@ -0,0 +1,869 @@ +/* gpg-card-tool.c - An interactive tool to work with cards. + * Copyright (C) 2019 g10 Code GmbH Werner Koch + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0+ + */ + +#include +#include +#include +#include +#ifdef HAVE_LIBREADLINE +# define GNUPG_LIBREADLINE_H_INCLUDED +# include +#endif /*HAVE_LIBREADLINE*/ + +#include "../common/util.h" +#include "../common/status.h" +#include "../common/i18n.h" +#include "../common/init.h" +#include "../common/sysutils.h" +#include "../common/asshelp.h" +#include "../common/userids.h" +#include "../common/ccparray.h" +#include "../common/exectool.h" +#include "../common/ttyio.h" + +#define CONTROL_D ('D' - 'A' + 1) + +/* Constants to identify the commands and options. */ +enum cmd_and_opt_values + { + aNull = 0, + + oQuiet = 'q', + oVerbose = 'v', + + oDebug = 500, + + oGpgProgram, + oGpgsmProgram, + oStatusFD, + oWithColons, + + oDummy + }; + + +/* The list of commands and options. */ +static ARGPARSE_OPTS opts[] = { + ARGPARSE_group (300, ("@Commands:\n ")), + + ARGPARSE_group (301, ("@\nOptions:\n ")), + + ARGPARSE_s_n (oVerbose, "verbose", ("verbose")), + ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")), + ARGPARSE_s_s (oDebug, "debug", "@"), + ARGPARSE_s_s (oGpgProgram, "gpg", "@"), + ARGPARSE_s_s (oGpgsmProgram, "gpgsm", "@"), + ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), + ARGPARSE_s_n (oWithColons, "with-colons", "@"), + + ARGPARSE_end () +}; + +/* Debug values and macros. */ +#define DBG_IPC_VALUE 1024 /* Debug assuan communication. */ +#define DBG_EXTPROG_VALUE 16384 /* debug external program calls */ + + +/* The list of supported debug flags. */ +static struct debug_flags_s debug_flags [] = + { + { DBG_IPC_VALUE , "ipc" }, + { DBG_EXTPROG_VALUE, "extprog" }, + { 0, NULL } + }; + + + +/* We keep all global options in the structure OPT. */ +struct +{ + int verbose; + unsigned int debug; + int quiet; + int with_colons; + const char *gpg_program; + const char *gpgsm_program; +} opt; + + +static void wrong_args (const char *text) GPGRT_ATTR_NORETURN; +static void interactive_loop (void); +#ifdef HAVE_LIBREADLINE +static char **command_completion (const char *text, int start, int end); +#endif /*HAVE_LIBREADLINE*/ + + + +/* Print usage information and provide strings for help. */ +static const char * +my_strusage( int level ) +{ + const char *p; + + switch (level) + { + case 11: p = "gpg-card-tool"; break; + case 12: p = "@GNUPG@"; break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break; + + case 1: + case 40: + p = ("Usage: gpg-card-tool [command] [options] [args] (-h for help)"); + break; + case 41: + p = ("Syntax: gpg-card-tool [command] [options] [args]\n" + "Tool to configure cards and tokens\n"); + break; + + default: p = NULL; break; + } + return p; +} + + +static void +wrong_args (const char *text) +{ + es_fprintf (es_stderr, _("usage: %s [options] %s\n"), strusage (11), text); + exit (2); +} + + + +/* Command line parsing. */ +static enum cmd_and_opt_values +parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) +{ + enum cmd_and_opt_values cmd = 0; + int no_more_options = 0; + + while (!no_more_options && optfile_parse (NULL, NULL, NULL, pargs, popts)) + { + switch (pargs->r_opt) + { + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + case oDebug: + if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags)) + { + pargs->r_opt = ARGPARSE_INVALID_ARG; + pargs->err = ARGPARSE_PRINT_ERROR; + } + break; + + case oGpgProgram: + opt.gpg_program = pargs->r.ret_str; + break; + case oGpgsmProgram: + opt.gpgsm_program = pargs->r.ret_str; + break; + case oStatusFD: + gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1)); + break; + case oWithColons: + opt.with_colons = 1; + break; + + default: pargs->err = 2; break; + } + } + + return cmd; +} + + + +/* gpg-card-tool main. */ +int +main (int argc, char **argv) +{ + gpg_error_t err; + ARGPARSE_ARGS pargs; + enum cmd_and_opt_values cmd; + + gnupg_reopen_std ("gpg-card-tool"); + set_strusage (my_strusage); + gnupg_rl_initialize (); + log_set_prefix ("gpg-card-tool", GPGRT_LOG_WITH_PREFIX); + + /* Make sure that our subsystems are ready. */ + i18n_init(); + init_common_subsystems (&argc, &argv); + + assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); + setup_libassuan_logging (&opt.debug, NULL); + + /* Parse the command line. */ + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags = ARGPARSE_FLAG_KEEP; + cmd = parse_arguments (&pargs, opts); + + if (log_get_errorcount (0)) + exit (2); + + /* 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]); + } + + /* Set defaults for non given options. */ + if (!opt.gpg_program) + opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); + if (!opt.gpgsm_program) + opt.gpgsm_program = gnupg_module_name (GNUPG_MODULE_NAME_GPGSM); + + /* Run the selected command. */ + switch (cmd) + { + default: + interactive_loop (); + err = 0; + break; + } + + if (err) + gnupg_status_printf (STATUS_FAILURE, "- %u", err); + else if (log_get_errorcount (0)) + gnupg_status_printf (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL); + else + gnupg_status_printf (STATUS_SUCCESS, NULL); + return log_get_errorcount (0)? 1:0; +} + + + +/* Print all available information about the current card. */ +static void +print_card_status (char *serialno, size_t serialnobuflen) +{ + /* struct agent_card_info_s info; */ + /* PKT_public_key *pk = xcalloc (1, sizeof *pk); */ + /* kbnode_t keyblock = NULL; */ + /* int rc; */ + /* unsigned int uval; */ + /* const unsigned char *thefpr; */ + /* unsigned int thefprlen; */ + /* int i; */ + + /* if (serialno && serialnobuflen) */ + /* *serialno = 0; */ + + /* rc = agent_scd_learn (&info, 0); */ + /* if (rc) */ + /* { */ + /* if (opt.with_colons) */ + /* es_fputs ("AID:::\n", fp); */ + /* log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (rc)); */ + /* xfree (pk); */ + /* return; */ + /* } */ + + /* if (opt.with_colons) */ + /* es_fprintf (fp, "Reader:%s:", info.reader? info.reader : ""); */ + /* else */ + /* tty_fprintf (fp, "Reader ...........: %s\n", */ + /* info.reader? info.reader : "[none]"); */ + /* if (opt.with_colons) */ + /* es_fprintf (fp, "AID:%s:", info.serialno? info.serialno : ""); */ + /* else */ + /* tty_fprintf (fp, "Application ID ...: %s\n", */ + /* info.serialno? info.serialno : "[none]"); */ + /* if (!info.serialno || strncmp (info.serialno, "D27600012401", 12) */ + /* || strlen (info.serialno) != 32 ) */ + /* { */ + /* if (info.apptype && !strcmp (info.apptype, "NKS")) */ + /* { */ + /* if (opt.with_colons) */ + /* es_fputs ("netkey-card:\n", fp); */ + /* log_info ("this is a NetKey card\n"); */ + /* } */ + /* else if (info.apptype && !strcmp (info.apptype, "DINSIG")) */ + /* { */ + /* if (opt.with_colons) */ + /* es_fputs ("dinsig-card:\n", fp); */ + /* log_info ("this is a DINSIG compliant card\n"); */ + /* } */ + /* else if (info.apptype && !strcmp (info.apptype, "P15")) */ + /* { */ + /* if (opt.with_colons) */ + /* es_fputs ("pkcs15-card:\n", fp); */ + /* log_info ("this is a PKCS#15 compliant card\n"); */ + /* } */ + /* else if (info.apptype && !strcmp (info.apptype, "GELDKARTE")) */ + /* { */ + /* if (opt.with_colons) */ + /* es_fputs ("geldkarte-card:\n", fp); */ + /* log_info ("this is a Geldkarte compliant card\n"); */ + /* } */ + /* else */ + /* { */ + /* if (opt.with_colons) */ + /* es_fputs ("unknown:\n", fp); */ + /* } */ + /* log_info ("not an OpenPGP card\n"); */ + /* agent_release_card_info (&info); */ + /* xfree (pk); */ + /* return; */ + /* } */ + + /* if (!serialno) */ + /* ; */ + /* else if (strlen (info.serialno)+1 > serialnobuflen) */ + /* log_error ("serial number longer than expected\n"); */ + /* else */ + /* strcpy (serialno, info.serialno); */ + + /* if (opt.with_colons) */ + /* es_fputs ("openpgp-card:\n", fp); */ + + + /* tty_fprintf (fp, "Version ..........: %.1s%c.%.1s%c\n", */ + /* info.serialno[12] == '0'?"":info.serialno+12, */ + /* info.serialno[13], */ + /* info.serialno[14] == '0'?"":info.serialno+14, */ + /* info.serialno[15]); */ + /* tty_fprintf (fp, "Manufacturer .....: %s\n", */ + /* get_manufacturer (xtoi_2(info.serialno+16)*256 */ + /* + xtoi_2 (info.serialno+18))); */ + /* tty_fprintf (fp, "Serial number ....: %.8s\n", info.serialno+20); */ + + /* print_isoname (fp, "Name of cardholder: ", "name", info.disp_name); */ + /* print_name (fp, "Language prefs ...: ", info.disp_lang); */ + /* tty_fprintf (fp, "Salutation .......: %s\n", */ + /* info.disp_sex == 1? _("Mr."): */ + /* info.disp_sex == 2? _("Mrs.") : ""); */ + /* print_name (fp, "URL of public key : ", info.pubkey_url); */ + /* print_name (fp, "Login data .......: ", info.login_data); */ + /* if (info.private_do[0]) */ + /* print_name (fp, "Private DO 1 .....: ", info.private_do[0]); */ + /* if (info.private_do[1]) */ + /* print_name (fp, "Private DO 2 .....: ", info.private_do[1]); */ + /* if (info.private_do[2]) */ + /* print_name (fp, "Private DO 3 .....: ", info.private_do[2]); */ + /* if (info.private_do[3]) */ + /* print_name (fp, "Private DO 4 .....: ", info.private_do[3]); */ + /* if (info.cafpr1len) */ + /* { */ + /* tty_fprintf (fp, "CA fingerprint %d .:", 1); */ + /* print_shax_fpr (fp, info.cafpr1, info.cafpr1len); */ + /* } */ + /* if (info.cafpr2len) */ + /* { */ + /* tty_fprintf (fp, "CA fingerprint %d .:", 2); */ + /* print_shax_fpr (fp, info.cafpr2, info.cafpr2len); */ + /* } */ + /* if (info.cafpr3len) */ + /* { */ + /* tty_fprintf (fp, "CA fingerprint %d .:", 3); */ + /* print_shax_fpr (fp, info.cafpr3, info.cafpr3len); */ + /* } */ + /* tty_fprintf (fp, "Signature PIN ....: %s\n", */ + /* info.chv1_cached? _("not forced"): _("forced")); */ + /* if (info.key_attr[0].algo) */ + /* { */ + /* tty_fprintf (fp, "Key attributes ...:"); */ + /* for (i=0; i < DIM (info.key_attr); i++) */ + /* if (info.key_attr[i].algo == PUBKEY_ALGO_RSA) */ + /* tty_fprintf (fp, " rsa%u", info.key_attr[i].nbits); */ + /* else if (info.key_attr[i].algo == PUBKEY_ALGO_ECDH */ + /* || info.key_attr[i].algo == PUBKEY_ALGO_ECDSA */ + /* || info.key_attr[i].algo == PUBKEY_ALGO_EDDSA) */ + /* { */ + /* const char *curve_for_print = "?"; */ + + /* if (info.key_attr[i].curve) */ + /* { */ + /* const char *oid; */ + /* oid = openpgp_curve_to_oid (info.key_attr[i].curve, NULL); */ + /* if (oid) */ + /* curve_for_print = openpgp_oid_to_curve (oid, 0); */ + /* } */ + /* tty_fprintf (fp, " %s", curve_for_print); */ + /* } */ + /* tty_fprintf (fp, "\n"); */ + /* } */ + /* tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n", */ + /* info.chvmaxlen[0], info.chvmaxlen[1], info.chvmaxlen[2]); */ + /* tty_fprintf (fp, "PIN retry counter : %d %d %d\n", */ + /* info.chvretry[0], info.chvretry[1], info.chvretry[2]); */ + /* tty_fprintf (fp, "Signature counter : %lu\n", info.sig_counter); */ + /* if (info.extcap.kdf) */ + /* { */ + /* tty_fprintf (fp, "KDF setting ......: %s\n", */ + /* info.kdf_do_enabled ? "on" : "off"); */ + /* } */ + /* if (info.extcap.bt) */ + /* { */ + /* tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n", */ + /* info.uif[0] ? "on" : "off", info.uif[1] ? "on" : "off", */ + /* info.uif[2] ? "on" : "off"); */ + /* } */ + /* tty_fprintf (fp, "Signature key ....:"); */ + /* print_shax_fpr (fp, info.fpr1len? info.fpr1:NULL, info.fpr1len); */ + /* if (info.fpr1len && info.fpr1time) */ + /* { */ + /* tty_fprintf (fp, " created ....: %s\n", */ + /* isotimestamp (info.fpr1time)); */ + /* print_keygrip (fp, info.grp1); */ + /* } */ + /* tty_fprintf (fp, "Encryption key....:"); */ + /* print_shax_fpr (fp, info.fpr2len? info.fpr2:NULL, info.fpr2len); */ + /* if (info.fpr2len && info.fpr2time) */ + /* { */ + /* tty_fprintf (fp, " created ....: %s\n", */ + /* isotimestamp (info.fpr2time)); */ + /* print_keygrip (fp, info.grp2); */ + /* } */ + /* tty_fprintf (fp, "Authentication key:"); */ + /* print_shax_fpr (fp, info.fpr3len? info.fpr3:NULL, info.fpr3len); */ + /* if (info.fpr3len && info.fpr3time) */ + /* { */ + /* tty_fprintf (fp, " created ....: %s\n", */ + /* isotimestamp (info.fpr3time)); */ + /* print_keygrip (fp, info.grp3); */ + /* } */ + /* tty_fprintf (fp, "General key info..: "); */ + + /* thefpr = (info.fpr1len? info.fpr1 : info.fpr2len? info.fpr2 : */ + /* info.fpr3len? info.fpr3 : NULL); */ + /* thefprlen = (info.fpr1len? info.fpr1len : info.fpr2len? info.fpr2len : */ + /* info.fpr3len? info.fpr3len : 0); */ + /* /\* If the fingerprint is all 0xff, the key has no associated */ + /* OpenPGP certificate. *\/ */ + /* if ( thefpr && !fpr_is_ff (thefpr, thefprlen) */ + /* && !get_pubkey_byfprint (ctrl, pk, &keyblock, thefpr, thefprlen)) */ + /* { */ + /* print_pubkey_info (ctrl, fp, pk); */ + /* if (keyblock) */ + /* print_card_key_info (fp, keyblock); */ + /* } */ + /* else */ + /* tty_fprintf (fp, "[none]\n"); */ + + /* release_kbnode (keyblock); */ + /* free_public_key (pk); */ + /* agent_release_card_info (&info); */ +} + + + +static void +cmd_verify (void) +{ + /* agent_scd_checkpin (serialnobuf); */ +} + + +static void +cmd_name (void) +{ + /* change_name (); */ +} + + +static void +cmd_url (void) +{ + /* change_url (); */ +} + + +static void +cmd_fetch (void) +{ + /* fetch_url (); */ +} + + +static void +cmd_login (char *arg_string) +{ + /* change_login (arg_string); */ +} + + +static void +cmd_lang (void) +{ + /* change_lang (); */ +} + + +static void +cmd_salut (void) +{ + /* change_salut (); */ +} + + +static void +cmd_cafpr (int arg_number) +{ + if ( arg_number < 1 || arg_number > 3 ) + tty_printf ("usage: cafpr N\n" + " 1 <= N <= 3\n"); + /* else */ + /* change_cafpr (arg_number); */ +} + + +static void +cmd_privatedo (int arg_number, char *arg_string) +{ + if ( arg_number < 1 || arg_number > 4 ) + tty_printf ("usage: privatedo N\n" + " 1 <= N <= 4\n"); + /* else */ + /* change_private_do (arg_string, arg_number); */ +} + + +static void +cmd_writecert (int arg_number, char *arg_rest) +{ + if ( arg_number != 3 ) + tty_printf ("usage: writecert 3 < FILE\n"); + /* else */ + /* change_cert (arg_rest); */ +} + + +static void +cmd_readcert (int arg_number, char *arg_rest) +{ + if ( arg_number != 3 ) + tty_printf ("usage: readcert 3 > FILE\n"); + /* else */ + /* read_cert (arg_rest); */ +} + + +static void +cmd_forcesig (void) +{ + /* toggle_forcesig (); */ +} + + +static void +cmd_generate (void) +{ + /* generate_card_keys (); */ +} + + +static void +cmd_passwd (int allow_admin) +{ + /* change_pin (0, allow_admin); */ +} + + +static void +cmd_unblock (int allow_admin) +{ + /* change_pin (1, allow_admin); */ +} + + +static void +cmd_factoryreset (void) +{ + /* factory_reset (); */ +} + + +static void +cmd_kdfsetup (char *argstring) +{ + /* kdf_setup (arg_string); */ +} + + +static void +cmd_keyattr (void) +{ + /* key_attr (); */ +} + + +static void +cmd_uif (int arg_number, char *arg_rest) +{ + if ( arg_number < 1 || arg_number > 3 ) + tty_printf ("usage: uif N [on|off|permanent]\n" + " 1 <= N <= 3\n"); + /* else */ + /* uif (arg_number, arg_rest); */ +} + + + +/* Data used by the command parser. This needs to be outside of the + * function scope to allow readline based command completion. */ +enum cmdids + { + cmdNOP = 0, + cmdQUIT, cmdADMIN, cmdHELP, cmdLIST, cmdDEBUG, cmdVERIFY, + cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, + cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, + cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET, cmdKDFSETUP, + cmdKEYATTR, cmdUIF, + cmdINVCMD + }; + +static struct +{ + const char *name; + enum cmdids id; + int admin_only; + const char *desc; +} cmds[] = { + { "quit" , cmdQUIT , 0, N_("quit this menu")}, + { "q" , cmdQUIT , 0, NULL }, + { "admin" , cmdADMIN , 0, N_("show admin commands")}, + { "help" , cmdHELP , 0, N_("show this help")}, + { "?" , cmdHELP , 0, NULL }, + { "list" , cmdLIST , 0, N_("list all available data")}, + { "l" , cmdLIST , 0, NULL }, + { "debug" , cmdDEBUG , 0, NULL }, + { "name" , cmdNAME , 1, N_("change card holder's name")}, + { "url" , cmdURL , 1, N_("change URL to retrieve key")}, + { "fetch" , cmdFETCH , 0, N_("fetch the key specified in the card URL")}, + { "login" , cmdLOGIN , 1, N_("change the login name")}, + { "lang" , cmdLANG , 1, N_("change the language preferences")}, + { "salutation",cmdSALUT, 1, N_("change card holder's salutation")}, + { "cafpr" , cmdCAFPR , 1, N_("change a CA fingerprint")}, + { "forcesig", cmdFORCESIG, 1, N_("toggle the signature force PIN flag")}, + { "generate", cmdGENERATE, 1, N_("generate new keys")}, + { "passwd" , cmdPASSWD, 0, N_("menu to change or unblock the PIN")}, + { "verify" , cmdVERIFY, 0, N_("verify the PIN and list all data")}, + { "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code")}, + { "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")}, + { "kdf-setup", cmdKDFSETUP, 1, N_("setup KDF for PIN authentication")}, + { "key-attr", cmdKEYATTR, 1, N_("change the key attribute")}, + { "uif", cmdUIF, 1, N_("change the User Interaction Flag")}, + /* Note, that we do not announce these command yet. */ + { "privatedo", cmdPRIVATEDO, 0, NULL }, + { "readcert", cmdREADCERT, 0, NULL }, + { "writecert", cmdWRITECERT, 1, NULL }, + { NULL, cmdINVCMD, 0, NULL } +}; + + +/* The main loop. */ +static void +interactive_loop (void) +{ + char *answer = NULL; /* The input line. */ + enum cmdids cmd = cmdNOP; /* The command. */ + int cmd_admin_only; /* The command is an admin only command. */ + int arg_number; /* The first argument as a number. */ + char *arg_string = ""; /* The first argument as a string. */ + char *arg_rest = ""; /* The remaining arguments. */ + int redisplay = 1; /* Whether to redisplay the main info. */ + int allow_admin = 0; /* Whether admin commands are allowed. */ + char serialnobuf[50]; + char *p; + int i; + + for (;;) + { + + tty_printf ("\n"); + if (redisplay) + { + print_card_status (serialnobuf, DIM (serialnobuf)); + tty_printf("\n"); + redisplay = 0; + } + + do + { + xfree (answer); + tty_enable_completion (command_completion); + answer = tty_get (_("gpg/card> ")); + tty_kill_prompt(); + tty_disable_completion (); + trim_spaces(answer); + } + while ( *answer == '#' ); + + arg_number = 0; + cmd_admin_only = 0; + if (!*answer) + cmd = cmdLIST; /* We default to the list command */ + else if (*answer == CONTROL_D) + cmd = cmdQUIT; + else + { + if ((p=strchr (answer,' '))) + { + *p++ = 0; + trim_spaces (answer); + trim_spaces (p); + arg_number = atoi (p); + arg_string = p; + arg_rest = p; + while (digitp (arg_rest)) + arg_rest++; + while (spacep (arg_rest)) + arg_rest++; + } + + for (i=0; cmds[i].name; i++ ) + if (!ascii_strcasecmp (answer, cmds[i].name )) + break; + + cmd = cmds[i].id; + cmd_admin_only = cmds[i].admin_only; + } + + if (!allow_admin && cmd_admin_only) + { + tty_printf ("\n"); + tty_printf (_("Admin-only command\n")); + continue; + } + + switch (cmd) + { + case cmdNOP: + break; + + case cmdQUIT: + goto leave; + + case cmdHELP: + for (i=0; cmds[i].name; i++ ) + if(cmds[i].desc + && (!cmds[i].admin_only || (cmds[i].admin_only && allow_admin))) + tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); + break; + + case cmdADMIN: + if ( !strcmp (arg_string, "on") ) + allow_admin = 1; + else if ( !strcmp (arg_string, "off") ) + allow_admin = 0; + else if ( !strcmp (arg_string, "verify") ) + { + /* Force verification of the Admin Command. However, + this is only done if the retry counter is at initial + state. */ + /* FIXME: Must depend on the type of the card. */ + /* char *tmp = xmalloc (strlen (serialnobuf) + 6 + 1); */ + /* strcpy (stpcpy (tmp, serialnobuf), "[CHV3]"); */ + /* allow_admin = !agent_scd_checkpin (tmp); */ + /* xfree (tmp); */ + } + else /* Toggle. */ + allow_admin=!allow_admin; + if(allow_admin) + tty_printf(_("Admin commands are allowed\n")); + else + tty_printf(_("Admin commands are not allowed\n")); + break; + + case cmdVERIFY: cmd_verify (); redisplay = 1; break; + case cmdLIST: redisplay = 1; break; + case cmdNAME: cmd_name (); break; + case cmdURL: cmd_url (); break; + case cmdFETCH: cmd_fetch (); break; + case cmdLOGIN: cmd_login (arg_string); break; + case cmdLANG: cmd_lang (); break; + case cmdSALUT: cmd_salut (); break; + case cmdCAFPR: cmd_cafpr (arg_number); break; + case cmdPRIVATEDO: cmd_privatedo (arg_number, arg_string); break; + case cmdWRITECERT: cmd_writecert (arg_number, arg_rest); break; + case cmdREADCERT: cmd_readcert (arg_number, arg_rest); break; + case cmdFORCESIG: cmd_forcesig (); break; + case cmdGENERATE: cmd_generate (); break; + case cmdPASSWD: cmd_passwd (allow_admin); break; + case cmdUNBLOCK: cmd_unblock (allow_admin); break; + case cmdFACTORYRESET: cmd_factoryreset (); break; + case cmdKDFSETUP: cmd_kdfsetup (arg_string); break; + case cmdKEYATTR: cmd_keyattr (); break; + case cmdUIF: cmd_uif (arg_number, arg_rest); break; + + case cmdINVCMD: + default: + tty_printf ("\n"); + tty_printf (_("Invalid command (try \"help\")\n")); + break; + } /* End command switch. */ + } /* End of main menu loop. */ + + leave: + xfree (answer); +} + +#ifdef HAVE_LIBREADLINE +/* Helper function for readline's command completion. */ +static char * +command_generator (const char *text, int state) +{ + static int list_index, len; + const char *name; + + /* If this is a new word to complete, initialize now. This includes + * saving the length of TEXT for efficiency, and initializing the + index variable to 0. */ + if (!state) + { + list_index = 0; + len = strlen(text); + } + + /* Return the next partial match */ + while ((name = cmds[list_index].name)) + { + /* Only complete commands that have help text. */ + if (cmds[list_index++].desc && !strncmp (name, text, len)) + return strdup(name); + } + + return NULL; +} + +/* Second helper function for readline's command completion. */ +static char ** +command_completion (const char *text, int start, int end) +{ + (void)end; + + /* If we are at the start of a line, we try and command-complete. + * If not, just do nothing for now. */ + if (!start) + return rl_completion_matches (text, command_generator); + + rl_attempted_completion_over = 1; + + return NULL; +} +#endif /*HAVE_LIBREADLINE*/ diff --git a/tools/gpg-connect-agent-w32info.rc b/tools/gpg-connect-agent-w32info.rc index 4e7b19d35..8c6735918 100644 --- a/tools/gpg-connect-agent-w32info.rc +++ b/tools/gpg-connect-agent-w32info.rc @@ -1,4 +1,4 @@ -/* scdaemon-w32info.rc -*- c -*- +/* gpg-connect-agent-w32info.rc -*- c -*- * Copyright (C) 2013 g10 Code GmbH * * This file is free software; as a special exception the author gives From f97dc55ff1b041071bc3cbe98aa761bf77bb7ac8 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 22 Jan 2019 10:06:15 +0100 Subject: [PATCH 122/235] gpg: Stop early when trying to create a primary Elgamal key. * g10/misc.c (openpgp_pk_test_algo2): Add extra check. -- The problem is that --key-gen --batch with a parameter file didn't detect that Elgamal is not capable of signing and so an error was only triggered at the time the self-signature was created. See the code comment for details. GnuPG-bug-id: 4329 Signed-off-by: Werner Koch --- g10/misc.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/g10/misc.c b/g10/misc.c index f129e8327..a3f0c679e 100644 --- a/g10/misc.c +++ b/g10/misc.c @@ -723,6 +723,13 @@ openpgp_pk_test_algo2 (pubkey_algo_t algo, unsigned int use) if (!ga) return gpg_error (GPG_ERR_PUBKEY_ALGO); + /* Elgamal in OpenPGP used to support signing and Libgcrypt still + * does. However, we removed the signing capability from gpg ages + * ago. This function should reflect this so that errors are thrown + * early and not only when we try to sign using Elgamal. */ + if (ga == GCRY_PK_ELG && (use & (PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG))) + return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); + /* Now check whether Libgcrypt has support for the algorithm. */ return gcry_pk_algo_info (ga, GCRYCTL_TEST_ALGO, NULL, &use_buf); } From fec75a3868da72196f76aca95c7ab07debb7dc04 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 23 Jan 2019 12:01:51 +0900 Subject: [PATCH 123/235] scd: Support PASSWD --clear for OpenPGP card. * scd/app-openpgp.c (do_change_pin): Implement handling APP_CHANGE_FLAG_CLEAR. -- It is specified in the specification version 3.1 or later. Some version 2 cards (including Gnuk) support this feature. Any version 1 card has no support for this feature. For CHVNO = 1, it clears for both of 81 and 82; That is, user's key usages for signing and others (decryption and auth). For CHVNO = 3, it clears for 83, admin key. For CHVNO = 2, it clears 82; That is, user's key usages others (decryption and auth). Signed-off-by: NIIBE Yutaka --- scd/app-openpgp.c | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index fddc3b8cf..db5188e33 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -2608,7 +2608,38 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, pininfo.minlen = minlen; if ((flags & APP_CHANGE_FLAG_CLEAR)) - return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + { + unsigned char apdu[4]; + + if (!app->app_local->extcap.is_v2) + return GPG_ERR_UNSUPPORTED_OPERATION; + + apdu[0] = 0x00; + apdu[1] = ISO7816_VERIFY; + apdu[2] = 0xff; + apdu[3] = 0x80+chvno; + + rc = iso7816_apdu_direct (app->slot, apdu, 4, 0, NULL, NULL, NULL); + if (rc) + { + if (rc == GPG_ERR_INV_VALUE) + rc = GPG_ERR_UNSUPPORTED_OPERATION; + return rc; + } + + if (chvno == 1) + { + apdu[3]++; + rc = iso7816_apdu_direct (app->slot, apdu, 4, 0, NULL, NULL, NULL); + app->did_chv1 = app->did_chv2 = 0; + } + else if (chvno == 2) + app->did_chv2 = 0; + else if (chvno == 3) + app->did_chv3 = 0; + + return rc; + } if (reset_mode && chvno == 3) { From 055f8854d3f49b8d06105d20f344f5ac10e4f6a6 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 24 Jan 2019 10:02:52 +0100 Subject: [PATCH 124/235] common: Extend function percent_data_escape. * common/percent.c (percent_data_escape): Add new args prefix and plus_escape. * agent/command.c (cmd_put_secret): Adjust for changed function * common/t-percent.c (test_percent_data_escape): Extend test for the prefix. (test_percent_data_escape_plus): new test for the plus escaping. Signed-off-by: Werner Koch --- agent/command.c | 2 +- common/percent.c | 77 +++++++++++++++----- common/t-percent.c | 170 +++++++++++++++++++++++++++++++++++++++++++-- common/util.h | 3 +- 4 files changed, 225 insertions(+), 27 deletions(-) diff --git a/agent/command.c b/agent/command.c index 7fbf1de1c..c395b1e59 100644 --- a/agent/command.c +++ b/agent/command.c @@ -2751,7 +2751,7 @@ cmd_put_secret (assuan_context_t ctx, char *line) * into a string. Instead of resorting to base64 encoding we use a * special percent escaping which only quoted the Nul and the * percent character. */ - string = percent_data_escape (value? value : valstr, valuelen); + string = percent_data_escape (0, NULL, value? value : valstr, valuelen); if (!string) { err = gpg_error_from_syserror (); diff --git a/common/percent.c b/common/percent.c index 7b817684d..ecc6a1959 100644 --- a/common/percent.c +++ b/common/percent.c @@ -37,16 +37,16 @@ /* Create a newly alloced string from STRING with all spaces and - control characters converted to plus signs or %xx sequences. The - function returns the new string or NULL in case of a malloc - failure. - - Note that we also escape the quote character to work around a bug - in the mingw32 runtime which does not correctly handle command line - quoting. We correctly double the quote mark when calling a program - (i.e. gpg-protect-tool), but the pre-main code does not notice the - double quote as an escaped quote. We do this also on POSIX systems - for consistency. */ + * control characters converted to plus signs or %xx sequences. The + * function returns the new string or NULL in case of a malloc + * failure. + * + * Note that this fucntion also escapes the quote character to work + * around a bug in the mingw32 runtime which does not correctly handle + * command line quoting. We correctly double the quote mark when + * calling a program (i.e. gpg-protect-tool), but the pre-main code + * does not notice the double quote as an escaped quote. We do this + * also on POSIX systems for consistency. */ char * percent_plus_escape (const char *string) { @@ -87,19 +87,36 @@ percent_plus_escape (const char *string) } -/* Create a newly alloced string from (DATA,DATALEN) with embedded - * Nuls quoted as %00. The standard percent unescaping can be - * used to reverse this encoding. */ +/* Create a newly malloced string from (DATA,DATALEN) with embedded + * nuls quoted as %00. The standard percent unescaping can be used to + * reverse this encoding. With PLUS_ESCAPE set plus-escaping (spaces + * are replaced by a '+') and escaping of characters with values less + * than 0x20 is used. If PREFIX is not NULL it will be prepended to + * the output in standard escape format; that is PLUS_ESCAPING is + * ignored for PREFIX. */ char * -percent_data_escape (const void *data, size_t datalen) +percent_data_escape (int plus_escape, const char *prefix, + const void *data, size_t datalen) { char *buffer, *p; - const char *s; - size_t n, length; + const unsigned char *s; + size_t n; + size_t length = 1; - for (length=1, s=data, n=datalen; n; s++, n--) + if (prefix) { - if (!*s || *s == '%') + for (s = prefix; *s; s++) + { + if (*s == '%' || *s < 0x20) + length += 3; + else + length++; + } + } + + for (s=data, n=datalen; n; s++, n--) + { + if (!*s || *s == '%' || (plus_escape && (*s < ' ' || *s == '+'))) length += 3; else length++; @@ -109,6 +126,20 @@ percent_data_escape (const void *data, size_t datalen) if (!buffer) return NULL; + if (prefix) + { + for (s = prefix; *s; s++) + { + if (*s == '%' || *s < 0x20) + { + snprintf (p, 4, "%%%02X", *s); + p += 3; + } + else + *p++ = *s; + } + } + for (s=data, n=datalen; n; s++, n--) { if (!*s) @@ -121,13 +152,21 @@ percent_data_escape (const void *data, size_t datalen) memcpy (p, "%25", 3); p += 3; } + else if (plus_escape && *s == ' ') + { + *p++ = '+'; + } + else if (plus_escape && (*s < ' ' || *s == '+')) + { + snprintf (p, 4, "%%%02X", *s); + p += 3; + } else *p++ = *s; } *p = 0; return buffer; - } diff --git a/common/t-percent.c b/common/t-percent.c index 94ece9249..774fa80ee 100644 --- a/common/t-percent.c +++ b/common/t-percent.c @@ -101,6 +101,142 @@ test_percent_plus_escape (void) static void test_percent_data_escape (void) +{ + static struct { + const char *prefix; + const char *data; + size_t datalen; + const char *expect; + } tbl[] = { + { + NULL, + "", 0, + "" + }, { + NULL, + "a", 1, + "a", + }, { + NULL, + "%22", 3, + "%2522" + }, { + NULL, + "%%", 3, + "%25%25%00" + }, { + NULL, + "\n \0BC\t", 6, + "\n %00BC\t" + }, { + "", + "", 0, + "" + }, { + "", + "a", 1, + "a", + }, { + "", + "%22", 3, + "%2522" + }, { + "", + "%%", 3, + "%25%25%00" + }, { + "", + "\n \0BC\t", 6, + "\n %00BC\t" + }, { + "a", + "", 0, + "a" + }, { + "a", + "a", 1, + "aa", + }, { + "a", + "%22", 3, + "a%2522" + }, { + "a", + "%%", 3, + "a%25%25%00" + }, { + "a", + "\n \0BC\t", 6, + "a\n %00BC\t" + }, { + " ", + "%%", 3, + " %25%25%00" + }, { + "+", + "%%", 3, + "+%25%25%00" + }, { + "%", + "%%", 3, + "%25%25%25%00" + }, { + "a b", + "%%", 3, + "a b%25%25%00" + }, { + "a%2Bb", + "%%", 3, + "a%252Bb%25%25%00" + }, { + "\n", + "%%", 3, + "%0A%25%25%00" + }, { + NULL, + NULL, 0, + NULL } + }; + char *buf; + int i; + size_t len, prefixlen; + + for (i=0; tbl[i].data; i++) + { + buf = percent_data_escape (0, tbl[i].prefix, tbl[i].data, tbl[i].datalen); + if (!buf) + { + fprintf (stderr, "out of core: %s\n", strerror (errno)); + exit (2); + } + if (strcmp (buf, tbl[i].expect)) + { + fail (i); + } + len = percent_plus_unescape_inplace (buf, 0); + prefixlen = tbl[i].prefix? strlen (tbl[i].prefix) : 0; + if (len != tbl[i].datalen + prefixlen) + fail (i); + else if (tbl[i].prefix && memcmp (buf, tbl[i].prefix, prefixlen) + && !(prefixlen == 1 && *tbl[i].prefix == '+' && *buf == ' ')) + { + /* Note extra condition above handles the one test case + * which reverts a plus to a space due to the use of the + * plus-unescape fucntion also for the prefix part. */ + fail (i); + } + else if (memcmp (buf+prefixlen, tbl[i].data, tbl[i].datalen)) + { + fail (i); + } + xfree (buf); + } +} + + + +static void +test_percent_data_escape_plus (void) { static struct { const char *data; @@ -121,7 +257,28 @@ test_percent_data_escape (void) "%25%25%00" }, { "\n \0BC\t", 6, - "\n %00BC\t" + "%0A+%00BC%09" + }, { + " ", 1, + "+" + }, { + " ", 2, + "++" + }, { + "+ +", 3, + "%2B+%2B" + }, { + "\" \"", 3, /* Note: This function does not escape quotes. */ + "\"+\"" + }, { + "%22", 3, + "%2522" + }, { + "%% ", 3, + "%25%25+" + }, { + "\n ABC\t", 6, + "%0A+ABC%09" }, { NULL, 0, NULL } }; char *buf; @@ -130,14 +287,16 @@ test_percent_data_escape (void) for (i=0; tbl[i].data; i++) { - buf = percent_data_escape (tbl[i].data, tbl[i].datalen); + buf = percent_data_escape (1, NULL, tbl[i].data, tbl[i].datalen); if (!buf) { fprintf (stderr, "out of core: %s\n", strerror (errno)); exit (2); } if (strcmp (buf, tbl[i].expect)) - fail (i); + { + fail (i); + } len = percent_plus_unescape_inplace (buf, 0); if (len != tbl[i].datalen) fail (i); @@ -148,16 +307,15 @@ test_percent_data_escape (void) } - int main (int argc, char **argv) { (void)argc; (void)argv; - /* FIXME: We escape_unescape is not tested - only - percent_plus_unescape. */ + /* FIXME: escape_unescape is not tested - only percent_plus_unescape. */ test_percent_plus_escape (); test_percent_data_escape (); + test_percent_data_escape_plus (); return 0; } diff --git a/common/util.h b/common/util.h index 682415d92..d3a846f00 100644 --- a/common/util.h +++ b/common/util.h @@ -201,7 +201,8 @@ char *hex2str_alloc (const char *hexstring, size_t *r_count); /*-- percent.c --*/ char *percent_plus_escape (const char *string); -char *percent_data_escape (const void *data, size_t datalen); +char *percent_data_escape (int plus, const char *prefix, + const void *data, size_t datalen); char *percent_plus_unescape (const char *string, int nulrepl); char *percent_unescape (const char *string, int nulrepl); From 1f8817475f59ede3f28f57edc10ba56bbdd08b49 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Fri, 25 Jan 2019 10:15:39 +0900 Subject: [PATCH 125/235] dirmngr: Fix initialization of assuan's nPth hook. * dirmngr/dirmngr.c (main): Move assuan_set_system_hooks to... (thread_init): ... here. Signed-off-by: NIIBE Yutaka --- dirmngr/dirmngr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index 80fb13476..5b9e7a86e 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -802,6 +802,7 @@ static void thread_init (void) { npth_init (); + assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); /* Now with NPth running we can set the logging callback. Our @@ -877,7 +878,6 @@ main (int argc, char **argv) assuan_set_malloc_hooks (&malloc_hooks); assuan_set_assuan_log_prefix (log_get_prefix (NULL)); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); - assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); assuan_sock_init (); setup_libassuan_logging (&opt.debug, dirmngr_assuan_log_monitor); From ae966bbe9b16ed68a51391afdde615339755e22d Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Fri, 25 Jan 2019 12:08:09 +0900 Subject: [PATCH 126/235] agent: Support --mode=ssh option for CLEAR_PASSPHRASE. * agent/command.c (cmd_clear_passphrase): Add support for SSH. -- GnuPG-bug-id: 4340 Signed-off-by: NIIBE Yutaka --- agent/command.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/agent/command.c b/agent/command.c index c395b1e59..332d20ff4 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1595,19 +1595,24 @@ static const char hlp_clear_passphrase[] = "may be used to invalidate the cache entry for a passphrase. The\n" "function returns with OK even when there is no cached passphrase.\n" "The --mode=normal option is used to clear an entry for a cacheid\n" - "added by the agent.\n"; + "added by the agent. The --mode=ssh option is used for a cacheid\n" + "added for ssh.\n"; static gpg_error_t cmd_clear_passphrase (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *cacheid = NULL; char *p; - int opt_normal; + cache_mode_t cache_mode = CACHE_MODE_USER; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); - opt_normal = has_option (line, "--mode=normal"); + if (has_option (line, "--mode=normal")) + cache_mode = CACHE_MODE_NORMAL; + else if (has_option (line, "--mode=ssh")) + cache_mode = CACHE_MODE_SSH; + line = skip_options (line); /* parse the stuff */ @@ -1620,12 +1625,9 @@ cmd_clear_passphrase (assuan_context_t ctx, char *line) if (!*cacheid || strlen (cacheid) > 50) return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID"); - agent_put_cache (ctrl, cacheid, - opt_normal ? CACHE_MODE_NORMAL : CACHE_MODE_USER, - NULL, 0); + agent_put_cache (ctrl, cacheid, cache_mode, NULL, 0); - agent_clear_passphrase (ctrl, cacheid, - opt_normal ? CACHE_MODE_NORMAL : CACHE_MODE_USER); + agent_clear_passphrase (ctrl, cacheid, cache_mode); return 0; } From c9f4c1f0de06672c6ae2b793d86cc001d131f9a6 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 25 Jan 2019 09:37:19 +0100 Subject: [PATCH 127/235] scd: Fix flushing of CA-FPR data objects * scd/app-openpgp.c (do_setattr): Add new table item to flush a different tag. -- For whatever reasons the OpenPGP card reads the 3 CA fingerprints from one object but sets them individually using 3 different tags. The cache flushing was not prepared for this and so a changed CA fingerprint showed only up after a card reset. This patch fixes it. Signed-off-by: Werner Koch --- scd/app-openpgp.c | 48 ++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index db5188e33..dbc51be7f 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -2451,32 +2451,33 @@ do_setattr (app_t app, const char *name, static struct { const char *name; int tag; + int flush_tag; /* The tag which needs to be flushed or 0. */ int need_chv; int special; unsigned int need_v2:1; } table[] = { - { "DISP-NAME", 0x005B, 3 }, - { "LOGIN-DATA", 0x005E, 3, 2 }, - { "DISP-LANG", 0x5F2D, 3 }, - { "DISP-SEX", 0x5F35, 3 }, - { "PUBKEY-URL", 0x5F50, 3 }, - { "CHV-STATUS-1", 0x00C4, 3, 1 }, - { "CA-FPR-1", 0x00CA, 3 }, - { "CA-FPR-2", 0x00CB, 3 }, - { "CA-FPR-3", 0x00CC, 3 }, - { "PRIVATE-DO-1", 0x0101, 2 }, - { "PRIVATE-DO-2", 0x0102, 3 }, - { "PRIVATE-DO-3", 0x0103, 2 }, - { "PRIVATE-DO-4", 0x0104, 3 }, - { "CERT-3", 0x7F21, 3, 0, 1 }, - { "SM-KEY-ENC", 0x00D1, 3, 0, 1 }, - { "SM-KEY-MAC", 0x00D2, 3, 0, 1 }, - { "KEY-ATTR", 0, 0, 3, 1 }, - { "AESKEY", 0x00D5, 3, 0, 1 }, - { "UIF-1", 0x00D6, 3, 5, 1 }, - { "UIF-2", 0x00D7, 3, 5, 1 }, - { "UIF-3", 0x00D8, 3, 5, 1 }, - { "KDF", 0x00F9, 3, 4, 1 }, + { "DISP-NAME", 0x005B, 0, 3 }, + { "LOGIN-DATA", 0x005E, 0, 3, 2 }, + { "DISP-LANG", 0x5F2D, 0, 3 }, + { "DISP-SEX", 0x5F35, 0, 3 }, + { "PUBKEY-URL", 0x5F50, 0, 3 }, + { "CHV-STATUS-1", 0x00C4, 0, 3, 1 }, + { "CA-FPR-1", 0x00CA, 0x00C6, 3 }, + { "CA-FPR-2", 0x00CB, 0x00C6, 3 }, + { "CA-FPR-3", 0x00CC, 0x00C6, 3 }, + { "PRIVATE-DO-1", 0x0101, 0, 2 }, + { "PRIVATE-DO-2", 0x0102, 0, 3 }, + { "PRIVATE-DO-3", 0x0103, 0, 2 }, + { "PRIVATE-DO-4", 0x0104, 0, 3 }, + { "CERT-3", 0x7F21, 0, 3, 0, 1 }, + { "SM-KEY-ENC", 0x00D1, 0, 3, 0, 1 }, + { "SM-KEY-MAC", 0x00D2, 0, 3, 0, 1 }, + { "KEY-ATTR", 0, 0, 0, 3, 1 }, + { "AESKEY", 0x00D5, 0, 3, 0, 1 }, + { "UIF-1", 0x00D6, 0, 3, 5, 1 }, + { "UIF-2", 0x00D7, 0, 3, 5, 1 }, + { "UIF-3", 0x00D8, 0, 3, 5, 1 }, + { "KDF", 0x00F9, 0, 3, 4, 1 }, { NULL, 0 } }; int exmode; @@ -2511,7 +2512,8 @@ do_setattr (app_t app, const char *name, /* Flush the cache before writing it, so that the next get operation will reread the data from the card and thus get synced in case of errors (e.g. data truncated by the card). */ - flush_cache_item (app, table[idx].tag); + flush_cache_item (app, table[idx].flush_tag? table[idx].flush_tag + /* */ : table[idx].tag); if (app->app_local->cardcap.ext_lc_le && valuelen > 254) exmode = 1; /* Use extended length w/o a limit. */ From 0415b80227c52620bece3ae7502f38f24a23e59d Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 25 Jan 2019 19:12:32 +0100 Subject: [PATCH 128/235] scd: Improve app selection for app "undefined". * scd/app.c (app_new_register): Don't bail out early in undefined mode. Signed-off-by: Werner Koch --- scd/app.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/scd/app.c b/scd/app.c index 8e0955538..219cee68d 100644 --- a/scd/app.c +++ b/scd/app.c @@ -305,21 +305,27 @@ app_new_register (int slot, ctrl_t ctrl, const char *name, } } - /* For certain error codes, there is no need to try more. */ - if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT - || gpg_err_code (err) == GPG_ERR_ENODEV) - goto leave; - /* Figure out the application to use. */ if (want_undefined) { /* We switch to the "undefined" application only if explicitly requested. */ app->apptype = "UNDEFINED"; + /* Clear the error so that we don't run through the application + * selection chain. */ err = 0; } else - err = gpg_error (GPG_ERR_NOT_FOUND); + { + /* For certain error codes, there is no need to try more. */ + if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT + || gpg_err_code (err) == GPG_ERR_ENODEV) + goto leave; + + /* Set a default error so that we run through the application + * selecion chain. */ + err = gpg_error (GPG_ERR_NOT_FOUND); + } if (err && is_app_allowed ("openpgp") && (!name || !strcmp (name, "openpgp"))) From ec13b1c562e34c0fcbc7b848ab6dc187b79cf2c1 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Sat, 26 Jan 2019 23:10:38 +0100 Subject: [PATCH 129/235] gpg: Move S2K encoding function to a shared file. * g10/passphrase.c (encode_s2k_iterations): Move function to ... * common/openpgp-s2k.c: new file. Remove default intialization code. * common/openpgpdefs.h (S2K_DECODE_COUNT): New to keep only one copy. * g10/call-agent.c (agent_get_s2k_count): Change to return the count and print an error. * agent/protect.c: Include openpgpdefs.h * g10/card-util.c (gen_kdf_data): Adjust for changes * g10/gpgcompose.c: Include call-agent.h. (sk_esk): Adjust for changes. * g10/passphrase (passphrase_to_dek): Adjust for changes. * g10/main.h (S2K_DECODE_COUNT): Remove macro. Signed-off-by: Werner Koch --- agent/protect.c | 4 +-- common/Makefile.am | 2 +- common/openpgp-s2k.c | 67 ++++++++++++++++++++++++++++++++++++++++++++ common/openpgpdefs.h | 10 +++++++ common/ttyio.c | 2 ++ g10/call-agent.c | 26 ++++++++++++----- g10/call-agent.h | 2 +- g10/card-util.c | 2 +- g10/gpgcompose.c | 10 +++++-- g10/keydb.h | 1 - g10/main.h | 2 -- g10/passphrase.c | 54 ++--------------------------------- 12 files changed, 111 insertions(+), 71 deletions(-) create mode 100644 common/openpgp-s2k.c diff --git a/agent/protect.c b/agent/protect.c index f95527f78..eda247f27 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -41,6 +41,7 @@ #include "cvt-openpgp.h" #include "../common/sexp-parse.h" +#include "../common/openpgpdefs.h" /* For s2k functions. */ /* The protection mode for encryption. The supported modes for @@ -49,9 +50,6 @@ #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. */ diff --git a/common/Makefile.am b/common/Makefile.am index d288fa36b..b6a6605f1 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -83,7 +83,7 @@ common_sources = \ localename.c \ session-env.c session-env.h \ userids.c userids.h \ - openpgp-oid.c \ + openpgp-oid.c openpgp-s2k.c \ ssh-utils.c ssh-utils.h \ agent-opt.c \ helpfile.c \ diff --git a/common/openpgp-s2k.c b/common/openpgp-s2k.c new file mode 100644 index 000000000..2b0ba604b --- /dev/null +++ b/common/openpgp-s2k.c @@ -0,0 +1,67 @@ +/* openpgp-s2ks.c - OpenPGP S2K helper functions + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, + * 2005, 2006 Free Software Foundation, Inc. + * Copyright (C) 2010, 2019 g10 Code GmbH + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either + * + * - the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at + * your option) any later version. + * + * or + * + * - the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * or both in parallel, as here. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 "util.h" +#include "openpgpdefs.h" + + +/* Pack an s2k iteration count into the form specified in RFC-48800. + * If we're in between valid values, round up. */ +unsigned char +encode_s2k_iterations (int iterations) +{ + unsigned char c=0; + unsigned char result; + unsigned int count; + + if (iterations <= 1024) + return 0; /* Command line arg compatibility. */ + + 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; +} diff --git a/common/openpgpdefs.h b/common/openpgpdefs.h index 8699a178d..aadda434b 100644 --- a/common/openpgpdefs.h +++ b/common/openpgpdefs.h @@ -197,4 +197,14 @@ typedef enum compress_algo_t; + + +/* Decode an rfc4880 encoded S2K count. */ +#define S2K_DECODE_COUNT(_val) ((16ul + ((_val) & 15)) << (((_val) >> 4) + 6)) + + +/*--openpgp-s2k.c --*/ +unsigned char encode_s2k_iterations (int iterations); + + #endif /*GNUPG_COMMON_OPENPGPDEFS_H*/ diff --git a/common/ttyio.c b/common/ttyio.c index c7c9d85ab..374b9f38a 100644 --- a/common/ttyio.c +++ b/common/ttyio.c @@ -564,6 +564,8 @@ do_get( const char *prompt, int hidden ) } + +/* Note: This function never returns NULL. */ char * tty_get( const char *prompt ) { diff --git a/g10/call-agent.c b/g10/call-agent.c index c958b84b7..91af2be39 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -1461,19 +1461,19 @@ gpg_agent_get_confirmation (const char *desc) } -/* Return the S2K iteration count as computed by gpg-agent. */ -gpg_error_t -agent_get_s2k_count (unsigned long *r_count) +/* Return the S2K iteration count as computed by gpg-agent. On error + * print a warning and return a default value. */ +unsigned long +agent_get_s2k_count (void) { gpg_error_t err; membuf_t data; char *buf; - - *r_count = 0; + unsigned long count = 0; err = start_agent (NULL, 0); if (err) - return err; + goto leave; init_membuf (&data, 32); err = assuan_transact (agent_ctx, "GETINFO s2k_count", @@ -1489,10 +1489,22 @@ agent_get_s2k_count (unsigned long *r_count) err = gpg_error_from_syserror (); else { - *r_count = strtoul (buf, NULL, 10); + count = strtoul (buf, NULL, 10); xfree (buf); } } + + leave: + if (err || count < 65536) + { + /* Don't print an error if an older agent is used. */ + if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER) + log_error (_("problem with the agent: %s\n"), gpg_strerror (err)); + + /* Default to 65536 which was used up to 2.0.13. */ + return 65536; + } + return err; } diff --git a/g10/call-agent.h b/g10/call-agent.h index 8ea8ffea6..8619a34f8 100644 --- a/g10/call-agent.h +++ b/g10/call-agent.h @@ -146,7 +146,7 @@ gpg_error_t agent_clear_passphrase (const char *cache_id); gpg_error_t gpg_agent_get_confirmation (const char *desc); /* Return the S2K iteration count as computed by gpg-agent. */ -gpg_error_t agent_get_s2k_count (unsigned long *r_count); +unsigned long agent_get_s2k_count (void); /* Check whether a secret key for public key PK is available. Returns 0 if the secret key is available. */ diff --git a/g10/card-util.c b/g10/card-util.c index eca248433..08844bae3 100644 --- a/g10/card-util.c +++ b/g10/card-util.c @@ -2039,7 +2039,7 @@ gen_kdf_data (unsigned char *data, int single_salt) p = data; - s2k_char = encode_s2k_iterations (0); + s2k_char = encode_s2k_iterations (agent_get_s2k_count ()); iterations = S2K_DECODE_COUNT (s2k_char); count_4byte[0] = (iterations >> 24) & 0xff; count_4byte[1] = (iterations >> 16) & 0xff; diff --git a/g10/gpgcompose.c b/g10/gpgcompose.c index 6f573ce46..5c0857590 100644 --- a/g10/gpgcompose.c +++ b/g10/gpgcompose.c @@ -25,6 +25,7 @@ #include "keydb.h" #include "main.h" #include "options.h" +#include "call-agent.h" static int do_debug; #define debug(fmt, ...) \ @@ -2248,9 +2249,12 @@ sk_esk (const char *option, int argc, char *argv[], void *cookie) log_assert (sizeof (si.salt) == sizeof (ske->s2k.salt)); memcpy (ske->s2k.salt, si.salt, sizeof (ske->s2k.salt)); if (! si.s2k_is_session_key) - /* 0 means get the default. */ - ske->s2k.count = encode_s2k_iterations (si.iterations); - + { + if (!si.iterations) + ske->s2k.count = encode_s2k_iterations (agent_get_s2k_count ()); + else + ske->s2k.count = encode_s2k_iterations (si.iterations); + } /* Derive the symmetric key that is either the session key or the key used to encrypt the session key. */ diff --git a/g10/keydb.h b/g10/keydb.h index 1def2bb81..acb424455 100644 --- a/g10/keydb.h +++ b/g10/keydb.h @@ -276,7 +276,6 @@ gpg_error_t build_sk_list (ctrl_t ctrl, strlist_t locusr, SK_LIST *ret_sk_list, unsigned use); /*-- passphrase.h --*/ -unsigned char encode_s2k_iterations (int iterations); int have_static_passphrase(void); const char *get_static_passphrase (void); void set_passphrase_from_string(const char *pass); diff --git a/g10/main.h b/g10/main.h index 86f8589b2..867f6975b 100644 --- a/g10/main.h +++ b/g10/main.h @@ -507,8 +507,6 @@ gpg_error_t card_generate_subkey (ctrl_t ctrl, kbnode_t pub_keyblock); int card_store_subkey (KBNODE node, int use); #endif -#define S2K_DECODE_COUNT(_val) ((16ul + ((_val) & 15)) << (((_val) >> 4) + 6)) - /*-- migrate.c --*/ void migrate_secring (ctrl_t ctrl); diff --git a/g10/passphrase.c b/g10/passphrase.c index 10574ec6a..99a2c0dc2 100644 --- a/g10/passphrase.c +++ b/g10/passphrase.c @@ -48,57 +48,6 @@ static char *next_pw = NULL; static char *last_pw = NULL; - -/* Pack an s2k iteration count into the form specified in 2440. If - we're in between valid values, round up. With value 0 return the - old default. */ -unsigned char -encode_s2k_iterations (int iterations) -{ - gpg_error_t err; - unsigned char c=0; - unsigned char result; - unsigned int count; - - if (!iterations) - { - unsigned long mycnt; - - /* Ask the gpg-agent for a useful iteration count. */ - err = agent_get_s2k_count (&mycnt); - if (err || mycnt < 65536) - { - /* Don't print an error if an older agent is used. */ - if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER) - log_error (_("problem with the agent: %s\n"), gpg_strerror (err)); - /* Default to 65536 which we used up to 2.0.13. */ - return 96; - } - else if (mycnt >= 65011712) - return 255; /* Largest possible value. */ - else - return encode_s2k_iterations ((int)mycnt); - } - - if (iterations <= 1024) - return 0; /* Command line arg compatibility. */ - - 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; -} - - int have_static_passphrase() { @@ -106,6 +55,7 @@ have_static_passphrase() && (opt.batch || opt.pinentry_mode == PINENTRY_MODE_LOOPBACK)); } + /* Return a static passphrase. The returned value is only valid as long as no other passphrase related function is called. NULL may be returned if no passphrase has been set; better use @@ -342,7 +292,7 @@ passphrase_to_dek (int cipher_algo, STRING2KEY *s2k, call out to gpg-agent and that should not be done during option processing in main(). */ if (!opt.s2k_count) - opt.s2k_count = encode_s2k_iterations (0); + opt.s2k_count = encode_s2k_iterations (agent_get_s2k_count ()); s2k->count = opt.s2k_count; } } From 54f88afba4564e62e51fe6e22beabbdee75f91ac Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Sat, 26 Jan 2019 23:17:54 +0100 Subject: [PATCH 130/235] gpg: Fix just changed agent_get_s2k_count. * g10/call-agent.c (agent_get_s2k_count): Actually return the count. -- The previous push was definitely a bit to hasty. Fixes-Commit: ec13b1c562e34c0fcbc7b848ab6dc187b79cf2c1 Signed-off-by: Werner Koch --- g10/call-agent.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/g10/call-agent.c b/g10/call-agent.c index 91af2be39..83777534e 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -1502,10 +1502,10 @@ agent_get_s2k_count (void) log_error (_("problem with the agent: %s\n"), gpg_strerror (err)); /* Default to 65536 which was used up to 2.0.13. */ - return 65536; + count = 65536; } - return err; + return count; } From 1c9251004592415b27988064ae20504dd1c37f57 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Sun, 27 Jan 2019 20:12:00 +0100 Subject: [PATCH 131/235] card: Implement the bulk of OpenPGP stuff into gpg-card-tool. * tools/card-call-scd.c: New. * tools/card-tool.h: new. * tools/gpg-card-tool.c: Largely extended. -- gpg-card-tool will eventually replace the --card-edit command of gpg because it makes more sense to have a multi-protocol aware tool and not just one for OpenPGP cards. Most OpenPGP card things works now but a few, those which require close interaction with gpg, still need to be implemented. And of course the whole planned non-interactive stuff needs to be written. Signed-off-by: Werner Koch --- po/POTFILES.in | 3 +- tools/Makefile.am | 2 +- tools/card-call-scd.c | 1355 +++++++++++++++++++++ tools/card-tool.h | 161 +++ tools/gpg-card-tool.c | 2694 ++++++++++++++++++++++++++++++++++------- 5 files changed, 3796 insertions(+), 419 deletions(-) create mode 100644 tools/card-call-scd.c create mode 100644 tools/card-tool.h diff --git a/po/POTFILES.in b/po/POTFILES.in index fe8d45f7e..53a7dc8a5 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -127,4 +127,5 @@ tools/gpgconf.c tools/no-libgcrypt.c tools/symcryptrun.c tools/gpg-check-pattern.c - +tools/gpg-card-tool.c +tools/card-call-scd.c diff --git a/tools/Makefile.am b/tools/Makefile.am index 4833bff5e..e29e6a281 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -123,7 +123,7 @@ gpg_connect_agent_LDADD = ../common/libgpgrl.a $(common_libs) \ $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ $(gpg_connect_agent_rc_objs) -gpg_card_tool_SOURCES = gpg-card-tool.c +gpg_card_tool_SOURCES = gpg-card-tool.c card-tool.h card-call-scd.c gpg_card_tool_LDADD = ../common/libgpgrl.a $(common_libs) \ $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ $(GPG_ERROR_LIBS) \ diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c new file mode 100644 index 000000000..7df7861d3 --- /dev/null +++ b/tools/card-call-scd.c @@ -0,0 +1,1355 @@ +/* card-call-scd.c - IPC calls to scdaemon. + * Copyright (C) 2019 g10 Code GmbH + * Copyright (C) 2001-2003, 2006-2011, 2013 Free Software Foundation, Inc. + * Copyright (C) 2013-2015 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LOCALE_H +#include +#endif + +#include "../common/util.h" +#include "../common/membuf.h" +#include "../common/i18n.h" +#include "../common/asshelp.h" +#include "../common/sysutils.h" +#include "../common/status.h" +#include "../common/host2net.h" +#include "../common/openpgpdefs.h" +#include "card-tool.h" + +#define CONTROL_D ('D' - 'A' + 1) + +#define START_AGENT_NO_STARTUP_CMDS 1 +#define START_AGENT_SUPPRESS_ERRORS 2 + +struct default_inq_parm_s +{ + assuan_context_t ctx; + struct { + u32 *keyid; + u32 *mainkeyid; + int pubkey_algo; + } keyinfo; +}; + +struct cipher_parm_s +{ + struct default_inq_parm_s *dflt; + assuan_context_t ctx; + unsigned char *ciphertext; + size_t ciphertextlen; +}; + +struct writecert_parm_s +{ + struct default_inq_parm_s *dflt; + const unsigned char *certdata; + size_t certdatalen; +}; + +struct writekey_parm_s +{ + struct default_inq_parm_s *dflt; + const unsigned char *keydata; + size_t keydatalen; +}; + +struct genkey_parm_s +{ + struct default_inq_parm_s *dflt; + const char *keyparms; + const char *passphrase; +}; + +struct card_cardlist_parm_s +{ + gpg_error_t error; + strlist_t list; +}; + +struct import_key_parm_s +{ + struct default_inq_parm_s *dflt; + const void *key; + size_t keylen; +}; + + +struct cache_nonce_parm_s +{ + char **cache_nonce_addr; + char **passwd_nonce_addr; +}; + + + +/* + * File local variables + */ + +/* The established context to the agent. Note that all calls to + * scdaemon are routed via the agent and thus we only need to care + * about the IPC with the agent. */ +static assuan_context_t agent_ctx; + + + +/* + * Local prototypes + */ +static gpg_error_t learn_status_cb (void *opaque, const char *line); + + + + +/* Release the card info structure INFO. */ +void +release_card_info (card_info_t info) +{ + int i; + + if (!info) + return; + + xfree (info->reader); info->reader = NULL; + xfree (info->serialno); info->serialno = NULL; + xfree (info->dispserialno); info->dispserialno = NULL; + xfree (info->apptypestr); info->apptypestr = NULL; + info->apptype = APP_TYPE_NONE; + xfree (info->disp_name); info->disp_name = NULL; + xfree (info->disp_lang); info->disp_lang = NULL; + xfree (info->pubkey_url); info->pubkey_url = NULL; + xfree (info->login_data); info->login_data = NULL; + info->cafpr1len = info->cafpr2len = info->cafpr3len = 0; + info->fpr1len = info->fpr2len = info->fpr3len = 0; + for (i=0; i < DIM(info->private_do); i++) + { + xfree (info->private_do[i]); + info->private_do[i] = NULL; + } +} + + +/* Map an application type string to an integer. */ +static app_type_t +map_apptypestr (const char *string) +{ + app_type_t result; + + if (!string) + result = APP_TYPE_NONE; + else if (!ascii_strcasecmp (string, "OPENPGP")) + result = APP_TYPE_OPENPGP; + else if (!ascii_strcasecmp (string, "NKS")) + result = APP_TYPE_NKS; + else if (!ascii_strcasecmp (string, "DINSIG")) + result = APP_TYPE_DINSIG; + else if (!ascii_strcasecmp (string, "P15")) + result = APP_TYPE_P15; + else if (!ascii_strcasecmp (string, "GELDKARTE")) + result = APP_TYPE_GELDKARTE; + else if (!ascii_strcasecmp (string, "SC-HSM")) + result = APP_TYPE_SC_HSM; + else if (!ascii_strcasecmp (string, "PIV")) + result = APP_TYPE_PIV; + else + result = APP_TYPE_UNKNOWN; + + return result; +} + + +/* Return a string representation of the application type. */ +const char * +app_type_string (app_type_t app_type) +{ + const char *result = "?"; + switch (app_type) + { + case APP_TYPE_NONE: result = "None"; break; + case APP_TYPE_OPENPGP: result = "OpenPGP"; break; + case APP_TYPE_NKS: result = "NetKey"; break; + case APP_TYPE_DINSIG: result = "DINSIG"; break; + case APP_TYPE_P15: result = "PKCS#15"; break; + case APP_TYPE_GELDKARTE: result = "Geldkarte"; break; + case APP_TYPE_SC_HSM: result = "SC-HSM"; break; + case APP_TYPE_PIV: result = "PIV"; break; + case APP_TYPE_UNKNOWN: result = "Unknown"; break; + } + return result; +} + + + +/* If RC is not 0, write an appropriate status message. */ +static gpg_error_t +status_sc_op_failure (gpg_error_t err) +{ + switch (gpg_err_code (err)) + { + case 0: + break; + case GPG_ERR_CANCELED: + case GPG_ERR_FULLY_CANCELED: + gnupg_status_printf (STATUS_SC_OP_FAILURE, "1"); + break; + case GPG_ERR_BAD_PIN: + gnupg_status_printf (STATUS_SC_OP_FAILURE, "2"); + break; + default: + gnupg_status_printf (STATUS_SC_OP_FAILURE, NULL); + break; + } + return err; +} + + +/* This is the default inquiry callback. It mainly handles the + Pinentry notifications. */ +static gpg_error_t +default_inq_cb (void *opaque, const char *line) +{ + gpg_error_t err = 0; + struct default_inq_parm_s *parm = opaque; + + (void)parm; + + if (has_leading_keyword (line, "PINENTRY_LAUNCHED")) + { + /* err = gpg_proxy_pinentry_notify (parm->ctrl, line); */ + /* if (err) */ + /* log_error (_("failed to proxy %s inquiry to client\n"), */ + /* "PINENTRY_LAUNCHED"); */ + /* We do not pass errors to avoid breaking other code. */ + } + else + log_debug ("ignoring gpg-agent inquiry '%s'\n", line); + + return err; +} + + +/* Print a warning if the server's version number is less than our + version number. Returns an error code on a connection problem. */ +static gpg_error_t +warn_version_mismatch (assuan_context_t ctx, const char *servername, int mode) +{ + gpg_error_t err; + char *serverversion; + const char *myversion = strusage (13); + + err = get_assuan_server_version (ctx, mode, &serverversion); + if (err) + log_log (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED? + GPGRT_LOGLVL_INFO : GPGRT_LOGLVL_ERROR, + _("error getting version from '%s': %s\n"), + servername, gpg_strerror (err)); + else if (compare_version_strings (serverversion, myversion) < 0) + { + char *warn; + + warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"), + servername, serverversion, myversion); + if (!warn) + err = gpg_error_from_syserror (); + else + { + log_info (_("WARNING: %s\n"), warn); + if (!opt.quiet) + { + log_info (_("Note: Outdated servers may lack important" + " security fixes.\n")); + log_info (_("Note: Use the command \"%s\" to restart them.\n"), + "gpgconf --kill all"); + } + gnupg_status_printf (STATUS_WARNING, "server_version_mismatch 0 %s", + warn); + xfree (warn); + } + } + xfree (serverversion); + return err; +} + + +/* Try to connect to the agent via socket or fork it off and work by + * pipes. Handle the server's initial greeting. */ +static gpg_error_t +start_agent (unsigned int flags) +{ + gpg_error_t err; + + if (agent_ctx) + err = 0; + else + { + err = start_new_gpg_agent (&agent_ctx, + GPG_ERR_SOURCE_DEFAULT, + opt.agent_program, + opt.lc_ctype, opt.lc_messages, + opt.session_env, + opt.autostart, opt.verbose, DBG_IPC, + NULL, NULL); + if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_AGENT) + { + static int shown; + + if (!shown) + { + shown = 1; + log_info (_("no gpg-agent running in this session\n")); + } + } + else if (!err + && !(err = warn_version_mismatch (agent_ctx, GPG_AGENT_NAME, 0))) + { + /* Tell the agent that we support Pinentry notifications. + No error checking so that it will work also with older + agents. */ + assuan_transact (agent_ctx, "OPTION allow-pinentry-notify", + NULL, NULL, NULL, NULL, NULL, NULL); + /* Tell the agent about what version we are aware. This is + here used to indirectly enable GPG_ERR_FULLY_CANCELED. */ + assuan_transact (agent_ctx, "OPTION agent-awareness=2.1.0", + NULL, NULL, NULL, NULL, NULL, NULL); + } + } + + if (!err && !(flags & START_AGENT_NO_STARTUP_CMDS)) + { + /* Request the serial number of the card for an early test. */ + struct card_info_s info; + + memset (&info, 0, sizeof info); + + if (!(flags & START_AGENT_SUPPRESS_ERRORS)) + err = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2); + + if (!err) + err = assuan_transact (agent_ctx, "SCD SERIALNO", + NULL, NULL, NULL, NULL, + learn_status_cb, &info); + if (err && !(flags & START_AGENT_SUPPRESS_ERRORS)) + { + switch (gpg_err_code (err)) + { + case GPG_ERR_NOT_SUPPORTED: + case GPG_ERR_NO_SCDAEMON: + gnupg_status_printf (STATUS_CARDCTRL, "6"); /* No card support. */ + break; + case GPG_ERR_OBJ_TERM_STATE: + /* Card is in termination state. */ + gnupg_status_printf (STATUS_CARDCTRL, "7"); + break; + default: + gnupg_status_printf (STATUS_CARDCTRL, "4"); /* No card. */ + break; + } + } + + if (!err && info.serialno) + gnupg_status_printf (STATUS_CARDCTRL, "3 %s", info.serialno); + + release_card_info (&info); + } + + return err; +} + + +/* Return a new malloced string by unescaping the string S. Escaping + * is percent escaping and '+'/space mapping. A binary nul will + * silently be replaced by a 0xFF. Function returns NULL to indicate + * an out of memory status. */ +static char * +unescape_status_string (const unsigned char *s) +{ + return percent_plus_unescape (s, 0xff); +} + + +/* Take a 20 or 32 byte hexencoded string and put it into the provided + * FPRLEN byte long buffer FPR in binary format. Returns the actual + * used length of the FPR buffer or 0 on error. */ +static unsigned int +unhexify_fpr (const char *hexstr, unsigned char *fpr, unsigned int fprlen) +{ + const char *s; + int n; + + for (s=hexstr, n=0; hexdigitp (s); s++, n++) + ; + if ((*s && *s != ' ') || !(n == 40 || n == 64)) + return 0; /* no fingerprint (invalid or wrong length). */ + for (s=hexstr, n=0; *s && n < fprlen; s += 2, n++) + fpr[n] = xtoi_2 (s); + + return (n == 20 || n == 32)? n : 0; +} + + +/* Take the serial number from LINE and return it verbatim in a newly + * allocated string. We make sure that only hex characters are + * returned. Returns NULL on error. */ +static char * +store_serialno (const char *line) +{ + const char *s; + char *p; + + for (s=line; hexdigitp (s); s++) + ; + p = xtrymalloc (s + 1 - line); + if (p) + { + memcpy (p, line, s-line); + p[s-line] = 0; + } + return p; +} + + + +/* Send an APDU to the current card. On success the status word is + * stored at R_SW inless R_SW is NULL. With HEXAPDU being NULL only a + * RESET command is send to scd. With HEXAPDU being the string + * "undefined" the command "SERIALNO undefined" is send to scd. */ +gpg_error_t +scd_apdu (const char *hexapdu, unsigned int *r_sw) +{ + gpg_error_t err; + + err = start_agent (START_AGENT_NO_STARTUP_CMDS); + if (err) + return err; + + if (!hexapdu) + { + err = assuan_transact (agent_ctx, "SCD RESET", + NULL, NULL, NULL, NULL, NULL, NULL); + + } + else if (!strcmp (hexapdu, "undefined")) + { + err = assuan_transact (agent_ctx, "SCD SERIALNO undefined", + NULL, NULL, NULL, NULL, NULL, NULL); + } + else + { + char line[ASSUAN_LINELENGTH]; + membuf_t mb; + unsigned char *data; + size_t datalen; + + init_membuf (&mb, 256); + + snprintf (line, DIM(line), "SCD APDU %s", hexapdu); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &mb, NULL, NULL, NULL, NULL); + if (!err) + { + data = get_membuf (&mb, &datalen); + if (!data) + err = gpg_error_from_syserror (); + else if (datalen < 2) /* Ooops */ + err = gpg_error (GPG_ERR_CARD); + else + { + if (r_sw) + *r_sw = buf16_to_uint (data+datalen-2); + } + xfree (data); + } + } + + return err; +} + + +/* This is a dummy data line callback. */ +static gpg_error_t +dummy_data_cb (void *opaque, const void *buffer, size_t length) +{ + (void)opaque; + (void)buffer; + (void)length; + return 0; +} + +/* A simple callback used to return the serialnumber of a card. */ +static gpg_error_t +get_serialno_cb (void *opaque, const char *line) +{ + char **serialno = opaque; + const char *keyword = line; + const char *s; + int keywordlen, n; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + /* FIXME: Should we use has_leading_keyword? */ + if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) + { + if (*serialno) + return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */ + for (n=0,s=line; hexdigitp (s); s++, n++) + ; + if (!n || (n&1)|| !(spacep (s) || !*s) ) + return gpg_error (GPG_ERR_ASS_PARAMETER); + *serialno = xtrymalloc (n+1); + if (!*serialno) + return out_of_core (); + memcpy (*serialno, line, n); + (*serialno)[n] = 0; + } + + return 0; +} + + +/* The status callback to handle the LEARN and GETATTR commands. */ +static gpg_error_t +learn_status_cb (void *opaque, const char *line) +{ + struct card_info_s *parm = opaque; + const char *keyword = line; + int keywordlen; + int i; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + switch (keywordlen) + { + case 3: + if (!memcmp (keyword, "KDF", 3)) + { + parm->kdf_do_enabled = 1; + } + break; + + case 5: + if (!memcmp (keyword, "UIF-", 4) + && strchr("123", keyword[4])) + { + unsigned char *data; + int no = keyword[4] - '1'; + + log_assert (no >= 0 && no <= 2); + data = unescape_status_string (line); + parm->uif[no] = (data[0] != 0xff); + xfree (data); + } + break; + + case 6: + if (!memcmp (keyword, "READER", keywordlen)) + { + xfree (parm->reader); + parm->reader = unescape_status_string (line); + } + else if (!memcmp (keyword, "EXTCAP", keywordlen)) + { + char *p, *p2, *buf; + int abool; + + buf = p = unescape_status_string (line); + if (buf) + { + for (p = strtok (buf, " "); p; p = strtok (NULL, " ")) + { + p2 = strchr (p, '='); + if (p2) + { + *p2++ = 0; + abool = (*p2 == '1'); + if (!strcmp (p, "ki")) + parm->extcap.ki = abool; + else if (!strcmp (p, "aac")) + parm->extcap.aac = abool; + else if (!strcmp (p, "bt")) + parm->extcap.bt = abool; + else if (!strcmp (p, "kdf")) + parm->extcap.kdf = abool; + else if (!strcmp (p, "si")) + parm->status_indicator = strtoul (p2, NULL, 10); + } + } + xfree (buf); + } + } + else if (!memcmp (keyword, "CA-FPR", keywordlen)) + { + int no = atoi (line); + while (*line && !spacep (line)) + line++; + while (spacep (line)) + line++; + if (no == 1) + parm->cafpr1len = unhexify_fpr (line, parm->cafpr1, + sizeof parm->cafpr1); + else if (no == 2) + parm->cafpr2len = unhexify_fpr (line, parm->cafpr2, + sizeof parm->cafpr2); + else if (no == 3) + parm->cafpr3len = unhexify_fpr (line, parm->cafpr3, + sizeof parm->cafpr3); + } + break; + + case 7: + if (!memcmp (keyword, "APPTYPE", keywordlen)) + { + xfree (parm->apptypestr); + parm->apptypestr = unescape_status_string (line); + parm->apptype = map_apptypestr (parm->apptypestr); + } + else if (!memcmp (keyword, "KEY-FPR", keywordlen)) + { + int no = atoi (line); + + while (*line && !spacep (line)) + line++; + while (spacep (line)) + line++; + if (no == 1) + parm->fpr1len = unhexify_fpr (line, parm->fpr1, sizeof parm->fpr1); + else if (no == 2) + parm->fpr2len = unhexify_fpr (line, parm->fpr2, sizeof parm->fpr2); + else if (no == 3) + parm->fpr3len = unhexify_fpr (line, parm->fpr3, sizeof parm->fpr3); + } + break; + + case 8: + if (!memcmp (keyword, "SERIALNO", keywordlen)) + { + xfree (parm->serialno); + parm->serialno = store_serialno (line); + parm->is_v2 = (strlen (parm->serialno) >= 16 + && xtoi_2 (parm->serialno+12) >= 2 ); + } + else if (!memcmp (keyword, "DISP-SEX", keywordlen)) + { + parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0; + } + else if (!memcmp (keyword, "KEY-TIME", keywordlen)) + { + int no = atoi (line); + while (* line && !spacep (line)) + line++; + while (spacep (line)) + line++; + if (no == 1) + parm->fpr1time = strtoul (line, NULL, 10); + else if (no == 2) + parm->fpr2time = strtoul (line, NULL, 10); + else if (no == 3) + parm->fpr3time = strtoul (line, NULL, 10); + } + else if (!memcmp (keyword, "KEY-ATTR", keywordlen)) + { + int keyno = 0; + int algo = GCRY_PK_RSA; + int n = 0; + + sscanf (line, "%d %d %n", &keyno, &algo, &n); + keyno--; + if (keyno < 0 || keyno >= DIM (parm->key_attr)) + ; /* Out of range - ignore. */ + else + { + parm->key_attr[keyno].algo = algo; + if (algo == PUBKEY_ALGO_RSA) + parm->key_attr[keyno].nbits = strtoul (line+n+3, NULL, 10); + else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA + || algo == PUBKEY_ALGO_EDDSA) + { + parm->key_attr[keyno].curve = + openpgp_is_curve_supported (line + n, NULL, NULL); + } + } + } + break; + + case 9: + if (!memcmp (keyword, "DISP-NAME", keywordlen)) + { + xfree (parm->disp_name); + parm->disp_name = unescape_status_string (line); + } + else if (!memcmp (keyword, "DISP-LANG", keywordlen)) + { + xfree (parm->disp_lang); + parm->disp_lang = unescape_status_string (line); + } + break; + + case 10: + if (!memcmp (keyword, "PUBKEY-URL", keywordlen)) + { + xfree (parm->pubkey_url); + parm->pubkey_url = unescape_status_string (line); + } + else if (!memcmp (keyword, "LOGIN-DATA", keywordlen)) + { + xfree (parm->login_data); + parm->login_data = unescape_status_string (line); + } + else if (!memcmp (keyword, "CHV-STATUS", keywordlen)) + { + char *p, *buf; + + buf = p = unescape_status_string (line); + if (buf) + { + while (spacep (p)) + p++; + parm->chv1_cached = atoi (p); + while (*p && !spacep (p)) + p++; + while (spacep (p)) + p++; + for (i=0; *p && i < 3; i++) + { + parm->chvmaxlen[i] = atoi (p); + while (*p && !spacep (p)) + p++; + while (spacep (p)) + p++; + } + for (i=0; *p && i < 3; i++) + { + parm->chvretry[i] = atoi (p); + while (*p && !spacep (p)) + p++; + while (spacep (p)) + p++; + } + xfree (buf); + } + } + break; + + case 11: + if (!memcmp (keyword, "SIG-COUNTER", keywordlen)) + { + parm->sig_counter = strtoul (line, NULL, 0); + } + else if (!memcmp (keyword, "KEYPAIRINFO", keywordlen)) + { + const char *hexgrp = line; + int no; + + while (*line && !spacep (line)) + line++; + while (spacep (line)) + line++; + if (strncmp (line, "OPENPGP.", 8)) + ; + else if ((no = atoi (line+8)) == 1) + unhexify_fpr (hexgrp, parm->grp1, sizeof parm->grp1); + else if (no == 2) + unhexify_fpr (hexgrp, parm->grp2, sizeof parm->grp2); + else if (no == 3) + unhexify_fpr (hexgrp, parm->grp3, sizeof parm->grp3); + } + break; + + case 12: + if (!memcmp (keyword, "PRIVATE-DO-", 11) + && strchr("1234", keyword[11])) + { + int no = keyword[11] - '1'; + log_assert (no >= 0 && no <= 3); + xfree (parm->private_do[no]); + parm->private_do[no] = unescape_status_string (line); + } + break; + + case 13: + if (!memcmp (keyword, "$DISPSERIALNO", keywordlen)) + { + xfree (parm->dispserialno); + parm->dispserialno = unescape_status_string (line); + } + break; + + default: + /* Unknown. */ + break; + } + + return 0; +} + + +/* Call the scdaemon to learn about a smartcard. This fills INFO + * wioth data from the card. */ +gpg_error_t +scd_learn (card_info_t info) +{ + gpg_error_t err; + struct default_inq_parm_s parm; + struct card_info_s dummyinfo; + + if (!info) + info = &dummyinfo; + + memset (info, 0, sizeof *info); + memset (&parm, 0, sizeof parm); + + err = start_agent (0); + if (err) + return err; + + parm.ctx = agent_ctx; + err = assuan_transact (agent_ctx, "SCD LEARN --force", + dummy_data_cb, NULL, default_inq_cb, &parm, + learn_status_cb, info); + /* Also try to get some other key attributes. */ + if (!err) + { + err = scd_getattr ("KEY-ATTR", info); + if (gpg_err_code (err) == GPG_ERR_INV_NAME + || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION) + err = 0; /* Not implemented or GETATTR not supported. */ + err = scd_getattr ("$DISPSERIALNO", info); + if (gpg_err_code (err) == GPG_ERR_INV_NAME + || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION) + err = 0; /* Not implemented or GETATTR not supported. */ + + } + + if (info == &dummyinfo) + release_card_info (info); + + return err; +} + + +/* Call the agent to retrieve a data object. This function returns + * the data in the same structure as used by the learn command. It is + * allowed to update such a structure using this command. */ +gpg_error_t +scd_getattr (const char *name, struct card_info_s *info) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s parm; + + memset (&parm, 0, sizeof parm); + + if (!*name) + return gpg_error (GPG_ERR_INV_VALUE); + + /* We assume that NAME does not need escaping. */ + if (12 + strlen (name) > DIM(line)-1) + return gpg_error (GPG_ERR_TOO_LARGE); + stpcpy (stpcpy (line, "SCD GETATTR "), name); + + err = start_agent (0); + if (err) + return err; + + parm.ctx = agent_ctx; + err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, + learn_status_cb, info); + + return err; +} + + +/* Send an setattr command to the SCdaemon. */ +gpg_error_t +scd_setattr (const char *name, + const unsigned char *value, size_t valuelen) +{ + gpg_error_t err; + char *tmp; + char *line = NULL; + struct default_inq_parm_s parm; + + + if (!*name || !valuelen) + return gpg_error (GPG_ERR_INV_VALUE); + + tmp = strconcat ("SCD SETATTR ", name, " ", NULL); + if (!tmp) + { + err = gpg_error_from_syserror (); + goto leave; + } + line = percent_data_escape (1, tmp, value, valuelen); + xfree (tmp); + if (!line) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (strlen (line) + 10 > ASSUAN_LINELENGTH) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + + err = start_agent (0); + if (err ) + goto leave; + + memset (&parm, 0, sizeof parm); + parm.ctx = agent_ctx; + err = assuan_transact (agent_ctx, line, NULL, NULL, + default_inq_cb, &parm, NULL, NULL); + + leave: + xfree (line); + return status_sc_op_failure (err); +} + + + +/* Handle a CERTDATA inquiry. Note, we only send the data, + * assuan_transact takes care of flushing and writing the END + * command. */ +static gpg_error_t +inq_writecert_parms (void *opaque, const char *line) +{ + gpg_error_t err; + struct writecert_parm_s *parm = opaque; + + if (has_leading_keyword (line, "CERTDATA")) + { + err = assuan_send_data (parm->dflt->ctx, + parm->certdata, parm->certdatalen); + } + else + err = default_inq_cb (parm->dflt, line); + + return err; +} + + +/* Send a WRITECERT command to the SCdaemon. */ +gpg_error_t +scd_writecert (const char *certidstr, + const unsigned char *certdata, size_t certdatalen) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + struct writecert_parm_s parms; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + err = start_agent (0); + if (err) + return err; + + memset (&parms, 0, sizeof parms); + + snprintf (line, sizeof line, "SCD WRITECERT %s", certidstr); + dfltparm.ctx = agent_ctx; + parms.dflt = &dfltparm; + parms.certdata = certdata; + parms.certdatalen = certdatalen; + + err = assuan_transact (agent_ctx, line, NULL, NULL, + inq_writecert_parms, &parms, NULL, NULL); + + return status_sc_op_failure (err); +} + + + +/* Handle a KEYDATA inquiry. Note, we only send the data, + assuan_transact takes care of flushing and writing the end */ +static gpg_error_t +inq_writekey_parms (void *opaque, const char *line) +{ + gpg_error_t err; + struct writekey_parm_s *parm = opaque; + + if (has_leading_keyword (line, "KEYDATA")) + { + err = assuan_send_data (parm->dflt->ctx, parm->keydata, parm->keydatalen); + } + else + err = default_inq_cb (parm->dflt, line); + + return err; +} + + +/* Send a WRITEKEY command to the SCdaemon. */ +gpg_error_t +scd_writekey (int keyno, const unsigned char *keydata, size_t keydatalen) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + struct writekey_parm_s parms; + struct default_inq_parm_s dfltparm; + + memset (&parms, 0, sizeof parms); + memset (&dfltparm, 0, sizeof dfltparm); + + err = start_agent (0); + if (err) + return err; + + snprintf (line, sizeof line, "SCD WRITEKEY --force OPENPGP.%d", keyno); + dfltparm.ctx = agent_ctx; + parms.dflt = &dfltparm; + parms.keydata = keydata; + parms.keydatalen = keydatalen; + + err = assuan_transact (agent_ctx, line, NULL, NULL, + inq_writekey_parms, &parms, NULL, NULL); + + return status_sc_op_failure (err); +} + + + +/* Status callback for the SCD GENKEY command. */ +static gpg_error_t +scd_genkey_cb (void *opaque, const char *line) +{ + u32 *createtime = opaque; + const char *keyword = line; + int keywordlen; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen)) + { + *createtime = (u32)strtoul (line, NULL, 10); + } + else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen)) + { + gnupg_status_printf (STATUS_PROGRESS, "%s", line); + } + + return 0; +} + +/* Send a GENKEY command to the SCdaemon. If *CREATETIME is not 0, + * the value will be passed to SCDAEMON with --timestamp option so that + * the key is created with this. Otherwise, timestamp was generated by + * SCDEAMON. On success, creation time is stored back to + * CREATETIME. */ +gpg_error_t +scd_genkey (int keyno, int force, u32 *createtime) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + gnupg_isotime_t tbuf; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + err = start_agent (0); + if (err) + return err; + + if (*createtime) + epoch2isotime (tbuf, *createtime); + else + *tbuf = 0; + + snprintf (line, sizeof line, "SCD GENKEY %s%s %s %d", + *tbuf? "--timestamp=":"", tbuf, + force? "--force":"", + keyno); + + dfltparm.ctx = agent_ctx; + err = assuan_transact (agent_ctx, line, + NULL, NULL, default_inq_cb, &dfltparm, + scd_genkey_cb, createtime); + + return status_sc_op_failure (err); +} + + + +/* Return the serial number of the card or an appropriate error. The + * serial number is returned as a hexstring. If DEMAND is not NULL + * the reader with the a card of the serilanumber DEMAND is + * requested. */ +gpg_error_t +scd_serialno (char **r_serialno, const char *demand) +{ + int err; + char *serialno = NULL; + char line[ASSUAN_LINELENGTH]; + + err = start_agent (START_AGENT_SUPPRESS_ERRORS); + if (err) + return err; + + if (!demand) + strcpy (line, "SCD SERIALNO"); + else + snprintf (line, DIM(line), "SCD SERIALNO --demand=%s", demand); + + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, + get_serialno_cb, &serialno); + if (err) + { + xfree (serialno); + return err; + } + + *r_serialno = serialno; + return 0; +} + + + +/* Send a READCERT command to the SCdaemon. */ +gpg_error_t +scd_readcert (const char *certidstr, void **r_buf, size_t *r_buflen) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + size_t len; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + *r_buf = NULL; + err = start_agent (0); + if (err) + return err; + + dfltparm.ctx = agent_ctx; + + init_membuf (&data, 2048); + + snprintf (line, sizeof line, "SCD READCERT %s", certidstr); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &dfltparm, + NULL, NULL); + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + + *r_buf = get_membuf (&data, r_buflen); + if (!*r_buf) + return gpg_error_from_syserror (); + + return 0; +} + + + +/* Callback function for card_cardlist. */ +static gpg_error_t +card_cardlist_cb (void *opaque, const char *line) +{ + struct card_cardlist_parm_s *parm = opaque; + const char *keyword = line; + int keywordlen; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) + { + const char *s; + int n; + + for (n=0,s=line; hexdigitp (s); s++, n++) + ; + + if (!n || (n&1) || *s) + parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); + else + add_to_strlist (&parm->list, line); + } + + return 0; +} + + +/* Return the serial numbers of all cards currently inserted. */ +gpg_error_t +scd_cardlist (strlist_t *result) +{ + gpg_error_t err; + struct card_cardlist_parm_s parm; + + memset (&parm, 0, sizeof parm); + *result = NULL; + + err = start_agent (START_AGENT_SUPPRESS_ERRORS); + if (err) + return err; + + err = assuan_transact (agent_ctx, "SCD GETINFO card_list", + NULL, NULL, NULL, NULL, + card_cardlist_cb, &parm); + if (!err && parm.error) + err = parm.error; + + if (!err) + *result = parm.list; + else + free_strlist (parm.list); + + return err; +} + + + +/* Change the PIN of an OpenPGP card or reset the retry counter. + * CHVNO 1: Change the PIN + * 2: For v1 cards: Same as 1. + * For v2 cards: Reset the PIN using the Reset Code. + * 3: Change the admin PIN + * 101: Set a new PIN and reset the retry counter + * 102: For v1 cars: Same as 101. + * For v2 cards: Set a new Reset Code. + * SERIALNO is not used. + */ +gpg_error_t +scd_change_pin (int chvno) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + const char *reset = ""; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + if (chvno >= 100) + reset = "--reset"; + chvno %= 100; + + err = start_agent (0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + snprintf (line, sizeof line, "SCD PASSWD %s %d", reset, chvno); + err = assuan_transact (agent_ctx, line, + NULL, NULL, + default_inq_cb, &dfltparm, + NULL, NULL); + + return status_sc_op_failure (err); +} + + +/* Perform a CHECKPIN operation. SERIALNO should be the serial + * number of the card - optionally followed by the fingerprint; + * however the fingerprint is ignored here. */ +gpg_error_t +scd_checkpin (const char *serialno) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + err = start_agent (0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + snprintf (line, sizeof line, "SCD CHECKPIN %s", serialno); + err = assuan_transact (agent_ctx, line, + NULL, NULL, + default_inq_cb, &dfltparm, + NULL, NULL); + return status_sc_op_failure (err); +} + + +/* Return the S2K iteration count as computed by gpg-agent. On error + * print a warning and return a default value. */ +unsigned long +agent_get_s2k_count (void) +{ + gpg_error_t err; + membuf_t data; + char *buf; + unsigned long count = 0; + + err = start_agent (0); + if (err) + goto leave; + + init_membuf (&data, 32); + err = assuan_transact (agent_ctx, "GETINFO s2k_count", + put_membuf_cb, &data, + NULL, NULL, NULL, NULL); + if (err) + xfree (get_membuf (&data, NULL)); + else + { + put_membuf (&data, "", 1); + buf = get_membuf (&data, NULL); + if (!buf) + err = gpg_error_from_syserror (); + else + { + count = strtoul (buf, NULL, 10); + xfree (buf); + } + } + + leave: + if (err || count < 65536) + { + /* Don't print an error if an older agent is used. */ + if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER) + log_error (_("problem with the agent: %s\n"), gpg_strerror (err)); + + /* Default to 65536 which was used up to 2.0.13. */ + count = 65536; + } + + return count; +} diff --git a/tools/card-tool.h b/tools/card-tool.h new file mode 100644 index 000000000..d51698c76 --- /dev/null +++ b/tools/card-tool.h @@ -0,0 +1,161 @@ +/* card-tool.h - Common definitions for the gpg-card-tool + * Copyright (C) 2019 g10 Code GmbH + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef GNUPG_CARD_TOOL_H +#define GNUPG_CARD_TOOL_H + +#include "../common/session-env.h" + + +/* We keep all global options in the structure OPT. */ +struct +{ + int verbose; + unsigned int debug; + int quiet; + int with_colons; + const char *gpg_program; + const char *gpgsm_program; + const char *agent_program; + int autostart; + + /* Options passed to the gpg-agent: */ + session_env_t session_env; + char *lc_ctype; + char *lc_messages; + +} opt; + +/* Debug values and macros. */ +#define DBG_IPC_VALUE 1024 /* Debug assuan communication. */ +#define DBG_EXTPROG_VALUE 16384 /* Debug external program calls */ + +#define DBG_IPC (opt.debug & DBG_IPC_VALUE) +#define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE) + + +/* Enumeration of the known card application types. */ +typedef enum + { + APP_TYPE_NONE, /* Not yet known or for direct APDU sending. */ + APP_TYPE_OPENPGP, + APP_TYPE_NKS, + APP_TYPE_DINSIG, + APP_TYPE_P15, + APP_TYPE_GELDKARTE, + APP_TYPE_SC_HSM, + APP_TYPE_PIV, + APP_TYPE_UNKNOWN /* Unknown by this tool. */ + } app_type_t; + + +/* OpenPGP card key attributes. */ +struct key_attr +{ + int algo; /* Algorithm identifier. */ + union { + unsigned int nbits; /* Supported keysize. */ + const char *curve; /* Name of curve. */ + }; +}; + + +/* + * The object used to store information about a card. + */ +struct card_info_s +{ + int error; /* private. */ + char *reader; /* Reader information. */ + char *apptypestr; /* Malloced application type string. */ + app_type_t apptype;/* Translated from APPTYPESTR. */ + char *serialno; /* malloced hex string. */ + char *dispserialno;/* malloced string. */ + char *disp_name; /* malloced. */ + char *disp_lang; /* malloced. */ + int disp_sex; /* 0 = unspecified, 1 = male, 2 = female */ + char *pubkey_url; /* malloced. */ + char *login_data; /* malloced. */ + char *private_do[4]; /* malloced. */ + char cafpr1len; /* Length of the CA-fingerprint or 0 if invalid. */ + char cafpr2len; + char cafpr3len; + char cafpr1[20]; + char cafpr2[20]; + char cafpr3[20]; + unsigned char fpr1len; /* Length of the fingerprint or 0 if invalid. */ + unsigned char fpr2len; + unsigned char fpr3len; + char fpr1[20]; + char fpr2[20]; + char fpr3[20]; + u32 fpr1time; + u32 fpr2time; + u32 fpr3time; + char grp1[20]; /* The keygrip for OPENPGP.1 */ + char grp2[20]; /* The keygrip for OPENPGP.2 */ + char grp3[20]; /* The keygrip for OPENPGP.3 */ + unsigned long sig_counter; + int chv1_cached; /* True if a PIN is not required for each + signing. Note that the gpg-agent might cache + it anyway. */ + int is_v2; /* True if this is a v2 card. */ + int chvmaxlen[3]; /* Maximum allowed length of a CHV. */ + int chvretry[3]; /* Allowed retries for the CHV; 0 = blocked. */ + struct key_attr key_attr[3]; /* OpenPGP card key attributes. */ + struct { + unsigned int ki:1; /* Key import available. */ + unsigned int aac:1; /* Algorithm attributes are changeable. */ + unsigned int kdf:1; /* KDF object to support PIN hashing available. */ + unsigned int bt:1; /* Button for confirmation available. */ + } extcap; + unsigned int status_indicator; + int kdf_do_enabled; /* True if card has a KDF object. */ + int uif[3]; /* True if User Interaction Flag is on. */ +}; +typedef struct card_info_s *card_info_t; + + +/*-- card-call-scd.c --*/ +void release_card_info (card_info_t info); +const char *app_type_string (app_type_t app_type); + +gpg_error_t scd_apdu (const char *hexapdu, unsigned int *r_sw); +gpg_error_t scd_learn (card_info_t info); +gpg_error_t scd_getattr (const char *name, struct card_info_s *info); +gpg_error_t scd_setattr (const char *name, + const unsigned char *value, size_t valuelen); +gpg_error_t scd_writecert (const char *certidstr, + const unsigned char *certdata, size_t certdatalen); +gpg_error_t scd_writekey (int keyno, + const unsigned char *keydata, size_t keydatalen); +gpg_error_t scd_genkey (int keyno, int force, u32 *createtime); +gpg_error_t scd_serialno (char **r_serialno, const char *demand); +gpg_error_t scd_readcert (const char *certidstr, + void **r_buf, size_t *r_buflen); +gpg_error_t scd_cardlist (strlist_t *result); +gpg_error_t scd_change_pin (int chvno); +gpg_error_t scd_checkpin (const char *serialno); + +unsigned long agent_get_s2k_count (void); + + + +#endif /*GNUPG_CARD_TOOL_H*/ diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index 91f04108e..b40914a6f 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -1,5 +1,5 @@ /* gpg-card-tool.c - An interactive tool to work with cards. - * Copyright (C) 2019 g10 Code GmbH Werner Koch + * Copyright (C) 2019 g10 Code GmbH * * This file is part of GnuPG. * @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, see . - * SPDX-License-Identifier: GPL-3.0+ + * SPDX-License-Identifier: GPL-3.0-or-later */ #include @@ -37,6 +37,11 @@ #include "../common/ccparray.h" #include "../common/exectool.h" #include "../common/ttyio.h" +#include "../common/server-help.h" +#include "../common/openpgpdefs.h" + +#include "card-tool.h" + #define CONTROL_D ('D' - 'A' + 1) @@ -54,6 +59,15 @@ enum cmd_and_opt_values oGpgsmProgram, oStatusFD, oWithColons, + oNoAutostart, + oAgentProgram, + + oDisplay, + oTTYname, + oTTYtype, + oXauthority, + oLCctype, + oLCmessages, oDummy }; @@ -72,15 +86,18 @@ static ARGPARSE_OPTS opts[] = { ARGPARSE_s_s (oGpgsmProgram, "gpgsm", "@"), ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), ARGPARSE_s_n (oWithColons, "with-colons", "@"), + ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), + ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), + ARGPARSE_s_s (oDisplay, "display", "@"), + ARGPARSE_s_s (oTTYname, "ttyname", "@"), + ARGPARSE_s_s (oTTYtype, "ttytype", "@"), + ARGPARSE_s_s (oXauthority, "xauthority", "@"), + ARGPARSE_s_s (oLCctype, "lc-ctype", "@"), + ARGPARSE_s_s (oLCmessages, "lc-messages","@"), ARGPARSE_end () }; -/* Debug values and macros. */ -#define DBG_IPC_VALUE 1024 /* Debug assuan communication. */ -#define DBG_EXTPROG_VALUE 16384 /* debug external program calls */ - - /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { @@ -90,19 +107,19 @@ static struct debug_flags_s debug_flags [] = }; +/* Limit of size of data we read from a file for certain commands. */ +#define MAX_GET_DATA_FROM_FILE 16384 -/* We keep all global options in the structure OPT. */ -struct -{ - int verbose; - unsigned int debug; - int quiet; - int with_colons; - const char *gpg_program; - const char *gpgsm_program; -} opt; +/* Constats for OpenPGP cards. */ +#define OPENPGP_USER_PIN_DEFAULT "123456" +#define OPENPGP_ADMIN_PIN_DEFAULT "12345678" +#define OPENPGP_KDF_DATA_LENGTH_MIN 90 +#define OPENPGP_KDF_DATA_LENGTH_MAX 110 + + +/* Local prototypes. */ static void wrong_args (const char *text) GPGRT_ATTR_NORETURN; static void interactive_loop (void); #ifdef HAVE_LIBREADLINE @@ -148,6 +165,18 @@ wrong_args (const char *text) } +static void +set_opt_session_env (const char *name, const char *value) +{ + gpg_error_t err; + + err = session_env_setenv (opt.session_env, name, value); + if (err) + log_fatal ("error setting session environment: %s\n", + gpg_strerror (err)); +} + + /* Command line parsing. */ static enum cmd_and_opt_values @@ -170,18 +199,24 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) } break; - case oGpgProgram: - opt.gpg_program = pargs->r.ret_str; - break; - case oGpgsmProgram: - opt.gpgsm_program = pargs->r.ret_str; - break; + case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break; + case oGpgsmProgram: opt.gpgsm_program = pargs->r.ret_str; break; + case oAgentProgram: opt.agent_program = pargs->r.ret_str; break; + case oStatusFD: gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1)); break; - case oWithColons: - opt.with_colons = 1; - break; + + case oWithColons: opt.with_colons = 1; break; + case oNoAutostart: opt.autostart = 0; break; + + case oDisplay: set_opt_session_env ("DISPLAY", pargs->r.ret_str); break; + case oTTYname: set_opt_session_env ("GPG_TTY", pargs->r.ret_str); break; + case oTTYtype: set_opt_session_env ("TERM", pargs->r.ret_str); break; + case oXauthority: set_opt_session_env ("XAUTHORITY", + pargs->r.ret_str); break; + case oLCctype: opt.lc_ctype = pargs->r.ret_str; break; + case oLCmessages: opt.lc_messages = pargs->r.ret_str; break; default: pargs->err = 2; break; } @@ -212,6 +247,14 @@ main (int argc, char **argv) assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); setup_libassuan_logging (&opt.debug, NULL); + /* Setup default options. */ + opt.autostart = 1; + opt.session_env = session_env_new (); + if (!opt.session_env) + log_fatal ("error allocating session environment block: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + + /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; @@ -256,370 +299,2067 @@ main (int argc, char **argv) } +/* Read data from file FNAME up to MAX_GET_DATA_FROM_FILE characters. + * On error return an error code and stores NULL at R_BUFFER; on + * success returns 0, stpres the number of bytes read at R_BUFLEN and + * the address of a newly allocated buffer at R_BUFFER. */ +static gpg_error_t +get_data_from_file (const char *fname, char **r_buffer, size_t *r_buflen) +{ + gpg_error_t err; + estream_t fp; + char *data; + int n; + + *r_buffer = NULL; + *r_buflen = 0; + + fp = es_fopen (fname, "rb"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err)); + return err; + } + + data = xtrymalloc (MAX_GET_DATA_FROM_FILE); + if (!data) + { + err = gpg_error_from_syserror (); + log_error (_("error allocating enough memory: %s\n"), gpg_strerror (err)); + es_fclose (fp); + return err; + } + + n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE, fp); + es_fclose (fp); + if (n < 0) + { + err = gpg_error_from_syserror (); + tty_printf (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); + xfree (data); + return err; + } + *r_buffer = data; + *r_buflen = n; + return 0; +} + + +/* Write LENGTH bytes from BUFFER to file FNAME. Return 0 on + * success. */ +static gpg_error_t +put_data_to_file (const char *fname, const void *buffer, size_t length) +{ + gpg_error_t err; + estream_t fp; + + fp = es_fopen (fname, "wb"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error (_("can't create '%s': %s\n"), fname, gpg_strerror (err)); + return err; + } + + if (length && es_fwrite (buffer, length, 1, fp) != 1) + { + err = gpg_error_from_syserror (); + log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); + es_fclose (fp); + return err; + } + if (es_fclose (fp)) + { + err = gpg_error_from_syserror (); + log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); + return err; + } + return 0; +} + + + +/* Simply prints TEXT to the output. Returns 0 as a convenience. + * This is a separate fucntion so that it can be extended to run + * less(1) or so. The extra arguments are int values terminated by a + * 0 to indicate card application types supported with this command. + * If none are given (just teh final 0), this is a general + * command. */ +static gpg_error_t +print_help (const char *text, ...) +{ + va_list arg_ptr; + int value; + int any = 0; + + tty_fprintf (NULL, "%s\n", text); + + va_start (arg_ptr, text); + while ((value = va_arg (arg_ptr, int))) + { + if (!any) + tty_fprintf (NULL, "[Supported by: "); + tty_fprintf (NULL, "%s%s", any?", ":"", app_type_string (value)); + any = 1; + } + if (any) + tty_fprintf (NULL, "]\n"); + + va_end (arg_ptr); + return 0; +} + + +/* Return the OpenPGP card manufacturer name. */ +static const char * +get_manufacturer (unsigned int no) +{ + /* Note: Make sure that there is no colon or linefeed in the string. */ + switch (no) + { + case 0x0001: return "PPC Card Systems"; + case 0x0002: return "Prism"; + case 0x0003: return "OpenFortress"; + case 0x0004: return "Wewid"; + case 0x0005: return "ZeitControl"; + case 0x0006: return "Yubico"; + case 0x0007: return "OpenKMS"; + case 0x0008: return "LogoEmail"; + case 0x0009: return "Fidesmo"; + case 0x000A: return "Dangerous Things"; + + case 0x002A: return "Magrathea"; + case 0x0042: return "GnuPG e.V."; + + case 0x1337: return "Warsaw Hackerspace"; + case 0x2342: return "warpzone"; /* hackerspace Muenster. */ + case 0x4354: return "Confidential Technologies"; /* cotech.de */ + case 0x63AF: return "Trustica"; + case 0xBD0E: return "Paranoidlabs"; + case 0xF517: return "FSIJ"; + + /* 0x0000 and 0xFFFF are defined as test cards per spec, + * 0xFF00 to 0xFFFE are assigned for use with randomly created + * serial numbers. */ + case 0x0000: + case 0xffff: return "test card"; + default: return (no & 0xff00) == 0xff00? "unmanaged S/N range":"unknown"; + } +} + +/* Print an (OpenPGP) fingerprint. */ +static void +print_shax_fpr (estream_t fp, const unsigned char *fpr, unsigned int fprlen) +{ + int i; + + if (fpr) + { + /* FIXME: Fix formatting for FPRLEN != 20 */ + for (i=0; i < fprlen ; i+=2, fpr += 2 ) + { + if (i == 10 ) + tty_fprintf (fp, " "); + tty_fprintf (fp, " %02X%02X", *fpr, fpr[1]); + } + } + else + tty_fprintf (fp, " [none]"); + tty_fprintf (fp, "\n"); +} + +/* Print the keygrip GRP. */ +static void +print_keygrip (estream_t fp, const unsigned char *grp) +{ + int i; + + for (i=0; i < 20 ; i++, grp++) + tty_fprintf (fp, "%02X", *grp); + tty_fprintf (fp, "\n"); +} + + +/* Print a string but avoid printing control characters. */ +static void +print_string (estream_t fp, const char *text, const char *name) +{ + tty_fprintf (fp, "%s", text); + + /* FIXME: tty_printf_utf8_string2 eats everything after and + including an @ - e.g. when printing an url. */ + if (name && *name) + { + if (fp) + print_utf8_buffer2 (fp, name, strlen (name), '\n'); + else + tty_print_utf8_string2 (NULL, name, strlen (name), 0); + } + else + tty_fprintf (fp, _("[not set]")); + tty_fprintf (fp, "\n"); +} + + +/* Print an ISO formatted name or "[not set]". */ +static void +print_isoname (estream_t fp, const char *name) +{ + if (name && *name) + { + char *p, *given, *buf; + + buf = xstrdup (name); + given = strstr (buf, "<<"); + for (p=buf; *p; p++) + if (*p == '<') + *p = ' '; + if (given && given[2]) + { + *given = 0; + given += 2; + if (fp) + print_utf8_buffer2 (fp, given, strlen (given), '\n'); + else + tty_print_utf8_string2 (NULL, given, strlen (given), 0); + + if (*buf) + tty_fprintf (fp, " "); + } + + if (fp) + print_utf8_buffer2 (fp, buf, strlen (buf), '\n'); + else + tty_print_utf8_string2 (NULL, buf, strlen (buf), 0); + + xfree (buf); + } + else + { + tty_fprintf (fp, _("[not set]")); + } + + tty_fprintf (fp, "\n"); +} + + +/* Return true if the SHA1 fingerprint FPR consists only of zeroes. */ +static int +fpr_is_zero (const char *fpr, unsigned int fprlen) +{ + int i; + + for (i=0; i < fprlen && !fpr[i]; i++) + ; + return (i == fprlen); +} + + +/* Return true if the fingerprint FPR consists only of 0xFF. */ +static int +fpr_is_ff (const char *fpr, unsigned int fprlen) +{ + int i; + + for (i=0; i < fprlen && fpr[i] == '\xff'; i++) + ; + return (i == fprlen); +} + + + +/* List OpenPGP card specific data. */ +static void +list_openpgp (card_info_t info, estream_t fp) +{ + int i; + + if (!info->serialno + || strncmp (info->serialno, "D27600012401", 12) + || strlen (info->serialno) != 32 ) + { + tty_fprintf (fp, "invalid OpenPGP card\n"); + return; + } + + tty_fprintf (fp, "Version ..........: %.1s%c.%.1s%c\n", + info->serialno[12] == '0'?"":info->serialno+12, + info->serialno[13], + info->serialno[14] == '0'?"":info->serialno+14, + info->serialno[15]); + tty_fprintf (fp, "Manufacturer .....: %s\n", + get_manufacturer (xtoi_2(info->serialno+16)*256 + + xtoi_2 (info->serialno+18))); + tty_fprintf (fp, "Name of cardholder: "); + print_isoname (fp, info->disp_name); + + print_string (fp, "Language prefs ...: ", info->disp_lang); + tty_fprintf (fp, "Salutation .......: %s\n", + info->disp_sex == 1? _("Mr."): + info->disp_sex == 2? _("Mrs.") : ""); + print_string (fp, "URL of public key : ", info->pubkey_url); + print_string (fp, "Login data .......: ", info->login_data); + if (info->private_do[0]) + print_string (fp, "Private DO 1 .....: ", info->private_do[0]); + if (info->private_do[1]) + print_string (fp, "Private DO 2 .....: ", info->private_do[1]); + if (info->private_do[2]) + print_string (fp, "Private DO 3 .....: ", info->private_do[2]); + if (info->private_do[3]) + print_string (fp, "Private DO 4 .....: ", info->private_do[3]); + if (info->cafpr1len) + { + tty_fprintf (fp, "CA fingerprint %d .:", 1); + print_shax_fpr (fp, info->cafpr1, info->cafpr1len); + } + if (info->cafpr2len) + { + tty_fprintf (fp, "CA fingerprint %d .:", 2); + print_shax_fpr (fp, info->cafpr2, info->cafpr2len); + } + if (info->cafpr3len) + { + tty_fprintf (fp, "CA fingerprint %d .:", 3); + print_shax_fpr (fp, info->cafpr3, info->cafpr3len); + } + tty_fprintf (fp, "Signature PIN ....: %s\n", + info->chv1_cached? _("not forced"): _("forced")); + if (info->key_attr[0].algo) + { + tty_fprintf (fp, "Key attributes ...:"); + for (i=0; i < DIM (info->key_attr); i++) + if (info->key_attr[i].algo == PUBKEY_ALGO_RSA) + tty_fprintf (fp, " rsa%u", info->key_attr[i].nbits); + else if (info->key_attr[i].algo == PUBKEY_ALGO_ECDH + || info->key_attr[i].algo == PUBKEY_ALGO_ECDSA + || info->key_attr[i].algo == PUBKEY_ALGO_EDDSA) + { + const char *curve_for_print = "?"; + const char *oid; + + if (info->key_attr[i].curve + && (oid = openpgp_curve_to_oid (info->key_attr[i].curve, NULL))) + curve_for_print = openpgp_oid_to_curve (oid, 0); + tty_fprintf (fp, " %s", curve_for_print); + } + tty_fprintf (fp, "\n"); + } + tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n", + info->chvmaxlen[0], info->chvmaxlen[1], info->chvmaxlen[2]); + tty_fprintf (fp, "PIN retry counter : %d %d %d\n", + info->chvretry[0], info->chvretry[1], info->chvretry[2]); + tty_fprintf (fp, "Signature counter : %lu\n", info->sig_counter); + if (info->extcap.kdf) + { + tty_fprintf (fp, "KDF setting ......: %s\n", + info->kdf_do_enabled ? "on" : "off"); + } + if (info->extcap.bt) + { + tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n", + info->uif[0] ? "on" : "off", info->uif[1] ? "on" : "off", + info->uif[2] ? "on" : "off"); + } + tty_fprintf (fp, "Signature key ....:"); + print_shax_fpr (fp, info->fpr1len? info->fpr1:NULL, info->fpr1len); + if (info->fpr1len && info->fpr1time) + { + tty_fprintf (fp, " created ....: %s\n", + isotimestamp (info->fpr1time)); + tty_fprintf (fp, " keygrip ....: "); + print_keygrip (fp, info->grp1); + } + tty_fprintf (fp, "Encryption key....:"); + print_shax_fpr (fp, info->fpr2len? info->fpr2:NULL, info->fpr2len); + if (info->fpr2len && info->fpr2time) + { + tty_fprintf (fp, " created ....: %s\n", + isotimestamp (info->fpr2time)); + tty_fprintf (fp, " keygrip ....: "); + print_keygrip (fp, info->grp2); + } + tty_fprintf (fp, "Authentication key:"); + print_shax_fpr (fp, info->fpr3len? info->fpr3:NULL, info->fpr3len); + if (info->fpr3len && info->fpr3time) + { + tty_fprintf (fp, " created ....: %s\n", + isotimestamp (info->fpr3time)); + tty_fprintf (fp, " keygrip ....: "); + print_keygrip (fp, info->grp3); + } + + /* tty_fprintf (fp, "General key info->.: "); */ + /* thefpr = (info->fpr1len? info->fpr1 : info->fpr2len? info->fpr2 : */ + /* info->fpr3len? info->fpr3 : NULL); */ + /* thefprlen = (info->fpr1len? info->fpr1len : info->fpr2len? info->fpr2len : */ + /* info->fpr3len? info->fpr3len : 0); */ + /* If the fingerprint is all 0xff, the key has no associated + OpenPGP certificate. */ + /* if ( thefpr && !fpr_is_ff (thefpr, thefprlen) */ + /* && !get_pubkey_byfprint (ctrl, pk, &keyblock, thefpr, thefprlen)) */ + /* { */ + /* print_pubkey_info (ctrl, fp, pk); */ + /* if (keyblock) */ + /* print_card_key_info (fp, keyblock); */ + /* } */ + /* else */ + /* tty_fprintf (fp, "[none]\n"); */ +} + /* Print all available information about the current card. */ static void -print_card_status (char *serialno, size_t serialnobuflen) +list_card (card_info_t info) { - /* struct agent_card_info_s info; */ - /* PKT_public_key *pk = xcalloc (1, sizeof *pk); */ - /* kbnode_t keyblock = NULL; */ - /* int rc; */ - /* unsigned int uval; */ - /* const unsigned char *thefpr; */ - /* unsigned int thefprlen; */ - /* int i; */ + estream_t fp = NULL; - /* if (serialno && serialnobuflen) */ - /* *serialno = 0; */ + tty_fprintf (fp, "Reader ...........: %s\n", + info->reader? info->reader : "[none]"); + tty_fprintf (fp, "Serial number ....: %s\n", + info->serialno? info->serialno : "[none]"); + tty_fprintf (fp, "Application Type .: %s%s%s%s\n", + app_type_string (info->apptype), + info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? "(":"", + info->apptype == APP_TYPE_UNKNOWN && info->apptypestr + ? info->apptypestr:"", + info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? ")":""); + if (info->serialno && info->dispserialno + && strcmp (info->serialno, info->dispserialno)) + tty_fprintf (fp, "Displayed S/N ....: %s\n", info->dispserialno); - /* rc = agent_scd_learn (&info, 0); */ - /* if (rc) */ - /* { */ - /* if (opt.with_colons) */ - /* es_fputs ("AID:::\n", fp); */ - /* log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (rc)); */ - /* xfree (pk); */ - /* return; */ - /* } */ - - /* if (opt.with_colons) */ - /* es_fprintf (fp, "Reader:%s:", info.reader? info.reader : ""); */ - /* else */ - /* tty_fprintf (fp, "Reader ...........: %s\n", */ - /* info.reader? info.reader : "[none]"); */ - /* if (opt.with_colons) */ - /* es_fprintf (fp, "AID:%s:", info.serialno? info.serialno : ""); */ - /* else */ - /* tty_fprintf (fp, "Application ID ...: %s\n", */ - /* info.serialno? info.serialno : "[none]"); */ - /* if (!info.serialno || strncmp (info.serialno, "D27600012401", 12) */ - /* || strlen (info.serialno) != 32 ) */ - /* { */ - /* if (info.apptype && !strcmp (info.apptype, "NKS")) */ - /* { */ - /* if (opt.with_colons) */ - /* es_fputs ("netkey-card:\n", fp); */ - /* log_info ("this is a NetKey card\n"); */ - /* } */ - /* else if (info.apptype && !strcmp (info.apptype, "DINSIG")) */ - /* { */ - /* if (opt.with_colons) */ - /* es_fputs ("dinsig-card:\n", fp); */ - /* log_info ("this is a DINSIG compliant card\n"); */ - /* } */ - /* else if (info.apptype && !strcmp (info.apptype, "P15")) */ - /* { */ - /* if (opt.with_colons) */ - /* es_fputs ("pkcs15-card:\n", fp); */ - /* log_info ("this is a PKCS#15 compliant card\n"); */ - /* } */ - /* else if (info.apptype && !strcmp (info.apptype, "GELDKARTE")) */ - /* { */ - /* if (opt.with_colons) */ - /* es_fputs ("geldkarte-card:\n", fp); */ - /* log_info ("this is a Geldkarte compliant card\n"); */ - /* } */ - /* else */ - /* { */ - /* if (opt.with_colons) */ - /* es_fputs ("unknown:\n", fp); */ - /* } */ - /* log_info ("not an OpenPGP card\n"); */ - /* agent_release_card_info (&info); */ - /* xfree (pk); */ - /* return; */ - /* } */ - - /* if (!serialno) */ - /* ; */ - /* else if (strlen (info.serialno)+1 > serialnobuflen) */ - /* log_error ("serial number longer than expected\n"); */ - /* else */ - /* strcpy (serialno, info.serialno); */ - - /* if (opt.with_colons) */ - /* es_fputs ("openpgp-card:\n", fp); */ + switch (info->apptype) + { + case APP_TYPE_OPENPGP: list_openpgp (info, fp); break; + default: break; + } +} - /* tty_fprintf (fp, "Version ..........: %.1s%c.%.1s%c\n", */ - /* info.serialno[12] == '0'?"":info.serialno+12, */ - /* info.serialno[13], */ - /* info.serialno[14] == '0'?"":info.serialno+14, */ - /* info.serialno[15]); */ - /* tty_fprintf (fp, "Manufacturer .....: %s\n", */ - /* get_manufacturer (xtoi_2(info.serialno+16)*256 */ - /* + xtoi_2 (info.serialno+18))); */ - /* tty_fprintf (fp, "Serial number ....: %.8s\n", info.serialno+20); */ + +/* The VERIFY command. */ +static gpg_error_t +cmd_verify (card_info_t info, char *argstr) +{ + gpg_error_t err; - /* print_isoname (fp, "Name of cardholder: ", "name", info.disp_name); */ - /* print_name (fp, "Language prefs ...: ", info.disp_lang); */ - /* tty_fprintf (fp, "Salutation .......: %s\n", */ - /* info.disp_sex == 1? _("Mr."): */ - /* info.disp_sex == 2? _("Mrs.") : ""); */ - /* print_name (fp, "URL of public key : ", info.pubkey_url); */ - /* print_name (fp, "Login data .......: ", info.login_data); */ - /* if (info.private_do[0]) */ - /* print_name (fp, "Private DO 1 .....: ", info.private_do[0]); */ - /* if (info.private_do[1]) */ - /* print_name (fp, "Private DO 2 .....: ", info.private_do[1]); */ - /* if (info.private_do[2]) */ - /* print_name (fp, "Private DO 3 .....: ", info.private_do[2]); */ - /* if (info.private_do[3]) */ - /* print_name (fp, "Private DO 4 .....: ", info.private_do[3]); */ - /* if (info.cafpr1len) */ - /* { */ - /* tty_fprintf (fp, "CA fingerprint %d .:", 1); */ - /* print_shax_fpr (fp, info.cafpr1, info.cafpr1len); */ - /* } */ - /* if (info.cafpr2len) */ - /* { */ - /* tty_fprintf (fp, "CA fingerprint %d .:", 2); */ - /* print_shax_fpr (fp, info.cafpr2, info.cafpr2len); */ - /* } */ - /* if (info.cafpr3len) */ - /* { */ - /* tty_fprintf (fp, "CA fingerprint %d .:", 3); */ - /* print_shax_fpr (fp, info.cafpr3, info.cafpr3len); */ - /* } */ - /* tty_fprintf (fp, "Signature PIN ....: %s\n", */ - /* info.chv1_cached? _("not forced"): _("forced")); */ - /* if (info.key_attr[0].algo) */ - /* { */ - /* tty_fprintf (fp, "Key attributes ...:"); */ - /* for (i=0; i < DIM (info.key_attr); i++) */ - /* if (info.key_attr[i].algo == PUBKEY_ALGO_RSA) */ - /* tty_fprintf (fp, " rsa%u", info.key_attr[i].nbits); */ - /* else if (info.key_attr[i].algo == PUBKEY_ALGO_ECDH */ - /* || info.key_attr[i].algo == PUBKEY_ALGO_ECDSA */ - /* || info.key_attr[i].algo == PUBKEY_ALGO_EDDSA) */ - /* { */ - /* const char *curve_for_print = "?"; */ + if (!info) + return print_help ("verify [chvid]", 0); - /* if (info.key_attr[i].curve) */ - /* { */ - /* const char *oid; */ - /* oid = openpgp_curve_to_oid (info.key_attr[i].curve, NULL); */ - /* if (oid) */ - /* curve_for_print = openpgp_oid_to_curve (oid, 0); */ - /* } */ - /* tty_fprintf (fp, " %s", curve_for_print); */ - /* } */ - /* tty_fprintf (fp, "\n"); */ - /* } */ - /* tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n", */ - /* info.chvmaxlen[0], info.chvmaxlen[1], info.chvmaxlen[2]); */ - /* tty_fprintf (fp, "PIN retry counter : %d %d %d\n", */ - /* info.chvretry[0], info.chvretry[1], info.chvretry[2]); */ - /* tty_fprintf (fp, "Signature counter : %lu\n", info.sig_counter); */ - /* if (info.extcap.kdf) */ - /* { */ - /* tty_fprintf (fp, "KDF setting ......: %s\n", */ - /* info.kdf_do_enabled ? "on" : "off"); */ - /* } */ - /* if (info.extcap.bt) */ - /* { */ - /* tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n", */ - /* info.uif[0] ? "on" : "off", info.uif[1] ? "on" : "off", */ - /* info.uif[2] ? "on" : "off"); */ - /* } */ - /* tty_fprintf (fp, "Signature key ....:"); */ - /* print_shax_fpr (fp, info.fpr1len? info.fpr1:NULL, info.fpr1len); */ - /* if (info.fpr1len && info.fpr1time) */ - /* { */ - /* tty_fprintf (fp, " created ....: %s\n", */ - /* isotimestamp (info.fpr1time)); */ - /* print_keygrip (fp, info.grp1); */ - /* } */ - /* tty_fprintf (fp, "Encryption key....:"); */ - /* print_shax_fpr (fp, info.fpr2len? info.fpr2:NULL, info.fpr2len); */ - /* if (info.fpr2len && info.fpr2time) */ - /* { */ - /* tty_fprintf (fp, " created ....: %s\n", */ - /* isotimestamp (info.fpr2time)); */ - /* print_keygrip (fp, info.grp2); */ - /* } */ - /* tty_fprintf (fp, "Authentication key:"); */ - /* print_shax_fpr (fp, info.fpr3len? info.fpr3:NULL, info.fpr3len); */ - /* if (info.fpr3len && info.fpr3time) */ - /* { */ - /* tty_fprintf (fp, " created ....: %s\n", */ - /* isotimestamp (info.fpr3time)); */ - /* print_keygrip (fp, info.grp3); */ - /* } */ - /* tty_fprintf (fp, "General key info..: "); */ + if (info->apptype == APP_TYPE_OPENPGP) + err = scd_checkpin (info->serialno); + else + err = scd_checkpin (argstr); - /* thefpr = (info.fpr1len? info.fpr1 : info.fpr2len? info.fpr2 : */ - /* info.fpr3len? info.fpr3 : NULL); */ - /* thefprlen = (info.fpr1len? info.fpr1len : info.fpr2len? info.fpr2len : */ - /* info.fpr3len? info.fpr3len : 0); */ - /* /\* If the fingerprint is all 0xff, the key has no associated */ - /* OpenPGP certificate. *\/ */ - /* if ( thefpr && !fpr_is_ff (thefpr, thefprlen) */ - /* && !get_pubkey_byfprint (ctrl, pk, &keyblock, thefpr, thefprlen)) */ - /* { */ - /* print_pubkey_info (ctrl, fp, pk); */ - /* if (keyblock) */ - /* print_card_key_info (fp, keyblock); */ - /* } */ - /* else */ - /* tty_fprintf (fp, "[none]\n"); */ + if (err) + log_error ("verify failed: %s <%s>\n", + gpg_strerror (err), gpg_strsource (err)); + return err; +} - /* release_kbnode (keyblock); */ - /* free_public_key (pk); */ - /* agent_release_card_info (&info); */ + +/* Helper for cmd_name to qyery a part of name. */ +static char * +ask_one_name (const char *prompt) +{ + char *name; + int i; + + for (;;) + { + name = tty_get (prompt); + trim_spaces (name); + tty_kill_prompt (); + if (!*name || *name == CONTROL_D) + { + if (*name == CONTROL_D) + tty_fprintf (NULL, "\n"); + xfree (name); + return NULL; + } + for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++) + ; + + /* The name must be in Latin-1 and not UTF-8 - lacking the code + * to ensure this we restrict it to ASCII. */ + if (name[i]) + tty_printf (_("Error: Only plain ASCII is currently allowed.\n")); + else if (strchr (name, '<')) + tty_printf (_("Error: The \"<\" character may not be used.\n")); + else if (strstr (name, " ")) + tty_printf (_("Error: Double spaces are not allowed.\n")); + else + return name; + xfree (name); + } +} + + +/* The NAME command. */ +static gpg_error_t +cmd_name (card_info_t info, const char *argstr) +{ + gpg_error_t err; + char *surname, *givenname; + char *isoname, *p; + + if (!info) + return print_help + ("name [--clear]\n\n" + "Set the name field of an OpenPGP card. With --clear the stored\n" + "name is cleared off the card.", APP_TYPE_OPENPGP, APP_TYPE_NKS, 0); + + if (info->apptype != APP_TYPE_OPENPGP) + { + log_info ("Note: This is an OpenPGP only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + again: + if (!strcmp (argstr, "--clear")) + isoname = xstrdup (" "); /* No real way to clear; set to space instead. */ + else + { + surname = ask_one_name (_("Cardholder's surname: ")); + givenname = ask_one_name (_("Cardholder's given name: ")); + if (!surname || !givenname || (!*surname && !*givenname)) + { + xfree (surname); + xfree (givenname); + return gpg_error (GPG_ERR_CANCELED); + } + + isoname = xstrconcat (surname, "<<", givenname, NULL); + xfree (surname); + xfree (givenname); + for (p=isoname; *p; p++) + if (*p == ' ') + *p = '<'; + + if (strlen (isoname) > 39 ) + { + log_info (_("Error: Combined name too long " + "(limit is %d characters).\n"), 39); + xfree (isoname); + goto again; + } + } + + err = scd_setattr ("DISP-NAME", isoname, strlen (isoname)); + + xfree (isoname); + return err; +} + + +static gpg_error_t +cmd_url (card_info_t info, const char *argstr) +{ + gpg_error_t err; + char *url; + + if (!info) + return print_help + ("URL [--clear]\n\n" + "Set the URL data object. That data object can be used by\n" + "the FETCH command to retrieve the full public key. The\n" + "option --clear deletes the content of that data object.", + APP_TYPE_OPENPGP, 0); + + if (info->apptype != APP_TYPE_OPENPGP) + { + log_info ("Note: This is an OpenPGP only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + if (!strcmp (argstr, "--clear")) + url = xstrdup (" "); /* No real way to clear; set to space instead. */ + else + { + url = tty_get (_("URL to retrieve public key: ")); + trim_spaces (url); + tty_kill_prompt (); + if (!*url || *url == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + } + + err = scd_setattr ("PUBKEY-URL", url, strlen (url)); + + leave: + xfree (url); + return err; +} + + +/* Fetch the key from the URL given on the card or try to get it from + * the default keyserver. */ +static gpg_error_t +cmd_fetch (card_info_t info) +{ + gpg_error_t err; + + if (!info) + return print_help + ("FETCH\n\n" + "Retrieve a key using the URL data object or if that is missing\n" + "using the fingerprint.", APP_TYPE_OPENPGP, 0); + + if (info->pubkey_url && *info->pubkey_url) + { + /* strlist_t sl = NULL; */ + + /* add_to_strlist (&sl, info.pubkey_url); */ + /* err = keyserver_fetch (ctrl, sl, KEYORG_URL); */ + /* free_strlist (sl); */ + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ + } + else if (info->fpr1len) + { + /* rc = keyserver_import_fprint (ctrl, info.fpr1, info.fpr1len, */ + /* opt.keyserver, 0); */ + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ + } + else + err = gpg_error (GPG_ERR_NO_DATA); + + return err; +} + + +static gpg_error_t +cmd_login (card_info_t info, char *argstr) +{ + gpg_error_t err; + char *data; + size_t datalen; + + if (!info) + return print_help + ("LOGIN [--clear] [< FILE]\n\n" + "Set the login data object. If FILE is given the data is\n" + "is read from that file. This allows for binary data.\n" + "The option --clear deletes the login data.", + APP_TYPE_OPENPGP, 0); + + if (!strcmp (argstr, "--clear")) + { + data = xstrdup (" "); /* kludge. */ + datalen = 1; + } + else if (*argstr == '<') /* Read it from a file */ + { + for (argstr++; spacep (argstr); argstr++) + ; + err = get_data_from_file (argstr, &data, &datalen); + if (err) + goto leave; + } + else + { + data = tty_get (_("Login data (account name): ")); + trim_spaces (data); + tty_kill_prompt (); + if (!*data || *data == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + datalen = strlen (data); + } + + err = scd_setattr ("LOGIN-DATA", data, datalen); + + leave: + xfree (data); + return err; +} + + +static gpg_error_t +cmd_lang (card_info_t info, const char *argstr) +{ + gpg_error_t err; + char *data, *p; + + if (!info) + return print_help + ("LANG [--clear]\n\n" + "Change the language info for the card. This info can be used\n" + "by applications for a personalized greeting. Up to 4 two-digit\n" + "language identifiers can be entered as a preference. The option\n" + "--clear removes all identifiers. GnuPG does not use this info.", + APP_TYPE_OPENPGP, 0); + + if (!strcmp (argstr, "--clear")) + data = xstrdup (" "); /* Note that we need two spaces here. */ + else + { + again: + data = tty_get (_("Language preferences: ")); + trim_spaces (data); + tty_kill_prompt (); + if (!*data || *data == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + if (strlen (data) > 8 || (strlen (data) & 1)) + { + log_info (_("Error: invalid length of preference string.\n")); + xfree (data); + goto again; + } + + for (p=data; *p && *p >= 'a' && *p <= 'z'; p++) + ; + if (*p) + { + log_info (_("Error: invalid characters in preference string.\n")); + xfree (data); + goto again; + } + } + + err = scd_setattr ("DISP-LANG", data, strlen (data)); + + leave: + xfree (data); + return err; +} + + +static gpg_error_t +cmd_salut (card_info_t info, const char *argstr) +{ + gpg_error_t err; + char *data = NULL; + const char *str; + + if (!info) + return print_help + ("SALUT [--clear]\n\n" + "Change the salutation info for the card. This info can be used\n" + "by applications for a personalized greeting. The option --clear\n" + "removes this data object. GnuPG does not use this info.", + APP_TYPE_OPENPGP, 0); + + again: + if (!strcmp (argstr, "--clear")) + str = "9"; + else + { + data = tty_get (_("Salutation (M = Mr., F = Mrs., or space): ")); + trim_spaces (data); + tty_kill_prompt (); + if (*data == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + if (!*data) + str = "9"; + else if ((*data == 'M' || *data == 'm') && !data[1]) + str = "1"; + else if ((*data == 'F' || *data == 'f') && !data[1]) + str = "2"; + else + { + tty_printf (_("Error: invalid response.\n")); + xfree (data); + goto again; + } + } + + err = scd_setattr ("DISP-SEX", str, 1); + leave: + xfree (data); + return err; +} + + +static gpg_error_t +cmd_cafpr (card_info_t info, char *argstr) +{ + gpg_error_t err; + char *data = NULL; + const char *s; + int i, c; + unsigned char fpr[32]; + int fprlen; + int fprno; + int opt_clear = 0; + + if (!info) + return print_help + ("CAFPR [--clear] N\n\n" + "Change the CA fingerprint number N. N must be in the\n" + "range 1 to 3. The option --clear clears the specified\n" + "CA fingerprint N or all of them if N is 0 or not given.", + APP_TYPE_OPENPGP, 0); + + + opt_clear = has_leading_option (argstr, "--clear"); + argstr = skip_options (argstr); + + if (digitp (argstr)) + { + fprno = atoi (argstr); + while (digitp (argstr)) + argstr++; + while (spacep (argstr)) + argstr++; + } + else + fprno = 0; + + if (opt_clear && !fprno) + ; /* Okay: clear all fprs. */ + else if (fprno < 1 || fprno > 3) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + again: + if (opt_clear) + { + memset (fpr, 0, 20); + fprlen = 20; + } + else + { + xfree (data); + data = tty_get (_("CA fingerprint: ")); + trim_spaces (data); + tty_kill_prompt (); + if (!*data || *data == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + for (i=0, s=data; i < sizeof fpr && *s; ) + { + while (spacep(s)) + s++; + if (*s == ':') + s++; + while (spacep(s)) + s++; + c = hextobyte (s); + if (c == -1) + break; + fpr[i++] = c; + s += 2; + } + fprlen = i; + if ((fprlen != 20 && fprlen != 32) || *s) + { + log_error (_("Error: invalid formatted fingerprint.\n")); + goto again; + } + } + + if (!fprno) + { + log_assert (opt_clear); + err = scd_setattr ("CA-FPR-1", fpr, fprlen); + if (!err) + err = scd_setattr ("CA-FPR-2", fpr, fprlen); + if (!err) + err = scd_setattr ("CA-FPR-3", fpr, fprlen); + } + else + err = scd_setattr (fprno==1?"CA-FPR-1": + fprno==2?"CA-FPR-2": + fprno==3?"CA-FPR-3":"x", fpr, fprlen); + + leave: + xfree (data); + return err; +} + + +static gpg_error_t +cmd_privatedo (card_info_t info, char *argstr) +{ + gpg_error_t err; + int opt_clear; + char *do_name = NULL; + char *data = NULL; + size_t datalen; + int do_no; + + if (!info) + return print_help + ("PRIVATEDO [--clear] N [< FILE]\n\n" + "Change the private data object N. N must be in the\n" + "range 1 to 4. If FILE is given the data is is read\n" + "from that file. The option --clear clears the data.", + APP_TYPE_OPENPGP, 0); + + opt_clear = has_leading_option (argstr, "--clear"); + argstr = skip_options (argstr); + + if (digitp (argstr)) + { + do_no = atoi (argstr); + while (digitp (argstr)) + argstr++; + while (spacep (argstr)) + argstr++; + } + else + do_no = 0; + + if (do_no < 1 || do_no > 4) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + do_name = xasprintf ("PRIVATE-DO-%d", do_no); + + if (opt_clear) + { + data = xstrdup (" "); + datalen = 1; + } + else if (*argstr == '<') /* Read it from a file */ + { + for (argstr++; spacep (argstr); argstr++) + ; + err = get_data_from_file (argstr, &data, &datalen); + if (err) + goto leave; + } + else if (*argstr) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + else + { + data = tty_get (_("Private DO data: ")); + trim_spaces (data); + tty_kill_prompt (); + datalen = strlen (data); + if (!*data || *data == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + } + + err = scd_setattr (do_name, data, datalen); + + leave: + xfree (do_name); + xfree (data); + return err; +} + + +static gpg_error_t +cmd_writecert (card_info_t info, char *argstr) +{ + gpg_error_t err; + int opt_clear; + int do_no; + char *data = NULL; + size_t datalen; + + if (!info) + return print_help + ("WRITECERT [--clear] 3 < FILE\n\n" + "Write a certificate for key 3. Unless --clear is given\n" + "the file argement is mandatory. The option --clear removes\n" + "the certificate from the card.", + APP_TYPE_OPENPGP, 0); + + opt_clear = has_leading_option (argstr, "--clear"); + argstr = skip_options (argstr); + + if (digitp (argstr)) + { + do_no = atoi (argstr); + while (digitp (argstr)) + argstr++; + while (spacep (argstr)) + argstr++; + } + else + do_no = 0; + + if (do_no != 3) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + if (opt_clear) + { + data = xstrdup (" "); + datalen = 1; + } + else if (*argstr == '<') /* Read it from a file */ + { + for (argstr++; spacep (argstr); argstr++) + ; + err = get_data_from_file (argstr, &data, &datalen); + if (err) + goto leave; + } + else + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + err = scd_writecert ("OPENPGP.3", data, datalen); + + leave: + xfree (data); + return err; +} + + +static gpg_error_t +cmd_readcert (card_info_t info, char *argstr) +{ + gpg_error_t err; + int do_no; + void *data = NULL; + size_t datalen; + const char *fname; + + if (!info) + return print_help + ("READCERT 3 > FILE\n\n" + "Read the certificate for key 3 and store it in FILE.", + APP_TYPE_OPENPGP, 0); + + argstr = skip_options (argstr); + + if (digitp (argstr)) + { + do_no = atoi (argstr); + while (digitp (argstr)) + argstr++; + while (spacep (argstr)) + argstr++; + } + else + do_no = 0; + + if (do_no != 3) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + if (*argstr == '>') /* Read it from a file */ + { + for (argstr++; spacep (argstr); argstr++) + ; + fname = argstr; + } + else + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + err = scd_readcert ("OPENPGP.3", &data, &datalen); + if (err) + goto leave; + + err = put_data_to_file (fname, data, datalen); + + leave: + xfree (data); + return err; +} + + +static gpg_error_t +cmd_forcesig (card_info_t info) +{ + gpg_error_t err; + int newstate; + + if (!info) + return print_help + ("FORCESIG\n\n" + "Toggle the forcesig flag of an OpenPGP card.", + APP_TYPE_OPENPGP, 0); + + if (info->apptype != APP_TYPE_OPENPGP) + { + log_info ("Note: This is an OpenPGP only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + newstate = !info->chv1_cached; + + err = scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1); + if (err) + goto leave; + + /* Read it back to be sure we have the right toggle state the next + * time. */ + err = scd_getattr ("CHV-STATUS", info); + + leave: + return err; +} + + +/* Helper for cmd_generate. Noe that either 0 or 1 is stored at + * FORCED_CHV1. */ +static gpg_error_t +check_pin_for_key_operation (card_info_t info, int *forced_chv1) +{ + gpg_error_t err = 0; + + *forced_chv1 = !info->chv1_cached; + if (*forced_chv1) + { /* Switch off the forced mode so that during key generation we + * don't get bothered with PIN queries for each self-signature. */ + err = scd_setattr ("CHV-STATUS-1", "\x01", 1); + if (err) + { + log_error ("error clearing forced signature PIN flag: %s\n", + gpg_strerror (err)); + *forced_chv1 = -1; /* Not changed. */ + goto leave; + } + } + + /* Check the PIN now, so that we won't get asked later for each + * binding signature. */ + err = scd_checkpin (info->serialno); + if (err) + log_error ("error checking the PIN: %s\n", gpg_strerror (err)); + + leave: + return err; +} + + +/* Helper for cmd_generate. */ +static void +restore_forced_chv1 (int *forced_chv1) +{ + gpg_error_t err; + + /* Note the possible values stored at FORCED_CHV1: + * 0 - forcesig was not enabled. + * 1 - forcesig was enabled - enable it again. + * -1 - We have not changed anything. */ + if (*forced_chv1 == 1) + { /* Switch back to forced state. */ + err = scd_setattr ("CHV-STATUS-1", "", 1); + if (err) + log_error ("error setting forced signature PIN flag: %s\n", + gpg_strerror (err)); + *forced_chv1 = 0; + } +} + + +static gpg_error_t +cmd_generate (card_info_t info) +{ + gpg_error_t err; + int forced_chv1 = -1; + int want_backup; + char *answer = NULL; + + if (!info) + return print_help + ("GENERATE\n\n" + "Menu to generate a new keys.", + APP_TYPE_OPENPGP, 0); + + if (info->apptype != APP_TYPE_OPENPGP) + { + log_info ("Note: This is an OpenPGP only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + if (info->extcap.ki) + { + xfree (answer); + answer = tty_get (_("Make off-card backup of encryption key? (Y/n) ")); + want_backup = answer_is_yes_no_default (answer, 1/*(default to Yes)*/); + tty_kill_prompt (); + if (*answer == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + } + else + want_backup = 0; + + if ( (info->fpr1len && !fpr_is_zero (info->fpr1, info->fpr1len)) + || (info->fpr2len && !fpr_is_zero (info->fpr2, info->fpr2len)) + || (info->fpr3len && !fpr_is_zero (info->fpr3, info->fpr3len))) + { + tty_printf ("\n"); + log_info (_("Note: keys are already stored on the card!\n")); + tty_printf ("\n"); + answer = tty_get (_("Replace existing keys? (y/N) ")); + tty_kill_prompt (); + if (*answer == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + if (!answer_is_yes_no_default (answer, 0/*(default to No)*/)) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + } + + /* If no displayed name has been set, we assume that this is a fresh + * card and print a hint about the default PINs. */ + if (!info->disp_name || !*info->disp_name) + { + tty_printf ("\n"); + tty_printf (_("Please note that the factory settings of the PINs are\n" + " PIN = '%s' Admin PIN = '%s'\n" + "You should change them using the command --change-pin\n"), + OPENPGP_USER_PIN_DEFAULT, OPENPGP_ADMIN_PIN_DEFAULT); + tty_printf ("\n"); + } + + err = check_pin_for_key_operation (info, &forced_chv1); + if (err) + goto leave; + + /* FIXME: We need to divert to a function which spwans gpg which + * will then create the key. This also requires new features in + * gpg. We might also first create the keys on the card and then + * tell gpg to use them to create the OpenPGP keyblock. */ + /* generate_keypair (ctrl, 1, NULL, info.serialno, want_backup); */ + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + leave: + restore_forced_chv1 (&forced_chv1); + xfree (answer); + return err; +} + + +/* Sub-menu to change a PIN. The presented options may depend on the + * the ALLOW_ADMIN flag. */ +static gpg_error_t +cmd_passwd (card_info_t info, int allow_admin) +{ + gpg_error_t err; + char *answer = NULL; + + if (!info) + return print_help + ("PASSWD\n\n" + "Menu to change or unblock the PINs. Note that the\n" + "presented menu options depend on the type of card\n" + "and whether the admin mode is enabled.", + 0); + + /* Convenience message because we did this in gpg --card-edit too. */ + if (info->apptype == APP_TYPE_OPENPGP) + log_info (_("OpenPGP card no. %s detected\n"), + info->dispserialno? info->dispserialno : info->serialno); + + if (!allow_admin) + { + err = scd_change_pin (1); + if (err) + goto leave; + log_info ("PIN changed.\n"); + } + else if (info->apptype == APP_TYPE_OPENPGP) + { + for (;;) + { + tty_printf ("\n"); + tty_printf ("1 - change PIN\n" + "2 - unblock and set new PIN\n" + "3 - change Admin PIN\n" + "4 - set the Reset Code\n" + "Q - quit\n"); + tty_printf ("\n"); + + err = 0; + xfree (answer); + answer = tty_get (_("Your selection? ")); + tty_kill_prompt (); + if (*answer == CONTROL_D) + break; /* Quit. */ + if (strlen (answer) != 1) + continue; + if (*answer == 'q' || *answer == 'Q') + break; /* Quit. */ + + if (*answer == '1') + { + /* Change PIN (same as the direct thing in non-admin mode). */ + err = scd_change_pin (1); + if (err) + log_error ("Error changing the PIN: %s\n", gpg_strerror (err)); + else + log_info ("PIN changed.\n"); + } + else if (*answer == '2') + { + /* Unblock PIN by setting a new PIN. */ + err = scd_change_pin (101); + if (err) + log_error ("Error unblocking the PIN: %s\n", gpg_strerror(err)); + else + log_info ("PIN unblocked and new PIN set.\n"); + } + else if (*answer == '3') + { + /* Change Admin PIN. */ + err = scd_change_pin (3); + if (err) + log_error ("Error changing the PIN: %s\n", gpg_strerror (err)); + else + log_info ("PIN changed.\n"); + } + else if (*answer == '4') + { + /* Set a new Reset Code. */ + err = scd_change_pin (102); + if (err) + log_error ("Error setting the Reset Code: %s\n", + gpg_strerror (err)); + else + log_info ("Reset Code set.\n"); + } + + } /*end for loop*/ + } + else + { + log_info ("Admin related passwd options not yet supported for '%s'\n", + app_type_string (info->apptype)); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + leave: + xfree (answer); + return err; +} + + +static gpg_error_t +cmd_unblock (card_info_t info) +{ + gpg_error_t err = 0; + + if (!info) + return print_help + ("UNBLOCK\n\n" + "Unblock a PIN using a PUK or Reset Code. Note that OpenPGP\n" + "cards prior to version 2 can't use this; instead the PASSWD\n" + "command can be used to set a new PIN.", + 0); + + if (info->apptype == APP_TYPE_OPENPGP) + log_info (_("OpenPGP card no. %s detected\n"), + info->dispserialno? info->dispserialno : info->serialno); + + if (info->apptype == APP_TYPE_OPENPGP && !info->is_v2) + log_error (_("This command is only available for version 2 cards\n")); + else if (info->apptype == APP_TYPE_OPENPGP && !info->chvretry[1]) + log_error (_("Reset Code not or not anymore available\n")); + else if (info->apptype == APP_TYPE_OPENPGP) + { + err = scd_change_pin (2); + if (!err) + log_info ("PIN changed.\n"); + } + else + log_info ("Unblocking not yet supported for '%s'\n", + app_type_string (info->apptype)); + + return err; +} + + +/* Direct sending of an hex encoded APDU with error printing. */ +static gpg_error_t +send_apdu (const char *hexapdu, const char *desc, unsigned int ignore) +{ + gpg_error_t err; + unsigned int sw; + + err = scd_apdu (hexapdu, &sw); + if (err) + log_error ("sending card command %s failed: %s\n", desc, + gpg_strerror (err)); + else if (!hexapdu || !strcmp (hexapdu, "undefined")) + ; + else if (ignore == 0xffff) + ; /* Ignore all status words. */ + else if (sw != 0x9000) + { + switch (sw) + { + case 0x6285: err = gpg_error (GPG_ERR_OBJ_TERM_STATE); break; + case 0x6982: err = gpg_error (GPG_ERR_BAD_PIN); break; + case 0x6985: err = gpg_error (GPG_ERR_USE_CONDITIONS); break; + default: err = gpg_error (GPG_ERR_CARD); + } + if (!(ignore && ignore == sw)) + log_error ("card command %s failed: %s (0x%04x)\n", desc, + gpg_strerror (err), sw); + } + return err; +} + + +/* Note: On successful execution a redisplay should be scheduled. If + * this function fails the card may be in an unknown state. */ +static gpg_error_t +cmd_factoryreset (card_info_t info) +{ + gpg_error_t err; + char *answer = NULL; + int termstate = 0; + int any_apdu = 0; + int i; + + + if (!info) + return print_help + ("FACTORY-RESET\n\n" + "Do a complete reset of an OpenPGP card. This deletes all\n" + "data and keys and resets the PINs to their default. This\n" + "mainly used by developers with scratch cards. Don't worry,\n" + "you need to confirm before the command proceeds.", + APP_TYPE_OPENPGP, 0); + + if (info->apptype != APP_TYPE_OPENPGP) + { + log_info ("Note: This is an OpenPGP only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + /* The code below basically does the same what this + * gpg-connect-agent script does: + * + * scd reset + * scd serialno undefined + * scd apdu 00 A4 04 00 06 D2 76 00 01 24 01 + * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + * scd apdu 00 e6 00 00 + * scd apdu 00 44 00 00 + * scd reset + * /echo Card has been reset to factory defaults + * + * but tries to find out something about the card first. + */ + + err = scd_learn (info); + if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE + && gpg_err_source (err) == GPG_ERR_SOURCE_SCD) + termstate = 1; + else if (err) + { + log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err)); + goto leave; + } + + if (!termstate) + { + log_info (_("OpenPGP card no. %s detected\n"), + info->dispserialno? info->dispserialno : info->serialno); + if (!(info->status_indicator == 3 || info->status_indicator == 5)) + { + /* Note: We won't see status-indicator 3 here because it is not + * possible to select a card application in termination state. */ + log_error (_("This command is not supported by this card\n")); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + tty_printf ("\n"); + log_info + (_("Note: This command destroys all keys stored on the card!\n")); + tty_printf ("\n"); + xfree (answer); + answer = tty_get (_("Continue? (y/N) ")); + tty_kill_prompt (); + trim_spaces (answer); + if (*answer == CONTROL_D + || !answer_is_yes_no_default (answer, 0/*(default to no)*/)) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + xfree (answer); + answer = tty_get (_("Really do a factory reset? (enter \"yes\") ")); + tty_kill_prompt (); + trim_spaces (answer); + if (strcmp (answer, "yes") && strcmp (answer,_("yes"))) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + any_apdu = 1; + /* We need to select a card application before we can send APDUs + * to the card without scdaemon doing anything on its own. */ + err = send_apdu (NULL, "RESET", 0); + if (err) + goto leave; + err = send_apdu ("undefined", "dummy select ", 0); + if (err) + goto leave; + + /* Select the OpenPGP application. */ + err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0); + if (err) + goto leave; + + /* Do some dummy verifies with wrong PINs to set the retry + * counter to zero. We can't easily use the card version 2.1 + * feature of presenting the admin PIN to allow the terminate + * command because there is no machinery in scdaemon to catch + * the verify command and ask for the PIN when the "APDU" + * command is used. + * Here, the length of dummy wrong PIN is 32-byte, also + * supporting authentication with KDF DO. */ + for (i=0; i < 4; i++) + send_apdu ("0020008120" + "40404040404040404040404040404040" + "40404040404040404040404040404040", "VERIFY", 0xffff); + for (i=0; i < 4; i++) + send_apdu ("0020008320" + "40404040404040404040404040404040" + "40404040404040404040404040404040", "VERIFY", 0xffff); + + /* Send terminate datafile command. */ + err = send_apdu ("00e60000", "TERMINATE DF", 0x6985); + if (err) + goto leave; + } + + any_apdu = 1; + /* Send activate datafile command. This is used without + * confirmation if the card is already in termination state. */ + err = send_apdu ("00440000", "ACTIVATE DF", 0); + if (err) + goto leave; + + /* Finally we reset the card reader once more. */ + err = send_apdu (NULL, "RESET", 0); + if (err) + goto leave; + + /* Then, connect the card again (answer used as a dummy). */ + xfree (answer); answer = NULL; + err = scd_serialno (&answer, NULL); + + leave: + if (err && any_apdu) + { + log_info ("Due to an error the card might be in an inconsistent state\n" + "You should run the LIST command to check this.\n"); + /* FIXME: We need a better solution in the case that the card is + * in a termination state, i.e. the card was removed before the + * activate was sent. The best solution I found with v2.1 + * Zeitcontrol card was to kill scdaemon and the issue this + * sequence with gpg-connect-agent: + * scd reset + * scd serialno undefined + * scd apdu 00A4040006D27600012401 (returns error) + * scd apdu 00440000 + * Then kill scdaemon again and issue: + * scd reset + * scd serialno openpgp + */ + } + xfree (answer); + return err; +} + + +/* Generate KDF data. This is a helper for cmd_kdfsetup. */ +static gpg_error_t +gen_kdf_data (unsigned char *data, int single_salt) +{ + gpg_error_t err; + const unsigned char h0[] = { 0x81, 0x01, 0x03, + 0x82, 0x01, 0x08, + 0x83, 0x04 }; + const unsigned char h1[] = { 0x84, 0x08 }; + const unsigned char h2[] = { 0x85, 0x08 }; + const unsigned char h3[] = { 0x86, 0x08 }; + const unsigned char h4[] = { 0x87, 0x20 }; + const unsigned char h5[] = { 0x88, 0x20 }; + unsigned char *p, *salt_user, *salt_admin; + unsigned char s2k_char; + unsigned int iterations; + unsigned char count_4byte[4]; + + p = data; + + s2k_char = encode_s2k_iterations (agent_get_s2k_count ()); + iterations = S2K_DECODE_COUNT (s2k_char); + count_4byte[0] = (iterations >> 24) & 0xff; + count_4byte[1] = (iterations >> 16) & 0xff; + count_4byte[2] = (iterations >> 8) & 0xff; + count_4byte[3] = (iterations & 0xff); + + memcpy (p, h0, sizeof h0); + p += sizeof h0; + memcpy (p, count_4byte, sizeof count_4byte); + p += sizeof count_4byte; + memcpy (p, h1, sizeof h1); + salt_user = (p += sizeof h1); + gcry_randomize (p, 8, GCRY_STRONG_RANDOM); + p += 8; + + if (single_salt) + salt_admin = salt_user; + else + { + memcpy (p, h2, sizeof h2); + p += sizeof h2; + gcry_randomize (p, 8, GCRY_STRONG_RANDOM); + p += 8; + memcpy (p, h3, sizeof h3); + salt_admin = (p += sizeof h3); + gcry_randomize (p, 8, GCRY_STRONG_RANDOM); + p += 8; + } + + memcpy (p, h4, sizeof h4); + p += sizeof h4; + err = gcry_kdf_derive (OPENPGP_USER_PIN_DEFAULT, + strlen (OPENPGP_USER_PIN_DEFAULT), + GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, + salt_user, 8, iterations, 32, p); + p += 32; + if (!err) + { + memcpy (p, h5, sizeof h5); + p += sizeof h5; + err = gcry_kdf_derive (OPENPGP_ADMIN_PIN_DEFAULT, + strlen (OPENPGP_ADMIN_PIN_DEFAULT), + GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, + salt_admin, 8, iterations, 32, p); + } + + return err; +} + + +static gpg_error_t +cmd_kdfsetup (card_info_t info, char *argstr) +{ + gpg_error_t err; + unsigned char kdf_data[OPENPGP_KDF_DATA_LENGTH_MAX]; + int single = (*argstr != 0); + + if (!info) + return print_help + ("KDF-SETUP\n\n" + "Prepare the OpenPGP card KDF feature for this card.", + APP_TYPE_OPENPGP, 0); + + if (info->apptype != APP_TYPE_OPENPGP) + { + log_info ("Note: This is an OpenPGP only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + if (!info->extcap.kdf) + { + log_error (_("This command is not supported by this card\n")); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + err = gen_kdf_data (kdf_data, single); + if (err) + goto leave; + + err = scd_setattr ("KDF", kdf_data, + single ? OPENPGP_KDF_DATA_LENGTH_MIN + /* */ : OPENPGP_KDF_DATA_LENGTH_MAX); + if (err) + goto leave; + + err = scd_getattr ("KDF", info); + + leave: + return err; } static void -cmd_verify (void) +show_keysize_warning (void) { - /* agent_scd_checkpin (serialnobuf); */ + static int shown; + + if (shown) + return; + shown = 1; + tty_printf + (_("Note: There is no guarantee that the card supports the requested\n" + " key type or size. If the key generation does not succeed,\n" + " please check the documentation of your card to see which\n" + " key types and sizes are supported.\n") + ); } -static void -cmd_name (void) +/* Ask for the size of a card key. NBITS is the current size + * configured for the card. Returns 0 on success and stored the + * chosen key size at R_KEYSIZE; 0 is stored to indicate that the + * default size shall be used. */ +static gpg_error_t +ask_card_rsa_keysize (unsigned int nbits, unsigned int *r_keysize) { - /* change_name (); */ + unsigned int min_nbits = 1024; + unsigned int max_nbits = 4096; + char*answer; + unsigned int req_nbits; + + for (;;) + { + answer = tty_getf (_("What keysize do you want? (%u) "), nbits); + trim_spaces (answer); + tty_kill_prompt (); + if (*answer == CONTROL_D) + { + xfree (answer); + return gpg_error (GPG_ERR_CANCELED); + } + req_nbits = *answer? atoi (answer): nbits; + xfree (answer); + + if (req_nbits != nbits && (req_nbits % 32) ) + { + req_nbits = ((req_nbits + 31) / 32) * 32; + tty_printf (_("rounded up to %u bits\n"), req_nbits); + } + + if (req_nbits == nbits) + { + /* Use default. */ + *r_keysize = 0; + return 0; + } + + if (req_nbits < min_nbits || req_nbits > max_nbits) + { + tty_printf (_("%s keysizes must be in the range %u-%u\n"), + "RSA", min_nbits, max_nbits); + } + else + { + *r_keysize = req_nbits; + return 0; + } + } } -static void -cmd_url (void) +/* Ask for the key attribute of a card key. CURRENT is the current + * attribute configured for the card. KEYNO is the number of the key + * used to select the prompt. Stores NULL at result to use the + * default attribute or stores the selected attribute structure at + * RESULT. On error an error code is returned. */ +static gpg_error_t +ask_card_keyattr (int keyno, const struct key_attr *current, + struct key_attr **result) { - /* change_url (); */ + gpg_error_t err; + struct key_attr *key_attr = NULL; + char *answer = NULL; + int selection; + + *result = NULL; + + key_attr = xcalloc (1, sizeof *key_attr); + + tty_printf (_("Changing card key attribute for: ")); + if (keyno == 0) + tty_printf (_("Signature key\n")); + else if (keyno == 1) + tty_printf (_("Encryption key\n")); + else + tty_printf (_("Authentication key\n")); + + tty_printf (_("Please select what kind of key you want:\n")); + tty_printf (_(" (%d) RSA\n"), 1 ); + tty_printf (_(" (%d) ECC\n"), 2 ); + + for (;;) + { + xfree (answer); + answer = tty_get (_("Your selection? ")); + trim_spaces (answer); + tty_kill_prompt (); + if (!*answer || *answer == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + selection = *answer? atoi (answer) : 0; + + if (selection == 1 || selection == 2) + break; + else + tty_printf (_("Invalid selection.\n")); + } + + + if (selection == 1) + { + unsigned int nbits, result_nbits; + + if (current->algo == PUBKEY_ALGO_RSA) + nbits = current->nbits; + else + nbits = 2048; + + err = ask_card_rsa_keysize (nbits, &result_nbits); + if (err) + goto leave; + if (result_nbits == 0) + { + if (current->algo == PUBKEY_ALGO_RSA) + { + xfree (key_attr); + key_attr = NULL; + } + else + result_nbits = nbits; + } + + if (key_attr) + { + key_attr->algo = PUBKEY_ALGO_RSA; + key_attr->nbits = result_nbits; + } + } + else if (selection == 2) + { + const char *curve; + /* const char *oid_str; */ + int algo; + + if (current->algo == PUBKEY_ALGO_RSA) + { + if (keyno == 1) /* Encryption key */ + algo = PUBKEY_ALGO_ECDH; + else /* Signature key or Authentication key */ + algo = PUBKEY_ALGO_ECDSA; + curve = NULL; + } + else + { + algo = current->algo; + curve = current->curve; + } + + err = GPG_ERR_NOT_IMPLEMENTED; + goto leave; + /* FIXME: We need to mve the ask_cure code out to common or + * provide another sultion. */ + /* curve = ask_curve (&algo, NULL, curve); */ + /* if (curve) */ + /* { */ + /* key_attr->algo = algo; */ + /* oid_str = openpgp_curve_to_oid (curve, NULL); */ + /* key_attr->curve = openpgp_oid_to_curve (oid_str, 0); */ + /* } */ + /* else */ + /* { */ + /* xfree (key_attr); */ + /* key_attr = NULL; */ + /* } */ + } + else + { + err = gpg_error (GPG_ERR_BUG); + goto leave; + } + + /* Tell the user what we are going to do. */ + if (key_attr->algo == PUBKEY_ALGO_RSA) + { + tty_printf (_("The card will now be re-configured" + " to generate a key of %u bits\n"), key_attr->nbits); + } + else if (key_attr->algo == PUBKEY_ALGO_ECDH + || key_attr->algo == PUBKEY_ALGO_ECDSA + || key_attr->algo == PUBKEY_ALGO_EDDSA) + { + tty_printf (_("The card will now be re-configured" + " to generate a key of type: %s\n"), key_attr->curve); + } + show_keysize_warning (); + + *result = key_attr; + key_attr = NULL; + + leave: + xfree (key_attr); + xfree (answer); + return err; } -static void -cmd_fetch (void) +/* Change the key attribute of key KEYNO (0..2) and show an error + * message if that fails. */ +static gpg_error_t +do_change_keyattr (int keyno, const struct key_attr *key_attr) { - /* fetch_url (); */ + gpg_error_t err = 0; + char args[100]; + + if (key_attr->algo == PUBKEY_ALGO_RSA) + snprintf (args, sizeof args, "--force %d 1 rsa%u", keyno+1, + key_attr->nbits); + else if (key_attr->algo == PUBKEY_ALGO_ECDH + || key_attr->algo == PUBKEY_ALGO_ECDSA + || key_attr->algo == PUBKEY_ALGO_EDDSA) + snprintf (args, sizeof args, "--force %d %d %s", + keyno+1, key_attr->algo, key_attr->curve); + else + { + /* FIXME: Above we use opnepgp algo names but in the error + * message we use the gcrypt names. We should settle for a + * consistent solution. */ + log_error (_("public key algorithm %d (%s) is not supported\n"), + key_attr->algo, gcry_pk_algo_name (key_attr->algo)); + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + goto leave; + } + + err = scd_setattr ("KEY-ATTR", args, strlen (args)); + if (err) + log_error (_("error changing key attribute for key %d: %s\n"), + keyno+1, gpg_strerror (err)); + leave: + return err; } -static void -cmd_login (char *arg_string) +static gpg_error_t +cmd_keyattr (card_info_t info, char *argstr) { - /* change_login (arg_string); */ + gpg_error_t err = 0; + int keyno; + struct key_attr *key_attr = NULL; + + (void)argstr; + + if (!info) + return print_help + ("KEY-ATTR\n\n" + "Menu to change the key attributes of an OpenPGP card.", + APP_TYPE_OPENPGP, 0); + + if (info->apptype != APP_TYPE_OPENPGP) + { + log_info ("Note: This is an OpenPGP only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + if (!(info->is_v2 && info->extcap.aac)) + { + log_error (_("This command is not supported by this card\n")); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + for (keyno = 0; keyno < DIM (info->key_attr); keyno++) + { + xfree (key_attr); + key_attr = NULL; + err = ask_card_keyattr (keyno, &info->key_attr[keyno], &key_attr); + if (err) + goto leave; + + err = do_change_keyattr (keyno, key_attr); + if (err) + { + /* Error: Better read the default key attribute again. */ + log_debug ("FIXME\n"); + /* Ask again for this key. */ + keyno--; + } + } + + leave: + xfree (key_attr); + return err; } -static void -cmd_lang (void) +static gpg_error_t +cmd_uif (card_info_t info, char *argstr) { - /* change_lang (); */ -} + gpg_error_t err; + int keyno; + + if (!info) + return print_help + ("UIF N [on|off|permanent]\n\n" + "Change the User Interaction Flag. N must in the range 1 to 3.", + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); + + argstr = skip_options (argstr); + + if (digitp (argstr)) + { + keyno = atoi (argstr); + while (digitp (argstr)) + argstr++; + while (spacep (argstr)) + argstr++; + } + else + keyno = 0; + + if (keyno < 1 || keyno > 3) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } -static void -cmd_salut (void) -{ - /* change_salut (); */ -} + err = GPG_ERR_NOT_IMPLEMENTED; - -static void -cmd_cafpr (int arg_number) -{ - if ( arg_number < 1 || arg_number > 3 ) - tty_printf ("usage: cafpr N\n" - " 1 <= N <= 3\n"); - /* else */ - /* change_cafpr (arg_number); */ -} - - -static void -cmd_privatedo (int arg_number, char *arg_string) -{ - if ( arg_number < 1 || arg_number > 4 ) - tty_printf ("usage: privatedo N\n" - " 1 <= N <= 4\n"); - /* else */ - /* change_private_do (arg_string, arg_number); */ -} - - -static void -cmd_writecert (int arg_number, char *arg_rest) -{ - if ( arg_number != 3 ) - tty_printf ("usage: writecert 3 < FILE\n"); - /* else */ - /* change_cert (arg_rest); */ -} - - -static void -cmd_readcert (int arg_number, char *arg_rest) -{ - if ( arg_number != 3 ) - tty_printf ("usage: readcert 3 > FILE\n"); - /* else */ - /* read_cert (arg_rest); */ -} - - -static void -cmd_forcesig (void) -{ - /* toggle_forcesig (); */ -} - - -static void -cmd_generate (void) -{ - /* generate_card_keys (); */ -} - - -static void -cmd_passwd (int allow_admin) -{ - /* change_pin (0, allow_admin); */ -} - - -static void -cmd_unblock (int allow_admin) -{ - /* change_pin (1, allow_admin); */ -} - - -static void -cmd_factoryreset (void) -{ - /* factory_reset (); */ -} - - -static void -cmd_kdfsetup (char *argstring) -{ - /* kdf_setup (arg_string); */ -} - - -static void -cmd_keyattr (void) -{ - /* key_attr (); */ -} - - -static void -cmd_uif (int arg_number, char *arg_rest) -{ - if ( arg_number < 1 || arg_number > 3 ) - tty_printf ("usage: uif N [on|off|permanent]\n" - " 1 <= N <= 3\n"); - /* else */ - /* uif (arg_number, arg_rest); */ + leave: + return err; } @@ -629,7 +2369,7 @@ cmd_uif (int arg_number, char *arg_rest) enum cmdids { cmdNOP = 0, - cmdQUIT, cmdADMIN, cmdHELP, cmdLIST, cmdDEBUG, cmdVERIFY, + cmdQUIT, cmdADMIN, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY, cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET, cmdKDFSETUP, @@ -651,13 +2391,13 @@ static struct { "?" , cmdHELP , 0, NULL }, { "list" , cmdLIST , 0, N_("list all available data")}, { "l" , cmdLIST , 0, NULL }, - { "debug" , cmdDEBUG , 0, NULL }, { "name" , cmdNAME , 1, N_("change card holder's name")}, { "url" , cmdURL , 1, N_("change URL to retrieve key")}, { "fetch" , cmdFETCH , 0, N_("fetch the key specified in the card URL")}, { "login" , cmdLOGIN , 1, N_("change the login name")}, { "lang" , cmdLANG , 1, N_("change the language preferences")}, { "salutation",cmdSALUT, 1, N_("change card holder's salutation")}, + { "salut" , cmdSALUT, 1, NULL }, { "cafpr" , cmdCAFPR , 1, N_("change a CA fingerprint")}, { "forcesig", cmdFORCESIG, 1, N_("toggle the signature force PIN flag")}, { "generate", cmdGENERATE, 1, N_("generate new keys")}, @@ -669,9 +2409,9 @@ static struct { "key-attr", cmdKEYATTR, 1, N_("change the key attribute")}, { "uif", cmdUIF, 1, N_("change the User Interaction Flag")}, /* Note, that we do not announce these command yet. */ - { "privatedo", cmdPRIVATEDO, 0, NULL }, - { "readcert", cmdREADCERT, 0, NULL }, - { "writecert", cmdWRITECERT, 1, NULL }, + { "privatedo", cmdPRIVATEDO, 0, N_("change a private data object")}, + { "readcert", cmdREADCERT, 0, N_("read a certificate from a data object")}, + { "writecert", cmdWRITECERT, 1, N_("store a certificate to a data object")}, { NULL, cmdINVCMD, 0, NULL } }; @@ -680,41 +2420,75 @@ static struct static void interactive_loop (void) { + gpg_error_t err; char *answer = NULL; /* The input line. */ enum cmdids cmd = cmdNOP; /* The command. */ int cmd_admin_only; /* The command is an admin only command. */ - int arg_number; /* The first argument as a number. */ - char *arg_string = ""; /* The first argument as a string. */ - char *arg_rest = ""; /* The remaining arguments. */ + char *argstr; /* The argument as a string. */ int redisplay = 1; /* Whether to redisplay the main info. */ int allow_admin = 0; /* Whether admin commands are allowed. */ - char serialnobuf[50]; + char *help_arg = NULL; /* Argument of the HELP command. */ + struct card_info_s info_buffer; + card_info_t info = &info_buffer; char *p; int i; + /* In the interactive mode we do not want to print the program prefix. */ + log_set_prefix (NULL, 0); + for (;;) { - - tty_printf ("\n"); - if (redisplay) + if (help_arg) { - print_card_status (serialnobuf, DIM (serialnobuf)); - tty_printf("\n"); + /* Clear info to indicate helpmode */ + info = NULL; + } + else if (!info) + { + /* Get out of help. */ + info = &info_buffer; + help_arg = NULL; redisplay = 0; - } - - do + } + else if (redisplay) { - xfree (answer); - tty_enable_completion (command_completion); - answer = tty_get (_("gpg/card> ")); - tty_kill_prompt(); - tty_disable_completion (); - trim_spaces(answer); + err = scd_learn (info); + if (err) + { + log_error ("Error reading card: %s\n", gpg_strerror (err)); + } + else + { + list_card (info); + tty_printf("\n"); + redisplay = 0; + } } - while ( *answer == '#' ); - arg_number = 0; + if (!info) + { + /* Copy the pending help arg into our answer. Noe that + * help_arg points into answer. */ + p = xstrdup (help_arg); + help_arg = NULL; + xfree (answer); + answer = p; + } + else + { + do + { + xfree (answer); + tty_enable_completion (command_completion); + answer = tty_get (_("gpg/card> ")); + tty_kill_prompt(); + tty_disable_completion (); + trim_spaces(answer); + } + while ( *answer == '#' ); + } + + argstr = NULL; cmd_admin_only = 0; if (!*answer) cmd = cmdLIST; /* We default to the list command */ @@ -722,18 +2496,11 @@ interactive_loop (void) cmd = cmdQUIT; else { - if ((p=strchr (answer,' '))) + if ((argstr = strchr (answer,' '))) { - *p++ = 0; + *argstr++ = 0; trim_spaces (answer); - trim_spaces (p); - arg_number = atoi (p); - arg_string = p; - arg_rest = p; - while (digitp (arg_rest)) - arg_rest++; - while (spacep (arg_rest)) - arg_rest++; + trim_spaces (argstr); } for (i=0; cmds[i].name; i++ ) @@ -744,34 +2511,104 @@ interactive_loop (void) cmd_admin_only = cmds[i].admin_only; } - if (!allow_admin && cmd_admin_only) - { - tty_printf ("\n"); - tty_printf (_("Admin-only command\n")); - continue; + /* Make sure we have valid strings for the args. They are + * allowed to be modifed and must thus point to a buffer. */ + if (!argstr) + argstr = answer + strlen (answer); + + if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP)) + { + /* If redisplay is set we know that there was an error reading + * the card. In this case we force a LIST command to retry. */ + if (!info) + ; /* In help mode. */ + else if (redisplay) + { + cmd = cmdLIST; + cmd_admin_only = 0; + } + else if (!info->serialno) + { + /* Without a serial number most commands won't work. + * Catch it here. */ + tty_printf ("\n"); + tty_printf ("Serial number missing\n"); + continue; + } + else if (!allow_admin && cmd_admin_only) + { + tty_printf ("\n"); + tty_printf (_("Admin-only command\n")); + continue; + } } + err = 0; switch (cmd) { case cmdNOP: + if (!info) + print_help ("NOP\n\n" + "Dummy command.", 0); break; case cmdQUIT: - goto leave; + if (!info) + print_help ("QUIT\n\n" + "Leave this tool.", 0); + else + { + tty_printf ("\n"); + goto leave; + } + break; case cmdHELP: - for (i=0; cmds[i].name; i++ ) - if(cmds[i].desc - && (!cmds[i].admin_only || (cmds[i].admin_only && allow_admin))) - tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); + if (!info) + print_help ("HELP [command]\n\n" + "Show all commands. With an argument show help\n" + "for that command.", 0); + else if (*argstr) + help_arg = argstr; /* Trigger help for a command. */ + else + { + tty_printf + ("List of commands (\"help \" for details):\n"); + for (i=0; cmds[i].name; i++ ) + if(cmds[i].desc + && (!cmds[i].admin_only + || (cmds[i].admin_only && allow_admin))) + tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); + } + break; + + case cmdLIST: + if (!info) + print_help ("LIST\n\n" + "Show content of the card.", 0); + else + { + /* Actual work is done by the redisplay code block. */ + redisplay = 1; + } + break; + + case cmdRESET: + if (!info) + print_help ("RESET\n\n" + "Send a RESET to the card daemon.", 0); + else + { + err = scd_apdu (NULL, NULL); + } break; case cmdADMIN: - if ( !strcmp (arg_string, "on") ) + if ( !strcmp (argstr, "on") ) allow_admin = 1; - else if ( !strcmp (arg_string, "off") ) + else if ( !strcmp (argstr, "off") ) allow_admin = 0; - else if ( !strcmp (arg_string, "verify") ) + else if ( !strcmp (argstr, "verify") ) { /* Force verification of the Admin Command. However, this is only done if the retry counter is at initial @@ -790,26 +2627,33 @@ interactive_loop (void) tty_printf(_("Admin commands are not allowed\n")); break; - case cmdVERIFY: cmd_verify (); redisplay = 1; break; - case cmdLIST: redisplay = 1; break; - case cmdNAME: cmd_name (); break; - case cmdURL: cmd_url (); break; - case cmdFETCH: cmd_fetch (); break; - case cmdLOGIN: cmd_login (arg_string); break; - case cmdLANG: cmd_lang (); break; - case cmdSALUT: cmd_salut (); break; - case cmdCAFPR: cmd_cafpr (arg_number); break; - case cmdPRIVATEDO: cmd_privatedo (arg_number, arg_string); break; - case cmdWRITECERT: cmd_writecert (arg_number, arg_rest); break; - case cmdREADCERT: cmd_readcert (arg_number, arg_rest); break; - case cmdFORCESIG: cmd_forcesig (); break; - case cmdGENERATE: cmd_generate (); break; - case cmdPASSWD: cmd_passwd (allow_admin); break; - case cmdUNBLOCK: cmd_unblock (allow_admin); break; - case cmdFACTORYRESET: cmd_factoryreset (); break; - case cmdKDFSETUP: cmd_kdfsetup (arg_string); break; - case cmdKEYATTR: cmd_keyattr (); break; - case cmdUIF: cmd_uif (arg_number, arg_rest); break; + case cmdVERIFY: + err = cmd_verify (info, argstr); + if (!err) + redisplay = 1; + break; + case cmdNAME: err = cmd_name (info, argstr); break; + case cmdURL: err = cmd_url (info, argstr); break; + case cmdFETCH: err = cmd_fetch (info); break; + case cmdLOGIN: err = cmd_login (info, argstr); break; + case cmdLANG: err = cmd_lang (info, argstr); break; + case cmdSALUT: err = cmd_salut (info, argstr); break; + case cmdCAFPR: err = cmd_cafpr (info, argstr); break; + case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; + case cmdWRITECERT: err = cmd_writecert (info, argstr); break; + case cmdREADCERT: err = cmd_readcert (info, argstr); break; + case cmdFORCESIG: err = cmd_forcesig (info); break; + case cmdGENERATE: err = cmd_generate (info); break; + case cmdPASSWD: err = cmd_passwd (info, allow_admin); break; + case cmdUNBLOCK: err = cmd_unblock (info); break; + case cmdFACTORYRESET: + err = cmd_factoryreset (info); + if (!err) + redisplay = 1; + break; + case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; + case cmdKEYATTR: err = cmd_keyattr (info, argstr); break; + case cmdUIF: err = cmd_uif (info, argstr); break; case cmdINVCMD: default: @@ -817,9 +2661,25 @@ interactive_loop (void) tty_printf (_("Invalid command (try \"help\")\n")); break; } /* End command switch. */ + + if (gpg_err_code (err) == GPG_ERR_CANCELED) + tty_fprintf (NULL, "\n"); + else if (err) + { + const char *s = "?"; + for (i=0; cmds[i].name; i++ ) + if (cmd == cmds[i].id) + { + s = cmds[i].name; + break; + } + log_error ("Command '%s' failed: %s\n", s, gpg_strerror (err)); + } + } /* End of main menu loop. */ leave: + release_card_info (info); xfree (answer); } From 02a2633a7f0b7d91aa48ea615fb3a0edfd6ed6bb Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Mon, 28 Jan 2019 12:58:13 +0900 Subject: [PATCH 132/235] agent: Clear bogus pinentry cache, when it causes an error. * agent/agent.h (PINENTRY_STATUS_*): Expose to public. (struct pin_entry_info_s): Add status. * agent/call-pinentry.c (agent_askpin): Clearing the ->status before the loop, let the assuan_transact set ->status. When failure with PINENTRY_STATUS_PASSWORD_FROM_CACHE, it returns soon. * agent/findkey.c (unprotect): Clear the pinentry cache, when it causes an error. -- GnuPG-bug-id: 4348 Signed-off-by: NIIBE Yutaka --- agent/agent.h | 11 ++++++++++- agent/call-pinentry.c | 37 ++++++++++++++++++------------------- agent/findkey.c | 12 +++++++++++- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/agent/agent.h b/agent/agent.h index 05080f1f2..ee5a31eef 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -266,6 +266,14 @@ struct server_control_s }; +/* Status of pinentry. */ +enum + { + PINENTRY_STATUS_CLOSE_BUTTON = 1 << 0, + PINENTRY_STATUS_PIN_REPEATED = 1 << 8, + PINENTRY_STATUS_PASSWORD_FROM_CACHE = 1 << 9 + }; + /* Information pertaining to pinentry requests. */ struct pin_entry_info_s { @@ -275,7 +283,8 @@ struct pin_entry_info_s int failed_tries; /* Number of tries so far failed. */ int with_qualitybar; /* Set if the quality bar should be displayed. */ int with_repeat; /* Request repetition of the passphrase. */ - int repeat_okay; /* Repetition worked. */ + int repeat_okay; /* Repetition worked. */ + unsigned int status; /* Status. */ gpg_error_t (*check_cb)(struct pin_entry_info_s *); /* CB used to check the PIN */ void *check_cb_arg; /* optional argument which might be of use in the CB */ diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index 38c01e26f..541a4a3f1 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -894,13 +894,6 @@ setup_qualitybar (ctrl_t ctrl) return 0; } -enum - { - PINENTRY_STATUS_CLOSE_BUTTON = 1 << 0, - PINENTRY_STATUS_PIN_REPEATED = 1 << 8, - PINENTRY_STATUS_PASSWORD_FROM_CACHE = 1 << 9 - }; - /* Check the button_info line for a close action. Also check for the PIN_REPEATED flag. */ static gpg_error_t @@ -965,7 +958,6 @@ agent_askpin (ctrl_t ctrl, const char *errtext = NULL; int is_pin = 0; int saveflag; - unsigned int pinentry_status; if (opt.batch) return 0; /* fixme: we should return BAD PIN */ @@ -1076,6 +1068,7 @@ agent_askpin (ctrl_t ctrl, pininfo->with_repeat = 0; /* Pinentry does not support it. */ } pininfo->repeat_okay = 0; + pininfo->status = 0; for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++) { @@ -1109,10 +1102,9 @@ agent_askpin (ctrl_t ctrl, saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); assuan_begin_confidential (entry_ctx); - pinentry_status = 0; rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm, inq_quality, entry_ctx, - pinentry_status_cb, &pinentry_status); + pinentry_status_cb, &pininfo->status); assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag); /* Most pinentries out in the wild return the old Assuan error code for canceled which gets translated to an assuan Cancel error and @@ -1124,7 +1116,7 @@ agent_askpin (ctrl_t ctrl, /* Change error code in case the window close button was clicked to cancel the operation. */ - if ((pinentry_status & PINENTRY_STATUS_CLOSE_BUTTON) + if ((pininfo->status & PINENTRY_STATUS_CLOSE_BUTTON) && gpg_err_code (rc) == GPG_ERR_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED); @@ -1151,12 +1143,19 @@ agent_askpin (ctrl_t ctrl, /* More checks by utilizing the optional callback. */ pininfo->cb_errtext = NULL; rc = pininfo->check_cb (pininfo); - if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE - && pininfo->cb_errtext) - errtext = pininfo->cb_errtext; - else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE - || gpg_err_code (rc) == GPG_ERR_BAD_PIN) - errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase")); + /* When pinentry cache causes an error, return now. */ + if (rc + && (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) + return unlock_pinentry (ctrl, rc); + + if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE) + { + if (pininfo->cb_errtext) + errtext = pininfo->cb_errtext; + else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE + || gpg_err_code (rc) == GPG_ERR_BAD_PIN) + errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase")); + } else if (rc) return unlock_pinentry (ctrl, rc); } @@ -1164,12 +1163,12 @@ agent_askpin (ctrl_t ctrl, if (!errtext) { if (pininfo->with_repeat - && (pinentry_status & PINENTRY_STATUS_PIN_REPEATED)) + && (pininfo->status & PINENTRY_STATUS_PIN_REPEATED)) pininfo->repeat_okay = 1; return unlock_pinentry (ctrl, 0); /* okay, got a PIN or passphrase */ } - if ((pinentry_status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) + if ((pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) /* The password was read from the cache. Don't count this against the retry count. */ pininfo->failed_tries --; diff --git a/agent/findkey.c b/agent/findkey.c index 78c3b1a47..89a18fa9e 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -632,7 +632,17 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, pi->check_cb_arg = &arg; rc = agent_askpin (ctrl, desc_text, NULL, NULL, pi, hexgrip, cache_mode); - if (!rc) + if (rc) + { + if ((pi->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) + { + log_error ("Clearing pinentry cache which caused error %s\n", + gpg_strerror (rc)); + + agent_clear_passphrase (ctrl, hexgrip, cache_mode); + } + } + else { assert (arg.unprotected_key); if (arg.change_required) From 237880175f59d372011cd2e20bb49726eeccf058 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 29 Jan 2019 08:48:53 +0100 Subject: [PATCH 133/235] card: Make printing of key information more flexible. * tools/card-tool-misc.c: New. * tools/card-tool.h: Rewored data structures for key infos. * tools/gpg-card-tool.c: Ditto. * tools/card-call-scd.c: Ditto. -- Note that this also changes the way the key information is printed. Formerly we printed it like: Signature key ....: created ....: keygrip ... : now we do: Signature key ....: fingerprint : created ....: This is because a keygrip is always available but a fingerprint and the creation date are properties of an OpenPGP card. A standard way of listing keys is better than one depending on the type of card. Signed-off-by: Werner Koch --- tools/Makefile.am | 8 ++- tools/card-call-scd.c | 150 ++++++++++++++++++++++++++++++++--------- tools/card-tool-misc.c | 44 ++++++++++++ tools/card-tool.h | 37 ++++++---- tools/gpg-card-tool.c | 140 +++++++++++++++++++++++++++----------- 5 files changed, 293 insertions(+), 86 deletions(-) create mode 100644 tools/card-tool-misc.c diff --git a/tools/Makefile.am b/tools/Makefile.am index e29e6a281..f74221b2d 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -123,7 +123,13 @@ gpg_connect_agent_LDADD = ../common/libgpgrl.a $(common_libs) \ $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ $(gpg_connect_agent_rc_objs) -gpg_card_tool_SOURCES = gpg-card-tool.c card-tool.h card-call-scd.c + +gpg_card_tool_SOURCES = \ + gpg-card-tool.c \ + card-tool.h \ + card-call-scd.c \ + card-tool-misc.c + gpg_card_tool_LDADD = ../common/libgpgrl.a $(common_libs) \ $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ $(GPG_ERROR_LIBS) \ diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c index 7df7861d3..abf35ed17 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -132,6 +132,7 @@ release_card_info (card_info_t info) { int i; + if (!info) return; @@ -145,12 +146,18 @@ release_card_info (card_info_t info) xfree (info->pubkey_url); info->pubkey_url = NULL; xfree (info->login_data); info->login_data = NULL; info->cafpr1len = info->cafpr2len = info->cafpr3len = 0; - info->fpr1len = info->fpr2len = info->fpr3len = 0; for (i=0; i < DIM(info->private_do); i++) { xfree (info->private_do[i]); info->private_do[i] = NULL; } + while (info->kinfo) + { + key_info_t kinfo = info->kinfo->next; + xfree (info->kinfo); + info->kinfo = kinfo; + } + } @@ -534,6 +541,48 @@ get_serialno_cb (void *opaque, const char *line) } + +/* For historical reasons OpenPGP cards simply use the numbers 1 to 3 + * for the . Other cards and future versions of + * scd/app-openpgp.c may print the full keyref; i.e. "OpenPGP.1" + * instead of "1". This is a helper to cope with that. */ +static const char * +parse_keyref_helper (const char *string) +{ + if (*string == '1' && spacep (string+1)) + return "OPENPGP.1"; + else if (*string == '2' && spacep (string+1)) + return "OPENPGP.2"; + else if (*string == '3' && spacep (string+1)) + return "OPENPGP.3"; + else + return string; +} + + +/* Create a new key info object with KEYREF. All fields but the + * keyref are zeroed out. Never returns NULL. The created object is + * appended to the list at INFO. */ +static key_info_t +create_kinfo (card_info_t info, const char *keyref) +{ + key_info_t kinfo, ki; + + kinfo = xcalloc (1, sizeof *kinfo + strlen (keyref)); + strcpy (kinfo->keyref, keyref); + + if (!info->kinfo) + info->kinfo = kinfo; + else + { + for (ki=info->kinfo; ki->next; ki = ki->next) + ; + ki->next = kinfo; + } + return kinfo; +} + + /* The status callback to handle the LEARN and GETATTR commands. */ static gpg_error_t learn_status_cb (void *opaque, const char *line) @@ -541,6 +590,10 @@ learn_status_cb (void *opaque, const char *line) struct card_info_s *parm = opaque; const char *keyword = line; int keywordlen; + char *line_buffer = NULL; /* In case we need a copy. */ + char *pline; + key_info_t kinfo; + const char *keyref; int i; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) @@ -635,18 +688,31 @@ learn_status_cb (void *opaque, const char *line) } else if (!memcmp (keyword, "KEY-FPR", keywordlen)) { - int no = atoi (line); + /* The format of such a line is: + * KEY-FPR + */ + const char *fpr; - while (*line && !spacep (line)) - line++; - while (spacep (line)) - line++; - if (no == 1) - parm->fpr1len = unhexify_fpr (line, parm->fpr1, sizeof parm->fpr1); - else if (no == 2) - parm->fpr2len = unhexify_fpr (line, parm->fpr2, sizeof parm->fpr2); - else if (no == 3) - parm->fpr3len = unhexify_fpr (line, parm->fpr3, sizeof parm->fpr3); + line_buffer = pline = xstrdup (line); + + keyref = parse_keyref_helper (pline); + while (*pline && !spacep (pline)) + pline++; + if (*pline) + *pline++ = 0; /* Terminate keyref. */ + while (spacep (pline)) /* Skip to the fingerprint. */ + pline++; + fpr = pline; + + /* Check whether we already have an item for the keyref. */ + kinfo = find_kinfo (parm, keyref); + if (!kinfo) /* No: new entry. */ + kinfo = create_kinfo (parm, keyref); + else /* Existing entry - clear the fpr. */ + memset (kinfo->fpr, 0, sizeof kinfo->fpr); + + /* Set or update or the fingerprint. */ + kinfo->fprlen = unhexify_fpr (fpr, kinfo->fpr, sizeof kinfo->fpr); } break; @@ -664,17 +730,28 @@ learn_status_cb (void *opaque, const char *line) } else if (!memcmp (keyword, "KEY-TIME", keywordlen)) { - int no = atoi (line); - while (* line && !spacep (line)) - line++; - while (spacep (line)) - line++; - if (no == 1) - parm->fpr1time = strtoul (line, NULL, 10); - else if (no == 2) - parm->fpr2time = strtoul (line, NULL, 10); - else if (no == 3) - parm->fpr3time = strtoul (line, NULL, 10); + /* The format of such a line is: + * KEY-TIME + */ + const char *timestamp; + + line_buffer = pline = xstrdup (line); + + keyref = parse_keyref_helper (pline); + while (*pline && !spacep (pline)) + pline++; + if (*pline) + *pline++ = 0; /* Terminate keyref. */ + while (spacep (pline)) /* Skip to the timestamp. */ + pline++; + timestamp = pline; + + /* Check whether we already have an item for the keyref. */ + kinfo = find_kinfo (parm, keyref); + if (!kinfo) /* No: new entry. */ + kinfo = create_kinfo (parm, keyref); + + kinfo->created = strtoul (timestamp, NULL, 10); } else if (!memcmp (keyword, "KEY-ATTR", keywordlen)) { @@ -767,21 +844,29 @@ learn_status_cb (void *opaque, const char *line) } else if (!memcmp (keyword, "KEYPAIRINFO", keywordlen)) { + /* The format of such a line is: + * KEYPARINFO + */ const char *hexgrp = line; - int no; while (*line && !spacep (line)) line++; while (spacep (line)) line++; - if (strncmp (line, "OPENPGP.", 8)) - ; - else if ((no = atoi (line+8)) == 1) - unhexify_fpr (hexgrp, parm->grp1, sizeof parm->grp1); - else if (no == 2) - unhexify_fpr (hexgrp, parm->grp2, sizeof parm->grp2); - else if (no == 3) - unhexify_fpr (hexgrp, parm->grp3, sizeof parm->grp3); + + keyref = line; + + /* Check whether we already have an item for the keyref. */ + kinfo = find_kinfo (parm, keyref); + if (!kinfo) /* New entry. */ + kinfo = create_kinfo (parm, keyref); + else /* Existing entry - clear the grip. */ + memset (kinfo->grip, 0, sizeof kinfo->grip); + + /* Set or update the grip. Note that due to the + * calloc/memset an erroneous too short grip will be nul + * padded on the right. */ + unhexify_fpr (hexgrp, kinfo->grip, sizeof kinfo->grip); } break; @@ -809,6 +894,7 @@ learn_status_cb (void *opaque, const char *line) break; } + xfree (line_buffer); return 0; } diff --git a/tools/card-tool-misc.c b/tools/card-tool-misc.c new file mode 100644 index 000000000..0f5fcc0a0 --- /dev/null +++ b/tools/card-tool-misc.c @@ -0,0 +1,44 @@ +/* card-tool-misc.c - Helper functions for gpg-card-tool + * Copyright (C) 2019 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 . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include +#include +#include +#include + +#include "../common/util.h" +#include "../common/i18n.h" +#include "../common/openpgpdefs.h" +#include "card-tool.h" + +/* Return the key info object for the key KEYREF. If it is not found + * NULL is returned. */ +key_info_t +find_kinfo (card_info_t info, const char *keyref) +{ + key_info_t kinfo; + + for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) + if (!strcmp (kinfo->keyref, keyref)) + return kinfo; + return NULL; +} diff --git a/tools/card-tool.h b/tools/card-tool.h index d51698c76..0af618676 100644 --- a/tools/card-tool.h +++ b/tools/card-tool.h @@ -76,6 +76,26 @@ struct key_attr }; }; +/* An object to store information pertaining to a key pair. This is + * commonly used as a linked list with all keys known for the current + * card. */ +struct key_info_s +{ + struct key_info_s *next; + + unsigned char grip[20];/* The keygrip. */ + + unsigned char xflag; /* Temporary flag to help processing a list. */ + + /* The three next items are mostly useful for OpenPGP cards. */ + unsigned char fprlen; /* Use length of the next item. */ + unsigned char fpr[32]; /* The binary fingerprint of length FPRLEN. */ + u32 created; /* The time the key was created. */ + + char keyref[1]; /* String with the keyref (e.g. OPENPGP.1). */ +}; +typedef struct key_info_s *key_info_t; + /* * The object used to store information about a card. @@ -100,18 +120,7 @@ struct card_info_s char cafpr1[20]; char cafpr2[20]; char cafpr3[20]; - unsigned char fpr1len; /* Length of the fingerprint or 0 if invalid. */ - unsigned char fpr2len; - unsigned char fpr3len; - char fpr1[20]; - char fpr2[20]; - char fpr3[20]; - u32 fpr1time; - u32 fpr2time; - u32 fpr3time; - char grp1[20]; /* The keygrip for OPENPGP.1 */ - char grp2[20]; /* The keygrip for OPENPGP.2 */ - char grp3[20]; /* The keygrip for OPENPGP.3 */ + key_info_t kinfo; /* Linked list with all keypair related data. */ unsigned long sig_counter; int chv1_cached; /* True if a PIN is not required for each signing. Note that the gpg-agent might cache @@ -133,6 +142,10 @@ struct card_info_s typedef struct card_info_s *card_info_t; +/*-- card-tool-misc.c --*/ +key_info_t find_kinfo (card_info_t info, const char *keyref); + + /*-- card-call-scd.c --*/ void release_card_info (card_info_t info); const char *app_type_string (app_type_t app_type); diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index b40914a6f..31d9c220e 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -107,10 +107,19 @@ static struct debug_flags_s debug_flags [] = }; +/* An object to create lists of labels and keyrefs. */ +struct keyinfolabel_s +{ + const char *label; + const char *keyref; +}; +typedef struct keyinfolabel_s *keyinfolabel_t; + + /* Limit of size of data we read from a file for certain commands. */ #define MAX_GET_DATA_FROM_FILE 16384 -/* Constats for OpenPGP cards. */ +/* Constants for OpenPGP cards. */ #define OPENPGP_USER_PIN_DEFAULT "123456" #define OPENPGP_ADMIN_PIN_DEFAULT "12345678" #define OPENPGP_KDF_DATA_LENGTH_MIN 90 @@ -544,35 +553,101 @@ print_isoname (estream_t fp, const char *name) } -/* Return true if the SHA1 fingerprint FPR consists only of zeroes. */ +/* Return true if the buffer MEM of length memlen consists only of zeroes. */ static int -fpr_is_zero (const char *fpr, unsigned int fprlen) +mem_is_zero (const char *mem, unsigned int memlen) { int i; - for (i=0; i < fprlen && !fpr[i]; i++) + for (i=0; i < memlen && !mem[i]; i++) ; - return (i == fprlen); + return (i == memlen); } -/* Return true if the fingerprint FPR consists only of 0xFF. */ +/* Return true if the buffer MEM or length MEMLEN consists only of 0xFF. */ static int -fpr_is_ff (const char *fpr, unsigned int fprlen) +mem_is_ff (const char *mem, unsigned int memlen) { int i; - for (i=0; i < fprlen && fpr[i] == '\xff'; i++) + for (i=0; i < memlen && mem[i] == '\xff'; i++) ; - return (i == fprlen); + return (i == memlen); } +/* Helper to list a single keyref. */ +static void +list_one_kinfo (key_info_t kinfo, estream_t fp) +{ + if (kinfo) + { + tty_fprintf (fp, " "); + if (mem_is_zero (kinfo->grip, sizeof kinfo->grip)) + tty_fprintf (fp, "[none]\n"); + else + print_keygrip (fp, kinfo->grip); + + if (kinfo->fprlen && kinfo->created) + { + tty_fprintf (fp, " fingerprint :"); + print_shax_fpr (fp, kinfo->fpr, kinfo->fprlen); + tty_fprintf (fp, " created ....: %s\n", + isotimestamp (kinfo->created)); + } + } + else + tty_fprintf (fp, " [none]\n"); +} + + +/* List all keyinfo in INFO using the list of LABELS. */ +static void +list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp) +{ + key_info_t kinfo; + int idx, i; + + /* Print the keyinfo. We first print those we known and then all + * remaining item. */ + for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) + kinfo->xflag = 0; + if (labels) + { + for (idx=0; labels[idx].label; idx++) + { + tty_fprintf (fp, "%s", labels[idx].label); + kinfo = find_kinfo (info, labels[idx].keyref); + list_one_kinfo (kinfo, fp); + if (kinfo) + kinfo->xflag = 1; + } + } + for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) + { + if (kinfo->xflag) + continue; + tty_fprintf (fp, "Key %s ", kinfo->keyref); + for (i=5+strlen (kinfo->keyref); i < 18; i++) + tty_fprintf (fp, "."); + tty_fprintf (fp, ":"); + list_one_kinfo (kinfo, fp); + } +} + + /* List OpenPGP card specific data. */ static void list_openpgp (card_info_t info, estream_t fp) { + static struct keyinfolabel_s keyinfolabels[] = { + { "Signature key ....:", "OPENPGP.1" }, + { "Encryption key....:", "OPENPGP.2" }, + { "Authentication key:", "OPENPGP.3" }, + { NULL, NULL } + }; int i; if (!info->serialno @@ -661,33 +736,8 @@ list_openpgp (card_info_t info, estream_t fp) info->uif[0] ? "on" : "off", info->uif[1] ? "on" : "off", info->uif[2] ? "on" : "off"); } - tty_fprintf (fp, "Signature key ....:"); - print_shax_fpr (fp, info->fpr1len? info->fpr1:NULL, info->fpr1len); - if (info->fpr1len && info->fpr1time) - { - tty_fprintf (fp, " created ....: %s\n", - isotimestamp (info->fpr1time)); - tty_fprintf (fp, " keygrip ....: "); - print_keygrip (fp, info->grp1); - } - tty_fprintf (fp, "Encryption key....:"); - print_shax_fpr (fp, info->fpr2len? info->fpr2:NULL, info->fpr2len); - if (info->fpr2len && info->fpr2time) - { - tty_fprintf (fp, " created ....: %s\n", - isotimestamp (info->fpr2time)); - tty_fprintf (fp, " keygrip ....: "); - print_keygrip (fp, info->grp2); - } - tty_fprintf (fp, "Authentication key:"); - print_shax_fpr (fp, info->fpr3len? info->fpr3:NULL, info->fpr3len); - if (info->fpr3len && info->fpr3time) - { - tty_fprintf (fp, " created ....: %s\n", - isotimestamp (info->fpr3time)); - tty_fprintf (fp, " keygrip ....: "); - print_keygrip (fp, info->grp3); - } + + list_all_kinfo (info, keyinfolabels, fp); /* tty_fprintf (fp, "General key info->.: "); */ /* thefpr = (info->fpr1len? info->fpr1 : info->fpr2len? info->fpr2 : */ @@ -696,7 +746,7 @@ list_openpgp (card_info_t info, estream_t fp) /* info->fpr3len? info->fpr3len : 0); */ /* If the fingerprint is all 0xff, the key has no associated OpenPGP certificate. */ - /* if ( thefpr && !fpr_is_ff (thefpr, thefprlen) */ + /* if ( thefpr && !mem_is_ff (thefpr, thefprlen) */ /* && !get_pubkey_byfprint (ctrl, pk, &keyblock, thefpr, thefprlen)) */ /* { */ /* print_pubkey_info (ctrl, fp, pk); */ @@ -900,6 +950,7 @@ static gpg_error_t cmd_fetch (card_info_t info) { gpg_error_t err; + key_info_t kinfo; if (!info) return print_help @@ -916,7 +967,7 @@ cmd_fetch (card_info_t info) /* free_strlist (sl); */ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ } - else if (info->fpr1len) + else if ((kinfo = find_kinfo (info, "OPENPGP.1")) && kinfo->fprlen) { /* rc = keyserver_import_fprint (ctrl, info.fpr1, info.fpr1len, */ /* opt.keyserver, 0); */ @@ -1479,6 +1530,7 @@ cmd_generate (card_info_t info) int forced_chv1 = -1; int want_backup; char *answer = NULL; + key_info_t kinfo1, kinfo2, kinfo3; if (!info) return print_help @@ -1507,9 +1559,15 @@ cmd_generate (card_info_t info) else want_backup = 0; - if ( (info->fpr1len && !fpr_is_zero (info->fpr1, info->fpr1len)) - || (info->fpr2len && !fpr_is_zero (info->fpr2, info->fpr2len)) - || (info->fpr3len && !fpr_is_zero (info->fpr3, info->fpr3len))) + + kinfo1 = find_kinfo (info, "OPENPGP.1"); + kinfo2 = find_kinfo (info, "OPENPGP.2"); + kinfo3 = find_kinfo (info, "OPENPGP.3"); + + if ((kinfo1 && kinfo1->fprlen && !mem_is_zero (kinfo1->fpr,kinfo1->fprlen)) + || (kinfo2 && kinfo2->fprlen && !mem_is_zero (kinfo2->fpr,kinfo2->fprlen)) + || (kinfo3 && kinfo3->fprlen && !mem_is_zero (kinfo3->fpr,kinfo3->fprlen)) + ) { tty_printf ("\n"); log_info (_("Note: keys are already stored on the card!\n")); From 9325c92284bb346d11c3591bb2ea88095989361a Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 29 Jan 2019 09:30:15 +0100 Subject: [PATCH 134/235] card: Print keyinfo for PIV cards. * scd/app-piv.c (do_learn_status): Print CHV-STATUS. * tools/card-tool.h (struct card_info_s): Rename chvretry to chvinfo. * tools/card-call-scd.c (learn_status_cb): Depend CHV-STATUS on app type. * tools/gpg-card-tool.c (list_piv): New. Signed-off-by: Werner Koch --- scd/app-piv.c | 5 ++++- tools/card-call-scd.c | 24 ++++++++++++++++++++---- tools/card-tool.h | 10 +++++----- tools/gpg-card-tool.c | 43 +++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 70 insertions(+), 12 deletions(-) diff --git a/scd/app-piv.c b/scd/app-piv.c index 011b55229..69f12f43a 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -569,7 +569,7 @@ send_keypair_and_cert_info (app_t app, ctrl_t ctrl, data_object_t dobj, } -/* Handle the LEARN command for OpenPGP. */ +/* Handle the LEARN command. */ static gpg_error_t do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) { @@ -577,10 +577,13 @@ do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) (void)flags; + do_getattr (app, ctrl, "CHV-STATUS"); + for (i=0; data_objects[i].tag; i++) if (data_objects[i].keypair) send_keypair_and_cert_info (app, ctrl, data_objects + i, !!(flags & 1)); + return 0; } diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c index abf35ed17..2551b19f6 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -808,9 +808,13 @@ learn_status_cb (void *opaque, const char *line) buf = p = unescape_status_string (line); if (buf) + while (spacep (p)) + p++; + + if (!buf) + ; + else if (parm->apptype == APP_TYPE_OPENPGP) { - while (spacep (p)) - p++; parm->chv1_cached = atoi (p); while (*p && !spacep (p)) p++; @@ -826,14 +830,26 @@ learn_status_cb (void *opaque, const char *line) } for (i=0; *p && i < 3; i++) { - parm->chvretry[i] = atoi (p); + parm->chvinfo[i] = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; } - xfree (buf); } + else if (parm->apptype == APP_TYPE_PIV) + { + for (i=0; *p && DIM (parm->chvinfo); i++) + { + parm->chvinfo[i] = atoi (p); + while (*p && !spacep (p)) + p++; + while (spacep (p)) + p++; + } + } + + xfree (buf); } break; diff --git a/tools/card-tool.h b/tools/card-tool.h index 0af618676..bcc257ce2 100644 --- a/tools/card-tool.h +++ b/tools/card-tool.h @@ -122,12 +122,12 @@ struct card_info_s char cafpr3[20]; key_info_t kinfo; /* Linked list with all keypair related data. */ unsigned long sig_counter; - int chv1_cached; /* True if a PIN is not required for each - signing. Note that the gpg-agent might cache - it anyway. */ - int is_v2; /* True if this is a v2 card. */ + int chv1_cached; /* For openpgp this is true if a PIN is not + required for each signing. Note that the + gpg-agent might cache it anyway. */ + int is_v2; /* True if this is a v2 openpgp card. */ int chvmaxlen[3]; /* Maximum allowed length of a CHV. */ - int chvretry[3]; /* Allowed retries for the CHV; 0 = blocked. */ + int chvinfo[3]; /* Allowed retries for the CHV; 0 = blocked. */ struct key_attr key_attr[3]; /* OpenPGP card key attributes. */ struct { unsigned int ki:1; /* Key import available. */ diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index 31d9c220e..5ba44fcbb 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -723,7 +723,7 @@ list_openpgp (card_info_t info, estream_t fp) tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n", info->chvmaxlen[0], info->chvmaxlen[1], info->chvmaxlen[2]); tty_fprintf (fp, "PIN retry counter : %d %d %d\n", - info->chvretry[0], info->chvretry[1], info->chvretry[2]); + info->chvinfo[0], info->chvinfo[1], info->chvinfo[2]); tty_fprintf (fp, "Signature counter : %lu\n", info->sig_counter); if (info->extcap.kdf) { @@ -758,6 +758,44 @@ list_openpgp (card_info_t info, estream_t fp) } +/* List PIV card specific data. */ +static void +list_piv (card_info_t info, estream_t fp) +{ + static struct keyinfolabel_s keyinfolabels[] = { + { "PIV Authentication:", "PIV.9A" }, + { "Card Authenticat. :", "PIV.9E" }, + { "Digital Signature :", "PIV.9C" }, + { "Key Management ...:", "PIV.9D" }, + { NULL, NULL } + }; + const char *s; + int i; + + tty_fprintf (fp, "PIN retry counter :"); + for (i=0; i < DIM (info->chvinfo); i++) + { + if (info->chvinfo[i] > 0) + tty_fprintf (fp, " %d", info->chvinfo[i]); + else + { + switch (info->chvinfo[i]) + { + case -1: s = "[error]"; break; + case -2: s = "-"; break; /* No such PIN */ + case -3: s = "[blocked]"; break; + case -5: s = "[verified]"; break; + default: s = "[?]"; break; + } + tty_fprintf (fp, " %s", s); + } + } + tty_fprintf (fp, "\n", s); + list_all_kinfo (info, keyinfolabels, fp); + +} + + /* Print all available information about the current card. */ static void list_card (card_info_t info) @@ -781,6 +819,7 @@ list_card (card_info_t info) switch (info->apptype) { case APP_TYPE_OPENPGP: list_openpgp (info, fp); break; + case APP_TYPE_PIV: list_piv (info, fp); break; default: break; } } @@ -1740,7 +1779,7 @@ cmd_unblock (card_info_t info) if (info->apptype == APP_TYPE_OPENPGP && !info->is_v2) log_error (_("This command is only available for version 2 cards\n")); - else if (info->apptype == APP_TYPE_OPENPGP && !info->chvretry[1]) + else if (info->apptype == APP_TYPE_OPENPGP && !info->chvinfo[1]) log_error (_("Reset Code not or not anymore available\n")); else if (info->apptype == APP_TYPE_OPENPGP) { From 79bed504e51034d960fcb858fb643901cad85913 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 29 Jan 2019 13:28:10 +0100 Subject: [PATCH 135/235] card: Support factory reset for Yubikey PIV application. * scd/app-common.h (struct app_ctx_s): Add field cardtype. * scd/app.c (app_new_register): Set cardtype for yubikey. (app_getattr): Add CARDTYPE. (app_write_learn_status): Emit new attribute. * scd/app-piv.c (do_getattr): Add CHV-USAGE. (do_learn_status): Emit it. * tools/card-tool.h (struct card_info_s): Add field cardtype. * tools/card-call-scd.c (learn_status_cb): Parse "CARDTYPE". * tools/gpg-card-tool.c (list_piv): Print PIN usage policy. (list_card): Print card type. (cmd_factoryreset): Implement for Yubikey with PIV. Signed-off-by: Werner Koch --- scd/app-common.h | 1 + scd/app-piv.c | 20 ++++- scd/app.c | 13 ++- tools/card-call-scd.c | 41 ++++++--- tools/card-tool.h | 2 + tools/gpg-card-tool.c | 198 +++++++++++++++++++++++++++--------------- 6 files changed, 190 insertions(+), 85 deletions(-) diff --git a/scd/app-common.h b/scd/app-common.h index b1661b524..98d8464dc 100644 --- a/scd/app-common.h +++ b/scd/app-common.h @@ -52,6 +52,7 @@ struct app_ctx_s { unsigned char *serialno; /* Serialnumber in raw form, allocated. */ size_t serialnolen; /* Length in octets of serialnumber. */ + const char *cardtype; /* NULL or string with the token's type. */ const char *apptype; unsigned int card_version; unsigned int card_status; diff --git a/scd/app-piv.c b/scd/app-piv.c index 69f12f43a..d984e9c7a 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -469,13 +469,16 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name) { "SERIALNO", 0x0000, -1 }, { "$AUTHKEYID", 0x0000, -2 }, /* Default key for ssh. */ { "$DISPSERIALNO",0x0000, -3 }, - { "CHV-STATUS", 0x0000, -4 } + { "CHV-STATUS", 0x0000, -4 }, + { "CHV-USAGE", 0x007E, -5 } }; gpg_error_t err = 0; int idx; void *relptr; unsigned char *value; size_t valuelen; + const unsigned char *s; + size_t n; for (idx=0; (idx < DIM (table) && ascii_strcasecmp (table[idx].name, name)); idx++) @@ -521,6 +524,20 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name) err = send_status_printf (ctrl, table[idx].name, "%d %d %d", tmp[0], tmp[1], tmp[2]); } + else if (table[idx].special == -5) /* CHV-USAGE (aka PIN Usage Policy) */ + { + /* We return 2 hex bytes or nothing in case the discovery object + * is not supported. */ + relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err); + if (relptr) + { + s = find_tlv (value, valuelen, 0x7E, &n); + if (s && n && (s = find_tlv (s, n, 0x5F2F, &n)) && n >=2 ) + err = send_status_printf (ctrl, table[idx].name, "%02X %02X", + s[0], s[1]); + xfree (relptr); + } + } else { relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err); @@ -577,6 +594,7 @@ do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) (void)flags; + do_getattr (app, ctrl, "CHV-USAGE"); do_getattr (app, ctrl, "CHV-STATUS"); for (i=0; data_objects[i].tag; i++) diff --git a/scd/app.c b/scd/app.c index 219cee68d..c79a174fc 100644 --- a/scd/app.c +++ b/scd/app.c @@ -228,6 +228,7 @@ app_new_register (int slot, ctrl_t ctrl, const char *name, && !iso7816_apdu_direct (slot, "\x00\x1d\x00\x00\x00", 5, 0, NULL, &buf, &buflen)) { + app->cardtype = "yubikey"; if (opt.verbose) { log_info ("Yubico: config="); @@ -640,9 +641,12 @@ app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) if (!app->fnc.learn_status) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - /* We do not send APPTYPE if only keypairinfo is requested. */ + /* We do not send CARD and APPTYPE if only keypairinfo is requested. */ + if (app->cardtype && !(flags & 1)) + send_status_direct (ctrl, "CARDTYPE", app->cardtype); if (app->apptype && !(flags & 1)) send_status_direct (ctrl, "APPTYPE", app->apptype); + err = lock_app (app, ctrl); if (err) return err; @@ -721,6 +725,11 @@ app_getattr (app_t app, ctrl_t ctrl, const char *name) if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (app->cardtype && name && !strcmp (name, "CARDTYPE")) + { + send_status_direct (ctrl, "CARDTYPE", app->cardtype); + return 0; + } if (app->apptype && name && !strcmp (name, "APPTYPE")) { send_status_direct (ctrl, "APPTYPE", app->apptype); @@ -744,7 +753,7 @@ app_getattr (app_t app, ctrl_t ctrl, const char *name) err = lock_app (app, ctrl); if (err) return err; - err = app->fnc.getattr (app, ctrl, name); + err = app->fnc.getattr (app, ctrl, name); unlock_app (app); return err; } diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c index 2551b19f6..9a742a728 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -137,6 +137,7 @@ release_card_info (card_info_t info) return; xfree (info->reader); info->reader = NULL; + xfree (info->cardtype); info->cardtype = NULL; xfree (info->serialno); info->serialno = NULL; xfree (info->dispserialno); info->dispserialno = NULL; xfree (info->apptypestr); info->apptypestr = NULL; @@ -157,7 +158,7 @@ release_card_info (card_info_t info) xfree (info->kinfo); info->kinfo = kinfo; } - + info->chvusage[0] = info->chvusage[1] = 0; } @@ -724,6 +725,11 @@ learn_status_cb (void *opaque, const char *line) parm->is_v2 = (strlen (parm->serialno) >= 16 && xtoi_2 (parm->serialno+12) >= 2 ); } + else if (!memcmp (keyword, "CARDTYPE", keywordlen)) + { + xfree (parm->cardtype); + parm->cardtype = unescape_status_string (line); + } else if (!memcmp (keyword, "DISP-SEX", keywordlen)) { parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0; @@ -779,17 +785,26 @@ learn_status_cb (void *opaque, const char *line) break; case 9: - if (!memcmp (keyword, "DISP-NAME", keywordlen)) - { - xfree (parm->disp_name); - parm->disp_name = unescape_status_string (line); - } - else if (!memcmp (keyword, "DISP-LANG", keywordlen)) - { - xfree (parm->disp_lang); - parm->disp_lang = unescape_status_string (line); - } - break; + if (!memcmp (keyword, "DISP-NAME", keywordlen)) + { + xfree (parm->disp_name); + parm->disp_name = unescape_status_string (line); + } + else if (!memcmp (keyword, "DISP-LANG", keywordlen)) + { + xfree (parm->disp_lang); + parm->disp_lang = unescape_status_string (line); + } + else if (!memcmp (keyword, "CHV-USAGE", keywordlen)) + { + unsigned int byte1, byte2; + + byte1 = byte2 = 0; + sscanf (line, "%x %x", &byte1, &byte2); + parm->chvusage[0] = byte1; + parm->chvusage[1] = byte2; + } + break; case 10: if (!memcmp (keyword, "PUBKEY-URL", keywordlen)) @@ -839,7 +854,7 @@ learn_status_cb (void *opaque, const char *line) } else if (parm->apptype == APP_TYPE_PIV) { - for (i=0; *p && DIM (parm->chvinfo); i++) + for (i=0; *p && i < DIM (parm->chvinfo); i++) { parm->chvinfo[i] = atoi (p); while (*p && !spacep (p)) diff --git a/tools/card-tool.h b/tools/card-tool.h index bcc257ce2..b1d866228 100644 --- a/tools/card-tool.h +++ b/tools/card-tool.h @@ -104,6 +104,7 @@ struct card_info_s { int error; /* private. */ char *reader; /* Reader information. */ + char *cardtype; /* NULL or type of the card. */ char *apptypestr; /* Malloced application type string. */ app_type_t apptype;/* Translated from APPTYPESTR. */ char *serialno; /* malloced hex string. */ @@ -128,6 +129,7 @@ struct card_info_s int is_v2; /* True if this is a v2 openpgp card. */ int chvmaxlen[3]; /* Maximum allowed length of a CHV. */ int chvinfo[3]; /* Allowed retries for the CHV; 0 = blocked. */ + unsigned char chvusage[2]; /* Data object 5F2F */ struct key_attr key_attr[3]; /* OpenPGP card key attributes. */ struct { unsigned int ki:1; /* Key import available. */ diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index 5ba44fcbb..4f7962060 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -763,15 +763,37 @@ static void list_piv (card_info_t info, estream_t fp) { static struct keyinfolabel_s keyinfolabels[] = { - { "PIV Authentication:", "PIV.9A" }, - { "Card Authenticat. :", "PIV.9E" }, - { "Digital Signature :", "PIV.9C" }, - { "Key Management ...:", "PIV.9D" }, + { "PIV authentication:", "PIV.9A" }, + { "Card authenticat. :", "PIV.9E" }, + { "Digital signature :", "PIV.9C" }, + { "Key management ...:", "PIV.9D" }, { NULL, NULL } }; const char *s; int i; + if (info->chvusage[0] || info->chvusage[1]) + { + tty_fprintf (fp, "PIN usage policy .:"); + if ((info->chvusage[0] & 0x40)) + tty_fprintf (fp, " app-pin"); + if ((info->chvusage[0] & 0x20)) + tty_fprintf (fp, " global-pin"); + if ((info->chvusage[0] & 0x10)) + tty_fprintf (fp, " occ"); + if ((info->chvusage[0] & 0x08)) + tty_fprintf (fp, " vci"); + if ((info->chvusage[0] & 0x08) && !(info->chvusage[0] & 0x04)) + tty_fprintf (fp, " pairing"); + + if (info->chvusage[1] == 0x10) + tty_fprintf (fp, " primary:card"); + else if (info->chvusage[1] == 0x20) + tty_fprintf (fp, " primary:global"); + + tty_fprintf (fp, "\n"); + } + tty_fprintf (fp, "PIN retry counter :"); for (i=0; i < DIM (info->chvinfo); i++) { @@ -790,7 +812,7 @@ list_piv (card_info_t info, estream_t fp) tty_fprintf (fp, " %s", s); } } - tty_fprintf (fp, "\n", s); + tty_fprintf (fp, "\n"); list_all_kinfo (info, keyinfolabels, fp); } @@ -804,9 +826,11 @@ list_card (card_info_t info) tty_fprintf (fp, "Reader ...........: %s\n", info->reader? info->reader : "[none]"); + if (info->cardtype) + tty_fprintf (fp, "Card type ........: %s\n", info->cardtype); tty_fprintf (fp, "Serial number ....: %s\n", info->serialno? info->serialno : "[none]"); - tty_fprintf (fp, "Application Type .: %s%s%s%s\n", + tty_fprintf (fp, "Application type .: %s%s%s%s\n", app_type_string (info->apptype), info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? "(":"", info->apptype == APP_TYPE_UNKNOWN && info->apptypestr @@ -1836,26 +1860,32 @@ cmd_factoryreset (card_info_t info) char *answer = NULL; int termstate = 0; int any_apdu = 0; + int is_yubikey = 0; int i; if (!info) return print_help ("FACTORY-RESET\n\n" - "Do a complete reset of an OpenPGP card. This deletes all\n" - "data and keys and resets the PINs to their default. This\n" - "mainly used by developers with scratch cards. Don't worry,\n" - "you need to confirm before the command proceeds.", - APP_TYPE_OPENPGP, 0); + "Do a complete reset of some OpenPGP and PIV cards. This\n" + "deletes all data and keys and resets the PINs to their default.\n" + "This is mainly used by developers with scratch cards. Don't\n" + "worry, you need to confirm before the command proceeds.", + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); - if (info->apptype != APP_TYPE_OPENPGP) - { - log_info ("Note: This is an OpenPGP only command.\n"); - return gpg_error (GPG_ERR_NOT_SUPPORTED); - } + /* We support the factory reset for most OpenPGP cards and Yubikeys + * with the PIV application. */ + if (info->apptype == APP_TYPE_OPENPGP) + ; + else if (info->apptype == APP_TYPE_PIV + && info->cardtype && !strcmp (info->cardtype, "yubikey")) + is_yubikey = 1; + else - /* The code below basically does the same what this - * gpg-connect-agent script does: + return gpg_error (GPG_ERR_NOT_SUPPORTED); + + /* For an OpenPGP card the code below basically does the same what + * this gpg-connect-agent script does: * * scd reset * scd serialno undefined @@ -1873,7 +1903,8 @@ cmd_factoryreset (card_info_t info) * scd reset * /echo Card has been reset to factory defaults * - * but tries to find out something about the card first. + * For a PIV application on a Yubikey it merely issues the Yubikey + * specific resset command. */ err = scd_learn (info); @@ -1886,17 +1917,24 @@ cmd_factoryreset (card_info_t info) goto leave; } - if (!termstate) + if (!termstate || is_yubikey) { - log_info (_("OpenPGP card no. %s detected\n"), - info->dispserialno? info->dispserialno : info->serialno); - if (!(info->status_indicator == 3 || info->status_indicator == 5)) + if (is_yubikey) + log_info (_("Yubikey no. %s with PIV application detected\n"), + info->dispserialno? info->dispserialno : info->serialno); + else { - /* Note: We won't see status-indicator 3 here because it is not - * possible to select a card application in termination state. */ - log_error (_("This command is not supported by this card\n")); - err = gpg_error (GPG_ERR_NOT_SUPPORTED); - goto leave; + log_info (_("OpenPGP card no. %s detected\n"), + info->dispserialno? info->dispserialno : info->serialno); + if (!(info->status_indicator == 3 || info->status_indicator == 5)) + { + /* Note: We won't see status-indicator 3 here because it + * is not possible to select a card application in + * termination state. */ + log_error (_("This command is not supported by this card\n")); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } } tty_printf ("\n"); @@ -1924,51 +1962,73 @@ cmd_factoryreset (card_info_t info) goto leave; } + + if (is_yubikey) + { + /* The PIV application si already selected, we only need to + * send the special reset APDU after having blocked PIN and + * PUK. Note that blocking the PUK is done using the + * unblock PIN command. */ + any_apdu = 1; + for (i=0; i < 5; i++) + send_apdu ("0020008008FFFFFFFFFFFFFFFF", "VERIFY", 0xffff); + for (i=0; i < 5; i++) + send_apdu ("002C008010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + "RESET RETRY COUNTER", 0xffff); + err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0); + if (err) + goto leave; + } + else /* OpenPGP card. */ + { + any_apdu = 1; + /* We need to select a card application before we can send APDUs + * to the card without scdaemon doing anything on its own. */ + err = send_apdu (NULL, "RESET", 0); + if (err) + goto leave; + err = send_apdu ("undefined", "dummy select ", 0); + if (err) + goto leave; + /* Select the OpenPGP application. */ + err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0); + if (err) + goto leave; + + /* Do some dummy verifies with wrong PINs to set the retry + * counter to zero. We can't easily use the card version 2.1 + * feature of presenting the admin PIN to allow the terminate + * command because there is no machinery in scdaemon to catch + * the verify command and ask for the PIN when the "APDU" + * command is used. + * Here, the length of dummy wrong PIN is 32-byte, also + * supporting authentication with KDF DO. */ + for (i=0; i < 4; i++) + send_apdu ("0020008120" + "40404040404040404040404040404040" + "40404040404040404040404040404040", "VERIFY", 0xffff); + for (i=0; i < 4; i++) + send_apdu ("0020008320" + "40404040404040404040404040404040" + "40404040404040404040404040404040", "VERIFY", 0xffff); + + /* Send terminate datafile command. */ + err = send_apdu ("00e60000", "TERMINATE DF", 0x6985); + if (err) + goto leave; + } + } + + if (!is_yubikey) + { any_apdu = 1; - /* We need to select a card application before we can send APDUs - * to the card without scdaemon doing anything on its own. */ - err = send_apdu (NULL, "RESET", 0); - if (err) - goto leave; - err = send_apdu ("undefined", "dummy select ", 0); - if (err) - goto leave; - - /* Select the OpenPGP application. */ - err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0); - if (err) - goto leave; - - /* Do some dummy verifies with wrong PINs to set the retry - * counter to zero. We can't easily use the card version 2.1 - * feature of presenting the admin PIN to allow the terminate - * command because there is no machinery in scdaemon to catch - * the verify command and ask for the PIN when the "APDU" - * command is used. - * Here, the length of dummy wrong PIN is 32-byte, also - * supporting authentication with KDF DO. */ - for (i=0; i < 4; i++) - send_apdu ("0020008120" - "40404040404040404040404040404040" - "40404040404040404040404040404040", "VERIFY", 0xffff); - for (i=0; i < 4; i++) - send_apdu ("0020008320" - "40404040404040404040404040404040" - "40404040404040404040404040404040", "VERIFY", 0xffff); - - /* Send terminate datafile command. */ - err = send_apdu ("00e60000", "TERMINATE DF", 0x6985); + /* Send activate datafile command. This is used without + * confirmation if the card is already in termination state. */ + err = send_apdu ("00440000", "ACTIVATE DF", 0); if (err) goto leave; } - any_apdu = 1; - /* Send activate datafile command. This is used without - * confirmation if the card is already in termination state. */ - err = send_apdu ("00440000", "ACTIVATE DF", 0); - if (err) - goto leave; - /* Finally we reset the card reader once more. */ err = send_apdu (NULL, "RESET", 0); if (err) @@ -1979,7 +2039,7 @@ cmd_factoryreset (card_info_t info) err = scd_serialno (&answer, NULL); leave: - if (err && any_apdu) + if (err && any_apdu && !is_yubikey) { log_info ("Due to an error the card might be in an inconsistent state\n" "You should run the LIST command to check this.\n"); From 4a1558d0c7190cf13d35385e47291a7aa121be3e Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 29 Jan 2019 18:19:05 +0100 Subject: [PATCH 136/235] common: New helper functions for OpenPGP curve OIDs. * common/openpgp-oid.c (openpgp_oidbuf_to_str): Factor most code out to ... (openpgp_oidbuf_to_str): new. (openpgp_oidbuf_is_ed25519): New. (openpgp_oidbuf_is_cv25519): New. -- At some places it is more convenient (and faster) to directly work on buffers and avoid the way via opaque MPIs. These 3 new functions allow for that. Signed-off-by: Werner Koch --- common/openpgp-oid.c | 87 ++++++++++++++++++++++++++---------------- common/t-openpgp-oid.c | 10 ++++- common/util.h | 3 ++ 3 files changed, 66 insertions(+), 34 deletions(-) diff --git a/common/openpgp-oid.c b/common/openpgp-oid.c index 86885e0aa..419471870 100644 --- a/common/openpgp-oid.c +++ b/common/openpgp-oid.c @@ -184,48 +184,36 @@ openpgp_oid_from_str (const char *string, gcry_mpi_t *r_mpi) } -/* Return a malloced string representation of the OID in the opaque MPI - A. In case of an error NULL is returned and ERRNO is set. */ +/* Return a malloced string representation of the OID in the buffer + * (BUF,LEN). In case of an error NULL is returned and ERRNO is set. + * As per OpenPGP spec the first byte of the buffer is the length of + * the rest; the function performs a consistency check. */ char * -openpgp_oid_to_str (gcry_mpi_t a) +openpgp_oidbuf_to_str (const unsigned char *buf, size_t len) { - const unsigned char *buf; - size_t length; - unsigned int lengthi; char *string, *p; int n = 0; unsigned long val, valmask; valmask = (unsigned long)0xfe << (8 * (sizeof (valmask) - 1)); - - if (!a - || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE) - || !(buf = gcry_mpi_get_opaque (a, &lengthi))) - { - gpg_err_set_errno (EINVAL); - return NULL; - } - - buf = gcry_mpi_get_opaque (a, &lengthi); - length = (lengthi+7)/8; - /* The first bytes gives the length; check consistency. */ - if (!length || buf[0] != length -1) + + if (!len || buf[0] != len -1) { gpg_err_set_errno (EINVAL); return NULL; } /* Skip length byte. */ - length--; + len--; buf++; /* To calculate the length of the string we can safely assume an upper limit of 3 decimal characters per byte. Two extra bytes account for the special first octet */ - string = p = xtrymalloc (length*(1+3)+2+1); + string = p = xtrymalloc (len*(1+3)+2+1); if (!string) return NULL; - if (!length) + if (!len) { *p = 0; return string; @@ -237,7 +225,7 @@ openpgp_oid_to_str (gcry_mpi_t a) p += sprintf (p, "1.%d", buf[n]-40); else { val = buf[n] & 0x7f; - while ( (buf[n]&0x80) && ++n < length ) + while ( (buf[n]&0x80) && ++n < len ) { if ( (val & valmask) ) goto badoid; /* Overflow. */ @@ -250,10 +238,10 @@ openpgp_oid_to_str (gcry_mpi_t a) sprintf (p, "2.%lu", val); p += strlen (p); } - for (n++; n < length; n++) + for (n++; n < len; n++) { val = buf[n] & 0x7f; - while ( (buf[n]&0x80) && ++n < length ) + while ( (buf[n]&0x80) && ++n < len ) { if ( (val & valmask) ) goto badoid; /* Overflow. */ @@ -278,6 +266,35 @@ openpgp_oid_to_str (gcry_mpi_t a) } +/* Return a malloced string representation of the OID in the opaque + * MPI A. In case of an error NULL is returned and ERRNO is set. */ +char * +openpgp_oid_to_str (gcry_mpi_t a) +{ + const unsigned char *buf; + unsigned int lengthi; + + if (!a + || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE) + || !(buf = gcry_mpi_get_opaque (a, &lengthi))) + { + gpg_err_set_errno (EINVAL); + return NULL; + } + + buf = gcry_mpi_get_opaque (a, &lengthi); + return openpgp_oidbuf_to_str (buf, (lengthi+7)/8); +} + + +/* Return true if (BUF,LEN) represents the OID for Ed25519. */ +int +openpgp_oidbuf_is_ed25519 (const void *buf, size_t len) +{ + return (buf && len == DIM (oid_ed25519) + && !memcmp (buf, oid_ed25519, DIM (oid_ed25519))); +} + /* Return true if A represents the OID for Ed25519. */ int @@ -285,32 +302,36 @@ openpgp_oid_is_ed25519 (gcry_mpi_t a) { const unsigned char *buf; unsigned int nbits; - size_t n; if (!a || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE)) return 0; buf = gcry_mpi_get_opaque (a, &nbits); - n = (nbits+7)/8; - return (n == DIM (oid_ed25519) - && !memcmp (buf, oid_ed25519, DIM (oid_ed25519))); + return openpgp_oidbuf_is_ed25519 (buf, (nbits+7)/8); } +/* Return true if (BUF,LEN) represents the OID for Curve25519. */ +int +openpgp_oidbuf_is_cv25519 (const void *buf, size_t len) +{ + return (buf && len == DIM (oid_cv25519) + && !memcmp (buf, oid_cv25519, DIM (oid_cv25519))); +} + + +/* Return true if the MPI A represents the OID for Curve25519. */ int openpgp_oid_is_cv25519 (gcry_mpi_t a) { const unsigned char *buf; unsigned int nbits; - size_t n; if (!a || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE)) return 0; buf = gcry_mpi_get_opaque (a, &nbits); - n = (nbits+7)/8; - return (n == DIM (oid_cv25519) - && !memcmp (buf, oid_cv25519, DIM (oid_cv25519))); + return openpgp_oidbuf_is_cv25519 (buf, (nbits+7)/8); } diff --git a/common/t-openpgp-oid.c b/common/t-openpgp-oid.c index cb5709d98..fd9de5dde 100644 --- a/common/t-openpgp-oid.c +++ b/common/t-openpgp-oid.c @@ -142,7 +142,15 @@ test_openpgp_oid_to_str (void) fail (idx, 0); xfree (string); gcry_mpi_release (a); - } + + /* Again using the buffer variant. */ + string = openpgp_oidbuf_to_str (samples[idx].der, samples[idx].der[0]+1); + if (!string) + fail (idx, gpg_error_from_syserror ()); + if (strcmp (string, samples[idx].string)) + fail (idx, 0); + xfree (string); +} } diff --git a/common/util.h b/common/util.h index d3a846f00..8234b6257 100644 --- a/common/util.h +++ b/common/util.h @@ -211,8 +211,11 @@ size_t percent_unescape_inplace (char *string, int nulrepl); /*-- openpgp-oid.c --*/ gpg_error_t openpgp_oid_from_str (const char *string, gcry_mpi_t *r_mpi); +char *openpgp_oidbuf_to_str (const unsigned char *buf, size_t len); char *openpgp_oid_to_str (gcry_mpi_t a); +int openpgp_oidbuf_is_ed25519 (const void *buf, size_t len); int openpgp_oid_is_ed25519 (gcry_mpi_t a); +int openpgp_oidbuf_is_cv25519 (const void *buf, size_t len); int openpgp_oid_is_cv25519 (gcry_mpi_t a); const char *openpgp_curve_to_oid (const char *name, unsigned int *r_nbits); const char *openpgp_oid_to_curve (const char *oid, int canon); From f382984966a31a4cbe572bce5370590c5490ed1e Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 29 Jan 2019 18:20:34 +0100 Subject: [PATCH 137/235] common: Provide some convenient OpenPGP related constants. * common/openpgpdefs.h (OPENPGP_MAX_NPKEY): New. (OPENPGP_MAX_NSKEY): New. (OPENPGP_MAX_NSIG): New. (OPENPGP_MAX_NENC): New. * g10/packet.h: Define PUBKEY_MAX using the new consts. Signed-off-by: Werner Koch --- common/openpgpdefs.h | 6 +++++- g10/packet.h | 10 +++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/common/openpgpdefs.h b/common/openpgpdefs.h index aadda434b..2f7ff456e 100644 --- a/common/openpgpdefs.h +++ b/common/openpgpdefs.h @@ -196,7 +196,11 @@ typedef enum } compress_algo_t; - +/* Limits to be used for static arrays. */ +#define OPENPGP_MAX_NPKEY 5 /* Maximum number of public key parameters. */ +#define OPENPGP_MAX_NSKEY 7 /* Maximum number of secret key parameters. */ +#define OPENPGP_MAX_NSIG 2 /* Maximum number of signature parameters. */ +#define OPENPGP_MAX_NENC 2 /* Maximum number of encryption parameters. */ /* Decode an rfc4880 encoded S2K count. */ diff --git a/g10/packet.h b/g10/packet.h index 78a57bacf..6160d0b01 100644 --- a/g10/packet.h +++ b/g10/packet.h @@ -34,11 +34,11 @@ #define DEBUG_PARSE_PACKET 1 -/* Constants to allocate static MPI arrays. */ -#define PUBKEY_MAX_NPKEY 5 -#define PUBKEY_MAX_NSKEY 7 -#define PUBKEY_MAX_NSIG 2 -#define PUBKEY_MAX_NENC 2 +/* Constants to allocate static MPI arrays. */ +#define PUBKEY_MAX_NPKEY OPENPGP_MAX_NPKEY +#define PUBKEY_MAX_NSKEY OPENPGP_MAX_NSKEY +#define PUBKEY_MAX_NSIG OPENPGP_MAX_NSIG +#define PUBKEY_MAX_NENC OPENPGP_MAX_NENC /* Usage flags */ #define PUBKEY_USAGE_SIG GCRY_PK_USAGE_SIGN /* Good for signatures. */ From c128667b3cba749dd14262e032d4c260a2b0acd3 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 29 Jan 2019 19:52:08 +0100 Subject: [PATCH 138/235] gpg: Implement searching keys via keygrip. * kbx/keybox-defs.h (struct _keybox_openpgp_key_info): Add field grip. * kbx/keybox-openpgp.c (struct keyparm_s): New. (keygrip_from_keyparm): New. (parse_key): Compute keygrip. * kbx/keybox-search.c (blob_openpgp_has_grip): New. (has_keygrip): Call it. -- This has been marked for too long as not yet working. However, it is a pretty useful feature and will come pretty handy when looking for all keys matching one keygrip. Can be optimized a lot by storing the keygrip in the meta data. This will be done along with the upgrade of KBX for v5 fingerprints. Signed-off-by: Werner Koch --- doc/specify-user-id.texi | 5 +- kbx/kbxutil.c | 18 +++++ kbx/keybox-defs.h | 3 +- kbx/keybox-openpgp.c | 145 ++++++++++++++++++++++++++++++++++----- kbx/keybox-search.c | 57 ++++++++++++++- 5 files changed, 204 insertions(+), 24 deletions(-) diff --git a/doc/specify-user-id.texi b/doc/specify-user-id.texi index b363c2ace..64e354bdf 100644 --- a/doc/specify-user-id.texi +++ b/doc/specify-user-id.texi @@ -135,7 +135,7 @@ RFC-2253 encoded DN of the issuer. See note above. @item By keygrip. This is indicated by an ampersand followed by the 40 hex digits of a keygrip. @command{gpgsm} prints the keygrip when using the command -@option{--dump-cert}. It does not yet work for OpenPGP keys. +@option{--dump-cert}. @cartouche @example @@ -171,6 +171,3 @@ Using the RFC-2253 format of DNs has the drawback that it is not possible to map them back to the original encoding, however we don't have to do this because our key database stores this encoding as meta data. - - - diff --git a/kbx/kbxutil.c b/kbx/kbxutil.c index 2cfd07019..35f92ab6e 100644 --- a/kbx/kbxutil.c +++ b/kbx/kbxutil.c @@ -329,6 +329,18 @@ dump_fpr (const unsigned char *buffer, size_t len) } +static void +dump_grip (const unsigned char *buffer, size_t len) +{ + int i; + + for (i=0; i < len; i++, buffer++) + { + printf ("%02X", buffer[0]); + } +} + + static void dump_openpgp_key (keybox_openpgp_info_t info, const unsigned char *image) { @@ -338,6 +350,9 @@ dump_openpgp_key (keybox_openpgp_info_t info, const unsigned char *image) info->primary.keyid[6], info->primary.keyid[7] ); dump_fpr (info->primary.fpr, info->primary.fprlen); putchar ('\n'); + fputs ("grp ", stdout); + dump_grip (info->primary.grip, 20); + putchar ('\n'); if (info->nsubkeys) { struct _keybox_openpgp_key_info *k; @@ -351,6 +366,9 @@ dump_openpgp_key (keybox_openpgp_info_t info, const unsigned char *image) k->keyid[6], k->keyid[7] ); dump_fpr (k->fpr, k->fprlen); putchar ('\n'); + fputs ("grp ", stdout); + dump_grip (k->grip, 20); + putchar ('\n'); k = k->next; } while (k); diff --git a/kbx/keybox-defs.h b/kbx/keybox-defs.h index be2dd721f..d2b79baf2 100644 --- a/kbx/keybox-defs.h +++ b/kbx/keybox-defs.h @@ -94,11 +94,12 @@ struct keybox_handle { }; -/* Openpgp helper structures. */ +/* OpenPGP helper structures. */ struct _keybox_openpgp_key_info { struct _keybox_openpgp_key_info *next; int algo; + unsigned char grip[20]; unsigned char keyid[8]; int fprlen; /* Either 16 or 20 */ unsigned char fpr[20]; diff --git a/kbx/keybox-openpgp.c b/kbx/keybox-openpgp.c index 0ba0b9ae8..6d6ed77dc 100644 --- a/kbx/keybox-openpgp.c +++ b/kbx/keybox-openpgp.c @@ -38,6 +38,13 @@ #include "../common/openpgpdefs.h" #include "../common/host2net.h" +struct keyparm_s +{ + const char *mpi; + int len; /* int to avoid a cast in gcry_sexp_build. */ +}; + + /* Assume a valid OpenPGP packet at the address pointed to by BUFBTR which has a maximum length as stored at BUFLEN. Return the header information of that packet and advance the pointer stored at BUFPTR @@ -165,6 +172,86 @@ next_packet (unsigned char const **bufptr, size_t *buflen, } +/* Take a list of key parameters KP for the OpenPGP ALGO and compute + * the keygrip which will be stored at GRIP. GRIP needs to be a + * buffer of 20 bytes. */ +static gpg_error_t +keygrip_from_keyparm (int algo, struct keyparm_s *kp, unsigned char *grip) +{ + gpg_error_t err; + gcry_sexp_t s_pkey = NULL; + + switch (algo) + { + case PUBKEY_ALGO_DSA: + err = gcry_sexp_build (&s_pkey, NULL, + "(public-key(dsa(p%b)(q%b)(g%b)(y%b)))", + kp[0].len, kp[0].mpi, + kp[1].len, kp[1].mpi, + kp[2].len, kp[2].mpi, + kp[3].len, kp[3].mpi); + break; + + case PUBKEY_ALGO_ELGAMAL: + case PUBKEY_ALGO_ELGAMAL_E: + err = gcry_sexp_build (&s_pkey, NULL, + "(public-key(elg(p%b)(g%b)(y%b)))", + kp[0].len, kp[0].mpi, + kp[1].len, kp[1].mpi, + kp[2].len, kp[2].mpi); + break; + + case PUBKEY_ALGO_RSA: + case PUBKEY_ALGO_RSA_S: + case PUBKEY_ALGO_RSA_E: + err = gcry_sexp_build (&s_pkey, NULL, + "(public-key(rsa(n%b)(e%b)))", + kp[0].len, kp[0].mpi, + kp[1].len, kp[1].mpi); + break; + + case PUBKEY_ALGO_EDDSA: + case PUBKEY_ALGO_ECDSA: + case PUBKEY_ALGO_ECDH: + { + char *curve = openpgp_oidbuf_to_str (kp[0].mpi, kp[0].len); + if (!curve) + err = gpg_error_from_syserror (); + else + { + err = gcry_sexp_build + (&s_pkey, NULL, + (algo == PUBKEY_ALGO_EDDSA)? + "(public-key(ecc(curve%s)(flags eddsa)(q%b)))": + (algo == PUBKEY_ALGO_ECDH + && openpgp_oidbuf_is_cv25519 (kp[0].mpi, kp[0].len))? + "(public-key(ecc(curve%s)(flags djb-tweak)(q%b)))": + "(public-key(ecc(curve%s)(q%b)))", + curve, kp[1].len, kp[1].mpi); + xfree (curve); + } + } + break; + + default: + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + break; + } + + if (!err && !gcry_pk_get_keygrip (s_pkey, grip)) + { + log_info ("kbx: error computing keygrip\n"); + err = gpg_error (GPG_ERR_GENERAL); + } + + gcry_sexp_release (s_pkey); + + if (err) + memset (grip, 0, 20); + return err; +} + + /* Parse a key packet and store the information in KI. */ static gpg_error_t parse_key (const unsigned char *data, size_t datalen, @@ -176,10 +263,10 @@ parse_key (const unsigned char *data, size_t datalen, size_t n; int npkey; unsigned char hashbuffer[768]; - const unsigned char *mpi_n = NULL; - size_t mpi_n_len = 0, mpi_e_len = 0; gcry_md_hd_t md; int is_ecc = 0; + struct keyparm_s keyparm[OPENPGP_MAX_NPKEY]; + unsigned char *helpmpibuf[OPENPGP_MAX_NPKEY] = { NULL }; if (datalen < 5) return gpg_error (GPG_ERR_INV_PACKET); @@ -245,6 +332,9 @@ parse_key (const unsigned char *data, size_t datalen, nbytes++; /* The size byte itself. */ if (datalen < nbytes) return gpg_error (GPG_ERR_INV_PACKET); + + keyparm[i].mpi = data; + keyparm[i].len = nbytes; } else { @@ -254,21 +344,40 @@ parse_key (const unsigned char *data, size_t datalen, nbytes = (nbits+7) / 8; if (datalen < nbytes) return gpg_error (GPG_ERR_INV_PACKET); - /* For use by v3 fingerprint calculation we need to know the RSA - modulus and exponent. */ - if (i==0) - { - mpi_n = data; - mpi_n_len = nbytes; - } - else if (i==1) - mpi_e_len = nbytes; + + keyparm[i].mpi = data; + keyparm[i].len = nbytes; } data += nbytes; datalen -= nbytes; } n = data - data_start; + + /* Note: Starting here we need to jump to leave on error. */ + + /* Make sure the MPIs are unsigned. */ + for (i=0; i < npkey; i++) + { + if (!keyparm[i].len || (keyparm[i].mpi[0] & 0x80)) + { + helpmpibuf[i] = xtrymalloc (1+keyparm[i].len); + if (!helpmpibuf[i]) + { + err = gpg_error_from_syserror (); + goto leave; + } + helpmpibuf[i][0] = 0; + memcpy (helpmpibuf[i]+1, keyparm[i].mpi, keyparm[i].len); + keyparm[i].mpi = helpmpibuf[i]; + keyparm[i].len++; + } + } + + err = keygrip_from_keyparm (algorithm, keyparm, ki->grip); + if (err) + goto leave; + if (version < 4) { /* We do not support any other algorithm than RSA in v3 @@ -279,20 +388,20 @@ parse_key (const unsigned char *data, size_t datalen, err = gcry_md_open (&md, GCRY_MD_MD5, 0); if (err) return err; /* Oops */ - gcry_md_write (md, mpi_n, mpi_n_len); - gcry_md_write (md, mpi_n+mpi_n_len+2, mpi_e_len); + gcry_md_write (md, keyparm[0].mpi, keyparm[0].len); + gcry_md_write (md, keyparm[1].mpi, keyparm[1].len); memcpy (ki->fpr, gcry_md_read (md, 0), 16); gcry_md_close (md); ki->fprlen = 16; - if (mpi_n_len < 8) + if (keyparm[0].len < 8) { /* Moduli less than 64 bit are out of the specs scope. Zero them out because this is what gpg does too. */ memset (ki->keyid, 0, 8); } else - memcpy (ki->keyid, mpi_n + mpi_n_len - 8, 8); + memcpy (ki->keyid, keyparm[0].mpi + keyparm[0].len - 8, 8); } else { @@ -327,7 +436,11 @@ parse_key (const unsigned char *data, size_t datalen, memcpy (ki->keyid, ki->fpr+12, 8); } - return 0; + leave: + for (i=0; i < npkey; i++) + xfree (helpmpibuf[i]); + + return err; } diff --git a/kbx/keybox-search.c b/kbx/keybox-search.c index 0a050ff9b..946ef52ad 100644 --- a/kbx/keybox-search.c +++ b/kbx/keybox-search.c @@ -497,6 +497,58 @@ blob_cmp_mail (KEYBOXBLOB blob, const char *name, size_t namelen, int substr, } +/* Return true if the key in BLOB matches the 20 bytes keygrip GRIP. + * We don't have the keygrips as meta data, thus we need to parse the + * certificate. Fixme: We might want to return proper error codes + * instead of failing a search for invalid certificates etc. */ +static int +blob_openpgp_has_grip (KEYBOXBLOB blob, const unsigned char *grip) +{ + int rc = 0; + const unsigned char *buffer; + size_t length; + size_t cert_off, cert_len; + struct _keybox_openpgp_info info; + struct _keybox_openpgp_key_info *k; + + buffer = _keybox_get_blob_image (blob, &length); + if (length < 40) + return 0; /* Too short. */ + cert_off = get32 (buffer+8); + cert_len = get32 (buffer+12); + if ((uint64_t)cert_off+(uint64_t)cert_len > (uint64_t)length) + return 0; /* Too short. */ + + if (_keybox_parse_openpgp (buffer + cert_off, cert_len, NULL, &info)) + return 0; /* Parse error. */ + + if (!memcmp (info.primary.grip, grip, 20)) + { + rc = 1; + goto leave; + } + + if (info.nsubkeys) + { + k = &info.subkeys; + do + { + if (!memcmp (k->grip, grip, 20)) + { + rc = 1; + goto leave; + } + k = k->next; + } + while (k); + } + + leave: + _keybox_destroy_openpgp_info (&info); + return rc; +} + + #ifdef KEYBOX_WITH_X509 /* Return true if the key in BLOB matches the 20 bytes keygrip GRIP. We don't have the keygrips as meta data, thus we need to parse the @@ -606,12 +658,11 @@ has_fingerprint (KEYBOXBLOB blob, const unsigned char *fpr) static inline int has_keygrip (KEYBOXBLOB blob, const unsigned char *grip) { + if (blob_get_type (blob) == KEYBOX_BLOBTYPE_PGP) + return blob_openpgp_has_grip (blob, grip); #ifdef KEYBOX_WITH_X509 if (blob_get_type (blob) == KEYBOX_BLOBTYPE_X509) return blob_x509_has_grip (blob, grip); -#else - (void)blob; - (void)grip; #endif return 0; } From dee0138dc022c7113309087736312d2451adf115 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 29 Jan 2019 22:15:49 +0100 Subject: [PATCH 139/235] doc: Fix typo -- --- doc/gpg.texi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/gpg.texi b/doc/gpg.texi index 6b6c8cc54..e6829b911 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -3310,7 +3310,7 @@ command has the same effect as using @option{--list-keys} with @option{--with-sig-list}. Note that in contrast to @option{--check-signatures} the key signatures are not verified. This command can be used to create a list of signing keys missing in the -lcoal keyring; for example: +local keyring; for example: @example gpg --list-sigs --with-colons USERID | \ From 6ecedd0b25b6b1a33be63b99f2a8256370000521 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 30 Jan 2019 08:28:56 +0100 Subject: [PATCH 140/235] common: New function decode_c_string. * common/miscellaneous.c (decode_c_string): New. -- This is basically a copy from the code we use in gpgme and gpa. Signed-off-by: Werner Koch --- common/miscellaneous.c | 76 ++++++++++++++++++++++++++++++++++++++++++ common/util.h | 1 + 2 files changed, 77 insertions(+) diff --git a/common/miscellaneous.c b/common/miscellaneous.c index 0b374e6c8..260552828 100644 --- a/common/miscellaneous.c +++ b/common/miscellaneous.c @@ -328,6 +328,82 @@ make_printable_string (const void *p, size_t n, int delim ) } +/* Decode the C formatted string SRC and return the result in a newly + * allocated buffer. In error returns NULL and sets ERRNO. */ +char * +decode_c_string (const char *src) +{ + char *buffer, *dst; + int val; + + /* The converted string will never be larger than the original + string. */ + buffer = dst = xtrymalloc (strlen (src) + 1); + if (!buffer) + return NULL; + + while (*src) + { + if (*src != '\\') + { + *dst++ = *src++; + continue; + } + +#define DECODE_ONE(_m,_r) case _m: src += 2; *dst++ = _r; break; + + switch (src[1]) + { + DECODE_ONE ('n', '\n'); + DECODE_ONE ('r', '\r'); + DECODE_ONE ('f', '\f'); + DECODE_ONE ('v', '\v'); + DECODE_ONE ('b', '\b'); + DECODE_ONE ('t', '\t'); + DECODE_ONE ('\\', '\\'); + DECODE_ONE ('\'', '\''); + DECODE_ONE ('\"', '\"'); + + case 'x': + val = hextobyte (src+2); + if (val == -1) /* Bad coding, keep as is. */ + { + *dst++ = *src++; + *dst++ = *src++; + if (*src) + *dst++ = *src++; + if (*src) + *dst++ = *src++; + } + else if (!val) + { + /* A binary zero is not representable in a C string thus + * we keep the C-escaping. Note that this will also + * never be larger than the source string. */ + *dst++ = '\\'; + *dst++ = '0'; + src += 4; + } + else + { + *(unsigned char *)dst++ = val; + src += 4; + } + break; + + default: /* Bad coding; keep as is.. */ + *dst++ = *src++; + *dst++ = *src++; + break; + } +#undef DECODE_ONE + } + *dst++ = 0; + + return buffer; +} + + /* Check whether (BUF,LEN) is valid header for an OpenPGP compressed * packet. LEN should be at least 6. */ static int diff --git a/common/util.h b/common/util.h index 8234b6257..a4b1cbd79 100644 --- a/common/util.h +++ b/common/util.h @@ -303,6 +303,7 @@ void print_hexstring (FILE *fp, const void *buffer, size_t length, int reserved); char *try_make_printable_string (const void *p, size_t n, int delim); char *make_printable_string (const void *p, size_t n, int delim); +char *decode_c_string (const char *src); int is_file_compressed (const char *s, int *ret_rc); From 346a98fabe03adf2e202e36fc2aa24b1c2571154 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 30 Jan 2019 11:28:14 +0100 Subject: [PATCH 141/235] gpg: Allow generating Ed25519 key from an existing key. * g10/misc.c (map_pk_gcry_to_openpgp): Add EdDSA mapping. -- Due to this missing mapping a "gpg --export --full-gen-key" with selection "13 - Existing key" did not worked for an ed25519 key. Signed-off-by: Werner Koch --- g10/misc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/g10/misc.c b/g10/misc.c index a3f0c679e..0541d2b77 100644 --- a/g10/misc.c +++ b/g10/misc.c @@ -513,6 +513,7 @@ map_pk_gcry_to_openpgp (enum gcry_pk_algos algo) { switch (algo) { + case GCRY_PK_EDDSA: return PUBKEY_ALGO_EDDSA; case GCRY_PK_ECDSA: return PUBKEY_ALGO_ECDSA; case GCRY_PK_ECDH: return PUBKEY_ALGO_ECDH; default: return algo < 110 ? (pubkey_algo_t)algo : 0; From 1fd3d864b4eceaf45b33e754e5d832b7ccc0d17f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 30 Jan 2019 14:38:37 +0100 Subject: [PATCH 142/235] common: Add kludge to allow silencing gnupg_exec_tool_stream. * common/exectool.c (read_and_log_buffer_t): Take care of a --quiet argument. (gnupg_exec_tool_stream): Ditto. -- If we pass --quiet to a program it should really shutup and the parent should be quite as well. --- common/exectool.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/common/exectool.c b/common/exectool.c index 3458de4a7..82b398542 100644 --- a/common/exectool.c +++ b/common/exectool.c @@ -53,6 +53,7 @@ typedef struct exec_tool_status_cb_t status_cb; void *status_cb_value; int cont; + int quiet; size_t used; size_t buffer_size; char *buffer; @@ -110,6 +111,8 @@ read_and_log_stderr (read_and_log_buffer_t *state, es_poll_t *fderr) state->status_cb (state->status_cb_value, state->buffer + 9, rest); } + else if (state->quiet) + ; else if (!state->cont && !strncmp (state->buffer, pname, len) && strlen (state->buffer) > strlen (pname) @@ -331,10 +334,16 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], int count; read_and_log_buffer_t fderrstate; struct copy_buffer *cpbuf_in = NULL, *cpbuf_out = NULL, *cpbuf_extra = NULL; + int quiet = 0; + int dummy_exitcode; memset (fds, 0, sizeof fds); memset (&fderrstate, 0, sizeof fderrstate); + /* If the first argument to the program is "--quiet" avoid all extra + * diagnostics. */ + quiet = (argv && argv[0] && !strcmp (argv[0], "--quiet")); + cpbuf_in = xtrymalloc (sizeof *cpbuf_in); if (cpbuf_in == NULL) { @@ -360,6 +369,7 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], copy_buffer_init (cpbuf_extra); fderrstate.pgmname = pgmname; + fderrstate.quiet = quiet; fderrstate.status_cb = status_cb; fderrstate.status_cb_value = status_cb_value; fderrstate.buffer_size = 256; @@ -375,7 +385,7 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], err = gnupg_create_outbound_pipe (extrapipe, &extrafp, 1); if (err) { - log_error ("error running outbound pipe for extra fp: %s\n", + log_error ("error creating outbound pipe for extra fp: %s\n", gpg_strerror (err)); goto leave; } @@ -411,7 +421,8 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], argv[argsaveidx] = argsave; if (err) { - log_error ("error running '%s': %s\n", pgmname, gpg_strerror (err)); + if (!quiet) + log_error ("error running '%s': %s\n", pgmname, gpg_strerror (err)); goto leave; } @@ -535,7 +546,7 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], es_fclose (outfp); outfp = NULL; es_fclose (errfp); errfp = NULL; - err = gnupg_wait_process (pgmname, pid, 1, NULL); + err = gnupg_wait_process (pgmname, pid, 1, quiet? &dummy_exitcode : NULL); pid = (pid_t)(-1); leave: @@ -547,7 +558,7 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], es_fclose (outfp); es_fclose (errfp); if (pid != (pid_t)(-1)) - gnupg_wait_process (pgmname, pid, 1, NULL); + gnupg_wait_process (pgmname, pid, 1, quiet? &dummy_exitcode : NULL); gnupg_release_process (pid); copy_buffer_shred (cpbuf_in); From 140fda8c61422ec055c3f7e214cc35706c4320dd Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 30 Jan 2019 14:40:26 +0100 Subject: [PATCH 143/235] gpg: Emit an ERROR status if no key was found with --list-keys. * g10/keylist.c (list_one): Emit status line. -- Signed-off-by: Werner Koch --- g10/keylist.c | 1 + 1 file changed, 1 insertion(+) diff --git a/g10/keylist.c b/g10/keylist.c index 0b8aa88cd..fffd95c31 100644 --- a/g10/keylist.c +++ b/g10/keylist.c @@ -611,6 +611,7 @@ list_one (ctrl_t ctrl, strlist_t names, int secret, int mark_secret) { log_error ("error reading key: %s\n", gpg_strerror (rc)); getkey_end (ctrl, ctx); + write_status_error ("keylist.getkey", rc); return; } From 833f27a6a7e059e38bccaf360f05e72e4403545a Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 30 Jan 2019 15:01:34 +0100 Subject: [PATCH 144/235] card: Print matching OpenPGP and X.509 data. * tools/card-tool-keys.c: New. * tools/Makefile.am (gpg_card_tool_SOURCES): Add file. * tools/card-tool.h (struct pubkey_s, pubkey_t): New. (struct userid_s, userid_t): New. (struct keyblock_s, keyblock_t): New. * common/util.h (GNUPG_PROTOCOL_): New const * tools/gpg-card-tool.c (aTest): Add temporary command. (list_one_kinfo): Print info from gpg and gpgsm. Signed-off-by: Werner Koch --- common/util.h | 7 + tools/Makefile.am | 1 + tools/card-tool-keys.c | 467 +++++++++++++++++++++++++++++++++++++++++ tools/card-tool.h | 48 ++++- tools/gpg-card-tool.c | 92 +++++++- 5 files changed, 605 insertions(+), 10 deletions(-) create mode 100644 tools/card-tool-keys.c diff --git a/common/util.h b/common/util.h index a4b1cbd79..863f9e36f 100644 --- a/common/util.h +++ b/common/util.h @@ -262,6 +262,13 @@ void gnupg_module_name_flush_some (void); void gnupg_set_builddir (const char *newdir); +/* A list of constants to identify protocols. This is used by tools + * which need to distinguish between the different protocols + * implemented by GnuPG. May be used as bit flags. */ +#define GNUPG_PROTOCOL_OPENPGP 1 /* The one and only (gpg). */ +#define GNUPG_PROTOCOL_CMS 2 /* The core of S/MIME (gpgsm) */ +#define GNUPG_PROTOCOL_SSH_AGENT 4 /* Out ssh-agent implementation */ + /*-- gpgrlhelp.c --*/ void gnupg_rl_initialize (void); diff --git a/tools/Makefile.am b/tools/Makefile.am index f74221b2d..ad0f223b4 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -128,6 +128,7 @@ gpg_card_tool_SOURCES = \ gpg-card-tool.c \ card-tool.h \ card-call-scd.c \ + card-tool-keys.c \ card-tool-misc.c gpg_card_tool_LDADD = ../common/libgpgrl.a $(common_libs) \ diff --git a/tools/card-tool-keys.c b/tools/card-tool-keys.c new file mode 100644 index 000000000..af2425cbf --- /dev/null +++ b/tools/card-tool-keys.c @@ -0,0 +1,467 @@ +/* card-tool-keys.c - OpenPGP and CMS related functions for gpg-card-tool + * Copyright (C) 2019 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 . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include +#include + +#include "../common/util.h" +#include "../common/i18n.h" +#include "../common/ccparray.h" +#include "../common/exectool.h" +#include "../common/openpgpdefs.h" +#include "card-tool.h" + +/* Release a keyblocm object. */ +void +release_keyblock (keyblock_t keyblock) +{ + pubkey_t pubkey; + userid_t uid; + + while (keyblock) + { + keyblock_t keyblocknext = keyblock->next; + pubkey = keyblock->keys; + while (pubkey) + { + pubkey_t pubkeynext = pubkey->next; + xfree (pubkey); + pubkey = pubkeynext; + } + uid = keyblock->uids; + while (uid) + { + userid_t uidnext = uid->next; + xfree (uid->value); + xfree (uid); + uid = uidnext; + } + xfree (keyblock); + keyblock = keyblocknext; + } +} + + + +/* Object to communicate with the status_cb. */ +struct status_cb_s +{ + const char *pgm; /* Name of the program for debug purposes. */ + int no_pubkey; /* Result flag. */ +}; + + +/* Status callback helper for the exec functions. */ +static void +status_cb (void *opaque, const char *keyword, char *args) +{ + struct status_cb_s *c = opaque; + const char *s; + + if (DBG_EXTPROG) + log_debug ("%s: status: %s %s\n", c->pgm, keyword, args); + + if (!strcmp (keyword, "ERROR") + && (s=has_leading_keyword (args, "keylist.getkey")) + && gpg_err_code (atoi (s)) == GPG_ERR_NO_PUBKEY) + { + /* No public key was found. gpg terminates with an error in + * this case and we can't change that behaviour. Instead we + * detect this status and carry that error forward. */ + c->no_pubkey = 1; + } + +} + + +/* Helper for get_matching_keys to parse "pub" style records. */ +static gpg_error_t +parse_key_record (char **fields, int nfields, pubkey_t *r_pubkey) +{ + pubkey_t pubkey; + + pubkey = xtrycalloc (1, sizeof *pubkey); + if (!pubkey) + return gpg_error_from_syserror (); + *r_pubkey = pubkey; + return 0; +} + + +/* Run gpg or gpgsm to get a list of all keys matching the 20 byte + * KEYGRIP. PROTOCOL is one of or a combination of + * GNUPG_PROTOCOL_OPENPGP and GNUPG_PROTOCOL_CMS. On success a new + * keyblock is stored at R_KEYBLOCK; on error NULL is stored there. */ +gpg_error_t +get_matching_keys (const unsigned char *keygrip, int protocol, + keyblock_t *r_keyblock) +{ + gpg_error_t err; + ccparray_t ccp; + const char **argv; + estream_t listing; + char hexgrip[1 + (2*KEYGRIP_LEN) + 1]; + char *line = NULL; + size_t length_of_line = 0; + size_t maxlen; + ssize_t len; + char **fields = NULL; + int nfields; + int first_seen; + keyblock_t keyblock_head, *keyblock_tail, kb; + pubkey_t pubkey, pk; + size_t n; + struct status_cb_s status_cb_parm; + + *r_keyblock = NULL; + + keyblock_head = NULL; + keyblock_tail = &keyblock_head; + kb = NULL; + + /* Shortcut to run a listing on both protocols. */ + if ((protocol & GNUPG_PROTOCOL_OPENPGP) && (protocol & GNUPG_PROTOCOL_CMS)) + { + err = get_matching_keys (keygrip, GNUPG_PROTOCOL_OPENPGP, &kb); + if (!err || gpg_err_code (err) == GPG_ERR_NO_PUBKEY) + { + *keyblock_tail = kb; + keyblock_tail = &kb->next; + kb = NULL; + err = get_matching_keys (keygrip, GNUPG_PROTOCOL_CMS, &kb); + if (!err) + { + *keyblock_tail = kb; + keyblock_tail = &kb->next; + kb = NULL; + } + else if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY) + err = 0; + } + if (err) + release_keyblock (keyblock_head); + else + *r_keyblock = keyblock_head; + return err; + } + + /* Check that we have only one protocol. */ + if (protocol != GNUPG_PROTOCOL_OPENPGP && protocol != GNUPG_PROTOCOL_CMS) + return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL); + + /* Open a memory stream. */ + listing = es_fopenmem (0, "w+b"); + if (!listing) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); + return err; + } + + status_cb_parm.pgm = protocol == GNUPG_PROTOCOL_OPENPGP? "gpg":"gpgsm"; + status_cb_parm.no_pubkey = 0; + + hexgrip[0] = '&'; + bin2hex (keygrip, KEYGRIP_LEN, hexgrip+1); + + ccparray_init (&ccp, 0); + + if (opt.verbose > 1 || DBG_EXTPROG) + ccparray_put (&ccp, "--verbose"); + else + ccparray_put (&ccp, "--quiet"); + ccparray_put (&ccp, "--no-options"); + ccparray_put (&ccp, "--batch"); + ccparray_put (&ccp, "--status-fd=2"); + ccparray_put (&ccp, "--with-colons"); + ccparray_put (&ccp, "--with-keygrip"); + ccparray_put (&ccp, "--list-keys"); + ccparray_put (&ccp, hexgrip); + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = gnupg_exec_tool_stream (protocol == GNUPG_PROTOCOL_OPENPGP? + opt.gpg_program : opt.gpgsm_program, + argv, NULL, NULL, listing, status_cb, + &status_cb_parm); + if (err) + { + if (status_cb_parm.no_pubkey) + err = gpg_error (GPG_ERR_NO_PUBKEY); + else if (gpg_err_code (err) != GPG_ERR_GENERAL) + log_error ("key listing failed: %s\n", gpg_strerror (err)); + goto leave; + } + + es_rewind (listing); + first_seen = 0; + maxlen = 8192; /* Set limit large enough for all escaped UIDs. */ + while ((len = es_read_line (listing, &line, &length_of_line, &maxlen)) > 0) + { + if (!maxlen) + { + log_error ("received line too long\n"); + err = gpg_error (GPG_ERR_LINE_TOO_LONG); + goto leave; + } + /* Strip newline and carriage return, if present. */ + while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) + line[--len] = '\0'; + + xfree (fields); + fields = strtokenize (line, ":"); + if (!fields) + { + err = gpg_error_from_syserror (); + log_error ("strtokenize failed: %s\n", gpg_strerror (err)); + goto leave; + } + for (nfields = 0; fields[nfields]; nfields++) + ; + if (!nfields) + { + err = gpg_error (GPG_ERR_INV_ENGINE); + goto leave; + } + + /* Skip over all records until we reach a pub or sec. */ + if (!first_seen + && (!strcmp (fields[0], "pub") || !strcmp (fields[0], "sec") + || !strcmp (fields[0], "crt") || !strcmp (fields[0], "crs"))) + first_seen = 1; + if (!first_seen) + continue; + + if (!strcmp (fields[0], "pub") || !strcmp (fields[0], "sec") + || !strcmp (fields[0], "crt") || !strcmp (fields[0], "crs")) + { + if (kb) /* Finish the current keyblock. */ + { + *keyblock_tail = kb; + keyblock_tail = &kb->next; + } + kb = xtrycalloc (1, sizeof *kb); + if (!kb) + { + err = gpg_error_from_syserror (); + goto leave; + } + kb->protocol = protocol; + err = parse_key_record (fields, nfields, &pubkey); + if (err) + goto leave; + kb->keys = pubkey; + pubkey = NULL; + } + else if (!strcmp (fields[0], "sub") || !strcmp (fields[0], "ssb")) + { + log_assert (kb && kb->keys); + err = parse_key_record (fields, nfields, &pubkey); + if (err) + goto leave; + for (pk = kb->keys; pk->next; pk = pk->next) + ; + pk->next = pubkey; + pubkey = NULL; + } + else if (!strcmp (fields[0], "fpr") && nfields > 9) + { + log_assert (kb && kb->keys); + n = strlen (fields[9]); + if (n != 64 && n != 40 && n != 32) + { + log_debug ("bad length (%zu) in fpr record\n", n); + err = gpg_error (GPG_ERR_INV_ENGINE); + goto leave; + } + n /= 2; + + for (pk = kb->keys; pk->next; pk = pk->next) + ; + if (pk->fprlen) + { + log_debug ("too many fpr records\n"); + err = gpg_error (GPG_ERR_INV_ENGINE); + goto leave; + } + log_assert (n <= sizeof pk->fpr); + pk->fprlen = n; + if (hex2bin (fields[9], pk->fpr, n) < 0) + { + log_debug ("bad chars in fpr record\n"); + err = gpg_error (GPG_ERR_INV_ENGINE); + goto leave; + } + } + else if (!strcmp (fields[0], "grp") && nfields > 9) + { + log_assert (kb && kb->keys); + n = strlen (fields[9]); + if (n != 2*KEYGRIP_LEN) + { + log_debug ("bad length (%zu) in grp record\n", n); + err = gpg_error (GPG_ERR_INV_ENGINE); + goto leave; + } + n /= 2; + + for (pk = kb->keys; pk->next; pk = pk->next) + ; + if (pk->grip_valid) + { + log_debug ("too many grp records\n"); + err = gpg_error (GPG_ERR_INV_ENGINE); + goto leave; + } + if (hex2bin (fields[9], pk->grip, KEYGRIP_LEN) < 0) + { + log_debug ("bad chars in fpr record\n"); + err = gpg_error (GPG_ERR_INV_ENGINE); + goto leave; + } + pk->grip_valid = 1; + if (!memcmp (pk->grip, keygrip, KEYGRIP_LEN)) + pk->requested = 1; + } + else if (!strcmp (fields[0], "uid") && nfields > 9) + { + userid_t uid, u; + + uid = xtrycalloc (1, sizeof *uid); + if (!uid) + { + err = gpg_error_from_syserror (); + goto leave; + } + uid->value = decode_c_string (fields[9]); + if (!uid->value) + { + err = gpg_error_from_syserror (); + xfree (uid); + goto leave; + } + if (!kb->uids) + kb->uids = uid; + else + { + for (u = kb->uids; u->next; u = u->next) + ; + u->next = uid; + } + } + } + if (len < 0 || es_ferror (listing)) + { + err = gpg_error_from_syserror (); + log_error ("error reading memory stream\n"); + goto leave; + } + + if (kb) /* Finish the current keyblock. */ + { + *keyblock_tail = kb; + keyblock_tail = &kb->next; + kb = NULL; + } + + if (!keyblock_head) + err = gpg_error (GPG_ERR_NO_PUBKEY); + + leave: + if (err) + release_keyblock (keyblock_head); + else + *r_keyblock = keyblock_head; + xfree (kb); + xfree (fields); + es_free (line); + xfree (argv); + es_fclose (listing); + return err; +} + + +void +dump_keyblock (keyblock_t keyblock) +{ + keyblock_t kb; + pubkey_t pubkey; + userid_t uid; + + for (kb = keyblock; kb; kb = kb->next) + { + log_info ("%s key:\n", + kb->protocol == GNUPG_PROTOCOL_OPENPGP? "OpenPGP":"X.509"); + for (pubkey = kb->keys; pubkey; pubkey = pubkey->next) + { + log_info (" grip: "); + if (pubkey->grip_valid) + log_printhex (pubkey->grip, KEYGRIP_LEN, NULL); + log_printf ("%s\n", pubkey->requested? " (*)":""); + + log_info (" fpr: "); + log_printhex (pubkey->fpr, pubkey->fprlen, ""); + } + for (uid = kb->uids; uid; uid = uid->next) + { + log_info (" uid: %s\n", uid->value); + } + } +} + + + +gpg_error_t +test_get_matching_keys (const char *hexgrip) +{ + gpg_error_t err; + unsigned char grip[KEYGRIP_LEN]; + keyblock_t keyblock; + + if (strlen (hexgrip) != 40) + { + log_error ("error: invalid keygrip\n"); + return 0; + } + if (hex2bin (hexgrip, grip, sizeof grip) < 0) + { + log_error ("error: bad kegrip\n"); + return 0; + } + err = get_matching_keys (grip, + (GNUPG_PROTOCOL_OPENPGP | GNUPG_PROTOCOL_CMS), + &keyblock); + if (err) + { + log_error ("get_matching_keys failed: %s\n", gpg_strerror (err)); + return err; + } + + dump_keyblock (keyblock); + release_keyblock (keyblock); + return 0; +} diff --git a/tools/card-tool.h b/tools/card-tool.h index b1d866228..d502ecb58 100644 --- a/tools/card-tool.h +++ b/tools/card-tool.h @@ -50,6 +50,41 @@ struct #define DBG_IPC (opt.debug & DBG_IPC_VALUE) #define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE) +/* The maximum length of a binary fingerprint. */ +#define MAX_FINGERPRINT_LEN 32 + + +/* + * Data structures to store keyblocks (aka certificates). + */ +struct pubkey_s +{ + struct pubkey_s *next; /* The next key. */ + unsigned char grip[KEYGRIP_LEN]; + unsigned char fpr[MAX_FINGERPRINT_LEN]; + unsigned char fprlen; /* The used length of a FPR. */ + unsigned int grip_valid:1;/* The grip is valid. */ + unsigned int requested: 1;/* This is the requested grip. */ +}; +typedef struct pubkey_s *pubkey_t; + +struct userid_s +{ + struct userid_s *next; + char *value; /* Malloced. */ +}; +typedef struct userid_s *userid_t; + +struct keyblock_s +{ + struct keyblock_s *next; /* Allow to link several keyblocks. */ + int protocol; /* GPGME_PROTOCOL_OPENPGP or _CMS. */ + pubkey_t keys; /* The key. For OpenPGP primary + list of subkeys. */ + userid_t uids; /* The list of user ids. */ +}; +typedef struct keyblock_s *keyblock_t; + + /* Enumeration of the known card application types. */ typedef enum @@ -76,9 +111,9 @@ struct key_attr }; }; -/* An object to store information pertaining to a key pair. This is - * commonly used as a linked list with all keys known for the current - * card. */ +/* An object to store information pertaining to a key pair as stored + * on a card. This is commonly used as a linked list with all keys + * known for the current card. */ struct key_info_s { struct key_info_s *next; @@ -144,6 +179,13 @@ struct card_info_s typedef struct card_info_s *card_info_t; +/*-- card-tool-keys.c --*/ +void release_keyblock (keyblock_t keyblock); +gpg_error_t get_matching_keys (const unsigned char *keygrip, int protocol, + keyblock_t *r_keyblock); +gpg_error_t test_get_matching_keys (const char *hexgrip); + + /*-- card-tool-misc.c --*/ key_info_t find_kinfo (card_info_t info, const char *keyref); diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index 4f7962060..321426bdd 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -69,6 +69,9 @@ enum cmd_and_opt_values oLCctype, oLCmessages, + aTest, + + oDummy }; @@ -76,6 +79,7 @@ enum cmd_and_opt_values /* The list of commands and options. */ static ARGPARSE_OPTS opts[] = { ARGPARSE_group (300, ("@Commands:\n ")), + ARGPARSE_c (aTest, "test", "test command"), ARGPARSE_group (301, ("@\nOptions:\n ")), @@ -227,6 +231,10 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) case oLCctype: opt.lc_ctype = pargs->r.ret_str; break; case oLCmessages: opt.lc_messages = pargs->r.ret_str; break; + case aTest: + cmd = pargs->r_opt; + break; + default: pargs->err = 2; break; } } @@ -292,6 +300,12 @@ main (int argc, char **argv) /* Run the selected command. */ switch (cmd) { + case aTest: + if (!argc) + wrong_args ("--test KEYGRIP"); + err = test_get_matching_keys (*argv); + break; + default: interactive_loop (); err = 0; @@ -580,15 +594,25 @@ mem_is_ff (const char *mem, unsigned int memlen) /* Helper to list a single keyref. */ static void -list_one_kinfo (key_info_t kinfo, estream_t fp) +list_one_kinfo (key_info_t firstkinfo, key_info_t kinfo, estream_t fp) { - if (kinfo) + gpg_error_t err; + keyblock_t keyblock = NULL; + keyblock_t kb; + pubkey_t pubkey; + userid_t uid; + key_info_t ki; + const char *s; + + if (firstkinfo && kinfo) { tty_fprintf (fp, " "); if (mem_is_zero (kinfo->grip, sizeof kinfo->grip)) - tty_fprintf (fp, "[none]\n"); - else - print_keygrip (fp, kinfo->grip); + { + tty_fprintf (fp, "[none]\n"); + goto leave; + } + print_keygrip (fp, kinfo->grip); if (kinfo->fprlen && kinfo->created) { @@ -597,9 +621,63 @@ list_one_kinfo (key_info_t kinfo, estream_t fp) tty_fprintf (fp, " created ....: %s\n", isotimestamp (kinfo->created)); } + err = get_matching_keys (kinfo->grip, + (GNUPG_PROTOCOL_OPENPGP | GNUPG_PROTOCOL_CMS), + &keyblock); + if (err) + { + if (gpg_err_code (err) != GPG_ERR_NO_PUBKEY) + tty_fprintf (fp, " error ......: %s\n", gpg_strerror (err)); + goto leave; + } + for (kb = keyblock; kb; kb = kb->next) + { + tty_fprintf (fp, " used for ...: %s\n", + kb->protocol == GNUPG_PROTOCOL_OPENPGP? "OpenPGP" : + kb->protocol == GNUPG_PROTOCOL_CMS? "X.509" : "?"); + pubkey = kb->keys; + /* If this is not the primary key print the primary key's + * fingerprint or a reference to it. */ + if (kb->protocol == GNUPG_PROTOCOL_OPENPGP) + { + tty_fprintf (fp, " main key .:"); + for (ki=firstkinfo; ki; ki = ki->next) + if (pubkey->grip_valid + && !memcmp (ki->grip, pubkey->grip, KEYGRIP_LEN)) + break; + if (ki) + { + /* Fixme: Replace mapping by a table lookup. */ + if (!memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN)) + s = "this"; + else if (!strcmp (ki->keyref, "OPENPGP.1")) + s = "Signature key"; + else if (!strcmp (ki->keyref, "OPENPGP.2")) + s = "Encryption key"; + else if (!strcmp (ki->keyref, "OPENPGP.3")) + s = "Authentication key"; + else + s = NULL; + if (s) + tty_fprintf (fp, " <%s>\n", s); + else + tty_fprintf (fp, " \n", ki->keyref); + } + else + print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen); + } + for (uid = kb->uids; uid; uid = uid->next) + { + print_string (fp, " user id ..: ", uid->value); + } + + } } else tty_fprintf (fp, " [none]\n"); + + leave: + release_keyblock (keyblock); } @@ -620,7 +698,7 @@ list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp) { tty_fprintf (fp, "%s", labels[idx].label); kinfo = find_kinfo (info, labels[idx].keyref); - list_one_kinfo (kinfo, fp); + list_one_kinfo (info->kinfo, kinfo, fp); if (kinfo) kinfo->xflag = 1; } @@ -633,7 +711,7 @@ list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp) for (i=5+strlen (kinfo->keyref); i < 18; i++) tty_fprintf (fp, "."); tty_fprintf (fp, ":"); - list_one_kinfo (kinfo, fp); + list_one_kinfo (info->kinfo, kinfo, fp); } } From 0107984f9f55f84e4842642bceefd2181ec09dab Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 30 Jan 2019 17:48:41 +0100 Subject: [PATCH 145/235] card: Cache the results from gpg and gpgsm. * tools/card-tool-keys.c (keyblock_cache): New var. (release_keyblock): Factor code out to a new do_release_keyblock. Add a cache. (flush_keyblock_cache): New. (get_matching_keys): Use the cache. * tools/gpg-card-tool.c (cmds): Add command "reset". (interactive_loop): Implement reset. -- Signed-off-by: Werner Koch --- tools/card-tool-keys.c | 88 ++++++++++++++++++++++++++++++++++++++++-- tools/card-tool.h | 1 + tools/gpg-card-tool.c | 4 ++ 3 files changed, 90 insertions(+), 3 deletions(-) diff --git a/tools/card-tool-keys.c b/tools/card-tool-keys.c index af2425cbf..e9edf9818 100644 --- a/tools/card-tool-keys.c +++ b/tools/card-tool-keys.c @@ -30,9 +30,22 @@ #include "../common/openpgpdefs.h" #include "card-tool.h" -/* Release a keyblocm object. */ -void -release_keyblock (keyblock_t keyblock) + +/* It is quite common that all keys of an OpenPGP card belong to the + * the same OpenPGP keyblock. To avoid running several queries + * despite that we already got the information with the previous + * keyblock, we keep a small cache of of previous done queries. */ +static struct +{ + unsigned int lru; + keyblock_t keyblock; +} keyblock_cache[5]; + + + +/* Helper for release_keyblock. */ +static void +do_release_keyblock (keyblock_t keyblock) { pubkey_t pubkey; userid_t uid; @@ -61,6 +74,62 @@ release_keyblock (keyblock_t keyblock) } +/* Release a keyblock object. */ +void +release_keyblock (keyblock_t keyblock) +{ + static unsigned int lru_counter; + unsigned int lru; + int i, lru_idx; + + if (!keyblock) + return; + + lru = (unsigned int)(-1); + lru_idx = 0; + for (i=0; i < DIM (keyblock_cache); i++) + { + if (!keyblock_cache[i].keyblock) + { + keyblock_cache[i].keyblock = keyblock; + keyblock_cache[i].lru = ++lru_counter; + goto leave; + } + if (keyblock_cache[i].lru < lru) + { + lru = keyblock_cache[i].lru; + lru_idx = i; + } + } + + /* No free slot. Replace one. */ + do_release_keyblock (keyblock_cache[lru_idx].keyblock); + keyblock_cache[lru_idx].keyblock = keyblock; + keyblock_cache[lru_idx].lru = ++lru_counter; + + leave: + if (!lru_counter) + { + /* Wrapped around. We simply clear the entire cache. */ + flush_keyblock_cache (); + } +} + + +/* Flush the enire keyblock cache. */ +void +flush_keyblock_cache (void) +{ + int i; + + for (i=0; i < DIM (keyblock_cache); i++) + { + do_release_keyblock (keyblock_cache[i].keyblock); + keyblock_cache[i].keyblock = NULL; + } +} + + /* Object to communicate with the status_cb. */ struct status_cb_s @@ -127,6 +196,7 @@ get_matching_keys (const unsigned char *keygrip, int protocol, char **fields = NULL; int nfields; int first_seen; + int i; keyblock_t keyblock_head, *keyblock_tail, kb; pubkey_t pubkey, pk; size_t n; @@ -168,6 +238,18 @@ get_matching_keys (const unsigned char *keygrip, int protocol, if (protocol != GNUPG_PROTOCOL_OPENPGP && protocol != GNUPG_PROTOCOL_CMS) return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL); + /* Try to get it from our cache. */ + for (i=0; i < DIM (keyblock_cache); i++) + for (kb = keyblock_cache[i].keyblock; kb; kb = kb->next) + if (kb->protocol == protocol) + for (pk = kb->keys; pk; pk = pk->next) + if (pk->grip_valid && !memcmp (pk->grip, keygrip, KEYGRIP_LEN)) + { + *r_keyblock = keyblock_cache[i].keyblock; + keyblock_cache[i].keyblock = NULL; + return 0; + } + /* Open a memory stream. */ listing = es_fopenmem (0, "w+b"); if (!listing) diff --git a/tools/card-tool.h b/tools/card-tool.h index d502ecb58..05d6ea47d 100644 --- a/tools/card-tool.h +++ b/tools/card-tool.h @@ -181,6 +181,7 @@ typedef struct card_info_s *card_info_t; /*-- card-tool-keys.c --*/ void release_keyblock (keyblock_t keyblock); +void flush_keyblock_cache (void); gpg_error_t get_matching_keys (const unsigned char *keygrip, int protocol, keyblock_t *r_keyblock); gpg_error_t test_get_matching_keys (const char *hexgrip); diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index 321426bdd..07b8bc67b 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -312,6 +312,8 @@ main (int argc, char **argv) break; } + flush_keyblock_cache (); + if (err) gnupg_status_printf (STATUS_FAILURE, "- %u", err); else if (log_get_errorcount (0)) @@ -2639,6 +2641,7 @@ static struct { "passwd" , cmdPASSWD, 0, N_("menu to change or unblock the PIN")}, { "verify" , cmdVERIFY, 0, N_("verify the PIN and list all data")}, { "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code")}, + { "reset" , cmdRESET, 0, N_("send a reset to the card daemon")}, { "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")}, { "kdf-setup", cmdKDFSETUP, 1, N_("setup KDF for PIN authentication")}, { "key-attr", cmdKEYATTR, 1, N_("change the key attribute")}, @@ -2834,6 +2837,7 @@ interactive_loop (void) "Send a RESET to the card daemon.", 0); else { + flush_keyblock_cache (); err = scd_apdu (NULL, NULL); } break; From 1d57450f3e71b198e66e155a8ebbfab452f58ffc Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 31 Jan 2019 14:26:17 +0100 Subject: [PATCH 146/235] scd: Add DES authentication for PIV card. * scd/app-piv.c (flush_cached_data): New. (auth_adm_key): New. (set_adm_key): New. (do_setattr): New. * scd/command.c (MAXLEN_SETATTRDATA): New. (cmd_setattr): Add an inquire option. Signed-off-by: Werner Koch --- scd/app-piv.c | 255 ++++++++++++++++++++++++++++++++++++++++++++++++-- scd/command.c | 49 ++++++++-- 2 files changed, 287 insertions(+), 17 deletions(-) diff --git a/scd/app-piv.c b/scd/app-piv.c index d984e9c7a..d34ff7d10 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -112,7 +112,7 @@ static struct data_object_s data_objects[] = { { 0x5FC122, 0, 0,0, 1, 0,0, 0, "", "2.16.23", "SM Cert Signer" }, { 0x5FC123, 0, 3,3, 1, 0,0, 0, "", "2.16.24", "Pairing Code Ref Data" }, { 0 } - /* Other key reference values without a tag: + /* Other key reference values without a data object: * "00" Global PIN (not cleared by application switching) * "04" PIV Secure Messaging Key * "80" PIV Application PIN @@ -142,7 +142,7 @@ struct app_local_s { /* Various flags. */ struct { - unsigned int dummy:1; + unsigned int yubikey:1; /* This is on a Yubikey. */ } flags; }; @@ -266,6 +266,30 @@ get_cached_data (app_t app, int tag, } +/* Remove data object described by TAG from the cache. */ +static void +flush_cached_data (app_t app, int tag) +{ + struct cache_s *c, *cprev; + + for (c=app->app_local->cache, cprev=NULL; c; cprev=c, c = c->next) + if (c->tag == tag) + { + if (cprev) + cprev->next = c->next; + else + app->app_local->cache = c->next; + xfree (c); + + for (c=app->app_local->cache; c ; c = c->next) + { + log_assert (c->tag != tag); /* Oops: duplicated entry. */ + } + return; + } +} + + /* Get the DO identified by TAG from the card in SLOT and return a * buffer with its content in RESULT and NBYTES. The return value is * NULL if not found or a pointer which must be used to release the @@ -552,6 +576,218 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name) } +/* Authenticate the card using the Card Application Administration + * Key. (VALUE,VALUELEN) has that 24 byte key. */ +static gpg_error_t +auth_adm_key (app_t app, const unsigned char *value, size_t valuelen) +{ + gpg_error_t err; + unsigned char tmpl[4+24]; + size_t tmpllen; + unsigned char *outdata = NULL; + size_t outdatalen; + const unsigned char *s; + char witness[8]; + size_t n; + gcry_cipher_hd_t cipher = NULL; + + /* Prepare decryption. */ + err = gcry_cipher_open (&cipher, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_ECB, 0); + if (err) + goto leave; + err = gcry_cipher_setkey (cipher, value, valuelen); + if (err) + goto leave; + + /* Request a witness. */ + tmpl[0] = 0x7c; + tmpl[1] = 0x02; + tmpl[2] = 0x80; + tmpl[3] = 0; /* (Empty witness requests a witness.) */ + tmpllen = 4; + err = iso7816_general_authenticate (app->slot, 0, + PIV_ALGORITHM_3DES_ECB_0, 0x9B, + tmpl, tmpllen, 0, + &outdata, &outdatalen); + if (err) + goto leave; + if (!(outdatalen && *outdata == 0x7c + && (s = find_tlv (outdata, outdatalen, 0x80, &n)) + && n == 8)) + { + err = gpg_error (GPG_ERR_CARD); + log_error ("piv: improper witness received\n"); + goto leave; + } + err = gcry_cipher_decrypt (cipher, witness, 8, s, 8); + if (err) + goto leave; + + /* Return decrypted witness and send our challenge. */ + tmpl[0] = 0x7c; + tmpl[1] = 22; + tmpl[2] = 0x80; + tmpl[3] = 8; + memcpy (tmpl+4, witness, 8); + tmpl[12] = 0x81; + tmpl[13] = 8; + gcry_create_nonce (tmpl+14, 8); + tmpl[22] = 0x80; + tmpl[23] = 0; + tmpllen = 24; + xfree (outdata); + err = iso7816_general_authenticate (app->slot, 0, + PIV_ALGORITHM_3DES_ECB_0, 0x9B, + tmpl, tmpllen, 0, + &outdata, &outdatalen); + if (err) + goto leave; + if (!(outdatalen && *outdata == 0x7c + && (s = find_tlv (outdata, outdatalen, 0x82, &n)) + && n == 8)) + { + err = gpg_error (GPG_ERR_CARD); + log_error ("piv: improper challenge received\n"); + goto leave; + } + /* (We reuse the witness buffer.) */ + err = gcry_cipher_decrypt (cipher, witness, 8, s, 8); + if (err) + goto leave; + if (memcmp (witness, tmpl+14, 8)) + { + err = gpg_error (GPG_ERR_BAD_SIGNATURE); + goto leave; + } + + leave: + xfree (outdata); + gcry_cipher_close (cipher); + return err; +} + + +/* Set a new admin key. */ +static gpg_error_t +set_adm_key (app_t app, const unsigned char *value, size_t valuelen) +{ + gpg_error_t err; + unsigned char apdu[8+24]; + unsigned int sw; + + /* Check whether it is a weak key and that it is of proper length. */ + { + gcry_cipher_hd_t cipher; + + err = gcry_cipher_open (&cipher, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_ECB, 0); + if (!err) + { + err = gcry_cipher_setkey (cipher, value, valuelen); + gcry_cipher_close (cipher); + } + if (err) + goto leave; + } + + if (app->app_local->flags.yubikey) + { + /* This is a Yubikey. */ + if (valuelen != 24) + { + err = gpg_error (GPG_ERR_INV_LENGTH); + goto leave; + } + + /* We use a proprietary Yubikey command. */ + apdu[0] = 0; + apdu[1] = 0xff; + apdu[2] = 0xff; + apdu[3] = 0xff; /* touch policy: 0xff=never, 0xfe = always. */ + apdu[4] = 3 + 24; + apdu[5] = PIV_ALGORITHM_3DES_ECB; + apdu[6] = 0x9b; + apdu[7] = 24; + memcpy (apdu+8, value, 24); + err = iso7816_apdu_direct (app->slot, apdu, 8+24, 0, &sw, NULL, NULL); + wipememory (apdu+8, 24); + if (err) + log_error ("piv: setting admin key failed; sw=%04x\n", sw); + } + else + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + + + leave: + return err; +} + + +/* Handle the SETATTR operation. All arguments are already basically + * checked. */ +static gpg_error_t +do_setattr (app_t app, const char *name, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *value, size_t valuelen) +{ + gpg_error_t err; + static struct { + const char *name; + unsigned short tag; + unsigned short flush_tag; /* The tag which needs to be flushed or 0. */ + int special; /* Special mode to use for thus NAME. */ + } table[] = { + /* Authenticate using the PIV Card Application Administration Key + * (0x0B). Note that Yubico calls this key the "management key" + * which we don't do because that term is too similar to "Cert + * Management Key" (0x9D). */ + { "AUTH-ADM-KEY", 0x0000, 0x0000, 1 }, + { "SET-ADM-KEY", 0x0000, 0x0000, 2 } + }; + int idx; + + (void)pincb; + (void)pincb_arg; + + for (idx=0; (idx < DIM (table) + && ascii_strcasecmp (table[idx].name, name)); idx++) + ; + if (!(idx < DIM (table))) + return gpg_error (GPG_ERR_INV_NAME); + + /* Flush the cache before writing it, so that the next get operation + * will reread the data from the card and thus get synced in case of + * errors (e.g. data truncated by the card). */ + if (table[idx].tag) + flush_cached_data (app, table[idx].flush_tag? table[idx].flush_tag + /* */ : table[idx].tag); + + switch (table[idx].special) + { + case 0: + err = iso7816_put_data (app->slot, 0, table[idx].tag, value, valuelen); + if (err) + log_error ("failed to set '%s': %s\n", + table[idx].name, gpg_strerror (err)); + break; + + case 1: + err = auth_adm_key (app, value, valuelen); + break; + + case 2: + err = set_adm_key (app, value, valuelen); + break; + + default: + err = gpg_error (GPG_ERR_BUG); + break; + } + + return err; +} + + /* Send the KEYPAIRINFO back. DOBJ describes the data object carrying * the key. This is used by the LEARN command. */ static gpg_error_t @@ -1086,13 +1322,15 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *pwidstr, char *newpin = NULL; char *oldpin = NULL; - size_t newpinlen; - size_t oldpinlen; - const char *newdesc; - int pwid; + /* size_t newpinlen; */ + /* size_t oldpinlen; */ + /* const char *newdesc; */ + /* int pwid; */ pininfo_t pininfo; (void)ctrl; + (void)pincb; + (void)pincb_arg; /* The minimum and maximum lengths are enforced by PIV. */ memset (&pininfo, 0, sizeof pininfo); @@ -1416,6 +1654,9 @@ app_select_piv (app_t app) goto leave; } + if (app->cardtype && !strcmp (app->cardtype, "yubikey")) + app->app_local->flags.yubikey = 1; + /* FIXME: Parse the optional and conditional DOs in the APT. */ @@ -1427,7 +1668,7 @@ app_select_piv (app_t app) app->fnc.readcert = do_readcert; app->fnc.readkey = NULL; app->fnc.getattr = do_getattr; - /* app->fnc.setattr = do_setattr; */ + app->fnc.setattr = do_setattr; /* app->fnc.writecert = do_writecert; */ /* app->fnc.writekey = do_writekey; */ /* app->fnc.genkey = do_genkey; */ diff --git a/scd/command.c b/scd/command.c index 044831f01..fb0ba98fc 100644 --- a/scd/command.c +++ b/scd/command.c @@ -55,6 +55,9 @@ /* Maximum allowed size of certificate data as used in inquiries. */ #define MAXLEN_CERTDATA 16384 +/* Maximum allowed size for "SETATTR --inquire". */ +#define MAXLEN_SETATTRDATA 16384 + #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) @@ -926,7 +929,7 @@ cmd_getattr (assuan_context_t ctx, char *line) static const char hlp_setattr[] = - "SETATTR \n" + "SETATTR [--inquire] \n" "\n" "This command is used to store data on a smartcard. The allowed\n" "names and values are depend on the currently selected smartcard\n" @@ -935,6 +938,10 @@ static const char hlp_setattr[] = "However, the current implementation assumes that NAME is not\n" "escaped; this works as long as no one uses arbitrary escaping.\n" "\n" + "If the option --inquire is used, VALUE shall not be given; instead\n" + "an inquiry using the keyword \"VALUE\" is used to retrieve it. The\n" + "value is in this case considered to be confidential and not logged.\n" + "\n" "A PIN will be requested for most NAMEs. See the corresponding\n" "setattr function of the actually used application (app-*.c) for\n" "details."; @@ -942,14 +949,18 @@ static gpg_error_t cmd_setattr (assuan_context_t ctx, char *orig_line) { ctrl_t ctrl = assuan_get_pointer (ctx); - int rc; + gpg_error_t err; char *keyword; int keywordlen; size_t nbytes; char *line, *linebuf; + int opt_inquire; - if ((rc = open_card (ctrl))) - return rc; + opt_inquire = has_option (orig_line, "--inquire"); + orig_line = skip_options (orig_line); + + if ((err = open_card (ctrl))) + return err; /* We need to use a copy of LINE, because PIN_CB uses the same context and thus reuses the Assuan provided LINE. */ @@ -964,20 +975,38 @@ cmd_setattr (assuan_context_t ctx, char *orig_line) *line++ = 0; while (spacep (line)) line++; - nbytes = percent_plus_unescape_inplace (line, 0); + if (opt_inquire) + { + unsigned char *value; + + assuan_begin_confidential (ctx); + err = assuan_inquire (ctx, "VALUE", &value, &nbytes, MAXLEN_SETATTRDATA); + assuan_end_confidential (ctx); + if (!err) + { + err = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx, + value, nbytes); + wipememory (value, nbytes); + xfree (value); + } + + } + else + { + nbytes = percent_plus_unescape_inplace (line, 0); + err = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx, + (const unsigned char*)line, nbytes); + } - rc = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx, - (const unsigned char*)line, nbytes); xfree (linebuf); - - return rc; + return err; } static const char hlp_writecert[] = "WRITECERT \n" "\n" - "This command is used to store a certifciate on a smartcard. The\n" + "This command is used to store a certificate on a smartcard. The\n" "allowed certids depend on the currently selected smartcard\n" "application. The actual certifciate is requested using the inquiry\n" "\"CERTDATA\" and needs to be provided in its raw (e.g. DER) form.\n" From da383257404cde9689bc58259ef3f46e9903bf34 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 31 Jan 2019 16:06:47 +0100 Subject: [PATCH 147/235] card: New command 'authenticate'. * tools/card-tool-misc.c (hex_to_buffer): New. * tools/gpg-card-tool.c (get_data_from_file): Change to allow returning a string. (cmd_authenticate): New. (cmds): Add command "authenticate". Signed-off-by: Werner Koch --- tools/card-tool-misc.c | 35 +++++++++++++++++ tools/card-tool.h | 1 + tools/gpg-card-tool.c | 88 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 118 insertions(+), 6 deletions(-) diff --git a/tools/card-tool-misc.c b/tools/card-tool-misc.c index 0f5fcc0a0..06fcb6705 100644 --- a/tools/card-tool-misc.c +++ b/tools/card-tool-misc.c @@ -42,3 +42,38 @@ find_kinfo (card_info_t info, const char *keyref) return kinfo; return NULL; } + + +/* Convert STRING into a newly allocated buffer while translating the + * hex numbers. Blanks and colons are allowed to separate pairs of + * hex digits. Returns NULL on error or a newly malloced buffer and + * its length in LENGTH. */ +void * +hex_to_buffer (const char *string, size_t *r_length) +{ + unsigned char *buffer; + const char *s; + size_t n; + + buffer = xtrymalloc (strlen (string)+1); + if (!buffer) + return NULL; + for (s=string, n=0; *s; s++) + { + if (ascii_isspace (*s) || *s == ':') + continue; + if (hexdigitp (s) && hexdigitp (s+1)) + { + buffer[n++] = xtoi_2 (s); + s++; + } + else + { + xfree (buffer); + gpg_err_set_errno (EINVAL); + return NULL; + } + } + *r_length = n; + return buffer; +} diff --git a/tools/card-tool.h b/tools/card-tool.h index 05d6ea47d..bea618a8c 100644 --- a/tools/card-tool.h +++ b/tools/card-tool.h @@ -189,6 +189,7 @@ gpg_error_t test_get_matching_keys (const char *hexgrip); /*-- card-tool-misc.c --*/ key_info_t find_kinfo (card_info_t info, const char *keyref); +void *hex_to_buffer (const char *string, size_t *r_length); /*-- card-call-scd.c --*/ diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index 07b8bc67b..3145229ad 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -326,8 +326,11 @@ main (int argc, char **argv) /* Read data from file FNAME up to MAX_GET_DATA_FROM_FILE characters. * On error return an error code and stores NULL at R_BUFFER; on - * success returns 0, stpres the number of bytes read at R_BUFLEN and - * the address of a newly allocated buffer at R_BUFFER. */ + * success returns 0 and stores the number of bytes read at R_BUFLEN + * and the address of a newly allocated buffer at R_BUFFER. A + * complementary nul byte is always appended to the data but not + * counted; this allows to pass NULL for R-BUFFER and consider the + * returned data as a string. */ static gpg_error_t get_data_from_file (const char *fname, char **r_buffer, size_t *r_buflen) { @@ -337,7 +340,8 @@ get_data_from_file (const char *fname, char **r_buffer, size_t *r_buflen) int n; *r_buffer = NULL; - *r_buflen = 0; + if (r_buflen) + *r_buflen = 0; fp = es_fopen (fname, "rb"); if (!fp) @@ -356,7 +360,7 @@ get_data_from_file (const char *fname, char **r_buffer, size_t *r_buflen) return err; } - n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE, fp); + n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE - 1, fp); es_fclose (fp); if (n < 0) { @@ -365,8 +369,11 @@ get_data_from_file (const char *fname, char **r_buffer, size_t *r_buflen) xfree (data); return err; } + data[n] = 0; + *r_buffer = data; - *r_buflen = n; + if (r_buflen) + *r_buflen = n; return 0; } @@ -951,6 +958,73 @@ cmd_verify (card_info_t info, char *argstr) } +static gpg_error_t +cmd_authenticate (card_info_t info, char *argstr) +{ + gpg_error_t err; + int opt_setkey; + int opt_raw; + char *string = NULL; + char *key = NULL; + size_t keylen; + + if (!info) + return print_help + ("AUTHENTICATE [--setkey] [--raw] [< FILE]|KEY\n\n" + "Perform a mutual autentication either by reading the key\n" + "from FILE or by taking it from the command line. Without\n" + "the option --raw the key is expected to be hex encoded.\n" + "To install a new administration key --setkey is used; this\n" + "requires a prior authentication with the old key.", + APP_TYPE_PIV, 0); + + if (info->apptype != APP_TYPE_PIV) + { + log_info ("Note: This is a PIV only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + opt_setkey = has_leading_option (argstr, "--setkey"); + opt_raw = has_leading_option (argstr, "--raw"); + argstr = skip_options (argstr); + + if (*argstr == '<') /* Read key from a file. */ + { + for (argstr++; spacep (argstr); argstr++) + ; + err = get_data_from_file (argstr, &string, NULL); + if (err) + goto leave; + } + + if (opt_raw) + { + key = string? string : xstrdup (argstr); + string = NULL; + keylen = strlen (key); + } + else + { + key = hex_to_buffer (string? string: argstr, &keylen); + if (!key) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + err = scd_setattr (opt_setkey? "SET-ADM-KEY":"AUTH-ADM-KEY", key, keylen); + + leave: + if (key) + { + wipememory (key, keylen); + xfree (key); + } + xfree (string); + return err; +} + + /* Helper for cmd_name to qyery a part of name. */ static char * ask_one_name (const char *prompt) @@ -2610,7 +2684,7 @@ enum cmdids cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET, cmdKDFSETUP, - cmdKEYATTR, cmdUIF, + cmdKEYATTR, cmdUIF, cmdAUTHENTICATE, cmdINVCMD }; @@ -2641,6 +2715,7 @@ static struct { "passwd" , cmdPASSWD, 0, N_("menu to change or unblock the PIN")}, { "verify" , cmdVERIFY, 0, N_("verify the PIN and list all data")}, { "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code")}, + { "authenticate",cmdAUTHENTICATE, 0,N_("authenticate to the card")}, { "reset" , cmdRESET, 0, N_("send a reset to the card daemon")}, { "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")}, { "kdf-setup", cmdKDFSETUP, 1, N_("setup KDF for PIN authentication")}, @@ -2871,6 +2946,7 @@ interactive_loop (void) if (!err) redisplay = 1; break; + case cmdAUTHENTICATE: err = cmd_authenticate (info, argstr); break; case cmdNAME: err = cmd_name (info, argstr); break; case cmdURL: err = cmd_url (info, argstr); break; case cmdFETCH: err = cmd_fetch (info); break; From 1c0fa3e6f74692d5e9b5f08cda523f0fcec305eb Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 31 Jan 2019 18:57:16 +0100 Subject: [PATCH 148/235] card: Implement non-interactive mode. * tools/card-tool.h (opt): Add field 'initialized'. * tools/card-call-scd.c (scd_learn): Set it. * tools/gpg-card-tool.c (main): Reworked. (dispatch_command): New. -- This work is not yet finished because most commands need some tweaks for non-interactive work. What you already can do are things like: $ gpg-card-tool list -- 'auth --- tools/card-call-scd.c | 3 +- tools/card-tool.h | 2 + tools/gpg-card-tool.c | 316 ++++++++++++++++++++++++++++++++---------- 3 files changed, 248 insertions(+), 73 deletions(-) diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c index 9a742a728..c51282f14 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -956,6 +956,8 @@ scd_learn (card_info_t info) /* Also try to get some other key attributes. */ if (!err) { + info->initialized = 1; + err = scd_getattr ("KEY-ATTR", info); if (gpg_err_code (err) == GPG_ERR_INV_NAME || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION) @@ -964,7 +966,6 @@ scd_learn (card_info_t info) if (gpg_err_code (err) == GPG_ERR_INV_NAME || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION) err = 0; /* Not implemented or GETATTR not supported. */ - } if (info == &dummyinfo) diff --git a/tools/card-tool.h b/tools/card-tool.h index bea618a8c..9aca8131d 100644 --- a/tools/card-tool.h +++ b/tools/card-tool.h @@ -27,6 +27,7 @@ /* We keep all global options in the structure OPT. */ struct { + int interactive; int verbose; unsigned int debug; int quiet; @@ -137,6 +138,7 @@ typedef struct key_info_s *key_info_t; */ struct card_info_s { + int initialized; /* True if a learn command was successful. */ int error; /* private. */ char *reader; /* Reader information. */ char *cardtype; /* NULL or type of the card. */ diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index 3145229ad..243ee555a 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -46,7 +46,7 @@ #define CONTROL_D ('D' - 'A' + 1) /* Constants to identify the commands and options. */ -enum cmd_and_opt_values +enum opt_values { aNull = 0, @@ -69,18 +69,12 @@ enum cmd_and_opt_values oLCctype, oLCmessages, - aTest, - - oDummy }; /* The list of commands and options. */ static ARGPARSE_OPTS opts[] = { - ARGPARSE_group (300, ("@Commands:\n ")), - ARGPARSE_c (aTest, "test", "test command"), - ARGPARSE_group (301, ("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", ("verbose")), @@ -133,7 +127,7 @@ typedef struct keyinfolabel_s *keyinfolabel_t; /* Local prototypes. */ -static void wrong_args (const char *text) GPGRT_ATTR_NORETURN; +static gpg_error_t dispatch_command (card_info_t info, const char *command); static void interactive_loop (void); #ifdef HAVE_LIBREADLINE static char **command_completion (const char *text, int start, int end); @@ -157,11 +151,14 @@ my_strusage( int level ) case 1: case 40: - p = ("Usage: gpg-card-tool [command] [options] [args] (-h for help)"); + p = ("Usage: gpg-card-tool" + " [options] [{[--] command [args]}] (-h for help)"); break; case 41: - p = ("Syntax: gpg-card-tool [command] [options] [args]\n" - "Tool to configure cards and tokens\n"); + p = ("Syntax: gpg-card-tool" + " [options] [command [args] {-- command [args]}]\n\n" + "Tool to manage cards and tokens. With a command an interactive\n" + "mode is used. Use command \"help\" to list all commands."); break; default: p = NULL; break; @@ -170,14 +167,6 @@ my_strusage( int level ) } -static void -wrong_args (const char *text) -{ - es_fprintf (es_stderr, _("usage: %s [options] %s\n"), strusage (11), text); - exit (2); -} - - static void set_opt_session_env (const char *name, const char *value) { @@ -192,13 +181,10 @@ set_opt_session_env (const char *name, const char *value) /* Command line parsing. */ -static enum cmd_and_opt_values +static void parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) { - enum cmd_and_opt_values cmd = 0; - int no_more_options = 0; - - while (!no_more_options && optfile_parse (NULL, NULL, NULL, pargs, popts)) + while (optfile_parse (NULL, NULL, NULL, pargs, popts)) { switch (pargs->r_opt) { @@ -231,15 +217,9 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) case oLCctype: opt.lc_ctype = pargs->r.ret_str; break; case oLCmessages: opt.lc_messages = pargs->r.ret_str; break; - case aTest: - cmd = pargs->r_opt; - break; - default: pargs->err = 2; break; } } - - return cmd; } @@ -250,7 +230,9 @@ main (int argc, char **argv) { gpg_error_t err; ARGPARSE_ARGS pargs; - enum cmd_and_opt_values cmd; + char **command_list = NULL; + int cmdidx; + char *command; gnupg_reopen_std ("gpg-card-tool"); set_strusage (my_strusage); @@ -276,44 +258,80 @@ main (int argc, char **argv) pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; - cmd = parse_arguments (&pargs, opts); + parse_arguments (&pargs, opts); if (log_get_errorcount (0)) exit (2); - /* 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]); - } - /* Set defaults for non given options. */ if (!opt.gpg_program) opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); if (!opt.gpgsm_program) opt.gpgsm_program = gnupg_module_name (GNUPG_MODULE_NAME_GPGSM); - /* Run the selected command. */ - switch (cmd) + /* Now build the list of commands. We guess the size of the array + * by assuming each item is a complete command. Obviously this will + * be rarely the case, but it is less code to allocate a possible + * too large array. */ + command_list = xcalloc (argc+1, sizeof *command_list); + cmdidx = 0; + command = NULL; + while (argc) { - case aTest: - if (!argc) - wrong_args ("--test KEYGRIP"); - err = test_get_matching_keys (*argv); - break; + for ( ; argc && strcmp (*argv, "--"); argc--, argv++) + { + if (!command) + command = xstrdup (*argv); + else + { + char *tmp = xstrconcat (command, " ", *argv, NULL); + xfree (command); + command = tmp; + } + } + if (argc) + { /* Skip the double dash. */ + argc--; + argv++; + } + if (command) + { + command_list[cmdidx++] = command; + command = NULL; + } + } + opt.interactive = !cmdidx; - default: + if (opt.interactive) + { interactive_loop (); err = 0; - break; + } + else + { + struct card_info_s info_buffer; + card_info_t info = &info_buffer; + + err = 0; + for (cmdidx=0; (command = command_list[cmdidx]); cmdidx++) + { + err = dispatch_command (info, command); + if (err) + break; + } + if (gpg_err_code (err) == GPG_ERR_EOF) + err = 0; /* This was a "quit". */ + else if (command && !opt.quiet) + log_info ("stopped at command '%s'\n", command); } flush_keyblock_cache (); - + if (command_list) + { + for (cmdidx=0; command_list[cmdidx]; cmdidx++) + xfree (command_list[cmdidx]); + xfree (command_list); + } if (err) gnupg_status_printf (STATUS_FAILURE, "- %u", err); else if (log_get_errorcount (0)) @@ -421,22 +439,24 @@ put_data_to_file (const char *fname, const void *buffer, size_t length) static gpg_error_t print_help (const char *text, ...) { + estream_t fp; va_list arg_ptr; int value; int any = 0; - tty_fprintf (NULL, "%s\n", text); + fp = opt.interactive? NULL : es_stdout; + tty_fprintf (fp, "%s\n", text); va_start (arg_ptr, text); while ((value = va_arg (arg_ptr, int))) { if (!any) - tty_fprintf (NULL, "[Supported by: "); - tty_fprintf (NULL, "%s%s", any?", ":"", app_type_string (value)); + tty_fprintf (fp, "[Supported by: "); + tty_fprintf (fp, "%s%s", any?", ":"", app_type_string (value)); any = 1; } if (any) - tty_fprintf (NULL, "]\n"); + tty_fprintf (fp, "]\n"); va_end (arg_ptr); return 0; @@ -588,18 +608,6 @@ mem_is_zero (const char *mem, unsigned int memlen) } -/* Return true if the buffer MEM or length MEMLEN consists only of 0xFF. */ -static int -mem_is_ff (const char *mem, unsigned int memlen) -{ - int i; - - for (i=0; i < memlen && mem[i] == '\xff'; i++) - ; - return (i == memlen); -} - - /* Helper to list a single keyref. */ static void @@ -909,7 +917,7 @@ list_piv (card_info_t info, estream_t fp) static void list_card (card_info_t info) { - estream_t fp = NULL; + estream_t fp = opt.interactive? NULL : es_stdout; tty_fprintf (fp, "Reader ...........: %s\n", info->reader? info->reader : "[none]"); @@ -2716,6 +2724,7 @@ static struct { "verify" , cmdVERIFY, 0, N_("verify the PIN and list all data")}, { "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code")}, { "authenticate",cmdAUTHENTICATE, 0,N_("authenticate to the card")}, + { "auth" , cmdAUTHENTICATE, 0, NULL }, { "reset" , cmdRESET, 0, N_("send a reset to the card daemon")}, { "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")}, { "kdf-setup", cmdKDFSETUP, 1, N_("setup KDF for PIN authentication")}, @@ -2729,7 +2738,169 @@ static struct }; -/* The main loop. */ +/* The command line command dispatcher. */ +static gpg_error_t +dispatch_command (card_info_t info, const char *orig_command) +{ + gpg_error_t err = 0; + enum cmdids cmd; /* The command. */ + char *command; /* A malloced copy of ORIG_COMMAND. */ + char *argstr; /* The argument as a string. */ + int i; + int ignore_error; + + if ((ignore_error = *orig_command == '-')) + orig_command++; + command = xstrdup (orig_command); + argstr = NULL; + if ((argstr = strchr (command, ' '))) + { + *argstr++ = 0; + trim_spaces (command); + trim_spaces (argstr); + } + + for (i=0; cmds[i].name; i++ ) + if (!ascii_strcasecmp (command, cmds[i].name )) + break; + cmd = cmds[i].id; /* (If not found this will be cmdINVCMD). */ + + /* Make sure we have valid strings for the args. They are allowed + * to be modified and must thus point to a buffer. */ + if (!argstr) + argstr = command + strlen (command); + + /* For most commands we need to make sure that we have a card. */ + if (!info) + ; /* Help mode */ + else if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP + || cmd == cmdINVCMD) + && !info->initialized) + { + err = scd_learn (info); + if (err) + { + log_error ("Error reading card: %s\n", gpg_strerror (err)); + goto leave; + } + } + + switch (cmd) + { + case cmdNOP: + if (!info) + print_help ("NOP\n\n" + "Dummy command.", 0); + break; + + case cmdQUIT: + if (!info) + print_help ("QUIT\n\n" + "Stop processing.", 0); + else + { + err = gpg_error (GPG_ERR_EOF); + goto leave; + } + break; + + case cmdHELP: + if (!info) + print_help ("HELP [command]\n\n" + "Show all commands. With an argument show help\n" + "for that command.", 0); + else if (*argstr) + dispatch_command (NULL, argstr); + else + { + es_printf + ("List of commands (\"help \" for details):\n"); + for (i=0; cmds[i].name; i++ ) + if(cmds[i].desc) + es_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); + es_printf ("Prefix a command with a dash to ignore its error.\n"); + } + break; + + case cmdLIST: + if (!info) + print_help ("LIST\n\n" + "Show content of the card.", 0); + else + { + err = scd_learn (info); + if (err) + log_error ("Error reading card: %s\n", gpg_strerror (err)); + else + list_card (info); + } + break; + + case cmdRESET: + if (!info) + print_help ("RESET\n\n" + "Send a RESET to the card daemon.", 0); + else + { + flush_keyblock_cache (); + err = scd_apdu (NULL, NULL); + } + break; + + case cmdADMIN: + /* This is a NOP in non-interactive mode. */ + break; + + case cmdVERIFY: err = cmd_verify (info, argstr); break; + case cmdAUTHENTICATE: err = cmd_authenticate (info, argstr); break; + case cmdNAME: err = cmd_name (info, argstr); break; + case cmdURL: err = cmd_url (info, argstr); break; + case cmdFETCH: err = cmd_fetch (info); break; + case cmdLOGIN: err = cmd_login (info, argstr); break; + case cmdLANG: err = cmd_lang (info, argstr); break; + case cmdSALUT: err = cmd_salut (info, argstr); break; + case cmdCAFPR: err = cmd_cafpr (info, argstr); break; + case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; + case cmdWRITECERT: err = cmd_writecert (info, argstr); break; + case cmdREADCERT: err = cmd_readcert (info, argstr); break; + case cmdFORCESIG: err = cmd_forcesig (info); break; + case cmdGENERATE: err = cmd_generate (info); break; + case cmdPASSWD: err = cmd_passwd (info, 1); break; + case cmdUNBLOCK: err = cmd_unblock (info); break; + case cmdFACTORYRESET: err = cmd_factoryreset (info); break; + case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; + case cmdKEYATTR: err = cmd_keyattr (info, argstr); break; + case cmdUIF: err = cmd_uif (info, argstr); break; + + case cmdINVCMD: + default: + log_error (_("Invalid command (try \"help\")\n")); + break; + } /* End command switch. */ + + + leave: + /* Return GPG_ERR_EOF only if its origin was "quit". */ + es_fflush (es_stdout); + if (gpg_err_code (err) == GPG_ERR_EOF && cmd != cmdQUIT) + err = gpg_error (GPG_ERR_GENERAL); + if (err && gpg_err_code (err) != GPG_ERR_EOF) + { + if (ignore_error) + { + log_info ("Command '%s' failed: %s\n", command, gpg_strerror (err)); + err = 0; + } + else + log_error ("Command '%s' failed: %s\n", command, gpg_strerror (err)); + } + xfree (command); + + return err; +} + + +/* The interactive main loop. */ static void interactive_loop (void) { @@ -2825,11 +2996,12 @@ interactive_loop (void) } /* Make sure we have valid strings for the args. They are - * allowed to be modifed and must thus point to a buffer. */ + * allowed to be modified and must thus point to a buffer. */ if (!argstr) argstr = answer + strlen (answer); - if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP)) + if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP + || cmd == cmdINVCMD)) { /* If redisplay is set we know that there was an error reading * the card. In this case we force a LIST command to retry. */ From 3231ecdafd71ac47b734469b07170756979ede72 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 5 Feb 2019 14:48:49 +0100 Subject: [PATCH 149/235] scd: Allow standard keyref scheme for app-openpgp. * scd/app-openpgp.c (do_change_pin): Allow prefixing the CHVNO with "OPENPGP." * tools/card-call-scd.c (scd_change_pin): Change API to use strings. * tools/gpg-card-tool.c (cmd_passwd): Adjust for change. (cmd_unblock): Ditto. -- The generic keyref allows for better error detection in case a keyref is send to a wrong card. Signed-off-by: Werner Koch --- doc/wks.texi | 2 +- scd/app-openpgp.c | 15 ++++++++++++++- scd/iso7816.c | 1 + tools/card-call-scd.c | 11 +++-------- tools/card-tool.h | 2 +- tools/gpg-card-tool.c | 12 ++++++------ 6 files changed, 26 insertions(+), 17 deletions(-) diff --git a/doc/wks.texi b/doc/wks.texi index a0b2a34b9..f132b3186 100644 --- a/doc/wks.texi +++ b/doc/wks.texi @@ -124,7 +124,7 @@ Requires installation of that command. @item --with-colons @opindex with-colons This option has currently only an effect on the @option{--supported} -command. If it is used all arguimenst on the command line are taken +command. If it is used all arguments on the command line are taken as domain names and tested for WKD support. The output format is one line per domain with colon delimited fields. The currently specified fields are (future versions may specify additional fields): diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index dbc51be7f..72ed80a3d 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -2583,6 +2583,8 @@ do_writecert (app_t app, ctrl_t ctrl, - 2 1 Verify CHV2 and set a new CHV1 and CHV2. - 2 2 Verify Reset Code and set a new PW1. - 3 any Verify CHV3/PW3 and set a new CHV3/PW3. + + The CHVNO can be prefixed with "OPENPGP.". */ static gpg_error_t do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, @@ -2591,7 +2593,7 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, void *pincb_arg) { int rc = 0; - int chvno = atoi (chvnostr); + int chvno; char *resetcode = NULL; char *oldpinvalue = NULL; char *pinvalue = NULL; @@ -2605,6 +2607,17 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, (void)ctrl; + if (digitp (chvnostr)) + chvno = atoi (chvnostr); + else if (!ascii_strcasecmp (chvnostr, "OPENPGP.1")) + chvno = 1; + else if (!ascii_strcasecmp (chvnostr, "OPENPGP.2")) + chvno = 2; + else if (!ascii_strcasecmp (chvnostr, "OPENPGP.3")) + chvno = 3; + else + return gpg_error (GPG_ERR_INV_ID); + memset (&pininfo, 0, sizeof pininfo); pininfo.fixedlen = -1; pininfo.minlen = minlen; diff --git a/scd/iso7816.c b/scd/iso7816.c index c8a2138cb..69009c43e 100644 --- a/scd/iso7816.c +++ b/scd/iso7816.c @@ -346,6 +346,7 @@ iso7816_change_reference_data (int slot, int chvno, sw = apdu_send_simple (slot, 0, 0x00, CMD_CHANGE_REFERENCE_DATA, oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf); + wipememory (buf, oldchvlen+newchvlen); xfree (buf); return map_sw (sw); diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c index c51282f14..7add56daf 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -1368,28 +1368,23 @@ scd_cardlist (strlist_t *result) * 101: Set a new PIN and reset the retry counter * 102: For v1 cars: Same as 101. * For v2 cards: Set a new Reset Code. - * SERIALNO is not used. */ gpg_error_t -scd_change_pin (int chvno) +scd_change_pin (const char *pinref, int reset_mode) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; - const char *reset = ""; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); - if (chvno >= 100) - reset = "--reset"; - chvno %= 100; - err = start_agent (0); if (err) return err; dfltparm.ctx = agent_ctx; - snprintf (line, sizeof line, "SCD PASSWD %s %d", reset, chvno); + snprintf (line, sizeof line, "SCD PASSWD%s %s", + reset_mode? " --reset":"", pinref); err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, diff --git a/tools/card-tool.h b/tools/card-tool.h index 9aca8131d..2707b3e8f 100644 --- a/tools/card-tool.h +++ b/tools/card-tool.h @@ -212,7 +212,7 @@ gpg_error_t scd_serialno (char **r_serialno, const char *demand); gpg_error_t scd_readcert (const char *certidstr, void **r_buf, size_t *r_buflen); gpg_error_t scd_cardlist (strlist_t *result); -gpg_error_t scd_change_pin (int chvno); +gpg_error_t scd_change_pin (const char *pinref, int reset_mode); gpg_error_t scd_checkpin (const char *serialno); unsigned long agent_get_s2k_count (void); diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index 243ee555a..1c4413b15 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -1865,7 +1865,7 @@ cmd_passwd (card_info_t info, int allow_admin) if (!allow_admin) { - err = scd_change_pin (1); + err = scd_change_pin ("OPENPGP.1", 0); if (err) goto leave; log_info ("PIN changed.\n"); @@ -1896,7 +1896,7 @@ cmd_passwd (card_info_t info, int allow_admin) if (*answer == '1') { /* Change PIN (same as the direct thing in non-admin mode). */ - err = scd_change_pin (1); + err = scd_change_pin ("OPENPGP.1", 0); if (err) log_error ("Error changing the PIN: %s\n", gpg_strerror (err)); else @@ -1905,7 +1905,7 @@ cmd_passwd (card_info_t info, int allow_admin) else if (*answer == '2') { /* Unblock PIN by setting a new PIN. */ - err = scd_change_pin (101); + err = scd_change_pin ("OPENPGP.1", 1); if (err) log_error ("Error unblocking the PIN: %s\n", gpg_strerror(err)); else @@ -1914,7 +1914,7 @@ cmd_passwd (card_info_t info, int allow_admin) else if (*answer == '3') { /* Change Admin PIN. */ - err = scd_change_pin (3); + err = scd_change_pin ("OPENPGP.3", 0); if (err) log_error ("Error changing the PIN: %s\n", gpg_strerror (err)); else @@ -1923,7 +1923,7 @@ cmd_passwd (card_info_t info, int allow_admin) else if (*answer == '4') { /* Set a new Reset Code. */ - err = scd_change_pin (102); + err = scd_change_pin ("OPENPGP.2", 1); if (err) log_error ("Error setting the Reset Code: %s\n", gpg_strerror (err)); @@ -1969,7 +1969,7 @@ cmd_unblock (card_info_t info) log_error (_("Reset Code not or not anymore available\n")); else if (info->apptype == APP_TYPE_OPENPGP) { - err = scd_change_pin (2); + err = scd_change_pin ("OPENPGP.2", 0); if (!err) log_info ("PIN changed.\n"); } From e9e876cb5572670322aa1d3462d64c75c03974d9 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 6 Feb 2019 09:45:54 +0100 Subject: [PATCH 150/235] scd: Implement PIN changing and unblocking for PIV cards. * scd/app-piv.c: Some refactoring (do_change_chv): Implement. Signed-off-by: Werner Koch --- scd/app-piv.c | 269 +++++++++++++++++++++++++++++++----------- tools/gpg-card-tool.c | 108 ++++++++++++----- 2 files changed, 278 insertions(+), 99 deletions(-) diff --git a/scd/app-piv.c b/scd/app-piv.c index d34ff7d10..42f16de40 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -20,6 +20,24 @@ /* Some notes: * - Specs for PIV are at http://dx.doi.org/10.6028/NIST.SP.800-73-4 * + * - Access control matrix: + * | Action | 9B | PIN | PUK | | + * |--------------+-----+-----+-----+------------------------------| + * | Generate key | yes | | | | + * | Change 9B | yes | | | | + * | Change retry | yes | yes | | Yubikey only | + * | Import key | yes | | | | + * | Import cert | yes | | | | + * | Change CHUID | yes | | | | + * | Reset card | | | | PIN and PUK in blocked state | + * | Verify PIN | | yes | | | + * | Sign data | | yes | | | + * | Decrypt data | | yes | | | + * | Change PIN | | yes | | | + * | Change PUK | | | yes | | + * | Unblock PIN | | | yes | New PIN required | + * |---------------------------------------------------------------| + * (9B indicates the 24 byte PIV Card Application Administration Key) */ #include @@ -389,10 +407,10 @@ dump_all_do (int slot) /* Parse the key reference KEYREFSTR which is expected to hold a key - * reference for a PIN object. Return the one octet keyref or -1 for + * reference for a CHV object. Return the one octet keyref or -1 for * an invalid reference. */ static int -parse_pin_keyref (const char *keyrefstr) +parse_chv_keyref (const char *keyrefstr) { if (!keyrefstr) return -1; @@ -457,7 +475,7 @@ get_chv_status (app_t app, const char *keyrefstr) int result; int keyref; - keyref = parse_pin_keyref (keyrefstr); + keyref = parse_chv_keyref (keyrefstr); if (!keyrefstr) return -1; @@ -467,7 +485,7 @@ get_chv_status (app_t app, const char *keyrefstr) apdu[3] = keyref; if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL)) result = -5; /* No need to verification. */ - else if (sw == 0x6a88) + else if (sw == 0x6a88 || sw == 0x6a80) result = -2; /* No such PIN. */ else if (sw == 0x6983) result = -3; /* PIN is blocked. */ @@ -540,7 +558,7 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name) } else if (table[idx].special == -4) /* CHV-STATUS */ { - int tmp[3]; + int tmp[4]; tmp[0] = get_chv_status (app, "PIV.00"); tmp[1] = get_chv_status (app, "PIV.80"); @@ -1177,40 +1195,29 @@ make_prompt (app_t app, int remaining, const char *firstline) } -/* Verify the Application PIN KEYREF. */ +/* Helper for verify_chv to ask for the PIN and to prepare/pad it. On + * success the result is stored at (R_PIN,R_PINLEN). */ static gpg_error_t -verify_pin (app_t app, int keyref, - gpg_error_t (*pincb)(void*,const char *,char **), void *pincb_arg) +ask_and_prepare_chv (app_t app, int keyref, int ask_new, int remaining, + gpg_error_t (*pincb)(void*,const char *,char **), + void *pincb_arg, char **r_pin, unsigned int *r_pinlen) { gpg_error_t err; - unsigned char apdu[4]; - unsigned int sw; - int remaining; const char *label; char *prompt; char *pinvalue = NULL; unsigned int pinlen; - char pinbuffer[8]; + char *pinbuffer = NULL; int minlen, maxlen, padding, onlydigits; - /* First check whether a verify is at all needed. This is done with - * P1 being 0 and no Lc and command data send. */ - apdu[0] = 0x00; - apdu[1] = ISO7816_VERIFY; - apdu[2] = 0x00; - apdu[3] = keyref; - if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL)) - { - /* No need to verification. */ - return 0; /* All fine. */ - } - if ((sw & 0xfff0) == 0x63C0) - remaining = (sw & 0x000f); /* PIN has REMAINING tries left. */ - else + *r_pin = NULL; + *r_pinlen = 0; + + if (ask_new) remaining = -1; if (remaining != -1) - log_debug ("piv: PIN %2X has %d attempts left\n", keyref, remaining); + log_debug ("piv: CHV %02X has %d attempts left\n", keyref, remaining); switch (keyref) { @@ -1219,21 +1226,24 @@ verify_pin (app_t app, int keyref, maxlen = 8; padding = 1; onlydigits = 1; - label = _("||Please enter the Global-PIN of your PIV card"); + label = (ask_new? _("|N|Please enter the new Global-PIN") + /**/ : _("||Please enter the Global-PIN of your PIV card")); break; case 0x80: minlen = 6; maxlen = 8; padding = 1; onlydigits = 1; - label = _("||Please enter the PIN of your PIV card"); + label = (ask_new? _("|N|Please enter the new PIN") + /**/ : _("||Please enter the PIN of your PIV card")); break; case 0x81: minlen = 8; maxlen = 8; padding = 0; onlydigits = 0; - label = _("||Please enter the Unblocking Key of your PIV card"); + label = (ask_new? _("|N|Please enter the new Unblocking Key") + /**/ :_("||Please enter the Unblocking Key of your PIV card")); break; case 0x96: @@ -1245,8 +1255,6 @@ verify_pin (app_t app, int keyref, default: return gpg_error (GPG_ERR_INV_ID); } - log_assert (sizeof pinbuffer >= maxlen); - /* Ask for the PIN. */ prompt = make_prompt (app, remaining, label); @@ -1282,21 +1290,72 @@ verify_pin (app_t app, int keyref, xfree (pinvalue); return gpg_error (GPG_ERR_BAD_PIN); } + + pinbuffer = xtrymalloc_secure (maxlen); + if (!pinbuffer) + { + err = gpg_error_from_syserror (); + wipememory (pinvalue, pinlen); + xfree (pinvalue); + return err; + } + memcpy (pinbuffer, pinvalue, pinlen); + wipememory (pinvalue, pinlen); + xfree (pinvalue); if (padding) { memset (pinbuffer + pinlen, 0xff, maxlen - pinlen); - wipememory (pinvalue, pinlen); pinlen = maxlen; } - else - wipememory (pinvalue, pinlen); - xfree (pinvalue); - err = iso7816_verify (app->slot, keyref, pinbuffer, pinlen); - wipememory (pinbuffer, pinlen); + *r_pin = pinbuffer; + *r_pinlen = pinlen; + + return 0; +} + + +/* Verify the card holder verification identified by KEYREF. This is + * either the Appication PIN or the Global PIN. */ +static gpg_error_t +verify_chv (app_t app, int keyref, + gpg_error_t (*pincb)(void*,const char *,char **), void *pincb_arg) +{ + gpg_error_t err; + unsigned char apdu[4]; + unsigned int sw; + int remaining; + char *pin = NULL; + unsigned int pinlen; + + /* First check whether a verify is at all needed. This is done with + * P1 being 0 and no Lc and command data send. */ + apdu[0] = 0x00; + apdu[1] = ISO7816_VERIFY; + apdu[2] = 0x00; + apdu[3] = keyref; + if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL)) + { + /* No need to verification. */ + return 0; /* All fine. */ + } + if ((sw & 0xfff0) == 0x63C0) + remaining = (sw & 0x000f); /* PIN has REMAINING tries left. */ + else + remaining = -1; + + err = ask_and_prepare_chv (app, keyref, 0, remaining, pincb, pincb_arg, + &pin, &pinlen); if (err) - log_error ("PIN %02X verification failed: %s\n", keyref,gpg_strerror (err)); + return err; + + err = iso7816_verify (app->slot, keyref, pin, pinlen); + wipememory (pin, pinlen); + xfree (pin); + if (err) + log_error ("CHV %02X verification failed: %s\n", + keyref, gpg_strerror (err)); return err; } @@ -1309,40 +1368,41 @@ verify_pin (app_t app, int keyref, * PIV.81 - The PIN Unblocking key * The supported flags are: * APP_CHANGE_FLAG_CLEAR Clear the PIN verification state. + * APP_CHANGE_FLAG_RESET Reset a PIN using the PUK. Only + * allowed with PIV.80. */ static gpg_error_t -do_change_pin (app_t app, ctrl_t ctrl, const char *pwidstr, +do_change_chv (app_t app, ctrl_t ctrl, const char *pwidstr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; - int keyref; + int keyref, targetkeyref; unsigned char apdu[4]; - - char *newpin = NULL; + unsigned int sw; + int remaining; char *oldpin = NULL; - /* size_t newpinlen; */ - /* size_t oldpinlen; */ - /* const char *newdesc; */ - /* int pwid; */ - pininfo_t pininfo; + unsigned int oldpinlen; + char *newpin = NULL; + unsigned int newpinlen; (void)ctrl; - (void)pincb; - (void)pincb_arg; - /* The minimum and maximum lengths are enforced by PIV. */ - memset (&pininfo, 0, sizeof pininfo); - pininfo.minlen = 6; - pininfo.maxlen = 8; + /* Check for unknown flags. */ + if ((flags & ~(APP_CHANGE_FLAG_CLEAR|APP_CHANGE_FLAG_RESET))) + { + err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + goto leave; + } - keyref = parse_pin_keyref (pwidstr); + /* Parse the keyref. */ + targetkeyref = keyref = parse_chv_keyref (pwidstr); if (keyref == -1) - return gpg_error (GPG_ERR_INV_ID); - - if ((flags & ~APP_CHANGE_FLAG_CLEAR)) - return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + { + err = gpg_error (GPG_ERR_INV_ID); + goto leave; + } /* First see whether the special --clear mode has been requested. */ if ((flags & APP_CHANGE_FLAG_CLEAR)) @@ -1355,7 +1415,82 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *pwidstr, goto leave; } - err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + /* Prepare reset mode. */ + if ((flags & APP_CHANGE_FLAG_RESET)) + { + if (keyref == 0x81) + { + err = gpg_error (GPG_ERR_INV_ID); /* Can't reset the PUK. */ + goto leave; + } + /* Set the keyref to the PUK and keep the TARGETKEYREF. */ + keyref = 0x81; + } + + /* Get the remaining tries count. This is done by using the check + * for verified state feature. */ + apdu[0] = 0x00; + apdu[1] = ISO7816_VERIFY; + apdu[2] = 0x00; + apdu[3] = keyref; + if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL)) + remaining = -1; /* Already verified, thus full number of tries. */ + else if ((sw & 0xfff0) == 0x63C0) + remaining = (sw & 0x000f); /* PIN has REMAINING tries left. */ + else + remaining = -1; + + /* Ask for the old pin or puk. */ + err = ask_and_prepare_chv (app, keyref, 0, remaining, pincb, pincb_arg, + &oldpin, &oldpinlen); + if (err) + return err; + + /* Verify the old pin so that we don't prompt for the new pin if the + * old is wrong. This is not possible for the PUK, though. */ + if (keyref != 0x81) + { + err = iso7816_verify (app->slot, keyref, oldpin, oldpinlen); + if (err) + { + log_error ("CHV %02X verification failed: %s\n", + keyref, gpg_strerror (err)); + goto leave; + } + } + + /* Ask for the new pin. */ + err = ask_and_prepare_chv (app, targetkeyref, 1, -1, pincb, pincb_arg, + &newpin, &newpinlen); + if (err) + return err; + + if ((flags & APP_CHANGE_FLAG_RESET)) + { + char *buf = xtrymalloc_secure (oldpinlen + newpinlen); + if (!buf) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (buf, oldpin, oldpinlen); + memcpy (buf+oldpinlen, newpin, newpinlen); + err = iso7816_reset_retry_counter_with_rc (app->slot, targetkeyref, + buf, oldpinlen+newpinlen); + xfree (buf); + if (err) + log_error ("resetting CHV %02X using CHV %02X failed: %s\n", + targetkeyref, keyref, gpg_strerror (err)); + } + else + { + err = iso7816_change_reference_data (app->slot, keyref, + oldpin, oldpinlen, + newpin, newpinlen); + if (err) + log_error ("CHV %02X changing PIN failed: %s\n", + keyref, gpg_strerror (err)); + } leave: xfree (oldpin); @@ -1365,19 +1500,19 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *pwidstr, /* Perform a simple verify operation for the PIN specified by PWIDSTR. - * For valid values see do_change_pin. */ + * For valid values see do_change_chv. */ static gpg_error_t -do_check_pin (app_t app, const char *pwidstr, +do_check_chv (app_t app, const char *pwidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { int keyref; - keyref = parse_pin_keyref (pwidstr); + keyref = parse_chv_keyref (pwidstr); if (keyref == -1) return gpg_error (GPG_ERR_INV_ID); - return verify_pin (app, keyref, pincb, pincb_arg); + return verify_chv (app, keyref, pincb, pincb_arg); } @@ -1492,7 +1627,7 @@ do_auth (app_t app, const char *keyidstr, } /* Now verify the Application PIN. */ - err = verify_pin (app, 0x80, pincb, pincb_arg); + err = verify_chv (app, 0x80, pincb, pincb_arg); if (err) return err; @@ -1675,8 +1810,8 @@ app_select_piv (app_t app) /* app->fnc.sign = do_sign; */ app->fnc.auth = do_auth; /* app->fnc.decipher = do_decipher; */ - app->fnc.change_pin = do_change_pin; - app->fnc.check_pin = do_check_pin; + app->fnc.change_pin = do_change_chv; + app->fnc.check_pin = do_check_chv; leave: diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index 1c4413b15..fd7aa9a7e 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -899,7 +899,7 @@ list_piv (card_info_t info, estream_t fp) switch (info->chvinfo[i]) { case -1: s = "[error]"; break; - case -2: s = "-"; break; /* No such PIN */ + case -2: s = "-"; break; /* No such PIN or info not available. */ case -3: s = "[blocked]"; break; case -5: s = "[verified]"; break; default: s = "[?]"; break; @@ -950,15 +950,21 @@ static gpg_error_t cmd_verify (card_info_t info, char *argstr) { gpg_error_t err; + const char *pinref; if (!info) return print_help ("verify [chvid]", 0); - if (info->apptype == APP_TYPE_OPENPGP) - err = scd_checkpin (info->serialno); + if (*argstr) + pinref = argstr; + else if (info->apptype == APP_TYPE_OPENPGP) + pinref = info->serialno; + else if (info->apptype == APP_TYPE_PIV) + pinref = "PIV.80"; else - err = scd_checkpin (argstr); + return gpg_error (GPG_ERR_MISSING_VALUE); + err = scd_checkpin (pinref); if (err) log_error ("verify failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); @@ -1845,30 +1851,48 @@ cmd_generate (card_info_t info) /* Sub-menu to change a PIN. The presented options may depend on the * the ALLOW_ADMIN flag. */ static gpg_error_t -cmd_passwd (card_info_t info, int allow_admin) +cmd_passwd (card_info_t info, int allow_admin, char *argstr) { gpg_error_t err; char *answer = NULL; + const char *pinref; if (!info) return print_help - ("PASSWD\n\n" + ("PASSWD [PINREF]\n\n" "Menu to change or unblock the PINs. Note that the\n" "presented menu options depend on the type of card\n" - "and whether the admin mode is enabled.", + "and whether the admin mode is enabled. For OpenPGP\n" + "and PIV cards defaults for PINREF are available.", 0); - /* Convenience message because we did this in gpg --card-edit too. */ - if (info->apptype == APP_TYPE_OPENPGP) - log_info (_("OpenPGP card no. %s detected\n"), + if (opt.interactive || opt.verbose) + log_info (_("%s card no. %s detected\n"), + app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); - if (!allow_admin) + if (!allow_admin || info->apptype != APP_TYPE_OPENPGP) { - err = scd_change_pin ("OPENPGP.1", 0); + if (*argstr) + pinref = argstr; + else if (info->apptype == APP_TYPE_OPENPGP) + pinref = "OPENPGP.1"; + else if (info->apptype == APP_TYPE_PIV) + pinref = "PIV.80"; + else + { + err = gpg_error (GPG_ERR_MISSING_VALUE); + goto leave; + } + err = scd_change_pin (pinref, 0); if (err) goto leave; - log_info ("PIN changed.\n"); + + if (info->apptype == APP_TYPE_PIV + && !ascii_strcasecmp (pinref, "PIV.81")) + log_info ("PUK changed.\n"); + else + log_info ("PIN changed.\n"); } else if (info->apptype == APP_TYPE_OPENPGP) { @@ -1959,23 +1983,43 @@ cmd_unblock (card_info_t info) "command can be used to set a new PIN.", 0); - if (info->apptype == APP_TYPE_OPENPGP) - log_info (_("OpenPGP card no. %s detected\n"), + if (opt.interactive || opt.verbose) + log_info (_("%s card no. %s detected\n"), + app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); - if (info->apptype == APP_TYPE_OPENPGP && !info->is_v2) - log_error (_("This command is only available for version 2 cards\n")); - else if (info->apptype == APP_TYPE_OPENPGP && !info->chvinfo[1]) - log_error (_("Reset Code not or not anymore available\n")); - else if (info->apptype == APP_TYPE_OPENPGP) + if (info->apptype == APP_TYPE_OPENPGP) { - err = scd_change_pin ("OPENPGP.2", 0); + if (!info->is_v2) + { + log_error (_("This command is only available for version 2 cards\n")); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + } + else if (!info->chvinfo[1]) + { + log_error (_("Reset Code not or not anymore available\n")); + err = gpg_error (GPG_ERR_PIN_BLOCKED); + } + else + { + err = scd_change_pin ("OPENPGP.2", 0); + if (!err) + log_info ("PIN changed.\n"); + } + } + else if (info->apptype == APP_TYPE_PIV) + { + /* Unblock the Application PIN. */ + err = scd_change_pin ("PIV.80", 1); if (!err) - log_info ("PIN changed.\n"); + log_info ("PIN unblocked and changed.\n"); } else - log_info ("Unblocking not yet supported for '%s'\n", - app_type_string (info->apptype)); + { + log_info ("Unblocking not yet supported for '%s'\n", + app_type_string (info->apptype)); + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + } return err; } @@ -2079,15 +2123,15 @@ cmd_factoryreset (card_info_t info) goto leave; } + if (opt.interactive || opt.verbose) + log_info (_("%s card no. %s detected\n"), + app_type_string (info->apptype), + info->dispserialno? info->dispserialno : info->serialno); + if (!termstate || is_yubikey) { - if (is_yubikey) - log_info (_("Yubikey no. %s with PIV application detected\n"), - info->dispserialno? info->dispserialno : info->serialno); - else + if (!is_yubikey) { - log_info (_("OpenPGP card no. %s detected\n"), - info->dispserialno? info->dispserialno : info->serialno); if (!(info->status_indicator == 3 || info->status_indicator == 5)) { /* Note: We won't see status-indicator 3 here because it @@ -2865,7 +2909,7 @@ dispatch_command (card_info_t info, const char *orig_command) case cmdREADCERT: err = cmd_readcert (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info); break; - case cmdPASSWD: err = cmd_passwd (info, 1); break; + case cmdPASSWD: err = cmd_passwd (info, 1, argstr); break; case cmdUNBLOCK: err = cmd_unblock (info); break; case cmdFACTORYRESET: err = cmd_factoryreset (info); break; case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; @@ -3131,7 +3175,7 @@ interactive_loop (void) case cmdREADCERT: err = cmd_readcert (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info); break; - case cmdPASSWD: err = cmd_passwd (info, allow_admin); break; + case cmdPASSWD: err = cmd_passwd (info, allow_admin, argstr); break; case cmdUNBLOCK: err = cmd_unblock (info); break; case cmdFACTORYRESET: err = cmd_factoryreset (info); From 6c581cc468d7a1b860632b7a465d71224c6e4999 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 6 Feb 2019 10:37:03 +0100 Subject: [PATCH 151/235] doc: Add basic man page for the gpg-card-tool. -- --- doc/Makefile.am | 7 +-- doc/card-tool.texi | 126 +++++++++++++++++++++++++++++++++++++++++++++ doc/gnupg.texi | 12 ++--- 3 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 doc/card-tool.texi diff --git a/doc/Makefile.am b/doc/Makefile.am index cb69cd993..19b3825b1 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -69,7 +69,7 @@ nobase_dist_doc_DATA = FAQ DETAILS HACKING DCO TRANSLATE OpenPGP KEYSERVER \ gnupg_TEXINFOS = \ gpg.texi gpgsm.texi gpg-agent.texi scdaemon.texi instguide.texi \ tools.texi debugging.texi glossary.texi contrib.texi gpl.texi \ - sysnotes.texi dirmngr.texi wks.texi \ + sysnotes.texi dirmngr.texi wks.texi card-tool.texi \ gnupg-module-overview.svg \ gnupg-card-architecture.fig \ howtos.texi howto-create-a-server-cert.texi @@ -89,12 +89,13 @@ YAT2M_OPTIONS = -I $(srcdir) \ --release "GnuPG @PACKAGE_VERSION@" --source "GNU Privacy Guard 2.2" myman_sources = gnupg7.texi gpg.texi gpgsm.texi gpg-agent.texi \ - dirmngr.texi scdaemon.texi tools.texi wks.texi + dirmngr.texi scdaemon.texi tools.texi wks.texi \ + card-tool.texi myman_pages = gpgsm.1 gpg-agent.1 dirmngr.8 scdaemon.1 \ watchgnupg.1 gpgconf.1 addgnupghome.8 gpg-preset-passphrase.1 \ gpg-connect-agent.1 gpgparsemail.1 symcryptrun.1 gpgtar.1 \ applygnupgdefaults.8 gpg-wks-client.1 gpg-wks-server.1 \ - dirmngr-client.1 + dirmngr-client.1 gpg-card-tool.1 if USE_GPG2_HACK myman_pages += gpg2.1 gpgv2.1 else diff --git a/doc/card-tool.texi b/doc/card-tool.texi new file mode 100644 index 000000000..b84f20721 --- /dev/null +++ b/doc/card-tool.texi @@ -0,0 +1,126 @@ +@c card-tool.texi - man page for gpg-card-tool +@c Copyright (C) 2019 g10 Code GmbH +@c This is part of the GnuPG manual. +@c For copying conditions, see the file GnuPG.texi. + +@include defs.inc + +@node Smart Card Tool +@chapter Smart Card Tool + +GnuPG comes with tool to administrate smart cards and USB tokens. This +tool is an extension of the @option{--edit-key} command available with +@command{gpg}. + +@menu +* gpg-card-tool:: Administrate smart cards. +@end menu + +@c +@c GPG-CARD-TOOL +@c +@manpage gpg-card-tool.1 +@node gpg-card-tool +@section Administrate smart cards. +@ifset manverb +.B gpg-card-tool +\- Administrate Smart Cards +@end ifset + +@mansect synopsis +@ifset manverb +.B gpg-card-tool +.RI [ options ] +.br +.B gpg-card-tool +.RI [ options ] +.I command +.RI { +.B -- +.I command +.RI } +@end ifset + +@mansect description +The @command{gpg-card-tool} is used to administrate smart cards and USB +tokens. It provides a superset of features from @command{gpg +--card-edit} an can be considered a frontend to @command{scdaemon} +which is a daemon started by @command{gpg-agent} to handle smart +cards. + +If @command{gpg-card-tool} is invoked without commands an interactive +mode is used. + +If @command{gpg-card-tool} is invoked with one or more commands the +same commands as available in the interactive mode are run from the +command line. These commands need to be delimited with a double-dash. +If a double-dash or a shell specific character is required as part of +a command the entire command needs to be put in quotes. If one of +those commands returns an error the remaining commands are mot anymore +run unless the command was prefixed with a single dash. + +A list of commands is available by using the command @code{help} and a +detailed description of each command is printed by using @code{help +COMMAND}. + + +@mansect options +@noindent +@command{gpg-card-tool} understands these options: + +@table @gnupgtabopt + +@item --with-colons +@opindex with-colons +This option has currently no effect. + +@item --status-fd @var{n} +@opindex status-fd +Write special status strings to the file descriptor @var{n}. This +program returns only the status messages SUCCESS or FAILURE which are +helpful when the caller uses a double fork approach and can't easily +get the return code of the process. + +@item --verbose +@opindex verbose +Enable extra informational output. + +@item --quiet +@opindex quiet +Disable almost all informational output. + +@item --version +@opindex version +Print version of the program and exit. + +@item --help +@opindex help +Display a brief help page and exit. + +@item --no-autostart +@opindex no-autostart +Do not start the gpg-agent if it has not yet been started and its +service is required. This option is mostly useful on machines where +the connection to gpg-agent has been redirected to another machines. + +@item --agent-program @var{file} +@opindex agent-program +Specify the agent program to be started if none is running. The +default value is determined by running @command{gpgconf} with the +option @option{--list-dirs}. + +@item --gpg-program @var{file} +@opindex gpg-program +Specify a non-default gpg binary to be used by certain commands. + +@item --gpgsm-program @var{file} +@opindex gpgsm-program +Specify a non-default gpgsm binary to be used by certain commands. + +@end table + + +@mansect see also +@ifset isman +@command{scdaemon}(1) +@end ifset diff --git a/doc/gnupg.texi b/doc/gnupg.texi index 336414870..6210486a0 100644 --- a/doc/gnupg.texi +++ b/doc/gnupg.texi @@ -142,15 +142,16 @@ the administration and the architecture. * Specify a User ID:: How to Specify a User Id. * Trust Values:: How GnuPG displays trust values. -* Helper Tools:: Description of small helper tools -* Web Key Service:: Tools for the Web Key Service +* Smart Card Tool:: Tool to administrate smart cards. +* Helper Tools:: Description of small helper tools. +* Web Key Service:: Tools for the Web Key Service. * Howtos:: How to do certain things. * System Notes:: Notes pertaining to certain OSes. -* Debugging:: How to solve problems +* Debugging:: How to solve problems. * Copying:: GNU General Public License says - how you can copy and share GnuPG + how you can copy and share GnuPG. * Contributors:: People who have contributed to GnuPG. * Glossary:: Short description of terms used. @@ -186,6 +187,7 @@ the administration and the architecture. @cindex trust values @include trust-values.texi +@include card-tool.texi @include tools.texi @include wks.texi @@ -237,5 +239,3 @@ the administration and the architecture. @bye - - From c26af8ac263ea006ed32e110a09271e4bfbf1f37 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 6 Feb 2019 12:24:30 +0100 Subject: [PATCH 152/235] scd: Fix parameter name of app_change_key. * scd/app-common.h (APP_GENKEY_FLAG_FORCE): New. * scd/app.c (app_change_pin): Rename arg reset_mode to flags and change from int to unsigned int. -- This is basically a documentation fix. Signed-off-by: Werner Koch --- scd/app-common.h | 11 +++++++---- scd/app.c | 6 +++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/scd/app-common.h b/scd/app-common.h index 98d8464dc..d8b301c9a 100644 --- a/scd/app-common.h +++ b/scd/app-common.h @@ -25,11 +25,14 @@ #include #include - +/* Flags used with app_change_pin. */ #define APP_CHANGE_FLAG_RESET 1 /* PIN Reset mode. */ #define APP_CHANGE_FLAG_NULLPIN 2 /* NULL PIN mode. */ #define APP_CHANGE_FLAG_CLEAR 4 /* Clear the given PIN. */ +/* Flags used with app_genkey. */ +#define APP_GENKEY_FLAG_FORCE 1 /* Force overwriting existing key. */ + /* Bit flags set by the decipher function into R_INFO. */ #define APP_DECIPHER_INFO_NOPAD 1 /* Padding has been removed. */ @@ -182,9 +185,9 @@ gpg_error_t app_genkey (app_t app, ctrl_t ctrl, gpg_error_t app_get_challenge (app_t app, ctrl_t ctrl, size_t nbytes, unsigned char *buffer); gpg_error_t app_change_pin (app_t app, ctrl_t ctrl, - const char *chvnostr, int reset_mode, - gpg_error_t (*pincb)(void*, const char *, char **), - void *pincb_arg); + const char *chvnostr, unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg); gpg_error_t app_check_pin (app_t app, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); diff --git a/scd/app.c b/scd/app.c index c79a174fc..c6c1f84c0 100644 --- a/scd/app.c +++ b/scd/app.c @@ -988,7 +988,8 @@ app_get_challenge (app_t app, ctrl_t ctrl, size_t nbytes, unsigned char *buffer) /* Perform a CHANGE REFERENCE DATA or RESET RETRY COUNTER operation. */ gpg_error_t -app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, +app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, + unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { @@ -1003,8 +1004,7 @@ app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, err = lock_app (app, ctrl); if (err) return err; - err = app->fnc.change_pin (app, ctrl, chvnostr, reset_mode, - pincb, pincb_arg); + err = app->fnc.change_pin (app, ctrl, chvnostr, flags, pincb, pincb_arg); unlock_app (app); if (opt.verbose) log_info ("operation change_pin result: %s\n", gpg_strerror (err)); From 9a9cb0257aebb1480b999fdf9d90904083eb8e3c Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 6 Feb 2019 14:07:42 +0100 Subject: [PATCH 153/235] scd: Make app_genkey and supporting ISO function more flexible. * scd/app.c (app_genkey): Add arg keytype. * scd/app-common.h (struct app_ctx_s): Fitto for the genkey member. * scd/command.c (cmd_genkey): Adjust for change. * scd/iso7816.c (do_generate_keypair): Replace arg read_only by new args p1 and p2. (iso7816_read_public_key): Adjust for this. (iso7816_generate_keypair): Add new args p1 and p2. * scd/app-openpgp.c (do_genkey): Adjust for changes. -- The OpenPGP card creates keys according to parameters read from a data object. Other cards we are about to implement require a direct specification of the requested keytype. This patch implements the required changes. Signed-off-by: Werner Koch --- scd/app-common.h | 8 ++++---- scd/app-openpgp.c | 8 +++++--- scd/app.c | 6 +++--- scd/command.c | 3 ++- scd/iso7816.c | 10 +++++----- scd/iso7816.h | 8 +++++--- 6 files changed, 24 insertions(+), 19 deletions(-) diff --git a/scd/app-common.h b/scd/app-common.h index d8b301c9a..e9d167ecc 100644 --- a/scd/app-common.h +++ b/scd/app-common.h @@ -106,8 +106,8 @@ struct app_ctx_s { void *pincb_arg, const unsigned char *pk, size_t pklen); gpg_error_t (*genkey) (app_t app, ctrl_t ctrl, - const char *keynostr, unsigned int flags, - time_t createtime, + const char *keyref, const char *keytype, + unsigned int flags, time_t createtime, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); gpg_error_t (*change_pin) (app_t app, ctrl_t ctrl, @@ -178,8 +178,8 @@ gpg_error_t app_writekey (app_t app, ctrl_t ctrl, void *pincb_arg, const unsigned char *keydata, size_t keydatalen); gpg_error_t app_genkey (app_t app, ctrl_t ctrl, - const char *keynostr, unsigned int flags, - time_t createtime, + const char *keynostr, const char *keytype, + unsigned int flags, time_t createtime, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); gpg_error_t app_get_challenge (app_t app, ctrl_t ctrl, size_t nbytes, diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index 72ed80a3d..ae87d19f1 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -4086,8 +4086,8 @@ do_writekey (app_t app, ctrl_t ctrl, /* Handle the GENKEY command. */ static gpg_error_t -do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, - time_t createtime, +do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, const char *keytype, + unsigned int flags, time_t createtime, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { @@ -4103,6 +4103,8 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, int exmode = 0; int le_value = 256; /* Use legacy value. */ + (void)keytype; /* Ignored for OpenPGP cards. */ + if (keyno < 0 || keyno > 2) return gpg_error (GPG_ERR_INV_ID); @@ -4151,7 +4153,7 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, log_info (_("please wait while key is being generated ...\n")); start_at = time (NULL); - err = iso7816_generate_keypair (app->slot, exmode, + err = iso7816_generate_keypair (app->slot, exmode, 0x80, 0, (keyno == 0? "\xB6" : keyno == 1? "\xB8" : "\xA4"), 2, le_value, &buffer, &buflen); diff --git a/scd/app.c b/scd/app.c index c6c1f84c0..2e2729e32 100644 --- a/scd/app.c +++ b/scd/app.c @@ -939,8 +939,8 @@ app_writekey (app_t app, ctrl_t ctrl, /* Perform a SETATTR operation. */ gpg_error_t -app_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, - time_t createtime, +app_genkey (app_t app, ctrl_t ctrl, const char *keynostr, + const char *keytype, unsigned int flags, time_t createtime, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { @@ -955,7 +955,7 @@ app_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, err = lock_app (app, ctrl); if (err) return err; - err = app->fnc.genkey (app, ctrl, keynostr, flags, + err = app->fnc.genkey (app, ctrl, keynostr, keytype, flags, createtime, pincb, pincb_arg); unlock_app (app); if (opt.verbose) diff --git a/scd/command.c b/scd/command.c index fb0ba98fc..8fd6ba55d 100644 --- a/scd/command.c +++ b/scd/command.c @@ -1190,7 +1190,8 @@ cmd_genkey (assuan_context_t ctx, char *line) keyno = xtrystrdup (keyno); if (!keyno) return out_of_core (); - rc = app_genkey (ctrl->app_ctx, ctrl, keyno, force? 1:0, + rc = app_genkey (ctrl->app_ctx, ctrl, keyno, NULL, + force? APP_GENKEY_FLAG_FORCE : 0, timestamp, pin_cb, ctx); xfree (keyno); diff --git a/scd/iso7816.c b/scd/iso7816.c index 69009c43e..758de5b48 100644 --- a/scd/iso7816.c +++ b/scd/iso7816.c @@ -709,7 +709,7 @@ iso7816_general_authenticate (int slot, int extended_mode, returned. In that case a value of -1 uses a large default (e.g. 4096 bytes), a value larger 256 used that value. */ static gpg_error_t -do_generate_keypair (int slot, int extended_mode, int read_only, +do_generate_keypair (int slot, int extended_mode, int p1, int p2, const char *data, size_t datalen, int le, unsigned char **result, size_t *resultlen) { @@ -721,7 +721,7 @@ do_generate_keypair (int slot, int extended_mode, int read_only, *resultlen = 0; sw = apdu_send_le (slot, extended_mode, - 0x00, CMD_GENERATE_KEYPAIR, read_only? 0x81:0x80, 0, + 0x00, CMD_GENERATE_KEYPAIR, p1, p2, datalen, data, le >= 0 && le < 256? 256:le, result, resultlen); @@ -739,12 +739,12 @@ do_generate_keypair (int slot, int extended_mode, int read_only, gpg_error_t -iso7816_generate_keypair (int slot, int extended_mode, +iso7816_generate_keypair (int slot, int extended_mode, int p1, int p2, const char *data, size_t datalen, int le, unsigned char **result, size_t *resultlen) { - return do_generate_keypair (slot, extended_mode, 0, + return do_generate_keypair (slot, extended_mode, p1, p2, data, datalen, le, result, resultlen); } @@ -755,7 +755,7 @@ iso7816_read_public_key (int slot, int extended_mode, int le, unsigned char **result, size_t *resultlen) { - return do_generate_keypair (slot, extended_mode, 1, + return do_generate_keypair (slot, extended_mode, 0x81, 0, data, datalen, le, result, resultlen); } diff --git a/scd/iso7816.h b/scd/iso7816.h index 4a366e6eb..df5d25fe8 100644 --- a/scd/iso7816.h +++ b/scd/iso7816.h @@ -112,9 +112,11 @@ gpg_error_t iso7816_general_authenticate (int slot, int extended_mode, unsigned char **result, size_t *resultlen); gpg_error_t iso7816_generate_keypair (int slot, int extended_mode, - const char *data, size_t datalen, - int le, - unsigned char **result, size_t *resultlen); + int p1, int p2, + const char *data, size_t datalen, + int le, + unsigned char **result, + size_t *resultlen); gpg_error_t iso7816_read_public_key (int slot, int extended_mode, const char *data, size_t datalen, int le, From b5b1f721582df9d0379cb68b4faeceed32a56e49 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 6 Feb 2019 20:47:07 +0100 Subject: [PATCH 154/235] scd: Add genkey command to app-piv (rsa-only) * scd/app-piv.c (struct genkey_result_s): new. (struct app_local_s): add member genkey_results. (do_deinit): Free that one. (flush_cached_data): Extend to delete all items. (keyref_from_dobj): New. (do_readkey): New. (do_auth): Use keyref_from_dobj. (does_key_exist): New. (genkey_parse_rsa): New. (do_genkey): New. -- We need to extend the GENKEY in command.c to support other algos. Signed-off-by: Werner Koch --- scd/app-piv.c | 362 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 355 insertions(+), 7 deletions(-) diff --git a/scd/app-piv.c b/scd/app-piv.c index 42f16de40..cfc4a27b3 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -38,6 +38,7 @@ * | Unblock PIN | | | yes | New PIN required | * |---------------------------------------------------------------| * (9B indicates the 24 byte PIV Card Application Administration Key) + * */ #include @@ -152,11 +153,22 @@ struct cache_s { }; +/* A cache item used by genkey. */ +struct genkey_result_s { + struct genkey_result_s *next; + int keyref; + gcry_sexp_t s_pkey; +}; + + /* Object with application specific data. */ struct app_local_s { /* A linked list with cached DOs. */ struct cache_s *cache; + /* A list with results from recent genkey operations. */ + struct genkey_result_s *genkey_results; + /* Various flags. */ struct { @@ -181,12 +193,19 @@ do_deinit (app_t app) if (app && app->app_local) { struct cache_s *c, *c2; + struct genkey_result_s *gr, *gr2; for (c = app->app_local->cache; c; c = c2) { c2 = c->next; xfree (c); } + for (gr = app->app_local->genkey_results; gr; gr = gr2) + { + gr2 = gr->next; + gcry_sexp_release (gr->s_pkey); + xfree (gr); + } xfree (app->app_local); app->app_local = NULL; @@ -284,14 +303,15 @@ get_cached_data (app_t app, int tag, } -/* Remove data object described by TAG from the cache. */ +/* Remove data object described by TAG from the cache. If TAG is 0 + * all cache iterms are flushed. */ static void flush_cached_data (app_t app, int tag) { struct cache_s *c, *cprev; for (c=app->app_local->cache, cprev=NULL; c; cprev=c, c = c->next) - if (c->tag == tag) + if (c->tag == tag || !tag) { if (cprev) cprev->next = c->next; @@ -860,7 +880,7 @@ do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) } -/* Core of do-readcert which fetches the certificate based on the +/* Core of do_readcert which fetches the certificate based on the * given tag and returns it in a freshly allocated buffer stored at * R_CERT and the length of the certificate stored at R_CERTLEN. */ static gpg_error_t @@ -1010,6 +1030,17 @@ find_dobj_by_keyref (app_t app, const char *keyref) } +/* Return the keyref from DOBJ as an integer. If it does not exist, + * return -1. */ +static int +keyref_from_dobj (data_object_t dobj) +{ + if (!dobj || !hexdigitp (dobj->keyref) || !hexdigitp (dobj->keyref+1)) + return -1; + return xtoi_2 (dobj->keyref); +} + + /* Read a certificate from the card and returned in a freshly * allocated buffer stored at R_CERT and the length of the certificate * stored at R_CERTLEN. CERTID is either the OID of the cert's @@ -1031,6 +1062,70 @@ do_readcert (app_t app, const char *certid, } +/* Return a public key in a freshly allocated buffer. This will only + * work for a freshly generated key as long as no reset of the + * application has been performed. This is because we return a cached + * result from key generation. If no cached result is available, the + * error GPG_ERR_UNSUPPORTED_OPERATION is returned so that the higher + * layer can then to get the key by reading the matching certificate. + * On success a canonical encoded S-expression with the public key is + * stored at (R_PK,R_PKLEN); the caller must release that buffer. On + * error R_PK and R_PKLEN are not changed and an error code is + * returned. + */ +static gpg_error_t +do_readkey (app_t app, int advanced, const char *keyrefstr, + unsigned char **r_pk, size_t *r_pklen) +{ + gpg_error_t err; + data_object_t dobj; + int keyref; + struct genkey_result_s *gres; + unsigned char *pk = NULL; + size_t pklen; + + dobj = find_dobj_by_keyref (app, keyrefstr); + if ((keyref = keyref_from_dobj (dobj)) == -1) + { + err = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + for (gres = app->app_local->genkey_results; gres; gres = gres->next) + if (gres->keyref == keyref) + break; + if (!gres || !gres->s_pkey) + { + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + goto leave; + } + + err = make_canon_sexp (gres->s_pkey, &pk, &pklen); + if (err) + goto leave; + if (advanced) + { + /* FIXME: How ugly - we should move that to command.c */ + char *p = canon_sexp_to_string (pk, pklen); + if (!p) + { + err = gpg_error_from_syserror (); + goto leave; + } + xfree (pk); + pk = p; + pklen = strlen (pk); + } + + *r_pk = pk; + pk = NULL; + *r_pklen = pklen; + + leave: + xfree (pk); + return err; +} + + /* Given a data object DOBJ return the corresponding PIV algorithm and * store it at R_ALGO. The algorithm is taken from the corresponding * certificate or from a cache. */ @@ -1553,12 +1648,11 @@ do_auth (app_t app, const char *keyidstr, * make sense for X.509 certs? */ dobj = find_dobj_by_keyref (app, keyidstr); - if (!dobj) + if ((keyref = keyref_from_dobj (dobj)) == -1) { err = gpg_error (GPG_ERR_INV_ID); goto leave; } - keyref = xtoi_2 (dobj->keyref); err = get_key_algorithm_by_dobj (app, dobj, &algo); if (err) @@ -1714,6 +1808,260 @@ do_auth (app_t app, const char *keyidstr, } +/* Check whether a key for DOBJ already exists. We detect this by + * reading the certificate described by DOBJ. If FORCE is TRUE a + * diagnositic will be printed but no error returned if the key + * already exists. The flag GENERATING is used to select a + * diagnositic. */ +static gpg_error_t +does_key_exist (app_t app, data_object_t dobj, int generating, int force) +{ + void *relptr; + unsigned char *buffer; + size_t buflen; + int found; + + relptr = get_one_do (app, dobj->tag, &buffer, &buflen, NULL); + found = (relptr && buflen); + xfree (relptr); + + if (found && !force) + { + log_error ("piv: %s", _("key already exists\n")); + return gpg_error (GPG_ERR_EEXIST); + } + + if (found) + log_info ("piv: %s", _("existing key will be replaced\n")); + else if (generating) + log_info ("piv: %s", _("generating new key\n")); + else + log_info ("piv: %s", _("writing new key\n")); + return 0; +} + + +/* Parse an RSA response object, consisting of the content of tag + * 0x7f49, into a gcrypt s-expresstion object and store that R_SEXP. + * On error NULL is stored at R_SEXP. */ +static gpg_error_t +genkey_parse_rsa (const unsigned char *data, size_t datalen, + gcry_sexp_t *r_sexp) +{ + gpg_error_t err; + const unsigned char *m, *e; + unsigned char *mbuf = NULL; + unsigned char *ebuf = NULL; + size_t mlen, elen; + + *r_sexp = NULL; + + m = find_tlv (data, datalen, 0x0081, &mlen); + if (!m) + { + log_error (_("response does not contain the RSA modulus\n")); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + + e = find_tlv (data, datalen, 0x0082, &elen); + if (!e) + { + log_error (_("response does not contain the RSA public exponent\n")); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + + for (; mlen && !*m; mlen--, m++) /* Strip leading zeroes */ + ; + for (; elen && !*e; elen--, e++) /* Strip leading zeroes */ + ; + + mbuf = xtrymalloc (mlen + 1); + if (!mbuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + /* Prepend numbers with a 0 if needed. */ + if (mlen && (*m & 0x80)) + { + *mbuf = 0; + memcpy (mbuf+1, m, mlen); + mlen++; + } + else + memcpy (mbuf, m, mlen); + + ebuf = xtrymalloc (elen + 1); + if (!ebuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + /* Prepend numbers with a 0 if needed. */ + if (elen && (*e & 0x80)) + { + *ebuf = 0; + memcpy (ebuf+1, e, elen); + elen++; + } + else + memcpy (ebuf, e, elen); + + err = gcry_sexp_build (r_sexp, NULL, "(public-key(rsa(n%b)(e%b)))", + (int)mlen, mbuf, (int)elen, ebuf); + + leave: + xfree (mbuf); + xfree (ebuf); + return err; +} + + +/* Create a new keypair for KEYREF. If KEYTYPE is NULL a default + * keytype is selected, else it may be one of the strings: + * "rsa2048", "nistp256, or "nistp384". + * + * Supported FLAGS are: + * APP_GENKEY_FLAG_FORCE Overwrite existing key. + * + * Note that CREATETIME is not used for PIV cards. + * + * Because there seems to be no way to read the public key we need to + * retrieve it from a certificate. The GnuPG system however requires + * the use of app_readkey to fetch the public key from the card to + * create the certificate; to support this we temporary store the + * generated public key in the local context for use by app_readkey. + */ +static gpg_error_t +do_genkey (app_t app, ctrl_t ctrl, const char *keyrefstr, const char *keytype, + unsigned int flags, time_t createtime, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + data_object_t dobj; + unsigned char *buffer = NULL; + size_t buflen; + int force = !!(flags & APP_GENKEY_FLAG_FORCE); + int mechanism; + time_t start_at; + int keyref; + unsigned char tmpl[5]; + size_t tmpllen; + const unsigned char *keydata; + size_t keydatalen; + gcry_sexp_t s_pkey = NULL; + struct genkey_result_s *gres; + + (void)ctrl; + (void)createtime; + (void)pincb; + (void)pincb_arg; + + if (!keytype) + keytype = "rsa2048"; + + if (!strcmp (keytype, "rsa2048")) + mechanism = PIV_ALGORITHM_RSA; + else if (!strcmp (keytype, "nistp256")) + mechanism = PIV_ALGORITHM_ECC_P256; + else if (!strcmp (keytype, "nistp384")) + mechanism = PIV_ALGORITHM_ECC_P384; + else + return gpg_error (GPG_ERR_UNKNOWN_CURVE); + + /* We flush the cache to increase the I/O traffic before a key + * generation. This _might_ help the card to gather more entropy + * and is anyway a prerequisite for does_key_exist. */ + flush_cached_data (app, 0); + + /* Check whether a key already exists. */ + dobj = find_dobj_by_keyref (app, keyrefstr); + if ((keyref = keyref_from_dobj (dobj)) == -1) + { + err = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + err = does_key_exist (app, dobj, 1, force); + if (err) + goto leave; + + + /* FIXME: Check that the authentication has already been done. */ + + + + /* Create the key. */ + log_info (_("please wait while key is being generated ...\n")); + start_at = time (NULL); + tmpl[0] = 0xac; + tmpl[1] = 3; + tmpl[2] = 0x80; + tmpl[3] = 1; + tmpl[4] = mechanism; + tmpllen = 5; + err = iso7816_generate_keypair (app->slot, 0, 0, keyref, + tmpl, tmpllen, 0, &buffer, &buflen); + if (err) + { + log_error (_("generating key failed\n")); + return gpg_error (GPG_ERR_CARD); + } + + { + int nsecs = (int)(time (NULL) - start_at); + log_info (ngettext("key generation completed (%d second)\n", + "key generation completed (%d seconds)\n", + nsecs), nsecs); + } + + /* Parse the result and store it as an s-expression in a dedicated + * cache for later retrieval by app_readkey. */ + keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen); + if (!keydata || !keydatalen) + { + err = gpg_error (GPG_ERR_CARD); + log_error (_("response does not contain the public key data\n")); + goto leave; + } + + if (mechanism == PIV_ALGORITHM_RSA) + err = genkey_parse_rsa (keydata, keydatalen, &s_pkey); + else + err = gpg_error (GPG_ERR_BUG); + if (err) + goto leave; + + for (gres = app->app_local->genkey_results; gres; gres = gres->next) + if (gres->keyref == keyref) + break; + if (!gres) + { + gres = xtrycalloc (1, sizeof *gres); + if (!gres) + { + err = gpg_error_from_syserror (); + goto leave; + } + gres->keyref = keyref; + gres->next = app->app_local->genkey_results; + app->app_local->genkey_results = gres; + } + else + gcry_sexp_release (gres->s_pkey); + gres->s_pkey = s_pkey; + s_pkey = NULL; + + + leave: + gcry_sexp_release (s_pkey); + xfree (buffer); + return err; +} + + /* Select the PIV application on the card in SLOT. This function must * be used before any other PIV application functions. */ gpg_error_t @@ -1801,12 +2149,12 @@ app_select_piv (app_t app) app->fnc.deinit = do_deinit; app->fnc.learn_status = do_learn_status; app->fnc.readcert = do_readcert; - app->fnc.readkey = NULL; + app->fnc.readkey = do_readkey; app->fnc.getattr = do_getattr; app->fnc.setattr = do_setattr; /* app->fnc.writecert = do_writecert; */ /* app->fnc.writekey = do_writekey; */ - /* app->fnc.genkey = do_genkey; */ + app->fnc.genkey = do_genkey; /* app->fnc.sign = do_sign; */ app->fnc.auth = do_auth; /* app->fnc.decipher = do_decipher; */ From 090b5f804aee41a36a9ee1fbf0976109970d810d Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 7 Feb 2019 08:16:02 +0100 Subject: [PATCH 155/235] card: Add readline completion for help arguments -- --- tools/gpg-card-tool.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index fd7aa9a7e..08248f766 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -3249,9 +3249,12 @@ command_completion (const char *text, int start, int end) (void)end; /* If we are at the start of a line, we try and command-complete. - * If not, just do nothing for now. */ + * If not, just do nothing for now. The support for help completion + * needs to be more smarter. */ if (!start) return rl_completion_matches (text, command_generator); + else if (start == 5 && !ascii_strncasecmp (rl_line_buffer, "help ", 5)) + return rl_completion_matches (text, command_generator); rl_attempted_completion_over = 1; From fcec5b40e589b2ef201efb89f22a952feb4a9069 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 7 Feb 2019 11:05:22 +0100 Subject: [PATCH 156/235] card: Support reading and writing PIV certificates * scd/app-piv.c (add_tlv): New. (put_data): New. (do_writecert): New. (do_setattr): Remove usused special mode 0. * tools/gpg-card-tool.c (cmd_writecert): Allow other cards than OPENPGP. (cmd_readcert): Ditto. Signed-off-by: Werner Koch --- scd/app-piv.c | 199 ++++++++++++++++++++++++++++++++++++++++-- tools/gpg-card-tool.c | 76 +++++++++------- 2 files changed, 235 insertions(+), 40 deletions(-) diff --git a/scd/app-piv.c b/scd/app-piv.c index cfc4a27b3..59f2725fe 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -426,6 +426,157 @@ dump_all_do (int slot) } +/* Create a TLV tag and value and store it at BUFFER. Return the + * length of tag and length. A LENGTH greater than 65535 is + * truncated. TAG must be less or equal to 2^16. If BUFFER is NULL, + * only the required length is computed. */ +static size_t +add_tlv (unsigned char *buffer, unsigned int tag, size_t length) +{ + if (length > 0xffff) + length = 0xffff; + + if (buffer) + { + unsigned char *p = buffer; + + if (tag > 0xff) + *p++ = tag >> 8; + *p++ = tag; + if (length < 128) + *p++ = length; + else if (length < 256) + { + *p++ = 0x81; + *p++ = length; + } + else + { + *p++ = 0x82; + *p++ = length >> 8; + *p++ = length; + } + + return p - buffer; + } + else + { + size_t n = 0; + + if (tag > 0xff) + n++; + n++; + if (length < 128) + n++; + else if (length < 256) + n += 2; + else + n += 3; + return n; + } +} + + +/* Wrapper around iso7816_put_data_odd which also sets the tag into + * the '5C' data object. The varargs are tuples of (int,size_t,void) + * with the tag, the length and the actual data. A (0,0,NULL) tuple + * terminates the list. Up to 10 tuples are supported. */ +static gpg_error_t +put_data (int slot, unsigned int tag, ...) +{ + gpg_error_t err; + va_list arg_ptr; + struct { + int tag; + size_t len; + const void *data; + } argv[10]; + int i, argc; + unsigned char data5c[5]; + size_t data5clen; + unsigned char *data = NULL; + size_t datalen; + unsigned char *p; + size_t n; + + /* Collect all args. Check that length is <= 2^16 to match the + * behaviour of add_tlv. */ + va_start (arg_ptr, tag); + argc = 0; + while (((argv[argc].tag = va_arg (arg_ptr, int)))) + { + argv[argc].len = va_arg (arg_ptr, size_t); + argv[argc].data = va_arg (arg_ptr, const void *); + if (argc >= DIM (argv)-1 || argv[argc].len > 0xffff) + { + va_end (arg_ptr); + return GPG_ERR_EINVAL; + } + argc++; + } + va_end (arg_ptr); + + /* Build the TLV with the tag to be updated. */ + data5c[0] = 0x5c; /* Tag list */ + if (tag <= 0xff) + { + data5c[1] = 1; + data5c[2] = tag; + data5clen = 3; + } + else if (tag <= 0xffff) + { + data5c[1] = 2; + data5c[2] = (tag >> 8); + data5c[3] = tag; + data5clen = 4; + } + else + { + data5c[1] = 3; + data5c[2] = (tag >> 16); + data5c[3] = (tag >> 8); + data5c[4] = tag; + data5clen = 5; + } + + /* Compute the required buffer length and allocate the buffer. */ + n = 0; + for (i=0; i < argc; i++) + { + n += add_tlv (NULL, argv[i].tag, argv[i].len); + n += argv[i].len; + } + datalen = data5clen + add_tlv (NULL, 0x53, n) + n; + data = xtrymalloc (datalen); + if (!data) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Copy that data to the buffer. */ + p = data; + memcpy (p, data5c, data5clen); + p += data5clen; + p += add_tlv (p, 0x53, n); + for (i=0; i < argc; i++) + { + p += add_tlv (p, argv[i].tag, argv[i].len); + memcpy (p, argv[i].data, argv[i].len); + p += argv[i].len; + } + log_assert ( data + datalen == p ); + log_printhex (data, datalen, "Put data"); + err = iso7816_put_data_odd (slot, -1 /* use command chaining */, + 0x3fff, data, datalen); + + leave: + xfree (data); + return err; +} + + /* Parse the key reference KEYREFSTR which is expected to hold a key * reference for a CHV object. Return the one octet keyref or -1 for * an invalid reference. */ @@ -802,13 +953,6 @@ do_setattr (app_t app, const char *name, switch (table[idx].special) { - case 0: - err = iso7816_put_data (app->slot, 0, table[idx].tag, value, valuelen); - if (err) - log_error ("failed to set '%s': %s\n", - table[idx].name, gpg_strerror (err)); - break; - case 1: err = auth_adm_key (app, value, valuelen); break; @@ -2062,6 +2206,45 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keyrefstr, const char *keytype, } +/* Write the certificate (CERT,CERTLEN) to the card at CERTREFSTR. + * CERTREFSTR is either the OID of the certificate's container data + * object or of the form "PIV.". */ +static gpg_error_t +do_writecert (app_t app, ctrl_t ctrl, + const char *certrefstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *cert, size_t certlen) +{ + gpg_error_t err; + data_object_t dobj; + + (void)ctrl; + (void)pincb; /* Not used; instead authentication is needed. */ + (void)pincb_arg; + + dobj = find_dobj_by_keyref (app, certrefstr); + if (!dobj || !*dobj->keyref) + return gpg_error (GPG_ERR_INV_ID); + + /* FIXME: Check that the authentication has already been done. */ + + flush_cached_data (app, dobj->tag); + + err = put_data (app->slot, dobj->tag, + (int)0x70, (size_t)certlen, cert,/* Certificate */ + (int)0x71, (size_t)1, "", /* No compress */ + (int)0xfe, (size_t)0, "", /* Empty LRC. */ + (int)0, (size_t)0, NULL); + if (err) + log_error ("piv: failed to write cert to %s: %s\n", + dobj->keyref, gpg_strerror (err)); + + + return err; +} + + /* Select the PIV application on the card in SLOT. This function must * be used before any other PIV application functions. */ gpg_error_t @@ -2152,7 +2335,7 @@ app_select_piv (app_t app) app->fnc.readkey = do_readkey; app->fnc.getattr = do_getattr; app->fnc.setattr = do_setattr; - /* app->fnc.writecert = do_writecert; */ + app->fnc.writecert = do_writecert; /* app->fnc.writekey = do_writekey; */ app->fnc.genkey = do_genkey; /* app->fnc.sign = do_sign; */ diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index 08248f766..917013247 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -1551,36 +1551,41 @@ cmd_writecert (card_info_t info, char *argstr) { gpg_error_t err; int opt_clear; - int do_no; + char *certref_buffer = NULL; + char *certref; char *data = NULL; size_t datalen; if (!info) return print_help - ("WRITECERT [--clear] 3 < FILE\n\n" + ("WRITECERT [--clear] CERTREF < FILE\n\n" "Write a certificate for key 3. Unless --clear is given\n" - "the file argement is mandatory. The option --clear removes\n" + "the file argument is mandatory. The option --clear removes\n" "the certificate from the card.", - APP_TYPE_OPENPGP, 0); + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); opt_clear = has_leading_option (argstr, "--clear"); argstr = skip_options (argstr); - if (digitp (argstr)) + certref = argstr; + if ((argstr = strchr (certref, ' '))) { - do_no = atoi (argstr); - while (digitp (argstr)) - argstr++; - while (spacep (argstr)) - argstr++; + *argstr++ = 0; + trim_spaces (certref); + trim_spaces (argstr); } - else - do_no = 0; + else /* Let argstr point to an empty string. */ + argstr = certref + strlen (certref); - if (do_no != 3) + if (info->apptype == APP_TYPE_OPENPGP) { - err = gpg_error (GPG_ERR_INV_ARG); - goto leave; + if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3")) + { + err = gpg_error (GPG_ERR_INV_ID); + log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n"); + goto leave; + } + certref = certref_buffer = xstrdup ("OPENPGP.3"); } if (opt_clear) @@ -1602,10 +1607,11 @@ cmd_writecert (card_info_t info, char *argstr) goto leave; } - err = scd_writecert ("OPENPGP.3", data, datalen); + err = scd_writecert (certref, data, datalen); leave: xfree (data); + xfree (certref_buffer); return err; } @@ -1614,37 +1620,42 @@ static gpg_error_t cmd_readcert (card_info_t info, char *argstr) { gpg_error_t err; - int do_no; + char *certref_buffer = NULL; + char *certref; void *data = NULL; size_t datalen; const char *fname; if (!info) return print_help - ("READCERT 3 > FILE\n\n" + ("READCERT CERTREF > FILE\n\n" "Read the certificate for key 3 and store it in FILE.", - APP_TYPE_OPENPGP, 0); + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); argstr = skip_options (argstr); - if (digitp (argstr)) + certref = argstr; + if ((argstr = strchr (certref, ' '))) { - do_no = atoi (argstr); - while (digitp (argstr)) - argstr++; - while (spacep (argstr)) - argstr++; + *argstr++ = 0; + trim_spaces (certref); + trim_spaces (argstr); } - else - do_no = 0; + else /* Let argstr point to an empty string. */ + argstr = certref + strlen (certref); - if (do_no != 3) + if (info->apptype == APP_TYPE_OPENPGP) { - err = gpg_error (GPG_ERR_INV_ARG); - goto leave; + if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3")) + { + err = gpg_error (GPG_ERR_INV_ID); + log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n"); + goto leave; + } + certref = certref_buffer = xstrdup ("OPENPGP.3"); } - if (*argstr == '>') /* Read it from a file */ + if (*argstr == '>') /* Write it to a file */ { for (argstr++; spacep (argstr); argstr++) ; @@ -1656,7 +1667,7 @@ cmd_readcert (card_info_t info, char *argstr) goto leave; } - err = scd_readcert ("OPENPGP.3", &data, &datalen); + err = scd_readcert (certref, &data, &datalen); if (err) goto leave; @@ -1664,6 +1675,7 @@ cmd_readcert (card_info_t info, char *argstr) leave: xfree (data); + xfree (certref_buffer); return err; } From 5bf1212000f48243642ace0f708fd27446879b9e Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 7 Feb 2019 16:13:21 +0100 Subject: [PATCH 157/235] scd: Store a new PIV public key in the certificate DO. * scd/app-piv.c (struct genkey_result_s): Remove type and all users. (send_keypair_and_cert_info): Print certinfo only if we got a cert.. (readcert_by_tag): Add arg r_mechanism and implement reading of public keys. (get_keygrip_by_tag): Use a public key to compute the keygrip. (do_readcert): Make sure to only return a certificate. (do_readkey): Read public key from the DO if a certificate is missing. (get_key_algorithm_by_dobj): Get the algorithm also from a public key. (does_key_exist): String changes. (do_genkey): Remove result caching and store public key in the DO. -- This removes the result cache and instead stores the public key in the certificate object. This allows to properly list public keys at any time after generating a key and before a new certificate is stored there. Signed-off-by: Werner Koch --- scd/app-piv.c | 289 +++++++++++++++++++++++++++++++------------------- scd/command.c | 3 +- 2 files changed, 183 insertions(+), 109 deletions(-) diff --git a/scd/app-piv.c b/scd/app-piv.c index 59f2725fe..f4eb918ba 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -39,6 +39,12 @@ * |---------------------------------------------------------------| * (9B indicates the 24 byte PIV Card Application Administration Key) * + * - When generating a key we store the created public key in the + * corresponding data object, so that gpg and gpgsm are able to get + * the public key, create a certificate and store that then in that + * data object. That is not standard compliant but due to the use + * of other tags, it should not harm. See do_genkey for the actual + * used tag structure. */ #include @@ -153,22 +159,11 @@ struct cache_s { }; -/* A cache item used by genkey. */ -struct genkey_result_s { - struct genkey_result_s *next; - int keyref; - gcry_sexp_t s_pkey; -}; - - /* Object with application specific data. */ struct app_local_s { /* A linked list with cached DOs. */ struct cache_s *cache; - /* A list with results from recent genkey operations. */ - struct genkey_result_s *genkey_results; - /* Various flags. */ struct { @@ -180,7 +175,9 @@ struct app_local_s { /***** Local prototypes *****/ static gpg_error_t get_keygrip_by_tag (app_t app, unsigned int tag, - char **r_keygripstr); + char **r_keygripstr, int *got_cert); +static gpg_error_t genkey_parse_rsa (const unsigned char *data, size_t datalen, + gcry_sexp_t *r_sexp); @@ -193,19 +190,12 @@ do_deinit (app_t app) if (app && app->app_local) { struct cache_s *c, *c2; - struct genkey_result_s *gr, *gr2; for (c = app->app_local->cache; c; c = c2) { c2 = c->next; xfree (c); } - for (gr = app->app_local->genkey_results; gr; gr = gr2) - { - gr2 = gr->next; - gcry_sexp_release (gr->s_pkey); - xfree (gr); - } xfree (app->app_local); app->app_local = NULL; @@ -567,7 +557,6 @@ put_data (int slot, unsigned int tag, ...) p += argv[i].len; } log_assert ( data + datalen == p ); - log_printhex (data, datalen, "Put data"); err = iso7816_put_data_odd (slot, -1 /* use command chaining */, 0x3fff, data, datalen); @@ -978,9 +967,10 @@ send_keypair_and_cert_info (app_t app, ctrl_t ctrl, data_object_t dobj, { gpg_error_t err = 0; char *keygripstr = NULL; + int got_cert; char idbuf[50]; - err = get_keygrip_by_tag (app, dobj->tag, &keygripstr); + err = get_keygrip_by_tag (app, dobj->tag, &keygripstr, &got_cert); if (err) goto leave; @@ -989,7 +979,7 @@ send_keypair_and_cert_info (app_t app, ctrl_t ctrl, data_object_t dobj, keygripstr, strlen (keygripstr), idbuf, strlen (idbuf), NULL, (size_t)0); - if (!only_keypair) + if (!only_keypair && got_cert) { /* All certificates are of type 100 (Regular X.509 Cert). */ send_status_info (ctrl, "CERTINFO", @@ -1026,20 +1016,24 @@ do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) /* Core of do_readcert which fetches the certificate based on the * given tag and returns it in a freshly allocated buffer stored at - * R_CERT and the length of the certificate stored at R_CERTLEN. */ + * R_CERT and the length of the certificate stored at R_CERTLEN. If + * on success a non-zero value is stored at R_MECHANISM, the returned + * data is not certificate but a public key (in the format used by the + * container '7f49'. */ static gpg_error_t readcert_by_tag (app_t app, unsigned int tag, - unsigned char **r_cert, size_t *r_certlen) + unsigned char **r_cert, size_t *r_certlen, int *r_mechanism) { gpg_error_t err; unsigned char *buffer; size_t buflen; void *relptr; - const unsigned char *s; - size_t n; + const unsigned char *s, *s2; + size_t n, n2; *r_cert = NULL; *r_certlen = 0; + *r_mechanism = 0; relptr = get_one_do (app, tag, &buffer, &buflen, NULL); if (!relptr || !buflen) @@ -1049,36 +1043,61 @@ readcert_by_tag (app_t app, unsigned int tag, } s = find_tlv (buffer, buflen, 0x71, &n); - if (!s || n != 1) + if (!s) { - log_error ("piv: no or invalid CertInfo in 0x%X\n", tag); - err = gpg_error (GPG_ERR_INV_CERT_OBJ); - goto leave; + /* No certificate; check whether a public key has been stored + * using our own scheme. */ + s = find_tlv (buffer, buflen, 0x7f49, &n); + if (!s || !n) + { + log_error ("piv: No public key in 0x%X\n", tag); + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + s2 = find_tlv (buffer, buflen, 0x80, &n2); + if (!s2 || n2 != 1 || !*s2) + { + log_error ("piv: No mechanism for public key in 0x%X\n", tag); + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + *r_mechanism = *s2; } - if (*s == 0x01) + else { - log_error ("piv: gzip compression not yet supported (tag 0x%X)\n", tag); - err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); - goto leave; - } - if (*s) - { - log_error ("piv: invalid CertInfo 0x%02x in 0x%X\n", *s, tag); - err = gpg_error (GPG_ERR_INV_CERT_OBJ); - goto leave; - } - - /* Note: We don't check that the LRC octet has a length of zero as - * required by the specs. */ - - /* Get the cert from the container. */ - s = find_tlv (buffer, buflen, 0x70, &n); - if (!s || !n) - { - err = gpg_error (GPG_ERR_NOT_FOUND); - goto leave; + if (n != 1) + { + log_error ("piv: invalid CertInfo in 0x%X\n", tag); + err = gpg_error (GPG_ERR_INV_CERT_OBJ); + goto leave; + } + if (*s == 0x01) + { + log_error ("piv: gzip compression not yet supported (tag 0x%X)\n", + tag); + err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); + goto leave; + } + if (*s) + { + log_error ("piv: invalid CertInfo 0x%02x in 0x%X\n", *s, tag); + err = gpg_error (GPG_ERR_INV_CERT_OBJ); + goto leave; + } + + /* Note: We don't check that the LRC octet has a length of zero + * as required by the specs. */ + + /* Get the cert from the container. */ + s = find_tlv (buffer, buflen, 0x70, &n); + if (!s || !n) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } } + /* The next is common for certificate and public key. */ if (!(*r_cert = xtrymalloc (n))) { err = gpg_error_from_syserror (); @@ -1095,17 +1114,22 @@ readcert_by_tag (app_t app, unsigned int tag, } -/* Get the keygrip of a key from the certificate stored at TAG. - * Caller must free the string at R_KEYGRIPSTR. */ +/* Get the keygrip in hex format of a key from the certificate stored + * at TAG. Caller must free the string at R_KEYGRIPSTR. */ static gpg_error_t -get_keygrip_by_tag (app_t app, unsigned int tag, char **r_keygripstr) +get_keygrip_by_tag (app_t app, unsigned int tag, + char **r_keygripstr, int *r_got_cert) { gpg_error_t err; unsigned char *certbuf = NULL; size_t certbuflen; + int mechanism; + gcry_sexp_t s_pkey = NULL; ksba_cert_t cert = NULL; + unsigned char grip[KEYGRIP_LEN]; - *r_keygripstr = xtrymalloc (40+1); + *r_got_cert = 0; + *r_keygripstr = xtrymalloc (2*KEYGRIP_LEN+1); if (!r_keygripstr) { err = gpg_error_from_syserror (); @@ -1113,21 +1137,41 @@ get_keygrip_by_tag (app_t app, unsigned int tag, char **r_keygripstr) } /* We need to get the public key from the certificate. */ - err = readcert_by_tag (app, tag, &certbuf, &certbuflen); + err = readcert_by_tag (app, tag, &certbuf, &certbuflen, &mechanism); if (err) goto leave; + if (mechanism) /* Compute keygrip from public key. */ + { + if (mechanism == PIV_ALGORITHM_RSA) + err = genkey_parse_rsa (certbuf, certbuflen, &s_pkey); + else + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + if (err) + goto leave; - /* Compute the keygrip. */ - err = ksba_cert_new (&cert); - if (err) - goto leave; - err = ksba_cert_init_from_mem (cert, certbuf, certbuflen); - if (err) - goto leave; - err = app_help_get_keygrip_string (cert, *r_keygripstr); + if (!gcry_pk_get_keygrip (s_pkey, grip)) + { + log_error ("piv: error computing keygrip\n"); + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + bin2hex (grip, sizeof grip, *r_keygripstr); + } + else /* Compute keygrip from certificate. */ + { + *r_got_cert = 0; + err = ksba_cert_new (&cert); + if (err) + goto leave; + err = ksba_cert_init_from_mem (cert, certbuf, certbuflen); + if (err) + goto leave; + err = app_help_get_keygrip_string (cert, *r_keygripstr); + } leave: + gcry_sexp_release (s_pkey); ksba_cert_release (cert); xfree (certbuf); if (err) @@ -1193,7 +1237,9 @@ static gpg_error_t do_readcert (app_t app, const char *certid, unsigned char **r_cert, size_t *r_certlen) { + gpg_error_t err; data_object_t dobj; + int mechanism; *r_cert = NULL; *r_certlen = 0; @@ -1202,7 +1248,16 @@ do_readcert (app_t app, const char *certid, if (!dobj) return gpg_error (GPG_ERR_INV_ID); - return readcert_by_tag (app, dobj->tag, r_cert, r_certlen); + err = readcert_by_tag (app, dobj->tag, r_cert, r_certlen, &mechanism); + if (!err && mechanism) + { + /* Well, no certificate but a public key - we don't want it. */ + xfree (*r_cert); + *r_cert = NULL; + *r_certlen = 0; + err = gpg_error (GPG_ERR_NOT_FOUND); + } + return err; } @@ -1212,7 +1267,7 @@ do_readcert (app_t app, const char *certid, * result from key generation. If no cached result is available, the * error GPG_ERR_UNSUPPORTED_OPERATION is returned so that the higher * layer can then to get the key by reading the matching certificate. - * On success a canonical encoded S-expression with the public key is + * On success a canonical encoded s-expression with the public key is * stored at (R_PK,R_PKLEN); the caller must release that buffer. On * error R_PK and R_PKLEN are not changed and an error code is * returned. @@ -1224,7 +1279,10 @@ do_readkey (app_t app, int advanced, const char *keyrefstr, gpg_error_t err; data_object_t dobj; int keyref; - struct genkey_result_s *gres; + unsigned char *cert = NULL; + size_t certlen; + int mechanism; + gcry_sexp_t s_pkey = NULL; unsigned char *pk = NULL; size_t pklen; @@ -1234,16 +1292,28 @@ do_readkey (app_t app, int advanced, const char *keyrefstr, err = gpg_error (GPG_ERR_INV_ID); goto leave; } - for (gres = app->app_local->genkey_results; gres; gres = gres->next) - if (gres->keyref == keyref) - break; - if (!gres || !gres->s_pkey) + + err = readcert_by_tag (app, dobj->tag, &cert, &certlen, &mechanism); + if (err) + goto leave; + if (!mechanism) { - err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + /* We got a certificate. Let the upper layer handle the + * extraction of the key. FIXME: It would be better to have a + * shared fucntion to dothis here. */ + err = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } - err = make_canon_sexp (gres->s_pkey, &pk, &pklen); + /* Convert the public key into the expected s-expression. */ + if (mechanism == PIV_ALGORITHM_RSA) + err = genkey_parse_rsa (cert, certlen, &s_pkey); + else + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + if (err) + goto leave; + + err = make_canon_sexp (s_pkey, &pk, &pklen); if (err) goto leave; if (advanced) @@ -1265,7 +1335,9 @@ do_readkey (app_t app, int advanced, const char *keyrefstr, *r_pklen = pklen; leave: + gcry_sexp_release (s_pkey); xfree (pk); + xfree (cert); return err; } @@ -1279,6 +1351,7 @@ get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_algo) gpg_error_t err; unsigned char *certbuf = NULL; size_t certbuflen; + int mechanism; ksba_cert_t cert = NULL; ksba_sexp_t k_pkey = NULL; gcry_sexp_t s_pkey = NULL; @@ -1290,9 +1363,28 @@ get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_algo) *r_algo = 0; - err = readcert_by_tag (app, dobj->tag, &certbuf, &certbuflen); + err = readcert_by_tag (app, dobj->tag, &certbuf, &certbuflen, &mechanism); if (err) goto leave; + if (mechanism) + { + /* A public key was found. That makes it easy. */ + switch (mechanism) + { + case PIV_ALGORITHM_RSA: + case PIV_ALGORITHM_ECC_P256: + case PIV_ALGORITHM_ECC_P384: + *r_algo = mechanism; + break; + + default: + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + log_error ("piv: unknown mechanism %d in public key at %s\n", + mechanism, dobj->keyref); + break; + } + goto leave; + } err = ksba_cert_new (&cert); if (err) @@ -1971,22 +2063,22 @@ does_key_exist (app_t app, data_object_t dobj, int generating, int force) if (found && !force) { - log_error ("piv: %s", _("key already exists\n")); + log_error (_("key already exists\n")); return gpg_error (GPG_ERR_EEXIST); } if (found) - log_info ("piv: %s", _("existing key will be replaced\n")); + log_info (_("existing key will be replaced\n")); else if (generating) - log_info ("piv: %s", _("generating new key\n")); + log_info (_("generating new key\n")); else - log_info ("piv: %s", _("writing new key\n")); + log_info (_("writing new key\n")); return 0; } /* Parse an RSA response object, consisting of the content of tag - * 0x7f49, into a gcrypt s-expresstion object and store that R_SEXP. + * 0x7f49, into a gcrypt s-expression object and store that R_SEXP. * On error NULL is stored at R_SEXP. */ static gpg_error_t genkey_parse_rsa (const unsigned char *data, size_t datalen, @@ -2096,8 +2188,6 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keyrefstr, const char *keytype, size_t tmpllen; const unsigned char *keydata; size_t keydatalen; - gcry_sexp_t s_pkey = NULL; - struct genkey_result_s *gres; (void)ctrl; (void)createtime; @@ -2151,7 +2241,7 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keyrefstr, const char *keytype, if (err) { log_error (_("generating key failed\n")); - return gpg_error (GPG_ERR_CARD); + return err; } { @@ -2171,36 +2261,20 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keyrefstr, const char *keytype, goto leave; } - if (mechanism == PIV_ALGORITHM_RSA) - err = genkey_parse_rsa (keydata, keydatalen, &s_pkey); - else - err = gpg_error (GPG_ERR_BUG); + tmpl[0] = mechanism; + flush_cached_data (app, dobj->tag); + err = put_data (app->slot, dobj->tag, + (int)0x80, (size_t)1, tmpl, + (int)0x7f49, (size_t)keydatalen, keydata, + (int)0, (size_t)0, NULL); if (err) - goto leave; - - for (gres = app->app_local->genkey_results; gres; gres = gres->next) - if (gres->keyref == keyref) - break; - if (!gres) { - gres = xtrycalloc (1, sizeof *gres); - if (!gres) - { - err = gpg_error_from_syserror (); - goto leave; - } - gres->keyref = keyref; - gres->next = app->app_local->genkey_results; - app->app_local->genkey_results = gres; + log_error ("piv: failed to write key to the cert DO %s: %s\n", + dobj->keyref, gpg_strerror (err)); + goto leave; } - else - gcry_sexp_release (gres->s_pkey); - gres->s_pkey = s_pkey; - s_pkey = NULL; - leave: - gcry_sexp_release (s_pkey); xfree (buffer); return err; } @@ -2230,7 +2304,6 @@ do_writecert (app_t app, ctrl_t ctrl, /* FIXME: Check that the authentication has already been done. */ flush_cached_data (app, dobj->tag); - err = put_data (app->slot, dobj->tag, (int)0x70, (size_t)certlen, cert,/* Certificate */ (int)0x71, (size_t)1, "", /* No compress */ diff --git a/scd/command.c b/scd/command.c index 8fd6ba55d..127fb5deb 100644 --- a/scd/command.c +++ b/scd/command.c @@ -544,7 +544,8 @@ cmd_readkey (assuan_context_t ctx, char *line) goto leave; } - if (gpg_err_code (rc) != GPG_ERR_UNSUPPORTED_OPERATION) + if (gpg_err_code (rc) != GPG_ERR_UNSUPPORTED_OPERATION + && gpg_err_code (rc) != GPG_ERR_NOT_FOUND) log_error ("app_readkey failed: %s\n", gpg_strerror (rc)); else { From df6ba6dfd235fddb7645bc16573da1a6a7e6b49d Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 7 Feb 2019 16:28:03 +0100 Subject: [PATCH 158/235] card: Fix a NULL-ptr deref in key listings. * tools/card-tool-keys.c (get_matching_keys): Fix segv. * tools/gpg-card-tool.c (main): Init info. Signed-off-by: Werner Koch --- tools/card-tool-keys.c | 9 ++++++--- tools/gpg-card-tool.c | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/tools/card-tool-keys.c b/tools/card-tool-keys.c index e9edf9818..4e057ad94 100644 --- a/tools/card-tool-keys.c +++ b/tools/card-tool-keys.c @@ -214,9 +214,12 @@ get_matching_keys (const unsigned char *keygrip, int protocol, err = get_matching_keys (keygrip, GNUPG_PROTOCOL_OPENPGP, &kb); if (!err || gpg_err_code (err) == GPG_ERR_NO_PUBKEY) { - *keyblock_tail = kb; - keyblock_tail = &kb->next; - kb = NULL; + if (!err) + { + *keyblock_tail = kb; + keyblock_tail = &kb->next; + kb = NULL; + } err = get_matching_keys (keygrip, GNUPG_PROTOCOL_CMS, &kb); if (!err) { diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index 917013247..0dbf2b224 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -309,7 +309,7 @@ main (int argc, char **argv) } else { - struct card_info_s info_buffer; + struct card_info_s info_buffer = { 0 }; card_info_t info = &info_buffer; err = 0; @@ -2968,7 +2968,7 @@ interactive_loop (void) int redisplay = 1; /* Whether to redisplay the main info. */ int allow_admin = 0; /* Whether admin commands are allowed. */ char *help_arg = NULL; /* Argument of the HELP command. */ - struct card_info_s info_buffer; + struct card_info_s info_buffer = { 0 }; card_info_t info = &info_buffer; char *p; int i; From b79bc877f2ad4d08e7de377cf4bf616b981b3c5f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 7 Feb 2019 20:28:43 +0100 Subject: [PATCH 159/235] card: Print the used algorithm of all keys. * tools/card-call-scd.c (scd_readkey): New. * tools/card-tool-misc.c (pubkey_algo_string): New. * tools/gpg-card-tool.c (list_one_kinfo): Print the algo. -- It is convenient to see the actual algorithm of keys even if no certificate has yet been created. Signed-off-by: Werner Koch --- tools/card-call-scd.c | 39 ++++++++++++++++++++++++++++ tools/card-tool-misc.c | 58 ++++++++++++++++++++++++++++++++++++++++++ tools/card-tool.h | 2 ++ tools/gpg-card-tool.c | 9 +++++++ 4 files changed, 108 insertions(+) diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c index 7add56daf..0d6dabf1b 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -1298,6 +1298,45 @@ scd_readcert (const char *certidstr, void **r_buf, size_t *r_buflen) } + +/* Send a READKEY command to the SCdaemon. On success a new + * s-expression is stored at R_RESULT. */ +gpg_error_t +scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + unsigned char *buf; + size_t len, buflen; + + *r_result = NULL; + err = start_agent (0); + if (err) + return err; + + init_membuf (&data, 1024); + snprintf (line, DIM(line), "SCD READKEY %s", keyrefstr); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + NULL, NULL, + NULL, NULL); + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + buf = get_membuf (&data, &buflen); + if (!buf) + return gpg_error_from_syserror (); + + err = gcry_sexp_new (r_result, buf, buflen, 0); + xfree (buf); + + return err; +} + + /* Callback function for card_cardlist. */ static gpg_error_t diff --git a/tools/card-tool-misc.c b/tools/card-tool-misc.c index 06fcb6705..5e0461cb8 100644 --- a/tools/card-tool-misc.c +++ b/tools/card-tool-misc.c @@ -77,3 +77,61 @@ hex_to_buffer (const char *string, size_t *r_length) *r_length = n; return buffer; } + + + +/* Given the public key S_PKEY, return a new buffer with a descriptive + * string for its algorithm. This function always returns a string. */ +char * +pubkey_algo_string (gcry_sexp_t s_pkey) +{ + const char *prefix; + gcry_sexp_t l1; + char *algoname; + int algo; + char *result; + + l1 = gcry_sexp_find_token (s_pkey, "public-key", 0); + if (!l1) + return xstrdup ("E_no_key"); + { + gcry_sexp_t l_tmp = gcry_sexp_cadr (l1); + gcry_sexp_release (l1); + l1 = l_tmp; + } + algoname = gcry_sexp_nth_string (l1, 0); + gcry_sexp_release (l1); + if (!algoname) + return xstrdup ("E_no_algo"); + + algo = gcry_pk_map_name (algoname); + switch (algo) + { + case GCRY_PK_RSA: prefix = "rsa"; break; + case GCRY_PK_ELG: prefix = "elg"; break; + case GCRY_PK_DSA: prefix = "dsa"; break; + case GCRY_PK_ECC: prefix = ""; break; + default: prefix = NULL; break; + } + + if (prefix && *prefix) + result = xasprintf ("%s%u", prefix, gcry_pk_get_nbits (s_pkey)); + else if (prefix) + { + const char *curve = gcry_pk_get_curve (s_pkey, 0, NULL); + const char *name = openpgp_oid_to_curve + (openpgp_curve_to_oid (curve, NULL), 0); + + if (name) + result = xstrdup (name); + else if (curve) + result = xasprintf ("X_%s", curve); + else + result = xstrdup ("E_unknown"); + } + else + result = xasprintf ("X_algo_%d", algo); + + xfree (algoname); + return result; +} diff --git a/tools/card-tool.h b/tools/card-tool.h index 2707b3e8f..c121e88ef 100644 --- a/tools/card-tool.h +++ b/tools/card-tool.h @@ -192,6 +192,7 @@ gpg_error_t test_get_matching_keys (const char *hexgrip); /*-- card-tool-misc.c --*/ key_info_t find_kinfo (card_info_t info, const char *keyref); void *hex_to_buffer (const char *string, size_t *r_length); +char *pubkey_algo_string (gcry_sexp_t s_pkey); /*-- card-call-scd.c --*/ @@ -211,6 +212,7 @@ gpg_error_t scd_genkey (int keyno, int force, u32 *createtime); gpg_error_t scd_serialno (char **r_serialno, const char *demand); gpg_error_t scd_readcert (const char *certidstr, void **r_buf, size_t *r_buflen); +gpg_error_t scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result); gpg_error_t scd_cardlist (strlist_t *result); gpg_error_t scd_change_pin (const char *pinref, int reset_mode); gpg_error_t scd_checkpin (const char *serialno); diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index 0dbf2b224..71ce13f1b 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -620,6 +620,7 @@ list_one_kinfo (key_info_t firstkinfo, key_info_t kinfo, estream_t fp) userid_t uid; key_info_t ki; const char *s; + gcry_sexp_t s_pkey; if (firstkinfo && kinfo) { @@ -630,6 +631,14 @@ list_one_kinfo (key_info_t firstkinfo, key_info_t kinfo, estream_t fp) goto leave; } print_keygrip (fp, kinfo->grip); + if (!scd_readkey (kinfo->keyref, &s_pkey)) + { + char *tmp = pubkey_algo_string (s_pkey); + tty_fprintf (fp, " algorithm ..: %s\n", tmp); + xfree (tmp); + gcry_sexp_release (s_pkey); + s_pkey = NULL; + } if (kinfo->fprlen && kinfo->created) { From e2f18023b3b3b7e55b35218f65e37448d1011172 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 8 Feb 2019 09:32:55 +0100 Subject: [PATCH 160/235] common: New functions get_option_value and ascii_strupr. * common/server-help.c (get_option_value): New. * common/stringhelp.c (ascii_strupr): New. Signed-off-by: Werner Koch --- common/server-help.c | 50 +++++++++++++++++++++++++++++++++++++++++++- common/server-help.h | 8 +++++++ common/stringhelp.c | 13 ++++++++++++ common/stringhelp.h | 1 + 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/common/server-help.c b/common/server-help.c index 53a888a86..e5a69e02d 100644 --- a/common/server-help.c +++ b/common/server-help.c @@ -30,8 +30,22 @@ #include #include -#include "server-help.h" #include "util.h" +#include "server-help.h" + + +static GPGRT_INLINE gpg_error_t +my_error (int e) +{ + return gpg_err_make (default_errsource, (e)); +} + +static GPGRT_INLINE gpg_error_t +my_error_from_syserror (void) +{ + return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); +} + /* Skip over options in LINE. @@ -114,6 +128,40 @@ has_option_name (const char *line, const char *name) } +/* Parse an option with the format "--NAME=VALUE" which must occur in + * LINE before a double-dash. LINE is written to but not modified by + * this function. If the option is found and has a value the value is + * stored as a malloced string at R_VALUE. If the option was not + * found or an error occurred NULL is stored there. Note that + * currently the value must be a string without any space; we may + * eventually update this function to allow for a quoted value. */ +gpg_error_t +get_option_value (char *line, const char *name, char **r_value) +{ + char *p, *pend; + int c; + + *r_value = NULL; + + p = (char*)has_option_name (line, name); + if (!p || p >= skip_options (line)) + return 0; + + if (*p != '=' || !p[1] || spacep (p+1)) + return my_error (GPG_ERR_INV_ARG); + p++; + for (pend = p; *pend && !spacep (pend); pend++) + ; + c = *pend; + *pend = 0; + *r_value = xtrystrdup (p); + *pend = c; + if (!p) + return my_error_from_syserror (); + return 0; +} + + /* Return a pointer to the argument of the option with NAME. If such an option is not given, NULL is returned. */ char * diff --git a/common/server-help.h b/common/server-help.h index 9e3d7ada1..9d2f4cfba 100644 --- a/common/server-help.h +++ b/common/server-help.h @@ -55,6 +55,14 @@ int has_leading_option (const char *line, const char *name); or a space. */ const char *has_option_name (const char *line, const char *name); +/* Same as has_option_name but ignores all options after a "--" and + * does not return a const char ptr. */ +char *has_leading_option_name (char *line, const char *name); + +/* Parse an option with the format "--NAME=VALUE" and return the value + * as a malloced string. */ +gpg_error_t get_option_value (char *line, const char *name, char **r_value); + /* Return a pointer to the argument of the option with NAME. If such an option is not given, NULL is returned. */ char *option_value (const char *line, const char *name); diff --git a/common/stringhelp.c b/common/stringhelp.c index 751e5711f..dd1711684 100644 --- a/common/stringhelp.c +++ b/common/stringhelp.c @@ -810,6 +810,19 @@ ascii_strlwr (char *s) return s; } +/* Upcase all ASCII characters in S. */ +char * +ascii_strupr (char *s) +{ + char *p = s; + + for (p=s; *p; p++ ) + if (isascii (*p) && *p >= 'a' && *p <= 'z') + *p &= ~0x20; + + return s; +} + int ascii_strcasecmp( const char *a, const char *b ) { diff --git a/common/stringhelp.h b/common/stringhelp.h index 5b07af95e..7df6c7656 100644 --- a/common/stringhelp.h +++ b/common/stringhelp.h @@ -76,6 +76,7 @@ int ascii_islower (int c); int ascii_toupper (int c); int ascii_tolower (int c); char *ascii_strlwr (char *s); +char *ascii_strupr (char *s); int ascii_strcasecmp( const char *a, const char *b ); int ascii_strncasecmp (const char *a, const char *b, size_t n); int ascii_memcasecmp( const void *a, const void *b, size_t n ); From b349adc5c0d00d2fc405a45bd078f1580b5610cc Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 8 Feb 2019 11:53:34 +0100 Subject: [PATCH 161/235] scd: Allow generating ECC curves on PIV cards. * scd/app-piv.c (genkey_parse_ecc): New. (get_keygrip_by_tag): Call that one. (do_readkey): Call that one. * scd/command.c (cmd_genkey): Add option --algo. Signed-off-by: Werner Koch --- scd/app-piv.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ scd/command.c | 42 ++++++++++++++++++++++++---------------- 2 files changed, 79 insertions(+), 16 deletions(-) diff --git a/scd/app-piv.c b/scd/app-piv.c index f4eb918ba..4387b3aef 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -178,6 +178,8 @@ static gpg_error_t get_keygrip_by_tag (app_t app, unsigned int tag, char **r_keygripstr, int *got_cert); static gpg_error_t genkey_parse_rsa (const unsigned char *data, size_t datalen, gcry_sexp_t *r_sexp); +static gpg_error_t genkey_parse_ecc (const unsigned char *data, size_t datalen, + int mechanism, gcry_sexp_t *r_sexp); @@ -1144,6 +1146,9 @@ get_keygrip_by_tag (app_t app, unsigned int tag, { if (mechanism == PIV_ALGORITHM_RSA) err = genkey_parse_rsa (certbuf, certbuflen, &s_pkey); + else if (mechanism == PIV_ALGORITHM_ECC_P256 + || mechanism == PIV_ALGORITHM_ECC_P384) + err = genkey_parse_ecc (certbuf, certbuflen, mechanism, &s_pkey); else err = gpg_error (GPG_ERR_PUBKEY_ALGO); if (err) @@ -1308,6 +1313,9 @@ do_readkey (app_t app, int advanced, const char *keyrefstr, /* Convert the public key into the expected s-expression. */ if (mechanism == PIV_ALGORITHM_RSA) err = genkey_parse_rsa (cert, certlen, &s_pkey); + else if (mechanism == PIV_ALGORITHM_ECC_P256 + || mechanism == PIV_ALGORITHM_ECC_P384) + err = genkey_parse_ecc (cert, certlen, mechanism, &s_pkey); else err = gpg_error (GPG_ERR_PUBKEY_ALGO); if (err) @@ -2155,6 +2163,48 @@ genkey_parse_rsa (const unsigned char *data, size_t datalen, } +/* Parse an ECC response object, consisting of the content of tag + * 0x7f49, into a gcrypt s-expression object and store that R_SEXP. + * On error NULL is stored at R_SEXP. MECHANISM specifies the + * curve. */ +static gpg_error_t +genkey_parse_ecc (const unsigned char *data, size_t datalen, int mechanism, + gcry_sexp_t *r_sexp) +{ + gpg_error_t err; + const unsigned char *ecc_q; + size_t ecc_qlen; + const char *curve; + + *r_sexp = NULL; + + ecc_q = find_tlv (data, datalen, 0x0086, &ecc_qlen); + if (!ecc_q) + { + log_error (_("response does not contain the EC public key\n")); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + + if (mechanism == PIV_ALGORITHM_ECC_P256) + curve = "nistp256"; + else if (mechanism == PIV_ALGORITHM_ECC_P384) + curve = "nistp384"; + else + { + err = gpg_error (GPG_ERR_BUG); /* Call with wrong parameters. */ + goto leave; + } + + + err = gcry_sexp_build (r_sexp, NULL, "(public-key(ecc(curve%s)(q%b)))", + curve, (int)ecc_qlen, ecc_q); + + leave: + return err; +} + + /* Create a new keypair for KEYREF. If KEYTYPE is NULL a default * keytype is selected, else it may be one of the strings: * "rsa2048", "nistp256, or "nistp384". @@ -2303,6 +2353,9 @@ do_writecert (app_t app, ctrl_t ctrl, /* FIXME: Check that the authentication has already been done. */ + /* FIXME: Check that the public key parameters from the certificate + * match an already stored key. */ + flush_cached_data (app, dobj->tag); err = put_data (app->slot, dobj->tag, (int)0x70, (size_t)certlen, cert,/* Certificate */ diff --git a/scd/command.c b/scd/command.c index 127fb5deb..237faf093 100644 --- a/scd/command.c +++ b/scd/command.c @@ -1126,10 +1126,10 @@ cmd_writekey (assuan_context_t ctx, char *line) static const char hlp_genkey[] = - "GENKEY [--force] [--timestamp=] \n" + "GENKEY [--force] [--timestamp=] \n" "\n" - "Generate a key on-card identified by NO, which is application\n" - "specific. Return values are application specific. For OpenPGP\n" + "Generate a key on-card identified by , which is application\n" + "specific. Return values are also application specific. For OpenPGP\n" "cards 3 status lines are returned:\n" "\n" " S KEY-FPR \n" @@ -1154,10 +1154,12 @@ static gpg_error_t cmd_genkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); - int rc; - char *keyno; + gpg_error_t err; + char *keyref_buffer = NULL; + char *keyref; int force; const char *s; + char *opt_algo = NULL; time_t timestamp; force = has_option (line, "--force"); @@ -1173,30 +1175,38 @@ cmd_genkey (assuan_context_t ctx, char *line) else timestamp = 0; + err = get_option_value (line, "--algo", &opt_algo); + if (err) + goto leave; line = skip_options (line); if (!*line) return set_error (GPG_ERR_ASS_PARAMETER, "no key number given"); - keyno = line; + keyref = line; while (*line && !spacep (line)) line++; *line = 0; - if ((rc = open_card (ctrl))) - return rc; + if ((err = open_card (ctrl))) + goto leave; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - keyno = xtrystrdup (keyno); - if (!keyno) - return out_of_core (); - rc = app_genkey (ctrl->app_ctx, ctrl, keyno, NULL, - force? APP_GENKEY_FLAG_FORCE : 0, - timestamp, pin_cb, ctx); - xfree (keyno); + keyref = keyref_buffer = xtrystrdup (keyref); + if (!keyref) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = app_genkey (ctrl->app_ctx, ctrl, keyref, opt_algo, + force? APP_GENKEY_FLAG_FORCE : 0, + timestamp, pin_cb, ctx); - return rc; + leave: + xfree (keyref_buffer); + xfree (opt_algo); + return err; } From a1cb4a940f308ba21ecc002b044efccf0c547784 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 8 Feb 2019 11:58:27 +0100 Subject: [PATCH 162/235] card: Make "generate" work for PIV cards. * tools/card-call-scd.c (scd_genkey_cb): Make createtime optional. (scd_genkey_cb): Ditto. Add arg algo. * tools/gpg-card-tool.c (cmd_generate): Add options and factor card specific code out to ... (generate_openpgp, generate_generic): new functions. -- This patch keeps the interactive OpenPGP mode but adds a pure command line mode for other cards; in particular PIV cards. What we still need to do is: a) Add an interactive mode for PIV cards b) Add a command line mode for OpenPGP cards. Signed-off-by: Werner Koch --- tools/card-call-scd.c | 15 +++-- tools/card-tool.h | 3 +- tools/gpg-card-tool.c | 148 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 141 insertions(+), 25 deletions(-) diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c index 0d6dabf1b..97fb6d9f1 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -202,7 +202,7 @@ app_type_string (app_type_t app_type) case APP_TYPE_OPENPGP: result = "OpenPGP"; break; case APP_TYPE_NKS: result = "NetKey"; break; case APP_TYPE_DINSIG: result = "DINSIG"; break; - case APP_TYPE_P15: result = "PKCS#15"; break; + case APP_TYPE_P15: result = "P15"; break; case APP_TYPE_GELDKARTE: result = "Geldkarte"; break; case APP_TYPE_SC_HSM: result = "SC-HSM"; break; case APP_TYPE_PIV: result = "PIV"; break; @@ -1174,7 +1174,8 @@ scd_genkey_cb (void *opaque, const char *line) if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen)) { - *createtime = (u32)strtoul (line, NULL, 10); + if (createtime) + *createtime = (u32)strtoul (line, NULL, 10); } else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen)) { @@ -1190,7 +1191,7 @@ scd_genkey_cb (void *opaque, const char *line) * SCDEAMON. On success, creation time is stored back to * CREATETIME. */ gpg_error_t -scd_genkey (int keyno, int force, u32 *createtime) +scd_genkey (const char *keyref, int force, const char *algo, u32 *createtime) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; @@ -1203,15 +1204,17 @@ scd_genkey (int keyno, int force, u32 *createtime) if (err) return err; - if (*createtime) + if (createtime && *createtime) epoch2isotime (tbuf, *createtime); else *tbuf = 0; - snprintf (line, sizeof line, "SCD GENKEY %s%s %s %d", + snprintf (line, sizeof line, "SCD GENKEY %s%s %s %s%s -- %s", *tbuf? "--timestamp=":"", tbuf, force? "--force":"", - keyno); + algo? "--algo=":"", + algo? algo:"", + keyref); dfltparm.ctx = agent_ctx; err = assuan_transact (agent_ctx, line, diff --git a/tools/card-tool.h b/tools/card-tool.h index c121e88ef..9daf7e498 100644 --- a/tools/card-tool.h +++ b/tools/card-tool.h @@ -208,7 +208,8 @@ gpg_error_t scd_writecert (const char *certidstr, const unsigned char *certdata, size_t certdatalen); gpg_error_t scd_writekey (int keyno, const unsigned char *keydata, size_t keydatalen); -gpg_error_t scd_genkey (int keyno, int force, u32 *createtime); +gpg_error_t scd_genkey (const char *keyref, int force, const char *algo, + u32 *createtime); gpg_error_t scd_serialno (char **r_serialno, const char *demand); gpg_error_t scd_readcert (const char *certidstr, void **r_buf, size_t *r_buflen); diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index 71ce13f1b..93153b12f 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -1722,7 +1722,8 @@ cmd_forcesig (card_info_t info) } -/* Helper for cmd_generate. Noe that either 0 or 1 is stored at + +/* Helper for cmd_generate_openpgp. Noe that either 0 or 1 is stored at * FORCED_CHV1. */ static gpg_error_t check_pin_for_key_operation (card_info_t info, int *forced_chv1) @@ -1754,7 +1755,7 @@ check_pin_for_key_operation (card_info_t info, int *forced_chv1) } -/* Helper for cmd_generate. */ +/* Helper for cmd_generate_openpgp. */ static void restore_forced_chv1 (int *forced_chv1) { @@ -1775,8 +1776,9 @@ restore_forced_chv1 (int *forced_chv1) } +/* Implementation of cmd_generate for OpenPGP cards. */ static gpg_error_t -cmd_generate (card_info_t info) +generate_openpgp (card_info_t info) { gpg_error_t err; int forced_chv1 = -1; @@ -1784,18 +1786,6 @@ cmd_generate (card_info_t info) char *answer = NULL; key_info_t kinfo1, kinfo2, kinfo3; - if (!info) - return print_help - ("GENERATE\n\n" - "Menu to generate a new keys.", - APP_TYPE_OPENPGP, 0); - - if (info->apptype != APP_TYPE_OPENPGP) - { - log_info ("Note: This is an OpenPGP only command.\n"); - return gpg_error (GPG_ERR_NOT_SUPPORTED); - } - if (info->extcap.ki) { xfree (answer); @@ -1811,7 +1801,6 @@ cmd_generate (card_info_t info) else want_backup = 0; - kinfo1 = find_kinfo (info, "OPENPGP.1"); kinfo2 = find_kinfo (info, "OPENPGP.2"); kinfo3 = find_kinfo (info, "OPENPGP.3"); @@ -1860,6 +1849,7 @@ cmd_generate (card_info_t info) * gpg. We might also first create the keys on the card and then * tell gpg to use them to create the OpenPGP keyblock. */ /* generate_keypair (ctrl, 1, NULL, info.serialno, want_backup); */ + (void)want_backup; err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); leave: @@ -1869,6 +1859,126 @@ cmd_generate (card_info_t info) } +/* Generic implementation of cmd_generate. */ +static gpg_error_t +generate_generic (card_info_t info, const char *keyref, int force, + const char *algo) +{ + gpg_error_t err; + + (void)info; + + err = scd_genkey (keyref, force, algo, NULL); + + return err; +} + + +static gpg_error_t +cmd_generate (card_info_t info, char *argstr) +{ + static char * const valid_algos[] = + { "rsa2048", "rsa3072", "rsa4096", + "nistp256", "nistp384", "nistp521", + "ed25519", "cv25519", + NULL + }; + gpg_error_t err; + int opt_force; + char *opt_algo = NULL; /* Malloced. */ + char *keyref_buffer = NULL; /* Malloced. */ + char *keyref; /* Points into argstr or keyref_buffer. */ + int i; + + if (!info) + return print_help + ("GENERATE [--force] [--algo=ALGO] KEYREF\n\n" + "Create a new key on a card. For OpenPGP cards are menu is used\n" + "and KEYREF is ignored. Use --force to overwrite an existing key.", + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); + + if (opt.interactive || opt.verbose) + log_info (_("%s card no. %s detected\n"), + app_type_string (info->apptype), + info->dispserialno? info->dispserialno : info->serialno); + + opt_force = has_leading_option (argstr, "--force"); + err = get_option_value (argstr, "--algo", &opt_algo); + if (err) + goto leave; + argstr = skip_options (argstr); + + keyref = argstr; + if ((argstr = strchr (keyref, ' '))) + { + *argstr++ = 0; + trim_spaces (keyref); + trim_spaces (argstr); + } + else /* Let argstr point to an empty string. */ + argstr = keyref + strlen (keyref); + + if (!*keyref) + keyref = NULL; + + if (*argstr) + { + /* Extra arguments found. */ + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + if (opt_algo) + { + for (i=0; valid_algos[i]; i++) + if (!strcmp (valid_algos[i], opt_algo)) + break; + if (!valid_algos[i]) + { + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + log_info ("Invalid algorithm '%s' given. Use one:\n", opt_algo); + for (i=0; valid_algos[i]; i++) + if (!(i%5)) + log_info (" %s%s", valid_algos[i], valid_algos[i+1]?",":"."); + else + log_printf (" %s%s", valid_algos[i], valid_algos[i+1]?",":"."); + log_info ("Note that the card may not support all of them.\n"); + goto leave; + } + } + + /* Upcase the keyref; if it misses the cardtype, prepend it. */ + if (keyref) + { + if (!strchr (keyref, '.')) + keyref_buffer = xstrconcat (app_type_string (info->apptype), ".", + keyref, NULL); + else + keyref_buffer = xstrdup (keyref); + ascii_strupr (keyref_buffer); + keyref = keyref_buffer; + } + + /* Divert to dedicated functions. */ + if (info->apptype == APP_TYPE_OPENPGP) + { + if (opt_force || opt_algo || keyref) + log_info ("Note: Options are ignored for OpenPGP cards.\n"); + err = generate_openpgp (info); + } + else if (!keyref) + err = gpg_error (GPG_ERR_INV_ID); + else + err = generate_generic (info, keyref, opt_force, opt_algo); + + leave: + xfree (opt_algo); + xfree (keyref_buffer); + return err; +} + + + /* Sub-menu to change a PIN. The presented options may depend on the * the ALLOW_ADMIN flag. */ static gpg_error_t @@ -2572,6 +2682,8 @@ ask_card_keyattr (int keyno, const struct key_attr *current, curve = current->curve; } + (void)curve; + (void)algo; err = GPG_ERR_NOT_IMPLEMENTED; goto leave; /* FIXME: We need to mve the ask_cure code out to common or @@ -2929,7 +3041,7 @@ dispatch_command (card_info_t info, const char *orig_command) case cmdWRITECERT: err = cmd_writecert (info, argstr); break; case cmdREADCERT: err = cmd_readcert (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; - case cmdGENERATE: err = cmd_generate (info); break; + case cmdGENERATE: err = cmd_generate (info, argstr); break; case cmdPASSWD: err = cmd_passwd (info, 1, argstr); break; case cmdUNBLOCK: err = cmd_unblock (info); break; case cmdFACTORYRESET: err = cmd_factoryreset (info); break; @@ -3195,7 +3307,7 @@ interactive_loop (void) case cmdWRITECERT: err = cmd_writecert (info, argstr); break; case cmdREADCERT: err = cmd_readcert (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; - case cmdGENERATE: err = cmd_generate (info); break; + case cmdGENERATE: err = cmd_generate (info, argstr); break; case cmdPASSWD: err = cmd_passwd (info, allow_admin, argstr); break; case cmdUNBLOCK: err = cmd_unblock (info); break; case cmdFACTORYRESET: From 03bf8e967adb2dd13329ba1089deb419d49e55c0 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 8 Feb 2019 12:10:45 +0100 Subject: [PATCH 163/235] common: Provide function to get public key algo names in our format. * tools/card-tool-misc.c (pubkey_algo_string): Move to ... * common/sexputil.c (pubkey_algo_string): here. -- The new gpg format for public key algorithms is useful at other places as well. Thus we make this new function available. Note that the code we use in gpg is not based on s-expressions and thus a new function was required. Signed-off-by: Werner Koch --- common/sexputil.c | 58 ++++++++++++++++++++++++++++++++++++++++++ common/util.h | 1 + tools/card-tool-misc.c | 58 ------------------------------------------ tools/card-tool.h | 2 -- 4 files changed, 59 insertions(+), 60 deletions(-) diff --git a/common/sexputil.c b/common/sexputil.c index 02e52d0ed..d3020e169 100644 --- a/common/sexputil.c +++ b/common/sexputil.c @@ -577,3 +577,61 @@ get_pk_algo_from_canon_sexp (const unsigned char *keydata, size_t keydatalen) gcry_sexp_release (sexp); return algo; } + + +/* Given the public key S_PKEY, return a new buffer with a descriptive + * string for its algorithm. This function may return NULL on memory + * error. */ +char * +pubkey_algo_string (gcry_sexp_t s_pkey) +{ + const char *prefix; + gcry_sexp_t l1; + char *algoname; + int algo; + char *result; + + l1 = gcry_sexp_find_token (s_pkey, "public-key", 0); + if (!l1) + return xtrystrdup ("E_no_key"); + { + gcry_sexp_t l_tmp = gcry_sexp_cadr (l1); + gcry_sexp_release (l1); + l1 = l_tmp; + } + algoname = gcry_sexp_nth_string (l1, 0); + gcry_sexp_release (l1); + if (!algoname) + return xtrystrdup ("E_no_algo"); + + algo = gcry_pk_map_name (algoname); + switch (algo) + { + case GCRY_PK_RSA: prefix = "rsa"; break; + case GCRY_PK_ELG: prefix = "elg"; break; + case GCRY_PK_DSA: prefix = "dsa"; break; + case GCRY_PK_ECC: prefix = ""; break; + default: prefix = NULL; break; + } + + if (prefix && *prefix) + result = xtryasprintf ("%s%u", prefix, gcry_pk_get_nbits (s_pkey)); + else if (prefix) + { + const char *curve = gcry_pk_get_curve (s_pkey, 0, NULL); + const char *name = openpgp_oid_to_curve + (openpgp_curve_to_oid (curve, NULL), 0); + + if (name) + result = xtrystrdup (name); + else if (curve) + result = xtryasprintf ("X_%s", curve); + else + result = xtrystrdup ("E_unknown"); + } + else + result = xtryasprintf ("X_algo_%d", algo); + + xfree (algoname); + return result; +} diff --git a/common/util.h b/common/util.h index 863f9e36f..d5bb225a7 100644 --- a/common/util.h +++ b/common/util.h @@ -189,6 +189,7 @@ gpg_error_t get_rsa_pk_from_canon_sexp (const unsigned char *keydata, int get_pk_algo_from_key (gcry_sexp_t key); int get_pk_algo_from_canon_sexp (const unsigned char *keydata, size_t keydatalen); +char *pubkey_algo_string (gcry_sexp_t s_pkey); /*-- convert.c --*/ int hex2bin (const char *string, void *buffer, size_t length); diff --git a/tools/card-tool-misc.c b/tools/card-tool-misc.c index 5e0461cb8..06fcb6705 100644 --- a/tools/card-tool-misc.c +++ b/tools/card-tool-misc.c @@ -77,61 +77,3 @@ hex_to_buffer (const char *string, size_t *r_length) *r_length = n; return buffer; } - - - -/* Given the public key S_PKEY, return a new buffer with a descriptive - * string for its algorithm. This function always returns a string. */ -char * -pubkey_algo_string (gcry_sexp_t s_pkey) -{ - const char *prefix; - gcry_sexp_t l1; - char *algoname; - int algo; - char *result; - - l1 = gcry_sexp_find_token (s_pkey, "public-key", 0); - if (!l1) - return xstrdup ("E_no_key"); - { - gcry_sexp_t l_tmp = gcry_sexp_cadr (l1); - gcry_sexp_release (l1); - l1 = l_tmp; - } - algoname = gcry_sexp_nth_string (l1, 0); - gcry_sexp_release (l1); - if (!algoname) - return xstrdup ("E_no_algo"); - - algo = gcry_pk_map_name (algoname); - switch (algo) - { - case GCRY_PK_RSA: prefix = "rsa"; break; - case GCRY_PK_ELG: prefix = "elg"; break; - case GCRY_PK_DSA: prefix = "dsa"; break; - case GCRY_PK_ECC: prefix = ""; break; - default: prefix = NULL; break; - } - - if (prefix && *prefix) - result = xasprintf ("%s%u", prefix, gcry_pk_get_nbits (s_pkey)); - else if (prefix) - { - const char *curve = gcry_pk_get_curve (s_pkey, 0, NULL); - const char *name = openpgp_oid_to_curve - (openpgp_curve_to_oid (curve, NULL), 0); - - if (name) - result = xstrdup (name); - else if (curve) - result = xasprintf ("X_%s", curve); - else - result = xstrdup ("E_unknown"); - } - else - result = xasprintf ("X_algo_%d", algo); - - xfree (algoname); - return result; -} diff --git a/tools/card-tool.h b/tools/card-tool.h index 9daf7e498..f49f253ce 100644 --- a/tools/card-tool.h +++ b/tools/card-tool.h @@ -192,8 +192,6 @@ gpg_error_t test_get_matching_keys (const char *hexgrip); /*-- card-tool-misc.c --*/ key_info_t find_kinfo (card_info_t info, const char *keyref); void *hex_to_buffer (const char *string, size_t *r_length); -char *pubkey_algo_string (gcry_sexp_t s_pkey); - /*-- card-call-scd.c --*/ void release_card_info (card_info_t info); From 0328976c94adc2c518c7a7763a35319a0000c5e2 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 8 Feb 2019 12:35:26 +0100 Subject: [PATCH 164/235] sm: In --gen-key with "key from card" show also the algorithm. * sm/certreqgen-ui.c (gpgsm_gencertreq_tty): Get and show algo. -- This extends the prompt to show something like Serial number of the card: FF020001008A77F6 Available keys: (1) 4130F84FA3704F4645924AEC3FFA48AD26D33656 PIV.9A nistp384 (2) AB2988FB8C227BCD5175BF92F66AA3A95AE83214 PIV.9E rsa2048 (3) DB7DDAEAA88534BA45CCD7A9B761425103EA2090 PIV.9C rsa2048 (4) BABB48C3D80ACCF9839F101DF2910966C8B988DF PIV.9D nistp256 Your selection? 1 Having the algorithm here is helpful in particular because right now we support only RSA with X.509. Take care: PIV card based certificate creation does not yet work. Signed-off-by: Werner Koch --- sm/certreqgen-ui.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/sm/certreqgen-ui.c b/sm/certreqgen-ui.c index f64baf365..70e5739e8 100644 --- a/sm/certreqgen-ui.c +++ b/sm/certreqgen-ui.c @@ -244,7 +244,27 @@ gpgsm_gencertreq_tty (ctrl_t ctrl, estream_t output_stream) { tty_printf (_("Available keys:\n")); for (count=1,sl=keypairlist; sl; sl = sl->next, count++) - tty_printf (" (%d) %s\n", count, sl->d); + { + ksba_sexp_t pkey; + gcry_sexp_t s_pkey; + char *algostr = NULL; + const char *keyref; + + keyref = strchr (sl->d, ' '); + if (keyref) + { + keyref++; + if (!gpgsm_agent_readkey (ctrl, 1, keyref, &pkey)) + { + if (!gcry_sexp_new (&s_pkey, pkey, 0, 0)) + algostr = pubkey_algo_string (s_pkey); + gcry_sexp_release (s_pkey); + } + xfree (pkey); + } + tty_printf (" (%d) %s %s\n", count, sl->d, algostr); + xfree (algostr); + } xfree (answer); answer = tty_get (_("Your selection? ")); tty_kill_prompt (); From 53beea56afecde76f0f4ca93fc50ca59298a093e Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 8 Feb 2019 16:46:52 +0100 Subject: [PATCH 165/235] scd: Implement RSA signing for PIV cards. * scd/app-piv.c (concat_tlv_list): New. (get_key_algorithm_by_dobj): Rename args for clarity. (do_auth): factor all code out to ... (do_sign): new. Implement RSA signing. Signed-off-by: Werner Koch --- scd/app-piv.c | 398 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 299 insertions(+), 99 deletions(-) diff --git a/scd/app-piv.c b/scd/app-piv.c index 4387b3aef..1d70db51c 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -469,6 +469,107 @@ add_tlv (unsigned char *buffer, unsigned int tag, size_t length) } +/* Function to build a list of TLV and return the result in a mallcoed + * buffer. The varargs are tuples of (int,size_t,void) each with the + * tag, the length and the actual data. A (0,0,NULL) tuple terminates + * the list. Up to 10 tuples are supported. */ +static gpg_error_t +concat_tlv_list (unsigned char **r_result, size_t *r_resultlen, ...) +{ + gpg_error_t err; + va_list arg_ptr; + struct { + int tag; + unsigned int len; + unsigned int contlen; + const void *data; + } argv[10]; + int i, j, argc; + unsigned char *data = NULL; + size_t datalen; + unsigned char *p; + size_t n; + + *r_result = NULL; + *r_resultlen = 0; + + /* Collect all args. Check that length is <= 2^16 to match the + * behaviour of add_tlv. */ + va_start (arg_ptr, r_resultlen); + argc = 0; + while (((argv[argc].tag = va_arg (arg_ptr, int)))) + { + argv[argc].len = va_arg (arg_ptr, size_t); + argv[argc].contlen = 0; + argv[argc].data = va_arg (arg_ptr, const void *); + if (argc >= DIM (argv)-1 || argv[argc].len > 0xffff) + { + va_end (arg_ptr); + err = gpg_error (GPG_ERR_EINVAL); + goto leave; + } + argc++; + } + va_end (arg_ptr); + + /* Compute the required buffer length and allocate the buffer. */ + datalen = 0; + for (i=0; i < argc; i++) + { + if (!argv[i].len && !argv[i].data) + { + /* Constructed tag. Compute its length. Note that we + * currently allow only one constructed tag in the list. */ + for (n=0, j = i + 1; j < argc; j++) + { + log_assert (!(!argv[j].len && !argv[j].data)); + n += add_tlv (NULL, argv[j].tag, argv[j].len); + n += argv[j].len; + } + argv[i].contlen = n; + datalen += add_tlv (NULL, argv[i].tag, n); + } + else + { + datalen += add_tlv (NULL, argv[i].tag, argv[i].len); + datalen += argv[i].len; + } + } + data = xtrymalloc (datalen); + if (!data) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Copy that data to the buffer. */ + p = data; + for (i=0; i < argc; i++) + { + if (!argv[i].len && !argv[i].data) + { + /* Constructed tag. */ + p += add_tlv (p, argv[i].tag, argv[i].contlen); + } + else + { + p += add_tlv (p, argv[i].tag, argv[i].len); + memcpy (p, argv[i].data, argv[i].len); + p += argv[i].len; + } + } + log_assert ( data + datalen == p ); + *r_result = data; + data = NULL; + *r_resultlen = datalen; + err = 0; + + leave: + xfree (data); + return err; +} + + /* Wrapper around iso7816_put_data_odd which also sets the tag into * the '5C' data object. The varargs are tuples of (int,size_t,void) * with the tag, the length and the actual data. A (0,0,NULL) tuple @@ -1354,7 +1455,7 @@ do_readkey (app_t app, int advanced, const char *keyrefstr, * store it at R_ALGO. The algorithm is taken from the corresponding * certificate or from a cache. */ static gpg_error_t -get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_algo) +get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_mechanism) { gpg_error_t err; unsigned char *certbuf = NULL; @@ -1369,7 +1470,7 @@ get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_algo) size_t n; const char *curve_name; - *r_algo = 0; + *r_mechanism = 0; err = readcert_by_tag (app, dobj->tag, &certbuf, &certbuflen, &mechanism); if (err) @@ -1382,7 +1483,7 @@ get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_algo) case PIV_ALGORITHM_RSA: case PIV_ALGORITHM_ECC_P256: case PIV_ALGORITHM_ECC_P384: - *r_algo = mechanism; + *r_mechanism = mechanism; break; default: @@ -1468,7 +1569,7 @@ get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_algo) dobj->keyref, algoname, gpg_strerror (err)); goto leave; } - *r_algo = algo; + *r_mechanism = algo; leave: gcry_free (algoname); @@ -1862,10 +1963,11 @@ do_check_chv (app_t app, const char *pwidstr, * stored there and an error code returned. For ECDSA the result is * the simple concatenation of R and S without any DER encoding. R * and S are left extended with zeroes to make sure they have an equal - * length. + * length. If HASHALGO is not zero, the function prepends the hash's + * OID to the indata or checks that it is consistent. */ static gpg_error_t -do_auth (app_t app, const char *keyidstr, +do_sign (app_t app, const char *keyidstr, int hashalgo, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata_arg, size_t indatalen, @@ -1874,13 +1976,16 @@ do_auth (app_t app, const char *keyidstr, const unsigned char *indata = indata_arg; gpg_error_t err; data_object_t dobj; - unsigned char tmpl[2+2+2+128]; - size_t tmpllen; + unsigned char oidbuf[64]; + size_t oidbuflen; unsigned char *outdata = NULL; size_t outdatalen; const unsigned char *s; size_t n; - int keyref, algo; + int keyref, mechanism; + unsigned char *indata_buffer = NULL; /* Malloced helper. */ + unsigned char *apdudata = NULL; + size_t apdudatalen; if (!keyidstr || !*keyidstr) { @@ -1888,9 +1993,6 @@ do_auth (app_t app, const char *keyidstr, goto leave; } - /* Fixme: Shall we support the KEYID/FINGERPRINT syntax? Does it - * make sense for X.509 certs? */ - dobj = find_dobj_by_keyref (app, keyidstr); if ((keyref = keyref_from_dobj (dobj)) == -1) { @@ -1898,69 +2000,141 @@ do_auth (app_t app, const char *keyidstr, goto leave; } - err = get_key_algorithm_by_dobj (app, dobj, &algo); + err = get_key_algorithm_by_dobj (app, dobj, &mechanism); if (err) goto leave; - /* We need to remove the ASN.1 prefix from INDATA. We use TEMPL as - * a temporary buffer for the OID. */ - if (algo == PIV_ALGORITHM_ECC_P256) + /* For ECC we need to remove the ASN.1 prefix from INDATA. For RSA + * we need to add the padding and possible also the ASN.1 prefix. */ + if (mechanism == PIV_ALGORITHM_ECC_P256 + || mechanism == PIV_ALGORITHM_ECC_P384) { - tmpllen = sizeof tmpl; - err = gcry_md_get_asnoid (GCRY_MD_SHA256, &tmpl, &tmpllen); - if (err) + int need_algo, need_digestlen; + + if (mechanism == PIV_ALGORITHM_ECC_P256) { - err = gpg_error (GPG_ERR_INTERNAL); - log_debug ("piv: no OID for hash algo %d\n", GCRY_MD_SHA256); + need_algo = GCRY_MD_SHA256; + need_digestlen = 32; + } + else + { + need_algo = GCRY_MD_SHA384; + need_digestlen = 48; + } + + if (hashalgo && hashalgo != need_algo) + { + err = gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + log_error ("piv: hash algo %d does not match mechanism %d\n", + need_algo, mechanism); goto leave; } - if (indatalen != tmpllen + 32 || memcmp (indata, tmpl, tmpllen)) + + if (indatalen > need_digestlen) { - err = GPG_ERR_INV_VALUE; - log_error ("piv: bad formatted input for ECC-P256 auth\n"); - goto leave; + oidbuflen = sizeof oidbuf; + err = gcry_md_get_asnoid (need_algo, &oidbuf, &oidbuflen); + if (err) + { + err = gpg_error (GPG_ERR_INTERNAL); + log_debug ("piv: no OID for hash algo %d\n", need_algo); + goto leave; + } + if (indatalen != oidbuflen + need_digestlen + || memcmp (indata, oidbuf, oidbuflen)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("piv: bad input for signing with mechanism %d\n", + mechanism); + goto leave; + } + indata += oidbuflen; + indatalen -= oidbuflen; } - indata +=tmpllen; - indatalen -= tmpllen; } - else if (algo == PIV_ALGORITHM_ECC_P384) + else if (mechanism == PIV_ALGORITHM_RSA) { - tmpllen = sizeof tmpl; - err = gcry_md_get_asnoid (GCRY_MD_SHA384, &tmpl, &tmpllen); - if (err) + /* PIV requires 2048 bit RSA. */ + unsigned int framelen = 2048 / 8; + unsigned char *frame; + int i; + + oidbuflen = sizeof oidbuf; + if (!hashalgo) { - err = gpg_error (GPG_ERR_INTERNAL); - log_debug ("piv: no OID for hash algo %d\n", GCRY_MD_SHA384); + /* We assume that indata already has the required + * digestinfo; thus merely prepend the padding below. */ + } + else if ((err = gcry_md_get_asnoid (hashalgo, &oidbuf, &oidbuflen))) + { + log_debug ("piv: no OID for hash algo %d\n", hashalgo); goto leave; } - if (indatalen != tmpllen + 48 || memcmp (indata, tmpl, tmpllen)) + else { - err = GPG_ERR_INV_VALUE; - log_error ("piv: bad formatted input for ECC-P384 auth\n"); + unsigned int digestlen = gcry_md_get_algo_dlen (hashalgo); + + if (indatalen == digestlen) + { + /* Plain hash in INDATA; prepend the digestinfo. */ + indata_buffer = xtrymalloc (oidbuflen + indatalen); + if (!indata_buffer) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (indata_buffer, oidbuf, oidbuflen); + memcpy (indata_buffer+oidbuflen, indata, indatalen); + indata = indata_buffer; + indatalen = oidbuflen + indatalen; + } + else if (indatalen == oidbuflen + digestlen + && !memcmp (indata, oidbuf, oidbuflen)) + ; /* Correct prefix. */ + else + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("piv: bad input for signing with RSA and hash %d\n", + hashalgo); + goto leave; + } + } + /* Now prepend the pkcs#v1.5 padding. We require at least 8 + * byte of padding and 3 extra bytes for the prefix and the + * delimiting nul. */ + if (!indatalen || indatalen + 8 + 4 > framelen) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("piv: input does not fit into a %u bit PKCS#v1.5 frame\n", + 8*framelen); goto leave; } - indata += tmpllen; - indatalen -= tmpllen; - } - else if (algo == PIV_ALGORITHM_RSA) - { - err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); - log_error ("piv: FIXME: implement RSA authentication\n"); - goto leave; + frame = xtrymalloc (framelen); + if (!frame) + { + err = gpg_error_from_syserror (); + goto leave; + } + n = 0; + frame[n++] = 0; + frame[n++] = 1; /* Block type. */ + i = framelen - indatalen - 3 ; + memset (frame+n, 0xff, i); + n += i; + frame[n++] = 0; /* Delimiter. */ + memcpy (frame+n, indata, indatalen); + n += indatalen; + log_assert (n == framelen); + /* And now put it into the indata_buffer. */ + xfree (indata_buffer); + indata_buffer = frame; + indata = indata_buffer; + indatalen = framelen; } else { err = gpg_error (GPG_ERR_INTERNAL); - log_debug ("piv: unknown PIV algo %d from helper function\n", algo); - goto leave; - } - - /* Because we don't have a dynamic template builder we make sure - * that we can encode all lengths in one octet. FIXME: Use add_tls - * from app-openpgp as a base for an strconcat like function. */ - if (indatalen >= 100) - { - err = gpg_error (GPG_ERR_TOO_LARGE); + log_debug ("piv: unknown PIV mechanism %d while signing\n", mechanism); goto leave; } @@ -1970,19 +2144,18 @@ do_auth (app_t app, const char *keyidstr, return err; /* Build the Dynamic Authentication Template. */ - tmpl[0] = 0x7c; - tmpl[1] = indatalen + 4; - tmpl[2] = 0x82; /* Response. */ - tmpl[3] = 0; /* Must be 0 to get the tag in the answer. */ - tmpl[4] = 0x81; /* Challenge. */ - tmpl[5] = indatalen; - memcpy (tmpl+6, indata, indatalen); - tmpllen = indatalen + 6; + err = concat_tlv_list (&apdudata, &apdudatalen, + (int)0x7c, (size_t)0, NULL, /* Constructed. */ + (int)0x82, (size_t)0, "", + (int)0x81, (size_t)indatalen, indata, + (int)0, (size_t)0, NULL); + if (err) + goto leave; /* Note: the -1 requests command chaining. */ err = iso7816_general_authenticate (app->slot, -1, - algo, keyref, - tmpl, (int)tmpllen, 0, + mechanism, keyref, + apdudata, (int)apdudatalen, 0, &outdata, &outdatalen); if (err) goto leave; @@ -1991,42 +2164,50 @@ do_auth (app_t app, const char *keyidstr, if (outdatalen && *outdata == 0x7c && (s = find_tlv (outdata, outdatalen, 0x82, &n))) { - const unsigned char *rval, *sval; - size_t rlen, rlenx, slen, slenx, resultlen; - char *result; - /* The result of an ECDSA signature is - * SEQUENCE { r INTEGER, s INTEGER } - * We re-pack that by concatenating R and S and making sure that - * both have the same length. We simplify parsing by using - * find_tlv and not a proper DER parser. */ - s = find_tlv (s, n, 0x30, &n); - if (!s) - goto bad_der; - rval = find_tlv (s, n, 0x02, &rlen); - if (!rval) - goto bad_der; - log_assert (n >= (rval-s)+rlen); - sval = find_tlv (rval+rlen, n-((rval-s)+rlen), 0x02, &slen); - if (!rval) - goto bad_der; - rlenx = slenx = 0; - if (rlen > slen) - slenx = rlen - slen; - else if (slen > rlen) - rlenx = slen - rlen; - - resultlen = rlen + rlenx + slen + slenx; - result = xtrycalloc (1, resultlen); - if (!result) + if (mechanism == PIV_ALGORITHM_RSA) { - err = gpg_error_from_syserror (); - goto leave; + memmove (outdata, outdata + (s - outdata), n); + outdatalen = n; + } + else /* ECC */ + { + const unsigned char *rval, *sval; + size_t rlen, rlenx, slen, slenx, resultlen; + char *result; + /* The result of an ECDSA signature is + * SEQUENCE { r INTEGER, s INTEGER } + * We re-pack that by concatenating R and S and making sure + * that both have the same length. We simplify parsing by + * using find_tlv and not a proper DER parser. */ + s = find_tlv (s, n, 0x30, &n); + if (!s) + goto bad_der; + rval = find_tlv (s, n, 0x02, &rlen); + if (!rval) + goto bad_der; + log_assert (n >= (rval-s)+rlen); + sval = find_tlv (rval+rlen, n-((rval-s)+rlen), 0x02, &slen); + if (!rval) + goto bad_der; + rlenx = slenx = 0; + if (rlen > slen) + slenx = rlen - slen; + else if (slen > rlen) + rlenx = slen - rlen; + + resultlen = rlen + rlenx + slen + slenx; + result = xtrycalloc (1, resultlen); + if (!result) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (result + rlenx, rval, rlen); + memcpy (result + rlenx + rlen + slenx, sval, slen); + xfree (outdata); + outdata = result; + outdatalen = resultlen; } - memcpy (result + rlenx, rval, rlen); - memcpy (result + rlenx + rlen + slenx, sval, slen); - xfree (outdata); - outdata = result; - outdatalen = resultlen; } else { @@ -2048,10 +2229,29 @@ do_auth (app_t app, const char *keyidstr, *r_outdata = outdata; *r_outdatalen = outdatalen; } + xfree (apdudata); + xfree (indata_buffer); return err; } +/* AUTH for PIV cards is actually the same as SIGN. The difference + * between AUTH and SIGN is that AUTH expects that pkcs#1.5 padding + * for RSA has already been done (digestInfo part w/o the padding) + * whereas SIGN may accept a plain digest and does the padding if + * needed. This is also the reason why SIGN takes a hashalgo. */ +static gpg_error_t +do_auth (app_t app, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **r_outdata, size_t *r_outdatalen) +{ + return do_sign (app, keyidstr, 0, pincb, pincb_arg, indata, indatalen, + r_outdata, r_outdatalen); +} + + /* Check whether a key for DOBJ already exists. We detect this by * reading the certificate described by DOBJ. If FORCE is TRUE a * diagnositic will be printed but no error returned if the key @@ -2464,7 +2664,7 @@ app_select_piv (app_t app) app->fnc.writecert = do_writecert; /* app->fnc.writekey = do_writekey; */ app->fnc.genkey = do_genkey; - /* app->fnc.sign = do_sign; */ + app->fnc.sign = do_sign; app->fnc.auth = do_auth; /* app->fnc.decipher = do_decipher; */ app->fnc.change_pin = do_change_chv; From b2838694402ce0cfc2ef70451bf0e6677b875ca9 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 11 Feb 2019 09:07:54 +0100 Subject: [PATCH 166/235] scd: For PIV cards used NO_AUTH instead of BAD_PIN. * common/util.h (GPG_ERR_NO_AUTH, GPG_ERR_BAD_AUTH): Add replacement codes for gpgrt < 1.36. * scd/app-piv.c (auth_adm_key): (do_genkey, do_writecert): Use better error codes. Signed-off-by: Werner Koch --- common/util.h | 5 ++++- scd/app-piv.c | 15 ++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/common/util.h b/common/util.h index d5bb225a7..8895137ec 100644 --- a/common/util.h +++ b/common/util.h @@ -39,7 +39,10 @@ * libgpg-error version. Define them here. * Example: (#if GPG_ERROR_VERSION_NUMBER < 0x011500 // 1.21) */ - +#if GPG_ERROR_VERSION_NUMBER < 0x012400 /* 1.36 */ +#define GPG_ERR_NO_AUTH 314 +#define GPG_ERR_BAD_AUTH 315 +#endif /*GPG_ERROR_VERSION_NUMBER*/ /* Hash function used with libksba. */ #define HASH_FNC ((void (*)(void *, const void*,size_t))gcry_md_write) diff --git a/scd/app-piv.c b/scd/app-piv.c index 1d70db51c..36086f546 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -890,6 +890,8 @@ auth_adm_key (app_t app, const unsigned char *value, size_t valuelen) PIV_ALGORITHM_3DES_ECB_0, 0x9B, tmpl, tmpllen, 0, &outdata, &outdatalen); + if (gpg_err_code (err) == GPG_ERR_BAD_PIN) + err = gpg_error (GPG_ERR_BAD_AUTH); if (err) goto leave; if (!(outdatalen && *outdata == 0x7c @@ -921,6 +923,8 @@ auth_adm_key (app_t app, const unsigned char *value, size_t valuelen) PIV_ALGORITHM_3DES_ECB_0, 0x9B, tmpl, tmpllen, 0, &outdata, &outdatalen); + if (gpg_err_code (err) == GPG_ERR_BAD_PIN) + err = gpg_error (GPG_ERR_BAD_AUTH); if (err) goto leave; if (!(outdatalen && *outdata == 0x7c @@ -937,7 +941,7 @@ auth_adm_key (app_t app, const unsigned char *value, size_t valuelen) goto leave; if (memcmp (witness, tmpl+14, 8)) { - err = gpg_error (GPG_ERR_BAD_SIGNATURE); + err = gpg_error (GPG_ERR_BAD_AUTH); goto leave; } @@ -993,6 +997,9 @@ set_adm_key (app_t app, const unsigned char *value, size_t valuelen) wipememory (apdu+8, 24); if (err) log_error ("piv: setting admin key failed; sw=%04x\n", sw); + /* A PIN is not required, thus use a better error code. */ + if (gpg_err_code (err) == GPG_ERR_BAD_PIN) + err = gpg_error (GPG_ERR_NO_AUTH); } else err = gpg_error (GPG_ERR_NOT_SUPPORTED); @@ -2490,6 +2497,9 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keyrefstr, const char *keytype, tmpl, tmpllen, 0, &buffer, &buflen); if (err) { + /* A PIN is not required, thus use a better error code. */ + if (gpg_err_code (err) == GPG_ERR_BAD_PIN) + err = gpg_error (GPG_ERR_NO_AUTH); log_error (_("generating key failed\n")); return err; } @@ -2562,6 +2572,9 @@ do_writecert (app_t app, ctrl_t ctrl, (int)0x71, (size_t)1, "", /* No compress */ (int)0xfe, (size_t)0, "", /* Empty LRC. */ (int)0, (size_t)0, NULL); + /* A PIN is not required, thus use a better error code. */ + if (gpg_err_code (err) == GPG_ERR_BAD_PIN) + err = gpg_error (GPG_ERR_NO_AUTH); if (err) log_error ("piv: failed to write cert to %s: %s\n", dobj->keyref, gpg_strerror (err)); From 43b14b4cc227311aa77b1fc1d9577c5f7d3eda86 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 11 Feb 2019 15:32:54 +0100 Subject: [PATCH 167/235] scd: Implement decryption for PIV cards. * scd/app-piv.c (do_decipher): New. -- Note that ECDH decryption has not been tested due to the lack of ECC support in gpgsm. Signed-off-by: Werner Koch --- scd/app-piv.c | 147 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 146 insertions(+), 1 deletion(-) diff --git a/scd/app-piv.c b/scd/app-piv.c index 36086f546..9e355b4c9 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -2259,6 +2259,151 @@ do_auth (app_t app, const char *keyidstr, } +/* Decrypt the data in (INDATA,INDATALEN) and on success store the + * mallocated result at (R_OUTDATA,R_OUTDATALEN). */ +static gpg_error_t +do_decipher (app_t app, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata_arg, size_t indatalen, + unsigned char **r_outdata, size_t *r_outdatalen, + unsigned int *r_info) +{ + const unsigned char *indata = indata_arg; + gpg_error_t err; + data_object_t dobj; + unsigned char *outdata = NULL; + size_t outdatalen; + const unsigned char *s; + size_t n; + int keyref, mechanism; + unsigned int framelen; + unsigned char *indata_buffer = NULL; /* Malloced helper. */ + unsigned char *apdudata = NULL; + size_t apdudatalen; + + if (!keyidstr || !*keyidstr) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + + dobj = find_dobj_by_keyref (app, keyidstr); + if ((keyref = keyref_from_dobj (dobj)) == -1) + { + err = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + if (keyref == 0x9A || keyref == 0x9C || keyref == 0x9E) + { + /* Signing only reference. We only allow '9D' and the retired + * cert key management DOs. */ + err = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + + err = get_key_algorithm_by_dobj (app, dobj, &mechanism); + if (err) + goto leave; + + switch (mechanism) + { + case PIV_ALGORITHM_ECC_P256: + framelen = 1+32+32; + break; + case PIV_ALGORITHM_ECC_P384: + framelen = 1+48+48; + break; + case PIV_ALGORITHM_RSA: + framelen = 2048 / 8; + break; + default: + err = gpg_error (GPG_ERR_INTERNAL); + log_debug ("piv: unknown PIV mechanism %d while decrypting\n", mechanism); + goto leave; + } + + /* Check that the ciphertext has the right length; due to internal + * convey mechanism using MPIs leading zero bytes might have been + * lost. Adjust for this. Note that for ECC this actually + * superfluous because the first octet is always '04' to indicate an + * uncompressed point. */ + if (indatalen > framelen) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("piv: input of %zu octets too large for mechanism %d\n", + indatalen, mechanism); + goto leave; + } + if (indatalen < framelen) + { + indata_buffer = xtrycalloc (1, framelen); + if (!indata_buffer) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (indata_buffer+(framelen-indatalen), indata, indatalen); + indata = indata_buffer; + indatalen = framelen; + } + + /* Now verify the Application PIN. */ + err = verify_chv (app, 0x80, pincb, pincb_arg); + if (err) + return err; + + /* Build the Dynamic Authentication Template. */ + err = concat_tlv_list (&apdudata, &apdudatalen, + (int)0x7c, (size_t)0, NULL, /* Constructed. */ + (int)0x82, (size_t)0, "", + mechanism == PIV_ALGORITHM_RSA? + (int)0x81 : (int)0x85, (size_t)indatalen, indata, + (int)0, (size_t)0, NULL); + if (err) + goto leave; + + /* Note: the -1 requests command chaining. */ + err = iso7816_general_authenticate (app->slot, -1, + mechanism, keyref, + apdudata, (int)apdudatalen, 0, + &outdata, &outdatalen); + if (err) + goto leave; + + /* Parse the response. */ + if (outdatalen && *outdata == 0x7c + && (s = find_tlv (outdata, outdatalen, 0x82, &n))) + { + memmove (outdata, outdata + (s - outdata), n); + outdatalen = n; + } + else + { + err = gpg_error (GPG_ERR_CARD); + log_error ("piv: response does not contain a proper result\n"); + goto leave; + } + + leave: + if (err) + { + xfree (outdata); + *r_outdata = NULL; + *r_outdatalen = 0; + } + else + { + *r_outdata = outdata; + *r_outdatalen = outdatalen; + } + *r_info = 0; + xfree (apdudata); + xfree (indata_buffer); + return err; +} + + /* Check whether a key for DOBJ already exists. We detect this by * reading the certificate described by DOBJ. If FORCE is TRUE a * diagnositic will be printed but no error returned if the key @@ -2679,7 +2824,7 @@ app_select_piv (app_t app) app->fnc.genkey = do_genkey; app->fnc.sign = do_sign; app->fnc.auth = do_auth; - /* app->fnc.decipher = do_decipher; */ + app->fnc.decipher = do_decipher; app->fnc.change_pin = do_change_chv; app->fnc.check_pin = do_check_chv; From 7e1cd2cd416f852fc039af310e3df1ce395d89a9 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 13 Feb 2019 09:46:36 +0100 Subject: [PATCH 168/235] card: New command "yubikey". * tools/card-tool-yubikey.c: New. * tools/Makefile.am (gpg_card_tool_SOURCES): Add it. * tools/card-call-scd.c (scd_apdu): Allow returning data. * tools/card-tool-misc.c (send_apdu): New. Move from gpg-card-tool.c and let it return data. Change all callers. * tools/gpg-card-tool.c (cmd_writecert): Prepend the certref with the current application type. (cmd_yubikey): New. -- This command allows listing of active applications and to enable or disable selected applications. This is in particular useful to disable the OpenPGP application so that the PIV support can easily be tested. Signed-off-by: Werner Koch --- tools/Makefile.am | 1 + tools/card-call-scd.c | 18 +- tools/card-tool-misc.c | 34 +++ tools/card-tool-yubikey.c | 438 ++++++++++++++++++++++++++++++++++++++ tools/card-tool.h | 8 +- tools/gpg-card-tool.c | 131 ++++++++---- 6 files changed, 580 insertions(+), 50 deletions(-) create mode 100644 tools/card-tool-yubikey.c diff --git a/tools/Makefile.am b/tools/Makefile.am index ad0f223b4..69f4098ca 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -129,6 +129,7 @@ gpg_card_tool_SOURCES = \ card-tool.h \ card-call-scd.c \ card-tool-keys.c \ + card-tool-yubikey.c \ card-tool-misc.c gpg_card_tool_LDADD = ../common/libgpgrl.a $(common_libs) \ diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c index 97fb6d9f1..8610da8b6 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -445,12 +445,20 @@ store_serialno (const char *line) /* Send an APDU to the current card. On success the status word is * stored at R_SW inless R_SW is NULL. With HEXAPDU being NULL only a * RESET command is send to scd. With HEXAPDU being the string - * "undefined" the command "SERIALNO undefined" is send to scd. */ + * "undefined" the command "SERIALNO undefined" is send to scd. If + * R_DATA is not NULL the data is without the status code is stored + * there. Caller must release it. */ gpg_error_t -scd_apdu (const char *hexapdu, unsigned int *r_sw) +scd_apdu (const char *hexapdu, unsigned int *r_sw, + unsigned char **r_data, size_t *r_datalen) { gpg_error_t err; + if (r_data) + *r_data = NULL; + if (r_datalen) + *r_datalen = 0; + err = start_agent (START_AGENT_NO_STARTUP_CMDS); if (err) return err; @@ -489,6 +497,12 @@ scd_apdu (const char *hexapdu, unsigned int *r_sw) { if (r_sw) *r_sw = buf16_to_uint (data+datalen-2); + if (r_data && r_datalen) + { + *r_data = data; + *r_datalen = datalen - 2; + data = NULL; + } } xfree (data); } diff --git a/tools/card-tool-misc.c b/tools/card-tool-misc.c index 06fcb6705..d0fb55dab 100644 --- a/tools/card-tool-misc.c +++ b/tools/card-tool-misc.c @@ -77,3 +77,37 @@ hex_to_buffer (const char *string, size_t *r_length) *r_length = n; return buffer; } + + +/* Direct sending of an hex encoded APDU with error printing. This is + * a simple wrapper around scd_apdu. */ +gpg_error_t +send_apdu (const char *hexapdu, const char *desc, unsigned int ignore, + unsigned char **r_data, size_t *r_datalen) +{ + gpg_error_t err; + unsigned int sw; + + err = scd_apdu (hexapdu, &sw, r_data, r_datalen); + if (err) + log_error ("sending card command %s failed: %s\n", desc, + gpg_strerror (err)); + else if (!hexapdu || !strcmp (hexapdu, "undefined")) + ; + else if (ignore == 0xffff) + ; /* Ignore all status words. */ + else if (sw != 0x9000) + { + switch (sw) + { + case 0x6285: err = gpg_error (GPG_ERR_OBJ_TERM_STATE); break; + case 0x6982: err = gpg_error (GPG_ERR_BAD_PIN); break; + case 0x6985: err = gpg_error (GPG_ERR_USE_CONDITIONS); break; + default: err = gpg_error (GPG_ERR_CARD); + } + if (!(ignore && ignore == sw)) + log_error ("card command %s failed: %s (0x%04x)\n", desc, + gpg_strerror (err), sw); + } + return err; +} diff --git a/tools/card-tool-yubikey.c b/tools/card-tool-yubikey.c new file mode 100644 index 000000000..a03915abf --- /dev/null +++ b/tools/card-tool-yubikey.c @@ -0,0 +1,438 @@ +/* card-tool-yubikey.c - Yubikey specific functions. + * Copyright (C) 2019 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 . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include +#include +#include +#include + +#include "../common/util.h" +#include "../common/i18n.h" +#include "../common/tlv.h" +#include "../common/ttyio.h" +#include "card-tool.h" + + +/* Object to describe requested interface options. */ +struct iface_s { + unsigned int usb:1; + unsigned int nfc:1; +}; + + +/* Bit flags as used by the fields in struct ykapps_s. */ +#define YKAPP_USB_SUPPORTED 0x01 +#define YKAPP_USB_ENABLED 0x02 +#define YKAPP_NFC_SUPPORTED 0x04 +#define YKAPP_NFC_ENABLED 0x08 +#define YKAPP_SELECTED 0x80 /* Selected by the command. */ + +/* An object to describe the applications on a Yubikey. Each field + * has 8 bits to hold the above flag values. */ +struct ykapps_s { + unsigned int otp:8; + unsigned int u2f:8; + unsigned int opgp:8; + unsigned int piv:8; + unsigned int oath:8; + unsigned int fido2:8; +}; + + + +/* Helper to parse an unsigned integer config value consisting of bit + * flags. TAG select the config item and MASK is the mask ORed into + * the value for a set bit. The function modifies YK. */ +static gpg_error_t +parse_ul_config_value (struct ykapps_s *yk, + const unsigned char *config, size_t configlen, + int tag, unsigned int mask) +{ + const unsigned char *s; + size_t n; + unsigned long ul = 0; + int i; + + s = find_tlv (config, configlen, tag, &n); + if (s && n) + { + if (n > sizeof ul) + { + log_error ("too large integer in Yubikey config tag %02x detected\n", + tag); + if (opt.verbose) + log_printhex (config, configlen, "config:"); + return gpg_error (GPG_ERR_CARD); + } + for (i=0; i < n; i++) + { + ul <<=8; + ul |= s[i]; + } + if (ul & 0x01) + yk->otp |= mask; + if (ul & 0x02) + yk->u2f |= mask; + if (ul & 0x08) + yk->opgp |= mask; + if (ul & 0x10) + yk->piv |= mask; + if (ul & 0x20) + yk->oath |= mask; + if (ul & 0x200) + yk->fido2 |= mask; + } + return 0; +} + + +/* Create an unsigned integer config value for TAG from the data in YK + * and store it the provided 4 byte buffer RESULT. If ENABLE is true + * the respective APP_SELECTED bit in YK sets the corresponding bit + * flags, it is is false that bit flag is cleared. IF APP_SELECTED is + * not set the bit flag is not changed. */ +static void +set_ul_config_value (struct ykapps_s *yk, + unsigned int bitflag, int tag, unsigned int enable, + unsigned char *result) +{ + unsigned long ul = 0; + + /* First set the current values. */ + if ((yk->otp & bitflag)) + ul |= 0x01; + if ((yk->u2f & bitflag)) + ul |= 0x02; + if ((yk->opgp & bitflag)) + ul |= 0x08; + if ((yk->piv & bitflag)) + ul |= 0x10; + if ((yk->oath & bitflag)) + ul |= 0x20; + if ((yk->fido2 & bitflag)) + ul |= 0x200; + + /* Then enable or disable the bits according to the selection flag. */ + if (enable) + { + if ((yk->otp & YKAPP_SELECTED)) + ul |= 0x01; + if ((yk->u2f & YKAPP_SELECTED)) + ul |= 0x02; + if ((yk->opgp & YKAPP_SELECTED)) + ul |= 0x08; + if ((yk->piv & YKAPP_SELECTED)) + ul |= 0x10; + if ((yk->oath & YKAPP_SELECTED)) + ul |= 0x20; + if ((yk->fido2 & YKAPP_SELECTED)) + ul |= 0x200; + } + else + { + if ((yk->otp & YKAPP_SELECTED)) + ul &= ~0x01; + if ((yk->u2f & YKAPP_SELECTED)) + ul &= ~0x02; + if ((yk->opgp & YKAPP_SELECTED)) + ul &= ~0x08; + if ((yk->piv & YKAPP_SELECTED)) + ul &= ~0x10; + if ((yk->oath & YKAPP_SELECTED)) + ul &= ~0x20; + if ((yk->fido2 & YKAPP_SELECTED)) + ul &= ~0x200; + } + + /* Make sure that we do not disable the CCID transport. Without + * CCID we won't have any way to change the configuration again. We + * would instead need one of the other Yubikey tools to enable an + * application and thus its transport again. */ + if (bitflag == YKAPP_USB_ENABLED && !(ul & (0x08|0x10|0x20))) + { + log_info ("Enabling PIV so that at least one CCI transport is enabled\n"); + ul |= 0x10; + } + + result[0] = tag; + result[1] = 2; + result[2] = ul >> 8; + result[3] = ul; +} + + +/* Print the info from YK. */ +static void +yk_list (estream_t fp, struct ykapps_s *yk) +{ + if (opt.interactive) + tty_fprintf (fp, ("Application USB NFC\n" + "-----------------------\n")); + tty_fprintf (fp, "OTP %s %s\n", + (yk->otp & YKAPP_USB_SUPPORTED)? + (yk->otp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", + (yk->otp & YKAPP_NFC_SUPPORTED)? + (yk->otp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); + tty_fprintf (fp, "U2F %s %s\n", + (yk->otp & YKAPP_USB_SUPPORTED)? + (yk->otp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", + (yk->otp & YKAPP_NFC_SUPPORTED)? + (yk->otp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); + tty_fprintf (fp, "OPGP %s %s\n", + (yk->opgp & YKAPP_USB_SUPPORTED)? + (yk->opgp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", + (yk->opgp & YKAPP_NFC_SUPPORTED)? + (yk->opgp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); + tty_fprintf (fp, "PIV %s %s\n", + (yk->piv & YKAPP_USB_SUPPORTED)? + (yk->piv & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", + (yk->piv & YKAPP_NFC_SUPPORTED)? + (yk->piv & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); + tty_fprintf (fp, "OATH %s %s\n", + (yk->oath & YKAPP_USB_SUPPORTED)? + (yk->oath & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", + (yk->oath & YKAPP_NFC_SUPPORTED)? + (yk->oath & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); + tty_fprintf (fp, "FIDO2 %s %s\n", + (yk->fido2 & YKAPP_USB_SUPPORTED)? + (yk->fido2 & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", + (yk->fido2 & YKAPP_NFC_SUPPORTED)? + (yk->fido2 & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); +} + + +/* Enable disable the apps as marked in YK with flag YKAPP_SELECTED. */ +static gpg_error_t +yk_enable_disable (struct ykapps_s *yk, struct iface_s *iface, + const unsigned char *config, size_t configlen, int enable) +{ + gpg_error_t err = 0; + unsigned char apdu[100]; + unsigned int apdulen; + /* const unsigned char *s; */ + /* size_t n; */ + char *hexapdu = NULL; + + apdulen = 0; + apdu[apdulen++] = 0x00; + apdu[apdulen++] = 0x1c; /* Write Config instruction. */ + apdu[apdulen++] = 0x00; + apdu[apdulen++] = 0x00; + apdu[apdulen++] = 0x00; /* Lc will be fixed up later. */ + apdu[apdulen++] = 0x00; /* Length of data will also be fixed up later. */ + + /* The ykman tool has no way to set NFC and USB flags in one go. + * Reasoning about the Yubikey's firmware it seems plausible that + * combining should work. Let's try it here if the user called for + * setting both interfaces. */ + if (iface->nfc) + { + set_ul_config_value (yk, YKAPP_NFC_ENABLED, 0x0e, enable, apdu+apdulen); + apdulen += 4; + } + if (iface->usb) + { + set_ul_config_value (yk, YKAPP_USB_ENABLED, 0x03, enable, apdu+apdulen); + apdulen += 4; + /* Yubikey's ykman copies parts of the config data when writing + * the config for USB. Below is a commented example on how that + * can be done. */ + (void)config; + (void)configlen; + /* Copy the device flags. */ + /* s = find_tlv (config, configlen, 0x08, &n); */ + /* if (s && n) */ + /* { */ + /* s -= 2; */ + /* n += 2; */ + /* if (apdulen + n > sizeof apdu) */ + /* { */ + /* err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT); */ + /* goto leave; */ + /* } */ + /* memcpy (apdu+apdulen, s, n); */ + /* apdulen += n; */ + /* } */ + } + if (iface->nfc || iface->usb) + { + if (apdulen + 2 > sizeof apdu) + { + err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT); + goto leave; + } + /* Disable the next two lines to let the card reboot. Not doing + * this is however more convenient for this tool because further + * commands don't end up with an error. It seems to be better + * that a "reset" command from gpg-card-tool is run at the + * user's discretion. */ + /* apdu[apdulen++] = 0x0c; /\* Reboot tag *\/ */ + /* apdu[apdulen++] = 0; /\* No data for reboot. *\/ */ + /* Fixup the lngth bytes. */ + apdu[4] = apdulen - 6 + 1; + apdu[5] = apdulen - 6; + + hexapdu = bin2hex (apdu, apdulen, NULL); + if (!hexapdu) + err = gpg_error_from_syserror (); + else + err = send_apdu (hexapdu, "YK.write_config", 0, NULL, NULL); + } + + leave: + xfree (hexapdu); + return err; +} + + +/* Implementation part of cmd_yubikey. ARGV is an array of size ARGc + * with the argumets given to the yubikey command. Note that ARGV has + * no terminating NULL so that ARGC must be considred. FP is the + * stream to output information. This function must only be called on + * Yubikeys. */ +gpg_error_t +yubikey_commands (estream_t fp, int argc, char *argv[]) +{ + gpg_error_t err; + enum {ykLIST, ykENABLE, ykDISABLE } cmd; + struct iface_s iface = {0,0}; + struct ykapps_s ykapps = {0}; + unsigned char *config = NULL; + size_t configlen; + int i; + + if (!argc) + return gpg_error (GPG_ERR_SYNTAX); + + /* Parse command. */ + if (!ascii_strcasecmp (argv[0], "list")) + cmd = ykLIST; + else if (!ascii_strcasecmp (argv[0], "enable")) + cmd = ykENABLE; + else if (!ascii_strcasecmp (argv[0], "disable")) + cmd = ykDISABLE; + else + { + err = gpg_error (GPG_ERR_UNKNOWN_COMMAND); + goto leave; + } + + /* Parse interface if needed. */ + if (cmd == ykLIST) + iface.usb = iface.nfc = 1; + else if (argc < 2) + { + err = gpg_error (GPG_ERR_SYNTAX); + goto leave; + } + else if (!ascii_strcasecmp (argv[1], "usb")) + iface.usb = 1; + else if (!ascii_strcasecmp (argv[1], "nfc")) + iface.nfc = 1; + else if (!ascii_strcasecmp (argv[1], "all") || !strcmp (argv[1], "*")) + iface.usb = iface.nfc = 1; + else + { + err = gpg_error (GPG_ERR_SYNTAX); + goto leave; + } + + /* Parse list of applications. */ + for (i=2; i < argc; i++) + { + if (!ascii_strcasecmp (argv[i], "otp")) + ykapps.otp = 0x80; + else if (!ascii_strcasecmp (argv[i], "u2f")) + ykapps.u2f = 0x80; + else if (!ascii_strcasecmp (argv[i], "opgp") + ||!ascii_strcasecmp (argv[i], "openpgp")) + ykapps.opgp = 0x80; + else if (!ascii_strcasecmp (argv[i], "piv")) + ykapps.piv = 0x80; + else if (!ascii_strcasecmp (argv[i], "oath") + || !ascii_strcasecmp (argv[i], "oauth")) + ykapps.oath = 0x80; + else if (!ascii_strcasecmp (argv[i], "fido2")) + ykapps.fido2 = 0x80; + else if (!ascii_strcasecmp (argv[i], "all")|| !strcmp (argv[i], "*")) + { + ykapps.otp = ykapps.u2f = ykapps.opgp = ykapps.piv = ykapps.oath + = ykapps.fido2 = 0x80; + } + else + { + err = gpg_error (GPG_ERR_SYNTAX); + goto leave; + } + } + + /* Select the Yubikey Manager application. */ + err = send_apdu ("00A4040008a000000527471117", "Select.YK-Manager", 0, + NULL, NULL); + if (err) + goto leave; + /* Send the read config command. */ + err = send_apdu ("001D000000", "YK.read_config", 0, &config, &configlen); + if (err) + goto leave; + if (!configlen || *config > configlen - 1) + { + /* The length byte is shorter than the actual length. */ + log_error ("Yubikey returned improper config data\n"); + log_printhex (config, configlen, "config:"); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + if (configlen-1 > *config) + { + log_info ("Extra config data ignored\n"); + log_printhex (config, configlen, "config:"); + } + configlen = *config; + + err = parse_ul_config_value (&ykapps, config+1, configlen, + 0x01, YKAPP_USB_SUPPORTED); + if (!err) + err = parse_ul_config_value (&ykapps, config+1, configlen, + 0x03, YKAPP_USB_ENABLED); + if (!err) + err = parse_ul_config_value (&ykapps, config+1, configlen, + 0x0d, YKAPP_NFC_SUPPORTED); + if (!err) + err = parse_ul_config_value (&ykapps, config+1, configlen, + 0x0e, YKAPP_NFC_ENABLED); + if (err) + goto leave; + + switch (cmd) + { + case ykLIST: yk_list (fp, &ykapps); break; + case ykENABLE: err = yk_enable_disable (&ykapps, &iface, + config+1, configlen, 1); break; + case ykDISABLE: err = yk_enable_disable (&ykapps, &iface, + config+1, configlen, 0); break; + } + + leave: + xfree (config); + return err; +} diff --git a/tools/card-tool.h b/tools/card-tool.h index f49f253ce..f83ebf983 100644 --- a/tools/card-tool.h +++ b/tools/card-tool.h @@ -192,12 +192,16 @@ gpg_error_t test_get_matching_keys (const char *hexgrip); /*-- card-tool-misc.c --*/ key_info_t find_kinfo (card_info_t info, const char *keyref); void *hex_to_buffer (const char *string, size_t *r_length); +gpg_error_t send_apdu (const char *hexapdu, const char *desc, + unsigned int ignore, + unsigned char **r_data, size_t *r_datalen); /*-- card-call-scd.c --*/ void release_card_info (card_info_t info); const char *app_type_string (app_type_t app_type); -gpg_error_t scd_apdu (const char *hexapdu, unsigned int *r_sw); +gpg_error_t scd_apdu (const char *hexapdu, unsigned int *r_sw, + unsigned char **r_data, size_t *r_datalen); gpg_error_t scd_learn (card_info_t info); gpg_error_t scd_getattr (const char *name, struct card_info_s *info); gpg_error_t scd_setattr (const char *name, @@ -218,6 +222,8 @@ gpg_error_t scd_checkpin (const char *serialno); unsigned long agent_get_s2k_count (void); +/*-- card-tool-yubikey.c --*/ +gpg_error_t yubikey_commands (estream_t fp, int argc, char *argv[]); #endif /*GNUPG_CARD_TOOL_H*/ diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index 93153b12f..2bc2e5f90 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -1596,6 +1596,16 @@ cmd_writecert (card_info_t info, char *argstr) } certref = certref_buffer = xstrdup ("OPENPGP.3"); } + else /* Upcase the certref; prepend cardtype if needed. */ + { + if (!strchr (certref, '.')) + certref_buffer = xstrconcat (app_type_string (info->apptype), ".", + certref, NULL); + else + certref_buffer = xstrdup (certref); + ascii_strupr (certref_buffer); + certref = certref_buffer; + } if (opt_clear) { @@ -2156,38 +2166,6 @@ cmd_unblock (card_info_t info) } -/* Direct sending of an hex encoded APDU with error printing. */ -static gpg_error_t -send_apdu (const char *hexapdu, const char *desc, unsigned int ignore) -{ - gpg_error_t err; - unsigned int sw; - - err = scd_apdu (hexapdu, &sw); - if (err) - log_error ("sending card command %s failed: %s\n", desc, - gpg_strerror (err)); - else if (!hexapdu || !strcmp (hexapdu, "undefined")) - ; - else if (ignore == 0xffff) - ; /* Ignore all status words. */ - else if (sw != 0x9000) - { - switch (sw) - { - case 0x6285: err = gpg_error (GPG_ERR_OBJ_TERM_STATE); break; - case 0x6982: err = gpg_error (GPG_ERR_BAD_PIN); break; - case 0x6985: err = gpg_error (GPG_ERR_USE_CONDITIONS); break; - default: err = gpg_error (GPG_ERR_CARD); - } - if (!(ignore && ignore == sw)) - log_error ("card command %s failed: %s (0x%04x)\n", desc, - gpg_strerror (err), sw); - } - return err; -} - - /* Note: On successful execution a redisplay should be scheduled. If * this function fails the card may be in an unknown state. */ static gpg_error_t @@ -2308,11 +2286,12 @@ cmd_factoryreset (card_info_t info) * unblock PIN command. */ any_apdu = 1; for (i=0; i < 5; i++) - send_apdu ("0020008008FFFFFFFFFFFFFFFF", "VERIFY", 0xffff); + send_apdu ("0020008008FFFFFFFFFFFFFFFF", "VERIFY", 0xffff, + NULL, NULL); for (i=0; i < 5; i++) send_apdu ("002C008010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", - "RESET RETRY COUNTER", 0xffff); - err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0); + "RESET RETRY COUNTER", 0xffff, NULL, NULL); + err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0, NULL, NULL); if (err) goto leave; } @@ -2321,14 +2300,15 @@ cmd_factoryreset (card_info_t info) any_apdu = 1; /* We need to select a card application before we can send APDUs * to the card without scdaemon doing anything on its own. */ - err = send_apdu (NULL, "RESET", 0); + err = send_apdu (NULL, "RESET", 0, NULL, NULL); if (err) goto leave; - err = send_apdu ("undefined", "dummy select ", 0); + err = send_apdu ("undefined", "dummy select ", 0, NULL, NULL); if (err) goto leave; /* Select the OpenPGP application. */ - err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0); + err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0, + NULL, NULL); if (err) goto leave; @@ -2343,14 +2323,16 @@ cmd_factoryreset (card_info_t info) for (i=0; i < 4; i++) send_apdu ("0020008120" "40404040404040404040404040404040" - "40404040404040404040404040404040", "VERIFY", 0xffff); + "40404040404040404040404040404040", "VERIFY", 0xffff, + NULL, NULL); for (i=0; i < 4; i++) send_apdu ("0020008320" "40404040404040404040404040404040" - "40404040404040404040404040404040", "VERIFY", 0xffff); + "40404040404040404040404040404040", "VERIFY", 0xffff, + NULL, NULL); /* Send terminate datafile command. */ - err = send_apdu ("00e60000", "TERMINATE DF", 0x6985); + err = send_apdu ("00e60000", "TERMINATE DF", 0x6985, NULL, NULL); if (err) goto leave; } @@ -2361,13 +2343,13 @@ cmd_factoryreset (card_info_t info) any_apdu = 1; /* Send activate datafile command. This is used without * confirmation if the card is already in termination state. */ - err = send_apdu ("00440000", "ACTIVATE DF", 0); + err = send_apdu ("00440000", "ACTIVATE DF", 0, NULL, NULL); if (err) goto leave; } /* Finally we reset the card reader once more. */ - err = send_apdu (NULL, "RESET", 0); + err = send_apdu (NULL, "RESET", 0, NULL, NULL); if (err) goto leave; @@ -2859,7 +2841,60 @@ cmd_uif (card_info_t info, char *argstr) } +static gpg_error_t +cmd_yubikey (card_info_t info, char *argstr) +{ + gpg_error_t err, err2; + estream_t fp = opt.interactive? NULL : es_stdout; + char *words[20]; + int nwords; + + if (!info) + return print_help + ("YUBIKEY args\n\n" + "Various commands pertaining to Yubikey tokens with being:\n" + "\n" + " LIST \n" + "\n" + "List supported and enabled applications.\n" + "\n" + " ENABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n" + " DISABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n" + "\n" + "Enable or disable the specified or all applications on the\n" + "given interface.", + 0); + + argstr = skip_options (argstr); + + if (!info->cardtype || strcmp (info->cardtype, "yubikey")) + { + log_info ("This command can only be used with Yubikeys.\n"); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + nwords = split_fields (argstr, words, DIM (words)); + if (nwords < 1) + { + err = gpg_error (GPG_ERR_SYNTAX); + goto leave; + } + + + /* Note that we always do a learn to get a chance to the card back + * into a usable state. */ + err = yubikey_commands (fp, nwords, words); + err2 = scd_learn (info); + if (err2) + log_error ("Error re-reading card: %s\n", gpg_strerror (err)); + + leave: + return err; +} + + /* Data used by the command parser. This needs to be outside of the * function scope to allow readline based command completion. */ enum cmdids @@ -2869,7 +2904,7 @@ enum cmdids cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET, cmdKDFSETUP, - cmdKEYATTR, cmdUIF, cmdAUTHENTICATE, + cmdKEYATTR, cmdUIF, cmdAUTHENTICATE, cmdYUBIKEY, cmdINVCMD }; @@ -2907,10 +2942,10 @@ static struct { "kdf-setup", cmdKDFSETUP, 1, N_("setup KDF for PIN authentication")}, { "key-attr", cmdKEYATTR, 1, N_("change the key attribute")}, { "uif", cmdUIF, 1, N_("change the User Interaction Flag")}, - /* Note, that we do not announce these command yet. */ { "privatedo", cmdPRIVATEDO, 0, N_("change a private data object")}, { "readcert", cmdREADCERT, 0, N_("read a certificate from a data object")}, { "writecert", cmdWRITECERT, 1, N_("store a certificate to a data object")}, + { "yubikey", cmdYUBIKEY, 0, N_("Yubikey management commands")}, { NULL, cmdINVCMD, 0, NULL } }; @@ -3020,7 +3055,7 @@ dispatch_command (card_info_t info, const char *orig_command) else { flush_keyblock_cache (); - err = scd_apdu (NULL, NULL); + err = scd_apdu (NULL, NULL, NULL, NULL); } break; @@ -3048,6 +3083,7 @@ dispatch_command (card_info_t info, const char *orig_command) case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; case cmdKEYATTR: err = cmd_keyattr (info, argstr); break; case cmdUIF: err = cmd_uif (info, argstr); break; + case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; case cmdINVCMD: default: @@ -3262,7 +3298,7 @@ interactive_loop (void) else { flush_keyblock_cache (); - err = scd_apdu (NULL, NULL); + err = scd_apdu (NULL, NULL, NULL, NULL); } break; @@ -3318,6 +3354,7 @@ interactive_loop (void) case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; case cmdKEYATTR: err = cmd_keyattr (info, argstr); break; case cmdUIF: err = cmd_uif (info, argstr); break; + case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; case cmdINVCMD: default: From b89f1790e0b9f3196a2382a9b9ff5f461c58a449 Mon Sep 17 00:00:00 2001 From: Ingvar Hagelund Date: Thu, 14 Feb 2019 09:44:38 -0500 Subject: [PATCH 169/235] po: Correct a simple typo in the Norwegian translation Signed-off-by: Ingvar Hagelund --- po/nb.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/nb.po b/po/nb.po index c6694e7e5..c040c44e8 100644 --- a/po/nb.po +++ b/po/nb.po @@ -3180,7 +3180,7 @@ msgid "Really sign all text user IDs? (y/N) " msgstr "Er du sikker pÃ¥ at du vil signerere alle bruker-id-er? (j/N) " msgid "Really sign all user IDs? (y/N) " -msgstr "Er du sikekr pÃ¥ at du vil signerere alle bruker-id-er? (j/N) " +msgstr "Er du sikker pÃ¥ at du vil signerere alle bruker-id-er? (j/N) " msgid "Hint: Select the user IDs to sign\n" msgstr "Tips: Velg bruker-id-en(e) du vil signere\n" From 74e9b579ca273fc07be090bb5fb7800a97b1b452 Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat via Gnupg-devel Date: Fri, 16 Nov 2018 01:27:37 +0000 Subject: [PATCH 170/235] sm: Support generation of card-based ECDSA CSR. * sm/call-agent.c (gpgsm_scd_pksign): Identify type of signing key and format resulting S-expression accordingly. * sm/misc.c (transform_sigval): Support ECDSA signatures. -- Current GpgSM implementation assumes card-based keys are RSA keys. This patch introduces support for ECDSA keys. By itself this patch is not sufficient, we also need support from libksba. GnuPG-bug-id: 4092 Signed-off-by: Damien Goutte-Gattat --- sm/call-agent.c | 57 ++++++++++++++++++++++++++----------- sm/misc.c | 75 +++++++++++++++++++++++++++++++++++++------------ 2 files changed, 98 insertions(+), 34 deletions(-) diff --git a/sm/call-agent.c b/sm/call-agent.c index 20d879fa4..6ac715fab 100644 --- a/sm/call-agent.c +++ b/sm/call-agent.c @@ -334,7 +334,7 @@ gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc, unsigned char *digest, size_t digestlen, int digestalgo, unsigned char **r_buf, size_t *r_buflen ) { - int rc, i; + int rc, i, pkalgo; char *p, line[ASSUAN_LINELENGTH]; membuf_t data; size_t len; @@ -342,6 +342,7 @@ gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc, unsigned char *sigbuf; size_t sigbuflen; struct default_inq_parm_s inq_parm; + gcry_sexp_t sig; (void)desc; @@ -366,6 +367,23 @@ gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc, if (digestlen*2 + 50 > DIM(line)) return gpg_error (GPG_ERR_GENERAL); + /* Get the key type from the scdaemon. */ + snprintf (line, DIM(line), "SCD READKEY %s", keyid); + init_membuf (&data, 1024); + rc = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, NULL, NULL, NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return rc; + } + + p = get_membuf (&data, &len); + pkalgo = get_pk_algo_from_canon_sexp (p, len); + xfree (p); + if (!pkalgo) + return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); + p = stpcpy (line, "SCD SETDATA " ); for (i=0; i < digestlen ; i++, p += 2 ) sprintf (p, "%02X", digest[i]); @@ -386,24 +404,31 @@ gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc, } sigbuf = get_membuf (&data, &sigbuflen); - /* Create an S-expression from it which is formatted like this: - "(7:sig-val(3:rsa(1:sSIGBUFLEN:SIGBUF)))" Fixme: If a card ever - creates non-RSA keys we need to change things. */ - *r_buflen = 21 + 11 + sigbuflen + 4; - p = xtrymalloc (*r_buflen); - *r_buf = (unsigned char*)p; - if (!p) + switch(pkalgo) { - xfree (sigbuf); - return 0; + case GCRY_PK_RSA: + rc = gcry_sexp_build (&sig, NULL, "(sig-val(rsa(s%b)))", + sigbuflen, sigbuf); + break; + + case GCRY_PK_ECC: + rc = gcry_sexp_build (&sig, NULL, "(sig-val(ecdsa(r%b)(s%b)))", + sigbuflen/2, sigbuf, + sigbuflen/2, sigbuf + sigbuflen/2); + break; + + default: + rc = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); + break; } - p = stpcpy (p, "(7:sig-val(3:rsa(1:s" ); - sprintf (p, "%u:", (unsigned int)sigbuflen); - p += strlen (p); - memcpy (p, sigbuf, sigbuflen); - p += sigbuflen; - strcpy (p, ")))"); xfree (sigbuf); + if (rc) + return rc; + + rc = make_canon_sexp (sig, r_buf, r_buflen); + gcry_sexp_release (sig); + if (rc) + return rc; assert (gcry_sexp_canon_len (*r_buf, *r_buflen, NULL, NULL)); return 0; diff --git a/sm/misc.c b/sm/misc.c index 6d047763b..9bf528513 100644 --- a/sm/misc.c +++ b/sm/misc.c @@ -109,13 +109,16 @@ transform_sigval (const unsigned char *sigval, size_t sigvallen, int mdalgo, gpg_error_t err; const unsigned char *buf, *tok; size_t buflen, toklen; - int depth, last_depth1, last_depth2; + int depth, last_depth1, last_depth2, pkalgo; int is_pubkey = 0; - const unsigned char *rsa_s = NULL; - size_t rsa_s_len = 0; + const unsigned char *rsa_s, *ecc_r, *ecc_s; + size_t rsa_s_len, ecc_r_len, ecc_s_len; const char *oid; gcry_sexp_t sexp; + rsa_s = ecc_r = ecc_s = NULL; + rsa_s_len = ecc_r_len = ecc_s_len = 0; + *r_newsigval = NULL; if (r_newsigvallen) *r_newsigvallen = 0; @@ -137,7 +140,13 @@ transform_sigval (const unsigned char *sigval, size_t sigvallen, int mdalgo, return err; if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) return err; - if (!tok || toklen != 3 || memcmp ("rsa", tok, toklen)) + if (!tok) + return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); + if (toklen == 3 && !memcmp ("rsa", tok, 3)) + pkalgo = GCRY_PK_RSA; + else if (toklen == 5 && !memcmp ("ecdsa", tok, 5)) + pkalgo = GCRY_PK_ECC; + else return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); last_depth1 = depth; @@ -150,16 +159,27 @@ transform_sigval (const unsigned char *sigval, size_t sigvallen, int mdalgo, return err; if (tok && toklen == 1) { - const unsigned char **mpi; - size_t *mpi_len; + const unsigned char **mpi = NULL; + size_t *mpi_len = NULL; switch (*tok) { - case 's': mpi = &rsa_s; mpi_len = &rsa_s_len; break; + case 's': + if (pkalgo == GCRY_PK_RSA) + { + mpi = &rsa_s; + mpi_len = &rsa_s_len; + } + else if (pkalgo == GCRY_PK_ECC) + { + mpi = &ecc_s; + mpi_len = &ecc_s_len; + } + break; + + case 'r': mpi = &ecc_r; mpi_len = &ecc_r_len; break; default: mpi = NULL; mpi_len = NULL; break; } - if (mpi && *mpi) - return gpg_error (GPG_ERR_DUP_VALUE); if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) return err; @@ -182,33 +202,52 @@ transform_sigval (const unsigned char *sigval, size_t sigvallen, int mdalgo, return err; /* Map the hash algorithm to an OID. */ - switch (mdalgo) + switch (mdalgo | (pkalgo << 8)) { - case GCRY_MD_SHA1: + case GCRY_MD_SHA1 | (GCRY_PK_RSA << 8): oid = "1.2.840.113549.1.1.5"; /* sha1WithRSAEncryption */ break; - case GCRY_MD_SHA256: + case GCRY_MD_SHA256 | (GCRY_PK_RSA << 8): oid = "1.2.840.113549.1.1.11"; /* sha256WithRSAEncryption */ break; - case GCRY_MD_SHA384: + case GCRY_MD_SHA384 | (GCRY_PK_RSA << 8): oid = "1.2.840.113549.1.1.12"; /* sha384WithRSAEncryption */ break; - case GCRY_MD_SHA512: + case GCRY_MD_SHA512 | (GCRY_PK_RSA << 8): oid = "1.2.840.113549.1.1.13"; /* sha512WithRSAEncryption */ break; + case GCRY_MD_SHA224 | (GCRY_PK_ECC << 8): + oid = "1.2.840.10045.4.3.1"; /* ecdsa-with-sha224 */ + break; + + case GCRY_MD_SHA256 | (GCRY_PK_ECC << 8): + oid = "1.2.840.10045.4.3.2"; /* ecdsa-with-sha256 */ + break; + + case GCRY_MD_SHA384 | (GCRY_PK_ECC << 8): + oid = "1.2.840.10045.4.3.3"; /* ecdsa-with-sha384 */ + break; + + case GCRY_MD_SHA512 | (GCRY_PK_ECC << 8): + oid = "1.2.840.10045.4.3.4"; /* ecdsa-with-sha512 */ + break; + default: return gpg_error (GPG_ERR_DIGEST_ALGO); } - if (rsa_s && !is_pubkey) - err = gcry_sexp_build (&sexp, NULL, "(sig-val(%s(s%b)))", - oid, (int)rsa_s_len, rsa_s); - else + if (is_pubkey) err = gcry_sexp_build (&sexp, NULL, "(sig-val(%s))", oid); + else if (pkalgo == GCRY_PK_RSA) + err = gcry_sexp_build (&sexp, NULL, "(sig-val(%s(s%b)))", oid, + (int)rsa_s_len, rsa_s); + else if (pkalgo == GCRY_PK_ECC) + err = gcry_sexp_build (&sexp, NULL, "(sig-val(%s(r%b)(s%b)))", oid, + (int)ecc_r_len, ecc_r, (int)ecc_s_len, ecc_s); if (err) return err; err = make_canon_sexp (sexp, r_newsigval, r_newsigvallen); From 3cbdf896e6919333b5423001ab58c01a04363386 Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat via Gnupg-devel Date: Sun, 17 Feb 2019 17:40:51 +0000 Subject: [PATCH 171/235] sm: Support generation of card-based ed25519 CSR. * sm/call-agent.c (gpgsm_scd_pksign): Allow SHA512. Create proper S-expression for EdDSA signature. * sm/certreqgen.c (create_request): Force use of SHA512 when using a ed25519 key. * sm/misc.c (transform_sigval): Insert OID for ed25519. -- GnuPG-bug-id: 4013 Signed-off-by: Damien Goutte-Gattat --- sm/call-agent.c | 7 +++++++ sm/certreqgen.c | 6 ++++-- sm/misc.c | 10 ++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/sm/call-agent.c b/sm/call-agent.c index 6ac715fab..4f2b83f56 100644 --- a/sm/call-agent.c +++ b/sm/call-agent.c @@ -354,6 +354,7 @@ gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc, case GCRY_MD_RMD160:hashopt = "--hash=rmd160"; break; case GCRY_MD_MD5: hashopt = "--hash=md5"; break; case GCRY_MD_SHA256:hashopt = "--hash=sha256"; break; + case GCRY_MD_SHA512:hashopt = "--hash=sha512"; break; default: return gpg_error (GPG_ERR_DIGEST_ALGO); } @@ -417,6 +418,12 @@ gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc, sigbuflen/2, sigbuf + sigbuflen/2); break; + case GCRY_PK_EDDSA: + rc = gcry_sexp_build (&sig, NULL, "(sig-val(eddsa(r%b)(s%b)))", + sigbuflen/2, sigbuf, + sigbuflen/2, sigbuf + sigbuflen/2); + break; + default: rc = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); break; diff --git a/sm/certreqgen.c b/sm/certreqgen.c index 1d610c1bb..01fba30f5 100644 --- a/sm/certreqgen.c +++ b/sm/certreqgen.c @@ -807,8 +807,10 @@ create_request (ctrl_t ctrl, if (err) return err; - string = get_parameter_value (para, pHASHALGO, 0); - if (string) + len = gcry_sexp_canon_len (public, 0, NULL, NULL); + if (get_pk_algo_from_canon_sexp (public, len) == GCRY_PK_EDDSA) + mdalgo = GCRY_MD_SHA512; + else if ((string = get_parameter_value (para, pHASHALGO, 0))) mdalgo = gcry_md_map_name (string); else mdalgo = GCRY_MD_SHA256; diff --git a/sm/misc.c b/sm/misc.c index 9bf528513..4672f269e 100644 --- a/sm/misc.c +++ b/sm/misc.c @@ -146,6 +146,8 @@ transform_sigval (const unsigned char *sigval, size_t sigvallen, int mdalgo, pkalgo = GCRY_PK_RSA; else if (toklen == 5 && !memcmp ("ecdsa", tok, 5)) pkalgo = GCRY_PK_ECC; + else if (toklen == 5 && !memcmp ("eddsa", tok, 5)) + pkalgo = GCRY_PK_EDDSA; else return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); @@ -170,7 +172,7 @@ transform_sigval (const unsigned char *sigval, size_t sigvallen, int mdalgo, mpi = &rsa_s; mpi_len = &rsa_s_len; } - else if (pkalgo == GCRY_PK_ECC) + else if (pkalgo == GCRY_PK_ECC || pkalgo == GCRY_PK_EDDSA) { mpi = &ecc_s; mpi_len = &ecc_s_len; @@ -236,6 +238,10 @@ transform_sigval (const unsigned char *sigval, size_t sigvallen, int mdalgo, oid = "1.2.840.10045.4.3.4"; /* ecdsa-with-sha512 */ break; + case GCRY_MD_SHA512 | (GCRY_PK_EDDSA << 8): + oid = "1.3.101.112"; /* ed25519 */ + break; + default: return gpg_error (GPG_ERR_DIGEST_ALGO); } @@ -245,7 +251,7 @@ transform_sigval (const unsigned char *sigval, size_t sigvallen, int mdalgo, else if (pkalgo == GCRY_PK_RSA) err = gcry_sexp_build (&sexp, NULL, "(sig-val(%s(s%b)))", oid, (int)rsa_s_len, rsa_s); - else if (pkalgo == GCRY_PK_ECC) + else if (pkalgo == GCRY_PK_ECC || pkalgo == GCRY_PK_EDDSA) err = gcry_sexp_build (&sexp, NULL, "(sig-val(%s(r%b)(s%b)))", oid, (int)ecc_r_len, ecc_r, (int)ecc_s_len, ecc_s); if (err) From ada797f477f923bee36d67c8e49f728ae7adb9e9 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 19 Feb 2019 11:55:55 +0900 Subject: [PATCH 172/235] agent: Factor out the getpin interaction. * agent/call-pinentry.c (do_getpin): New. (agent_askpin, agent_get_passphrase): Use do_getpin. Signed-off-by: NIIBE Yutaka --- agent/call-pinentry.c | 79 ++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 42 deletions(-) diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index 541a4a3f1..c7ff70a4f 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -940,6 +940,41 @@ build_cmd_setdesc (char *line, size_t linelen, const char *desc) } + +/* Ask pinentry to get a pin by "GETPIN" command. + * FIXME: Support EOF detection of the socket: ctrl->thread_startup.fd + */ +static gpg_error_t +do_getpin (ctrl_t ctrl, struct entry_parm_s *parm, + struct pin_entry_info_s *pininfo) +{ + int rc; + unsigned int pinentry_status; + int saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); + + (void)ctrl; + assuan_begin_confidential (entry_ctx); + pinentry_status = 0; + rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, parm, + inq_quality, entry_ctx, + pinentry_status_cb, &pinentry_status); + assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag); + /* Most pinentries out in the wild return the old Assuan error code + for canceled which gets translated to an assuan Cancel error and + not to the code for a user cancel. Fix this here. */ + if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) + rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); + /* Change error code in case the window close button was clicked + to cancel the operation. */ + if ((pinentry_status & PINENTRY_STATUS_CLOSE_BUTTON) + && gpg_err_code (rc) == GPG_ERR_CANCELED) + rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED); + + if (pininfo) + pininfo->status = pinentry_status; + + return rc; +} /* Call the Entry and ask for the PIN. We do check for a valid PIN number here and repeat it as long as we have invalid formed @@ -957,7 +992,6 @@ agent_askpin (ctrl_t ctrl, struct entry_parm_s parm; const char *errtext = NULL; int is_pin = 0; - int saveflag; if (opt.batch) return 0; /* fixme: we should return BAD PIN */ @@ -1100,26 +1134,7 @@ agent_askpin (ctrl_t ctrl, return unlock_pinentry (ctrl, rc); } - saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); - assuan_begin_confidential (entry_ctx); - rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm, - inq_quality, entry_ctx, - pinentry_status_cb, &pininfo->status); - assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag); - /* Most pinentries out in the wild return the old Assuan error code - for canceled which gets translated to an assuan Cancel error and - not to the code for a user cancel. Fix this here. */ - if (rc && gpg_err_source (rc) - && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) - rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); - - - /* Change error code in case the window close button was clicked - to cancel the operation. */ - if ((pininfo->status & PINENTRY_STATUS_CLOSE_BUTTON) - && gpg_err_code (rc) == GPG_ERR_CANCELED) - rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED); - + rc = do_getpin (ctrl, &parm, pininfo); if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA) errtext = is_pin? L_("PIN too long") : L_("Passphrase too long"); @@ -1188,12 +1203,9 @@ agent_get_passphrase (ctrl_t ctrl, const char *errtext, int with_qualitybar, const char *keyinfo, cache_mode_t cache_mode) { - int rc; char line[ASSUAN_LINELENGTH]; struct entry_parm_s parm; - int saveflag; - unsigned int pinentry_status; *retpass = NULL; if (opt.batch) @@ -1277,24 +1289,7 @@ agent_get_passphrase (ctrl_t ctrl, if (!parm.buffer) return unlock_pinentry (ctrl, out_of_core ()); - saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); - assuan_begin_confidential (entry_ctx); - pinentry_status = 0; - rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm, - inq_quality, entry_ctx, - pinentry_status_cb, &pinentry_status); - assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag); - /* Most pinentries out in the wild return the old Assuan error code - for canceled which gets translated to an assuan Cancel error and - not to the code for a user cancel. Fix this here. */ - if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) - rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); - /* Change error code in case the window close button was clicked - to cancel the operation. */ - if ((pinentry_status & PINENTRY_STATUS_CLOSE_BUTTON) - && gpg_err_code (rc) == GPG_ERR_CANCELED) - rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED); - + rc = do_getpin (ctrl, &parm, NULL); if (rc) xfree (parm.buffer); else From 99aa54323f97937613e02d8c2da91544e1fe7bcf Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 19 Feb 2019 13:12:07 +0900 Subject: [PATCH 173/235] agent: Minor change for pinentry status handling. * agent/call-pinentry.c (struct entry_parm_s): Add status. (do_getpin): Use param->status. (agent_askpin): Copy param->status. to pininfo. Signed-off-by: NIIBE Yutaka --- agent/call-pinentry.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index c7ff70a4f..0c8f7dc0e 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -85,6 +85,7 @@ struct entry_parm_s int lines; size_t size; unsigned char *buffer; + int status; }; @@ -945,19 +946,16 @@ build_cmd_setdesc (char *line, size_t linelen, const char *desc) * FIXME: Support EOF detection of the socket: ctrl->thread_startup.fd */ static gpg_error_t -do_getpin (ctrl_t ctrl, struct entry_parm_s *parm, - struct pin_entry_info_s *pininfo) +do_getpin (ctrl_t ctrl, struct entry_parm_s *parm) { int rc; - unsigned int pinentry_status; int saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); (void)ctrl; assuan_begin_confidential (entry_ctx); - pinentry_status = 0; rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, parm, inq_quality, entry_ctx, - pinentry_status_cb, &pinentry_status); + pinentry_status_cb, &parm->status); assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag); /* Most pinentries out in the wild return the old Assuan error code for canceled which gets translated to an assuan Cancel error and @@ -966,13 +964,10 @@ do_getpin (ctrl_t ctrl, struct entry_parm_s *parm, rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); /* Change error code in case the window close button was clicked to cancel the operation. */ - if ((pinentry_status & PINENTRY_STATUS_CLOSE_BUTTON) + if ((parm->status & PINENTRY_STATUS_CLOSE_BUTTON) && gpg_err_code (rc) == GPG_ERR_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED); - if (pininfo) - pininfo->status = pinentry_status; - return rc; } @@ -1134,7 +1129,8 @@ agent_askpin (ctrl_t ctrl, return unlock_pinentry (ctrl, rc); } - rc = do_getpin (ctrl, &parm, pininfo); + rc = do_getpin (ctrl, &parm); + pininfo->status = parm.status; if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA) errtext = is_pin? L_("PIN too long") : L_("Passphrase too long"); @@ -1289,7 +1285,7 @@ agent_get_passphrase (ctrl_t ctrl, if (!parm.buffer) return unlock_pinentry (ctrl, out_of_core ()); - rc = do_getpin (ctrl, &parm, NULL); + rc = do_getpin (ctrl, &parm); if (rc) xfree (parm.buffer); else From c395f8315362793409be54aca630ce6e903ea984 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 19 Feb 2019 14:36:50 +0900 Subject: [PATCH 174/235] agent: Terminate pinentry process gracefully, by watching socket. * agent/call-pinentry.c (watch_sock): New. (do_getpin): Spawn the watching thread. -- While we don't have npth_cancel (and it's difficult to implement it correctly), this is a kind of best compromise allowing a thread's polling when pinentry is active. GnuPG-bug-id: 2011 Signed-off-by: NIIBE Yutaka --- agent/call-pinentry.c | 85 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index 0c8f7dc0e..34dde3744 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -942,16 +942,88 @@ build_cmd_setdesc (char *line, size_t linelen, const char *desc) -/* Ask pinentry to get a pin by "GETPIN" command. - * FIXME: Support EOF detection of the socket: ctrl->thread_startup.fd +/* Watch the socket's EOF condition, while checking finish of + foreground thread. When EOF condition is detected, terminate + the pinentry process behind the assuan pipe. + */ +static void * +watch_sock (void *arg) +{ + gnupg_fd_t *p = (gnupg_fd_t *)arg; + pid_t pid = assuan_get_pid (entry_ctx); + + while (1) + { + int err; + gnupg_fd_t sock = *p; + fd_set fdset; + struct timeval timeout = { 0, 500000 }; + + if (sock == GNUPG_INVALID_FD) + return NULL; + + FD_ZERO (&fdset); + FD_SET (FD2INT (sock), &fdset); + err = npth_select (FD2INT (sock)+1, &fdset, NULL, NULL, &timeout); + + if (err < 0) + { + if (errno == EINTR) + continue; + else + return NULL; + } + + /* Possibly, it's EOF. */ + if (err > 0) + break; + } + + if (pid == (pid_t)(-1)) + ; /* No pid available can't send a kill. */ +#ifdef HAVE_W32_SYSTEM + /* Older versions of assuan set PID to 0 on Windows to indicate an + invalid value. */ + else if (pid != (pid_t) INVALID_HANDLE_VALUE && pid != 0) + TerminateProcess ((HANDLE)pid, 1); +#else + else if (pid > 0) + kill (pid, SIGINT); +#endif + + return NULL; +} + + +/* Ask pinentry to get a pin by "GETPIN" command, spawning a thread + detecting the socket's EOF. */ static gpg_error_t do_getpin (ctrl_t ctrl, struct entry_parm_s *parm) { - int rc; + npth_attr_t tattr; + gpg_error_t rc; + int err; + npth_t thread; int saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); + gnupg_fd_t sock_watched = ctrl->thread_startup.fd; + + err = npth_attr_init (&tattr); + if (err) + { + log_error ("do_getpin: error npth_attr_init: %s\n", strerror (err)); + return gpg_error_from_errno (err); + } + npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE); + + err = npth_create (&thread, &tattr, watch_sock, (void *)&sock_watched); + npth_attr_destroy (&tattr); + if (err) + { + log_error ("do_getpin: error spawning thread: %s\n", strerror (err)); + return gpg_error_from_errno (err); + } - (void)ctrl; assuan_begin_confidential (entry_ctx); rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, parm, inq_quality, entry_ctx, @@ -968,6 +1040,11 @@ do_getpin (ctrl_t ctrl, struct entry_parm_s *parm) && gpg_err_code (rc) == GPG_ERR_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED); + sock_watched = GNUPG_INVALID_FD; + err = npth_join (thread, NULL); + if (err) + log_error ("do_getpin: error joining thread: %s\n", strerror (err)); + return rc; } From 64b7c6fd1945bc206cf56979633dfca8a7494374 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 21 Feb 2019 12:26:09 +0900 Subject: [PATCH 175/235] tests: Add "disable-scdaemon" in gpg-agent.conf. * tests/openpgp/defs.scm: Add "disable-scdaemon". Remove "scdaemon-program". * tests/gpgme/gpgme-defs.scm, tests/gpgsm/gpgsm-defs.scm: Likewise. * tests/inittests, tests/pkits/inittests: Add "disable-scdaemon" -- Before this change, running "make check" accesses USB device by scdaemon on host computer. If there is any smartcard/token available, it may affect test results. Because default key choice depends on smartcard/token availability now and existing tests have nothing about testing smartcard/token, disabling scdaemon is good. Signed-off-by: NIIBE Yutaka --- tests/gpgme/gpgme-defs.scm | 3 +-- tests/gpgsm/gpgsm-defs.scm | 5 +---- tests/inittests | 1 + tests/openpgp/defs.scm | 5 +---- tests/pkits/inittests | 1 + 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/tests/gpgme/gpgme-defs.scm b/tests/gpgme/gpgme-defs.scm index 0de589ffe..bc40b3c76 100644 --- a/tests/gpgme/gpgme-defs.scm +++ b/tests/gpgme/gpgme-defs.scm @@ -67,8 +67,7 @@ (create-file "gpg-agent.conf" (string-append "pinentry-program " (tool 'pinentry)) - (string-append "scdaemon-program " (tool 'scdaemon)) - ) + "disable-scdaemon") (start-agent) diff --git a/tests/gpgsm/gpgsm-defs.scm b/tests/gpgsm/gpgsm-defs.scm index f11864201..848bc7581 100644 --- a/tests/gpgsm/gpgsm-defs.scm +++ b/tests/gpgsm/gpgsm-defs.scm @@ -67,10 +67,7 @@ "faked-system-time 1008241200") (create-file "gpg-agent.conf" (string-append "pinentry-program " (tool 'pinentry)) - (if (assoc "scdaemon" gpg-components) - (string-append "scdaemon-program " (tool 'scdaemon)) - "# No scdaemon available") - ) + "disable-scdaemon") (start-agent) (create-file "trustlist.txt" diff --git a/tests/inittests b/tests/inittests index 6fbccfb0f..9090674e3 100755 --- a/tests/inittests +++ b/tests/inittests @@ -85,6 +85,7 @@ EOF cat > gpg-agent.conf < trustlist.txt < gpg-agent.conf < Date: Thu, 21 Feb 2019 15:16:11 +0900 Subject: [PATCH 176/235] scd: Handle ack button timeout as GPG_ERR_TIMEOUT. * scd/apdu.h (SW_ACK_TIMEOUT): New. * scd/iso7816.c (map_sw): Return GPG_ERR_TIMEOUT for SW_ACK_TIMEOUT. Signed-off-by: NIIBE Yutaka --- scd/apdu.h | 1 + scd/iso7816.c | 1 + 2 files changed, 2 insertions(+) diff --git a/scd/apdu.h b/scd/apdu.h index 1392aab71..89df45cb8 100644 --- a/scd/apdu.h +++ b/scd/apdu.h @@ -31,6 +31,7 @@ enum { SW_EOF_REACHED = 0x6282, SW_TERM_STATE = 0x6285, /* Selected file is in termination state. */ SW_EEPROM_FAILURE = 0x6581, + SW_ACK_TIMEOUT = 0x6600, /* OpenPGPcard: Ack timeout. */ SW_WRONG_LENGTH = 0x6700, SW_SM_NOT_SUP = 0x6882, /* Secure Messaging is not supported. */ SW_CC_NOT_SUP = 0x6884, /* Command Chaining is not supported. */ diff --git a/scd/iso7816.c b/scd/iso7816.c index 758de5b48..a9cd73014 100644 --- a/scd/iso7816.c +++ b/scd/iso7816.c @@ -67,6 +67,7 @@ map_sw (int sw) case SW_EEPROM_FAILURE: ec = GPG_ERR_HARDWARE; break; case SW_TERM_STATE: ec = GPG_ERR_OBJ_TERM_STATE; break; case SW_WRONG_LENGTH: ec = GPG_ERR_INV_VALUE; break; + case SW_ACK_TIMEOUT: ec = GPG_ERR_TIMEOUT; break; case SW_SM_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break; case SW_CC_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break; case SW_CHV_WRONG: ec = GPG_ERR_BAD_PIN; break; From 2013cb5ee667610de35f8b92c2f979f5caa09d4c Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 21 Feb 2019 15:50:43 +0900 Subject: [PATCH 177/235] scd: Clear CHV status on timeout error. * scd/app-openpgp.c (clear_chv_status): New. (do_change_pin): Use clear_chv_status. (do_sign): Call clear_chv_status on GPG_ERR_TIMEOUT. (do_auth, do_decipher): Likewise. Signed-off-by: NIIBE Yutaka --- scd/app-openpgp.c | 79 +++++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index ae87d19f1..d7243ccd6 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -2568,6 +2568,42 @@ do_writecert (app_t app, ctrl_t ctrl, } +static gpg_error_t +clear_chv_status (app_t app, int chvno) +{ + unsigned char apdu[4]; + gpg_error_t err; + + if (!app->app_local->extcap.is_v2) + return GPG_ERR_UNSUPPORTED_OPERATION; + + apdu[0] = 0x00; + apdu[1] = ISO7816_VERIFY; + apdu[2] = 0xff; + apdu[3] = 0x80+chvno; + + err = iso7816_apdu_direct (app->slot, apdu, 4, 0, NULL, NULL, NULL); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_INV_VALUE) + err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + return err; + } + + if (chvno == 1) + { + apdu[3]++; + err = iso7816_apdu_direct (app->slot, apdu, 4, 0, NULL, NULL, NULL); + app->did_chv1 = app->did_chv2 = 0; + } + else if (chvno == 2) + app->did_chv2 = 0; + else if (chvno == 3) + app->did_chv3 = 0; + + return err; +} + /* Handle the PASSWD command. The following combinations are possible: @@ -2623,38 +2659,7 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, pininfo.minlen = minlen; if ((flags & APP_CHANGE_FLAG_CLEAR)) - { - unsigned char apdu[4]; - - if (!app->app_local->extcap.is_v2) - return GPG_ERR_UNSUPPORTED_OPERATION; - - apdu[0] = 0x00; - apdu[1] = ISO7816_VERIFY; - apdu[2] = 0xff; - apdu[3] = 0x80+chvno; - - rc = iso7816_apdu_direct (app->slot, apdu, 4, 0, NULL, NULL, NULL); - if (rc) - { - if (rc == GPG_ERR_INV_VALUE) - rc = GPG_ERR_UNSUPPORTED_OPERATION; - return rc; - } - - if (chvno == 1) - { - apdu[3]++; - rc = iso7816_apdu_direct (app->slot, apdu, 4, 0, NULL, NULL, NULL); - app->did_chv1 = app->did_chv2 = 0; - } - else if (chvno == 2) - app->did_chv2 = 0; - else if (chvno == 3) - app->did_chv3 = 0; - - return rc; - } + return clear_chv_status (app, chvno); if (reset_mode && chvno == 3) { @@ -4501,7 +4506,9 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, } rc = iso7816_compute_ds (app->slot, exmode, data, datalen, le_value, outdata, outdatalen); - if (!rc && app->force_chv1) + if (gpg_err_code (rc) == GPG_ERR_TIMEOUT) + clear_chv_status (app, 1); + else if (!rc && app->force_chv1) app->did_chv1 = 0; return rc; @@ -4610,6 +4617,8 @@ do_auth (app_t app, const char *keyidstr, rc = iso7816_internal_authenticate (app->slot, exmode, indata, indatalen, le_value, outdata, outdatalen); + if (gpg_err_code (rc) == GPG_ERR_TIMEOUT) + clear_chv_status (app, 1); } return rc; } @@ -4833,7 +4842,7 @@ do_decipher (app_t app, const char *keyidstr, indata, indatalen, le_value, padind, outdata, outdatalen); xfree (fixbuf); - if (app->app_local->keyattr[1].key_type == KEY_TYPE_ECC) + if (!rc && app->app_local->keyattr[1].key_type == KEY_TYPE_ECC) { unsigned char prefix = 0; @@ -4857,6 +4866,8 @@ do_decipher (app_t app, const char *keyidstr, *outdatalen = *outdatalen + 1; } } + if (gpg_err_code (rc) == GPG_ERR_TIMEOUT) + clear_chv_status (app, 1); if (gpg_err_code (rc) == GPG_ERR_CARD /* actual SW is 0x640a */ && app->app_local->manufacturer == 5 From d7a54ca461ad75e4fab77a2f1b25986c7637762a Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 21 Feb 2019 08:51:27 +0100 Subject: [PATCH 178/235] sm: Prepare algo mapping to handle values > 255. * sm/misc.c (transform_sigval): Allow for larger values of MDALGO and PKALGO. -- Libgcrypt already defines larger values for them, so we should be prepared in case we use them in the future. Signed-off-by: Werner Koch --- sm/misc.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/sm/misc.c b/sm/misc.c index 4672f269e..954529390 100644 --- a/sm/misc.c +++ b/sm/misc.c @@ -204,41 +204,44 @@ transform_sigval (const unsigned char *sigval, size_t sigvallen, int mdalgo, return err; /* Map the hash algorithm to an OID. */ - switch (mdalgo | (pkalgo << 8)) + if (mdalgo < 0 || mdalgo > (1<<15) || pkalgo < 0 || pkalgo > (1<<15)) + return gpg_error (GPG_ERR_DIGEST_ALGO); + + switch (mdalgo | (pkalgo << 16)) { - case GCRY_MD_SHA1 | (GCRY_PK_RSA << 8): + case GCRY_MD_SHA1 | (GCRY_PK_RSA << 16): oid = "1.2.840.113549.1.1.5"; /* sha1WithRSAEncryption */ break; - case GCRY_MD_SHA256 | (GCRY_PK_RSA << 8): + case GCRY_MD_SHA256 | (GCRY_PK_RSA << 16): oid = "1.2.840.113549.1.1.11"; /* sha256WithRSAEncryption */ break; - case GCRY_MD_SHA384 | (GCRY_PK_RSA << 8): + case GCRY_MD_SHA384 | (GCRY_PK_RSA << 16): oid = "1.2.840.113549.1.1.12"; /* sha384WithRSAEncryption */ break; - case GCRY_MD_SHA512 | (GCRY_PK_RSA << 8): + case GCRY_MD_SHA512 | (GCRY_PK_RSA << 16): oid = "1.2.840.113549.1.1.13"; /* sha512WithRSAEncryption */ break; - case GCRY_MD_SHA224 | (GCRY_PK_ECC << 8): + case GCRY_MD_SHA224 | (GCRY_PK_ECC << 16): oid = "1.2.840.10045.4.3.1"; /* ecdsa-with-sha224 */ break; - case GCRY_MD_SHA256 | (GCRY_PK_ECC << 8): + case GCRY_MD_SHA256 | (GCRY_PK_ECC << 16): oid = "1.2.840.10045.4.3.2"; /* ecdsa-with-sha256 */ break; - case GCRY_MD_SHA384 | (GCRY_PK_ECC << 8): + case GCRY_MD_SHA384 | (GCRY_PK_ECC << 16): oid = "1.2.840.10045.4.3.3"; /* ecdsa-with-sha384 */ break; - case GCRY_MD_SHA512 | (GCRY_PK_ECC << 8): + case GCRY_MD_SHA512 | (GCRY_PK_ECC << 16): oid = "1.2.840.10045.4.3.4"; /* ecdsa-with-sha512 */ break; - case GCRY_MD_SHA512 | (GCRY_PK_EDDSA << 8): + case GCRY_MD_SHA512 | (GCRY_PK_EDDSA << 16): oid = "1.3.101.112"; /* ed25519 */ break; From 5ecc7a02609dde65096ddb12e0ff8f6bce3b774a Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 21 Feb 2019 08:40:59 +0100 Subject: [PATCH 179/235] scd: Don't let the "undefined" app cause a conflict error. * scd/app.c (check_conflict): Ignore "undefined". Signed-off-by: Werner Koch --- scd/app.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scd/app.c b/scd/app.c index 2e2729e32..8d4d1c032 100644 --- a/scd/app.c +++ b/scd/app.c @@ -121,6 +121,9 @@ check_conflict (app_t app, const char *name) if (!app || !name || (app->apptype && !ascii_strcasecmp (app->apptype, name))) return 0; + if (app->apptype && !strcmp (app->apptype, "UNDEFINED")) + return 0; + log_info ("application '%s' in use - can't switch\n", app->apptype? app->apptype : ""); From 3384ba6c1c421cfa674dbd8294dc655d7320534e Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 21 Feb 2019 08:42:33 +0100 Subject: [PATCH 180/235] card: Print the keyref in the listing. * tools/gpg-card-tool.c (list_one_kinfo): Print the keyref. -- The named keys are nice but knowing the actual keyref mapping to them is also useful. Signed-off-by: Werner Koch --- tools/card-tool-yubikey.c | 2 +- tools/gpg-card-tool.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/card-tool-yubikey.c b/tools/card-tool-yubikey.c index a03915abf..996bbf041 100644 --- a/tools/card-tool-yubikey.c +++ b/tools/card-tool-yubikey.c @@ -169,7 +169,7 @@ set_ul_config_value (struct ykapps_s *yk, * application and thus its transport again. */ if (bitflag == YKAPP_USB_ENABLED && !(ul & (0x08|0x10|0x20))) { - log_info ("Enabling PIV so that at least one CCI transport is enabled\n"); + log_info ("Enabling PIV to have at least one CCID transport\n"); ul |= 0x10; } diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index 2bc2e5f90..a13a6bdaf 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -631,6 +631,7 @@ list_one_kinfo (key_info_t firstkinfo, key_info_t kinfo, estream_t fp) goto leave; } print_keygrip (fp, kinfo->grip); + tty_fprintf (fp, " keyref .....: %s\n", kinfo->keyref); if (!scd_readkey (kinfo->keyref, &s_pkey)) { char *tmp = pubkey_algo_string (s_pkey); From 5e21ef2d556ca65b7869bf16ab465f3511601e1e Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 21 Feb 2019 09:24:37 +0100 Subject: [PATCH 181/235] scd: Extend KEYPAIRINFO by key usage info. * scd/app-openpgp.c (send_keypair_info): Append usage string. * scd/app-piv.c (struct data_object_s): Remove column 'binary'. Add column 'usage'. (dump_all_do): Adjust for removed 'binary'. (send_keypair_and_cert_info): Append usage string. Signed-off-by: Werner Koch --- scd/app-openpgp.c | 10 ++++ scd/app-piv.c | 129 ++++++++++++++++++++++++++++++---------------- scd/command.c | 2 +- 3 files changed, 95 insertions(+), 46 deletions(-) diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index d7243ccd6..b6ead6478 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -1785,6 +1785,7 @@ send_keypair_info (app_t app, ctrl_t ctrl, int key) unsigned char grip[20]; char gripstr[41]; char idbuf[50]; + const char *usage; err = get_public_key (app, keyno); if (err) @@ -1802,10 +1803,19 @@ send_keypair_info (app_t app, ctrl_t ctrl, int key) bin2hex (grip, 20, gripstr); + switch (keyno) + { + case 0: usage = "sc"; break; + case 1: usage = "e"; break; + case 2: usage = "sa"; break; + default: usage = ""; break; + } + sprintf (idbuf, "OPENPGP.%d", keyno+1); send_status_info (ctrl, "KEYPAIRINFO", gripstr, 40, idbuf, strlen (idbuf), + usage, strlen (usage), NULL, (size_t)0); leave: diff --git a/scd/app-piv.c b/scd/app-piv.c index 9e355b4c9..edb00bf4c 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -90,52 +90,88 @@ struct data_object_s unsigned int acr_contact:2; /* 0=always, 1=VCI, 2=PIN, 3=PINorOCC */ unsigned int acr_contactless:2; /* 0=always, 1=VCI, 2=VCIandPIN, 3=VCIand(PINorOCC) */ - unsigned int binary:1; /* Data is not human readable. */ unsigned int dont_cache:1; /* Data item will not be cached. */ unsigned int flush_on_error:1; /* Flush cached item on error. */ unsigned int keypair:1; /* Has a public key for a keypair. */ - char keyref[3]; /* The key reference. */ - char *oidsuffix; /* Suffix of the OID, prefix is "2.16.840.1.101.3.7." */ - char *desc; /* Description of the DO. */ + const char keyref[3]; /* The key reference. */ + const char *oidsuffix; /* Suffix of the OID. */ + const char *usage; /* Usage string for a keypair or NULL. */ + const char *desc; /* Description of the DO. */ }; typedef struct data_object_s *data_object_t; static struct data_object_s data_objects[] = { - { 0x5FC107, 1, 0,1, 1, 0,0, 0, "", "1.219.0", "Card Capability Container"}, - { 0x5FC102, 1, 0,0, 1, 0,0, 0, "", "2.48.0", "Cardholder Unique Id" }, - { 0x5FC105, 1, 0,1, 1, 0,0, 1, "9A", "2.1.1", "Cert PIV Authentication" }, - { 0x5FC103, 1, 2,2, 1, 0,0, 0, "", "2.96.16", "Cardholder Fingerprints" }, - { 0x5FC106, 1, 0,1, 1, 0,0, 0, "", "2.144.0", "Security Object" }, - { 0x5FC108, 1, 2,2, 1, 0,0, 0, "", "2.96.48", "Cardholder Facial Image" }, - { 0x5FC101, 1, 0,0, 1, 0,0, 1, "9E", "2.5.0", "Cert Card Authentication"}, - { 0x5FC10A, 0, 0,1, 1, 0,0, 1, "9C", "2.1.0", "Cert Digital Signature" }, - { 0x5FC10B, 0, 0,1, 1, 0,0, 1, "9D", "2.1.2", "Cert Key Management" }, - { 0x5FC109, 0, 3,3, 0, 0,0, 0, "", "2.48.1", "Printed Information" }, - { 0x7E, 0, 0,0, 1, 0,0, 0, "", "2.96.80", "Discovery Object" }, - { 0x5FC10C, 0, 0,1, 1, 0,0, 0, "", "2.96.96", "Key History Object" }, - { 0x5FC10D, 0, 0,1, 1, 0,0, 0, "82", "2.16.1", "Retired Cert Key Mgm 1" }, - { 0x5FC10E, 0, 0,1, 1, 0,0, 0, "83", "2.16.2", "Retired Cert Key Mgm 2" }, - { 0x5FC10F, 0, 0,1, 1, 0,0, 0, "84", "2.16.3", "Retired Cert Key Mgm 3" }, - { 0x5FC110, 0, 0,1, 1, 0,0, 0, "85", "2.16.4", "Retired Cert Key Mgm 4" }, - { 0x5FC111, 0, 0,1, 1, 0,0, 0, "86", "2.16.5", "Retired Cert Key Mgm 5" }, - { 0x5FC112, 0, 0,1, 1, 0,0, 0, "87", "2.16.6", "Retired Cert Key Mgm 6" }, - { 0x5FC113, 0, 0,1, 1, 0,0, 0, "88", "2.16.7", "Retired Cert Key Mgm 7" }, - { 0x5FC114, 0, 0,1, 1, 0,0, 0, "89", "2.16.8", "Retired Cert Key Mgm 8" }, - { 0x5FC115, 0, 0,1, 1, 0,0, 0, "8A", "2.16.9", "Retired Cert Key Mgm 9" }, - { 0x5FC116, 0, 0,1, 1, 0,0, 0, "8B", "2.16.10", "Retired Cert Key Mgm 10" }, - { 0x5FC117, 0, 0,1, 1, 0,0, 0, "8C", "2.16.11", "Retired Cert Key Mgm 11" }, - { 0x5FC118, 0, 0,1, 1, 0,0, 0, "8D", "2.16.12", "Retired Cert Key Mgm 12" }, - { 0x5FC119, 0, 0,1, 1, 0,0, 0, "8E", "2.16.13", "Retired Cert Key Mgm 13" }, - { 0x5FC11A, 0, 0,1, 1, 0,0, 0, "8F", "2.16.14", "Retired Cert Key Mgm 14" }, - { 0x5FC11B, 0, 0,1, 1, 0,0, 0, "90", "2.16.15", "Retired Cert Key Mgm 15" }, - { 0x5FC11C, 0, 0,1, 1, 0,0, 0, "91", "2.16.16", "Retired Cert Key Mgm 16" }, - { 0x5FC11D, 0, 0,1, 1, 0,0, 0, "92", "2.16.17", "Retired Cert Key Mgm 17" }, - { 0x5FC11E, 0, 0,1, 1, 0,0, 0, "93", "2.16.18", "Retired Cert Key Mgm 18" }, - { 0x5FC11F, 0, 0,1, 1, 0,0, 0, "94", "2.16.19", "Retired Cert Key Mgm 19" }, - { 0x5FC120, 0, 0,1, 1, 0,0, 0, "95", "2.16.20", "Retired Cert Key Mgm 20" }, - { 0x5FC121, 0, 2,2, 1, 0,0, 0, "", "2.16.21", "Cardholder Iris Images" }, - { 0x7F61, 0, 0,0, 1, 0,0, 0, "", "2.16.22", "BIT Group Template" }, - { 0x5FC122, 0, 0,0, 1, 0,0, 0, "", "2.16.23", "SM Cert Signer" }, - { 0x5FC123, 0, 3,3, 1, 0,0, 0, "", "2.16.24", "Pairing Code Ref Data" }, + { 0x5FC107, 1, 0,1, 0,0, 0, "", "1.219.0", NULL, + "Card Capability Container"}, + { 0x5FC102, 1, 0,0, 0,0, 0, "", "2.48.0", NULL, + "Cardholder Unique Id" }, + { 0x5FC105, 1, 0,1, 0,0, 1, "9A", "2.1.1", "a", + "Cert PIV Authentication" }, + { 0x5FC103, 1, 2,2, 0,0, 0, "", "2.96.16", NULL, + "Cardholder Fingerprints" }, + { 0x5FC106, 1, 0,1, 0,0, 0, "", "2.144.0", NULL, + "Security Object" }, + { 0x5FC108, 1, 2,2, 0,0, 0, "", "2.96.48", NULL, + "Cardholder Facial Image" }, + { 0x5FC101, 1, 0,0, 0,0, 1, "9E", "2.5.0", "a", + "Cert Card Authentication"}, + { 0x5FC10A, 0, 0,1, 0,0, 1, "9C", "2.1.0", "sc", + "Cert Digital Signature" }, + { 0x5FC10B, 0, 0,1, 0,0, 1, "9D", "2.1.2", "e", + "Cert Key Management" }, + { 0x5FC109, 0, 3,3, 0,0, 0, "", "2.48.1", NULL, + "Printed Information" }, + { 0x7E, 0, 0,0, 0,0, 0, "", "2.96.80", NULL, + "Discovery Object" }, + { 0x5FC10C, 0, 0,1, 0,0, 0, "", "2.96.96", NULL, + "Key History Object" }, + { 0x5FC10D, 0, 0,1, 0,0, 0, "82", "2.16.1", "e", + "Retired Cert Key Mgm 1" }, + { 0x5FC10E, 0, 0,1, 0,0, 0, "83", "2.16.2", "e", + "Retired Cert Key Mgm 2" }, + { 0x5FC10F, 0, 0,1, 0,0, 0, "84", "2.16.3", "e", + "Retired Cert Key Mgm 3" }, + { 0x5FC110, 0, 0,1, 0,0, 0, "85", "2.16.4", "e", + "Retired Cert Key Mgm 4" }, + { 0x5FC111, 0, 0,1, 0,0, 0, "86", "2.16.5", "e", + "Retired Cert Key Mgm 5" }, + { 0x5FC112, 0, 0,1, 0,0, 0, "87", "2.16.6", "e", + "Retired Cert Key Mgm 6" }, + { 0x5FC113, 0, 0,1, 0,0, 0, "88", "2.16.7", "e", + "Retired Cert Key Mgm 7" }, + { 0x5FC114, 0, 0,1, 0,0, 0, "89", "2.16.8", "e", + "Retired Cert Key Mgm 8" }, + { 0x5FC115, 0, 0,1, 0,0, 0, "8A", "2.16.9", "e", + "Retired Cert Key Mgm 9" }, + { 0x5FC116, 0, 0,1, 0,0, 0, "8B", "2.16.10", "e", + "Retired Cert Key Mgm 10" }, + { 0x5FC117, 0, 0,1, 0,0, 0, "8C", "2.16.11", "e", + "Retired Cert Key Mgm 11" }, + { 0x5FC118, 0, 0,1, 0,0, 0, "8D", "2.16.12", "e", + "Retired Cert Key Mgm 12" }, + { 0x5FC119, 0, 0,1, 0,0, 0, "8E", "2.16.13", "e", + "Retired Cert Key Mgm 13" }, + { 0x5FC11A, 0, 0,1, 0,0, 0, "8F", "2.16.14", "e", + "Retired Cert Key Mgm 14" }, + { 0x5FC11B, 0, 0,1, 0,0, 0, "90", "2.16.15", "e", + "Retired Cert Key Mgm 15" }, + { 0x5FC11C, 0, 0,1, 0,0, 0, "91", "2.16.16", "e", + "Retired Cert Key Mgm 16" }, + { 0x5FC11D, 0, 0,1, 0,0, 0, "92", "2.16.17", "e", + "Retired Cert Key Mgm 17" }, + { 0x5FC11E, 0, 0,1, 0,0, 0, "93", "2.16.18", "e", + "Retired Cert Key Mgm 18" }, + { 0x5FC11F, 0, 0,1, 0,0, 0, "94", "2.16.19", "e", + "Retired Cert Key Mgm 19" }, + { 0x5FC120, 0, 0,1, 0,0, 0, "95", "2.16.20", "e", + "Retired Cert Key Mgm 20" }, + { 0x5FC121, 0, 2,2, 0,0, 0, "", "2.16.21", NULL, + "Cardholder Iris Images" }, + { 0x7F61, 0, 0,0, 0,0, 0, "", "2.16.22", NULL, + "BIT Group Template" }, + { 0x5FC122, 0, 0,0, 0,0, 0, "", "2.16.23", NULL, + "SM Cert Signer" }, + { 0x5FC123, 0, 3,3, 0,0, 0, "", "2.16.24", NULL, + "Pairing Code Ref Data" }, { 0 } /* Other key reference values without a data object: * "00" Global PIN (not cleared by application switching) @@ -396,7 +432,10 @@ dump_all_do (int slot) } else { - if (data_objects[i].binary) + if (data_objects[i].tag == 0x5FC109) + log_info ("DO '%s': '%.*s'\n", data_objects[i].desc, + (int)buflen, buffer); + else { log_info ("DO '%s': ", data_objects[i].desc); if (buflen > 16 && opt.verbose < 2) @@ -407,10 +446,6 @@ dump_all_do (int slot) else log_printhex (buffer, buflen, ""); } - else - log_info ("DO '%s': '%.*s'\n", - data_objects[i].desc, - (int)buflen, buffer); } xfree (buffer); buffer = NULL; @@ -1079,15 +1114,19 @@ send_keypair_and_cert_info (app_t app, ctrl_t ctrl, data_object_t dobj, char *keygripstr = NULL; int got_cert; char idbuf[50]; + const char *usage; err = get_keygrip_by_tag (app, dobj->tag, &keygripstr, &got_cert); if (err) goto leave; + usage = dobj->usage? dobj->usage : ""; + snprintf (idbuf, sizeof idbuf, "PIV.%s", dobj->keyref); send_status_info (ctrl, "KEYPAIRINFO", keygripstr, strlen (keygripstr), idbuf, strlen (idbuf), + usage, strlen (usage), NULL, (size_t)0); if (!only_keypair && got_cert) { diff --git a/scd/command.c b/scd/command.c index 237faf093..b4098e465 100644 --- a/scd/command.c +++ b/scd/command.c @@ -354,7 +354,7 @@ static const char hlp_learn[] = "\n" "are implemented. These strings are aliases for the AID\n" "\n" - " S KEYPAIRINFO \n" + " S KEYPAIRINFO []\n" "\n" "If there is no certificate yet stored on the card a single 'X' is\n" "returned as the keygrip. In addition to the keypair info, information\n" From 7317aeb3f448c98dcfa9c04f49b9a69d81c26776 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 21 Feb 2019 12:43:07 +0100 Subject: [PATCH 182/235] card: Print usage info for each key. * tools/card-call-scd.c (learn_status_cb): Handle extended KEYPARIRINFO. * tools/card-tool.h (struct key_info_s): Add field 'usage'. * tools/gpg-card-tool.c (list_one_kinfo): Show usage flags. Signed-off-by: Werner Koch --- tools/card-call-scd.c | 49 +++++++++++++++++++++++++++++++++++-------- tools/card-tool.h | 2 +- tools/gpg-card-tool.c | 20 +++++++++++++++++- 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c index 8610da8b6..83e9ba099 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -890,28 +890,59 @@ learn_status_cb (void *opaque, const char *line) else if (!memcmp (keyword, "KEYPAIRINFO", keywordlen)) { /* The format of such a line is: - * KEYPARINFO + * KEYPAIRINFO [usage] */ - const char *hexgrp = line; + char *hexgrp, *usage; - while (*line && !spacep (line)) - line++; - while (spacep (line)) - line++; + line_buffer = pline = xstrdup (line); - keyref = line; + hexgrp = pline; + while (*pline && !spacep (pline)) + pline++; + while (spacep (pline)) + pline++; + + keyref = pline; + while (*pline && !spacep (pline)) + pline++; + if (*pline) + { + *pline++ = 0; + while (spacep (pline)) + pline++; + usage = pline; + while (*pline && !spacep (pline)) + pline++; + *pline = 0; + } + else + usage = ""; /* Check whether we already have an item for the keyref. */ kinfo = find_kinfo (parm, keyref); if (!kinfo) /* New entry. */ kinfo = create_kinfo (parm, keyref); - else /* Existing entry - clear the grip. */ - memset (kinfo->grip, 0, sizeof kinfo->grip); + else /* Existing entry - clear grip and usage */ + { + memset (kinfo->grip, 0, sizeof kinfo->grip); + kinfo->usage = 0; + } /* Set or update the grip. Note that due to the * calloc/memset an erroneous too short grip will be nul * padded on the right. */ unhexify_fpr (hexgrp, kinfo->grip, sizeof kinfo->grip); + /* Parse and set the usage. */ + for (; *usage; usage++) + { + switch (*usage) + { + case 's': kinfo->usage |= GCRY_PK_USAGE_SIGN; break; + case 'c': kinfo->usage |= GCRY_PK_USAGE_CERT; break; + case 'a': kinfo->usage |= GCRY_PK_USAGE_AUTH; break; + case 'e': kinfo->usage |= GCRY_PK_USAGE_ENCR; break; + } + } } break; diff --git a/tools/card-tool.h b/tools/card-tool.h index f83ebf983..5598ae5fd 100644 --- a/tools/card-tool.h +++ b/tools/card-tool.h @@ -127,7 +127,7 @@ struct key_info_s unsigned char fprlen; /* Use length of the next item. */ unsigned char fpr[32]; /* The binary fingerprint of length FPRLEN. */ u32 created; /* The time the key was created. */ - + unsigned int usage; /* Usage flags. (GCRY_PK_USAGE_*) */ char keyref[1]; /* String with the keyref (e.g. OPENPGP.1). */ }; typedef struct key_info_s *key_info_t; diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index a13a6bdaf..eb723d7ab 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -621,6 +621,7 @@ list_one_kinfo (key_info_t firstkinfo, key_info_t kinfo, estream_t fp) key_info_t ki; const char *s; gcry_sexp_t s_pkey; + int any; if (firstkinfo && kinfo) { @@ -630,8 +631,25 @@ list_one_kinfo (key_info_t firstkinfo, key_info_t kinfo, estream_t fp) tty_fprintf (fp, "[none]\n"); goto leave; } + print_keygrip (fp, kinfo->grip); - tty_fprintf (fp, " keyref .....: %s\n", kinfo->keyref); + tty_fprintf (fp, " keyref .....: %s", kinfo->keyref); + if (kinfo->usage) + { + any = 0; + tty_fprintf (fp, " ("); + if ((kinfo->usage & GCRY_PK_USAGE_SIGN)) + { tty_fprintf (fp, "sign"); any=1; } + if ((kinfo->usage & GCRY_PK_USAGE_CERT)) + { tty_fprintf (fp, "%scert", any?",":""); any=1; } + if ((kinfo->usage & GCRY_PK_USAGE_AUTH)) + { tty_fprintf (fp, "%sauth", any?",":""); any=1; } + if ((kinfo->usage & GCRY_PK_USAGE_ENCR)) + { tty_fprintf (fp, "%sencr", any?",":""); any=1; } + tty_fprintf (fp, ")"); + } + tty_fprintf (fp, "\n"); + if (!scd_readkey (kinfo->keyref, &s_pkey)) { char *tmp = pubkey_algo_string (s_pkey); From c1000c673814e552923cf1361346d7dfeee55608 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 21 Feb 2019 17:32:39 +0100 Subject: [PATCH 183/235] sm: Fix certificate creation with key on card. * sm/certreqgen.c (create_request): Fix for certmode. -- When using an existing key from a card for certificate signing (in contrast to the default of generating a CSR), the code tried to use the same key for signing instead of the Signing-Key parameter. It is perfectly okay to use the regular signing path via gpg-agent for certificate creation - only self-signed certificates with a key on the card require the direct use of the card key (via "SCD PKSIGN"). Signed-off-by: Werner Koch --- sm/certreqgen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sm/certreqgen.c b/sm/certreqgen.c index 01fba30f5..d5c857b08 100644 --- a/sm/certreqgen.c +++ b/sm/certreqgen.c @@ -1314,7 +1314,7 @@ create_request (ctrl_t ctrl, log_info ("about to sign the %s for key: &%s\n", certmode? "certificate":"CSR", hexgrip); - if (carddirect) + if (carddirect && !certmode) rc = gpgsm_scd_pksign (ctrl, carddirect, NULL, gcry_md_read (md, mdalgo), gcry_md_get_algo_dlen (mdalgo), From 611faf1579a56925994d53eb08e1290a4b3958cf Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Fri, 22 Feb 2019 20:05:09 +0900 Subject: [PATCH 184/235] scd: internal driver: Submit SET_INTERFACE control transfer. * scd/ccid-driver.c (ccid_open_usb_reader): Alway submit SET_INTERFACE control transfer. -- This handling is not mondatory, but it's better to do so, because there are card reader with pinpad and token with ack button, which support user interaction. User interaction status should be reset at open time. The status should be reset when the session is closed/stopped. In practice, since cleanup routine in a driver may not be called properly, it's good to submit SET_INTERFACE at open time. Signed-off-by: NIIBE Yutaka --- scd/ccid-driver.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c index c165f6e30..69df17355 100644 --- a/scd/ccid-driver.c +++ b/scd/ccid-driver.c @@ -1653,15 +1653,13 @@ ccid_open_usb_reader (const char *spec_reader_name, goto leave; } - if (set_no != 0) + /* Submit SET_INTERFACE control transfer which can reset the device. */ + rc = libusb_set_interface_alt_setting (idev, ifc_no, set_no); + if (rc) { - rc = libusb_set_interface_alt_setting (idev, ifc_no, set_no); - if (rc) - { - DEBUGOUT_1 ("usb_set_interface_alt_setting failed: %d\n", rc); - rc = CCID_DRIVER_ERR_CARD_IO_ERROR; - goto leave; - } + DEBUGOUT_1 ("usb_set_interface_alt_setting failed: %d\n", rc); + rc = CCID_DRIVER_ERR_CARD_IO_ERROR; + goto leave; } rc = ccid_vendor_specific_init (*handle); From e140c6d4f581be1a60a34b67b16430452f3987e8 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Mon, 25 Feb 2019 10:44:16 +0900 Subject: [PATCH 185/235] gpgscm: Build well even if NDEBUG defined. * gpgscm/scheme.c (gc_reservation_failure): Fix adding ";". [!NDEBUG] (scheme_init_custom_alloc): Don't init seserved_lineno. -- Picked from libgpg-error commit of: 8a9397896fd202dcfb3fb46259e43bc05a0ddd2e In some build environment, NDEBUG is defined (although it's bad practice). This change supports such a situation. GnuPG-bug-id: 3959 Signed-off-by: NIIBE Yutaka --- tests/gpgscm/scheme.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/gpgscm/scheme.c b/tests/gpgscm/scheme.c index feb313349..a8191657d 100644 --- a/tests/gpgscm/scheme.c +++ b/tests/gpgscm/scheme.c @@ -872,7 +872,7 @@ gc_reservation_failure(struct scheme *sc) { #ifdef NDEBUG fprintf(stderr, - "insufficient reservation\n") + "insufficient reservation\n"); #else fprintf(stderr, "insufficient %s reservation in line %d\n", @@ -5614,7 +5614,9 @@ int scheme_init_custom_alloc(scheme *sc, func_alloc malloc, func_dealloc free) { sc->fcells = 0; sc->inhibit_gc = GC_ENABLED; sc->reserved_cells = 0; +#ifndef NDEBUG sc->reserved_lineno = 0; +#endif sc->no_memory=0; sc->inport=sc->NIL; sc->outport=sc->NIL; From a12c3a566e2e4b10bc02976a2819070877ee895c Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 22 Feb 2019 14:09:02 +0100 Subject: [PATCH 186/235] agent: Fix for suggested Libgcrypt use. * agent/divert-scd.c (divert_pkdecrypt): Skip a flags parameter. -- The libgcrypt docs say that a "flags" parameter should always be used in the input of pkdecrypt. Thus we should allow that parameter also when parsing an s-expression to figure out the algorithm for use with scdaemon. Signed-off-by: Werner Koch --- agent/divert-scd.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/agent/divert-scd.c b/agent/divert-scd.c index b9781547d..02fe5295a 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -498,6 +498,7 @@ divert_pkdecrypt (ctrl_t ctrl, const char *desc_text, char *kid; const unsigned char *s; size_t n; + int depth; const unsigned char *ciphertext; size_t ciphertextlen; char *plaintext; @@ -506,7 +507,6 @@ divert_pkdecrypt (ctrl_t ctrl, const char *desc_text, (void)desc_text; *r_padding = -1; - s = cipher; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); @@ -522,6 +522,21 @@ divert_pkdecrypt (ctrl_t ctrl, const char *desc_text, n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); + + /* First check whether we have a flags parameter and skip it. */ + if (smatch (&s, n, "flags")) + { + depth = 1; + if (sskip (&s, &depth) || depth) + return gpg_error (GPG_ERR_INV_SEXP); + 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, "rsa")) { if (*s != '(') From 28de5c0ea53373c56a4405fe6b08d194682dd1de Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 25 Feb 2019 09:28:22 +0100 Subject: [PATCH 187/235] card: Rename gpg-card-tool to gpg-card. * tools/card-tool-keys.c: Rename to card-keys.c. * tools/card-tool-misc.c: Rename to card-misc.c. * tools/card-tool-yubikey.c: Rename to card-yubikey.c. * tools/card-tool.h: Rename to gpg-card.h. * tools/gpg-card-tool-w32info.rc: Rename to gpg-card-w32info.rc * doc/card-tool.texi: Rename top gpg-card.texi Signed-off-by: Werner Koch --- doc/Makefile.am | 6 ++-- doc/gnupg.texi | 2 +- doc/{card-tool.texi => gpg-card.texi} | 20 ++++++------- po/POTFILES.in | 5 +++- tools/Makefile.am | 29 ++++++++++--------- tools/card-call-scd.c | 2 +- tools/{card-tool-keys.c => card-keys.c} | 7 +++-- tools/{card-tool-misc.c => card-misc.c} | 4 +-- tools/{card-tool-yubikey.c => card-yubikey.c} | 4 +-- ...rd-tool-w32info.rc => gpg-card-w32info.rc} | 6 ++-- tools/{gpg-card-tool.c => gpg-card.c} | 16 +++++----- tools/{card-tool.h => gpg-card.h} | 14 ++++----- 12 files changed, 61 insertions(+), 54 deletions(-) rename doc/{card-tool.texi => gpg-card.texi} (87%) rename tools/{card-tool-keys.c => card-keys.c} (99%) rename tools/{card-tool-misc.c => card-misc.c} (97%) rename tools/{card-tool-yubikey.c => card-yubikey.c} (99%) rename tools/{gpg-card-tool-w32info.rc => gpg-card-w32info.rc} (89%) rename tools/{gpg-card-tool.c => gpg-card.c} (99%) rename tools/{card-tool.h => gpg-card.h} (97%) diff --git a/doc/Makefile.am b/doc/Makefile.am index 19b3825b1..0720dd366 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -69,7 +69,7 @@ nobase_dist_doc_DATA = FAQ DETAILS HACKING DCO TRANSLATE OpenPGP KEYSERVER \ gnupg_TEXINFOS = \ gpg.texi gpgsm.texi gpg-agent.texi scdaemon.texi instguide.texi \ tools.texi debugging.texi glossary.texi contrib.texi gpl.texi \ - sysnotes.texi dirmngr.texi wks.texi card-tool.texi \ + sysnotes.texi dirmngr.texi wks.texi gpg-card.texi \ gnupg-module-overview.svg \ gnupg-card-architecture.fig \ howtos.texi howto-create-a-server-cert.texi @@ -90,12 +90,12 @@ YAT2M_OPTIONS = -I $(srcdir) \ myman_sources = gnupg7.texi gpg.texi gpgsm.texi gpg-agent.texi \ dirmngr.texi scdaemon.texi tools.texi wks.texi \ - card-tool.texi + gpg-card.texi myman_pages = gpgsm.1 gpg-agent.1 dirmngr.8 scdaemon.1 \ watchgnupg.1 gpgconf.1 addgnupghome.8 gpg-preset-passphrase.1 \ gpg-connect-agent.1 gpgparsemail.1 symcryptrun.1 gpgtar.1 \ applygnupgdefaults.8 gpg-wks-client.1 gpg-wks-server.1 \ - dirmngr-client.1 gpg-card-tool.1 + dirmngr-client.1 gpg-card.1 if USE_GPG2_HACK myman_pages += gpg2.1 gpgv2.1 else diff --git a/doc/gnupg.texi b/doc/gnupg.texi index 6210486a0..78d4669da 100644 --- a/doc/gnupg.texi +++ b/doc/gnupg.texi @@ -187,7 +187,7 @@ the administration and the architecture. @cindex trust values @include trust-values.texi -@include card-tool.texi +@include gpg-card.texi @include tools.texi @include wks.texi diff --git a/doc/card-tool.texi b/doc/gpg-card.texi similarity index 87% rename from doc/card-tool.texi rename to doc/gpg-card.texi index b84f20721..92bc12362 100644 --- a/doc/card-tool.texi +++ b/doc/gpg-card.texi @@ -13,26 +13,26 @@ tool is an extension of the @option{--edit-key} command available with @command{gpg}. @menu -* gpg-card-tool:: Administrate smart cards. +* gpg-card:: Administrate smart cards. @end menu @c @c GPG-CARD-TOOL @c -@manpage gpg-card-tool.1 -@node gpg-card-tool +@manpage gpg-card.1 +@node gpg-card @section Administrate smart cards. @ifset manverb -.B gpg-card-tool +.B gpg-card \- Administrate Smart Cards @end ifset @mansect synopsis @ifset manverb -.B gpg-card-tool +.B gpg-card .RI [ options ] .br -.B gpg-card-tool +.B gpg-card .RI [ options ] .I command .RI { @@ -42,16 +42,16 @@ tool is an extension of the @option{--edit-key} command available with @end ifset @mansect description -The @command{gpg-card-tool} is used to administrate smart cards and USB +The @command{gpg-card} is used to administrate smart cards and USB tokens. It provides a superset of features from @command{gpg --card-edit} an can be considered a frontend to @command{scdaemon} which is a daemon started by @command{gpg-agent} to handle smart cards. -If @command{gpg-card-tool} is invoked without commands an interactive +If @command{gpg-card} is invoked without commands an interactive mode is used. -If @command{gpg-card-tool} is invoked with one or more commands the +If @command{gpg-card} is invoked with one or more commands the same commands as available in the interactive mode are run from the command line. These commands need to be delimited with a double-dash. If a double-dash or a shell specific character is required as part of @@ -66,7 +66,7 @@ COMMAND}. @mansect options @noindent -@command{gpg-card-tool} understands these options: +@command{gpg-card} understands these options: @table @gnupgtabopt diff --git a/po/POTFILES.in b/po/POTFILES.in index 53a7dc8a5..da5581168 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -127,5 +127,8 @@ tools/gpgconf.c tools/no-libgcrypt.c tools/symcryptrun.c tools/gpg-check-pattern.c -tools/gpg-card-tool.c +tools/gpg-card.c +tools/card-misc.c +tools/card-keys.c +tools/card-yubikey.c tools/card-call-scd.c diff --git a/tools/Makefile.am b/tools/Makefile.am index 69f4098ca..fb37c05e7 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -22,14 +22,14 @@ EXTRA_DIST = \ lspgpot mail-signed-keys convert-from-106 sockprox.c \ ccidmon.c ChangeLog-2011 \ gpg-connect-agent-w32info.rc \ - gpg-card-tool-w32info.rc + gpg-card-w32info.rc AM_CPPFLAGS = include $(top_srcdir)/am/cmacros.am if HAVE_W32_SYSTEM gpg_connect_agent_rc_objs = gpg-connect-agent-w32info.o -gpg_card_tool_rc_objs = gpg-card-tool-w32info.o +gpg_card_tool_rc_objs = gpg-card-w32info.o resource_objs += $(gpg_connect_agent_rc_objs) $(gpg_card_tool_rc_objs) endif @@ -51,7 +51,7 @@ endif libexec_PROGRAMS = gpg-wks-client gpg-pair-tool -bin_PROGRAMS = gpgconf gpg-connect-agent gpg-card-tool ${symcryptrun} +bin_PROGRAMS = gpgconf gpg-connect-agent gpg-card ${symcryptrun} if !HAVE_W32_SYSTEM bin_PROGRAMS += watchgnupg gpgparsemail ${gpg_wks_server} endif @@ -124,19 +124,20 @@ gpg_connect_agent_LDADD = ../common/libgpgrl.a $(common_libs) \ $(gpg_connect_agent_rc_objs) -gpg_card_tool_SOURCES = \ - gpg-card-tool.c \ - card-tool.h \ +gpg_card_SOURCES = \ + gpg-card.c \ + gpg-card.h \ card-call-scd.c \ - card-tool-keys.c \ - card-tool-yubikey.c \ - card-tool-misc.c + card-keys.c \ + card-yubikey.c \ + card-misc.c -gpg_card_tool_LDADD = ../common/libgpgrl.a $(common_libs) \ - $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ - $(GPG_ERROR_LIBS) \ - $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ - $(gpg_card_tool_rc_objs) +gpg_card_LDADD = \ + ../common/libgpgrl.a $(common_libs) \ + $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ + $(GPG_ERROR_LIBS) \ + $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ + $(gpg_card_tool_rc_objs) if !DISABLE_REGEX diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c index 83e9ba099..55ecf126e 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -39,7 +39,7 @@ #include "../common/status.h" #include "../common/host2net.h" #include "../common/openpgpdefs.h" -#include "card-tool.h" +#include "gpg-card.h" #define CONTROL_D ('D' - 'A' + 1) diff --git a/tools/card-tool-keys.c b/tools/card-keys.c similarity index 99% rename from tools/card-tool-keys.c rename to tools/card-keys.c index 4e057ad94..ad06f2ff7 100644 --- a/tools/card-tool-keys.c +++ b/tools/card-keys.c @@ -1,4 +1,4 @@ -/* card-tool-keys.c - OpenPGP and CMS related functions for gpg-card-tool +/* card-keys.c - OpenPGP and CMS related functions for gpg-card * Copyright (C) 2019 g10 Code GmbH * * This file is part of GnuPG. @@ -28,7 +28,7 @@ #include "../common/ccparray.h" #include "../common/exectool.h" #include "../common/openpgpdefs.h" -#include "card-tool.h" +#include "gpg-card.h" /* It is quite common that all keys of an OpenPGP card belong to the @@ -168,6 +168,9 @@ parse_key_record (char **fields, int nfields, pubkey_t *r_pubkey) { pubkey_t pubkey; + (void)fields; /* Not yet used. */ + (void)nfields; + pubkey = xtrycalloc (1, sizeof *pubkey); if (!pubkey) return gpg_error_from_syserror (); diff --git a/tools/card-tool-misc.c b/tools/card-misc.c similarity index 97% rename from tools/card-tool-misc.c rename to tools/card-misc.c index d0fb55dab..bccdbda9d 100644 --- a/tools/card-tool-misc.c +++ b/tools/card-misc.c @@ -1,4 +1,4 @@ -/* card-tool-misc.c - Helper functions for gpg-card-tool +/* card-misc.c - Helper functions for gpg-card * Copyright (C) 2019 g10 Code GmbH * * This file is part of GnuPG. @@ -28,7 +28,7 @@ #include "../common/util.h" #include "../common/i18n.h" #include "../common/openpgpdefs.h" -#include "card-tool.h" +#include "gpg-card.h" /* Return the key info object for the key KEYREF. If it is not found * NULL is returned. */ diff --git a/tools/card-tool-yubikey.c b/tools/card-yubikey.c similarity index 99% rename from tools/card-tool-yubikey.c rename to tools/card-yubikey.c index 996bbf041..f9d130988 100644 --- a/tools/card-tool-yubikey.c +++ b/tools/card-yubikey.c @@ -1,4 +1,4 @@ -/* card-tool-yubikey.c - Yubikey specific functions. +/* card-yubikey.c - Yubikey specific functions. * Copyright (C) 2019 g10 Code GmbH * * This file is part of GnuPG. @@ -29,7 +29,7 @@ #include "../common/i18n.h" #include "../common/tlv.h" #include "../common/ttyio.h" -#include "card-tool.h" +#include "gpg-card.h" /* Object to describe requested interface options. */ diff --git a/tools/gpg-card-tool-w32info.rc b/tools/gpg-card-w32info.rc similarity index 89% rename from tools/gpg-card-tool-w32info.rc rename to tools/gpg-card-w32info.rc index 6937c3e34..b35ff4ce2 100644 --- a/tools/gpg-card-tool-w32info.rc +++ b/tools/gpg-card-w32info.rc @@ -1,4 +1,4 @@ -/* gpg-card-toolt-w32info.rc -*- c -*- +/* gpg-card-w32info.rc -*- c -*- * Copyright (C) 2019 g10 Code GmbH * * This file is free software; as a special exception the author gives @@ -34,8 +34,8 @@ BEGIN VALUE "FileDescription", L"GnuPG\x2019s card tool \ to the agent\0" - VALUE "InternalName", "gpg-card-tool\0" - VALUE "OriginalFilename", "gpg-card-tool.exe\0" + VALUE "InternalName", "gpg-card\0" + VALUE "OriginalFilename", "gpg-card.exe\0" VALUE "ProductName", W32INFO_PRODUCTNAME VALUE "ProductVersion", W32INFO_PRODUCTVERSION VALUE "CompanyName", W32INFO_COMPANYNAME diff --git a/tools/gpg-card-tool.c b/tools/gpg-card.c similarity index 99% rename from tools/gpg-card-tool.c rename to tools/gpg-card.c index eb723d7ab..f1d0dc8fc 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card.c @@ -1,4 +1,4 @@ -/* gpg-card-tool.c - An interactive tool to work with cards. +/* gpg-card.c - An interactive tool to work with cards. * Copyright (C) 2019 g10 Code GmbH * * This file is part of GnuPG. @@ -40,7 +40,7 @@ #include "../common/server-help.h" #include "../common/openpgpdefs.h" -#include "card-tool.h" +#include "gpg-card.h" #define CONTROL_D ('D' - 'A' + 1) @@ -143,7 +143,7 @@ my_strusage( int level ) switch (level) { - case 11: p = "gpg-card-tool"; break; + case 11: p = "gpg-card"; break; case 12: p = "@GNUPG@"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; @@ -151,11 +151,11 @@ my_strusage( int level ) case 1: case 40: - p = ("Usage: gpg-card-tool" + p = ("Usage: gpg-card" " [options] [{[--] command [args]}] (-h for help)"); break; case 41: - p = ("Syntax: gpg-card-tool" + p = ("Syntax: gpg-card" " [options] [command [args] {-- command [args]}]\n\n" "Tool to manage cards and tokens. With a command an interactive\n" "mode is used. Use command \"help\" to list all commands."); @@ -224,7 +224,7 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) -/* gpg-card-tool main. */ +/* gpg-card main. */ int main (int argc, char **argv) { @@ -234,10 +234,10 @@ main (int argc, char **argv) int cmdidx; char *command; - gnupg_reopen_std ("gpg-card-tool"); + gnupg_reopen_std ("gpg-card"); set_strusage (my_strusage); gnupg_rl_initialize (); - log_set_prefix ("gpg-card-tool", GPGRT_LOG_WITH_PREFIX); + log_set_prefix ("gpg-card", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); diff --git a/tools/card-tool.h b/tools/gpg-card.h similarity index 97% rename from tools/card-tool.h rename to tools/gpg-card.h index 5598ae5fd..03bad7530 100644 --- a/tools/card-tool.h +++ b/tools/gpg-card.h @@ -1,4 +1,4 @@ -/* card-tool.h - Common definitions for the gpg-card-tool +/* gpg-card.h - Common definitions for the gpg-card-tool * Copyright (C) 2019 g10 Code GmbH * * This file is part of GnuPG. @@ -18,8 +18,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef GNUPG_CARD_TOOL_H -#define GNUPG_CARD_TOOL_H +#ifndef GNUPG_GPG_CARD_H +#define GNUPG_GPG_CARD_H #include "../common/session-env.h" @@ -181,7 +181,7 @@ struct card_info_s typedef struct card_info_s *card_info_t; -/*-- card-tool-keys.c --*/ +/*-- card-keys.c --*/ void release_keyblock (keyblock_t keyblock); void flush_keyblock_cache (void); gpg_error_t get_matching_keys (const unsigned char *keygrip, int protocol, @@ -189,7 +189,7 @@ gpg_error_t get_matching_keys (const unsigned char *keygrip, int protocol, gpg_error_t test_get_matching_keys (const char *hexgrip); -/*-- card-tool-misc.c --*/ +/*-- card-misc.c --*/ key_info_t find_kinfo (card_info_t info, const char *keyref); void *hex_to_buffer (const char *string, size_t *r_length); gpg_error_t send_apdu (const char *hexapdu, const char *desc, @@ -222,8 +222,8 @@ gpg_error_t scd_checkpin (const char *serialno); unsigned long agent_get_s2k_count (void); -/*-- card-tool-yubikey.c --*/ +/*-- card-yubikey.c --*/ gpg_error_t yubikey_commands (estream_t fp, int argc, char *argv[]); -#endif /*GNUPG_CARD_TOOL_H*/ +#endif /*GNUPG_GPG_CARD_H*/ From a481d17432bf7cca19ca71b6aa5ccd9aee2b3baa Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 25 Feb 2019 11:29:30 +0100 Subject: [PATCH 188/235] scd: PIV: Always require a PIN for signing with 9C. * scd/app-piv.c (verify_chv): Add arg 'force'. (do_sign): Use force for 0x9c. Signed-off-by: Werner Koch --- scd/app-piv.c | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/scd/app-piv.c b/scd/app-piv.c index edb00bf4c..704a498c6 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -1803,9 +1803,10 @@ ask_and_prepare_chv (app_t app, int keyref, int ask_new, int remaining, /* Verify the card holder verification identified by KEYREF. This is - * either the Appication PIN or the Global PIN. */ + * either the Appication PIN or the Global PIN. If FORCE is true a + * verification is always done. */ static gpg_error_t -verify_chv (app_t app, int keyref, +verify_chv (app_t app, int keyref, int force, gpg_error_t (*pincb)(void*,const char *,char **), void *pincb_arg) { gpg_error_t err; @@ -1823,10 +1824,11 @@ verify_chv (app_t app, int keyref, apdu[3] = keyref; if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL)) { - /* No need to verification. */ - return 0; /* All fine. */ + if (!force) /* No need to verification. */ + return 0; /* All fine. */ + remaining = -1; } - if ((sw & 0xfff0) == 0x63C0) + else if ((sw & 0xfff0) == 0x63C0) remaining = (sw & 0x000f); /* PIN has REMAINING tries left. */ else remaining = -1; @@ -1998,7 +2000,7 @@ do_check_chv (app_t app, const char *pwidstr, if (keyref == -1) return gpg_error (GPG_ERR_INV_ID); - return verify_chv (app, keyref, pincb, pincb_arg); + return verify_chv (app, keyref, 0, pincb, pincb_arg); } @@ -2032,6 +2034,7 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, unsigned char *indata_buffer = NULL; /* Malloced helper. */ unsigned char *apdudata = NULL; size_t apdudatalen; + int force_verify; if (!keyidstr || !*keyidstr) { @@ -2046,6 +2049,15 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, goto leave; } + /* According to table 4b of SP800-73-4 the signing key always + * requires a verify. */ + switch (keyref) + { + case 0x9c: force_verify = 1; break; + default: force_verify = 0; break; + } + + err = get_key_algorithm_by_dobj (app, dobj, &mechanism); if (err) goto leave; @@ -2185,7 +2197,7 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, } /* Now verify the Application PIN. */ - err = verify_chv (app, 0x80, pincb, pincb_arg); + err = verify_chv (app, 0x80, force_verify, pincb, pincb_arg); if (err) return err; @@ -2388,7 +2400,7 @@ do_decipher (app_t app, const char *keyidstr, } /* Now verify the Application PIN. */ - err = verify_chv (app, 0x80, pincb, pincb_arg); + err = verify_chv (app, 0x80, 0, pincb, pincb_arg); if (err) return err; From 1c405499388fd5bed0968ab5c6c5d1b3373537b9 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 26 Feb 2019 09:42:54 +0900 Subject: [PATCH 189/235] libdns: Avoid using compound literals. * dirmngr/dns.c (dns_inet_pton, dns_so_tcp_keep): Use automatic variables. (dns_poll, dns_send_nopipe): Likewise, adding const qualifier. -- Compound literals is a feature of C99. Because we only use C90 plus some limited features, in the project, it's better to avoid it. Besides, we make sure when it's read-only. Signed-off-by: NIIBE Yutaka --- dirmngr/dns.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/dirmngr/dns.c b/dirmngr/dns.c index 210e9f49a..0645d2f55 100644 --- a/dirmngr/dns.c +++ b/dirmngr/dns.c @@ -944,10 +944,11 @@ static int dns_sa_cmp(void *a, void *b) { #if _WIN32 static int dns_inet_pton(int af, const void *src, void *dst) { union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u; + int size_of_u = (int)sizeof u; u.sin.sin_family = af; - if (0 != WSAStringToAddressA((void *)src, af, (void *)0, (struct sockaddr *)&u, &(int){ sizeof u })) + if (0 != WSAStringToAddressA((void *)src, af, (void *)0, (struct sockaddr *)&u, &size_of_u)) return -1; switch (af) { @@ -1125,6 +1126,7 @@ static inline _Bool dns_isgraph(unsigned char c) { static int dns_poll(int fd, short events, int timeout) { fd_set rset, wset; + struct timeval tv = { timeout, 0 }; if (!events) return 0; @@ -1141,7 +1143,7 @@ static int dns_poll(int fd, short events, int timeout) { if (events & DNS_POLLOUT) FD_SET(fd, &wset); - select(fd + 1, &rset, &wset, 0, (timeout >= 0)? &(struct timeval){ timeout, 0 } : NULL); + select(fd + 1, &rset, &wset, 0, (timeout >= 0)? &tv : NULL); return 0; } /* dns_poll() */ @@ -1215,9 +1217,10 @@ static size_t dns_send_nopipe(int fd, const void *src, size_t len, int flags, dn if (!sigismember(&pending, SIGPIPE)) { int saved = error; + const struct timespec ts = { 0, 0 }; if (!count && error == EPIPE) { - while (-1 == sigtimedwait(&piped, NULL, &(struct timespec){ 0, 0 }) && errno == EINTR) + while (-1 == sigtimedwait(&piped, NULL, &ts) && errno == EINTR) ;; } @@ -7111,7 +7114,8 @@ static int dns_socket(struct sockaddr *local, int type, int *error_) { #if defined SO_NOSIGPIPE if (type != SOCK_DGRAM) { - if (0 != setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &(int){ 1 }, sizeof (int))) + const int v = 1; + if (0 != setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &v, sizeof (int))) goto soerr; } #endif @@ -7487,11 +7491,12 @@ error: static _Bool dns_so_tcp_keep(struct dns_socket *so) { struct sockaddr_storage remote; + socklen_t l = sizeof remote; if (so->tcp == -1) return 0; - if (0 != getpeername(so->tcp, (struct sockaddr *)&remote, &(socklen_t){ sizeof remote })) + if (0 != getpeername(so->tcp, (struct sockaddr *)&remote, &l)) return 0; return 0 == dns_sa_cmp(&remote, &so->remote); From 455ef62d29a112de05897139716265d07e4c6ae3 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 26 Feb 2019 10:04:09 +0900 Subject: [PATCH 190/235] libdns: Avoid using compound literals (2). * dirmngr/dns.h (dns_strsection1, dns_strsection3): Remove. (dns_strclass1, dns_strclass3): Remove. (dns_strtype1, dns_strtype3): Remove. (dns_strsection, dns_strclass, dns_strtype): Directly use the function. * dirmngr/dns.c (dns_strsection): Use automatic variable. (dns_strclass, dns_strtype): Likewise. Signed-off-by: NIIBE Yutaka --- dirmngr/dns.c | 15 +++++++++------ dirmngr/dns.h | 16 +++------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/dirmngr/dns.c b/dirmngr/dns.c index 0645d2f55..92084d112 100644 --- a/dirmngr/dns.c +++ b/dirmngr/dns.c @@ -10086,8 +10086,9 @@ static const struct { { "AR", DNS_S_ADDITIONAL }, }; -const char *(dns_strsection)(enum dns_section section, void *_dst, size_t lim) { - struct dns_buf dst = DNS_B_INTO(_dst, lim); +const char *(dns_strsection)(enum dns_section section) { + char _dst[DNS_STRMAXLEN + 1] = { 0 }; + struct dns_buf dst = DNS_B_INTO(_dst, sizeof _dst); unsigned i; for (i = 0; i < lengthof(dns_sections); i++) { @@ -10135,8 +10136,9 @@ static const struct { { "IN", DNS_C_IN }, }; -const char *(dns_strclass)(enum dns_class type, void *_dst, size_t lim) { - struct dns_buf dst = DNS_B_INTO(_dst, lim); +const char *(dns_strclass)(enum dns_class type) { + char _dst[DNS_STRMAXLEN + 1] = { 0 }; + struct dns_buf dst = DNS_B_INTO(_dst, sizeof _dst); unsigned i; for (i = 0; i < lengthof(dns_classes); i++) { @@ -10171,8 +10173,9 @@ enum dns_class dns_iclass(const char *name) { } /* dns_iclass() */ -const char *(dns_strtype)(enum dns_type type, void *_dst, size_t lim) { - struct dns_buf dst = DNS_B_INTO(_dst, lim); +const char *(dns_strtype)(enum dns_type type) { + char _dst[DNS_STRMAXLEN + 1] = { 0 }; + struct dns_buf dst = DNS_B_INTO(_dst, sizeof _dst); unsigned i; for (i = 0; i < lengthof(dns_rrtypes); i++) { diff --git a/dirmngr/dns.h b/dirmngr/dns.h index 30d0b45af..6dedfbc91 100644 --- a/dirmngr/dns.h +++ b/dirmngr/dns.h @@ -291,25 +291,15 @@ enum dns_rcode { */ #define DNS_STRMAXLEN 47 /* "QUESTION|ANSWER|AUTHORITY|ADDITIONAL" */ -DNS_PUBLIC const char *dns_strsection(enum dns_section, void *, size_t); -#define dns_strsection3(a, b, c) \ - dns_strsection((a), (b), (c)) -#define dns_strsection1(a) dns_strsection((a), (char [DNS_STRMAXLEN + 1]){ 0 }, DNS_STRMAXLEN + 1) -#define dns_strsection(...) DNS_PP_CALL(DNS_PP_XPASTE(dns_strsection, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__) +DNS_PUBLIC const char *dns_strsection(enum dns_section); DNS_PUBLIC enum dns_section dns_isection(const char *); -DNS_PUBLIC const char *dns_strclass(enum dns_class, void *, size_t); -#define dns_strclass3(a, b, c) dns_strclass((a), (b), (c)) -#define dns_strclass1(a) dns_strclass((a), (char [DNS_STRMAXLEN + 1]){ 0 }, DNS_STRMAXLEN + 1) -#define dns_strclass(...) DNS_PP_CALL(DNS_PP_XPASTE(dns_strclass, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__) +DNS_PUBLIC const char *dns_strclass(enum dns_class); DNS_PUBLIC enum dns_class dns_iclass(const char *); -DNS_PUBLIC const char *dns_strtype(enum dns_type, void *, size_t); -#define dns_strtype3(a, b, c) dns_strtype((a), (b), (c)) -#define dns_strtype1(a) dns_strtype((a), (char [DNS_STRMAXLEN + 1]){ 0 }, DNS_STRMAXLEN + 1) -#define dns_strtype(...) DNS_PP_CALL(DNS_PP_XPASTE(dns_strtype, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__) +DNS_PUBLIC const char *dns_strtype(enum dns_type); DNS_PUBLIC enum dns_type dns_itype(const char *); From 72efb7840258808cd892b90d871ea1cc1c31d7f5 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 26 Feb 2019 10:34:03 +0900 Subject: [PATCH 191/235] libdns: Avoid using compound literals (3). * dirmngr/dns.h (dns_p_new): Remove. * dirmngr/dns.c (dns_hosts_query): Use dns_p_init with automatic variable. (dns_hints_query, dns_res_glue, parse_packet, query_hosts) (send_query, show_hints, echo_port): Likewise. -- Implicit automatic allocation by compound literals is confusing for C90 code. Signed-off-by: NIIBE Yutaka --- dirmngr/dns.c | 27 ++++++++++++++++++--------- dirmngr/dns.h | 3 --- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/dirmngr/dns.c b/dirmngr/dns.c index 92084d112..6acb4faa2 100644 --- a/dirmngr/dns.c +++ b/dirmngr/dns.c @@ -5278,7 +5278,8 @@ error: struct dns_packet *dns_hosts_query(struct dns_hosts *hosts, struct dns_packet *Q, int *error_) { - struct dns_packet *P = dns_p_new(512); + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _P = { 0 }; + struct dns_packet *P = dns_p_init(&_P.p, 512); struct dns_packet *A = 0; struct dns_rr rr; struct dns_hosts_entry *ent; @@ -6839,6 +6840,7 @@ unsigned dns_hints_grep(struct sockaddr **sa, socklen_t *sa_len, unsigned lim, s struct dns_packet *dns_hints_query(struct dns_hints *hints, struct dns_packet *Q, int *error_) { + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _P = { 0 }; struct dns_packet *A, *P; struct dns_rr rr; char zone[DNS_D_MAXNAME + 1]; @@ -6856,7 +6858,7 @@ struct dns_packet *dns_hints_query(struct dns_hints *hints, struct dns_packet *Q else if (zlen >= sizeof zone) goto toolong; - P = dns_p_new(512); + P = dns_p_init(&_P.p, 512); dns_header(P)->qr = 1; if ((error = dns_rr_copy(P, &rr, Q))) @@ -8461,7 +8463,8 @@ error: static struct dns_packet *dns_res_glue(struct dns_resolver *R, struct dns_packet *Q) { - struct dns_packet *P = dns_p_new(512); + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _P = { 0 }; + struct dns_packet *P = dns_p_init(&_P.p, 512); char qname[DNS_D_MAXNAME + 1]; size_t qlen; enum dns_type qtype; @@ -10586,8 +10589,10 @@ static void print_packet(struct dns_packet *P, FILE *fp) { static int parse_packet(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { - struct dns_packet *P = dns_p_new(512); - struct dns_packet *Q = dns_p_new(512); + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _P = { 0 }; + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _Q = { 0 }; + struct dns_packet *P = dns_p_init(&_P.p, 512); + struct dns_packet *Q = dns_p_init(&_Q.p, 512); enum dns_section section; struct dns_rr rr; int error; @@ -10787,7 +10792,8 @@ static int show_hosts(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { static int query_hosts(int argc, char *argv[]) { - struct dns_packet *Q = dns_p_new(512); + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _Q = { 0 }; + struct dns_packet *Q = dns_p_init(&_Q.p, 512); struct dns_packet *A; char qname[DNS_D_MAXNAME + 1]; size_t qlen; @@ -10905,7 +10911,8 @@ static int dump_random(int argc, char *argv[]) { static int send_query(int argc, char *argv[]) { - struct dns_packet *A, *Q = dns_p_new(512); + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _Q = { 0 }; + struct dns_packet *A, *Q = dns_p_init(&_Q.p, 512); char host[INET6_ADDRSTRLEN + 1]; struct sockaddr_storage ss; struct dns_socket *so; @@ -10999,9 +11006,10 @@ static int show_hints(int argc, char *argv[]) { if (0 == strcmp(how, "plain")) { dns_hints_dump(hints, stdout); } else { + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _P = { 0 }; struct dns_packet *query, *answer; - query = dns_p_new(512); + query = dns_p_init(&_P.p, 512); if ((error = dns_p_push(query, DNS_S_QUESTION, who, strlen(who), DNS_T_A, DNS_C_IN, 0, 0))) panic("%s: %s", who, dns_strerror(error)); @@ -11160,7 +11168,8 @@ static int echo_port(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { panic("127.0.0.1:5353: %s", dns_strerror(errno)); for (;;) { - struct dns_packet *pkt = dns_p_new(512); + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _P = { 0 }; + struct dns_packet *pkt = dns_p_init(&_P.p, 512); struct sockaddr_storage ss; socklen_t slen = sizeof ss; ssize_t count; diff --git a/dirmngr/dns.h b/dirmngr/dns.h index 6dedfbc91..4a9417120 100644 --- a/dirmngr/dns.h +++ b/dirmngr/dns.h @@ -412,9 +412,6 @@ struct dns_packet { #define dns_p_sizeof(P) dns_p_calcsize((P)->end) -/** takes size of maximum desired payload */ -#define dns_p_new(n) (dns_p_init((struct dns_packet *)&(union { unsigned char b[dns_p_calcsize((n))]; struct dns_packet p; }){ { 0 } }, dns_p_calcsize((n)))) - /** takes size of entire packet structure as allocated */ DNS_PUBLIC struct dns_packet *dns_p_init(struct dns_packet *, size_t); From 7313a112f9c7ada61d24285313d2e2d069a672e8 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 26 Feb 2019 10:58:16 +0900 Subject: [PATCH 192/235] libdns: Avoid using compound literals (4). * dirmngr/dns.h (dns_d_new*): Remove. * dirmngr/dns.c (parse_packet): Use dns_d_init with automatic variable. (parse_domain): Likewise. Signed-off-by: NIIBE Yutaka --- dirmngr/dns.c | 8 ++++++-- dirmngr/dns.h | 5 ----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/dirmngr/dns.c b/dirmngr/dns.c index 6acb4faa2..f6a158789 100644 --- a/dirmngr/dns.c +++ b/dirmngr/dns.c @@ -10632,8 +10632,11 @@ static int parse_packet(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { #if 0 dns_rr_foreach(&rr, Q, .name = "ns8.yahoo.com.") { #else + char _p[DNS_D_MAXNAME + 1] = { 0 }; + const char *dn = "ns8.yahoo.com"; + char *_name = dns_d_init(_p, sizeof _p, dn, strlen (dn), DNS_D_ANCHOR); struct dns_rr rrset[32]; - struct dns_rr_i *rri = dns_rr_i_new(Q, .name = dns_d_new("ns8.yahoo.com", DNS_D_ANCHOR), .sort = MAIN.sort); + struct dns_rr_i *rri = dns_rr_i_new(Q, .name = _name, .sort = MAIN.sort); unsigned rrcount = dns_rr_grep(rrset, lengthof(rrset), rri, Q, &error); for (unsigned i = 0; i < rrcount; i++) { @@ -10661,13 +10664,14 @@ static int parse_packet(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { static int parse_domain(int argc, char *argv[]) { + char _p[DNS_D_MAXNAME + 1] = { 0 }; char *dn; dn = (argc > 1)? argv[1] : "f.l.google.com"; printf("[%s]\n", dn); - dn = dns_d_new(dn); + dn = dns_d_init(_p, sizeof _p, dn, strlen (dn), DNS_D_ANCHOR); do { puts(dn); diff --git a/dirmngr/dns.h b/dirmngr/dns.h index 4a9417120..0953228f8 100644 --- a/dirmngr/dns.h +++ b/dirmngr/dns.h @@ -451,11 +451,6 @@ DNS_PUBLIC int dns_p_study(struct dns_packet *); #define DNS_D_CLEAVE 2 /* cleave sub-domain */ #define DNS_D_TRIM 4 /* remove superfluous dots */ -#define dns_d_new3(a, b, f) dns_d_init(&(char[DNS_D_MAXNAME + 1]){ 0 }, DNS_D_MAXNAME + 1, (a), (b), (f)) -#define dns_d_new2(a, f) dns_d_new3((a), strlen((a)), (f)) -#define dns_d_new1(a) dns_d_new3((a), strlen((a)), DNS_D_ANCHOR) -#define dns_d_new(...) DNS_PP_CALL(DNS_PP_XPASTE(dns_d_new, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__) - DNS_PUBLIC char *dns_d_init(void *, size_t, const void *, size_t, int); DNS_PUBLIC size_t dns_d_anchor(void *, size_t, const void *, size_t); From a1ccfe2b37847cce0db2fb94a7365c9fa501eda4 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 26 Feb 2019 11:43:10 +0900 Subject: [PATCH 193/235] libdns: Avoid using compound literals (5). * dirmngr/dns.h (dns_rr_foreach): Don't use dns_rr_i_new. Call dns_rr_grep with NULL. * dirmngr/dns.c (dns_rr_grep): Support NULL for error_. -- Here we still use C99 feature of struct member initialization in dns_rr_foreach, for struct dns_rr_i. Note that in C99, it guarantees non-specified member fields are initialized by zero. So, there's no need to use dns_rr_i_new at all. Signed-off-by: NIIBE Yutaka --- dirmngr/dns.c | 3 ++- dirmngr/dns.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dirmngr/dns.c b/dirmngr/dns.c index f6a158789..85fd37fb2 100644 --- a/dirmngr/dns.c +++ b/dirmngr/dns.c @@ -3266,7 +3266,8 @@ unsigned dns_rr_grep(struct dns_rr *rr, unsigned lim, struct dns_rr_i *i, struct return count; error: - *error_ = error; + if (error_) + *error_ = error; return count; } /* dns_rr_grep() */ diff --git a/dirmngr/dns.h b/dirmngr/dns.h index 0953228f8..da450c611 100644 --- a/dirmngr/dns.h +++ b/dirmngr/dns.h @@ -542,7 +542,7 @@ DNS_PUBLIC struct dns_rr_i *dns_rr_i_init(struct dns_rr_i *, struct dns_packet * DNS_PUBLIC unsigned dns_rr_grep(struct dns_rr *, unsigned, struct dns_rr_i *, struct dns_packet *, int *); #define dns_rr_foreach_(rr, P, ...) \ - for (struct dns_rr_i DNS_PP_XPASTE(i, __LINE__) = *dns_rr_i_new((P), __VA_ARGS__); dns_rr_grep((rr), 1, &DNS_PP_XPASTE(i, __LINE__), (P), &(int){ 0 }); ) + for (struct dns_rr_i DNS_PP_XPASTE(i, __LINE__) = { __VA_ARGS__ }; dns_rr_grep((rr), 1, &DNS_PP_XPASTE(i, __LINE__), (P), NULL); ) #define dns_rr_foreach(...) dns_rr_foreach_(__VA_ARGS__) From 6501e59d3685bb58753c9caea729a4b0eca3942a Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 26 Feb 2019 11:55:32 +0900 Subject: [PATCH 194/235] libdns: Avoid using compound literals (6). * dirmngr/dns.h (dns_rr_i_new): Remove. (dns_rr_i_init): Remove unused second argument. * dirmngr/dns.c (dns_p_dump, dns_hints_query, print_packet) (parse_packet): Use automatic variable for struct dns_rr_i. (dns_d_cname): No need to call dns_rr_i_init after memset 0. (dns_rr_i_init): Remove unused second argument. Return nothing. * dirmngr/dns-stuff.c (resolve_addr_libdns, get_dns_cert_libdns) (getsrv_libdns): Follow the change of dns_rr_i_init. Signed-off-by: NIIBE Yutaka --- dirmngr/dns-stuff.c | 6 ++-- dirmngr/dns.c | 73 +++++++++++++++++++++++++++++---------------- dirmngr/dns.h | 5 +--- 3 files changed, 51 insertions(+), 33 deletions(-) diff --git a/dirmngr/dns-stuff.c b/dirmngr/dns-stuff.c index 7aa07c716..a1aa3145e 100644 --- a/dirmngr/dns-stuff.c +++ b/dirmngr/dns-stuff.c @@ -1168,7 +1168,7 @@ resolve_addr_libdns (ctrl_t ctrl, struct dns_rr_i rri; memset (&rri, 0, sizeof rri); - dns_rr_i_init (&rri, ans); + dns_rr_i_init (&rri); rri.section = DNS_S_ALL & ~DNS_S_QD; rri.name = host; rri.type = DNS_T_PTR; @@ -1459,7 +1459,7 @@ get_dns_cert_libdns (ctrl_t ctrl, const char *name, int want_certtype, goto leave; memset (&rri, 0, sizeof rri); - dns_rr_i_init (&rri, ans); + dns_rr_i_init (&rri); rri.section = DNS_S_ALL & ~DNS_S_QD; rri.name = host; rri.type = qtype; @@ -1889,7 +1889,7 @@ getsrv_libdns (ctrl_t ctrl, goto leave; memset (&rri, 0, sizeof rri); - dns_rr_i_init (&rri, ans); + dns_rr_i_init (&rri); rri.section = DNS_S_ALL & ~DNS_S_QD; rri.name = host; rri.type = DNS_T_SRV; diff --git a/dirmngr/dns.c b/dirmngr/dns.c index 85fd37fb2..9da44cd77 100644 --- a/dirmngr/dns.c +++ b/dirmngr/dns.c @@ -2217,7 +2217,8 @@ static void dns_p_dump3(struct dns_packet *P, struct dns_rr_i *I, FILE *fp) { void dns_p_dump(struct dns_packet *P, FILE *fp) { - dns_p_dump3(P, dns_rr_i_new(P, .section = 0), fp); + struct dns_rr_i _I = { 0 }; + dns_p_dump3(P, &_I, fp); } /* dns_p_dump() */ @@ -2796,8 +2797,7 @@ size_t dns_d_cname(void *dst, size_t lim, const void *dn, size_t len, struct dns { error = ENAMETOOLONG; goto error; } for (depth = 0; depth < 7; depth++) { - dns_rr_i_init(memset(&i, 0, sizeof i), P); - + memset(&i, 0, sizeof i); i.section = DNS_S_ALL & ~DNS_S_QD; i.name = host; i.type = DNS_T_CNAME; @@ -3222,15 +3222,11 @@ int dns_rr_i_shuffle(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, str } /* dns_rr_i_shuffle() */ -struct dns_rr_i *dns_rr_i_init(struct dns_rr_i *i, struct dns_packet *P) { +void dns_rr_i_init(struct dns_rr_i *i) { static const struct dns_rr_i i_initializer; - (void)P; - i->state = i_initializer.state; i->saved = i->state; - - return i; } /* dns_rr_i_init() */ @@ -6850,8 +6846,11 @@ struct dns_packet *dns_hints_query(struct dns_hints *hints, struct dns_packet *Q struct sockaddr *sa; socklen_t slen; int error; + struct dns_rr_i _I = { 0 }; - if (!dns_rr_grep(&rr, 1, dns_rr_i_new(Q, .section = DNS_S_QUESTION), Q, &error)) + _I.section = DNS_S_QUESTION; + + if (!dns_rr_grep(&rr, 1, &_I, Q, &error)) goto error; if (!(zlen = dns_d_expand(zone, sizeof zone, rr.dn.p, Q, &error))) @@ -8537,12 +8536,22 @@ static int dns_res_nameserv_cmp(struct dns_rr *a, struct dns_rr *b, struct dns_r struct dns_ns ns; int cmp, error; - if (!(error = dns_ns_parse(&ns, a, P))) - glued[0] = !!dns_rr_grep(&x, 1, dns_rr_i_new(P, .section = (DNS_S_ALL & ~DNS_S_QD), .name = ns.host, .type = DNS_T_A), P, &error); + if (!(error = dns_ns_parse(&ns, a, P))) { + struct dns_rr_i _I = { 0 }; - if (!(error = dns_ns_parse(&ns, b, P))) - glued[1] = !!dns_rr_grep(&y, 1, dns_rr_i_new(P, .section = (DNS_S_ALL & ~DNS_S_QD), .name = ns.host, .type = DNS_T_A), P, &error); + _I.section = (DNS_S_ALL & ~DNS_S_QD); + _I.name = ns.host; + _I.type = DNS_T_A; + glued[0] = !!dns_rr_grep(&x, 1, &_I, P, &error); + } + if (!(error = dns_ns_parse(&ns, b, P))) { + struct dns_rr_i _I = { 0 }; + _I.section = (DNS_S_ALL & ~DNS_S_QD); + _I.name = ns.host; + _I.type = DNS_T_A; + glued[1] = !!dns_rr_grep(&y, 1, &_I, P, &error); + } if ((cmp = glued[1] - glued[0])) { return cmp; } else if ((cmp = (dns_rr_offset(&y) < i->args[0]) - (dns_rr_offset(&x) < i->args[0]))) { @@ -8743,7 +8752,7 @@ exec: F->state++; /* FALL THROUGH */ case DNS_R_ITERATE: - dns_rr_i_init(&F->hints_i, F->hints); + dns_rr_i_init(&F->hints_i); F->hints_i.section = DNS_S_AUTHORITY; F->hints_i.type = DNS_T_NS; @@ -8762,7 +8771,7 @@ exec: dgoto(R->sp, DNS_R_SWITCH); } - dns_rr_i_init(&F->hints_j, F->hints); + dns_rr_i_init(&F->hints_j); /* Assume there are glue records */ dgoto(R->sp, DNS_R_FOREACH_A); @@ -8815,14 +8824,14 @@ exec: if (!dns_rr_i_count(&F->hints_j)) { /* Check if we have in fact servers with an IPv6 address. */ - dns_rr_i_init(&F->hints_j, F->hints); + dns_rr_i_init(&F->hints_j); F->hints_j.name = u.ns.host; F->hints_j.type = DNS_T_AAAA; F->hints_j.section = DNS_S_ALL & ~DNS_S_QD; if (dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) { /* We do. Reinitialize iterator and handle it. */ - dns_rr_i_init(&F->hints_j, F->hints); + dns_rr_i_init(&F->hints_j); dgoto(R->sp, DNS_R_FOREACH_AAAA); } @@ -8951,14 +8960,14 @@ exec: if (!dns_rr_i_count(&F->hints_j)) { /* Check if we have in fact servers with an IPv4 address. */ - dns_rr_i_init(&F->hints_j, F->hints); + dns_rr_i_init(&F->hints_j); F->hints_j.name = u.ns.host; F->hints_j.type = DNS_T_A; F->hints_j.section = DNS_S_ALL & ~DNS_S_QD; if (dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) { /* We do. Reinitialize iterator and handle it. */ - dns_rr_i_init(&F->hints_j, F->hints); + dns_rr_i_init(&F->hints_j); dgoto(R->sp, DNS_R_FOREACH_A); } @@ -9096,7 +9105,7 @@ exec: R->smart.section = DNS_S_AN; R->smart.type = R->qtype; - dns_rr_i_init(&R->smart, F->answer); + dns_rr_i_init(&R->smart); F->state++; /* FALL THROUGH */ case DNS_R_SMART0_A: @@ -9840,7 +9849,7 @@ exec: return error; dns_strlcpy(ai->i_cname, ai->cname, sizeof ai->i_cname); - dns_rr_i_init(&ai->i, ai->answer); + dns_rr_i_init(&ai->i); ai->i.section = DNS_S_AN; ai->i.name = ai->i_cname; ai->i.type = dns_ai_qtype(ai); @@ -9887,7 +9896,7 @@ exec: ai->state++; /* FALL THROUGH */ case DNS_AI_S_ITERATE_G: dns_strlcpy(ai->g_cname, ai->cname, sizeof ai->g_cname); - dns_rr_i_init(&ai->g, ai->glue); + dns_rr_i_init(&ai->g); ai->g.section = DNS_S_ALL & ~DNS_S_QD; ai->g.name = ai->g_cname; ai->g.type = ai->af.qtype; @@ -9906,8 +9915,14 @@ exec: return dns_ai_setent(ent, &any, rr.type, ai); case DNS_AI_S_SUBMIT_G: + { + struct dns_rr_i _I = { 0 }; + + _I.section = DNS_S_QD; + _I.name = ai->g.name; + _I.type = ai->g.type; /* skip if already queried */ - if (dns_rr_grep(&rr, 1, dns_rr_i_new(ai->glue, .section = DNS_S_QD, .name = ai->g.name, .type = ai->g.type), ai->glue, &error)) + if (dns_rr_grep(&rr, 1, &_I, ai->glue, &error)) dns_ai_goto(DNS_AI_S_FOREACH_I); /* skip if we recursed (CNAME chains should have been handled in the resolver) */ if (++ai->g_depth > 1) @@ -9916,7 +9931,8 @@ exec: if ((error = dns_res_submit(ai->res, ai->g.name, ai->g.type, DNS_C_IN))) return error; - ai->state++; /* FALL THROUGH */ + ai->state++; + } /* FALL THROUGH */ case DNS_AI_S_CHECK_G: if ((error = dns_res_check(ai->res))) return error; @@ -10582,7 +10598,9 @@ static struct dns_trace *trace(const char *mode) { static void print_packet(struct dns_packet *P, FILE *fp) { - dns_p_dump3(P, dns_rr_i_new(P, .sort = MAIN.sort), fp); + struct dns_rr_i _I = { 0 }; + I.sort = MAIN.sort; + dns_p_dump3(P, &I, fp); if (MAIN.verbose > 2) hexdump(P->data, P->end, fp); @@ -10637,9 +10655,12 @@ static int parse_packet(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { const char *dn = "ns8.yahoo.com"; char *_name = dns_d_init(_p, sizeof _p, dn, strlen (dn), DNS_D_ANCHOR); struct dns_rr rrset[32]; - struct dns_rr_i *rri = dns_rr_i_new(Q, .name = _name, .sort = MAIN.sort); + struct dns_rr_i _I = { 0 }; + struct dns_rr_i *rri = &I; unsigned rrcount = dns_rr_grep(rrset, lengthof(rrset), rri, Q, &error); + I.name = _name; + I.sort = MAIN.sort; for (unsigned i = 0; i < rrcount; i++) { rr = rrset[i]; #endif diff --git a/dirmngr/dns.h b/dirmngr/dns.h index da450c611..49b1c78bb 100644 --- a/dirmngr/dns.h +++ b/dirmngr/dns.h @@ -503,9 +503,6 @@ DNS_PUBLIC int dns_rr_cmp(struct dns_rr *, struct dns_packet *, struct dns_rr *, DNS_PUBLIC size_t dns_rr_print(void *, size_t, struct dns_rr *, struct dns_packet *, int *); -#define dns_rr_i_new(P, ...) \ - dns_rr_i_init(&dns_quietinit((struct dns_rr_i){ 0, __VA_ARGS__ }), (P)) - struct dns_rr_i { enum dns_section section; const void *name; @@ -533,7 +530,7 @@ DNS_PUBLIC int dns_rr_i_order(struct dns_rr *, struct dns_rr *, struct dns_rr_i DNS_PUBLIC int dns_rr_i_shuffle(struct dns_rr *, struct dns_rr *, struct dns_rr_i *, struct dns_packet *); -DNS_PUBLIC struct dns_rr_i *dns_rr_i_init(struct dns_rr_i *, struct dns_packet *); +DNS_PUBLIC void dns_rr_i_init(struct dns_rr_i *); #define dns_rr_i_save(i) ((i)->saved = (i)->state) #define dns_rr_i_rewind(i) ((i)->state = (i)->saved) From d661acd483236d34720a4959fc816d05f89c2cb7 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 26 Feb 2019 12:13:35 +0900 Subject: [PATCH 195/235] libdns: Avoid using compound literals (7). * dirmngr/dns.h (DNS_OPTS_INIT, dns_opts): Remove. * dirmngr/dns-stuff.c (libdns_res_open): Use zero-ed, and initialized automatic variable for opts. * dirmngr/dns.c (send_query, resolve_query, resolve_addrinfo): Likewise. -- In fact, DNS_OPTS_INIT was only needed when args are none. With partially specified initialization, C99 guarantees zero-ed other members just like static object. Signed-off-by: NIIBE Yutaka --- dirmngr/dns-stuff.c | 10 ++++++---- dirmngr/dns.c | 15 ++++++++++----- dirmngr/dns.h | 3 --- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/dirmngr/dns-stuff.c b/dirmngr/dns-stuff.c index a1aa3145e..e48aca730 100644 --- a/dirmngr/dns-stuff.c +++ b/dirmngr/dns-stuff.c @@ -701,6 +701,11 @@ libdns_res_open (ctrl_t ctrl, struct dns_resolver **r_res) gpg_error_t err; struct dns_resolver *res; int derr; + struct dns_options opts = { 0 }; + + opts.socks_host = &libdns.socks_host; + opts.socks_user = tor_socks_user; + opts.socks_password = tor_socks_password; *r_res = NULL; @@ -726,10 +731,7 @@ libdns_res_open (ctrl_t ctrl, struct dns_resolver **r_res) set_dns_timeout (0); res = dns_res_open (libdns.resolv_conf, libdns.hosts, libdns.hints, NULL, - dns_opts (.socks_host = &libdns.socks_host, - .socks_user = tor_socks_user, - .socks_password = tor_socks_password ), - &derr); + &opts, &derr); if (!res) return libdns_error_to_gpg_error (derr); diff --git a/dirmngr/dns.c b/dirmngr/dns.c index 9da44cd77..fa5e5283d 100644 --- a/dirmngr/dns.c +++ b/dirmngr/dns.c @@ -10943,6 +10943,7 @@ static int send_query(int argc, char *argv[]) { struct sockaddr_storage ss; struct dns_socket *so; int error, type; + struct dns_options opts = { 0 }; memset(&ss, 0, sizeof ss); if (argc > 1) { @@ -10977,7 +10978,7 @@ static int send_query(int argc, char *argv[]) { fprintf(stderr, "querying %s for %s IN %s\n", host, MAIN.qname, dns_strtype(MAIN.qtype)); - if (!(so = dns_so_open((struct sockaddr *)&resconf()->iface, type, dns_opts(), &error))) + if (!(so = dns_so_open((struct sockaddr *)&resconf()->iface, type, &opts, &error))) panic("dns_so_open: %s", dns_strerror(error)); while (!(A = dns_so_query(so, Q, (struct sockaddr *)&ss, &error))) { @@ -11061,6 +11062,11 @@ static int resolve_query(int argc DNS_NOTUSED, char *argv[]) { struct dns_packet *ans; const struct dns_stat *st; int error; + struct dns_options opts = { 0 }; + + opts.socks_host = &MAIN.socks_host; + opts.socks_user = MAIN.socks_user; + opts.socks_password = MAIN.socks_password; if (!MAIN.qname) MAIN.qname = "www.google.com"; @@ -11070,9 +11076,7 @@ static int resolve_query(int argc DNS_NOTUSED, char *argv[]) { resconf()->options.recurse = recurse; if (!(R = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), - dns_opts(.socks_host=&MAIN.socks_host, - .socks_user=MAIN.socks_user, - .socks_password=MAIN.socks_password), &error))) + &opts, &error))) panic("%s: %s", MAIN.qname, dns_strerror(error)); dns_res_settrace(R, trace("w+b")); @@ -11116,6 +11120,7 @@ static int resolve_addrinfo(int argc DNS_NOTUSED, char *argv[]) { struct addrinfo *ent; char pretty[512]; int error; + struct dns_options opts = { 0 }; if (!MAIN.qname) MAIN.qname = "www.google.com"; @@ -11123,7 +11128,7 @@ static int resolve_addrinfo(int argc DNS_NOTUSED, char *argv[]) { resconf()->options.recurse = recurse; - if (!(res = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), dns_opts(), &error))) + if (!(res = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), &opts, &error))) panic("%s: %s", MAIN.qname, dns_strerror(error)); if (!(ai = dns_ai_open(MAIN.qname, "80", MAIN.qtype, &ai_hints, res, &error))) diff --git a/dirmngr/dns.h b/dirmngr/dns.h index 49b1c78bb..afc19a19a 100644 --- a/dirmngr/dns.h +++ b/dirmngr/dns.h @@ -1032,9 +1032,6 @@ DNS_PUBLIC void dns_cache_close(struct dns_cache *); #define DNS_OPTS_INITIALIZER_ { 0, 0 }, 0, 0 #define DNS_OPTS_INITIALIZER { DNS_OPTS_INITIALIZER_ } -#define DNS_OPTS_INIT(...) { DNS_OPTS_INITIALIZER_, __VA_ARGS__ } - -#define dns_opts(...) (&dns_quietinit((struct dns_options)DNS_OPTS_INIT(__VA_ARGS__))) struct dns_options { /* From 371ae25f8f6f2d1ac030bf984bca479393a5ed43 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 26 Feb 2019 12:26:02 +0900 Subject: [PATCH 196/235] libdns: Avoid using compound literals (8). * dirmngr/dns.h (dns_quietinit): Remove. (dns_hints_i_new): Remove. -- Even before our change, dns_quietinit was questionable macro; There was no place in dns.c which requires overrides in initializer list. Only redundant zero were. Signed-off-by: NIIBE Yutaka --- dirmngr/dns.h | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/dirmngr/dns.h b/dirmngr/dns.h index afc19a19a..024d6dcc8 100644 --- a/dirmngr/dns.h +++ b/dirmngr/dns.h @@ -132,19 +132,6 @@ DNS_PUBLIC int *dns_debug_p(void); /* * C O M P I L E R A N N O T A T I O N S * - * GCC with -Wextra, and clang by default, complain about overrides in - * initializer lists. Overriding previous member initializers is well - * defined behavior in C. dns.c relies on this behavior to define default, - * overrideable member values when instantiating configuration objects. - * - * dns_quietinit() guards a compound literal expression with pragmas to - * silence these shrill warnings. This alleviates the burden of requiring - * third-party projects to adjust their compiler flags. - * - * NOTE: If you take the address of the compound literal, take the address - * of the transformed expression, otherwise the compound literal lifetime is - * tied to the scope of the GCC statement expression. - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #if defined __clang__ @@ -152,21 +139,15 @@ DNS_PUBLIC int *dns_debug_p(void); #define DNS_PRAGMA_QUIET _Pragma("clang diagnostic ignored \"-Winitializer-overrides\"") #define DNS_PRAGMA_POP _Pragma("clang diagnostic pop") -#define dns_quietinit(...) \ - DNS_PRAGMA_PUSH DNS_PRAGMA_QUIET __VA_ARGS__ DNS_PRAGMA_POP #elif (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) || __GNUC__ > 4 #define DNS_PRAGMA_PUSH _Pragma("GCC diagnostic push") #define DNS_PRAGMA_QUIET _Pragma("GCC diagnostic ignored \"-Woverride-init\"") #define DNS_PRAGMA_POP _Pragma("GCC diagnostic pop") -/* GCC parses the _Pragma operator less elegantly than clang. */ -#define dns_quietinit(...) \ - __extension__ ({ DNS_PRAGMA_PUSH DNS_PRAGMA_QUIET __VA_ARGS__; DNS_PRAGMA_POP }) #else #define DNS_PRAGMA_PUSH #define DNS_PRAGMA_QUIET #define DNS_PRAGMA_POP -#define dns_quietinit(...) __VA_ARGS__ #endif #if defined __GNUC__ @@ -980,7 +961,6 @@ struct dns_hints_i { } state; }; /* struct dns_hints_i */ -#define dns_hints_i_new(...) (&(struct dns_hints_i){ __VA_ARGS__ }) DNS_PUBLIC unsigned dns_hints_grep(struct sockaddr **, socklen_t *, unsigned, struct dns_hints_i *, struct dns_hints *); From ac485b4f253ad6bbd2bc648650b56d60fc82f89d Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 25 Feb 2019 15:55:13 +0100 Subject: [PATCH 197/235] conf: New option --show-socket. * tools/gpgconf-comp.c (gc_component_t): Move this enum to ... * tools/gpgconf.h: here. * tools/gpgconf.c (oShowSocket): New. (opts): Add new option. (main): Implement new option. -- This is a convenience options for software which directly connects to gpg-agent and thus needs to new the socket. By using --show-socket along with --launch that software can also autostart the agent or the dirmngr. Without this two calls to gpgconf would be required. Actually the same behaviour can be achieved by running gpg-connect-agent to query the running gpg-agent's socket via GETINFO. The gpg-connect also makes sure that the agent is started. This is not anymore suggested because gpgconf shall in future be used for all such things. Signed-off-by: Werner Koch --- tools/gpgconf-comp.c | 28 ---------------------------- tools/gpgconf.c | 21 ++++++++++++++++++++- tools/gpgconf.h | 28 ++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 29 deletions(-) diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index 924f90785..2ae79d91d 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -1066,34 +1066,6 @@ static gc_option_t gc_options_pinentry[] = -/* Component system. Each component is a set of options that can be - configured at the same time. If you change this, don't forget to - update GC_COMPONENT below. */ -typedef enum - { - /* The classic GPG for OpenPGP. */ - GC_COMPONENT_GPG, - - /* The GPG Agent. */ - GC_COMPONENT_GPG_AGENT, - - /* The Smardcard Daemon. */ - GC_COMPONENT_SCDAEMON, - - /* GPG for S/MIME. */ - GC_COMPONENT_GPGSM, - - /* The LDAP Directory Manager for CRLs. */ - GC_COMPONENT_DIRMNGR, - - /* The external Pinentry. */ - GC_COMPONENT_PINENTRY, - - /* The number of components. */ - GC_COMPONENT_NR - } gc_component_t; - - /* The information associated with each component. */ static const struct { diff --git a/tools/gpgconf.c b/tools/gpgconf.c index df3ae8cab..b67125b89 100644 --- a/tools/gpgconf.c +++ b/tools/gpgconf.c @@ -47,6 +47,7 @@ enum cmd_and_opt_values oHomedir, oBuilddir, oStatusFD, + oShowSocket, aListComponents, aCheckPrograms, @@ -108,6 +109,7 @@ static ARGPARSE_OPTS opts[] = { oBuilddir, "build-prefix", 2, "@" }, { oNull, "null", 0, "@" }, { oNoVerbose, "no-verbose", 0, "@"}, + ARGPARSE_s_n (oShowSocket, "show-socket", "@"), ARGPARSE_end(), }; @@ -525,6 +527,7 @@ main (int argc, char **argv) int no_more_options = 0; enum cmd_and_opt_values cmd = 0; estream_t outfp = NULL; + int show_socket = 0; early_system_init (); gnupg_reopen_std (GPGCONF_NAME); @@ -558,6 +561,7 @@ main (int argc, char **argv) case oStatusFD: set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); break; + case oShowSocket: show_socket = 1; break; case aListDirs: case aListComponents: @@ -682,7 +686,22 @@ main (int argc, char **argv) } else if (cmd == aLaunch) { - if (gc_component_launch (idx)) + err = gc_component_launch (idx); + if (show_socket) + { + char *names[2]; + + if (idx == GC_COMPONENT_GPG_AGENT) + names[0] = "agent-socket"; + else if (idx == GC_COMPONENT_DIRMNGR) + names[0] = "dirmngr-socket"; + else + names[0] = NULL; + names[1] = NULL; + get_outfp (&outfp); + list_dirs (outfp, names); + } + if (err) gpgconf_failure (0); } else diff --git a/tools/gpgconf.h b/tools/gpgconf.h index 8a061ef68..192259789 100644 --- a/tools/gpgconf.h +++ b/tools/gpgconf.h @@ -43,6 +43,34 @@ void gpgconf_failure (gpg_error_t err) GPGRT_ATTR_NORETURN; /*-- gpgconf-comp.c --*/ +/* Component system. Each component is a set of options that can be + * configured at the same time. If you change this, don't forget to + * update GC_COMPONENT in gpgconf-comp.c. */ +typedef enum + { + /* The classic GPG for OpenPGP. */ + GC_COMPONENT_GPG, + + /* The GPG Agent. */ + GC_COMPONENT_GPG_AGENT, + + /* The Smardcard Daemon. */ + GC_COMPONENT_SCDAEMON, + + /* GPG for S/MIME. */ + GC_COMPONENT_GPGSM, + + /* The LDAP Directory Manager for CRLs. */ + GC_COMPONENT_DIRMNGR, + + /* The external Pinentry. */ + GC_COMPONENT_PINENTRY, + + /* The number of components. */ + GC_COMPONENT_NR + } gc_component_t; + + /* Initialize the components. */ void gc_components_init (void); From c2235d994dbb1d7ddba20f89a7c02f4a27b0610c Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 26 Feb 2019 16:42:50 +0100 Subject: [PATCH 198/235] scd: Simplify the app_readkey parameters. * scd/app-help.c (app_help_pubkey_from_cert): New. * scd/command.c (cmd_readkey): Refactor to use that new function and handle the --advanced flag only here. * scd/app.c (app_readkey): Remove parm advanced. * scd/app-common.h (struct app_ctx_s): Remove parm advanced from the readkey member. * scd/app-nks.c (do_readkey): Adjust for removed parm. * scd/app-piv.c (do_readkey): Ditto. * scd/app-openpgp.c (do_readkey): Ditto. Signed-off-by: Werner Koch --- scd/app-common.h | 6 ++-- scd/app-help.c | 45 +++++++++++++++++++++++-- scd/app-nks.c | 8 ++--- scd/app-openpgp.c | 46 +++++--------------------- scd/app-piv.c | 17 ++-------- scd/app.c | 4 +-- scd/command.c | 84 +++++++++++++++++++---------------------------- 7 files changed, 93 insertions(+), 117 deletions(-) diff --git a/scd/app-common.h b/scd/app-common.h index e9d167ecc..97274a7cb 100644 --- a/scd/app-common.h +++ b/scd/app-common.h @@ -71,7 +71,7 @@ struct app_ctx_s { gpg_error_t (*learn_status) (app_t app, ctrl_t ctrl, unsigned int flags); gpg_error_t (*readcert) (app_t app, const char *certid, unsigned char **cert, size_t *certlen); - gpg_error_t (*readkey) (app_t app, int advanced, const char *certid, + gpg_error_t (*readkey) (app_t app, const char *certid, unsigned char **pk, size_t *pklen); gpg_error_t (*getattr) (app_t app, ctrl_t ctrl, const char *name); gpg_error_t (*setattr) (app_t app, const char *name, @@ -123,6 +123,8 @@ struct app_ctx_s { /*-- app-help.c --*/ unsigned int app_help_count_bits (const unsigned char *a, size_t len); gpg_error_t app_help_get_keygrip_string (ksba_cert_t cert, char *hexkeygrip); +gpg_error_t app_help_pubkey_from_cert (const void *cert, size_t certlen, + unsigned char **r_pk, size_t *r_pklen); size_t app_help_read_length_of_cert (int slot, int fid, size_t *r_certoff); @@ -144,7 +146,7 @@ gpg_error_t app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags); gpg_error_t app_readcert (app_t app, ctrl_t ctrl, const char *certid, unsigned char **cert, size_t *certlen); -gpg_error_t app_readkey (app_t app, ctrl_t ctrl, int advanced, +gpg_error_t app_readkey (app_t app, ctrl_t ctrl, const char *keyid, unsigned char **pk, size_t *pklen); gpg_error_t app_getattr (app_t app, ctrl_t ctrl, const char *name); gpg_error_t app_setattr (app_t app, ctrl_t ctrl, const char *name, diff --git a/scd/app-help.c b/scd/app-help.c index 842a73d5a..f0f551c55 100644 --- a/scd/app-help.c +++ b/scd/app-help.c @@ -29,9 +29,9 @@ #include "../common/tlv.h" -/* Count the number of bits, assuming the A represents an unsigned big - integer of length LEN bytes. If A is NULL a length of 0 is - returned. */ +/* Count the number of bits, assuming that A represents an unsigned + * big integer of length LEN bytes. If A is NULL a length of 0 is + * returned. */ unsigned int app_help_count_bits (const unsigned char *a, size_t len) { @@ -87,6 +87,45 @@ app_help_get_keygrip_string (ksba_cert_t cert, char *hexkeygrip) } +gpg_error_t +app_help_pubkey_from_cert (const void *cert, size_t certlen, + unsigned char **r_pk, size_t *r_pklen) +{ + gpg_error_t err; + ksba_cert_t kc; + unsigned char *pk; + size_t pklen; + + *r_pk = NULL; + *r_pklen = 0; + + err = ksba_cert_new (&kc); + if (err) + return err; + + err = ksba_cert_init_from_mem (kc, cert, certlen); + if (err) + goto leave; + + pk = ksba_cert_get_public_key (kc); + if (!pk) + { + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + pklen = gcry_sexp_canon_len (pk, 0, NULL, &err); + + leave: + if (!err) + { + *r_pk = pk; + *r_pklen = pklen; + } + else + ksba_free (pk); + ksba_cert_release (kc); + return err; +} /* Given the SLOT and the File ID FID, return the length of the certificate contained in that file. Returns 0 if the file does not diff --git a/scd/app-nks.c b/scd/app-nks.c index 0f38e7cd0..40c941616 100644 --- a/scd/app-nks.c +++ b/scd/app-nks.c @@ -618,17 +618,13 @@ do_readcert (app_t app, const char *certid, certificate parsing code in commands.c:cmd_readkey. For internal use PK and PKLEN may be NULL to just check for an existing key. */ static gpg_error_t -do_readkey (app_t app, int advanced, const char *keyid, - unsigned char **pk, size_t *pklen) +do_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen) { gpg_error_t err; unsigned char *buffer[2]; size_t buflen[2]; unsigned short path[1] = { 0x4500 }; - if (advanced) - return GPG_ERR_NOT_SUPPORTED; - /* We use a generic name to retrieve PK.AUT.IFD-SPK. */ if (!strcmp (keyid, "$IFDAUTHKEY") && app->app_local->nks_version >= 3) ; @@ -702,7 +698,7 @@ do_writekey (app_t app, ctrl_t ctrl, else return gpg_error (GPG_ERR_INV_ID); - if (!force && !do_readkey (app, 0, keyid, NULL, NULL)) + if (!force && !do_readkey (app, keyid, NULL, NULL)) return gpg_error (GPG_ERR_EEXIST); /* Parse the S-expression. */ diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index b6ead6478..de8a7742b 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -1875,10 +1875,8 @@ do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) buffer. On error PK and PKLEN are not changed and an error code is returned. */ static gpg_error_t -do_readkey (app_t app, int advanced, const char *keyid, - unsigned char **pk, size_t *pklen) +do_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen) { -#if GNUPG_MAJOR_VERSION > 1 gpg_error_t err; int keyno; unsigned char *buf; @@ -1900,45 +1898,17 @@ do_readkey (app_t app, int advanced, const char *keyid, if (!buf) return gpg_error (GPG_ERR_NO_PUBKEY); - if (advanced) + *pklen = app->app_local->pk[keyno].keylen; + *pk = xtrymalloc (*pklen); + if (!*pk) { - gcry_sexp_t s_key; - - err = gcry_sexp_new (&s_key, buf, app->app_local->pk[keyno].keylen, 0); - if (err) - return err; - - *pklen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, NULL, 0); - *pk = xtrymalloc (*pklen); - if (!*pk) - { - err = gpg_error_from_syserror (); - *pklen = 0; - return err; - } - - gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, *pk, *pklen); - gcry_sexp_release (s_key); - /* Decrement for trailing '\0' */ - *pklen = *pklen - 1; - } - else - { - *pklen = app->app_local->pk[keyno].keylen; - *pk = xtrymalloc (*pklen); - if (!*pk) - { - err = gpg_error_from_syserror (); - *pklen = 0; - return err; - } - memcpy (*pk, buf, *pklen); + err = gpg_error_from_syserror (); + *pklen = 0; + return err; } + memcpy (*pk, buf, *pklen); return 0; -#else - return gpg_error (GPG_ERR_NOT_IMPLEMENTED); -#endif } /* Read the standard certificate of an OpenPGP v2 card. It is diff --git a/scd/app-piv.c b/scd/app-piv.c index 704a498c6..495e26a63 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -1418,14 +1418,14 @@ do_readcert (app_t app, const char *certid, * application has been performed. This is because we return a cached * result from key generation. If no cached result is available, the * error GPG_ERR_UNSUPPORTED_OPERATION is returned so that the higher - * layer can then to get the key by reading the matching certificate. + * layer can then get the key by reading the matching certificate. * On success a canonical encoded s-expression with the public key is * stored at (R_PK,R_PKLEN); the caller must release that buffer. On * error R_PK and R_PKLEN are not changed and an error code is * returned. */ static gpg_error_t -do_readkey (app_t app, int advanced, const char *keyrefstr, +do_readkey (app_t app, const char *keyrefstr, unsigned char **r_pk, size_t *r_pklen) { gpg_error_t err; @@ -1471,19 +1471,6 @@ do_readkey (app_t app, int advanced, const char *keyrefstr, err = make_canon_sexp (s_pkey, &pk, &pklen); if (err) goto leave; - if (advanced) - { - /* FIXME: How ugly - we should move that to command.c */ - char *p = canon_sexp_to_string (pk, pklen); - if (!p) - { - err = gpg_error_from_syserror (); - goto leave; - } - xfree (pk); - pk = p; - pklen = strlen (pk); - } *r_pk = pk; pk = NULL; diff --git a/scd/app.c b/scd/app.c index 8d4d1c032..ada1eb963 100644 --- a/scd/app.c +++ b/scd/app.c @@ -692,7 +692,7 @@ app_readcert (app_t app, ctrl_t ctrl, const char *certid, This function might not be supported by all applications. */ gpg_error_t -app_readkey (app_t app, ctrl_t ctrl, int advanced, const char *keyid, +app_readkey (app_t app, ctrl_t ctrl, const char *keyid, unsigned char **pk, size_t *pklen) { gpg_error_t err; @@ -711,7 +711,7 @@ app_readkey (app_t app, ctrl_t ctrl, int advanced, const char *keyid, err = lock_app (app, ctrl); if (err) return err; - err= app->fnc.readkey (app, advanced, keyid, pk, pklen); + err= app->fnc.readkey (app, keyid, pk, pklen); unlock_app (app); return err; } diff --git a/scd/command.c b/scd/command.c index b4098e465..5b2ca6c29 100644 --- a/scd/command.c +++ b/scd/command.c @@ -516,11 +516,8 @@ cmd_readkey (assuan_context_t ctx, char *line) int rc; int advanced = 0; unsigned char *cert = NULL; - size_t ncert, n; - ksba_cert_t kc = NULL; - ksba_sexp_t p = NULL; - unsigned char *pk; - size_t pklen; + unsigned char *pk = NULL; + size_t ncert, pklen; if ((rc = open_card (ctrl))) return rc; @@ -529,83 +526,68 @@ cmd_readkey (assuan_context_t ctx, char *line) advanced = 1; line = skip_options (line); - line = xstrdup (line); /* Need a copy of the line. */ + /* If the application supports the READKEY function we use that. Otherwise we use the old way by extracting it from the certificate. */ - rc = app_readkey (ctrl->app_ctx, ctrl, advanced, line, &pk, &pklen); + rc = app_readkey (ctrl->app_ctx, ctrl, line, &pk, &pklen); if (!rc) - { /* Yeah, got that key - send it back. */ - rc = assuan_send_data (ctx, pk, pklen); - xfree (pk); - xfree (line); - line = NULL; - goto leave; - } - - if (gpg_err_code (rc) != GPG_ERR_UNSUPPORTED_OPERATION - && gpg_err_code (rc) != GPG_ERR_NOT_FOUND) - log_error ("app_readkey failed: %s\n", gpg_strerror (rc)); - else + ; /* Okay, got that key. */ + else if (gpg_err_code (rc) == GPG_ERR_UNSUPPORTED_OPERATION + || gpg_err_code (rc) == GPG_ERR_NOT_FOUND) { + /* Fall back to certificate reading. */ rc = app_readcert (ctrl->app_ctx, ctrl, line, &cert, &ncert); if (rc) - log_error ("app_readcert failed: %s\n", gpg_strerror (rc)); + { + log_error ("app_readcert failed: %s\n", gpg_strerror (rc)); + goto leave; + } + rc = app_help_pubkey_from_cert (cert, ncert, &pk, &pklen); + if (rc) + { + log_error ("failed to parse the certificate: %s\n", + gpg_strerror (rc)); + goto leave; + } } - xfree (line); - line = NULL; - if (rc) - goto leave; - - rc = ksba_cert_new (&kc); - if (rc) - goto leave; - - rc = ksba_cert_init_from_mem (kc, cert, ncert); - if (rc) + else { - log_error ("failed to parse the certificate: %s\n", gpg_strerror (rc)); + log_error ("app_readkey failed: %s\n", gpg_strerror (rc)); goto leave; } - p = ksba_cert_get_public_key (kc); - if (!p) - { - rc = gpg_error (GPG_ERR_NO_PUBKEY); - goto leave; - } - n = gcry_sexp_canon_len (p, 0, NULL, NULL); - if (advanced) { gcry_sexp_t s_key; + unsigned char *pkadv; + size_t pkadvlen; - rc = gcry_sexp_new (&s_key, (void*)p, n, 0); + rc = gcry_sexp_new (&s_key, pk, pklen, 0); if (rc) goto leave; - pklen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, NULL, 0); - pk = xtrymalloc (pklen); - if (!pk) + pkadvlen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, NULL, 0); + pkadv = xtrymalloc (pkadvlen); + if (!pkadv) { rc = gpg_error_from_syserror (); goto leave; } - log_assert (pklen); + log_assert (pkadvlen); - gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, pk, pklen); + gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, pkadv, pkadvlen); gcry_sexp_release (s_key); /* (One less to adjust for the trailing '\0') */ - rc = assuan_send_data (ctx, pk, pklen-1); - xfree (pk); + rc = assuan_send_data (ctx, pkadv, pkadvlen-1); + xfree (pkadv); } else - rc = assuan_send_data (ctx, p, n); + rc = assuan_send_data (ctx, pk, pklen); leave: - xfree (p); - ksba_cert_release (kc); + xfree (pk); xfree (cert); return rc; } From 0173b249cfb7f02f94911ec759630d81f312e0bd Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 27 Feb 2019 10:37:26 +0900 Subject: [PATCH 199/235] agent: PKSIGN should return signature in same format for card. * agent/pksign.c (agent_pksign_do): -- It's best to keep same data format by libgcrypt. For card (due to historical reasons), gpg-agent or scdaemon used to prefix 0x00 when it starts 0x80, so that it can be parsed signed MPI as well as unsigned MPI. It used to do nothing for preceding zeros. Signed-off-by: NIIBE Yutaka --- agent/pksign.c | 84 ++++++++++++++++++++++---------------------------- 1 file changed, 37 insertions(+), 47 deletions(-) diff --git a/agent/pksign.c b/agent/pksign.c index f54af0817..828e63f58 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -367,20 +367,29 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, if (is_RSA) { - check_signature = 1; - if (*buf & 0x80) - { - len++; - buf = xtryrealloc (buf, len); - if (!buf) - goto leave; + unsigned char *p = buf; - memmove (buf + 1, buf, len - 1); - *buf = 0; + check_signature = 1; + + /* + * Smartcard returns fixed-size data, which is good for + * PKCS1. If variable-size unsigned MPI is needed, remove + * zeros. + */ + if (ctrl->digest.algo == MD_USER_TLS_MD5SHA1 + || ctrl->digest.raw_value) + { + int i; + + for (i = 0; i < len - 1; i++) + if (p[i]) + break; + p += i; + len -= i; } err = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%b)))", - (int)len, buf); + (int)len, p); } else if (is_EdDSA) { @@ -389,53 +398,34 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, } else if (is_ECDSA) { - unsigned char *r_buf_allocated = NULL; - unsigned char *s_buf_allocated = NULL; unsigned char *r_buf, *s_buf; int r_buflen, s_buflen; + int i; r_buflen = s_buflen = len/2; - if (*buf & 0x80) - { - r_buflen++; - r_buf_allocated = xtrymalloc (r_buflen); - if (!r_buf_allocated) - { - err = gpg_error_from_syserror (); - goto leave; - } + /* + * Smartcard returns fixed-size data. For ECDSA signature, + * variable-size unsigned MPI is assumed, thus, remove + * zeros. + */ + r_buf = buf; + for (i = 0; i < r_buflen - 1; i++) + if (r_buf[i]) + break; + r_buf += i; + r_buflen -= i; - r_buf = r_buf_allocated; - memcpy (r_buf + 1, buf, len/2); - *r_buf = 0; - } - else - r_buf = buf; - - if (*(buf + len/2) & 0x80) - { - s_buflen++; - s_buf_allocated = xtrymalloc (s_buflen); - if (!s_buf_allocated) - { - err = gpg_error_from_syserror (); - xfree (r_buf_allocated); - goto leave; - } - - s_buf = s_buf_allocated; - memcpy (s_buf + 1, buf + len/2, len/2); - *s_buf = 0; - } - else - s_buf = buf + len/2; + s_buf = buf + len/2; + for (i = 0; i < s_buflen - 1; i++) + if (s_buf[i]) + break; + s_buf += i; + s_buflen -= i; err = gcry_sexp_build (&s_sig, NULL, "(sig-val(ecdsa(r%b)(s%b)))", r_buflen, r_buf, s_buflen, s_buf); - xfree (r_buf_allocated); - xfree (s_buf_allocated); } else err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); From 2c75af9f65d15653ed1bc191f1098ae316607041 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 28 Feb 2019 14:43:42 +0100 Subject: [PATCH 200/235] sm: Don't mark a cert as de-vs compliant if it leads to SHA-1 sigs. * sm/keylist.c (print_compliance_flags): Also check the diges_also. -- A certificate with algorithm sha1WithRSAEncryption can be de-vs compliant (e.g. if the next in the chain used sha256WithRSAEncryption to sign it and RSA is long enough) but flagging it as such is useless because that certificate can't be used because it will create signatures using the non-compliant SHA-1 algorithm. Well, it could be used for encryption. But also evaluating the key-usage flags here would make it harder for the user to understand why certain certificates are listed as de-vs compliant and others are not. Signed-off-by: Werner Koch --- sm/keylist.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/sm/keylist.c b/sm/keylist.c index ea2a22093..420be0bd2 100644 --- a/sm/keylist.c +++ b/sm/keylist.c @@ -384,16 +384,21 @@ static void print_compliance_flags (ksba_cert_t cert, int algo, unsigned int nbits, estream_t fp) { - int any = 0; + int indent = 0; + int hashalgo; if (gnupg_pk_is_compliant (CO_DE_VS, algo, NULL, nbits, NULL)) { - es_fputs (gnupg_status_compliance_flag (CO_DE_VS), fp); - any++; + hashalgo = gcry_md_map_name (ksba_cert_get_digest_algo (cert)); + if (gnupg_digest_is_compliant (CO_DE_VS, hashalgo)) + { + es_fputs (gnupg_status_compliance_flag (CO_DE_VS), fp); + indent = 1; + } } if (opt.with_key_screening) - print_pk_screening (cert, 1+any, fp); + print_pk_screening (cert, 1+indent, fp); } From 280baee72dcb0ca54ce99b524bc2125cbc38e0e4 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 1 Mar 2019 12:20:24 +0100 Subject: [PATCH 201/235] card: Remove the "admin" command. * tools/gpg-card.c (cmd_passwd): Remove arg allow_admin. (enum cmdids): Rename cmdAUTHENTICATE to cmdAUTH and cmdFACTORYRESET to cmdFACTRST. (cmds): Remove column 'admin_only'. (interactive_loop): Remove admin_only stuff. -- That command has always been an annoyance. Symbols have been renamed for source cosmetics. Signed-off-by: Werner Koch --- tools/gpg-card.c | 181 ++++++++++++++++++----------------------------- 1 file changed, 67 insertions(+), 114 deletions(-) diff --git a/tools/gpg-card.c b/tools/gpg-card.c index f1d0dc8fc..3f972fee4 100644 --- a/tools/gpg-card.c +++ b/tools/gpg-card.c @@ -2008,10 +2008,9 @@ cmd_generate (card_info_t info, char *argstr) -/* Sub-menu to change a PIN. The presented options may depend on the - * the ALLOW_ADMIN flag. */ +/* Sub-menu to change a PIN. */ static gpg_error_t -cmd_passwd (card_info_t info, int allow_admin, char *argstr) +cmd_passwd (card_info_t info, char *argstr) { gpg_error_t err; char *answer = NULL; @@ -2031,31 +2030,10 @@ cmd_passwd (card_info_t info, int allow_admin, char *argstr) app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); - if (!allow_admin || info->apptype != APP_TYPE_OPENPGP) - { - if (*argstr) - pinref = argstr; - else if (info->apptype == APP_TYPE_OPENPGP) - pinref = "OPENPGP.1"; - else if (info->apptype == APP_TYPE_PIV) - pinref = "PIV.80"; - else - { - err = gpg_error (GPG_ERR_MISSING_VALUE); - goto leave; - } - err = scd_change_pin (pinref, 0); - if (err) - goto leave; - - if (info->apptype == APP_TYPE_PIV - && !ascii_strcasecmp (pinref, "PIV.81")) - log_info ("PUK changed.\n"); - else - log_info ("PIN changed.\n"); - } - else if (info->apptype == APP_TYPE_OPENPGP) + if (!*argstr && info->apptype == APP_TYPE_OPENPGP) { + /* For an OpenPGP card we present the well known menu if no + * argument is given. */ for (;;) { tty_printf ("\n"); @@ -2119,9 +2097,27 @@ cmd_passwd (card_info_t info, int allow_admin, char *argstr) } else { - log_info ("Admin related passwd options not yet supported for '%s'\n", - app_type_string (info->apptype)); - err = gpg_error (GPG_ERR_NOT_SUPPORTED); + if (*argstr) + pinref = argstr; + else if (info->apptype == APP_TYPE_PIV) + pinref = "PIV.80"; + else + { + /* Note that we do not have a default value for OpenPGP + * because we want to be mostly compatible to "gpg + * --card-edit" and show a menu in that case (above). */ + err = gpg_error (GPG_ERR_MISSING_VALUE); + goto leave; + } + err = scd_change_pin (pinref, 0); + if (err) + goto leave; + + if (info->apptype == APP_TYPE_PIV + && !ascii_strcasecmp (pinref, "PIV.81")) + log_info ("PUK changed.\n"); + else + log_info ("PIN changed.\n"); } leave: @@ -2919,11 +2915,11 @@ cmd_yubikey (card_info_t info, char *argstr) enum cmdids { cmdNOP = 0, - cmdQUIT, cmdADMIN, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY, + cmdQUIT, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY, cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, - cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET, cmdKDFSETUP, - cmdKEYATTR, cmdUIF, cmdAUTHENTICATE, cmdYUBIKEY, + cmdREADCERT, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP, + cmdKEYATTR, cmdUIF, cmdAUTH, cmdYUBIKEY, cmdINVCMD }; @@ -2931,41 +2927,39 @@ static struct { const char *name; enum cmdids id; - int admin_only; const char *desc; } cmds[] = { - { "quit" , cmdQUIT , 0, N_("quit this menu")}, - { "q" , cmdQUIT , 0, NULL }, - { "admin" , cmdADMIN , 0, N_("show admin commands")}, - { "help" , cmdHELP , 0, N_("show this help")}, - { "?" , cmdHELP , 0, NULL }, - { "list" , cmdLIST , 0, N_("list all available data")}, - { "l" , cmdLIST , 0, NULL }, - { "name" , cmdNAME , 1, N_("change card holder's name")}, - { "url" , cmdURL , 1, N_("change URL to retrieve key")}, - { "fetch" , cmdFETCH , 0, N_("fetch the key specified in the card URL")}, - { "login" , cmdLOGIN , 1, N_("change the login name")}, - { "lang" , cmdLANG , 1, N_("change the language preferences")}, - { "salutation",cmdSALUT, 1, N_("change card holder's salutation")}, - { "salut" , cmdSALUT, 1, NULL }, - { "cafpr" , cmdCAFPR , 1, N_("change a CA fingerprint")}, - { "forcesig", cmdFORCESIG, 1, N_("toggle the signature force PIN flag")}, - { "generate", cmdGENERATE, 1, N_("generate new keys")}, - { "passwd" , cmdPASSWD, 0, N_("menu to change or unblock the PIN")}, - { "verify" , cmdVERIFY, 0, N_("verify the PIN and list all data")}, - { "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code")}, - { "authenticate",cmdAUTHENTICATE, 0,N_("authenticate to the card")}, - { "auth" , cmdAUTHENTICATE, 0, NULL }, - { "reset" , cmdRESET, 0, N_("send a reset to the card daemon")}, - { "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")}, - { "kdf-setup", cmdKDFSETUP, 1, N_("setup KDF for PIN authentication")}, - { "key-attr", cmdKEYATTR, 1, N_("change the key attribute")}, - { "uif", cmdUIF, 1, N_("change the User Interaction Flag")}, - { "privatedo", cmdPRIVATEDO, 0, N_("change a private data object")}, - { "readcert", cmdREADCERT, 0, N_("read a certificate from a data object")}, - { "writecert", cmdWRITECERT, 1, N_("store a certificate to a data object")}, - { "yubikey", cmdYUBIKEY, 0, N_("Yubikey management commands")}, - { NULL, cmdINVCMD, 0, NULL } + { "quit" , cmdQUIT, N_("quit this menu")}, + { "q" , cmdQUIT, NULL }, + { "help" , cmdHELP, N_("show this help")}, + { "?" , cmdHELP, NULL }, + { "list" , cmdLIST, N_("list all available data")}, + { "l" , cmdLIST, NULL }, + { "name" , cmdNAME, N_("change card holder's name")}, + { "url" , cmdURL, N_("change URL to retrieve key")}, + { "fetch" , cmdFETCH, N_("fetch the key specified in the card URL")}, + { "login" , cmdLOGIN, N_("change the login name")}, + { "lang" , cmdLANG, N_("change the language preferences")}, + { "salutation",cmdSALUT, N_("change card holder's salutation")}, + { "salut" , cmdSALUT, NULL }, + { "cafpr" , cmdCAFPR , N_("change a CA fingerprint")}, + { "forcesig", cmdFORCESIG, N_("toggle the signature force PIN flag")}, + { "generate", cmdGENERATE, N_("generate new keys")}, + { "passwd" , cmdPASSWD, N_("menu to change or unblock the PIN")}, + { "verify" , cmdVERIFY, N_("verify the PIN and list all data")}, + { "unblock" , cmdUNBLOCK, N_("unblock the PIN using a Reset Code")}, + { "authenticate",cmdAUTH, N_("authenticate to the card")}, + { "auth" , cmdAUTH, NULL }, + { "reset" , cmdRESET, N_("send a reset to the card daemon")}, + { "factory-reset",cmdFACTRST, N_("destroy all keys and data")}, + { "kdf-setup", cmdKDFSETUP, N_("setup KDF for PIN authentication")}, + { "key-attr", cmdKEYATTR, N_("change the key attribute")}, + { "uif", cmdUIF, N_("change the User Interaction Flag")}, + { "privatedo", cmdPRIVATEDO, N_("change a private data object")}, + { "readcert", cmdREADCERT, N_("read a certificate from a data object")}, + { "writecert", cmdWRITECERT, N_("store a certificate to a data object")}, + { "yubikey", cmdYUBIKEY, N_("Yubikey management commands")}, + { NULL, cmdINVCMD, NULL } }; @@ -3078,12 +3072,8 @@ dispatch_command (card_info_t info, const char *orig_command) } break; - case cmdADMIN: - /* This is a NOP in non-interactive mode. */ - break; - case cmdVERIFY: err = cmd_verify (info, argstr); break; - case cmdAUTHENTICATE: err = cmd_authenticate (info, argstr); break; + case cmdAUTH: err = cmd_authenticate (info, argstr); break; case cmdNAME: err = cmd_name (info, argstr); break; case cmdURL: err = cmd_url (info, argstr); break; case cmdFETCH: err = cmd_fetch (info); break; @@ -3096,9 +3086,9 @@ dispatch_command (card_info_t info, const char *orig_command) case cmdREADCERT: err = cmd_readcert (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info, argstr); break; - case cmdPASSWD: err = cmd_passwd (info, 1, argstr); break; + case cmdPASSWD: err = cmd_passwd (info, argstr); break; case cmdUNBLOCK: err = cmd_unblock (info); break; - case cmdFACTORYRESET: err = cmd_factoryreset (info); break; + case cmdFACTRST: err = cmd_factoryreset (info); break; case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; case cmdKEYATTR: err = cmd_keyattr (info, argstr); break; case cmdUIF: err = cmd_uif (info, argstr); break; @@ -3139,10 +3129,8 @@ interactive_loop (void) gpg_error_t err; char *answer = NULL; /* The input line. */ enum cmdids cmd = cmdNOP; /* The command. */ - int cmd_admin_only; /* The command is an admin only command. */ char *argstr; /* The argument as a string. */ int redisplay = 1; /* Whether to redisplay the main info. */ - int allow_admin = 0; /* Whether admin commands are allowed. */ char *help_arg = NULL; /* Argument of the HELP command. */ struct card_info_s info_buffer = { 0 }; card_info_t info = &info_buffer; @@ -3205,7 +3193,6 @@ interactive_loop (void) } argstr = NULL; - cmd_admin_only = 0; if (!*answer) cmd = cmdLIST; /* We default to the list command */ else if (*answer == CONTROL_D) @@ -3224,7 +3211,6 @@ interactive_loop (void) break; cmd = cmds[i].id; - cmd_admin_only = cmds[i].admin_only; } /* Make sure we have valid strings for the args. They are @@ -3242,7 +3228,6 @@ interactive_loop (void) else if (redisplay) { cmd = cmdLIST; - cmd_admin_only = 0; } else if (!info->serialno) { @@ -3252,12 +3237,6 @@ interactive_loop (void) tty_printf ("Serial number missing\n"); continue; } - else if (!allow_admin && cmd_admin_only) - { - tty_printf ("\n"); - tty_printf (_("Admin-only command\n")); - continue; - } } err = 0; @@ -3292,9 +3271,7 @@ interactive_loop (void) tty_printf ("List of commands (\"help \" for details):\n"); for (i=0; cmds[i].name; i++ ) - if(cmds[i].desc - && (!cmds[i].admin_only - || (cmds[i].admin_only && allow_admin))) + if(cmds[i].desc) tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); } break; @@ -3321,36 +3298,12 @@ interactive_loop (void) } break; - case cmdADMIN: - if ( !strcmp (argstr, "on") ) - allow_admin = 1; - else if ( !strcmp (argstr, "off") ) - allow_admin = 0; - else if ( !strcmp (argstr, "verify") ) - { - /* Force verification of the Admin Command. However, - this is only done if the retry counter is at initial - state. */ - /* FIXME: Must depend on the type of the card. */ - /* char *tmp = xmalloc (strlen (serialnobuf) + 6 + 1); */ - /* strcpy (stpcpy (tmp, serialnobuf), "[CHV3]"); */ - /* allow_admin = !agent_scd_checkpin (tmp); */ - /* xfree (tmp); */ - } - else /* Toggle. */ - allow_admin=!allow_admin; - if(allow_admin) - tty_printf(_("Admin commands are allowed\n")); - else - tty_printf(_("Admin commands are not allowed\n")); - break; - case cmdVERIFY: err = cmd_verify (info, argstr); if (!err) redisplay = 1; break; - case cmdAUTHENTICATE: err = cmd_authenticate (info, argstr); break; + case cmdAUTH: err = cmd_authenticate (info, argstr); break; case cmdNAME: err = cmd_name (info, argstr); break; case cmdURL: err = cmd_url (info, argstr); break; case cmdFETCH: err = cmd_fetch (info); break; @@ -3363,9 +3316,9 @@ interactive_loop (void) case cmdREADCERT: err = cmd_readcert (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info, argstr); break; - case cmdPASSWD: err = cmd_passwd (info, allow_admin, argstr); break; + case cmdPASSWD: err = cmd_passwd (info, argstr); break; case cmdUNBLOCK: err = cmd_unblock (info); break; - case cmdFACTORYRESET: + case cmdFACTRST: err = cmd_factoryreset (info); if (!err) redisplay = 1; From 696d4c290dd4945b693263721f606b5049b9569d Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 1 Mar 2019 12:58:56 +0100 Subject: [PATCH 202/235] scd:piv: Allow writecert to only write matching certs. * scd/app-piv.c (do_readkey): Read the key from the cert here instead of letting the upper layer do this. (do_writecert): Check that the cert matches the key and that a key has already been generated. Signed-off-by: Werner Koch --- scd/app-piv.c | 77 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/scd/app-piv.c b/scd/app-piv.c index 495e26a63..2cbc6e195 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -1450,27 +1450,32 @@ do_readkey (app_t app, const char *keyrefstr, goto leave; if (!mechanism) { - /* We got a certificate. Let the upper layer handle the - * extraction of the key. FIXME: It would be better to have a - * shared fucntion to dothis here. */ - err = gpg_error (GPG_ERR_NOT_FOUND); - goto leave; + /* We got a certificate. Extract the pubkey from it. */ + err = app_help_pubkey_from_cert (cert, certlen, &pk, &pklen); + if (err) + { + log_error ("failed to parse the certificate: %s\n", + gpg_strerror (err)); + goto leave; + } } - - /* Convert the public key into the expected s-expression. */ - if (mechanism == PIV_ALGORITHM_RSA) - err = genkey_parse_rsa (cert, certlen, &s_pkey); - else if (mechanism == PIV_ALGORITHM_ECC_P256 - || mechanism == PIV_ALGORITHM_ECC_P384) - err = genkey_parse_ecc (cert, certlen, mechanism, &s_pkey); else - err = gpg_error (GPG_ERR_PUBKEY_ALGO); - if (err) - goto leave; + { + /* Convert the public key into the expected s-expression. */ + if (mechanism == PIV_ALGORITHM_RSA) + err = genkey_parse_rsa (cert, certlen, &s_pkey); + else if (mechanism == PIV_ALGORITHM_ECC_P256 + || mechanism == PIV_ALGORITHM_ECC_P384) + err = genkey_parse_ecc (cert, certlen, mechanism, &s_pkey); + else + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + if (err) + goto leave; - err = make_canon_sexp (s_pkey, &pk, &pklen); - if (err) - goto leave; + err = make_canon_sexp (s_pkey, &pk, &pklen); + if (err) + goto leave; + } *r_pk = pk; pk = NULL; @@ -2735,6 +2740,9 @@ do_writecert (app_t app, ctrl_t ctrl, { gpg_error_t err; data_object_t dobj; + unsigned char *pk = NULL; + unsigned char *orig_pk = NULL; + size_t pklen, orig_pklen; (void)ctrl; (void)pincb; /* Not used; instead authentication is needed. */ @@ -2746,10 +2754,33 @@ do_writecert (app_t app, ctrl_t ctrl, /* FIXME: Check that the authentication has already been done. */ - /* FIXME: Check that the public key parameters from the certificate - * match an already stored key. */ - flush_cached_data (app, dobj->tag); + + /* Check that the public key parameters from the certificate match + * an already stored key. Note that we do not allow writing a + * certificate if no key has yet been created (GPG_ERR_NOT_FOUND) or + * if there is a problem reading the public key from the certificate + * GPG_ERR_NO_PUBKEY). We enforce this because otherwise the only + * way to detect whether a key exists is by trying to use that + * key. */ + err = do_readkey (app, certrefstr, &orig_pk, &orig_pklen); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + err = gpg_error (GPG_ERR_NO_SECKEY); /* Use a better error code. */ + goto leave; + } + /* Compare pubkeys. */ + err = app_help_pubkey_from_cert (cert, certlen, &pk, &pklen); + if (err) + goto leave; /* No public key in new certificate. */ + if (orig_pklen != pklen || memcmp (orig_pk, pk, pklen)) + { + err = gpg_error (GPG_ERR_CONFLICT); + goto leave; + } + + err = put_data (app->slot, dobj->tag, (int)0x70, (size_t)certlen, cert,/* Certificate */ (int)0x71, (size_t)1, "", /* No compress */ @@ -2762,7 +2793,9 @@ do_writecert (app_t app, ctrl_t ctrl, log_error ("piv: failed to write cert to %s: %s\n", dobj->keyref, gpg_strerror (err)); - + leave: + xfree (pk); + xfree (orig_pk); return err; } From 51df13d9ec8e89c4236e2f4a9ae3647963c30783 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 1 Mar 2019 14:04:29 +0100 Subject: [PATCH 203/235] scd:piv: Add feature to read Yubikey attestation certificates. * scd/app-piv.c (do_readcert): Add hack to read Yubikey attestaions. -- Use gpg-card 'readcert PIV.ATST.9A >x.crt' to store the attestation certificate for 9A into X.CRT. Signed-off-by: Werner Koch --- scd/app-piv.c | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/scd/app-piv.c b/scd/app-piv.c index 2cbc6e195..d55d71f25 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -182,6 +182,9 @@ static struct data_object_s data_objects[] = { * "97" Secondary Finger OCC * "98" Pairing Code * "9B" PIV Card Application Administration Key + * + * Yubikey specific data objects: + * "F9" Attestation key (preloaded can be replaced) */ }; @@ -1044,7 +1047,6 @@ set_adm_key (app_t app, const unsigned char *value, size_t valuelen) return err; } - /* Handle the SETATTR operation. All arguments are already basically * checked. */ static gpg_error_t @@ -1167,7 +1169,7 @@ do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) * given tag and returns it in a freshly allocated buffer stored at * R_CERT and the length of the certificate stored at R_CERTLEN. If * on success a non-zero value is stored at R_MECHANISM, the returned - * data is not certificate but a public key (in the format used by the + * data is not a certificate but a public key (in the format used by the * container '7f49'. */ static gpg_error_t readcert_by_tag (app_t app, unsigned int tag, @@ -1396,6 +1398,30 @@ do_readcert (app_t app, const char *certid, *r_cert = NULL; *r_certlen = 0; + /* Hack to read a Yubikey attestation certificate. */ + if (app->app_local->flags.yubikey + && strlen (certid) == 11 + && !ascii_strncasecmp (certid, "PIV.ATST.", 9) + && hexdigitp (certid+9) && hexdigitp (certid+10)) + { + unsigned char apdu[4]; + unsigned char *result; + size_t resultlen; + + apdu[0] = 0; + apdu[1] = 0xf9; /* Yubikey: Get attestation cert. */ + apdu[2] = xtoi_2 (certid+9); + apdu[3] = 0; + err = iso7816_apdu_direct (app->slot, apdu, 4, 1, + NULL, &result, &resultlen); + if (!err) + { + *r_cert = result; + *r_certlen = resultlen; + } + return err; + } + dobj = find_dobj_by_keyref (app, certid); if (!dobj) return gpg_error (GPG_ERR_INV_ID); From 86c241a8c9a952ea8007066b70b04f435e2e483e Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 1 Mar 2019 15:23:49 +0100 Subject: [PATCH 204/235] sm: Print Yubikey attestation extensions with --dump-cert. * sm/keylist.c (oidtranstbl): Add Yubikey OIDs. (OID_FLAG_HEX): New. (print_hex_extn): New. (list_cert_raw): Make use of that flag. Signed-off-by: Werner Koch --- sm/keylist.c | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/sm/keylist.c b/sm/keylist.c index 420be0bd2..3e377dbb5 100644 --- a/sm/keylist.c +++ b/sm/keylist.c @@ -85,6 +85,8 @@ struct #define OID_FLAG_SKIP 1 /* The extension is a simple UTF8String and should be printed. */ #define OID_FLAG_UTF8 2 +/* The extension can be trnted as a hex string. */ +#define OID_FLAG_HEX 4 /* A table mapping OIDs to a descriptive string. */ static struct @@ -194,6 +196,12 @@ static struct /* Extensions used by the Bundesnetzagentur. */ { "1.3.6.1.4.1.8301.3.5", "validityModel" }, + /* Yubikey extensions for attestation certificates. */ + { "1.3.6.1.4.1.41482.3.3", "yubikey-firmware-version", OID_FLAG_HEX }, + { "1.3.6.1.4.1.41482.3.7", "yubikey-serial-number", OID_FLAG_HEX }, + { "1.3.6.1.4.1.41482.3.8", "yubikey-pin-touch-policy", OID_FLAG_HEX }, + { "1.3.6.1.4.1.41482.3.9", "yubikey-formfactor", OID_FLAG_HEX }, + { NULL } }; @@ -723,6 +731,21 @@ print_utf8_extn (estream_t fp, int indent, } +/* Print the extension described by (DER,DERLEN) in hex. */ +static void +print_hex_extn (estream_t fp, int indent, + const unsigned char *der, size_t derlen) +{ + if (indent < 0) + indent = - indent; + + es_fprintf (fp, "%*s(", indent, ""); + for (; derlen; der++, derlen--) + es_fprintf (fp, "%02X%s", *der, derlen > 1? " ":""); + es_fprintf (fp, ")\n"); +} + + /* List one certificate in raw mode useful to have a closer look at the certificate. This one does no beautification and only minimal output sanitation. It is mainly useful for debugging. */ @@ -1060,16 +1083,27 @@ list_cert_raw (ctrl_t ctrl, KEYDB_HANDLE hd, if ((flag & OID_FLAG_SKIP)) continue; - es_fprintf (fp, " %s: %s%s%s%s [%d octets]\n", + es_fprintf (fp, " %s: %s%s%s%s", i? "critExtn":" extn", - oid, s?" (":"", s?s:"", s?")":"", (int)len); + oid, s?" (":"", s?s:"", s?")":""); if ((flag & OID_FLAG_UTF8)) { if (!cert_der) cert_der = ksba_cert_get_image (cert, NULL); - assert (cert_der); + log_assert (cert_der); + es_fprintf (fp, "\n"); print_utf8_extn_raw (fp, -15, cert_der+off, len); } + else if ((flag & OID_FLAG_HEX)) + { + if (!cert_der) + cert_der = ksba_cert_get_image (cert, NULL); + log_assert (cert_der); + es_fprintf (fp, "\n"); + print_hex_extn (fp, -15, cert_der+off, len); + } + else + es_fprintf (fp, " [%d octets]\n", (int)len); } From bcc89a6df24c79690436340f65c7ab13c65c2c45 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 5 Mar 2019 12:08:27 +0100 Subject: [PATCH 205/235] agent: Minor change to the KEYTOCARD command. * agent/command.c (cmd_keytocard): Make timestamp optional. Use modern parser function. * agent/call-scd.c (agent_card_writekey): Rename an arg and for clarity return gpg_error_t instead of int. * agent/divert-scd.c (divert_writekey): Ditto. Signed-off-by: Werner Koch --- agent/agent.h | 17 +++++----- agent/call-scd.c | 23 ++++++++------ agent/command.c | 78 +++++++++++++++++----------------------------- agent/divert-scd.c | 9 +++--- 4 files changed, 56 insertions(+), 71 deletions(-) diff --git a/agent/agent.h b/agent/agent.h index ee5a31eef..3a29dc817 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -548,8 +548,9 @@ int divert_pkdecrypt (ctrl_t ctrl, const char *desc_text, char **r_buf, size_t *r_len, int *r_padding); int divert_generic_cmd (ctrl_t ctrl, const char *cmdline, void *assuan_context); -int divert_writekey (ctrl_t ctrl, int force, const char *serialno, - const char *id, const char *keydata, size_t keydatalen); +gpg_error_t divert_writekey (ctrl_t ctrl, int force, const char *serialno, + const char *keyref, + const char *keydata, size_t keydatalen); /*-- call-scd.c --*/ @@ -586,12 +587,12 @@ int agent_card_pkdecrypt (ctrl_t ctrl, int agent_card_readcert (ctrl_t ctrl, const char *id, char **r_buf, size_t *r_buflen); int agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf); -int agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, - const char *id, const char *keydata, - size_t keydatalen, - int (*getpin_cb)(void *, const char *, - const char *, char*, size_t), - void *getpin_cb_arg); +gpg_error_t agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, + const char *keyref, + const char *keydata, size_t keydatalen, + int (*getpin_cb)(void *, const char *, + const char *, char*, size_t), + void *getpin_cb_arg); gpg_error_t agent_card_getattr (ctrl_t ctrl, const char *name, char **result); gpg_error_t agent_card_cardlist (ctrl_t ctrl, strlist_t *result); int agent_card_scd (ctrl_t ctrl, const char *cmdline, diff --git a/agent/call-scd.c b/agent/call-scd.c index d0d4794a4..1189bd477 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -1075,23 +1075,26 @@ inq_writekey_parms (void *opaque, const char *line) } -int +/* Call scd to write a key to a card under the id KEYREF. */ +gpg_error_t agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, - const char *id, const char *keydata, size_t keydatalen, + const char *keyref, + const char *keydata, size_t keydatalen, int (*getpin_cb)(void *, const char *, const char *, char*, size_t), void *getpin_cb_arg) { - int rc; + gpg_error_t err; char line[ASSUAN_LINELENGTH]; struct inq_needpin_parm_s parms; (void)serialno; - rc = start_scd (ctrl); - if (rc) - return rc; - snprintf (line, DIM(line), "WRITEKEY %s%s", force ? "--force " : "", id); + err = start_scd (ctrl); + if (err) + return err; + + snprintf (line, DIM(line), "WRITEKEY %s%s", force ? "--force " : "", keyref); parms.ctx = ctrl->scd_local->ctx; parms.getpin_cb = getpin_cb; parms.getpin_cb_arg = getpin_cb_arg; @@ -1100,9 +1103,9 @@ agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, parms.keydata = keydata; parms.keydatalen = keydatalen; - rc = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, - inq_writekey_parms, &parms, NULL, NULL); - return unlock_scd (ctrl, rc); + err = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, + inq_writekey_parms, &parms, NULL, NULL); + return unlock_scd (ctrl, err); } diff --git a/agent/command.c b/agent/command.c index 332d20ff4..62b701467 100644 --- a/agent/command.c +++ b/agent/command.c @@ -2484,19 +2484,23 @@ cmd_delete_key (assuan_context_t ctx, char *line) #endif static const char hlp_keytocard[] = - "KEYTOCARD [--force] \n" - "\n"; + "KEYTOCARD [--force] []\n" + "\n" + "TIMESTAMP is required for OpenPGP and defaults to the Epoch." + ; static gpg_error_t cmd_keytocard (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int force; gpg_error_t err = 0; + char *argv[5]; + int argc; unsigned char grip[20]; + const char *serialno, *timestamp_str, *keyref; gcry_sexp_t s_skey = NULL; unsigned char *keydata; size_t keydatalen; - const char *serialno, *timestamp_str, *id; unsigned char *shadow_info = NULL; time_t timestamp; @@ -2506,7 +2510,14 @@ cmd_keytocard (assuan_context_t ctx, char *line) force = has_option (line, "--force"); line = skip_options (line); - err = parse_keygrip (ctx, line, grip); + argc = split_fields (line, argv, DIM (argv)); + if (argc < 3) + { + err = gpg_error (GPG_ERR_MISSING_VALUE); + goto leave; + } + + err = parse_keygrip (ctx, argv[0], grip); if (err) goto leave; @@ -2516,39 +2527,9 @@ cmd_keytocard (assuan_context_t ctx, char *line) goto leave; } - /* Fixme: Replace the parsing code by split_fields(). */ - line += 40; - while (*line && (*line == ' ' || *line == '\t')) - line++; - serialno = line; - while (*line && (*line != ' ' && *line != '\t')) - line++; - if (!*line) - { - err = gpg_error (GPG_ERR_MISSING_VALUE); - goto leave; - } - *line = '\0'; - line++; - while (*line && (*line == ' ' || *line == '\t')) - line++; - id = line; - while (*line && (*line != ' ' && *line != '\t')) - line++; - if (!*line) - { - err = gpg_error (GPG_ERR_MISSING_VALUE); - goto leave; - } - *line = '\0'; - line++; - while (*line && (*line == ' ' || *line == '\t')) - line++; - timestamp_str = line; - while (*line && (*line != ' ' && *line != '\t')) - line++; - if (*line) - *line = '\0'; + serialno = argv[1]; + keyref = argv[2]; + timestamp_str = argc > 3? argv[3] : "19700101T000000"; if ((timestamp = isotime2epoch (timestamp_str)) == (time_t)(-1)) { @@ -2560,38 +2541,37 @@ cmd_keytocard (assuan_context_t ctx, char *line) &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, NULL); if (err) - { - xfree (shadow_info); - goto leave; - } + goto leave; if (shadow_info) { - /* Key is on a smartcard already. */ - xfree (shadow_info); - gcry_sexp_release (s_skey); + /* Key is already on a smartcard - we can't extract it. */ err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); goto leave; } - keydatalen = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); + /* Note: We can't use make_canon_sexp because we need to allocate a + * few extra bytes for our hack below. */ + keydatalen = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); keydata = xtrymalloc_secure (keydatalen + 30); if (keydata == NULL) { err = gpg_error_from_syserror (); - gcry_sexp_release (s_skey); goto leave; } - gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, keydata, keydatalen); gcry_sexp_release (s_skey); + s_skey = NULL; keydatalen--; /* Decrement for last '\0'. */ - /* Add timestamp "created-at" in the private key */ + /* Hack to insert the timestamp "created-at" into the private key. */ snprintf (keydata+keydatalen-1, 30, KEYTOCARD_TIMESTAMP_FORMAT, timestamp); keydatalen += 10 + 19 - 1; - err = divert_writekey (ctrl, force, serialno, id, keydata, keydatalen); + + err = divert_writekey (ctrl, force, serialno, keyref, keydata, keydatalen); xfree (keydata); leave: + gcry_sexp_release (s_skey); + xfree (shadow_info); return leave_cmd (ctx, err); } diff --git a/agent/divert-scd.c b/agent/divert-scd.c index 02fe5295a..e89c74a19 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -597,12 +597,13 @@ divert_pkdecrypt (ctrl_t ctrl, const char *desc_text, return rc; } -int + +gpg_error_t divert_writekey (ctrl_t ctrl, int force, const char *serialno, - const char *id, const char *keydata, size_t keydatalen) + const char *keyref, const char *keydata, size_t keydatalen) { - return agent_card_writekey (ctrl, force, serialno, id, keydata, keydatalen, - getpin_cb, ctrl); + return agent_card_writekey (ctrl, force, serialno, keyref, + keydata, keydatalen, getpin_cb, ctrl); } int From db87132b10664718b7db6ec1dad584b54d1fb265 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 5 Mar 2019 12:39:11 +0100 Subject: [PATCH 206/235] gpg: Make invalid primary key algos obvious in key listings. * g10/keylist.c (print_key_line): Print a warning for invalid algos. -- Non-OpenPGP compliant keys now show a warning flag on the sec or pub line like in: gpg: can't encode a 256 bit MD into a 88 bits frame, algo=8 sec cv25519 2019-01-30 [INVALID_ALGO] 4239F3D606A19258E7A88C3F9A3F4F909C5034C5 uid [ultimate] ffffff Instead of showing the usage flags "[CE]". Without this patch only the error message is printed and the reason for it was not immediately obvious (cv25519 is encryption only but we always consider the primary key as having the "C" flag). Signed-off-by: Werner Koch --- g10/keylist.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/g10/keylist.c b/g10/keylist.c index fffd95c31..8d5b2e0b9 100644 --- a/g10/keylist.c +++ b/g10/keylist.c @@ -2102,10 +2102,18 @@ print_key_line (ctrl_t ctrl, estream_t fp, PKT_public_key *pk, int secret) tty_fprintf (fp, "/%s", keystr_from_pk (pk)); tty_fprintf (fp, " %s", datestr_from_pk (pk)); - if ((opt.list_options & LIST_SHOW_USAGE)) + if (pk->flags.primary + && !(openpgp_pk_algo_usage (pk->pubkey_algo) + & (PUBKEY_USAGE_CERT| PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH))) + { + /* A primary key which is really not capable to sign. */ + tty_fprintf (fp, " [INVALID_ALGO]"); + } + else if ((opt.list_options & LIST_SHOW_USAGE)) { tty_fprintf (fp, " [%s]", usagestr_from_pk (pk, 0)); } + if (pk->flags.revoked) { tty_fprintf (fp, " ["); From e897e1e255ef9870dfd1639d6f4e97bdf4e83b34 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 5 Mar 2019 15:49:20 +0100 Subject: [PATCH 207/235] scd:piv: Implement import of private keys for Yubikeys. * scd/app-piv.c (concat_tlv_list): Add arg 'secure' and adjust callers. (writekey_rsa, writekey_ecc): New. (do_writekey): New. (do_writecert): Provide a better error message for an empty cert. (app_select_piv): Register do_writekey. * scd/iso7816.c (iso7816_send_apdu): New. * scd/app-common.h (APP_WRITEKEY_FLAG_FORCE): New. * agent/command.c (cmd_keytocard): Make the timestamp optional. * tools/card-call-scd.c (inq_writekey_parms): Remove. (scd_writekey): Rewrite. * tools/gpg-card.c (cmd_writekey): New. (enum cmdids): Add cmdWRITEKEY. (dispatch_command, interactive_loop): Call cmd_writekey. -- This has been tested with gpgsm and RSA keys. For ECC keys only partly tested using the sample OpenPGP nistp256 and nistp384 keys because gpgsm does not yet support ECC certificates and thus we can't write the certificates to the cert object after a writekey. Note that they nevertheless show up in "gpgcard list" because gpg-card searches for them in gpg and gpgsm. However, this does not work completely. Signed-off-by: Werner Koch --- agent/call-scd.c | 3 +- agent/command.c | 14 +- scd/app-common.h | 3 + scd/app-piv.c | 442 ++++++++++++++++++++++++++++++++++++++++-- scd/iso7816.c | 33 ++++ scd/iso7816.h | 5 + tools/card-call-scd.c | 45 ++--- tools/gpg-card.c | 67 ++++++- tools/gpg-card.h | 3 +- 9 files changed, 561 insertions(+), 54 deletions(-) diff --git a/agent/call-scd.c b/agent/call-scd.c index 1189bd477..4c0186d74 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -1088,7 +1088,8 @@ agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, char line[ASSUAN_LINELENGTH]; struct inq_needpin_parm_s parms; - (void)serialno; + (void)serialno; /* NULL or a number to check for the correct card. + * But is is not implemented. */ err = start_scd (ctrl); if (err) diff --git a/agent/command.c b/agent/command.c index 62b701467..5e2b6df2b 100644 --- a/agent/command.c +++ b/agent/command.c @@ -2486,8 +2486,8 @@ cmd_delete_key (assuan_context_t ctx, char *line) static const char hlp_keytocard[] = "KEYTOCARD [--force] []\n" "\n" - "TIMESTAMP is required for OpenPGP and defaults to the Epoch." - ; + "TIMESTAMP is required for OpenPGP and defaults to the Epoch. The\n" + "SERIALNO is used for checking; use \"-\" to disable the check."; static gpg_error_t cmd_keytocard (assuan_context_t ctx, char *line) { @@ -2527,8 +2527,18 @@ cmd_keytocard (assuan_context_t ctx, char *line) goto leave; } + /* Note that checking of the s/n is currently not implemented but we + * want to provide a clean interface if we ever implement it. */ serialno = argv[1]; + if (!strcmp (serialno, "-")) + serialno = NULL; + keyref = argv[2]; + + /* FIXME: Default to the creation time as stored in the private + * key. The parameter is here so that gpg can make sure that the + * timestamp as used for key creation (and thus the openPGP + * fingerprint) is used. */ timestamp_str = argc > 3? argv[3] : "19700101T000000"; if ((timestamp = isotime2epoch (timestamp_str)) == (time_t)(-1)) diff --git a/scd/app-common.h b/scd/app-common.h index 97274a7cb..2404086e9 100644 --- a/scd/app-common.h +++ b/scd/app-common.h @@ -33,6 +33,9 @@ /* Flags used with app_genkey. */ #define APP_GENKEY_FLAG_FORCE 1 /* Force overwriting existing key. */ +/* Flags used with app_writekey. */ +#define APP_WRITEKEY_FLAG_FORCE 1 /* Force overwriting existing key. */ + /* Bit flags set by the decipher function into R_INFO. */ #define APP_DECIPHER_INFO_NOPAD 1 /* Padding has been removed. */ diff --git a/scd/app-piv.c b/scd/app-piv.c index d55d71f25..6d6611572 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -510,9 +510,10 @@ add_tlv (unsigned char *buffer, unsigned int tag, size_t length) /* Function to build a list of TLV and return the result in a mallcoed * buffer. The varargs are tuples of (int,size_t,void) each with the * tag, the length and the actual data. A (0,0,NULL) tuple terminates - * the list. Up to 10 tuples are supported. */ + * the list. Up to 10 tuples are supported. If SECMEM is true the + * returned buffer is allocated in secure memory. */ static gpg_error_t -concat_tlv_list (unsigned char **r_result, size_t *r_resultlen, ...) +concat_tlv_list (int secure, unsigned char **r_result, size_t *r_resultlen, ...) { gpg_error_t err; va_list arg_ptr; @@ -573,7 +574,7 @@ concat_tlv_list (unsigned char **r_result, size_t *r_resultlen, ...) datalen += argv[i].len; } } - data = xtrymalloc (datalen); + data = secure? xtrymalloc_secure (datalen) : xtrymalloc (datalen); if (!data) { err = gpg_error_from_syserror (); @@ -2220,7 +2221,7 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, return err; /* Build the Dynamic Authentication Template. */ - err = concat_tlv_list (&apdudata, &apdudatalen, + err = concat_tlv_list (0, &apdudata, &apdudatalen, (int)0x7c, (size_t)0, NULL, /* Constructed. */ (int)0x82, (size_t)0, "", (int)0x81, (size_t)indatalen, indata, @@ -2423,7 +2424,7 @@ do_decipher (app_t app, const char *keyidstr, return err; /* Build the Dynamic Authentication Template. */ - err = concat_tlv_list (&apdudata, &apdudatalen, + err = concat_tlv_list (0, &apdudata, &apdudatalen, (int)0x7c, (size_t)0, NULL, /* Constructed. */ (int)0x82, (size_t)0, "", mechanism == PIV_ALGORITHM_RSA? @@ -2506,6 +2507,424 @@ does_key_exist (app_t app, data_object_t dobj, int generating, int force) } +/* Helper for do_writekey; here the RSA part. BUF, BUFLEN, and DEPTH + * are the current parser state of the S-expression with the key. */ +static gpg_error_t +writekey_rsa (app_t app, data_object_t dobj, int keyref, + const unsigned char *buf, size_t buflen, int depth) +{ + gpg_error_t err; + const unsigned char *tok; + size_t toklen; + int last_depth1, last_depth2; + const unsigned char *rsa_n = NULL; + const unsigned char *rsa_e = NULL; + const unsigned char *rsa_p = NULL; + const unsigned char *rsa_q = NULL; + unsigned char *rsa_dpm1 = NULL; + unsigned char *rsa_dqm1 = NULL; + unsigned char *rsa_qinv = NULL; + size_t rsa_n_len, rsa_e_len, rsa_p_len, rsa_q_len; + size_t rsa_dpm1_len, rsa_dqm1_len, rsa_qinv_len; + unsigned char *apdudata = NULL; + size_t apdudatalen; + unsigned char tmpl[1]; + + last_depth1 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth1) + { + if (tok) + { + err = gpg_error (GPG_ERR_UNKNOWN_SEXP); + goto leave; + } + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + + if (tok && toklen == 1) + { + const unsigned char **mpi; + size_t *mpi_len; + + switch (*tok) + { + case 'n': mpi = &rsa_n; mpi_len = &rsa_n_len; break; + case 'e': mpi = &rsa_e; mpi_len = &rsa_e_len; break; + case 'p': mpi = &rsa_p; mpi_len = &rsa_p_len; break; + case 'q': mpi = &rsa_q; mpi_len = &rsa_q_len; break; + default: mpi = NULL; mpi_len = NULL; break; + } + if (mpi && *mpi) + { + err = gpg_error (GPG_ERR_DUP_VALUE); + goto leave; + } + + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if (tok && mpi) + { + /* Strip off leading zero bytes and save. */ + for (;toklen && !*tok; toklen--, tok++) + ; + *mpi = tok; + *mpi_len = toklen; + } + } + /* Skip until end of list. */ + last_depth2 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth2) + ; + if (err) + goto leave; + } + + /* Check that we have all parameters. */ + if (!rsa_n || !rsa_e || !rsa_p || !rsa_q) + { + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + /* Fixme: Shall we check whether n == pq ? */ + + if (opt.verbose) + log_info ("RSA private key size is %u bytes\n", (unsigned int)rsa_n_len); + + /* Compute the dp, dq and u components. */ + { + gcry_mpi_t mpi_e, mpi_p, mpi_q; + gcry_mpi_t mpi_dpm1 = gcry_mpi_snew (0); + gcry_mpi_t mpi_dqm1 = gcry_mpi_snew (0); + gcry_mpi_t mpi_qinv = gcry_mpi_snew (0); + gcry_mpi_t mpi_tmp = gcry_mpi_snew (0); + + gcry_mpi_scan (&mpi_e, GCRYMPI_FMT_USG, rsa_e, rsa_e_len, NULL); + gcry_mpi_scan (&mpi_p, GCRYMPI_FMT_USG, rsa_p, rsa_p_len, NULL); + gcry_mpi_scan (&mpi_q, GCRYMPI_FMT_USG, rsa_q, rsa_q_len, NULL); + + gcry_mpi_sub_ui (mpi_tmp, mpi_p, 1); + gcry_mpi_invm (mpi_dpm1, mpi_e, mpi_tmp); + + gcry_mpi_sub_ui (mpi_tmp, mpi_q, 1); + gcry_mpi_invm (mpi_dqm1, mpi_e, mpi_tmp); + + gcry_mpi_invm (mpi_qinv, mpi_q, mpi_p); + + gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dpm1, &rsa_dpm1_len, mpi_dpm1); + gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dqm1, &rsa_dqm1_len, mpi_dqm1); + gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_qinv, &rsa_qinv_len, mpi_qinv); + + gcry_mpi_release (mpi_e); + gcry_mpi_release (mpi_p); + gcry_mpi_release (mpi_q); + gcry_mpi_release (mpi_dpm1); + gcry_mpi_release (mpi_dqm1); + gcry_mpi_release (mpi_qinv); + gcry_mpi_release (mpi_tmp); + } + + err = concat_tlv_list (1, &apdudata, &apdudatalen, + (int)0x01, (size_t)rsa_p_len, rsa_p, + (int)0x02, (size_t)rsa_q_len, rsa_q, + (int)0x03, (size_t)rsa_dpm1_len, rsa_dpm1, + (int)0x04, (size_t)rsa_dqm1_len, rsa_dqm1, + (int)0x05, (size_t)rsa_qinv_len, rsa_qinv, + (int)0, (size_t)0, NULL); + if (err) + goto leave; + + err = iso7816_send_apdu (app->slot, + -1, /* Use command chaining. */ + 0, /* Class */ + 0xfe, /* Ins: Yubikey Import Asym. Key. */ + PIV_ALGORITHM_RSA, /* P1 */ + keyref, /* P2 */ + apdudatalen,/* Lc */ + apdudata, /* data */ + NULL, NULL, NULL); + if (err) + goto leave; + + /* Write the public key to the cert object. */ + xfree (apdudata); + err = concat_tlv_list (0, &apdudata, &apdudatalen, + (int)0x81, (size_t)rsa_n_len, rsa_n, + (int)0x82, (size_t)rsa_e_len, rsa_e, + (int)0, (size_t)0, NULL); + + if (err) + goto leave; + tmpl[0] = PIV_ALGORITHM_RSA; + err = put_data (app->slot, dobj->tag, + (int)0x80, (size_t)1, tmpl, + (int)0x7f49, (size_t)apdudatalen, apdudata, + (int)0, (size_t)0, NULL); + + leave: + xfree (rsa_dpm1); + xfree (rsa_dqm1); + xfree (rsa_qinv); + xfree (apdudata); + return err; +} + + +/* Helper for do_writekey; here the ECC part. BUF, BUFLEN, and DEPTH + * are the current parser state of the S-expression with the key. */ +static gpg_error_t +writekey_ecc (app_t app, data_object_t dobj, int keyref, + const unsigned char *buf, size_t buflen, int depth) +{ + gpg_error_t err; + const unsigned char *tok; + size_t toklen; + int last_depth1, last_depth2; + int mechanism = 0; + const unsigned char *ecc_q = NULL; + const unsigned char *ecc_d = NULL; + size_t ecc_q_len, ecc_d_len; + unsigned char *apdudata = NULL; + size_t apdudatalen; + unsigned char tmpl[1]; + + last_depth1 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth1) + { + if (tok) + { + err = gpg_error (GPG_ERR_UNKNOWN_SEXP); + goto leave; + } + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + + if (tok && toklen == 5 && !memcmp (tok, "curve", 5)) + { + char *name; + const char *xname; + + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + + name = xtrymalloc (toklen+1); + if (!name) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (name, tok, toklen); + name[toklen] = 0; + /* Canonicalize the curve name. We use the openpgp + * functions here because Libgcrypt has no generic curve + * alias lookup feature and the PIV suppotred curves alre + * also supported by OpenPGP. */ + xname = openpgp_oid_to_curve (openpgp_curve_to_oid (name, NULL), 0); + xfree (name); + + if (xname && !strcmp (xname, "nistp256")) + mechanism = PIV_ALGORITHM_ECC_P256; + else if (xname && !strcmp (xname, "nistp384")) + mechanism = PIV_ALGORITHM_ECC_P384; + else + { + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + goto leave; + } + } + else if (tok && toklen == 1) + { + const unsigned char **mpi; + size_t *mpi_len; + + switch (*tok) + { + case 'q': mpi = &ecc_q; mpi_len = &ecc_q_len; break; + case 'd': mpi = &ecc_d; mpi_len = &ecc_d_len; break; + default: mpi = NULL; mpi_len = NULL; break; + } + if (mpi && *mpi) + { + err = gpg_error (GPG_ERR_DUP_VALUE); + goto leave; + } + + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if (tok && mpi) + { + /* Strip off leading zero bytes and save. */ + for (;toklen && !*tok; toklen--, tok++) + ; + *mpi = tok; + *mpi_len = toklen; + } + } + /* Skip until end of list. */ + last_depth2 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth2) + ; + if (err) + goto leave; + } + + /* Check that we have all parameters. */ + if (!mechanism || !ecc_q || !ecc_d) + { + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + + if (opt.verbose) + log_info ("ECC private key size is %u bytes\n", (unsigned int)ecc_d_len); + + err = concat_tlv_list (1, &apdudata, &apdudatalen, + (int)0x06, (size_t)ecc_d_len, ecc_d, + (int)0, (size_t)0, NULL); + if (err) + goto leave; + + err = iso7816_send_apdu (app->slot, + -1, /* Use command chaining. */ + 0, /* Class */ + 0xfe, /* Ins: Yubikey Import Asym. Key. */ + mechanism, /* P1 */ + keyref, /* P2 */ + apdudatalen,/* Lc */ + apdudata, /* data */ + NULL, NULL, NULL); + if (err) + goto leave; + + /* Write the public key to the cert object. */ + xfree (apdudata); + err = concat_tlv_list (0, &apdudata, &apdudatalen, + (int)0x86, (size_t)ecc_q_len, ecc_q, + (int)0, (size_t)0, NULL); + + if (err) + goto leave; + tmpl[0] = mechanism; + err = put_data (app->slot, dobj->tag, + (int)0x80, (size_t)1, tmpl, + (int)0x7f49, (size_t)apdudatalen, apdudata, + (int)0, (size_t)0, NULL); + + + leave: + xfree (apdudata); + return err; +} + + +/* Write a key to a slot. This command requires proprietary + * extensions of the PIV specification and is thus only implemnted for + * supported card types. The input is a canonical encoded + * S-expression with the secret key in KEYDATA and its length (for + * assertion) in KEYDATALEN. KEYREFSTR needs to be the usual 2 + * hexdigit slot number prefixed with "PIV." PINCB and PINCB_ARG are + * not used for PIV cards. + * + * Supported FLAGS are: + * APP_WRITEKEY_FLAG_FORCE Overwrite existing key. + */ +static gpg_error_t +do_writekey (app_t app, ctrl_t ctrl, + const char *keyrefstr, unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *keydata, size_t keydatalen) +{ + gpg_error_t err; + int force = !!(flags & APP_WRITEKEY_FLAG_FORCE); + data_object_t dobj; + int keyref; + const unsigned char *buf, *tok; + size_t buflen, toklen; + int depth; + + (void)ctrl; + (void)pincb; + (void)pincb_arg; + + if (!app->app_local->flags.yubikey) + { + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + /* Check keyref and test whether a key already exists. */ + dobj = find_dobj_by_keyref (app, keyrefstr); + if ((keyref = keyref_from_dobj (dobj)) == -1) + { + err = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + err = does_key_exist (app, dobj, 0, force); + if (err) + goto leave; + + /* Parse the S-expression with the key. */ + buf = keydata; + buflen = keydatalen; + depth = 0; + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if (!tok || toklen != 11 || memcmp ("private-key", tok, toklen)) + { + if (!tok) + ; + else if (toklen == 21 && !memcmp ("protected-private-key", tok, toklen)) + log_info ("protected-private-key passed to writekey\n"); + else if (toklen == 20 && !memcmp ("shadowed-private-key", tok, toklen)) + log_info ("shadowed-private-key passed to writekey\n"); + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + + /* First clear an existing key. We do this by writing an empty 7f49 + * tag. This will return GPG_ERR_NO_PUBKEY on a later read. */ + flush_cached_data (app, dobj->tag); + err = put_data (app->slot, dobj->tag, + (int)0x7f49, (size_t)0, "", + (int)0, (size_t)0, NULL); + if (err) + { + log_error ("piv: failed to clear the cert DO %s: %s\n", + dobj->keyref, gpg_strerror (err)); + goto leave; + } + + /* Divert to the algo specific implementation. */ + if (tok && toklen == 3 && memcmp ("rsa", tok, toklen) == 0) + err = writekey_rsa (app, dobj, keyref, buf, buflen, depth); + else if (tok && toklen == 3 && memcmp ("ecc", tok, toklen) == 0) + err = writekey_ecc (app, dobj, keyref, buf, buflen, depth); + else + err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); + + if (err) + { + /* A PIN is not required, thus use a better error code. */ + if (gpg_err_code (err) == GPG_ERR_BAD_PIN) + err = gpg_error (GPG_ERR_NO_AUTH); + log_error (_("failed to store the key: %s\n"), gpg_strerror (err)); + } + + leave: + return err; +} + + /* Parse an RSA response object, consisting of the content of tag * 0x7f49, into a gcrypt s-expression object and store that R_SEXP. * On error NULL is stored at R_SEXP. */ @@ -2694,10 +3113,6 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keyrefstr, const char *keytype, goto leave; - /* FIXME: Check that the authentication has already been done. */ - - - /* Create the key. */ log_info (_("please wait while key is being generated ...\n")); start_at = time (NULL); @@ -2774,12 +3189,13 @@ do_writecert (app_t app, ctrl_t ctrl, (void)pincb; /* Not used; instead authentication is needed. */ (void)pincb_arg; + if (!certlen) + return gpg_error (GPG_ERR_INV_CERT_OBJ); + dobj = find_dobj_by_keyref (app, certrefstr); if (!dobj || !*dobj->keyref) return gpg_error (GPG_ERR_INV_ID); - /* FIXME: Check that the authentication has already been done. */ - flush_cached_data (app, dobj->tag); /* Check that the public key parameters from the certificate match @@ -2796,6 +3212,7 @@ do_writecert (app_t app, ctrl_t ctrl, err = gpg_error (GPG_ERR_NO_SECKEY); /* Use a better error code. */ goto leave; } + /* Compare pubkeys. */ err = app_help_pubkey_from_cert (cert, certlen, &pk, &pklen); if (err) @@ -2806,7 +3223,6 @@ do_writecert (app_t app, ctrl_t ctrl, goto leave; } - err = put_data (app->slot, dobj->tag, (int)0x70, (size_t)certlen, cert,/* Certificate */ (int)0x71, (size_t)1, "", /* No compress */ @@ -2917,7 +3333,7 @@ app_select_piv (app_t app) app->fnc.getattr = do_getattr; app->fnc.setattr = do_setattr; app->fnc.writecert = do_writecert; - /* app->fnc.writekey = do_writekey; */ + app->fnc.writekey = do_writekey; app->fnc.genkey = do_genkey; app->fnc.sign = do_sign; app->fnc.auth = do_auth; diff --git a/scd/iso7816.c b/scd/iso7816.c index a9cd73014..d9f3336c7 100644 --- a/scd/iso7816.c +++ b/scd/iso7816.c @@ -222,6 +222,39 @@ iso7816_list_directory (int slot, int list_dirs, } +/* Wrapper around apdu_send. RESULT can be NULL if no result is + * expected. In addition to an gpg-error return code the actual + * status word is stored at R_SW unless that is NULL. */ +gpg_error_t +iso7816_send_apdu (int slot, int extended_mode, + int class, int ins, int p0, int p1, + int lc, const void *data, + unsigned int *r_sw, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (result) + { + *result = NULL; + *resultlen = 0; + } + + sw = apdu_send (slot, extended_mode, class, ins, p0, p1, lc, data, + result, resultlen); + if (sw != SW_SUCCESS && result) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + } + if (r_sw) + *r_sw = sw; + return map_sw (sw); +} + + /* This function sends an already formatted APDU to the card. With HANDLE_MORE set to true a MORE DATA status will be handled internally. The return value is a gpg error code (i.e. a mapped diff --git a/scd/iso7816.h b/scd/iso7816.h index df5d25fe8..c1940ad8d 100644 --- a/scd/iso7816.h +++ b/scd/iso7816.h @@ -61,6 +61,11 @@ gpg_error_t iso7816_select_path (int slot, const unsigned short *path, size_t pathlen); gpg_error_t iso7816_list_directory (int slot, int list_dirs, unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_send_apdu (int slot, int extended_mode, + int class, int ins, int p0, int p1, + int lc, const void *data, + unsigned int *r_sw, + unsigned char **result, size_t *resultlen); gpg_error_t iso7816_apdu_direct (int slot, const void *apdudata, size_t apdudatalen, int handle_more, unsigned int *r_sw, diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c index 55ecf126e..f7dbfd6ec 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -1155,49 +1155,30 @@ scd_writecert (const char *certidstr, -/* Handle a KEYDATA inquiry. Note, we only send the data, - assuan_transact takes care of flushing and writing the end */ -static gpg_error_t -inq_writekey_parms (void *opaque, const char *line) -{ - gpg_error_t err; - struct writekey_parm_s *parm = opaque; - - if (has_leading_keyword (line, "KEYDATA")) - { - err = assuan_send_data (parm->dflt->ctx, parm->keydata, parm->keydatalen); - } - else - err = default_inq_cb (parm->dflt, line); - - return err; -} - - -/* Send a WRITEKEY command to the SCdaemon. */ +/* Send a WRITEKEY command to the agent (so that the agent can fetch + * the key to write). KEYGRIP is the hexified keygrip of the source + * key which will be written to tye slot KEYREF. FORCE must be true + * to overwrite an existing key. */ gpg_error_t -scd_writekey (int keyno, const unsigned char *keydata, size_t keydatalen) +scd_writekey (const char *keyref, int force, const char *keygrip) { gpg_error_t err; + struct default_inq_parm_s parm; char line[ASSUAN_LINELENGTH]; - struct writekey_parm_s parms; - struct default_inq_parm_s dfltparm; - memset (&parms, 0, sizeof parms); - memset (&dfltparm, 0, sizeof dfltparm); + memset (&parm, 0, sizeof parm); err = start_agent (0); if (err) return err; - snprintf (line, sizeof line, "SCD WRITEKEY --force OPENPGP.%d", keyno); - dfltparm.ctx = agent_ctx; - parms.dflt = &dfltparm; - parms.keydata = keydata; - parms.keydatalen = keydatalen; - + /* Note: We don't send the s/n but "-" because gpg-agent has + * currently no use for it. */ + /* FIXME: For OpenPGP we should provide the creation time. */ + snprintf (line, sizeof line, "KEYTOCARD%s %s - %s", + force? " --force":"", keygrip, keyref); err = assuan_transact (agent_ctx, line, NULL, NULL, - inq_writekey_parms, &parms, NULL, NULL); + default_inq_cb, &parm, NULL, NULL); return status_sc_op_failure (err); } diff --git a/tools/gpg-card.c b/tools/gpg-card.c index 3f972fee4..bd450c0bb 100644 --- a/tools/gpg-card.c +++ b/tools/gpg-card.c @@ -1667,7 +1667,7 @@ cmd_readcert (card_info_t info, char *argstr) if (!info) return print_help ("READCERT CERTREF > FILE\n\n" - "Read the certificate for key 3 and store it in FILE.", + "Read the certificate for key CERTREF and store it in FILE.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); argstr = skip_options (argstr); @@ -1718,6 +1718,62 @@ cmd_readcert (card_info_t info, char *argstr) } +static gpg_error_t +cmd_writekey (card_info_t info, char *argstr) +{ + gpg_error_t err; + int opt_force; + char *argv[2]; + int argc; + char *keyref_buffer = NULL; + char *keyref; + char *keygrip; + + if (!info) + return print_help + ("WRITEKEY [--force] KEYREF KEYGRIP\n\n" + "Write a private key object identified by KEYGRIP to slot KEYREF.\n" + "Use --force to overwrite an existing key.", + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); + + opt_force = has_leading_option (argstr, "--force"); + argstr = skip_options (argstr); + + argc = split_fields (argstr, argv, DIM (argv)); + if (argc < 2) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + /* Upcase the keyref; prepend cardtype if needed. */ + keyref = argv[0]; + if (!strchr (keyref, '.')) + keyref_buffer = xstrconcat (app_type_string (info->apptype), ".", + keyref, NULL); + else + keyref_buffer = xstrdup (keyref); + ascii_strupr (keyref_buffer); + keyref = keyref_buffer; + + /* Get the keygrip. */ + keygrip = argv[1]; + if (strlen (keygrip) != 40 + && !(keygrip[0] == '&' && strlen (keygrip+1) == 40)) + { + log_error (_("Not a valid keygrip (expecting 40 hex digits)\n")); + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + err = scd_writekey (keyref, opt_force, keygrip); + + leave: + xfree (keyref_buffer); + return err; +} + + static gpg_error_t cmd_forcesig (card_info_t info) { @@ -2683,7 +2739,7 @@ ask_card_keyattr (int keyno, const struct key_attr *current, (void)algo; err = GPG_ERR_NOT_IMPLEMENTED; goto leave; - /* FIXME: We need to mve the ask_cure code out to common or + /* FIXME: We need to move the ask_cure code out to common or * provide another sultion. */ /* curve = ask_curve (&algo, NULL, curve); */ /* if (curve) */ @@ -2747,7 +2803,7 @@ do_change_keyattr (int keyno, const struct key_attr *key_attr) keyno+1, key_attr->algo, key_attr->curve); else { - /* FIXME: Above we use opnepgp algo names but in the error + /* FIXME: Above we use openpgp algo names but in the error * message we use the gcrypt names. We should settle for a * consistent solution. */ log_error (_("public key algorithm %d (%s) is not supported\n"), @@ -2918,7 +2974,7 @@ enum cmdids cmdQUIT, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY, cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, - cmdREADCERT, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP, + cmdREADCERT, cmdWRITEKEY, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP, cmdKEYATTR, cmdUIF, cmdAUTH, cmdYUBIKEY, cmdINVCMD }; @@ -2958,6 +3014,7 @@ static struct { "privatedo", cmdPRIVATEDO, N_("change a private data object")}, { "readcert", cmdREADCERT, N_("read a certificate from a data object")}, { "writecert", cmdWRITECERT, N_("store a certificate to a data object")}, + { "writekey", cmdWRITEKEY, N_("store a private key to a data object")}, { "yubikey", cmdYUBIKEY, N_("Yubikey management commands")}, { NULL, cmdINVCMD, NULL } }; @@ -3084,6 +3141,7 @@ dispatch_command (card_info_t info, const char *orig_command) case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; case cmdWRITECERT: err = cmd_writecert (info, argstr); break; case cmdREADCERT: err = cmd_readcert (info, argstr); break; + case cmdWRITEKEY: err = cmd_writekey (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info, argstr); break; case cmdPASSWD: err = cmd_passwd (info, argstr); break; @@ -3314,6 +3372,7 @@ interactive_loop (void) case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; case cmdWRITECERT: err = cmd_writecert (info, argstr); break; case cmdREADCERT: err = cmd_readcert (info, argstr); break; + case cmdWRITEKEY: err = cmd_writekey (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info, argstr); break; case cmdPASSWD: err = cmd_passwd (info, argstr); break; diff --git a/tools/gpg-card.h b/tools/gpg-card.h index 03bad7530..3a86a67ec 100644 --- a/tools/gpg-card.h +++ b/tools/gpg-card.h @@ -208,8 +208,7 @@ gpg_error_t scd_setattr (const char *name, const unsigned char *value, size_t valuelen); gpg_error_t scd_writecert (const char *certidstr, const unsigned char *certdata, size_t certdatalen); -gpg_error_t scd_writekey (int keyno, - const unsigned char *keydata, size_t keydatalen); +gpg_error_t scd_writekey (const char *keyref, int force, const char *keygrip); gpg_error_t scd_genkey (const char *keyref, int force, const char *algo, u32 *createtime); gpg_error_t scd_serialno (char **r_serialno, const char *demand); From 64caa6a08298119b10dc36ddd27b357cb47825b5 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 5 Mar 2019 17:17:39 +0100 Subject: [PATCH 208/235] scd: Rename a shared info field name. * scd/app-piv.c (app_select_piv): * scd/app-common.h (struct app_ctx_s): Rename 'card_version' to 'cardversion'. Rename all users. Add 'appversion'. Signed-off-by: Werner Koch --- scd/app-common.h | 3 ++- scd/app-openpgp.c | 24 ++++++++++++------------ scd/app-piv.c | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/scd/app-common.h b/scd/app-common.h index 2404086e9..3df896228 100644 --- a/scd/app-common.h +++ b/scd/app-common.h @@ -60,7 +60,8 @@ struct app_ctx_s { size_t serialnolen; /* Length in octets of serialnumber. */ const char *cardtype; /* NULL or string with the token's type. */ const char *apptype; - unsigned int card_version; + unsigned int cardversion;/* Firmware version of the token or 0. */ + unsigned int appversion; /* Version of the application or 0. */ unsigned int card_status; unsigned int reset_requested:1; unsigned int periodical_check_needed:1; diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index de8a7742b..8b6286a7d 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -476,7 +476,7 @@ get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes, for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++) ; - if (app->card_version > 0x0100 && data_objects[i].get_immediate_in_v11) + if (app->appversion > 0x0100 && data_objects[i].get_immediate_in_v11) { exmode = 0; rc = iso7816_get_data (app->slot, exmode, tag, &buffer, &buflen); @@ -816,7 +816,7 @@ store_fpr (app_t app, int keynumber, u32 timestamp, unsigned char *fpr, xfree (buffer); - tag = (app->card_version > 0x0007? 0xC7 : 0xC6) + keynumber; + tag = (app->appversion > 0x0007? 0xC7 : 0xC6) + keynumber; flush_cache_item (app, 0xC5); tag2 = 0xCE + keynumber; flush_cache_item (app, 0xCD); @@ -825,7 +825,7 @@ store_fpr (app_t app, int keynumber, u32 timestamp, unsigned char *fpr, if (rc) log_error (_("failed to store the fingerprint: %s\n"),gpg_strerror (rc)); - if (!rc && app->card_version > 0x0100) + if (!rc && app->appversion > 0x0100) { unsigned char buf[4]; @@ -1655,7 +1655,7 @@ get_public_key (app_t app, int keyno) m = e = NULL; /* (avoid cc warning) */ - if (app->card_version > 0x0100) + if (app->appversion > 0x0100) { int exmode, le_value; @@ -1838,7 +1838,7 @@ do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) do_getattr (app, ctrl, "PUBKEY-URL"); do_getattr (app, ctrl, "LOGIN-DATA"); do_getattr (app, ctrl, "KEY-FPR"); - if (app->card_version > 0x0100) + if (app->appversion > 0x0100) do_getattr (app, ctrl, "KEY-TIME"); do_getattr (app, ctrl, "CA-FPR"); do_getattr (app, ctrl, "CHV-STATUS"); @@ -3699,7 +3699,7 @@ rsa_writekey (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), /* Store the key. */ err = iso7816_put_data (app->slot, 0, - (app->card_version > 0x0007? 0xE0:0xE9)+keyno, + (app->appversion > 0x0007? 0xE0:0xE9)+keyno, template, template_len); } if (err) @@ -4851,7 +4851,7 @@ do_decipher (app_t app, const char *keyidstr, if (gpg_err_code (rc) == GPG_ERR_CARD /* actual SW is 0x640a */ && app->app_local->manufacturer == 5 - && app->card_version == 0x0200) + && app->appversion == 0x0200) log_info ("NOTE: Cards with manufacturer id 5 and s/n <= 346 (0x15a)" " do not work with encryption keys > 2048 bits\n"); @@ -5210,8 +5210,8 @@ app_select_openpgp (app_t app) log_printhex (buffer, buflen, ""); } - app->card_version = buffer[6] << 8; - app->card_version |= buffer[7]; + app->appversion = buffer[6] << 8; + app->appversion |= buffer[7]; manufacturer = (buffer[8]<<8 | buffer[9]); xfree (app->serialno); @@ -5227,10 +5227,10 @@ app_select_openpgp (app_t app) app->app_local->manufacturer = manufacturer; - if (app->card_version >= 0x0200) + if (app->appversion >= 0x0200) app->app_local->extcap.is_v2 = 1; - if (app->card_version >= 0x0300) + if (app->appversion >= 0x0300) app->app_local->extcap.extcap_v3 = 1; /* Read the historical bytes. */ @@ -5297,7 +5297,7 @@ app_select_openpgp (app_t app) /* Some of the first cards accidentally don't set the CHANGE_FORCE_CHV bit but allow it anyway. */ - if (app->card_version <= 0x0100 && manufacturer == 1) + if (app->appversion <= 0x0100 && manufacturer == 1) app->app_local->extcap.change_force_chv = 1; /* Check optional DO of "General Feature Management" for button. */ diff --git a/scd/app-piv.c b/scd/app-piv.c index 6d6611572..5748c70b8 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -3292,7 +3292,7 @@ app_select_piv (app_t app) err = gpg_error (GPG_ERR_CARD); goto leave; } - app->card_version = ((s[4] << 8) | s[5]); + app->appversion = ((s[4] << 8) | s[5]); s = find_tlv (apt, aptlen, 0x79, &n); if (!s || n < 7) From 8d4af54ddd039d47e9c4803559193fcca97f0a46 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 5 Mar 2019 17:40:08 +0100 Subject: [PATCH 209/235] card: Print card version. Check for bad Yubikeys. * scd/app.c (app_new_register): Set card version for Yubikeys. (app_write_learn_status): Print CARDVERSION and APPVERSION. * tools/card-call-scd.c (learn_status_cb): Detect them. * tools/gpg-card.h (struct card_info_s): Add appversion and cardversion. * tools/gpg-card.c (list_openpgp): Remove version printing from serial number. (print_a_version): New. (list_card): Print card and app version. (cmd_generate): Do not allow broken Yubikeys. Signed-off-by: Werner Koch --- scd/app.c | 20 +++++++++++++++----- scd/command.c | 2 +- tools/card-call-scd.c | 16 +++++++++++++++- tools/gpg-card.c | 40 ++++++++++++++++++++++++++++++++++------ tools/gpg-card.h | 2 ++ 5 files changed, 67 insertions(+), 13 deletions(-) diff --git a/scd/app.c b/scd/app.c index ada1eb963..2ee104d9e 100644 --- a/scd/app.c +++ b/scd/app.c @@ -263,6 +263,9 @@ app_new_register (int slot, ctrl_t ctrl, const char *name, * set the serial number. */ } } + s1 = find_tlv (buf+1, buflen-1, 0x05, &n); /* version */ + if (s1 && n == 3) + app->cardversion = ((s1[0]<<16)|(s1[1]<<8)|s1[2]); } } xfree (buf); @@ -632,7 +635,7 @@ app_get_serialno (app_t app) } -/* Write out the application specifig status lines for the LEARN +/* Write out the application specific status lines for the LEARN command. */ gpg_error_t app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) @@ -645,10 +648,17 @@ app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); /* We do not send CARD and APPTYPE if only keypairinfo is requested. */ - if (app->cardtype && !(flags & 1)) - send_status_direct (ctrl, "CARDTYPE", app->cardtype); - if (app->apptype && !(flags & 1)) - send_status_direct (ctrl, "APPTYPE", app->apptype); + if (!(flags &1)) + { + if (app->cardtype) + send_status_direct (ctrl, "CARDTYPE", app->cardtype); + if (app->cardversion) + send_status_printf (ctrl, "CARDVERSION", "%X", app->cardversion); + if (app->apptype) + send_status_direct (ctrl, "APPTYPE", app->apptype); + if (app->appversion) + send_status_printf (ctrl, "APPVERSION", "%X", app->appversion); + } err = lock_app (app, ctrl); if (err) diff --git a/scd/command.c b/scd/command.c index 5b2ca6c29..0d1a5cd3f 100644 --- a/scd/command.c +++ b/scd/command.c @@ -1120,7 +1120,7 @@ static const char hlp_genkey[] = "\n" " 'p' and 'n' are the names of the RSA parameters; '-' is used to\n" " indicate that HEXDATA is the first chunk of a parameter given\n" - " by the next KEY-DATA.\n" + " by the next KEY-DATA. Only used by GnuPG version < 2.1.\n" "\n" "--force is required to overwrite an already existing key. The\n" "KEY-CREATED-AT is required for further processing because it is\n" diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c index f7dbfd6ec..0a01bf5ca 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -818,7 +818,7 @@ learn_status_cb (void *opaque, const char *line) parm->chvusage[0] = byte1; parm->chvusage[1] = byte2; } - break; + break; case 10: if (!memcmp (keyword, "PUBKEY-URL", keywordlen)) @@ -880,6 +880,13 @@ learn_status_cb (void *opaque, const char *line) xfree (buf); } + else if (!memcmp (keyword, "APPVERSION", keywordlen)) + { + unsigned int val = 0; + + sscanf (line, "%x", &val); + parm->appversion = val; + } break; case 11: @@ -944,6 +951,13 @@ learn_status_cb (void *opaque, const char *line) } } } + else if (!memcmp (keyword, "CARDVERSION", keywordlen)) + { + unsigned int val = 0; + + sscanf (line, "%x", &val); + parm->cardversion = val; + } break; case 12: diff --git a/tools/gpg-card.c b/tools/gpg-card.c index bd450c0bb..89cefdeb6 100644 --- a/tools/gpg-card.c +++ b/tools/gpg-card.c @@ -781,11 +781,6 @@ list_openpgp (card_info_t info, estream_t fp) return; } - tty_fprintf (fp, "Version ..........: %.1s%c.%.1s%c\n", - info->serialno[12] == '0'?"":info->serialno+12, - info->serialno[13], - info->serialno[14] == '0'?"":info->serialno+14, - info->serialno[15]); tty_fprintf (fp, "Manufacturer .....: %s\n", get_manufacturer (xtoi_2(info->serialno+16)*256 + xtoi_2 (info->serialno+18))); @@ -941,6 +936,25 @@ list_piv (card_info_t info, estream_t fp) } + +static void +print_a_version (estream_t fp, const char *prefix, unsigned int value) +{ + unsigned int a, b, c, d; + a = ((value >> 24) & 0xff); + b = ((value >> 16) & 0xff); + c = ((value >> 8) & 0xff); + d = ((value ) & 0xff); + + if (a) + tty_fprintf (fp, "%s %u.%u.%u.%u\n", prefix, a, b, c, d); + else if (b) + tty_fprintf (fp, "%s %u.%u.%u\n", prefix, b, c, d); + else + tty_fprintf (fp, "%s %u.%u\n", prefix, c, d); +} + + /* Print all available information about the current card. */ static void list_card (card_info_t info) @@ -951,6 +965,8 @@ list_card (card_info_t info) info->reader? info->reader : "[none]"); if (info->cardtype) tty_fprintf (fp, "Card type ........: %s\n", info->cardtype); + if (info->cardversion) + print_a_version (fp, "Card firmware ....:", info->cardversion); tty_fprintf (fp, "Serial number ....: %s\n", info->serialno? info->serialno : "[none]"); tty_fprintf (fp, "Application type .: %s%s%s%s\n", @@ -959,9 +975,11 @@ list_card (card_info_t info) info->apptype == APP_TYPE_UNKNOWN && info->apptypestr ? info->apptypestr:"", info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? ")":""); + if (info->appversion) + print_a_version (fp, "Version ..........:", info->appversion); if (info->serialno && info->dispserialno && strcmp (info->serialno, info->dispserialno)) - tty_fprintf (fp, "Displayed S/N ....: %s\n", info->dispserialno); + tty_fprintf (fp, "Displayed s/n ....: %s\n", info->dispserialno); switch (info->apptype) { @@ -2044,6 +2062,16 @@ cmd_generate (card_info_t info, char *argstr) keyref = keyref_buffer; } + /* Special checks. */ + if ((info->cardtype && !strcmp (info->cardtype, "yubikey")) + && info->cardversion >= 0x040200 && info->cardversion < 0x040305) + { + log_error ("On-chip key generation on this YubiKey has been blocked.\n"); + log_info ("Please see for details\n"); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + /* Divert to dedicated functions. */ if (info->apptype == APP_TYPE_OPENPGP) { diff --git a/tools/gpg-card.h b/tools/gpg-card.h index 3a86a67ec..099ea5448 100644 --- a/tools/gpg-card.h +++ b/tools/gpg-card.h @@ -142,8 +142,10 @@ struct card_info_s int error; /* private. */ char *reader; /* Reader information. */ char *cardtype; /* NULL or type of the card. */ + unsigned int cardversion; /* Firmware version of the card. */ char *apptypestr; /* Malloced application type string. */ app_type_t apptype;/* Translated from APPTYPESTR. */ + unsigned int appversion; /* Version of the application. */ char *serialno; /* malloced hex string. */ char *dispserialno;/* malloced string. */ char *disp_name; /* malloced. */ From 2abad7585a004586e27ead6ab8c9c57ce5ed1326 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 6 Mar 2019 10:33:54 +0900 Subject: [PATCH 210/235] agent: Fix detection of exit of scdaemon. * agent/call-scd.c (start_scd): Acquire START_SCD_LOCK for SCD_LOCAL_LIST. Move common case code to fast path. Release START_SCD_LOCK before calling unlock_scd. When new CTX is allocated, clear INVALID flag. (agent_reset_scd): Serialize the access to SCD_LOCAL_LIST by START_SCD_LOCK. -- GnuPG-bug-id: 4377 Signed-off-by: NIIBE Yutaka --- agent/call-scd.c | 142 ++++++++++++++++++++++++++--------------------- 1 file changed, 80 insertions(+), 62 deletions(-) diff --git a/agent/call-scd.c b/agent/call-scd.c index 4c0186d74..b2266225e 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -303,33 +303,19 @@ start_scd (ctrl_t ctrl) if (opt.disable_scdaemon) return gpg_error (GPG_ERR_NOT_SUPPORTED); - /* If this is the first call for this session, setup the local data - structure. */ - if (!ctrl->scd_local) + if (ctrl->scd_local && ctrl->scd_local->ctx) { - ctrl->scd_local = xtrycalloc (1, sizeof *ctrl->scd_local); - if (!ctrl->scd_local) - return gpg_error_from_syserror (); - ctrl->scd_local->next_local = scd_local_list; - scd_local_list = ctrl->scd_local; + ctrl->scd_local->in_use = 1; + return 0; /* Okay, the context is fine. */ } - if (ctrl->scd_local->in_use) + if (ctrl->scd_local && ctrl->scd_local->in_use) { log_error ("start_scd: CTX is in use\n"); return gpg_error (GPG_ERR_INTERNAL); } - ctrl->scd_local->in_use = 1; - if (ctrl->scd_local->ctx) - return 0; /* Okay, the context is fine. We used to test for an - alive context here and do an disconnect. Now that we - have a ticker function to check for it, it is easier - not to check here but to let the connection run on an - error instead. */ - - - /* We need to protect the following code. */ + /* We need to serialize the access to scd_local_list and primary_scd_ctx. */ rc = npth_mutex_lock (&start_scd_lock); if (rc) { @@ -338,6 +324,25 @@ start_scd (ctrl_t ctrl) return gpg_error (GPG_ERR_INTERNAL); } + /* If this is the first call for this session, setup the local data + structure. */ + if (!ctrl->scd_local) + { + ctrl->scd_local = xtrycalloc (1, sizeof *ctrl->scd_local); + if (!ctrl->scd_local) + { + err = gpg_error_from_syserror (); + rc = npth_mutex_unlock (&start_scd_lock); + if (rc) + log_error ("failed to release the start_scd lock: %s\n", strerror (rc)); + return err; + } + ctrl->scd_local->next_local = scd_local_list; + scd_local_list = ctrl->scd_local; + } + + ctrl->scd_local->in_use = 1; + /* Check whether the pipe server has already been started and in this case either reuse a lingering pipe connection or establish a new socket based one. */ @@ -522,6 +527,10 @@ start_scd (ctrl_t ctrl) } leave: + rc = npth_mutex_unlock (&start_scd_lock); + if (rc) + log_error ("failed to release the start_scd lock: %s\n", strerror (rc)); + xfree (abs_homedir); if (err) { @@ -531,11 +540,9 @@ start_scd (ctrl_t ctrl) } else { + ctrl->scd_local->invalid = 0; ctrl->scd_local->ctx = ctx; } - rc = npth_mutex_unlock (&start_scd_lock); - if (rc) - log_error ("failed to release the start_scd lock: %s\n", strerror (rc)); return err; } @@ -555,53 +562,64 @@ agent_scd_check_running (void) int agent_reset_scd (ctrl_t ctrl) { - if (ctrl->scd_local) + int err = npth_mutex_lock (&start_scd_lock); + + if (err) { - if (ctrl->scd_local->ctx) + log_error ("failed to acquire the start_scd lock: %s\n", + strerror (err)); + } + else + { + if (ctrl->scd_local) { - /* We can't disconnect the primary context because libassuan - does a waitpid on it and thus the system would hang. - Instead we send a reset and keep that connection for - reuse. */ - if (ctrl->scd_local->ctx == primary_scd_ctx) + if (ctrl->scd_local->ctx) { - /* Send a RESTART to the SCD. This is required for the - primary connection as a kind of virtual EOF; we don't - have another way to tell it that the next command - should be viewed as if a new connection has been - made. For the non-primary connections this is not - needed as we simply close the socket. We don't check - for an error here because the RESTART may fail for - example if the scdaemon has already been terminated. - Anyway, we need to set the reusable flag to make sure - that the aliveness check can clean it up. */ - assuan_transact (primary_scd_ctx, "RESTART", - NULL, NULL, NULL, NULL, NULL, NULL); - primary_scd_ctx_reusable = 1; + /* We send a reset and keep that connection for reuse. */ + if (ctrl->scd_local->ctx == primary_scd_ctx) + { + /* Send a RESTART to the SCD. This is required for the + primary connection as a kind of virtual EOF; we don't + have another way to tell it that the next command + should be viewed as if a new connection has been + made. For the non-primary connections this is not + needed as we simply close the socket. We don't check + for an error here because the RESTART may fail for + example if the scdaemon has already been terminated. + Anyway, we need to set the reusable flag to make sure + that the aliveness check can clean it up. */ + assuan_transact (primary_scd_ctx, "RESTART", + NULL, NULL, NULL, NULL, NULL, NULL); + primary_scd_ctx_reusable = 1; + } + else + assuan_release (ctrl->scd_local->ctx); + ctrl->scd_local->ctx = NULL; } - else - assuan_release (ctrl->scd_local->ctx); - ctrl->scd_local->ctx = NULL; - } - /* Remove the local context from our list and release it. */ - if (!scd_local_list) - BUG (); - else if (scd_local_list == ctrl->scd_local) - scd_local_list = ctrl->scd_local->next_local; - else - { - struct scd_local_s *sl; - - for (sl=scd_local_list; sl->next_local; sl = sl->next_local) - if (sl->next_local == ctrl->scd_local) - break; - if (!sl->next_local) + /* Remove the local context from our list and release it. */ + if (!scd_local_list) BUG (); - sl->next_local = ctrl->scd_local->next_local; + else if (scd_local_list == ctrl->scd_local) + scd_local_list = ctrl->scd_local->next_local; + else + { + struct scd_local_s *sl; + + for (sl=scd_local_list; sl->next_local; sl = sl->next_local) + if (sl->next_local == ctrl->scd_local) + break; + if (!sl->next_local) + BUG (); + sl->next_local = ctrl->scd_local->next_local; + } + xfree (ctrl->scd_local); + ctrl->scd_local = NULL; } - xfree (ctrl->scd_local); - ctrl->scd_local = NULL; + + err = npth_mutex_unlock (&start_scd_lock); + if (err) + log_error ("failed to release the start_scd lock: %s\n", strerror (err)); } return 0; From 772bba34ea089b3a00f0b1ea5138ba7422c95180 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 6 Mar 2019 10:23:56 +0100 Subject: [PATCH 211/235] card: Print the keyref also for non-initialized slots. * tools/gpg-card.c (list_one_kinfo): Add arg label_keyref and change callers. -- Signed-off-by: Werner Koch --- tools/gpg-card.c | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/tools/gpg-card.c b/tools/gpg-card.c index 89cefdeb6..c9b8380cd 100644 --- a/tools/gpg-card.c +++ b/tools/gpg-card.c @@ -609,9 +609,11 @@ mem_is_zero (const char *mem, unsigned int memlen) -/* Helper to list a single keyref. */ +/* Helper to list a single keyref. LABEL_KEYREF is a fallback key + * reference if no info is available; it may be NULL. */ static void -list_one_kinfo (key_info_t firstkinfo, key_info_t kinfo, estream_t fp) +list_one_kinfo (key_info_t firstkinfo, key_info_t kinfo, + const char *label_keyref, estream_t fp) { gpg_error_t err; keyblock_t keyblock = NULL; @@ -629,6 +631,7 @@ list_one_kinfo (key_info_t firstkinfo, key_info_t kinfo, estream_t fp) if (mem_is_zero (kinfo->grip, sizeof kinfo->grip)) { tty_fprintf (fp, "[none]\n"); + tty_fprintf (fp, " keyref .....: %s\n", kinfo->keyref); goto leave; } @@ -719,7 +722,11 @@ list_one_kinfo (key_info_t firstkinfo, key_info_t kinfo, estream_t fp) } } else - tty_fprintf (fp, " [none]\n"); + { + tty_fprintf (fp, " [none]\n"); + if (label_keyref) + tty_fprintf (fp, " keyref .....: %s\n", label_keyref); + } leave: release_keyblock (keyblock); @@ -743,7 +750,7 @@ list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp) { tty_fprintf (fp, "%s", labels[idx].label); kinfo = find_kinfo (info, labels[idx].keyref); - list_one_kinfo (info->kinfo, kinfo, fp); + list_one_kinfo (info->kinfo, kinfo, labels[idx].keyref, fp); if (kinfo) kinfo->xflag = 1; } @@ -756,7 +763,7 @@ list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp) for (i=5+strlen (kinfo->keyref); i < 18; i++) tty_fprintf (fp, "."); tty_fprintf (fp, ":"); - list_one_kinfo (info->kinfo, kinfo, fp); + list_one_kinfo (info->kinfo, kinfo, NULL, fp); } } @@ -857,22 +864,6 @@ list_openpgp (card_info_t info, estream_t fp) list_all_kinfo (info, keyinfolabels, fp); - /* tty_fprintf (fp, "General key info->.: "); */ - /* thefpr = (info->fpr1len? info->fpr1 : info->fpr2len? info->fpr2 : */ - /* info->fpr3len? info->fpr3 : NULL); */ - /* thefprlen = (info->fpr1len? info->fpr1len : info->fpr2len? info->fpr2len : */ - /* info->fpr3len? info->fpr3len : 0); */ - /* If the fingerprint is all 0xff, the key has no associated - OpenPGP certificate. */ - /* if ( thefpr && !mem_is_ff (thefpr, thefprlen) */ - /* && !get_pubkey_byfprint (ctrl, pk, &keyblock, thefpr, thefprlen)) */ - /* { */ - /* print_pubkey_info (ctrl, fp, pk); */ - /* if (keyblock) */ - /* print_card_key_info (fp, keyblock); */ - /* } */ - /* else */ - /* tty_fprintf (fp, "[none]\n"); */ } From 4e1f04a4cd30859507218395e630e886801ae2b7 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 6 Mar 2019 12:40:45 +0100 Subject: [PATCH 212/235] card: Allow PEM encoded certificates in "writecert". * tools/gpg-card.c (cmd_writecert): Convert from base64. Signed-off-by: Werner Koch --- tools/gpg-card.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tools/gpg-card.c b/tools/gpg-card.c index c9b8380cd..e2d728dab 100644 --- a/tools/gpg-card.c +++ b/tools/gpg-card.c @@ -1647,6 +1647,20 @@ cmd_writecert (card_info_t info, char *argstr) err = get_data_from_file (argstr, &data, &datalen); if (err) goto leave; + if (ascii_memistr (data, datalen, "-----BEGIN CERTIFICATE-----") + && ascii_memistr (data, datalen, "-----END CERTIFICATE-----") + && !memchr (data, 0, datalen) && !memchr (data, 1, datalen)) + { + struct b64state b64; + + err = b64dec_start (&b64, ""); + if (!err) + err = b64dec_proc (&b64, data, datalen, &datalen); + if (!err) + err = b64dec_finish (&b64); + if (err) + goto leave; + } } else { From af9f4fb3d20487caab6878302217000073c7720c Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 6 Mar 2019 12:46:09 +0100 Subject: [PATCH 213/235] doc: First take on instructions on how to init PIV cards -- Signed-off-by: Werner Koch --- doc/gpg-card.texi | 391 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 391 insertions(+) diff --git a/doc/gpg-card.texi b/doc/gpg-card.texi index 92bc12362..aa49f81e7 100644 --- a/doc/gpg-card.texi +++ b/doc/gpg-card.texi @@ -63,6 +63,8 @@ A list of commands is available by using the command @code{help} and a detailed description of each command is printed by using @code{help COMMAND}. +See the NOTES sections for instructions pertaining to specific cards +or card applications. @mansect options @noindent @@ -119,6 +121,395 @@ Specify a non-default gpgsm binary to be used by certain commands. @end table +@mansect notes (OpenPGP) +The support for OpenPGP cards in @command{gpg-card} is not yet +complete. For missing features, please continue to use @code{gpg +--card-edit}. + +@mansect notes (PIV) +@noindent +GnuPG has support for PIV cards (``Personal Identity Verification'' +as specified by NIST Special Publication 800-73-4). This section +describes how to initialize (personalize) a fresh Yubikey token +featuring the PIV application (requires Yubikey-5). We assume that +the credentials have not yet been changed and thus are: +@table @asis +@item Authentication key +This is a 24 byte key described by the hex string +@code{010203040506070801020304050607080102030405060708}. +@item PIV Application PIN +This is the string @code{123456}. +@item PIN Unblocking Key +This is the string @code{12345678}. +@end table +See the example section on how to change these defaults. For +production use it is important to use secure values for them. Note that +the Authentication Key is not queried via the usual Pinentry dialog +but needs to be entered manually or read from a file. The use of a +dedicated machine to personalize tokens is strongly suggested. + +To see what is on the card, the command @code{list} can be given. We +will use the interactive mode in the following (the string +@emph{gpg/card>} is the prompt). An example output for a fresh card +is: + +@example +gpg/card> list +Reader ...........: 1050:0407:X:0 +Card type ........: yubikey +Card firmware ....: 5.1.2 +Serial number ....: D2760001240102010006090746250000 +Application type .: OpenPGP +Version ..........: 2.1 +[...] +@end example + +It can be seen by the ``Application type'' line that GnuPG selected the +OpenPGP application of the Yubikey. This is because GnuPG assigns the +highest priority to the OpenPGP application. To use the PIV +application of the Yubikey, the OpenPGP application needs to be +disabled: + +@example +gpg/card> yubikey disable all opgp +gpg/card> yubikey list +Application USB NFC +----------------------- +OTP yes yes +U2F yes yes +OPGP no no +PIV yes no +OATH yes yes +FIDO2 yes yes +gpg/card> reset +@end example + +The @code{reset} is required so that the GnuPG system rereads the +card. Note that disabled applications keep all their data and can at +any time be re-enabled (see @emph{help yubikey}). Now a @emph{list} +command shows this: + +@example +gpg/card> list +Reader ...........: 1050:0407:X:0 +Card type ........: yubikey +Card firmware ....: 5.1.2 +Serial number ....: FF020001008A77C1 +Application type .: PIV +Version ..........: 1.0 +Displayed s/n ....: yk-9074625 +PIN usage policy .: app-pin +PIN retry counter : - 3 - +PIV authentication: [none] + keyref .....: PIV.9A +Card authenticat. : [none] + keyref .....: PIV.9E +Digital signature : [none] + keyref .....: PIV.9C +Key management ...: [none] + keyref .....: PIV.9D +@end example + +Note that the ``Displayed s/sn'' is printed on the token and also +shown in Pinentry prompts asking for the PIN. The four standard key +slots are always shown, if other key slots are initialized they are +shown as well. The @emph{PIV authentication} key (internal reference +@emph{PIV.9A}) is used to authenticate the card and the card holder. +The use of the associated private key is protected by the Application +PIN which needs to be provided once and the key can the be used until +the card is reset or removed from the reader or USB port. GnuPG uses +this key with its @emph{Secure Shell} support. The @emph{Card +authentication} key (@emph{PIV.9E}) is also known as the CAK and used +to support physical access applications. The private key is not +protected by a PIN and can thus immediately be used. The @emph{Digital +signature} key (@emph{PIV.9C}) is used to digitally sign documents. +The use of the associated private key is protected by the Application +PIN which needs to be provided for each signing operation. The +@emph{Key management} key (@emph{PIV.9D}) is used for encryption. The +use of the associated private key is protected by the Application PIN +which needs to be provided only once so that decryption operations can +then be done until the card is reset or removed from the reader or USB +port. + +We now generate tree of the four keys. Note that GnuPG does currently +not use the the @emph{Card authentication} key but because it is +mandatory by the specs we create it anyway. Key generation requires +that we authenticate to the card. This can be done either on the +command line (which would reveal the key): + +@example +gpg/card> auth 010203040506070801020304050607080102030405060708 +@end example + +or by reading the key from a file. That file needs to consist of one +LF terminated line with the hex encoded key (as above): + +@example +gpg/card> auth < myauth.key +@end example + +As usual @samp{help auth} gives help for this command. An error +message is printed if a non-matching key is used. The authentication +is valid until a reset of the card or until the card is removed from +the reader or the USB port. Note that that in non-interactive mode +the @samp{<} needs to be quoted so that the shell does not interpret +it as a its own redirection symbol. + +@noindent +Here are the actual commands to generate the keys: + +@example +gpg/card> generate --algo=nistp384 PIV.9A +PIV card no. yk-9074625 detected +gpg/card> generate --algo=nistp256 PIV.9E +PIV card no. yk-9074625 detected +gpg/card> generate --algo=rsa2048 PIV.9C +PIV card no. yk-9074625 detected +@end example + +If a key has already been created for one of the slots an error will +be printed; to create a new key anyway the option @samp{--force} can be +used. Note that only the private and public keys have been created +but no certificates are stored in the key slots. In fact, GnuPG uses +its own non-standard method to store just the public key in place of +the the certificate. Other application will not be able to make use +these keys until @command{gpgsm} or another tool has been used to +create and store the respective certificates. Let us see what the +list command now shows: + +@example +gpg/card> list +Reader ...........: 1050:0407:X:0 +Card type ........: yubikey +Card firmware ....: 5.1.2 +Serial number ....: FF020001008A77C1 +Application type .: PIV +Version ..........: 1.0 +Displayed s/n ....: yk-9074625 +PIN usage policy .: app-pin +PIN retry counter : - 3 - +PIV authentication: 213D1825FDE0F8240CB4E4229F01AF90AC658C2E + keyref .....: PIV.9A (auth) + algorithm ..: nistp384 +Card authenticat. : 7A53E6CFFE7220A0E646B4632EE29E5A7104499C + keyref .....: PIV.9E (auth) + algorithm ..: nistp256 +Digital signature : 32A6C6FAFCB8421878608AAB452D5470DD3223ED + keyref .....: PIV.9C (sign,cert) + algorithm ..: rsa2048 +Key management ...: [none] + keyref .....: PIV.9D +@end example + +The primary information for each key is the @emph{keygrip}, a 40 byte +hex-string identifying the key. This keygrip is a unique identifier +for the specific parameters of a key. It is used by +@command{gpg-agent} and other parts of GnuPG to associate a private +key to its protocol specific certificate format (X.509, OpenPGP, or +SecureShell). Below the keygrip the key reference along with the key +usage capabilities are show. Finally the algorithm is printed in the +format used by @command {gpg}. At that point no other information is +shown because for these new keys gpg won't be able to find matching +certificates. + +Although we could have created the @emph{Key management} key also with +the generate command, we will create that key off-card so that a +backup exists. To accomplish this a key needs to be created with +either @command{gpg} or @command{gpgsm} or imported in one of these +tools. In our example we create a self-signed X.509 certificate (exit +the gpg-card tool, first): + +@example +$ gpgsm --gen-key -o encr.crt + (1) RSA + (2) Existing key + (3) Existing key from card +Your selection? 1 +What keysize do you want? (3072) 2048 +Requested keysize is 2048 bits +Possible actions for a RSA key: + (1) sign, encrypt + (2) sign + (3) encrypt +Your selection? 3 +Enter the X.509 subject name: CN=Encryption key for yk-9074625,O=example,C=DE +Enter email addresses (end with an empty line): +> otto@@example.net +> +Enter DNS names (optional; end with an empty line): +> +Enter URIs (optional; end with an empty line): +> +Create self-signed certificate? (y/N) y +These parameters are used: + Key-Type: RSA + Key-Length: 2048 + Key-Usage: encrypt + Serial: random + Name-DN: CN=Encryption key for yk-9074625,O=example,C=DE + Name-Email: otto@@example.net + +Proceed with creation? (y/N) +Now creating self-signed certificate. This may take a while ... +gpgsm: about to sign the certificate for key: &34798AAFE0A7565088101CC4AE31C5C8C74461CB +gpgsm: certificate created +Ready. +$ gpgsm --import encr.crt +gpgsm: certificate imported +gpgsm: total number processed: 1 +gpgsm: imported: 1 +@end example + +Note the last steps which imported the created certificate. If you +you instead created a certificate signing request (CSR) instead of a +self-signed certificate and sent this off to a CA you would do the +same import step with the certificate received from the CA. Take note +of the keygrip (prefixed with an ampersand) as shown during the +certificate creation or listed it again using @samp{gpgsm +--with-keygrip -k otto@@example.net}. Now to move the key and +certificate to the card start @command{gpg-card} again and enter: + +@example +gpg/card> writekey PIV.9D 34798AAFE0A7565088101CC4AE31C5C8C74461CB +gpg/card> writecert PIV.9D < encr.crt +@end example + +If you entered a passphrase to protect the private key, you will be +asked for it via the Pinentry prompt. On success the key and the +certificate has been written to the card and a @code{list} command +shows: + +@example +[...] +Key management ...: 34798AAFE0A7565088101CC4AE31C5C8C74461CB + keyref .....: PIV.9D (encr) + algorithm ..: rsa2048 + used for ...: X.509 + user id ..: CN=Encryption key for yk-9074625,O=example,C=DE + user id ..: +@end example + +In case the same key (identified by the keygrip) has been used for +several certificates you will see several ``used for'' parts. With +this the encryption key is now fully functional and can be used to +decrypt messages encrypted to this certificate. @sc{Take care:} the +original key is still stored on-disk and should be moved to a backup +medium. This can simply be done by copying the file +@file{34798AAFE0A7565088101CC4AE31C5C8C74461CB.key} from the directory +@file{~/.gnupg/private-keys-v1.d/} to the backup medium and deleting +the file at its original place. + +The final example is to create a self-signed certificate for digital +signatures. Leave @command{gpg-card} using @code{quit} or by pressing +Control-D and use gpgsm: + +@example +$ gpgsm --learn +$ gpgsm --gen-key -o sign.crt +Please select what kind of key you want: + (1) RSA + (2) Existing key + (3) Existing key from card +Your selection? 3 +Serial number of the card: FF020001008A77C1 +Available keys: + (1) 213D1825FDE0F8240CB4E4229F01AF90AC658C2E PIV.9A nistp384 + (2) 7A53E6CFFE7220A0E646B4632EE29E5A7104499C PIV.9E nistp256 + (3) 32A6C6FAFCB8421878608AAB452D5470DD3223ED PIV.9C rsa2048 + (4) 34798AAFE0A7565088101CC4AE31C5C8C74461CB PIV.9D rsa2048 +Your selection? 3 +Possible actions for a RSA key: + (1) sign, encrypt + (2) sign + (3) encrypt +Your selection? 2 +Enter the X.509 subject name: CN=Signing key for yk-9074625,O=example,C=DE +Enter email addresses (end with an empty line): +> otto@@example.net +> +Enter DNS names (optional; end with an empty line): +> +Enter URIs (optional; end with an empty line): +> +Create self-signed certificate? (y/N) +These parameters are used: + Key-Type: card:PIV.9C + Key-Length: 1024 + Key-Usage: sign + Serial: random + Name-DN: CN=Signing key for yk-9074625,O=example,C=DE + Name-Email: otto@@example.net + +Proceed with creation? (y/N) y +Now creating self-signed certificate. This may take a while ... +gpgsm: about to sign the certificate for key: &32A6C6FAFCB8421878608AAB452D5470DD3223ED +gpgsm: certificate created +Ready. +$ gpgsm --import sign.crt +gpgsm: certificate imported +gpgsm: total number processed: 1 +gpgsm: imported: 1 +@end example + +The use of @samp{gpgsm --learn} is currently necessary so that +gpg-agent knows what keys are available on the card. The need for +this command will eventually be removed. The remaining commands are +similar to the creation of an on-disk key. However, here we select +the @samp{Digital signature} key. During the creation process you +will be asked for the Application PIN of the card. The final step is +to write the certificate to the card using @command{gpg-card}: + +@example +gpg/card> writecert PIV.9C < sign.crt +@end example + +By running list again we will see the fully initialized card: + +@example +Reader ...........: 1050:0407:X:0 +Card type ........: yubikey +Card firmware ....: 5.1.2 +Serial number ....: FF020001008A77C1 +Application type .: PIV +Version ..........: 1.0 +Displayed s/n ....: yk-9074625 +PIN usage policy .: app-pin +PIN retry counter : - [verified] - +PIV authentication: 213D1825FDE0F8240CB4E4229F01AF90AC658C2E + keyref .....: PIV.9A (auth) + algorithm ..: nistp384 +Card authenticat. : 7A53E6CFFE7220A0E646B4632EE29E5A7104499C + keyref .....: PIV.9E (auth) + algorithm ..: nistp256 +Digital signature : 32A6C6FAFCB8421878608AAB452D5470DD3223ED + keyref .....: PIV.9C (sign,cert) + algorithm ..: rsa2048 + used for ...: X.509 + user id ..: CN=Signing key for yk-9074625,O=example,C=DE + user id ..: +Key management ...: 34798AAFE0A7565088101CC4AE31C5C8C74461CB + keyref .....: PIV.9D (encr) + algorithm ..: rsa2048 + used for ...: X.509 + user id ..: CN=Encryption key for yk-9074625,O=example,C=DE + user id ..: +@end example + +It is now possible to sign and to encrypt with this card using gpgsm +and to use the @samp{PIV authentication} key with ssh: + +@example +$ ssh-add -l +384 SHA256:0qnJ0Y0ehWxKcx2frLfEljf6GCdlO55OZed5HqGHsaU cardno:yk-9074625 (ECDSA) +@end example + +As usual use ssh-add with the uppercase @samp{-L} to list the public +ssh key. To use the certificates with Thunderbird or Mozilla, please +consult the Scute manual for details. + + + +@c @mansect examples @mansect see also @ifset isman From 05eff1f6623c272fcabd4e238842afc832710324 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 6 Mar 2019 14:09:57 +0100 Subject: [PATCH 214/235] agent: Default to extended key format. * agent/gpg-agent.c (oDisableExtendedKeyFormat, oNoop): New. (oEnableExtendedKeyFormat): Remove. (opts): Make --enable-extended-key-format a dummy option. Add disable-extended-key-format. (parse_rereadable_options): Implement oDisableExtendedKeyFormat. -- Extended key format is supported since vesion 2.1.12 which should have long been replaced by a newer version inh all installations. Thus for 2.3 we will make use of the extended-key-format by default. Signed-off-by: Werner Koch --- agent/gpg-agent.c | 22 +++++++++++++--------- doc/gpg-agent.texi | 20 ++++++++++---------- tools/gpgconf-comp.c | 3 --- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index e1c0e2b0b..27a3f7135 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -112,7 +112,7 @@ enum cmd_and_opt_values oCheckPassphrasePattern, oMaxPassphraseDays, oEnablePassphraseHistory, - oEnableExtendedKeyFormat, + oDisableExtendedKeyFormat, oUseStandardSocket, oNoUseStandardSocket, oExtraSocket, @@ -139,7 +139,9 @@ enum cmd_and_opt_values oAutoExpandSecmem, oListenBacklog, - oWriteEnvFile + oWriteEnvFile, + + oNoop }; @@ -251,7 +253,7 @@ static ARGPARSE_OPTS opts[] = { /* */ "@" #endif ), - ARGPARSE_s_n (oEnableExtendedKeyFormat, "enable-extended-key-format", "@"), + ARGPARSE_s_n (oDisableExtendedKeyFormat, "disable-extended-key-format", "@"), ARGPARSE_s_u (oS2KCount, "s2k-count", "@"), ARGPARSE_s_u (oS2KCalibration, "s2k-calibration", "@"), @@ -265,6 +267,10 @@ static ARGPARSE_OPTS opts[] = { ARGPARSE_s_n (oUseStandardSocket, "use-standard-socket", "@"), ARGPARSE_s_n (oNoUseStandardSocket, "no-use-standard-socket", "@"), + /* Dummy options. */ + ARGPARSE_s_n (oNoop, "enable-extended-key-format", "@"), + + ARGPARSE_end () /* End of list */ }; @@ -825,7 +831,7 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) opt.check_passphrase_pattern = NULL; opt.max_passphrase_days = MAX_PASSPHRASE_DAYS; opt.enable_passphrase_history = 0; - opt.enable_extended_key_format = 0; + opt.enable_extended_key_format = 1; opt.ignore_cache_for_signing = 0; opt.allow_mark_trusted = 1; opt.allow_external_cache = 1; @@ -900,9 +906,7 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) opt.enable_passphrase_history = 1; break; - case oEnableExtendedKeyFormat: - opt.enable_extended_key_format = 1; - break; + case oDisableExtendedKeyFormat: opt.enable_extended_key_format = 0; break; case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break; @@ -936,6 +940,8 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) set_s2k_calibration_time (pargs->r.ret_ulong); break; + case oNoop: break; + default: return 0; /* not handled */ } @@ -1451,8 +1457,6 @@ main (int argc, char **argv ) 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); diff --git a/doc/gpg-agent.texi b/doc/gpg-agent.texi index 3997d2046..770da17b0 100644 --- a/doc/gpg-agent.texi +++ b/doc/gpg-agent.texi @@ -585,16 +585,16 @@ 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. +@item --disable-extended-key-format +@opindex disable-extended-key-format +Since version 2.3 keys are created in the extended private key format. +Changing the passphrase of a key will also convert the key to that new +format. This new key format is supported since GnuPG version 2.1.12 +and thus there should be no need to disable it. However, this options +allows to revert to the old behaviour for new keys; be aware that keys +are never migrated back to the old format. The advantage of the +extended private key format is that it is text based and can carry +additional meta data. @anchor{option --enable-ssh-support} @item --enable-ssh-support diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index 2ae79d91d..83283940c 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -508,9 +508,6 @@ static gc_option_t gc_options_gpg_agent[] = { "enable-putty-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "enable putty support", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, - { "enable-extended-key-format", GC_OPT_FLAG_RUNTIME, GC_LEVEL_INVISIBLE, - NULL, NULL, - GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "Debug", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, From 72feb8fa8280aba674573a1afc955a92e8065242 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 6 Mar 2019 17:46:40 +0100 Subject: [PATCH 215/235] gpgtar: Improve error messages. * tools/gpgtar.h (struct tarinfo_s): New. * tools/gpgtar.c (cmd, skip_crypto, files_from, null_names): Move global vars more to the top. (set_cmd): Rename 'cmd' to 'c'. * tools/gpgtar-list.c (parse_header): Add arg 'info' and improve error messages. (read_header): Add arg 'info' and update counter. (skip_data): Ditto. (gpgtar_list): Pass info object to read functions. (gpgtar_read_header): Add arg 'info'. * tools/gpgtar-extract.c (gpgtar_extract): add arg 'info' and pass on. (extract_regular): Add arg 'info' and update counter. -- This now prints the block number of a header with error. Signed-off-by: Werner Koch --- tools/gpgtar-extract.c | 22 +++++++++++++------ tools/gpgtar-list.c | 49 ++++++++++++++++++++++++++++++------------ tools/gpgtar.c | 35 +++++++++++++++++------------- tools/gpgtar.h | 26 +++++++++++++++------- 4 files changed, 89 insertions(+), 43 deletions(-) diff --git a/tools/gpgtar-extract.c b/tools/gpgtar-extract.c index 8613d193f..3da100c07 100644 --- a/tools/gpgtar-extract.c +++ b/tools/gpgtar-extract.c @@ -36,7 +36,7 @@ static gpg_error_t extract_regular (estream_t stream, const char *dirname, - tar_header_t hdr) + tarinfo_t info, tar_header_t hdr) { gpg_error_t err; char record[RECORDSIZE]; @@ -70,6 +70,7 @@ extract_regular (estream_t stream, const char *dirname, err = read_record (stream, record); if (err) goto leave; + info->nblocks++; n++; if (n < hdr->nrecords || (hdr->size && !(hdr->size % RECORDSIZE))) nbytes = RECORDSIZE; @@ -163,7 +164,8 @@ extract_directory (const char *dirname, tar_header_t hdr) static gpg_error_t -extract (estream_t stream, const char *dirname, tar_header_t hdr) +extract (estream_t stream, const char *dirname, tarinfo_t info, + tar_header_t hdr) { gpg_error_t err; size_t n; @@ -190,7 +192,7 @@ extract (estream_t stream, const char *dirname, tar_header_t hdr) } if (hdr->typeflag == TF_REGULAR || hdr->typeflag == TF_UNKNOWN) - err = extract_regular (stream, dirname, hdr); + err = extract_regular (stream, dirname, info, hdr); else if (hdr->typeflag == TF_DIRECTORY) err = extract_directory (dirname, hdr); else @@ -200,7 +202,11 @@ extract (estream_t stream, const char *dirname, tar_header_t hdr) log_info ("unsupported file type %d for '%s' - skipped\n", (int)hdr->typeflag, hdr->name); for (err = 0, n=0; !err && n < hdr->nrecords; n++) - err = read_record (stream, record); + { + err = read_record (stream, record); + if (!err) + info->nblocks++; + } } return err; } @@ -282,6 +288,10 @@ gpgtar_extract (const char *filename, int decrypt) tar_header_t header = NULL; const char *dirprefix = NULL; char *dirname = NULL; + struct tarinfo_s tarinfo_buffer; + tarinfo_t tarinfo = &tarinfo_buffer; + + memset (&tarinfo_buffer, 0, sizeof tarinfo_buffer); if (filename) { @@ -378,11 +388,11 @@ gpgtar_extract (const char *filename, int decrypt) for (;;) { - err = gpgtar_read_header (stream, &header); + err = gpgtar_read_header (stream, tarinfo, &header); if (err || header == NULL) goto leave; - err = extract (stream, dirname, header); + err = extract (stream, dirname, tarinfo, header); if (err) goto leave; xfree (header); diff --git a/tools/gpgtar-list.c b/tools/gpgtar-list.c index 0e10be8a0..396e837f4 100644 --- a/tools/gpgtar-list.c +++ b/tools/gpgtar-list.c @@ -77,12 +77,15 @@ parse_xoctal (const void *data, size_t length, const char *filename) static tar_header_t -parse_header (const void *record, const char *filename) +parse_header (const void *record, const char *filename, tarinfo_t info) { const struct ustar_raw_header *raw = record; size_t n, namelen, prefixlen; tar_header_t header; int use_prefix; + int anyerror = 0; + + info->headerblock = info->nblocks - 1; use_prefix = (!memcmp (raw->magic, "ustar", 5) && (raw->magic[5] == ' ' || !raw->magic[5])); @@ -91,27 +94,31 @@ parse_header (const void *record, const char *filename) for (namelen=0; namelen < sizeof raw->name && raw->name[namelen]; namelen++) ; if (namelen == sizeof raw->name) - log_info ("%s: warning: name not terminated by a nul byte\n", filename); + { + log_info ("%s: warning: name not terminated by a nul\n", filename); + anyerror = 1; + } for (n=namelen+1; n < sizeof raw->name; n++) if (raw->name[n]) { log_info ("%s: warning: garbage after name\n", filename); + anyerror = 1; break; } - if (use_prefix && raw->prefix[0]) { for (prefixlen=0; (prefixlen < sizeof raw->prefix && raw->prefix[prefixlen]); prefixlen++) ; if (prefixlen == sizeof raw->prefix) - log_info ("%s: warning: prefix not terminated by a nul byte\n", - filename); + log_info ("%s: warning: prefix not terminated by a nul (block %llu)\n", + filename, info->headerblock); for (n=prefixlen+1; n < sizeof raw->prefix; n++) if (raw->prefix[n]) { log_info ("%s: warning: garbage after prefix\n", filename); + anyerror = 1; break; } } @@ -156,25 +163,32 @@ parse_header (const void *record, const char *filename) default: header->typeflag = TF_UNKNOWN; break; } - /* Compute the number of data records following this header. */ if (header->typeflag == TF_REGULAR || header->typeflag == TF_UNKNOWN) header->nrecords = (header->size + RECORDSIZE-1)/RECORDSIZE; else header->nrecords = 0; + if (anyerror) + { + log_info ("%s: header block %llu is corrupt" + " (size=%llu type=%d nrec=%llu)\n", + filename, info->headerblock, + header->size, header->typeflag, header->nrecords); + /* log_printhex (record, RECORDSIZE, " "); */ + } return header; } -/* Read the next block, assming it is a tar header. Returns a header +/* Read the next block, assuming it is a tar header. Returns a header object on success in R_HEADER, or an error. If the stream is consumed, R_HEADER is set to NULL. In case of an error an error message has been printed. */ static gpg_error_t -read_header (estream_t stream, tar_header_t *r_header) +read_header (estream_t stream, tarinfo_t info, tar_header_t *r_header) { gpg_error_t err; char record[RECORDSIZE]; @@ -183,6 +197,7 @@ read_header (estream_t stream, tar_header_t *r_header) err = read_record (stream, record); if (err) return err; + info->nblocks++; for (i=0; i < RECORDSIZE && !record[i]; i++) ; @@ -193,6 +208,7 @@ read_header (estream_t stream, tar_header_t *r_header) err = read_record (stream, record); if (err) return err; + info->nblocks++; for (i=0; i < RECORDSIZE && !record[i]; i++) ; @@ -207,7 +223,7 @@ read_header (estream_t stream, tar_header_t *r_header) } } - *r_header = parse_header (record, es_fname_get (stream)); + *r_header = parse_header (record, es_fname_get (stream), info); return *r_header ? 0 : gpg_error_from_syserror (); } @@ -215,7 +231,7 @@ read_header (estream_t stream, tar_header_t *r_header) /* Skip the data records according to HEADER. Prints an error message on error and return -1. */ static int -skip_data (estream_t stream, tar_header_t header) +skip_data (estream_t stream, tarinfo_t info, tar_header_t header) { char record[RECORDSIZE]; unsigned long long n; @@ -224,6 +240,7 @@ skip_data (estream_t stream, tar_header_t header) { if (read_record (stream, record)) return -1; + info->nblocks++; } return 0; @@ -278,6 +295,10 @@ gpgtar_list (const char *filename, int decrypt) estream_t stream; estream_t cipher_stream = NULL; tar_header_t header = NULL; + struct tarinfo_s tarinfo_buffer; + tarinfo_t tarinfo = &tarinfo_buffer; + + memset (&tarinfo_buffer, 0, sizeof tarinfo_buffer); if (filename) { @@ -339,13 +360,13 @@ gpgtar_list (const char *filename, int decrypt) for (;;) { - err = read_header (stream, &header); + err = read_header (stream, tarinfo, &header); if (err || header == NULL) goto leave; print_header (header, es_stdout); - if (skip_data (stream, header)) + if (skip_data (stream, tarinfo, header)) goto leave; xfree (header); header = NULL; @@ -362,9 +383,9 @@ gpgtar_list (const char *filename, int decrypt) } gpg_error_t -gpgtar_read_header (estream_t stream, tar_header_t *r_header) +gpgtar_read_header (estream_t stream, tarinfo_t info, tar_header_t *r_header) { - return read_header (stream, r_header); + return read_header (stream, info, r_header); } void diff --git a/tools/gpgtar.c b/tools/gpgtar.c index 2757ab011..77001dc91 100644 --- a/tools/gpgtar.c +++ b/tools/gpgtar.c @@ -136,6 +136,14 @@ static ARGPARSE_OPTS tar_opts[] = { }; +/* Global flags. */ +enum cmd_and_opt_values cmd = 0; +int skip_crypto = 0; +const char *files_from = NULL; +int null_names = 0; + + + /* Print usage information and provide strings for help. */ static const char * @@ -169,23 +177,25 @@ my_strusage( int level ) static void set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd) { - enum cmd_and_opt_values cmd = *ret_cmd; + enum cmd_and_opt_values c = *ret_cmd; - if (!cmd || cmd == new_cmd) - cmd = new_cmd; - else if (cmd == aSign && new_cmd == aEncrypt) - cmd = aSignEncrypt; - else if (cmd == aEncrypt && new_cmd == aSign) - cmd = aSignEncrypt; + if (!c || c == new_cmd) + c = new_cmd; + else if (c == aSign && new_cmd == aEncrypt) + c = aSignEncrypt; + else if (c == aEncrypt && new_cmd == aSign) + c = aSignEncrypt; else { log_error (_("conflicting commands\n")); exit (2); } - *ret_cmd = cmd; + *ret_cmd = c; } + + /* Shell-like argument splitting. For compatibility with gpg-zip we accept arguments for GnuPG and @@ -287,14 +297,9 @@ shell_parse_argv (const char *s, int *r_argc, char ***r_argv) gpgrt_annotate_leaked_object (*r_argv); return 0; } + + -/* Global flags. */ -enum cmd_and_opt_values cmd = 0; -int skip_crypto = 0; -const char *files_from = NULL; -int null_names = 0; - - /* Command line parsing. */ static void parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) diff --git a/tools/gpgtar.h b/tools/gpgtar.h index 28d3d88b1..1a1b913d7 100644 --- a/tools/gpgtar.h +++ b/tools/gpgtar.h @@ -41,12 +41,21 @@ struct } opt; +/* An info structure to avoid global variables. */ +struct tarinfo_s +{ + unsigned long long nblocks; /* Count of processed blocks. */ + unsigned long long headerblock; /* Number of current header block. */ +}; +typedef struct tarinfo_s *tarinfo_t; + + /* The size of a tar record. All IO is done in chunks of this size. Note that we don't care about blocking because this version of tar is not expected to be used directly on a tape drive in fact it is used in a pipeline with GPG and thus any blocking would be useless. */ -#define RECORDSIZE 512 +#define RECORDSIZE 512 /* Description of the USTAR header format. */ @@ -64,16 +73,16 @@ struct ustar_raw_header char magic[6]; char version[2]; char uname[32]; - char gname[32]; - char devmajor[8]; + char gname[32]; + char devmajor[8]; char devminor[8]; - char prefix[155]; + char prefix[155]; char pad[12]; }; /* Filetypes as defined by USTAR. */ -typedef enum +typedef enum { TF_REGULAR, TF_HARDLINK, @@ -93,7 +102,7 @@ struct tar_header_s; typedef struct tar_header_s *tar_header_t; struct tar_header_s { - tar_header_t next; /* Used to build a linked list iof entries. */ + tar_header_t next; /* Used to build a linked list of entries. */ unsigned long mode; /* The file mode. */ unsigned long nlink; /* Number of hard links. */ @@ -106,7 +115,7 @@ struct tar_header_s that 32 bit and thus allows tracking times beyond 2106. */ typeflag_t typeflag; /* The type of the file. */ - + unsigned long long nrecords; /* Number of data records. */ @@ -126,7 +135,8 @@ gpg_error_t gpgtar_extract (const char *filename, int decrypt); /*-- gpgtar-list.c --*/ gpg_error_t gpgtar_list (const char *filename, int decrypt); -gpg_error_t gpgtar_read_header (estream_t stream, tar_header_t *r_header); +gpg_error_t gpgtar_read_header (estream_t stream, tarinfo_t info, + tar_header_t *r_header); void gpgtar_print_header (tar_header_t header, estream_t out); From 91ae3e7fb66271691f6fe507262a62fc7e2663a3 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 6 Mar 2019 17:58:39 +0100 Subject: [PATCH 216/235] agent: Re-introduce --enable-extended-key-format. * agent/gpg-agent.c (oEnableExtendedKeyFormat): Re-introduce. (parse_rereadable_options): Handle it in a special way. * agent/protect.c (agent_protect): Be safe and set use_ocb only to 1 or 0. * tools/gpgconf-comp.c: Add --enable-extended-key-format again. -- This is required for backward compatible with profiles. Signed-off-by: Werner Koch --- agent/agent.h | 6 +++++- agent/gpg-agent.c | 11 +++++++++-- agent/protect.c | 2 +- doc/gpg-agent.texi | 15 +++++++++------ tools/gpgconf-comp.c | 3 +++ 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/agent/agent.h b/agent/agent.h index 3a29dc817..0f804cd8b 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -124,7 +124,11 @@ struct passphrase change. */ int enable_passphrase_history; - /* If set the extended key format is used for new keys. */ + /* If set the extended key format is used for new keys. Note that + * this may vave the value 2 in which case + * --disable-extended-key-format won't have any effect and thus + * effectivley locking it. This is required to support existing + * profiles which lock the use of --enable-extended-key-format. */ int enable_extended_key_format; int running_detached; /* We are running detached from the tty. */ diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 27a3f7135..d9e2bbf25 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -113,6 +113,7 @@ enum cmd_and_opt_values oMaxPassphraseDays, oEnablePassphraseHistory, oDisableExtendedKeyFormat, + oEnableExtendedKeyFormat, oUseStandardSocket, oNoUseStandardSocket, oExtraSocket, @@ -254,6 +255,7 @@ static ARGPARSE_OPTS opts[] = { #endif ), ARGPARSE_s_n (oDisableExtendedKeyFormat, "disable-extended-key-format", "@"), + ARGPARSE_s_n (oEnableExtendedKeyFormat, "enable-extended-key-format", "@"), ARGPARSE_s_u (oS2KCount, "s2k-count", "@"), ARGPARSE_s_u (oS2KCalibration, "s2k-calibration", "@"), @@ -268,7 +270,6 @@ static ARGPARSE_OPTS opts[] = { ARGPARSE_s_n (oNoUseStandardSocket, "no-use-standard-socket", "@"), /* Dummy options. */ - ARGPARSE_s_n (oNoop, "enable-extended-key-format", "@"), ARGPARSE_end () /* End of list */ @@ -906,7 +907,13 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) opt.enable_passphrase_history = 1; break; - case oDisableExtendedKeyFormat: opt.enable_extended_key_format = 0; break; + case oEnableExtendedKeyFormat: + opt.enable_extended_key_format = 2; + break; + case oDisableExtendedKeyFormat: + if (opt.enable_extended_key_format != 2) + opt.enable_extended_key_format = 0; + break; case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break; diff --git a/agent/protect.c b/agent/protect.c index eda247f27..61fb8f45d 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -623,7 +623,7 @@ agent_protect (const unsigned char *plainkey, const char *passphrase, int have_curve = 0; if (use_ocb == -1) - use_ocb = opt.enable_extended_key_format; + use_ocb = !!opt.enable_extended_key_format; /* Create an S-expression with the protected-at timestamp. */ memcpy (timestamp_exp, "(12:protected-at15:", 19); diff --git a/doc/gpg-agent.texi b/doc/gpg-agent.texi index 770da17b0..d518c246b 100644 --- a/doc/gpg-agent.texi +++ b/doc/gpg-agent.texi @@ -585,16 +585,19 @@ 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. -@item --disable-extended-key-format +@item --enable-extended-key-format +@itemx --disable-extended-key-format +@opindex enable-extended-key-format @opindex disable-extended-key-format Since version 2.3 keys are created in the extended private key format. Changing the passphrase of a key will also convert the key to that new format. This new key format is supported since GnuPG version 2.1.12 -and thus there should be no need to disable it. However, this options -allows to revert to the old behaviour for new keys; be aware that keys -are never migrated back to the old format. The advantage of the -extended private key format is that it is text based and can carry -additional meta data. +and thus there should be no need to disable it. The disable option +allows to revert to the old behavior for new keys; be aware that keys +are never migrated back to the old format. However if the enable +option has been used the disable option won't have an effect. The +advantage of the extended private key format is that it is text based +and can carry additional meta data. @anchor{option --enable-ssh-support} @item --enable-ssh-support diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index 83283940c..2ae79d91d 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -508,6 +508,9 @@ static gc_option_t gc_options_gpg_agent[] = { "enable-putty-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "enable putty support", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, + { "enable-extended-key-format", GC_OPT_FLAG_RUNTIME, GC_LEVEL_INVISIBLE, + NULL, NULL, + GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "Debug", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, From b3a7a5140784b5a015107b5c5c73b15ae44e71dc Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 6 Mar 2019 20:08:26 +0100 Subject: [PATCH 217/235] gpgtar: Make option -C work for archive creation. * tools/gpgtar-create.c (gpgtar_create): Switch to the -C directory. -- The -C option is pretty useful given that pattern are always relative to the current directory. In contrast to GNU tar, the switching is done only once. Signed-off-by: Werner Koch --- doc/tools.texi | 9 +++++---- tools/gpgtar-create.c | 8 ++++++++ tools/gpgtar.c | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/doc/tools.texi b/doc/tools.texi index 6256c05ed..119f698d6 100644 --- a/doc/tools.texi +++ b/doc/tools.texi @@ -2014,10 +2014,11 @@ Do not actually output the extracted files. @item --directory @var{dir} @itemx -C @var{dir} @opindex directory -Extract the files into the directory @var{dir}. The -default is to take the directory name from -the input filename. If no input filename is known a directory named -@file{GPGARCH} is used. +Extract the files into the directory @var{dir}. The default is to +take the directory name from the input filename. If no input filename +is known a directory named @file{GPGARCH} is used. For tarball +creation, switch to directory @var{dir} before performing any +operations. @item --files-from @var{file} @itemx -T @var{file} diff --git a/tools/gpgtar-create.c b/tools/gpgtar-create.c index c622a6672..a08601634 100644 --- a/tools/gpgtar-create.c +++ b/tools/gpgtar-create.c @@ -762,6 +762,14 @@ gpgtar_create (char **inpattern, int encrypt, int sign) memset (scanctrl, 0, sizeof *scanctrl); scanctrl->flist_tail = &scanctrl->flist; + if (opt.directory && gnupg_chdir (opt.directory)) + { + err = gpg_error_from_syserror (); + log_error ("chdir to '%s' failed: %s\n", + opt.directory, gpg_strerror (err)); + return err; + } + while (!eof_seen) { char *pat, *p; diff --git a/tools/gpgtar.c b/tools/gpgtar.c index 77001dc91..b33aa6d0f 100644 --- a/tools/gpgtar.c +++ b/tools/gpgtar.c @@ -112,7 +112,7 @@ static ARGPARSE_OPTS opts[] = { ARGPARSE_group (302, N_("@\nTar options:\n ")), ARGPARSE_s_s (oDirectory, "directory", - N_("|DIRECTORY|extract files into DIRECTORY")), + N_("|DIRECTORY|change to DIRECTORY first")), ARGPARSE_s_s (oFilesFrom, "files-from", N_("|FILE|get names to create from FILE")), ARGPARSE_s_n (oNull, "null", N_("-T reads null-terminated names")), From 096c2aa705f85289ff8b610da1dd9181e4c904fd Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sun, 3 Mar 2019 10:22:34 -0500 Subject: [PATCH 218/235] gpgv: Improve documentation for keyring choices * doc/gpgv.texi: Improve documentation for keyring choices -- From the existing documentation, it's not clear whether the default keyring will always be mixed into the set of keyrings, or whether it will be skipped if a --keyring is present. The updated text here attempts to describe the keyring selection logic more completely. Signed-off-by: Daniel Kahn Gillmor --- doc/gpgv.texi | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/doc/gpgv.texi b/doc/gpgv.texi index a05286171..2dd9576b6 100644 --- a/doc/gpgv.texi +++ b/doc/gpgv.texi @@ -59,13 +59,14 @@ no configuration files and only a few options are implemented. That does also mean that it does not check for expired or revoked keys. -By default a keyring named @file{trustedkeys.kbx} is used; if that -does not exist a keyring named @file{trustedkeys.gpg} is used. The -default keyring is assumed to be in the home directory of GnuPG, -either the default home directory or the one set by an option or an -environment variable. The option @code{--keyring} may be used to -specify a different keyring or even multiple keyrings. - +If no @code{--keyring} option is given, @code{gpgv} looks for a +``default'' keyring named @file{trustedkeys.kbx} (preferred) or +@file{trustedkeys.gpg} in the home directory of GnuPG, either the +default home directory or the one set by the @code{--homedir} option +or the @code{GNUPGHOME} environment variable. If any @code{--keyring} +option is used, @code{gpgv} will not look for the default keyring. The +@code{--keyring} option may be used multiple times and all specified +keyrings will be used together. @noindent @mansect options From 134c3c16523b1a267ebdd2df6339240fd9e1e3b3 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 7 Mar 2019 11:34:03 +0100 Subject: [PATCH 219/235] dirmngr: Add CSRF protection exception for protonmail. * dirmngr/http.c (same_host_p): Add exception table. -- Please: Adding entries to this table shall be an exception and not the rule. Signed-off-by: Werner Koch --- dirmngr/http.c | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/dirmngr/http.c b/dirmngr/http.c index eb7d99a1e..d6856fe05 100644 --- a/dirmngr/http.c +++ b/dirmngr/http.c @@ -3520,16 +3520,51 @@ uri_query_lookup (parsed_uri_t uri, const char *key) } -/* Return true if both URI point to the same host. */ +/* Return true if both URI point to the same host for the purpose of + * redirection check. A is the original host and B the host given in + * the Location header. As a temporary workaround a fixed list of + * exceptions is also consulted. */ static int same_host_p (parsed_uri_t a, parsed_uri_t b) { - return a->host && b->host && !ascii_strcasecmp (a->host, b->host); + static struct + { + const char *from; /* NULL uses the last entry from the table. */ + const char *to; + } allow[] = + { + { "protonmail.com", "api.protonmail.com" }, + { NULL, "api.protonmail.ch" }, + { "protonmail.ch", "api.protonmail.com" }, + { NULL, "api.protonmail.ch" } + }; + int i; + const char *from; + + if (!a->host || !b->host) + return 0; + + if (!ascii_strcasecmp (a->host, b->host)) + return 1; + + from = NULL; + for (i=0; i < DIM (allow); i++) + { + if (allow[i].from) + from = allow[i].from; + if (!from) + continue; + if (!ascii_strcasecmp (from, a->host) + && !ascii_strcasecmp (allow[i].to, b->host)) + return 1; + } + + return 0; } /* Prepare a new URL for a HTTP redirect. INFO has flags controlling - * the operaion, STATUS_CODE is used for diagnostics, LOCATION is the + * the operation, STATUS_CODE is used for diagnostics, LOCATION is the * value of the "Location" header, and R_URL reveives the new URL on * success or NULL or error. Note that INFO->ORIG_URL is * required. */ @@ -3600,8 +3635,8 @@ http_prepare_redirect (http_redir_info_t *info, unsigned int status_code, } else if (same_host_p (origuri, locuri)) { - /* The host is the same and thus we can take the location - * verbatim. */ + /* The host is the same or on an exception list and thus we can + * take the location verbatim. */ http_release_parsed_uri (origuri); http_release_parsed_uri (locuri); newurl = xtrystrdup (location); From b7de105e0a836bd4d7bd558f8e699d88ab0cafec Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 7 Mar 2019 14:11:46 +0100 Subject: [PATCH 220/235] common: Minor rework of tty_get. * common/ttyio.c (do_get): Re-indent and remove the checking for char values larger than 0xa0. Use explicy control character checking. -- The code is really old (mid 1998) and with the checking for 0xa0 it has an implicit assumption of utf-8 or latin-1. Worse, the check was for c > 0xa0 and not c == 0xa0 so it never worked as intended. Signed-off-by: Werner Koch --- common/ttyio.c | 258 +++++++++++++++++++++++++++---------------------- 1 file changed, 141 insertions(+), 117 deletions(-) diff --git a/common/ttyio.c b/common/ttyio.c index 374b9f38a..4c095bc03 100644 --- a/common/ttyio.c +++ b/common/ttyio.c @@ -404,163 +404,187 @@ tty_print_utf8_string( const byte *p, size_t n ) static char * do_get( const char *prompt, int hidden ) { - char *buf; + char *buf; #ifndef __riscos__ - byte cbuf[1]; + byte cbuf[1]; #endif - int c, n, i; + int c, n, i; - if( batchmode ) { - log_error("Sorry, we are in batchmode - can't get input\n"); - exit(2); + if (batchmode) + { + log_error ("Sorry, we are in batchmode - can't get input\n"); + exit (2); } - if (no_terminal) { - log_error("Sorry, no terminal at all requested - can't get input\n"); - exit(2); + if (no_terminal) + { + log_error ("Sorry, no terminal at all requested - can't get input\n"); + exit (2); } - if( !initialized ) - init_ttyfp(); + if (!initialized) + init_ttyfp (); - last_prompt_len = 0; - tty_printf( "%s", prompt ); - buf = xmalloc((n=50)); - i = 0; + last_prompt_len = 0; + tty_printf ("%s", prompt); + buf = xmalloc ((n=50)); + i = 0; #ifdef USE_W32_CONSOLE - if( hidden ) - SetConsoleMode(con.in, HID_INPMODE ); + if (hidden) + SetConsoleMode(con.in, HID_INPMODE ); - for(;;) { - DWORD nread; + for (;;) + { + DWORD nread; - if( !ReadConsoleA( con.in, cbuf, 1, &nread, NULL ) ) - log_fatal("ReadConsole failed: rc=%d", (int)GetLastError() ); - if( !nread ) - continue; - if( *cbuf == '\n' ) - break; + if (!ReadConsoleA( con.in, cbuf, 1, &nread, NULL)) + log_fatal ("ReadConsole failed: rc=%d", (int)GetLastError ()); + if (!nread) + continue; + if (*cbuf == '\n') + break; - if( !hidden ) - last_prompt_len++; - c = *cbuf; - if( c == '\t' ) - c = ' '; - else if( c > 0xa0 ) - ; /* we don't allow 0xa0, as this is a protected blank which may - * confuse the user */ - else if( iscntrl(c) ) - continue; - if( !(i < n-1) ) { - n += 50; - buf = xrealloc (buf, n); - } - buf[i++] = c; + if (!hidden) + last_prompt_len++; + c = *cbuf; + if (c == '\t') + c = ' '; + else if ( (c >= 0 && c <= 0x1f) || c == 0x7f) + continue; + if (!(i < n-1)) + { + n += 50; + buf = xrealloc (buf, n); + } + buf[i++] = c; } - if( hidden ) - SetConsoleMode(con.in, DEF_INPMODE ); + if (hidden) + SetConsoleMode(con.in, DEF_INPMODE ); #elif defined(__riscos__) || defined(HAVE_W32CE_SYSTEM) - do { + do + { #ifdef HAVE_W32CE_SYSTEM /* Using getchar is not a correct solution but for now it doesn't matter because we have no real console at all. We should rework this as soon as we have switched this entire module to estream. */ - c = getchar(); + c = getchar(); #else - c = riscos_getchar(); + c = riscos_getchar(); #endif - if (c == 0xa || c == 0xd) { /* Return || Enter */ - c = (int) '\n'; - } else if (c == 0x8 || c == 0x7f) { /* Backspace || Delete */ - if (i>0) { - i--; - if (!hidden) { - last_prompt_len--; - fputc(8, ttyfp); - fputc(32, ttyfp); - fputc(8, ttyfp); - fflush(ttyfp); + if (c == 0xa || c == 0xd) /* Return || Enter */ + { + c = (int) '\n'; + } + else if (c == 0x8 || c == 0x7f) /* Backspace || Delete */ + { + if (i>0) + { + i--; + if (!hidden) + { + last_prompt_len--; + fputc(8, ttyfp); + fputc(32, ttyfp); + fputc(8, ttyfp); + fflush(ttyfp); } - } else { - fputc(7, ttyfp); - fflush(ttyfp); } - continue; - } else if (c == (int) '\t') { /* Tab */ - c = ' '; - } else if (c > 0xa0) { - ; /* we don't allow 0xa0, as this is a protected blank which may - * confuse the user */ - } else if (iscntrl(c)) { - continue; + else + { + fputc(7, ttyfp); + fflush(ttyfp); + } + continue; } - if(!(i < n-1)) { - n += 50; - buf = xrealloc (buf, n); + else if (c == (int) '\t') /* Tab */ + { + c = ' '; } - buf[i++] = c; - if (!hidden) { - last_prompt_len++; - fputc(c, ttyfp); - fflush(ttyfp); + else if (c > 0xa0) + { + ; /* we don't allow 0xa0, as this is a protected blank which may + * confuse the user */ } - } while (c != '\n'); - i = (i>0) ? i-1 : 0; -#else /* Other systems. */ - if( hidden ) { -#ifdef HAVE_TCGETATTR - struct termios term; + else if (iscntrl(c)) + { + continue; + } + if (!(i < n-1)) + { + n += 50; + buf = xrealloc (buf, n); + } + buf[i++] = c; + if (!hidden) + { + last_prompt_len++; + fputc(c, ttyfp); + fflush(ttyfp); + } + } + while (c != '\n'); + i = (i>0) ? i-1 : 0; - if( tcgetattr(fileno(ttyfp), &termsave) ) - log_fatal("tcgetattr() failed: %s\n", strerror(errno) ); - restore_termios = 1; - term = termsave; - term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); - if( tcsetattr( fileno(ttyfp), TCSAFLUSH, &term ) ) - log_fatal("tcsetattr() failed: %s\n", strerror(errno) ); +#else /* Other systems. */ + + if (hidden) + { +#ifdef HAVE_TCGETATTR + struct termios term; + + if (tcgetattr(fileno(ttyfp), &termsave)) + log_fatal("tcgetattr() failed: %s\n", strerror(errno)); + restore_termios = 1; + term = termsave; + term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); + if (tcsetattr( fileno(ttyfp), TCSAFLUSH, &term ) ) + log_fatal("tcsetattr() failed: %s\n", strerror(errno)); #endif } - /* fixme: How can we avoid that the \n is echoed w/o disabling - * canonical mode - w/o this kill_prompt can't work */ - while( read(fileno(ttyfp), cbuf, 1) == 1 && *cbuf != '\n' ) { - if( !hidden ) - last_prompt_len++; - c = *cbuf; - if( c == CONTROL_D ) - log_info("control d found\n"); - if( c == '\t' ) - c = ' '; - else if( c > 0xa0 ) - ; /* we don't allow 0xa0, as this is a protected blank which may - * confuse the user */ - else if( iscntrl(c) ) - continue; - if( !(i < n-1) ) { - n += 50; - buf = xrealloc (buf, n ); - } - buf[i++] = c; + /* fixme: How can we avoid that the \n is echoed w/o disabling + * canonical mode - w/o this kill_prompt can't work */ + while (read(fileno(ttyfp), cbuf, 1) == 1 && *cbuf != '\n') + { + if (!hidden) + last_prompt_len++; + c = *cbuf; + if (c == CONTROL_D) + log_info ("Control-D detected\n"); + + if (c == '\t') /* Map tab to a space. */ + c = ' '; + else if ( (c >= 0 && c <= 0x1f) || c == 0x7f) + continue; /* Skip all other ASCII control characters. */ + if (!(i < n-1)) + { + n += 50; + buf = xrealloc (buf, n); + } + buf[i++] = c; } - if( *cbuf != '\n' ) { - buf[0] = CONTROL_D; - i = 1; + if (*cbuf != '\n') + { + buf[0] = CONTROL_D; + i = 1; } - if( hidden ) { + if (hidden) + { #ifdef HAVE_TCGETATTR - if( tcsetattr(fileno(ttyfp), TCSAFLUSH, &termsave) ) - log_error("tcsetattr() failed: %s\n", strerror(errno) ); - restore_termios = 0; + if (tcsetattr(fileno(ttyfp), TCSAFLUSH, &termsave)) + log_error ("tcsetattr() failed: %s\n", strerror(errno)); + restore_termios = 0; #endif } #endif /* end unix version */ - buf[i] = 0; - return buf; + + buf[i] = 0; + return buf; } From 9f37e93dd741a5436ff412955628806ae84725ca Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 11 Mar 2019 18:15:05 +0100 Subject: [PATCH 221/235] dirmngr: Avoid testing for Tor with --gpgconf-list. * dirmngr/dirmngr.c (post_option_parsing): Do not call set_tor_mode. (dirmngr_sighup_action): Call it here. (main): Call it here unless in --gpgconf-list mode. Signed-off-by: Werner Koch --- dirmngr/dirmngr.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index 5b9e7a86e..3a99a2e2e 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -763,7 +763,6 @@ post_option_parsing (void) opt.connect_quick_timeout = opt.connect_timeout; set_debug (); - set_tor_mode (); } @@ -1090,7 +1089,12 @@ main (int argc, char **argv) log_printf ("\n"); } + /* Note that we do not run set_tor_mode in --gpgconf-list mode + * because it will attempt to connect to the tor client and that can + * be time consuming. */ post_option_parsing (); + if (cmd != aGPGConfTest && cmd != aGPGConfList) + set_tor_mode (); /* Get LDAP server list from file. */ #if USE_LDAP @@ -1864,6 +1868,7 @@ dirmngr_sighup_action (void) log_info (_("SIGHUP received - " "re-reading configuration and flushing caches\n")); reread_configuration (); + set_tor_mode (); cert_cache_deinit (0); crl_cache_deinit (); cert_cache_init (hkp_cacert_filenames); From af3efd149f555d36a455cb2ea311ff81caf5124c Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 13 Mar 2019 09:12:14 +0900 Subject: [PATCH 222/235] g10: Fix symmetric cipher algo constant for ECDH. * g10/ecdh.c (kek_params_table): Use CIPHER_ALGO_AES192 for ECC strength 384, according to RFC-6637. -- Reported-by: Trevor Bentley Signed-off-by: NIIBE Yutaka --- g10/ecdh.c | 2 +- scd/app-openpgp.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/g10/ecdh.c b/g10/ecdh.c index 6587cc4b4..5bbea96c0 100644 --- a/g10/ecdh.c +++ b/g10/ecdh.c @@ -39,7 +39,7 @@ static const struct /* Note: Must be sorted by ascending values for QBITS. */ { { 256, DIGEST_ALGO_SHA256, CIPHER_ALGO_AES }, - { 384, DIGEST_ALGO_SHA384, CIPHER_ALGO_AES256 }, + { 384, DIGEST_ALGO_SHA384, CIPHER_ALGO_AES192 }, /* Note: 528 is 521 rounded to the 8 bit boundary */ { 528, DIGEST_ALGO_SHA512, CIPHER_ALGO_AES256 } diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index 8b6286a7d..62b3cbc2f 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -1448,8 +1448,8 @@ ecdh_params (const char *curve) /* See RFC-6637 for those constants. 0x03: Number of bytes 0x01: Version for this parameter format - KDF algo - KEK algo + KDF hash algo + KEK symmetric cipher algo */ if (nbits <= 256) return (const unsigned char*)"\x03\x01\x08\x07"; From f199b627ce512c8495af5c9bd1c81127ccde3ca0 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 14 Mar 2019 08:23:38 +0900 Subject: [PATCH 223/235] Fix the previous commit. * g10/ecdh.c (kek_params_table): Revert the change. * scd/app-openpgp.c (ecdh_params): Use CIPHER_ALGO_AES256 for 384-bit key. -- Avoiding CIPHER_ALGO_AES192 is intentional here. Signed-off-by: NIIBE Yutaka --- g10/ecdh.c | 2 +- scd/app-openpgp.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/g10/ecdh.c b/g10/ecdh.c index 5bbea96c0..6587cc4b4 100644 --- a/g10/ecdh.c +++ b/g10/ecdh.c @@ -39,7 +39,7 @@ static const struct /* Note: Must be sorted by ascending values for QBITS. */ { { 256, DIGEST_ALGO_SHA256, CIPHER_ALGO_AES }, - { 384, DIGEST_ALGO_SHA384, CIPHER_ALGO_AES192 }, + { 384, DIGEST_ALGO_SHA384, CIPHER_ALGO_AES256 }, /* Note: 528 is 521 rounded to the 8 bit boundary */ { 528, DIGEST_ALGO_SHA512, CIPHER_ALGO_AES256 } diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index 62b3cbc2f..1e904b578 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -1448,13 +1448,13 @@ ecdh_params (const char *curve) /* See RFC-6637 for those constants. 0x03: Number of bytes 0x01: Version for this parameter format - KDF hash algo - KEK symmetric cipher algo + KEK digest algorithm + KEK cipher algorithm */ if (nbits <= 256) return (const unsigned char*)"\x03\x01\x08\x07"; else if (nbits <= 384) - return (const unsigned char*)"\x03\x01\x09\x08"; + return (const unsigned char*)"\x03\x01\x09\x09"; else return (const unsigned char*)"\x03\x01\x0a\x09"; } From a21ca3a1eff4722dea778cca4abe14a873ccebdf Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 12 Mar 2019 11:09:52 +0100 Subject: [PATCH 224/235] gpg: Implemented latest rfc4880bis version 5 packet hashing. * configure.ac (AC_CHECK_SIZEOF): Test size_t. * g10/sig-check.c (check_signature_end_simple): Support v5 signatures as per current rfc4880bis. For correctness also allow for N > 2^32. * g10/sign.c (pt_extra_hash_data_t): New. (hash_sigversion_to_magic): New arg EXTRAHASH. (write_plaintext_packet): New arg R_EXTRAHASH. (write_signature_packets): Pass EXTRAHASH. (sign_file): Ditto. (sign_symencrypt_file): Ditto. -- Take care: The code path for v5 sigs has not yet been tested. Signed-off-by: Werner Koch --- configure.ac | 1 + g10/sig-check.c | 50 ++++++++++++++------- g10/sign.c | 113 ++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 131 insertions(+), 33 deletions(-) diff --git a/configure.ac b/configure.ac index 89ef9398e..41dedf72b 100644 --- a/configure.ac +++ b/configure.ac @@ -1367,6 +1367,7 @@ AC_CHECK_SIZEOF(unsigned short) AC_CHECK_SIZEOF(unsigned int) AC_CHECK_SIZEOF(unsigned long) AC_CHECK_SIZEOF(unsigned long long) +AC_CHECK_SIZEOF(size_t) AC_HEADER_TIME AC_CHECK_SIZEOF(time_t,,[[ #include diff --git a/g10/sig-check.c b/g10/sig-check.c index d02c68e33..e8782f90d 100644 --- a/g10/sig-check.c +++ b/g10/sig-check.c @@ -513,6 +513,7 @@ check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig, byte buf[10]; int i; size_t n; + gcry_md_putc (digest, sig->pubkey_algo); gcry_md_putc (digest, sig->digest_algo); if (sig->hashed) @@ -531,22 +532,39 @@ check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig, gcry_md_putc (digest, 0); n = 6; } - /* add some magic per Section 5.2.4 of RFC 4880. */ - i = 0; - buf[i++] = sig->version; - buf[i++] = 0xff; - if (sig->version >= 5) - { - buf[i++] = 0; - buf[i++] = 0; - buf[i++] = 0; - buf[i++] = 0; - } - buf[i++] = n >> 24; - buf[i++] = n >> 16; - buf[i++] = n >> 8; - buf[i++] = n; - gcry_md_write (digest, buf, i); + /* Hash data from the literal data packet. */ + if (sig->version >= 5 + && (sig->sig_class == 0x00 || sig->sig_class == 0x01)) + { + /* - One octet content format + * - File name (one octet length followed by the name) + * - Four octet timestamp */ + memset (buf, 0, 6); + gcry_md_write (digest, buf, 6); + } + /* Add some magic per Section 5.2.4 of RFC 4880. */ + i = 0; + buf[i++] = sig->version; + buf[i++] = 0xff; + if (sig->version >= 5) + { +#if SIZEOF_SIZE_T > 4 + buf[i++] = n >> 56; + buf[i++] = n >> 48; + buf[i++] = n >> 40; + buf[i++] = n >> 32; +#else + buf[i++] = 0; + buf[i++] = 0; + buf[i++] = 0; + buf[i++] = 0; +#endif + } + buf[i++] = n >> 24; + buf[i++] = n >> 16; + buf[i++] = n >> 8; + buf[i++] = n; + gcry_md_write (digest, buf, i); } gcry_md_final( digest ); diff --git a/g10/sign.c b/g10/sign.c index b2d1c1826..67556d8ba 100644 --- a/g10/sign.c +++ b/g10/sign.c @@ -49,7 +49,19 @@ #define LF "\n" #endif -static int recipient_digest_algo=0; +/* A type for the extra data we hash into v5 signature packets. */ +struct pt_extra_hash_data_s +{ + unsigned char mode; + u32 timestamp; + unsigned char namelen; + char name[1]; +}; +typedef struct pt_extra_hash_data_s *pt_extra_hash_data_t; + + +/* Hack */ +static int recipient_digest_algo; /* @@ -216,10 +228,13 @@ hash_uid (gcry_md_hd_t md, int sigversion, const PKT_user_id *uid) /* - * Helper to hash some parts from the signature + * Helper to hash some parts from the signature. EXTRAHASH gives the + * extra data to be hashed into v5 signatures; it may by NULL for + * detached signatures. */ static void -hash_sigversion_to_magic (gcry_md_hd_t md, const PKT_signature *sig) +hash_sigversion_to_magic (gcry_md_hd_t md, const PKT_signature *sig, + pt_extra_hash_data_t extrahash) { byte buf[10]; int i; @@ -243,12 +258,39 @@ hash_sigversion_to_magic (gcry_md_hd_t md, const PKT_signature *sig) gcry_md_putc (md, 0); n = 6; } + /* Hash data from the literal data packet. */ + if (sig->version >= 5 && (sig->sig_class == 0x00 || sig->sig_class == 0x01)) + { + /* - One octet content format + * - File name (one octet length followed by the name) + * - Four octet timestamp */ + if (extrahash) + { + buf[0] = extrahash->mode; + buf[1] = extrahash->namelen; + gcry_md_write (md, buf, 2); + if (extrahash->namelen) + gcry_md_write (md, extrahash->name, extrahash->namelen); + buf[0] = extrahash->timestamp >> 24; + buf[1] = extrahash->timestamp >> 16; + buf[2] = extrahash->timestamp >> 8; + buf[3] = extrahash->timestamp; + gcry_md_write (md, buf, 4); + } + else /* Detached signatures */ + { + memset (buf, 0, 6); + gcry_md_write (md, buf, 6); + } + } /* Add some magic. */ i = 0; buf[i++] = sig->version; buf[i++] = 0xff; if (sig->version >= 5) { + /* Note: We don't hashed any data larger than 2^32 and thus we + * can always use 0 here. See also note below. */ buf[i++] = 0; buf[i++] = 0; buf[i++] = 0; @@ -633,10 +675,14 @@ write_onepass_sig_packets (SK_LIST sk_list, IOBUF out, int sigclass ) /* - * Helper to write the plaintext (literal data) packet + * Helper to write the plaintext (literal data) packet. At + * R_EXTRAHASH a malloced object with the with the extra data hashed + * into v5 signatures is stored. */ static int -write_plaintext_packet (IOBUF out, IOBUF inp, const char *fname, int ptmode) +write_plaintext_packet (iobuf_t out, iobuf_t inp, + const char *fname, int ptmode, + pt_extra_hash_data_t *r_extrahash) { PKT_plaintext *pt = NULL; u32 filesize; @@ -691,6 +737,19 @@ write_plaintext_packet (IOBUF out, IOBUF inp, const char *fname, int ptmode) if ((rc = build_packet (out, &pkt))) log_error ("build_packet(PLAINTEXT) failed: %s\n", gpg_strerror (rc) ); + + *r_extrahash = xtrymalloc (sizeof **r_extrahash + pt->namelen); + if (!*r_extrahash) + rc = gpg_error_from_syserror (); + else + { + (*r_extrahash)->mode = pt->mode; + (*r_extrahash)->timestamp = pt->timestamp; + (*r_extrahash)->namelen = pt->namelen; + /* Note that the last byte or NAME won't be initialized + * becuase we don't need it. */ + memcpy ((*r_extrahash)->name, pt->name, pt->namelen); + } pt->buf = NULL; free_packet (&pkt, NULL); } @@ -699,6 +758,18 @@ write_plaintext_packet (IOBUF out, IOBUF inp, const char *fname, int ptmode) byte copy_buffer[4096]; int bytes_copied; + *r_extrahash = xtrymalloc (sizeof **r_extrahash); + if (!*r_extrahash) + { + rc = gpg_error_from_syserror (); + goto leave; + } + /* FIXME: We need to parse INP to get the to be hashed data from + * it. */ + (*r_extrahash)->mode = 0; + (*r_extrahash)->timestamp = 0; + (*r_extrahash)->namelen = 0; + while ((bytes_copied = iobuf_read (inp, copy_buffer, 4096)) != -1) if ((rc = iobuf_write (out, copy_buffer, bytes_copied))) { @@ -708,19 +779,21 @@ write_plaintext_packet (IOBUF out, IOBUF inp, const char *fname, int ptmode) } wipememory (copy_buffer, 4096); /* burn buffer */ } - /* fixme: it seems that we never freed pt/pkt */ + leave: return rc; } /* - * Write the signatures from the SK_LIST to OUT. HASH must be a non-finalized - * hash which will not be changes here. + * Write the signatures from the SK_LIST to OUT. HASH must be a + * non-finalized hash which will not be changes here. EXTRAHASH is + * either NULL or the extra data tro be hashed into v5 signatures. */ static int write_signature_packets (ctrl_t ctrl, SK_LIST sk_list, IOBUF out, gcry_md_hd_t hash, + pt_extra_hash_data_t extrahash, int sigclass, u32 timestamp, u32 duration, int status_letter, const char *cache_nonce) { @@ -744,7 +817,7 @@ write_signature_packets (ctrl_t ctrl, if (pk->version >= 5) sig->version = 5; /* Required for v5 keys. */ else - sig->version = 4; /*Required. */ + sig->version = 4; /* Required. */ keyid_from_pk (pk, sig->keyid); sig->digest_algo = hash_for (pk); @@ -762,7 +835,7 @@ write_signature_packets (ctrl_t ctrl, build_sig_subpkt_from_sig (sig, pk); mk_notation_policy_etc (sig, NULL, pk); - hash_sigversion_to_magic (md, sig); + hash_sigversion_to_magic (md, sig, extrahash); gcry_md_final (md); rc = do_sign (ctrl, pk, sig, md, hash_for (pk), cache_nonce); @@ -826,6 +899,7 @@ sign_file (ctrl_t ctrl, strlist_t filenames, int detached, strlist_t locusr, SK_LIST sk_rover = NULL; int multifile = 0; u32 duration=0; + pt_extra_hash_data_t extrahash = NULL; pfx = new_progress_context (); afx = new_armor_context (); @@ -1125,7 +1199,8 @@ sign_file (ctrl_t ctrl, strlist_t filenames, int detached, strlist_t locusr, { rc = write_plaintext_packet (out, inp, fname, (opt.textmode && !outfile) ? - (opt.mimemode? 'm' : 't') : 'b'); + (opt.mimemode? 'm' : 't') : 'b', + &extrahash); } /* Catch errors from above. */ @@ -1133,7 +1208,7 @@ sign_file (ctrl_t ctrl, strlist_t filenames, int detached, strlist_t locusr, goto leave; /* Write the signatures. */ - rc = write_signature_packets (ctrl, sk_list, out, mfx.md, + rc = write_signature_packets (ctrl, sk_list, out, mfx.md, extrahash, opt.textmode && !outfile? 0x01 : 0x00, 0, duration, detached ? 'D':'S', NULL); if (rc) @@ -1156,6 +1231,7 @@ sign_file (ctrl_t ctrl, strlist_t filenames, int detached, strlist_t locusr, recipient_digest_algo = 0; release_progress_context (pfx); release_armor_context (afx); + xfree (extrahash); return rc; } @@ -1286,7 +1362,7 @@ clearsign_file (ctrl_t ctrl, push_armor_filter (afx, out); /* Write the signatures. */ - rc = write_signature_packets (ctrl, sk_list, out, textmd, 0x01, 0, + rc = write_signature_packets (ctrl, sk_list, out, textmd, NULL, 0x01, 0, duration, 'C', NULL); if (rc) goto leave; @@ -1328,6 +1404,7 @@ sign_symencrypt_file (ctrl_t ctrl, const char *fname, strlist_t locusr) int algo; u32 duration = 0; int canceled; + pt_extra_hash_data_t extrahash = NULL; pfx = new_progress_context (); afx = new_armor_context (); @@ -1452,13 +1529,14 @@ sign_symencrypt_file (ctrl_t ctrl, const char *fname, strlist_t locusr) /* Pipe data through all filters; i.e. write the signed stuff. */ /* (current filters: zip - encrypt - armor) */ rc = write_plaintext_packet (out, inp, fname, - opt.textmode ? (opt.mimemode?'m':'t'):'b'); + opt.textmode ? (opt.mimemode?'m':'t'):'b', + &extrahash); if (rc) goto leave; /* Write the signatures. */ /* (current filters: zip - encrypt - armor) */ - rc = write_signature_packets (ctrl, sk_list, out, mfx.md, + rc = write_signature_packets (ctrl, sk_list, out, mfx.md, extrahash, opt.textmode? 0x01 : 0x00, 0, duration, 'S', NULL); if (rc) @@ -1480,6 +1558,7 @@ sign_symencrypt_file (ctrl_t ctrl, const char *fname, strlist_t locusr) xfree (s2k); release_progress_context (pfx); release_armor_context (afx); + xfree (extrahash); return rc; } @@ -1599,7 +1678,7 @@ make_keysig_packet (ctrl_t ctrl, if (!rc) { - hash_sigversion_to_magic (md, sig); + hash_sigversion_to_magic (md, sig, NULL); gcry_md_final (md); rc = complete_sig (ctrl, sig, pksk, md, cache_nonce); } @@ -1699,7 +1778,7 @@ update_keysig_packet (ctrl_t ctrl, if (!rc) { - hash_sigversion_to_magic (md, sig); + hash_sigversion_to_magic (md, sig, NULL); gcry_md_final (md); rc = complete_sig (ctrl, sig, pksk, md, NULL); } From f40e9d6a528521d12795e1a6cc15c849b216be92 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 14 Mar 2019 08:54:59 +0100 Subject: [PATCH 225/235] kbx: Add support for 32 byte fingerprints. * common/userids.c (classify_user_id): Support 32 byte fingerprints. * kbx/keybox-search-desc.h (KEYDB_SEARCH_MODE_FPR32): New. (struct keydb_search_desc): Add field fprlen. * kbx/keybox-defs.h (struct _keybox_openpgp_key_info): Add field version and increase size of fpr to 32. * kbx/keybox-blob.c: Define new version 2 for PGP and X509 blobs. (struct keyboxblob_key): Add field fprlen and increase size of fpr. (pgp_create_key_part_single): Allow larger fingerprints. (create_blob_header): Implement blob version 2 and add arg want_fpr32. (_keybox_create_openpgp_blob): Detect the need for blob version 2. * kbx/keybox-search.c (blob_get_first_keyid): Support 32 byte fingerprints. (blob_cmp_fpr): Ditto. (blob_cmp_fpr_part): Ditto. (has_fingerprint): Add arg fprlen and pass on. (keybox_search): Support KEYDB_SEARCH_MODE_FPR32 and adjust for changed has_fingerprint. * kbx/keybox-openpgp.c (parse_key): Support version 5 keys. * kbx/keybox-dump.c (_keybox_dump_blob): Support blob version 2. * g10/delkey.c (do_delete_key): Support KEYDB_SEARCH_MODE_FPR32. * g10/export.c (exact_subkey_match_p): Ditto. * g10/gpg.c (main): Ditto. * g10/getkey.c (get_pubkey_byfprint): Adjust for changed KEYDB_SEARCH_MODE_FPR. * g10/keydb.c (keydb_search_desc_dump): Support KEYDB_SEARCH_MODE_FPR32 and adjust for changed KEYDB_SEARCH_MODE_FPR. (keydb_search): Add new arg fprlen and change all callers. * g10/keyedit.c (find_by_primary_fpr): Ditto. * g10/keyid.c (keystr_from_desc): Ditto. * g10/keyring.c (keyring_search): Ditto. * g10/keyserver.c (print_keyrec): Ditto. (parse_keyrec): Ditto. (keyserver_export): Ditto. (keyserver_retrieval_screener): Ditto. (keyserver_import): Ditto. (keyserver_import_fprint): Ditto. (keyidlist): Ditto. (keyserver_get_chunk): Ditto. * g10/keydb.c (keydb_search): Add new arg fprlen and change all callers. * sm/keydb.c (keydb_search_fpr): Adjust for changed KEYDB_SEARCH_MODE_FPR. -- This prepares the support for OpenPGP v5 keys. The new version 2 blob format is needed for the longer fingerprints and we also use this opportunity to prepare for storing the keygrip in the blob for faster lookup by keygrip. Right now this is not yet functional. Signed-off-by: Werner Koch --- common/userids.c | 45 +++++++++++++--- g10/delkey.c | 3 +- g10/export.c | 16 ++++-- g10/getkey.c | 11 ++-- g10/gpg.c | 1 + g10/import.c | 4 +- g10/keydb.c | 46 ++++++++++++----- g10/keydb.h | 6 +-- g10/keyedit.c | 16 ++++-- g10/keyid.c | 26 ++++++++++ g10/keyring.c | 22 +++++--- g10/keyserver.c | 68 +++++++++++++++++++----- kbx/keybox-blob.c | 109 +++++++++++++++++++++++++++++---------- kbx/keybox-defs.h | 5 +- kbx/keybox-dump.c | 30 +++++++---- kbx/keybox-openpgp.c | 85 +++++++++++++++++++++++------- kbx/keybox-search-desc.h | 6 ++- kbx/keybox-search.c | 70 ++++++++++++++++++++----- sm/keydb.c | 1 + 19 files changed, 440 insertions(+), 130 deletions(-) diff --git a/common/userids.c b/common/userids.c index 01f2cd84b..41cf2876c 100644 --- a/common/userids.c +++ b/common/userids.c @@ -226,14 +226,15 @@ classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack) goto out; } } - if (i != 32 && i != 40) + if (i != 32 && i != 40 && i != 64) { rc = gpg_error (GPG_ERR_INV_USER_ID); /* Invalid length of fpr. */ goto out; } for (i=0,si=s; si < se; i++, si +=2) desc->u.fpr[i] = hextobyte(si); - for (; i < 20; i++) + desc->fprlen = i; + for (; i < 32; i++) desc->u.fpr[i]= 0; mode = KEYDB_SEARCH_MODE_FPR; } @@ -326,6 +327,8 @@ classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack) } desc->u.fpr[i] = c; } + for (; i < 32; i++) + desc->u.fpr[i]= 0; mode = KEYDB_SEARCH_MODE_FPR16; } else if ((hexlength == 40 @@ -333,7 +336,7 @@ classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack) || (s[hexlength] == '!' && s[hexlength + 1] == 0))) || (!hexprefix && hexlength == 41 && *s == '0')) { - /* SHA1/RMD160 fingerprint. */ + /* SHA1 fingerprint. */ int i; if (hexlength == 41) s++; @@ -347,8 +350,31 @@ classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack) } desc->u.fpr[i] = c; } + for (; i < 32; i++) + desc->u.fpr[i]= 0; mode = KEYDB_SEARCH_MODE_FPR20; } + else if ((hexlength == 64 + && (s[hexlength] == 0 + || (s[hexlength] == '!' && s[hexlength + 1] == 0))) + || (!hexprefix && hexlength == 65 && *s == '0')) + { + /* SHA256 fingerprint. */ + int i; + if (hexlength == 65) + s++; + for (i=0; i < 32; i++, s+=2) + { + int c = hextobyte(s); + if (c == -1) + { + rc = gpg_error (GPG_ERR_INV_USER_ID); + goto out; + } + desc->u.fpr[i] = c; + } + mode = KEYDB_SEARCH_MODE_FPR32; + } else if (!hexprefix) { /* The fingerprint in an X.509 listing is often delimited by @@ -368,14 +394,17 @@ classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack) } if (i == 20) mode = KEYDB_SEARCH_MODE_FPR20; + for (; i < 32; i++) + desc->u.fpr[i]= 0; } if (!mode) { /* Still not found. Now check for a space separated - OpenPGP v4 fingerprint like: - 8061 5870 F5BA D690 3336 86D0 F2AD 85AC 1E42 B367 - or - 8061 5870 F5BA D690 3336 86D0 F2AD 85AC 1E42 B367 + * OpenPGP v4 fingerprint like: + * 8061 5870 F5BA D690 3336 86D0 F2AD 85AC 1E42 B367 + * or + * 8061 5870 F5BA D690 3336 86D0 F2AD 85AC 1E42 B367 + * FIXME: Support OpenPGP v5 fingerprint */ hexlength = strspn (s, " 0123456789abcdefABCDEF"); if (s[hexlength] && s[hexlength] != ' ') @@ -410,6 +439,8 @@ classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack) } if (i == 20) mode = KEYDB_SEARCH_MODE_FPR20; + for (; i < 32; i++) + desc->u.fpr[i]= 0; } } if (!mode) /* Default to substring search. */ diff --git a/g10/delkey.c b/g10/delkey.c index bf8c4e93b..b4d643f59 100644 --- a/g10/delkey.c +++ b/g10/delkey.c @@ -71,7 +71,8 @@ do_delete_key (ctrl_t ctrl, const char *username, int secret, int force, err = classify_user_id (username, &desc, 1); exactmatch = (desc.mode == KEYDB_SEARCH_MODE_FPR || desc.mode == KEYDB_SEARCH_MODE_FPR16 - || desc.mode == KEYDB_SEARCH_MODE_FPR20); + || desc.mode == KEYDB_SEARCH_MODE_FPR20 + || desc.mode == KEYDB_SEARCH_MODE_FPR32); if (!err) err = keydb_search (hd, &desc, 1, NULL); if (err) diff --git a/g10/export.c b/g10/export.c index d53be99fe..2d34c8244 100644 --- a/g10/export.c +++ b/g10/export.c @@ -453,8 +453,9 @@ exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, KBNODE node) case KEYDB_SEARCH_MODE_FPR16: case KEYDB_SEARCH_MODE_FPR20: + case KEYDB_SEARCH_MODE_FPR32: case KEYDB_SEARCH_MODE_FPR: - fingerprint_from_pk (node->pkt->pkt.public_key, fpr,&fprlen); + fingerprint_from_pk (node->pkt->pkt.public_key, fpr, &fprlen); break; default: @@ -474,13 +475,22 @@ exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, KBNODE node) break; case KEYDB_SEARCH_MODE_FPR16: - if (!memcmp (desc->u.fpr, fpr, 16)) + if (fprlen == 16 && !memcmp (desc->u.fpr, fpr, 16)) result = 1; break; case KEYDB_SEARCH_MODE_FPR20: + if (fprlen == 20 && !memcmp (desc->u.fpr, fpr, 20)) + result = 1; + break; + + case KEYDB_SEARCH_MODE_FPR32: + if (fprlen == 32 && !memcmp (desc->u.fpr, fpr, 32)) + result = 1; + break; + case KEYDB_SEARCH_MODE_FPR: - if (!memcmp (desc->u.fpr, fpr, 20)) + if (fprlen == desc->fprlen && !memcmp (desc->u.fpr, fpr, desc->fprlen)) result = 1; break; diff --git a/g10/getkey.c b/g10/getkey.c index 039a5edb5..1467bc89f 100644 --- a/g10/getkey.c +++ b/g10/getkey.c @@ -899,6 +899,7 @@ key_byname (ctrl_t ctrl, GETKEY_CTX *retctx, strlist_t namelist, && ctx->items[n].mode != KEYDB_SEARCH_MODE_LONG_KID && ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR16 && ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR20 + && ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR32 && ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR) { ctx->items[n].skipfnc = skip_unusable; @@ -1654,7 +1655,7 @@ get_pubkey_byfprint (ctrl_t ctrl, PKT_public_key *pk, kbnode_t *r_keyblock, if (r_keyblock) *r_keyblock = NULL; - if (fprint_len == 20 || fprint_len == 16) + if (fprint_len == 32 || fprint_len == 20 || fprint_len == 16) { struct getkey_ctx_s ctx; KBNODE kb = NULL; @@ -1670,9 +1671,9 @@ get_pubkey_byfprint (ctrl_t ctrl, PKT_public_key *pk, kbnode_t *r_keyblock, return gpg_error_from_syserror (); ctx.nitems = 1; - ctx.items[0].mode = fprint_len == 16 ? KEYDB_SEARCH_MODE_FPR16 - : KEYDB_SEARCH_MODE_FPR20; + ctx.items[0].mode = KEYDB_SEARCH_MODE_FPR; memcpy (ctx.items[0].u.fpr, fprint, fprint_len); + ctx.items[0].fprlen = fprint_len; if (pk) ctx.req_usage = pk->req_usage; rc = lookup (ctrl, &ctx, 0, &kb, &found_key); @@ -1745,8 +1746,6 @@ get_keyblock_byfprint_fast (kbnode_t *r_keyblock, KEYDB_HANDLE *r_hd, for (i = 0; i < MAX_FINGERPRINT_LEN && i < fprint_len; i++) fprbuf[i] = fprint[i]; - while (i < MAX_FINGERPRINT_LEN) - fprbuf[i++] = 0; hd = keydb_new (); if (!hd) @@ -1770,7 +1769,7 @@ get_keyblock_byfprint_fast (kbnode_t *r_keyblock, KEYDB_HANDLE *r_hd, if (r_hd) *r_hd = hd; - err = keydb_search_fpr (hd, fprbuf); + err = keydb_search_fpr (hd, fprbuf, fprint_len); if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) { if (!r_hd) diff --git a/g10/gpg.c b/g10/gpg.c index ddf8c86eb..b15c8eaa3 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -5077,6 +5077,7 @@ main (int argc, char **argv) || desc.mode == KEYDB_SEARCH_MODE_LONG_KID || desc.mode == KEYDB_SEARCH_MODE_FPR16 || desc.mode == KEYDB_SEARCH_MODE_FPR20 + || desc.mode == KEYDB_SEARCH_MODE_FPR32 || desc.mode == KEYDB_SEARCH_MODE_FPR || desc.mode == KEYDB_SEARCH_MODE_KEYGRIP)) { diff --git a/g10/import.c b/g10/import.c index 64cc4b093..25ccc2fc6 100644 --- a/g10/import.c +++ b/g10/import.c @@ -2957,9 +2957,7 @@ import_revoke_cert (ctrl_t ctrl, kbnode_t node, unsigned int options, size_t an; fingerprint_from_pk (pk, afp, &an); - while (an < MAX_FINGERPRINT_LEN) - afp[an++] = 0; - rc = keydb_search_fpr (hd, afp); + rc = keydb_search_fpr (hd, afp, an); } if (rc) { diff --git a/g10/keydb.c b/g10/keydb.c index 03fadfd54..6ecb4eb8b 100644 --- a/g10/keydb.c +++ b/g10/keydb.c @@ -81,6 +81,7 @@ enum keyblock_cache_states { struct keyblock_cache { enum keyblock_cache_states state; byte fpr[MAX_FINGERPRINT_LEN]; + byte fprlen; iobuf_t iobuf; /* Image of the keyblock. */ int pk_no; int uid_no; @@ -566,8 +567,12 @@ keydb_search_desc_dump (struct keydb_search_desc *desc) bin2hex (desc->u.fpr, 20, fpr); return xasprintf ("FPR20: '%s'", format_hexfingerprint (fpr, b, sizeof (b))); - case KEYDB_SEARCH_MODE_FPR: + case KEYDB_SEARCH_MODE_FPR32: bin2hex (desc->u.fpr, 20, fpr); + return xasprintf ("FPR32: '%s'", + format_hexfingerprint (fpr, b, sizeof (b))); + case KEYDB_SEARCH_MODE_FPR: + bin2hex (desc->u.fpr, desc->fprlen, fpr); return xasprintf ("FPR: '%s'", format_hexfingerprint (fpr, b, sizeof (b))); case KEYDB_SEARCH_MODE_ISSUER: @@ -1531,6 +1536,8 @@ keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb) fingerprint_from_pk (pk, desc.u.fpr, &len); if (len == 20) desc.mode = KEYDB_SEARCH_MODE_FPR20; + else if (len == 32) + desc.mode = KEYDB_SEARCH_MODE_FPR32; else log_bug ("%s: Unsupported key length: %zu\n", __func__, len); @@ -1862,6 +1869,7 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, int was_reset = hd->is_reset; /* If an entry is already in the cache, then don't add it again. */ int already_in_cache = 0; + int fprlen; if (descindex) *descindex = 0; /* Make sure it is always set on return. */ @@ -1902,12 +1910,21 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, /* NB: If one of the exact search modes below is used in a loop to walk over all keys (with the same fingerprint) the caching must have been disabled for the handle. */ + if (desc[0].mode == KEYDB_SEARCH_MODE_FPR20) + fprlen = 20; + else if (desc[0].mode == KEYDB_SEARCH_MODE_FPR32) + fprlen = 32; + else if (desc[0].mode == KEYDB_SEARCH_MODE_FPR) + fprlen = desc[0].fprlen; + else + fprlen = 0; + if (!hd->no_caching && ndesc == 1 - && (desc[0].mode == KEYDB_SEARCH_MODE_FPR20 - || desc[0].mode == KEYDB_SEARCH_MODE_FPR) - && hd->keyblock_cache.state == KEYBLOCK_CACHE_FILLED - && !memcmp (hd->keyblock_cache.fpr, desc[0].u.fpr, 20) + && fprlen + && hd->keyblock_cache.state == KEYBLOCK_CACHE_FILLED + && hd->keyblock_cache.fprlen == fprlen + && !memcmp (hd->keyblock_cache.fpr, desc[0].u.fpr, fprlen) /* Make sure the current file position occurs before the cached result to avoid an infinite loop. */ && (hd->current < hd->keyblock_cache.resource @@ -1922,8 +1939,7 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, hd->current = hd->keyblock_cache.resource; /* HD->KEYBLOCK_CACHE.OFFSET is the last byte in the record. Seek just beyond that. */ - keybox_seek (hd->active[hd->current].u.kb, - hd->keyblock_cache.offset + 1); + keybox_seek (hd->active[hd->current].u.kb, hd->keyblock_cache.offset + 1); keydb_stats.found_cached++; return 0; } @@ -1986,8 +2002,8 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, keyblock_cache_clear (hd); if (!hd->no_caching && !rc - && ndesc == 1 && (desc[0].mode == KEYDB_SEARCH_MODE_FPR20 - || desc[0].mode == KEYDB_SEARCH_MODE_FPR) + && ndesc == 1 + && fprlen && hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX) { hd->keyblock_cache.state = KEYBLOCK_CACHE_PREPARED; @@ -1997,11 +2013,14 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, within the record. */ hd->keyblock_cache.offset = keybox_offset (hd->active[hd->current].u.kb) - 1; - memcpy (hd->keyblock_cache.fpr, desc[0].u.fpr, 20); + memcpy (hd->keyblock_cache.fpr, desc[0].u.fpr, fprlen); + hd->keyblock_cache.fprlen = fprlen; } if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND - && ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID && was_reset + && ndesc == 1 + && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID + && was_reset && !already_in_cache) kid_not_found_insert (desc[0].u.kid); @@ -2078,12 +2097,13 @@ keydb_search_kid (KEYDB_HANDLE hd, u32 *kid) * off. If you want to search the whole database, then you need to * first call keydb_search_reset(). */ gpg_error_t -keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr) +keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr, size_t fprlen) { KEYDB_SEARCH_DESC desc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_FPR; - memcpy (desc.u.fpr, fpr, MAX_FINGERPRINT_LEN); + memcpy (desc.u.fpr, fpr, fprlen); + desc.fprlen = fprlen; return keydb_search (hd, &desc, 1, NULL); } diff --git a/g10/keydb.h b/g10/keydb.h index acb424455..c52856d7f 100644 --- a/g10/keydb.h +++ b/g10/keydb.h @@ -244,9 +244,9 @@ gpg_error_t keydb_search_next (KEYDB_HANDLE hd); key id. */ gpg_error_t keydb_search_kid (KEYDB_HANDLE hd, u32 *kid); -/* This is a convenience function for searching for keys with a long - (20 byte) fingerprint. */ -gpg_error_t keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr); +/* This is a convenience function for searching for keys by + * fingerprint. */ +gpg_error_t keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr, size_t fprlen); /*-- pkclist.c --*/ diff --git a/g10/keyedit.c b/g10/keyedit.c index 63a54fa21..ed1fd8a23 100644 --- a/g10/keyedit.c +++ b/g10/keyedit.c @@ -2566,7 +2566,8 @@ find_by_primary_fpr (ctrl_t ctrl, const char *fpr, if (classify_user_id (fpr, &desc, 1) || !(desc.mode == KEYDB_SEARCH_MODE_FPR || desc.mode == KEYDB_SEARCH_MODE_FPR16 - || desc.mode == KEYDB_SEARCH_MODE_FPR20)) + || desc.mode == KEYDB_SEARCH_MODE_FPR20 + || desc.mode == KEYDB_SEARCH_MODE_FPR32)) { log_error (_("\"%s\" is not a fingerprint\n"), fpr); err = gpg_error (GPG_ERR_INV_NAME); @@ -2591,10 +2592,16 @@ find_by_primary_fpr (ctrl_t ctrl, const char *fpr, && !desc.u.fpr[18] && !desc.u.fpr[19]) ; - else if (fprlen == 20 && (desc.mode == KEYDB_SEARCH_MODE_FPR20 - || desc.mode == KEYDB_SEARCH_MODE_FPR) + else if (fprlen == 20 && desc.mode == KEYDB_SEARCH_MODE_FPR20 && !memcmp (fprbin, desc.u.fpr, 20)) ; + else if (fprlen == 32 && desc.mode == KEYDB_SEARCH_MODE_FPR32 + && !memcmp (fprbin, desc.u.fpr, 32)) + ; + else if (desc.mode == KEYDB_SEARCH_MODE_FPR + && fprlen == desc.fprlen + && !memcmp (fprbin, desc.u.fpr, fprlen)) + ; else { log_error (_("\"%s\" is not the primary fingerprint\n"), fpr); @@ -2918,7 +2925,8 @@ keyedit_quick_set_expire (ctrl_t ctrl, const char *fpr, const char *expirestr, /* Parse the fingerprint. */ if (classify_user_id (subkeyfprs[idx], &desc, 1) || !(desc.mode == KEYDB_SEARCH_MODE_FPR - || desc.mode == KEYDB_SEARCH_MODE_FPR20)) + || desc.mode == KEYDB_SEARCH_MODE_FPR20 + || desc.mode == KEYDB_SEARCH_MODE_FPR32)) { log_error (_("\"%s\" is not a proper fingerprint\n"), subkeyfprs[idx] ); diff --git a/g10/keyid.c b/g10/keyid.c index e099c7d97..9558a2617 100644 --- a/g10/keyid.c +++ b/g10/keyid.c @@ -481,6 +481,32 @@ keystr_from_desc(KEYDB_SEARCH_DESC *desc) return keystr(keyid); } + case KEYDB_SEARCH_MODE_FPR32: + { + u32 keyid[2]; + + keyid[0] = buf32_to_u32 (desc->u.fpr); + keyid[1] = buf32_to_u32 (desc->u.fpr+4); + return keystr(keyid); + } + + case KEYDB_SEARCH_MODE_FPR: + { + u32 keyid[2]; + + if (desc->fprlen == 32) + { + keyid[0] = buf32_to_u32 (desc->u.fpr); + keyid[1] = buf32_to_u32 (desc->u.fpr+4); + } + else + { + keyid[0] = buf32_to_u32 (desc->u.fpr+12); + keyid[1] = buf32_to_u32 (desc->u.fpr+16); + } + return keystr(keyid); + } + case KEYDB_SEARCH_MODE_FPR16: return "?v3 fpr?"; diff --git a/g10/keyring.c b/g10/keyring.c index 25ef50747..bc1d06c09 100644 --- a/g10/keyring.c +++ b/g10/keyring.c @@ -997,6 +997,7 @@ keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc, break; case KEYDB_SEARCH_MODE_FPR16: case KEYDB_SEARCH_MODE_FPR20: + case KEYDB_SEARCH_MODE_FPR32: case KEYDB_SEARCH_MODE_FPR: need_fpr = 1; break; @@ -1134,11 +1135,12 @@ keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc, pk = pkt.pkt.public_key; ++pk_no; - if (need_fpr) { - fingerprint_from_pk (pk, afp, &an); - while (an < 20) /* fill up to 20 bytes */ - afp[an++] = 0; - } + if (need_fpr) + { + fingerprint_from_pk (pk, afp, &an); + while (an < 32) /* fill up to 32 bytes */ + afp[an++] = 0; + } if (need_keyid) keyid_from_pk (pk, aki); @@ -1185,10 +1187,18 @@ keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc, goto found; break; case KEYDB_SEARCH_MODE_FPR20: - case KEYDB_SEARCH_MODE_FPR: if (pk && !memcmp (desc[n].u.fpr, afp, 20)) goto found; break; + case KEYDB_SEARCH_MODE_FPR32: + if (pk && !memcmp (desc[n].u.fpr, afp, 32)) + goto found; + break; + case KEYDB_SEARCH_MODE_FPR: + if (pk && desc[n].fprlen >= 16 && desc[n].fprlen <= 32 + && !memcmp (desc[n].u.fpr, afp, desc[n].fprlen)) + goto found; + break; case KEYDB_SEARCH_MODE_FIRST: if (pk) goto found; diff --git a/g10/keyserver.c b/g10/keyserver.c index 1ba94ed49..c414e2cb1 100644 --- a/g10/keyserver.c +++ b/g10/keyserver.c @@ -527,6 +527,25 @@ print_keyrec (ctrl_t ctrl, int number,struct keyrec *keyrec) } break; + /* If we get a modern fingerprint, we have the most + flexibility. */ + case KEYDB_SEARCH_MODE_FPR32: + { + u32 kid[2]; + keyid_from_fingerprint (ctrl, keyrec->desc.u.fpr, 32, kid); + es_printf("key %s",keystr(kid)); + } + break; + + case KEYDB_SEARCH_MODE_FPR: + { + u32 kid[2]; + keyid_from_fingerprint (ctrl, keyrec->desc.u.fpr, keyrec->desc.fprlen, + kid); + es_printf("key %s",keystr(kid)); + } + break; + default: BUG(); break; @@ -614,7 +633,9 @@ parse_keyrec(char *keystring) if (err || (work->desc.mode != KEYDB_SEARCH_MODE_SHORT_KID && work->desc.mode != KEYDB_SEARCH_MODE_LONG_KID && work->desc.mode != KEYDB_SEARCH_MODE_FPR16 - && work->desc.mode != KEYDB_SEARCH_MODE_FPR20)) + && work->desc.mode != KEYDB_SEARCH_MODE_FPR20 + && work->desc.mode != KEYDB_SEARCH_MODE_FPR32 + && work->desc.mode != KEYDB_SEARCH_MODE_FPR)) { work->desc.mode=KEYDB_SEARCH_MODE_NONE; return ret; @@ -996,7 +1017,9 @@ keyserver_export (ctrl_t ctrl, strlist_t users) if (err || (desc.mode != KEYDB_SEARCH_MODE_SHORT_KID && desc.mode != KEYDB_SEARCH_MODE_LONG_KID && desc.mode != KEYDB_SEARCH_MODE_FPR16 - && desc.mode != KEYDB_SEARCH_MODE_FPR20)) + && desc.mode != KEYDB_SEARCH_MODE_FPR20 + && desc.mode != KEYDB_SEARCH_MODE_FPR32 + && desc.mode != KEYDB_SEARCH_MODE_FPR)) { log_error(_("\"%s\" not a key ID: skipping\n"),users->d); continue; @@ -1070,6 +1093,16 @@ keyserver_retrieval_screener (kbnode_t keyblock, void *opaque) if (fpr_len == 20 && !memcmp (fpr, desc[n].u.fpr, 20)) return 0; } + else if (desc[n].mode == KEYDB_SEARCH_MODE_FPR32) + { + if (fpr_len == 32 && !memcmp (fpr, desc[n].u.fpr, 32)) + return 0; + } + else if (desc[n].mode == KEYDB_SEARCH_MODE_FPR) + { + if (fpr_len == desc[n].fprlen && !memcmp (fpr, desc[n].u.fpr, 32)) + return 0; + } else if (desc[n].mode == KEYDB_SEARCH_MODE_FPR16) { if (fpr_len == 16 && !memcmp (fpr, desc[n].u.fpr, 16)) @@ -1111,7 +1144,9 @@ keyserver_import (ctrl_t ctrl, strlist_t users) if (err || (desc[count].mode != KEYDB_SEARCH_MODE_SHORT_KID && desc[count].mode != KEYDB_SEARCH_MODE_LONG_KID && desc[count].mode != KEYDB_SEARCH_MODE_FPR16 - && desc[count].mode != KEYDB_SEARCH_MODE_FPR20)) + && desc[count].mode != KEYDB_SEARCH_MODE_FPR20 + && desc[count].mode != KEYDB_SEARCH_MODE_FPR32 + && desc[count].mode != KEYDB_SEARCH_MODE_FPR)) { log_error (_("\"%s\" not a key ID: skipping\n"), users->d); continue; @@ -1171,10 +1206,13 @@ keyserver_import_fprint (ctrl_t ctrl, const byte *fprint,size_t fprint_len, desc.mode=KEYDB_SEARCH_MODE_FPR16; else if(fprint_len==20) desc.mode=KEYDB_SEARCH_MODE_FPR20; + else if(fprint_len==32) + desc.mode=KEYDB_SEARCH_MODE_FPR32; else return -1; memcpy(desc.u.fpr,fprint,fprint_len); + desc.fprlen = fprint_len; /* TODO: Warn here if the fingerprint we got doesn't match the one we asked for? */ @@ -1291,20 +1329,23 @@ keyidlist (ctrl_t ctrl, strlist_t users, KEYDB_SEARCH_DESC **klist, This is because it's easy to calculate any sort of keyid from a v4 fingerprint, but not a v3 fingerprint. */ - if(node->pkt->pkt.public_key->version<4) + if (node->pkt->pkt.public_key->version < 4) { (*klist)[*count].mode=KEYDB_SEARCH_MODE_LONG_KID; keyid_from_pk(node->pkt->pkt.public_key, (*klist)[*count].u.kid); } else - { + { size_t dummy; - (*klist)[*count].mode=KEYDB_SEARCH_MODE_FPR20; - fingerprint_from_pk(node->pkt->pkt.public_key, - (*klist)[*count].u.fpr,&dummy); - } + if (node->pkt->pkt.public_key->version == 4) + (*klist)[*count].mode = KEYDB_SEARCH_MODE_FPR20; + else + (*klist)[*count].mode = KEYDB_SEARCH_MODE_FPR32; + fingerprint_from_pk (node->pkt->pkt.public_key, + (*klist)[*count].u.fpr,&dummy); + } /* This is a little hackish, using the skipfncvalue as a void* pointer to the keyserver spec, but we don't need @@ -1621,9 +1662,10 @@ keyserver_get_chunk (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, int ndesc, int quiet = 0; if (desc[idx].mode == KEYDB_SEARCH_MODE_FPR20 + || desc[idx].mode == KEYDB_SEARCH_MODE_FPR32 || desc[idx].mode == KEYDB_SEARCH_MODE_FPR16) { - n = 1+2+2*20; + n = 1+2+2*32; if (idx && linelen + n > MAX_KS_GET_LINELEN) break; /* Declare end of this chunk. */ linelen += n; @@ -1635,10 +1677,12 @@ keyserver_get_chunk (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, int ndesc, { strcpy (pattern[npat], "0x"); bin2hex (desc[idx].u.fpr, + desc[idx].mode == KEYDB_SEARCH_MODE_FPR32? 32 : desc[idx].mode == KEYDB_SEARCH_MODE_FPR20? 20 : 16, pattern[npat]+2); npat++; - if (desc[idx].mode == KEYDB_SEARCH_MODE_FPR20) + if (desc[idx].mode == KEYDB_SEARCH_MODE_FPR20 + || desc[idx].mode == KEYDB_SEARCH_MODE_FPR32) npat_fpr++; } } @@ -1717,7 +1761,7 @@ keyserver_get_chunk (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, int ndesc, } } - /* Remember now many of search items were considered. Note that + /* Remember how many of the search items were considered. Note that this is different from NPAT. */ *r_ndesc_used = idx; diff --git a/kbx/keybox-blob.c b/kbx/keybox-blob.c index 817253590..0bcd4a323 100644 --- a/kbx/keybox-blob.c +++ b/kbx/keybox-blob.c @@ -62,7 +62,8 @@ 2 = OpenPGP 3 = X509 - byte Version number of this blob type - 1 = The only defined value + 1 = Blob with 20 byte fingerprints + 2 = Blob with 32 byte fingerprints and no keyids. - u16 Blob flags bit 0 = contains secret key material (not used) bit 1 = ephemeral blob (e.g. used while querying external resources) @@ -70,19 +71,36 @@ certificate - u32 The length of the keyblock or certificate - u16 [NKEYS] Number of keys (at least 1!) [X509: always 1] - - u16 Size of the key information structure (at least 28). + - u16 Size of the key information structure (at least 28 or 56). - NKEYS times: + Version 1 blob: - b20 The fingerprint of the key. Fingerprints are always 20 bytes, MD5 left padded with zeroes. - u32 Offset to the n-th key's keyID (a keyID is always 8 byte) or 0 if not known which is the case only for X.509. + Note that this separate keyid is not anymore used by + gnupg since the support for v3 keys has been removed. + We create this field anyway for backward compatibility with + old EOL-ed versions. Eventually we will completely move + to the version 2 blob format. - u16 Key flags bit 0 = qualified signature (not yet implemented} - u16 RFU - bN Optional filler up to the specified length of this structure. + Version 2 blob: + - b32 The fingerprint of the key. This fingerprint is + either 20 or 32 bytes. A 20 byte fingerprint is + right filled with zeroes. + - u16 Key flags + bit 0 = qualified signature (not yet implemented} + bit 7 = 32 byte fingerprint in use. + - u16 RFU + - b20 keygrip + - bN Optional filler up to the specified length of this + structure. - u16 Size of the serial number (may be zero) - - bN The serial number. N as giiven above. + - bN The serial number. N as given above. - u16 Number of user IDs - u16 [NUIDS] Size of user ID information structure - NUIDS times: @@ -172,15 +190,12 @@ struct membuf { }; -/* #if MAX_FINGERPRINT_LEN < 20 */ -/* #error fingerprints are 20 bytes */ -/* #endif */ - struct keyboxblob_key { - char fpr[20]; + char fpr[32]; u32 off_kid; ulong off_kid_addr; u16 flags; + u16 fprlen; /* Either 20 or 32 */ }; struct keyboxblob_uid { u32 off; @@ -380,10 +395,9 @@ pgp_create_key_part_single (KEYBOXBLOB blob, int n, int off; fprlen = kinfo->fprlen; - if (fprlen > 20) - fprlen = 20; memcpy (blob->keys[n].fpr, kinfo->fpr, fprlen); - if (fprlen != 20) /* v3 fpr - shift right and fill with zeroes. */ + blob->keys[n].fprlen = fprlen; + if (fprlen < 20) /* v3 fpr - shift right and fill with zeroes. */ { memmove (blob->keys[n].fpr + 20 - fprlen, blob->keys[n].fpr, fprlen); memset (blob->keys[n].fpr, 0, 20 - fprlen); @@ -533,30 +547,51 @@ release_kid_list (struct keyid_list *kl) } - +/* Create a new blob header. If WANT_FPR32 is set a version 2 blob is + * created. */ static int -create_blob_header (KEYBOXBLOB blob, int blobtype, int as_ephemeral) +create_blob_header (KEYBOXBLOB blob, int blobtype, int as_ephemeral, + int want_fpr32) { struct membuf *a = blob->buf; int i; put32 ( a, 0 ); /* blob length, needs fixup */ put8 ( a, blobtype); - put8 ( a, 1 ); /* blob type version */ + put8 ( a, want_fpr32? 2:1 ); /* blob type version */ put16 ( a, as_ephemeral? 2:0 ); /* blob flags */ put32 ( a, 0 ); /* offset to the raw data, needs fixup */ put32 ( a, 0 ); /* length of the raw data, needs fixup */ put16 ( a, blob->nkeys ); - put16 ( a, 20 + 4 + 2 + 2 ); /* size of key info */ + if (want_fpr32) + put16 ( a, 32 + 2 + 2 + 20); /* size of key info */ + else + put16 ( a, 20 + 4 + 2 + 2 ); /* size of key info */ for ( i=0; i < blob->nkeys; i++ ) { - put_membuf (a, blob->keys[i].fpr, 20); - blob->keys[i].off_kid_addr = a->len; - put32 ( a, 0 ); /* offset to keyid, fixed up later */ - put16 ( a, blob->keys[i].flags ); - put16 ( a, 0 ); /* reserved */ + if (want_fpr32) + { + put_membuf (a, blob->keys[i].fpr, blob->keys[i].fprlen); + blob->keys[i].off_kid_addr = a->len; + if (blob->keys[i].fprlen == 32) + put16 ( a, (blob->keys[i].flags | 0x80)); + else + put16 ( a, blob->keys[i].flags); + put16 ( a, 0 ); /* reserved */ + /* FIXME: Put the real grip here instead of the filler. */ + put_membuf (a, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 20); + } + else + { + log_assert (blob->keys[i].fprlen <= 20); + put_membuf (a, blob->keys[i].fpr, 20); + blob->keys[i].off_kid_addr = a->len; + put32 ( a, 0 ); /* offset to keyid, fixed up later */ + put16 ( a, blob->keys[i].flags ); + put16 ( a, 0 ); /* reserved */ + } } put16 (a, blob->seriallen); /*fixme: check that it fits into 16 bits*/ @@ -593,11 +628,14 @@ create_blob_header (KEYBOXBLOB blob, int blobtype, int as_ephemeral) /* space where we write keyIDs and other stuff so that the pointers can actually point to somewhere */ - if (blobtype == KEYBOX_BLOBTYPE_PGP) + if (blobtype == KEYBOX_BLOBTYPE_PGP && !want_fpr32) { - /* We need to store the keyids for all pgp v3 keys because those key - IDs are not part of the fingerprint. While we are doing that, we - fixup all the keyID offsets */ + /* For version 1 blobs, we need to store the keyids for all v3 + * keys because those key IDs are not part of the fingerprint. + * While we are doing that, we fixup all the keyID offsets. For + * version 2 blobs (which can't carry v3 keys) we compute the + * keyids in the fly because they are just stripped down + * fingerprints. */ for (i=0; i < blob->nkeys; i++ ) { if (blob->keys[i].off_kid) @@ -711,9 +749,27 @@ _keybox_create_openpgp_blob (KEYBOXBLOB *r_blob, { gpg_error_t err; KEYBOXBLOB blob; + int need_fpr32 = 0; *r_blob = NULL; + + /* Check whether we need a blob with 32 bit fingerprints. We could + * use this always but for backward compatiblity we do this only for + * v5 keys. */ + if (info->primary.version == 5) + need_fpr32 = 1; + else + { + struct _keybox_openpgp_key_info *kinfo; + for (kinfo = &info->subkeys; kinfo; kinfo = kinfo->next) + if (kinfo->version == 5) + { + need_fpr32 = 1; + break; + } + } + blob = xtrycalloc (1, sizeof *blob); if (!blob) return gpg_error_from_syserror (); @@ -756,7 +812,8 @@ _keybox_create_openpgp_blob (KEYBOXBLOB *r_blob, init_membuf (&blob->bufbuf, 1024); blob->buf = &blob->bufbuf; - err = create_blob_header (blob, KEYBOX_BLOBTYPE_PGP, as_ephemeral); + err = create_blob_header (blob, KEYBOX_BLOBTYPE_PGP, + as_ephemeral, need_fpr32); if (err) goto leave; err = pgp_create_blob_keyblock (blob, image, imagelen); @@ -943,7 +1000,7 @@ _keybox_create_x509_blob (KEYBOXBLOB *r_blob, ksba_cert_t cert, init_membuf (&blob->bufbuf, 1024); blob->buf = &blob->bufbuf; /* write out what we already have */ - rc = create_blob_header (blob, KEYBOX_BLOBTYPE_X509, as_ephemeral); + rc = create_blob_header (blob, KEYBOX_BLOBTYPE_X509, as_ephemeral, 0); if (rc) goto leave; rc = x509_create_blob_cert (blob, cert); diff --git a/kbx/keybox-defs.h b/kbx/keybox-defs.h index d2b79baf2..354d5fd11 100644 --- a/kbx/keybox-defs.h +++ b/kbx/keybox-defs.h @@ -99,10 +99,11 @@ struct _keybox_openpgp_key_info { struct _keybox_openpgp_key_info *next; int algo; + int version; unsigned char grip[20]; unsigned char keyid[8]; - int fprlen; /* Either 16 or 20 */ - unsigned char fpr[20]; + int fprlen; /* Either 16, 20 or 32 */ + unsigned char fpr[32]; }; struct _keybox_openpgp_uid_info diff --git a/kbx/keybox-dump.c b/kbx/keybox-dump.c index aa1d93be7..48c3f63c5 100644 --- a/kbx/keybox-dump.c +++ b/kbx/keybox-dump.c @@ -170,6 +170,7 @@ _keybox_dump_blob (KEYBOXBLOB blob, FILE *fp) ulong nserial; ulong unhashed; const byte *p; + int is_fpr32; /* blob ersion 2 */ buffer = _keybox_get_blob_image (blob, &length); @@ -207,7 +208,9 @@ _keybox_dump_blob (KEYBOXBLOB blob, FILE *fp) fprintf (fp, "[can't dump this blob type]\n"); return 0; } + /* Here we have either BLOGTYPE_X509 or BLOBTYPE_OPENPGP */ fprintf (fp, "Version: %d\n", buffer[5]); + is_fpr32 = buffer[5] == 2; if (length < 40) { @@ -267,15 +270,24 @@ _keybox_dump_blob (KEYBOXBLOB blob, FILE *fp) ulong kidoff, kflags; fprintf (fp, "Key-Fpr[%lu]: ", n ); - for (i=0; i < 20; i++ ) - fprintf (fp, "%02X", p[i]); - kidoff = get32 (p + 20); - fprintf (fp, "\nKey-Kid-Off[%lu]: %lu\n", n, kidoff ); - fprintf (fp, "Key-Kid[%lu]: ", n ); - /* fixme: check bounds */ - for (i=0; i < 8; i++ ) - fprintf (fp, "%02X", buffer[kidoff+i] ); - kflags = get16 (p + 24 ); + if (is_fpr32) + { + kflags = get16 (p + 32 ); + for (i=0; i < ((kflags & 0x80)?32:20); i++ ) + fprintf (fp, "%02X", p[i]); + } + else + { + for (i=0; i < 20; i++ ) + fprintf (fp, "%02X", p[i]); + kidoff = get32 (p + 20); + fprintf (fp, "\nKey-Kid-Off[%lu]: %lu\n", n, kidoff ); + fprintf (fp, "Key-Kid[%lu]: ", n ); + /* fixme: check bounds */ + for (i=0; i < 8; i++ ) + fprintf (fp, "%02X", buffer[kidoff+i] ); + kflags = get16 (p + 24 ); + } fprintf( fp, "\nKey-Flags[%lu]: %04lX\n", n, kflags); } diff --git a/kbx/keybox-openpgp.c b/kbx/keybox-openpgp.c index 6d6ed77dc..7a35475ca 100644 --- a/kbx/keybox-openpgp.c +++ b/kbx/keybox-openpgp.c @@ -265,14 +265,17 @@ parse_key (const unsigned char *data, size_t datalen, unsigned char hashbuffer[768]; gcry_md_hd_t md; int is_ecc = 0; + int is_v5; + /* unsigned int pkbytes; for v5: # of octets of the public key params. */ struct keyparm_s keyparm[OPENPGP_MAX_NPKEY]; unsigned char *helpmpibuf[OPENPGP_MAX_NPKEY] = { NULL }; if (datalen < 5) return gpg_error (GPG_ERR_INV_PACKET); version = *data++; datalen--; - if (version < 2 || version > 4 ) + if (version < 2 || version > 5 ) return gpg_error (GPG_ERR_INV_PACKET); /* Invalid version. */ + is_v5 = version == 5; /*timestamp = ((data[0]<<24)|(data[1]<<16)|(data[2]<<8)|(data[3]));*/ data +=4; datalen -=4; @@ -288,6 +291,15 @@ parse_key (const unsigned char *data, size_t datalen, return gpg_error (GPG_ERR_INV_PACKET); algorithm = *data++; datalen--; + if (is_v5) + { + if (datalen < 4) + return gpg_error (GPG_ERR_INV_PACKET); + /* pkbytes = buf32_to_uint (data); */ + data += 4; + datalen -= 4; + } + switch (algorithm) { case PUBKEY_ALGO_RSA: @@ -315,6 +327,7 @@ parse_key (const unsigned char *data, size_t datalen, return gpg_error (GPG_ERR_UNKNOWN_ALGORITHM); } + ki->version = version; ki->algo = algorithm; for (i=0; i < npkey; i++ ) @@ -411,29 +424,63 @@ parse_key (const unsigned char *data, size_t datalen, have a scatter-gather enabled hash function. What we do here is to use a static buffer if this one is large enough and only use the regular hash functions if this buffer is not - large enough. */ - if ( 3 + n < sizeof hashbuffer ) + large enough. + FIXME: Factor this out to a shared fingerprint function. + */ + if (version == 5) { - hashbuffer[0] = 0x99; /* CTB */ - hashbuffer[1] = (n >> 8); /* 2 byte length header. */ - hashbuffer[2] = n; - memcpy (hashbuffer + 3, data_start, n); - gcry_md_hash_buffer (GCRY_MD_SHA1, ki->fpr, hashbuffer, 3 + n); + if ( 5 + n < sizeof hashbuffer ) + { + hashbuffer[0] = 0x9a; /* CTB */ + hashbuffer[1] = (n >> 24);/* 4 byte length header. */ + hashbuffer[2] = (n >> 16); + hashbuffer[3] = (n >> 8); + hashbuffer[4] = (n ); + memcpy (hashbuffer + 5, data_start, n); + gcry_md_hash_buffer (GCRY_MD_SHA256, ki->fpr, hashbuffer, 5 + n); + } + else + { + err = gcry_md_open (&md, GCRY_MD_SHA256, 0); + if (err) + return err; /* Oops */ + gcry_md_putc (md, 0x9a ); /* CTB */ + gcry_md_putc (md, (n >> 24)); /* 4 byte length header. */ + gcry_md_putc (md, (n >> 16)); + gcry_md_putc (md, (n >> 8)); + gcry_md_putc (md, (n )); + gcry_md_write (md, data_start, n); + memcpy (ki->fpr, gcry_md_read (md, 0), 32); + gcry_md_close (md); + } + ki->fprlen = 32; + memcpy (ki->keyid, ki->fpr, 8); } else { - err = gcry_md_open (&md, GCRY_MD_SHA1, 0); - if (err) - return err; /* Oops */ - gcry_md_putc (md, 0x99 ); /* CTB */ - gcry_md_putc (md, (n >> 8) ); /* 2 byte length header. */ - gcry_md_putc (md, n ); - gcry_md_write (md, data_start, n); - memcpy (ki->fpr, gcry_md_read (md, 0), 20); - gcry_md_close (md); + if ( 3 + n < sizeof hashbuffer ) + { + hashbuffer[0] = 0x99; /* CTB */ + hashbuffer[1] = (n >> 8); /* 2 byte length header. */ + hashbuffer[2] = (n ); + memcpy (hashbuffer + 3, data_start, n); + gcry_md_hash_buffer (GCRY_MD_SHA1, ki->fpr, hashbuffer, 3 + n); + } + else + { + err = gcry_md_open (&md, GCRY_MD_SHA1, 0); + if (err) + return err; /* Oops */ + gcry_md_putc (md, 0x99 ); /* CTB */ + gcry_md_putc (md, (n >> 8)); /* 2 byte length header. */ + gcry_md_putc (md, (n )); + gcry_md_write (md, data_start, n); + memcpy (ki->fpr, gcry_md_read (md, 0), 20); + gcry_md_close (md); + } + ki->fprlen = 20; + memcpy (ki->keyid, ki->fpr+12, 8); } - ki->fprlen = 20; - memcpy (ki->keyid, ki->fpr+12, 8); } leave: diff --git a/kbx/keybox-search-desc.h b/kbx/keybox-search-desc.h index c75bfa4c7..cde1cf56f 100644 --- a/kbx/keybox-search-desc.h +++ b/kbx/keybox-search-desc.h @@ -38,7 +38,8 @@ typedef enum { KEYDB_SEARCH_MODE_LONG_KID, KEYDB_SEARCH_MODE_FPR16, KEYDB_SEARCH_MODE_FPR20, - KEYDB_SEARCH_MODE_FPR, + KEYDB_SEARCH_MODE_FPR32, + KEYDB_SEARCH_MODE_FPR, /* (Length of fpr in .fprlen) */ KEYDB_SEARCH_MODE_ISSUER, KEYDB_SEARCH_MODE_ISSUER_SN, KEYDB_SEARCH_MODE_SN, @@ -49,7 +50,7 @@ typedef enum { } KeydbSearchMode; -/* Forwward declaration. See g10/packet.h. */ +/* Forward declaration. See g10/packet.h. */ struct gpg_pkt_user_id_s; typedef struct gpg_pkt_user_id_s *gpg_pkt_user_id_t; @@ -73,6 +74,7 @@ struct keydb_search_desc u32 kid[2]; /* Note that this is in native endianness. */ unsigned char grip[20]; } u; + byte fprlen; /* Only used with KEYDB_SEARCH_MODE_FPR. */ int exact; /* Use exactly this key ('!' suffix in gpg). */ }; diff --git a/kbx/keybox-search.c b/kbx/keybox-search.c index 946ef52ad..db98648f4 100644 --- a/kbx/keybox-search.c +++ b/kbx/keybox-search.c @@ -66,18 +66,31 @@ blob_get_first_keyid (KEYBOXBLOB blob, u32 *kid) { const unsigned char *buffer; size_t length, nkeys, keyinfolen; + int fpr32; buffer = _keybox_get_blob_image (blob, &length); if (length < 48) return 0; /* blob too short */ + fpr32 = buffer[5] == 2; + if (fpr32 && length < 56) + return 0; /* blob to short */ nkeys = get16 (buffer + 16); keyinfolen = get16 (buffer + 18); - if (!nkeys || keyinfolen < 28) + if (!nkeys || keyinfolen < (fpr32?56:28)) return 0; /* invalid blob */ - kid[0] = get32 (buffer + 32); - kid[1] = get32 (buffer + 36); + if (fpr32 && (get16 (buffer + 20 + 32) & 0x80)) + { + /* 32 byte fingerprint. */ + kid[0] = get32 (buffer + 20); + kid[1] = get32 (buffer + 20 + 4); + } + else /* 20 byte fingerprint. */ + { + kid[0] = get32 (buffer + 20 + 12); + kid[1] = get32 (buffer + 20 + 16); + } return 1; } @@ -229,22 +242,23 @@ blob_cmp_sn (KEYBOXBLOB blob, const unsigned char *sn, int snlen) For X.509 this is always 1, for OpenPGP this is 1 for the primary key and 2 and more for the subkeys. */ static int -blob_cmp_fpr (KEYBOXBLOB blob, const unsigned char *fpr) +blob_cmp_fpr (KEYBOXBLOB blob, const unsigned char *fpr, unsigned int fprlen) { const unsigned char *buffer; size_t length; size_t pos, off; size_t nkeys, keyinfolen; - int idx; + int idx, fpr32, storedfprlen; buffer = _keybox_get_blob_image (blob, &length); if (length < 40) return 0; /* blob too short */ + fpr32 = buffer[5] == 2; /*keys*/ nkeys = get16 (buffer + 16); keyinfolen = get16 (buffer + 18 ); - if (keyinfolen < 28) + if (keyinfolen < (fpr32?56:28)) return 0; /* invalid blob */ pos = 20; if (pos + (uint64_t)keyinfolen*nkeys > (uint64_t)length) @@ -253,12 +267,19 @@ blob_cmp_fpr (KEYBOXBLOB blob, const unsigned char *fpr) for (idx=0; idx < nkeys; idx++) { off = pos + idx*keyinfolen; - if (!memcmp (buffer + off, fpr, 20)) + if (fpr32) + storedfprlen = (get16 (buffer + off + 32) & 0x80)? 32:20; + else + storedfprlen = 20; + if (storedfprlen == fprlen + && !memcmp (buffer + off, fpr, storedfprlen)) return idx+1; /* found */ } return 0; /* not found */ } + +/* Helper for has_short_kid and has_long_kid. */ static int blob_cmp_fpr_part (KEYBOXBLOB blob, const unsigned char *fpr, int fproff, int fprlen) @@ -267,25 +288,33 @@ blob_cmp_fpr_part (KEYBOXBLOB blob, const unsigned char *fpr, size_t length; size_t pos, off; size_t nkeys, keyinfolen; - int idx; + int idx, fpr32, storedfprlen; buffer = _keybox_get_blob_image (blob, &length); if (length < 40) return 0; /* blob too short */ + fpr32 = buffer[5] == 2; /*keys*/ nkeys = get16 (buffer + 16); keyinfolen = get16 (buffer + 18 ); - if (keyinfolen < 28) + if (keyinfolen < (fpr32?56:28)) return 0; /* invalid blob */ pos = 20; if (pos + (uint64_t)keyinfolen*nkeys > (uint64_t)length) return 0; /* out of bounds */ + if (fpr32) + fproff = 0; /* keyid are the high-order bits. */ for (idx=0; idx < nkeys; idx++) { off = pos + idx*keyinfolen; - if (!memcmp (buffer + off + fproff, fpr, fprlen)) + if (fpr32) + storedfprlen = (get16 (buffer + off + 32) & 0x80)? 32:20; + else + storedfprlen = 20; + if (storedfprlen == fproff + fprlen + && !memcmp (buffer + off + fproff, fpr, fprlen)) return idx+1; /* found */ } return 0; /* not found */ @@ -650,9 +679,9 @@ has_long_kid (KEYBOXBLOB blob, u32 mkid, u32 lkid) } static inline int -has_fingerprint (KEYBOXBLOB blob, const unsigned char *fpr) +has_fingerprint (KEYBOXBLOB blob, const unsigned char *fpr, unsigned int fprlen) { - return blob_cmp_fpr (blob, fpr); + return blob_cmp_fpr (blob, fpr, fprlen); } static inline int @@ -1047,12 +1076,25 @@ keybox_search (KEYBOX_HANDLE hd, KEYBOX_SEARCH_DESC *desc, size_t ndesc, if (pk_no) goto found; break; + case KEYDB_SEARCH_MODE_FPR: - case KEYDB_SEARCH_MODE_FPR20: - pk_no = has_fingerprint (blob, desc[n].u.fpr); + pk_no = has_fingerprint (blob, desc[n].u.fpr, desc[n].fprlen); if (pk_no) goto found; break; + + case KEYDB_SEARCH_MODE_FPR20: + pk_no = has_fingerprint (blob, desc[n].u.fpr, 20); + if (pk_no) + goto found; + break; + + case KEYDB_SEARCH_MODE_FPR32: + pk_no = has_fingerprint (blob, desc[n].u.fpr, 32); + if (pk_no) + goto found; + break; + case KEYDB_SEARCH_MODE_KEYGRIP: if (has_keygrip (blob, desc[n].u.grip)) goto found; diff --git a/sm/keydb.c b/sm/keydb.c index a6ea9f77f..f66a5766d 100644 --- a/sm/keydb.c +++ b/sm/keydb.c @@ -1052,6 +1052,7 @@ keydb_search_fpr (ctrl_t ctrl, KEYDB_HANDLE hd, const byte *fpr) memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_FPR; memcpy (desc.u.fpr, fpr, 20); + desc.fprlen = 20; return keydb_search (ctrl, hd, &desc, 1); } From 01c87d4ce23bc9fc44ec5301c2c6bf2ce615c375 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 14 Mar 2019 11:20:07 +0100 Subject: [PATCH 226/235] gpg: Implement v5 keys and v5 signatures. * g10/build-packet.c (gpg_mpi_write): New optional arg R_NWRITTEN. Allow NULL for OUT. Change all callers. (do_key): Support v5 keys. (build_sig_subpkt_from_sig): Support 32 byte fingerprints. * g10/parse-packet.c (parse_signature): First try to set the keyid from the issuer fingerprint. (parse_key): Support v5 keys. (create_gpg_control): Better make sure to always allocate the static size of the struct in case future compilers print warnings. * g10/keyid.c (hash_public_key): Add v5 support. (keyid_from_pk): Ditto. (keyid_from_fingerprint): Ditto. (fingerprint_from_pk): Ditto. * g10/keygen.c (KEYGEN_FLAG_CREATE_V5_KEY): New. (pVERSION, pSUBVERSION): New. (add_feature_v5): New. (keygen_upd_std_prefs): Call it. (do_create_from_keygrip): Add arg keygen_flags and support the v5 flag. (common_gen): Support the v5 flag. (parse_key_parameter_part): New flags v4 and v5. (parse_key_parameter_string): Add args for version and subversion. (read_parameter_file): New keywords "Key-Version" and "Subkey-Version". (quickgen_set_para): Add arg 'version'. (quick_generate_keypair, generate_keypair): Support version parms. (do_generate_keypair): Support v5 key flag. (generate_subkeypair): Ditto. (generate_card_subkeypair): Preparse for keyflags. (gen_card_key): Ditto. * g10/sig-check.c (check_signature2): Add args extrahash and extrahashlen. (check_signature_end): Ditto. (check_signature_end_simple): Ditto. Use them. * g10/mainproc.c (proc_plaintext): Put extra hash infor into the control packet. (do_check_sig): Add args extrahas and extrahashlen and pass them on. (issuer_fpr_raw): Support 32 byte fingerprint. (check_sig_and_print): get extra hash data and pass it on. -- Note that this is only basic support and requires more fine tuning/fixing. Signed-off-by: Werner Koch --- g10/build-packet.c | 167 +++++++++++++++++++++++--------- g10/keygen.c | 232 +++++++++++++++++++++++++++++++++------------ g10/keyid.c | 100 +++++++++++++------ g10/mainproc.c | 76 +++++++++++---- g10/packet.h | 3 +- g10/parse-packet.c | 129 +++++++++++++++++++------ g10/sig-check.c | 43 ++++++--- g10/sign.c | 12 +-- 8 files changed, 565 insertions(+), 197 deletions(-) diff --git a/g10/build-packet.c b/g10/build-packet.c index dd4ad54bf..07fccb099 100644 --- a/g10/build-packet.c +++ b/g10/build-packet.c @@ -243,12 +243,15 @@ build_packet_and_meta (iobuf_t out, PACKET *pkt) /* - * Write the mpi A to OUT. + * Write the mpi A to OUT. If R_NWRITTEN is not NULL the number of + * bytes written is stored there. To only get the number of bytes + * which would be written NULL may be passed for OUT. */ gpg_error_t -gpg_mpi_write (iobuf_t out, gcry_mpi_t a) +gpg_mpi_write (iobuf_t out, gcry_mpi_t a, unsigned int *r_nwritten) { - int rc; + gpg_error_t err; + unsigned int nwritten = 0; if (gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE)) { @@ -277,9 +280,17 @@ gpg_mpi_write (iobuf_t out, gcry_mpi_t a) /* gcry_log_debughex (" ", p, (nbits+7)/8); */ lenhdr[0] = nbits >> 8; lenhdr[1] = nbits; - rc = iobuf_write (out, lenhdr, 2); - if (!rc && p) - rc = iobuf_write (out, p, (nbits+7)/8); + err = out? iobuf_write (out, lenhdr, 2) : 0; + if (!err) + { + nwritten += 2; + if (p) + { + err = out? iobuf_write (out, p, (nbits+7)/8) : 0; + if (!err) + nwritten += (nbits+7)/8; + } + } } else { @@ -287,18 +298,25 @@ gpg_mpi_write (iobuf_t out, gcry_mpi_t a) size_t nbytes; nbytes = DIM(buffer); - rc = gcry_mpi_print (GCRYMPI_FMT_PGP, buffer, nbytes, &nbytes, a ); - if( !rc ) - rc = iobuf_write( out, buffer, nbytes ); - else if (gpg_err_code(rc) == GPG_ERR_TOO_SHORT ) + err = gcry_mpi_print (GCRYMPI_FMT_PGP, buffer, nbytes, &nbytes, a ); + if (!err) + { + err = out? iobuf_write (out, buffer, nbytes) : 0; + if (!err) + nwritten += nbytes; + } + else if (gpg_err_code (err) == GPG_ERR_TOO_SHORT ) { log_info ("mpi too large (%u bits)\n", gcry_mpi_get_nbits (a)); - /* The buffer was too small. We better tell the user about the MPI. */ - rc = gpg_error (GPG_ERR_TOO_LARGE); + /* The buffer was too small. We better tell the user about + * the MPI. */ + err = gpg_error (GPG_ERR_TOO_LARGE); } } - return rc; + if (r_nwritten) + *r_nwritten = nwritten; + return err; } @@ -463,29 +481,29 @@ static int do_key (iobuf_t out, int ctb, PKT_public_key *pk) { gpg_error_t err = 0; - /* The length of the body is stored in the packet's header, which - occurs before the body. Unfortunately, we don't know the length - of the packet's body until we've written all of the data! To - work around this, we first write the data into this temporary - buffer, then generate the header, and finally copy the contents - of this buffer to OUT. */ - iobuf_t a = iobuf_temp(); + iobuf_t a; int i, nskey, npkey; + u32 pkbytes = 0; + int is_v5; - log_assert (pk->version == 0 || pk->version == 4); + log_assert (pk->version == 0 || pk->version == 4 || pk->version == 5); log_assert (ctb_pkttype (ctb) == PKT_PUBLIC_KEY || ctb_pkttype (ctb) == PKT_PUBLIC_SUBKEY || ctb_pkttype (ctb) == PKT_SECRET_KEY || ctb_pkttype (ctb) == PKT_SECRET_SUBKEY); - /* Write the version number - if none is specified, use 4 */ - if ( !pk->version ) - iobuf_put ( a, 4 ); - else - iobuf_put ( a, pk->version ); - write_32 (a, pk->timestamp ); + /* The length of the body is stored in the packet's header, which + * occurs before the body. Unfortunately, we don't know the length + * of the packet's body until we've written all of the data! To + * work around this, we first write the data into this temporary + * buffer, then generate the header, and finally copy the content + * of this buffer to OUT. */ + a = iobuf_temp(); - iobuf_put (a, pk->pubkey_algo ); + /* Note that the Version number, Timestamp, Algo, and the v5 Key + * material count are written at the end of the function. */ + + is_v5 = (pk->version == 5); /* Get number of secret and public parameters. They are held in one array: the public ones followed by the secret ones. */ @@ -509,11 +527,13 @@ do_key (iobuf_t out, int ctb, PKT_public_key *pk) || (pk->pubkey_algo == PUBKEY_ALGO_ECDH && (i == 0 || i == 2))) err = gpg_mpi_write_nohdr (a, pk->pkey[i]); else - err = gpg_mpi_write (a, pk->pkey[i]); + err = gpg_mpi_write (a, pk->pkey[i], NULL); if (err) goto leave; } + /* Record the length of the public key part. */ + pkbytes = iobuf_get_temp_length (a); if (pk->seckey_info) { @@ -523,9 +543,26 @@ do_key (iobuf_t out, int ctb, PKT_public_key *pk) /* Build the header for protected (encrypted) secret parameters. */ if (ski->is_protected) { - /* OpenPGP protection according to rfc2440. */ - iobuf_put (a, ski->sha1chk? 0xfe : 0xff); - iobuf_put (a, ski->algo); + iobuf_put (a, ski->sha1chk? 0xfe : 0xff); /* S2k usage. */ + if (is_v5) + { + /* For a v5 key determine the count of the following + * key-protection material and write it. */ + int count = 1; /* Pubkey algo octet. */ + if (ski->s2k.mode >= 1000) + count += 6; /* GNU specific mode descriptor. */ + else + count += 2; /* Mode and hash algo. */ + if (ski->s2k.mode == 1 || ski->s2k.mode == 3) + count += 8; /* Salt. */ + if (ski->s2k.mode == 3) + count++; /* S2K.COUNT */ + if (ski->s2k.mode != 1001 && ski->s2k.mode != 1002) + count += ski->ivlen; + + iobuf_put (a, count); + } + iobuf_put (a, ski->algo); /* Pubkey algo octet. */ if (ski->s2k.mode >= 1000) { /* These modes are not possible in OpenPGP, we use them @@ -556,13 +593,24 @@ do_key (iobuf_t out, int ctb, PKT_public_key *pk) } else /* Not protected. */ - iobuf_put (a, 0 ); + { + iobuf_put (a, 0 ); /* S2K usage = not protected. */ + if (is_v5) + iobuf_put (a, 0); /* Zero octets of key-protection + * material follows. */ + } if (ski->s2k.mode == 1001) - ; /* GnuPG extension - don't write a secret key at all. */ + { + /* GnuPG extension - don't write a secret key at all. */ + if (is_v5) + write_32 (a, 0); /* Zero octets of key material. */ + } else if (ski->s2k.mode == 1002) { /* GnuPG extension - divert to OpenPGP smartcard. */ + if (is_v5) + write_32 (a, 1 + ski->ivlen); /* Length of the serial number or 0 for no serial number. */ iobuf_put (a, ski->ivlen ); /* The serial number gets stored in the IV field. */ @@ -576,15 +624,36 @@ do_key (iobuf_t out, int ctb, PKT_public_key *pk) log_assert (gcry_mpi_get_flag (pk->pkey[npkey], GCRYMPI_FLAG_OPAQUE)); p = gcry_mpi_get_opaque (pk->pkey[npkey], &ndatabits); + /* For v5 keys we first write the number of octets of the + * following encrypted key material. */ + if (is_v5) + write_32 (a, p? (ndatabits+7)/8 : 0); if (p) iobuf_write (a, p, (ndatabits+7)/8 ); } else { /* Non-protected key. */ + if (is_v5) + { + unsigned int skbytes = 0; + unsigned int n; + int j; + + for (j=i; j < nskey; j++ ) + { + if ((err = gpg_mpi_write (NULL, pk->pkey[j], &n))) + goto leave; + skbytes += n; + } + + write_32 (a, skbytes); + } + for ( ; i < nskey; i++ ) - if ( (err = gpg_mpi_write (a, pk->pkey[i]))) + if ( (err = gpg_mpi_write (a, pk->pkey[i], NULL))) goto leave; + write_16 (a, ski->csum ); } } @@ -593,11 +662,23 @@ do_key (iobuf_t out, int ctb, PKT_public_key *pk) if (!err) { /* Build the header of the packet - which we must do after - writing all the other stuff, so that we know the length of - the packet */ - write_header2 (out, ctb, iobuf_get_temp_length(a), 0); + * writing all the other stuff, so that we know the length of + * the packet */ + u32 len = iobuf_get_temp_length (a); + len += 1; /* version number */ + len += 4; /* timestamp */ + len += 1; /* algo */ + if (is_v5) + len += 4; /* public key material count */ + + write_header2 (out, ctb, len, 0); /* And finally write it out to the real stream. */ - err = iobuf_write_temp (out, a); + iobuf_put (out, pk->version? pk->version : 4); /* version number */ + write_32 (out, pk->timestamp ); + iobuf_put (out, pk->pubkey_algo); /* algo */ + if (is_v5) + write_32 (out, pkbytes); /* public key material count */ + err = iobuf_write_temp (out, a); /* pub and sec key material */ } iobuf_close (a); /* Close the temporary buffer */ @@ -688,7 +769,7 @@ do_pubkey_enc( IOBUF out, int ctb, PKT_pubkey_enc *enc ) if (enc->pubkey_algo == PUBKEY_ALGO_ECDH && i == 1) rc = gpg_mpi_write_nohdr (a, enc->data[i]); else - rc = gpg_mpi_write (a, enc->data[i]); + rc = gpg_mpi_write (a, enc->data[i], NULL); } if (!rc) @@ -1135,10 +1216,10 @@ build_sig_subpkt_from_sig (PKT_signature *sig, PKT_public_key *pksk) /* Write the new ISSUER_FPR subpacket. */ fingerprint_from_pk (pksk, buf+1, &fprlen); - if (fprlen == 20) + if (fprlen == 20 || fprlen == 32) { buf[0] = pksk->version; - build_sig_subpkt (sig, SIGSUBPKT_ISSUER_FPR, buf, 21); + build_sig_subpkt (sig, SIGSUBPKT_ISSUER_FPR, buf, fprlen + 1); } /* Write the timestamp. */ @@ -1567,7 +1648,7 @@ do_signature( IOBUF out, int ctb, PKT_signature *sig ) if ( !n ) write_fake_data( a, sig->data[0] ); for (i=0; i < n && !rc ; i++ ) - rc = gpg_mpi_write (a, sig->data[i] ); + rc = gpg_mpi_write (a, sig->data[i], NULL); if (!rc) { diff --git a/g10/keygen.c b/g10/keygen.c index 61f839a9f..64fefd231 100644 --- a/g10/keygen.c +++ b/g10/keygen.c @@ -1,6 +1,6 @@ /* keygen.c - Generate a key pair * Copyright (C) 1998-2007, 2009-2011 Free Software Foundation, Inc. - * Copyright (C) 2014, 2015, 2016 Werner Koch + * Copyright (C) 2014, 2015, 2016, 2017, 2018 Werner Koch * * This file is part of GnuPG. * @@ -59,6 +59,7 @@ const char *default_expiration_interval = "2y"; /* Flag bits used during key generation. */ #define KEYGEN_FLAG_NO_PROTECTION 1 #define KEYGEN_FLAG_TRANSIENT_KEY 2 +#define KEYGEN_FLAG_CREATE_V5_KEY 4 /* Maximum number of supported algorithm preferences. */ #define MAX_PREFS 30 @@ -90,7 +91,9 @@ enum para_name { pHANDLE, pKEYSERVER, pKEYGRIP, - pSUBKEYGRIP + pSUBKEYGRIP, + pVERSION, /* Desired version of the key packet. */ + pSUBVERSION, /* Ditto for the subpacket. */ }; struct para_data_s { @@ -148,13 +151,13 @@ static gpg_error_t parse_algo_usage_expire (ctrl_t ctrl, int for_subkey, const char *expirestr, int *r_algo, unsigned int *r_usage, u32 *r_expire, unsigned int *r_nbits, - const char **r_curve); + const char **r_curve, int *r_version); static void do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, struct output_control_s *outctrl, int card ); static int write_keyblock (iobuf_t out, kbnode_t node); static gpg_error_t gen_card_key (int keyno, int algo, int is_primary, kbnode_t pub_root, u32 *timestamp, - u32 expireval); + u32 expireval, int keygen_flags); static unsigned int get_keysize_range (int algo, unsigned int *min, unsigned int *max); @@ -760,6 +763,48 @@ add_feature_aead (PKT_signature *sig, int enabled) } +static void +add_feature_v5 (PKT_signature *sig, int enabled) +{ + const byte *s; + size_t n; + int i; + char *buf; + + s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES, &n ); + if (s && n && ((enabled && (s[0] & 0x04)) || (!enabled && !(s[0] & 0x04)))) + return; /* Already set or cleared */ + + if (!s || !n) + { /* Create a new one */ + n = 1; + buf = xmalloc_clear (n); + } + else + { + buf = xmalloc (n); + memcpy (buf, s, n); + } + + if (enabled) + buf[0] |= 0x04; /* v5 key supported */ + else + buf[0] &= ~0x04; + + /* Are there any bits set? */ + for (i=0; i < n; i++) + if (buf[i]) + break; + + if (i == n) + delete_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES); + else + build_sig_subpkt (sig, SIGSUBPKT_FEATURES, buf, n); + + xfree (buf); +} + + static void add_keyserver_modify (PKT_signature *sig,int enabled) { @@ -848,6 +893,7 @@ keygen_upd_std_prefs (PKT_signature *sig, void *opaque) /* Make sure that the MDC feature flag is set if needed. */ add_feature_mdc (sig,mdc_available); add_feature_aead (sig, aead_available); + add_feature_v5 (sig, opt.flags.rfc4880bis); add_keyserver_modify (sig,ks_modify); keygen_add_keyserver_url(sig,NULL); @@ -1370,7 +1416,7 @@ key_from_sexp (gcry_mpi_t *array, gcry_sexp_t sexp, static int do_create_from_keygrip (ctrl_t ctrl, int algo, const char *hexkeygrip, kbnode_t pub_root, u32 timestamp, u32 expireval, - int is_subkey) + int is_subkey, int keygen_flags) { int err; PACKET *pkt; @@ -1417,7 +1463,7 @@ do_create_from_keygrip (ctrl_t ctrl, int algo, const char *hexkeygrip, } pk->timestamp = timestamp; - pk->version = 4; + pk->version = (keygen_flags & KEYGEN_FLAG_CREATE_V5_KEY)? 5 : 4; if (expireval) pk->expiredate = pk->timestamp + expireval; pk->pubkey_algo = algo; @@ -1484,7 +1530,7 @@ common_gen (const char *keyparms, int algo, const char *algoelem, } pk->timestamp = timestamp; - pk->version = 4; + pk->version = (keygen_flags & KEYGEN_FLAG_CREATE_V5_KEY)? 5 : 4; if (expireval) pk->expiredate = pk->timestamp + expireval; pk->pubkey_algo = algo; @@ -2928,7 +2974,7 @@ ask_user_id (int mode, int full, KBNODE keyblock) /* Basic key generation. Here we divert to the actual generation routines based on the requested algorithm. */ static int -do_create (int algo, unsigned int nbits, const char *curve, KBNODE pub_root, +do_create (int algo, unsigned int nbits, const char *curve, kbnode_t pub_root, u32 timestamp, u32 expiredate, int is_subkey, int keygen_flags, const char *passphrase, char **cache_nonce_addr, char **passwd_nonce_addr) @@ -3007,12 +3053,14 @@ generate_user_id (KBNODE keyblock, const char *uidstr) * for any parameter. FOR_SUBKEY shall be true if this is used as a * subkey. If CLEAR_CERT is set a default CERT usage will be cleared; * this is useful if for example the default algorithm is used for a - * subkey. */ + * subkey. If R_KEYVERSION is not NULL it will receive the version of + * the key; this is currently 4 but can be changed with the flag "v5" + * to create a v5 key. */ static gpg_error_t parse_key_parameter_part (char *string, int for_subkey, int clear_cert, int *r_algo, unsigned int *r_size, unsigned int *r_keyuse, - char const **r_curve) + char const **r_curve, int *r_keyversion) { char *flags; int algo; @@ -3021,6 +3069,7 @@ parse_key_parameter_part (char *string, int for_subkey, int clear_cert, int ecdh_or_ecdsa = 0; unsigned int size; int keyuse; + int keyversion = 4; int i; const char *s; @@ -3119,6 +3168,13 @@ parse_key_parameter_part (char *string, int for_subkey, int clear_cert, return gpg_error (GPG_ERR_INV_FLAG); } } + else if (!ascii_strcasecmp (s, "v5")) + { + if (opt.flags.rfc4880bis) + keyversion = 5; + } + else if (!ascii_strcasecmp (s, "v4")) + keyversion = 4; else { xfree (tokens); @@ -3194,10 +3250,13 @@ parse_key_parameter_part (char *string, int for_subkey, int clear_cert, *r_keyuse = keyuse; if (r_curve) *r_curve = curve; + if (r_keyversion) + *r_keyversion = keyversion; return 0; } + /* Parse and return the standard key generation parameter. * The string is expected to be in this format: * @@ -3228,6 +3287,7 @@ parse_key_parameter_part (char *string, int for_subkey, int clear_cert, * ecdsa := Use algorithm ECDSA. * eddsa := Use algorithm EdDSA. * ecdh := Use algorithm ECDH. + * v5 := Create version 5 key (requires option --rfc4880bis) * * There are several defaults and fallbacks depending on the * algorithm. PART can be used to select which part of STRING is @@ -3246,9 +3306,11 @@ parse_key_parameter_string (const char *string, int part, int *r_algo, unsigned int *r_size, unsigned int *r_keyuse, char const **r_curve, + int *r_version, int *r_subalgo, unsigned int *r_subsize, - unsigned *r_subkeyuse, - char const **r_subcurve) + unsigned int *r_subkeyuse, + char const **r_subcurve, + int *r_subversion) { gpg_error_t err = 0; char *primary, *secondary; @@ -3261,6 +3323,8 @@ parse_key_parameter_string (const char *string, int part, *r_keyuse = 0; if (r_curve) *r_curve = NULL; + if (r_version) + *r_version = 4; if (r_subalgo) *r_subalgo = 0; if (r_subsize) @@ -3269,6 +3333,8 @@ parse_key_parameter_string (const char *string, int part, *r_subkeyuse = 0; if (r_subcurve) *r_subcurve = NULL; + if (r_subversion) + *r_subversion = 4; if (!string || !*string || !ascii_strcasecmp (string, "default") || !strcmp (string, "-")) @@ -3283,11 +3349,11 @@ parse_key_parameter_string (const char *string, int part, *secondary++ = 0; if (part == -1 || part == 0) { - err = parse_key_parameter_part (primary, 0, 0, r_algo, r_size, - r_keyuse, r_curve); + err = parse_key_parameter_part (primary, 0, 0, r_algo, r_size, + r_keyuse, r_curve, r_version); if (!err && part == -1) err = parse_key_parameter_part (secondary, 1, 0, r_subalgo, r_subsize, - r_subkeyuse, r_subcurve); + r_subkeyuse, r_subcurve, r_subversion); } else if (part == 1) { @@ -3300,14 +3366,17 @@ parse_key_parameter_string (const char *string, int part, if (secondary) { err = parse_key_parameter_part (secondary, 1, 0, - r_algo, r_size, r_keyuse, r_curve); + r_algo, r_size, r_keyuse, r_curve, + r_version); if (!err && suggested_use && r_keyuse && !(suggested_use & *r_keyuse)) err = parse_key_parameter_part (primary, 1, 1 /*(clear cert)*/, - r_algo, r_size, r_keyuse, r_curve); + r_algo, r_size, r_keyuse, r_curve, + r_version); } else err = parse_key_parameter_part (primary, 1, 0, - r_algo, r_size, r_keyuse, r_curve); + r_algo, r_size, r_keyuse, r_curve, + r_version); } xfree (primary); @@ -3395,9 +3464,8 @@ get_parameter_algo( struct para_data_s *para, enum para_name key, * compatibility with the batch key generation. It would be * better to make full use of parse_key_parameter_string. */ parse_key_parameter_string (NULL, 0, 0, - &i, NULL, NULL, NULL, - NULL, NULL, NULL, NULL); - + &i, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL); if (r_default) *r_default = 1; } @@ -3810,6 +3878,8 @@ read_parameter_file (ctrl_t ctrl, const char *fname ) { "Keygrip", pKEYGRIP }, { "Key-Grip", pKEYGRIP }, { "Subkey-grip", pSUBKEYGRIP }, + { "Key-Version", pVERSION }, + { "Subkey-Version", pSUBVERSION }, { NULL, 0 } }; IOBUF fp; @@ -3954,12 +4024,19 @@ read_parameter_file (ctrl_t ctrl, const char *fname ) break; } } - r = xmalloc_clear( sizeof *r + strlen( value ) ); - r->lnr = lnr; - r->key = keywords[i].key; - strcpy( r->u.value, value ); - r->next = para; - para = r; + + if (!opt.flags.rfc4880bis && (keywords[i].key == pVERSION + || keywords[i].key == pSUBVERSION)) + ; /* Ignore version unless --rfc4880bis is active. */ + else + { + r = xmalloc_clear( sizeof *r + strlen( value ) ); + r->lnr = lnr; + r->key = keywords[i].key; + strcpy( r->u.value, value ); + r->next = para; + para = r; + } } if( err ) log_error("%s:%d: %s\n", fname, lnr, err ); @@ -3994,7 +4071,8 @@ read_parameter_file (ctrl_t ctrl, const char *fname ) /* Helper for quick_generate_keypair. */ static struct para_data_s * quickgen_set_para (struct para_data_s *para, int for_subkey, - int algo, int nbits, const char *curve, unsigned int use) + int algo, int nbits, const char *curve, unsigned int use, + int version) { struct para_data_s *r; @@ -4033,6 +4111,15 @@ quickgen_set_para (struct para_data_s *para, int for_subkey, para = r; } + if (opt.flags.rfc4880bis) + { + r = xmalloc_clear (sizeof *r + 20); + r->key = for_subkey? pSUBVERSION : pVERSION; + snprintf (r->u.value, 20, "%d", version); + r->next = para; + para = r; + } + return para; } @@ -4125,25 +4212,26 @@ quick_generate_keypair (ctrl_t ctrl, const char *uid, const char *algostr, || !strcmp (usagestr, "-"))) { /* Use default key parameters. */ - int algo, subalgo; + int algo, subalgo, version, subversion; unsigned int size, subsize; unsigned int keyuse, subkeyuse; const char *curve, *subcurve; err = parse_key_parameter_string (algostr, -1, 0, - &algo, &size, &keyuse, &curve, + &algo, &size, &keyuse, &curve, &version, &subalgo, &subsize, &subkeyuse, - &subcurve); + &subcurve, &subversion); if (err) { log_error (_("Key generation failed: %s\n"), gpg_strerror (err)); goto leave; } - para = quickgen_set_para (para, 0, algo, size, curve, keyuse); + para = quickgen_set_para (para, 0, algo, size, curve, keyuse, version); if (subalgo) para = quickgen_set_para (para, 1, - subalgo, subsize, subcurve, subkeyuse); + subalgo, subsize, subcurve, subkeyuse, + subversion); if (*expirestr) { @@ -4166,21 +4254,22 @@ quick_generate_keypair (ctrl_t ctrl, const char *uid, const char *algostr, else { /* Extended unattended mode. Creates only the primary key. */ - int algo; + int algo, version; unsigned int use; u32 expire; unsigned int nbits; const char *curve; err = parse_algo_usage_expire (ctrl, 0, algostr, usagestr, expirestr, - &algo, &use, &expire, &nbits, &curve); + &algo, &use, &expire, &nbits, &curve, + &version); if (err) { log_error (_("Key generation failed: %s\n"), gpg_strerror (err) ); goto leave; } - para = quickgen_set_para (para, 0, algo, nbits, curve, use); + para = quickgen_set_para (para, 0, algo, nbits, curve, use, version); r = xmalloc_clear (sizeof *r + 20); r->key = pKEYEXPIRE; r->u.expire = expire; @@ -4494,7 +4583,7 @@ generate_keypair (ctrl_t ctrl, int full, const char *fname, } else /* Default key generation. */ { - int subalgo; + int subalgo, version, subversion; unsigned int size, subsize; unsigned int keyuse, subkeyuse; const char *curve, *subcurve; @@ -4509,18 +4598,19 @@ generate_keypair (ctrl_t ctrl, int full, const char *fname, , "--full-generate-key" ); err = parse_key_parameter_string (NULL, -1, 0, - &algo, &size, &keyuse, &curve, + &algo, &size, &keyuse, &curve, &version, &subalgo, &subsize, - &subkeyuse, &subcurve); + &subkeyuse, &subcurve, &subversion); if (err) { log_error (_("Key generation failed: %s\n"), gpg_strerror (err)); return; } - para = quickgen_set_para (para, 0, algo, size, curve, keyuse); + para = quickgen_set_para (para, 0, algo, size, curve, keyuse, version); if (subalgo) para = quickgen_set_para (para, 1, - subalgo, subsize, subcurve, subkeyuse); + subalgo, subsize, subcurve, subkeyuse, + subversion); } @@ -4740,6 +4830,7 @@ do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, int algo; u32 expire; const char *key_from_hexgrip = NULL; + unsigned int keygen_flags; if (outctrl->dryrun) { @@ -4808,9 +4899,14 @@ do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, algo = get_parameter_algo( para, pKEYTYPE, NULL ); expire = get_parameter_u32( para, pKEYEXPIRE ); key_from_hexgrip = get_parameter_value (para, pKEYGRIP); + + keygen_flags = outctrl->keygen_flags; + if (get_parameter_uint (para, pVERSION) == 5) + keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY; + if (key_from_hexgrip) err = do_create_from_keygrip (ctrl, algo, key_from_hexgrip, - pub_root, timestamp, expire, 0); + pub_root, timestamp, expire, 0, keygen_flags); else if (!card) err = do_create (algo, get_parameter_uint( para, pKEYLENGTH ), @@ -4818,13 +4914,13 @@ do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, pub_root, timestamp, expire, 0, - outctrl->keygen_flags, + keygen_flags, get_parameter_passphrase (para), &cache_nonce, NULL); else err = gen_card_key (1, algo, 1, pub_root, ×tamp, - expire); + expire, keygen_flags); /* Get the pointer to the generated public key packet. */ if (!err) @@ -4863,7 +4959,7 @@ do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, if (!err && card && get_parameter (para, pAUTHKEYTYPE)) { err = gen_card_key (3, get_parameter_algo( para, pAUTHKEYTYPE, NULL ), - 0, pub_root, ×tamp, expire); + 0, pub_root, ×tamp, expire, keygen_flags); if (!err) err = write_keybinding (ctrl, pub_root, pri_psk, NULL, PUBKEY_USAGE_AUTH, timestamp, cache_nonce); @@ -4875,11 +4971,16 @@ do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, s = NULL; key_from_hexgrip = get_parameter_value (para, pSUBKEYGRIP); + + keygen_flags = outctrl->keygen_flags; + if (get_parameter_uint (para, pSUBVERSION) == 5) + keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY; + if (key_from_hexgrip) err = do_create_from_keygrip (ctrl, subkey_algo, key_from_hexgrip, pub_root, timestamp, get_parameter_u32 (para, pSUBKEYEXPIRE), - 1); + 1, keygen_flags); else if (!card || (s = get_parameter_value (para, pCARDBACKUPKEY))) { err = do_create (subkey_algo, @@ -4888,7 +4989,7 @@ do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, pub_root, timestamp, get_parameter_u32 (para, pSUBKEYEXPIRE), 1, - s ? KEYGEN_FLAG_NO_PROTECTION : outctrl->keygen_flags, + s? KEYGEN_FLAG_NO_PROTECTION : keygen_flags, get_parameter_passphrase (para), &cache_nonce, NULL); /* Get the pointer to the generated public subkey packet. */ @@ -4908,7 +5009,8 @@ do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, } else { - err = gen_card_key (2, subkey_algo, 0, pub_root, ×tamp, expire); + err = gen_card_key (2, subkey_algo, 0, pub_root, ×tamp, expire, + keygen_flags); } if (!err) @@ -5032,13 +5134,15 @@ parse_algo_usage_expire (ctrl_t ctrl, int for_subkey, const char *algostr, const char *usagestr, const char *expirestr, int *r_algo, unsigned int *r_usage, u32 *r_expire, - unsigned int *r_nbits, const char **r_curve) + unsigned int *r_nbits, const char **r_curve, + int *r_version) { gpg_error_t err; int algo; unsigned int use, nbits; u32 expire; int wantuse; + int version = 4; const char *curve = NULL; *r_curve = NULL; @@ -5056,8 +5160,8 @@ parse_algo_usage_expire (ctrl_t ctrl, int for_subkey, err = parse_key_parameter_string (algostr, for_subkey? 1 : 0, usagestr? parse_usagestr (usagestr):0, - &algo, &nbits, &use, &curve, - NULL, NULL, NULL, NULL); + &algo, &nbits, &use, &curve, &version, + NULL, NULL, NULL, NULL, NULL); if (err) return err; @@ -5095,6 +5199,7 @@ parse_algo_usage_expire (ctrl_t ctrl, int for_subkey, *r_usage = use; *r_expire = expire; *r_nbits = nbits; + *r_version = version; return 0; } @@ -5122,6 +5227,7 @@ generate_subkeypair (ctrl_t ctrl, kbnode_t keyblock, const char *algostr, char *serialno = NULL; char *cache_nonce = NULL; char *passwd_nonce = NULL; + int keygen_flags = 0; interactive = (!algostr || !usagestr || !expirestr); @@ -5203,10 +5309,16 @@ generate_subkeypair (ctrl_t ctrl, kbnode_t keyblock, const char *algostr, } else /* Unattended mode. */ { + int version; + err = parse_algo_usage_expire (ctrl, 1, algostr, usagestr, expirestr, - &algo, &use, &expire, &nbits, &curve); + &algo, &use, &expire, &nbits, &curve, + &version); if (err) goto leave; + + if (version == 5) + keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY; } /* Verify the passphrase now so that we get a cache item for the @@ -5229,7 +5341,8 @@ generate_subkeypair (ctrl_t ctrl, kbnode_t keyblock, const char *algostr, if (key_from_hexgrip) { err = do_create_from_keygrip (ctrl, algo, key_from_hexgrip, - keyblock, cur_time, expire, 1); + keyblock, cur_time, expire, 1, + keygen_flags); } else { @@ -5245,7 +5358,7 @@ generate_subkeypair (ctrl_t ctrl, kbnode_t keyblock, const char *algostr, passwd = NULL; err = do_create (algo, nbits, curve, - keyblock, cur_time, expire, 1, 0, + keyblock, cur_time, expire, 1, keygen_flags, passwd, &cache_nonce, &passwd_nonce); } if (err) @@ -5293,6 +5406,7 @@ generate_card_subkeypair (ctrl_t ctrl, kbnode_t pub_keyblock, PKT_public_key *sub_pk = NULL; int algo; struct agent_card_info_s info; + int keygen_flags = 0; /* FIXME!!! */ log_assert (keyno >= 1 && keyno <= 3); @@ -5363,7 +5477,8 @@ generate_card_subkeypair (ctrl_t ctrl, kbnode_t pub_keyblock, /* Note, that depending on the backend, the card key generation may update CUR_TIME. */ - err = gen_card_key (keyno, algo, 0, pub_keyblock, &cur_time, expire); + err = gen_card_key (keyno, algo, 0, pub_keyblock, &cur_time, expire, + keygen_flags); /* Get the pointer to the generated public subkey packet. */ if (!err) { @@ -5409,10 +5524,11 @@ write_keyblock( IOBUF out, KBNODE node ) } -/* Note that timestamp is an in/out arg. */ +/* Note that timestamp is an in/out arg. + * FIXME: Does not yet support v5 keys. */ static gpg_error_t gen_card_key (int keyno, int algo, int is_primary, kbnode_t pub_root, - u32 *timestamp, u32 expireval) + u32 *timestamp, u32 expireval, int keygen_flags) { #ifdef ENABLE_CARD_SUPPORT gpg_error_t err; @@ -5486,7 +5602,7 @@ gen_card_key (int keyno, int algo, int is_primary, kbnode_t pub_root, } pk->timestamp = *timestamp; - pk->version = 4; + pk->version = (keygen_flags & KEYGEN_FLAG_CREATE_V5_KEY)? 5 : 4; if (expireval) pk->expiredate = pk->timestamp + expireval; pk->pubkey_algo = algo; diff --git a/g10/keyid.c b/g10/keyid.c index 9558a2617..92be95944 100644 --- a/g10/keyid.c +++ b/g10/keyid.c @@ -136,19 +136,21 @@ pubkey_string (PKT_public_key *pk, char *buffer, size_t bufsize) } -/* Hash a public key. This function is useful for v4 fingerprints and - for v3 or v4 key signing. */ +/* Hash a public key. This function is useful for v4 and v5 + * fingerprints and for v3 or v4 key signing. */ void hash_public_key (gcry_md_hd_t md, PKT_public_key *pk) { - unsigned int n = 6; + unsigned int n; unsigned int nn[PUBKEY_MAX_NPKEY]; byte *pp[PUBKEY_MAX_NPKEY]; int i; unsigned int nbits; size_t nbytes; int npkey = pubkey_get_npkey (pk->pubkey_algo); + int is_v5 = pk->version == 5; + n = is_v5? 10 : 6; /* FIXME: We can avoid the extra malloc by calling only the first mpi_print here which computes the required length and calling the real mpi_print only at the end. The speed advantage would only be @@ -201,12 +203,22 @@ hash_public_key (gcry_md_hd_t md, PKT_public_key *pk) } } - gcry_md_putc ( md, 0x99 ); /* ctb */ - /* What does it mean if n is greater than 0xFFFF ? */ - gcry_md_putc ( md, n >> 8 ); /* 2 byte length header */ - gcry_md_putc ( md, n ); - gcry_md_putc ( md, pk->version ); - + if (is_v5) + { + gcry_md_putc ( md, 0x9a ); /* ctb */ + gcry_md_putc ( md, n >> 24 ); /* 4 byte length header */ + gcry_md_putc ( md, n >> 16 ); + gcry_md_putc ( md, n >> 8 ); + gcry_md_putc ( md, n ); + gcry_md_putc ( md, pk->version ); + } + else + { + gcry_md_putc ( md, 0x99 ); /* ctb */ + gcry_md_putc ( md, n >> 8 ); /* 2 byte length header */ + gcry_md_putc ( md, n ); + gcry_md_putc ( md, pk->version ); + } gcry_md_putc ( md, pk->timestamp >> 24 ); gcry_md_putc ( md, pk->timestamp >> 16 ); gcry_md_putc ( md, pk->timestamp >> 8 ); @@ -214,6 +226,15 @@ hash_public_key (gcry_md_hd_t md, PKT_public_key *pk) gcry_md_putc ( md, pk->pubkey_algo ); + if (is_v5) + { + n -= 10; + gcry_md_putc ( md, n >> 24 ); + gcry_md_putc ( md, n >> 16 ); + gcry_md_putc ( md, n >> 8 ); + gcry_md_putc ( md, n ); + } + if(npkey==0 && pk->pkey[0] && gcry_mpi_get_flag (pk->pkey[0], GCRYMPI_FLAG_OPAQUE)) { @@ -237,10 +258,10 @@ do_fingerprint_md( PKT_public_key *pk ) { gcry_md_hd_t md; - if (gcry_md_open (&md, DIGEST_ALGO_SHA1, 0)) + if (gcry_md_open (&md, pk->version == 5 ? GCRY_MD_SHA256 : GCRY_MD_SHA1, 0)) BUG (); - hash_public_key(md,pk); - gcry_md_final( md ); + hash_public_key (md,pk); + gcry_md_final (md); return md; } @@ -517,13 +538,12 @@ keystr_from_desc(KEYDB_SEARCH_DESC *desc) /* - * Get the keyid from the public key and put it into keyid - * if this is not NULL. Return the 32 low bits of the keyid. + * Get the keyid from the public key PK and store it at KEYID unless + * this is NULL. Returns the 32 bit short keyid. */ u32 keyid_from_pk (PKT_public_key *pk, u32 *keyid) { - u32 lowbits; u32 dummy_keyid[2]; if (!keyid) @@ -533,7 +553,6 @@ keyid_from_pk (PKT_public_key *pk, u32 *keyid) { keyid[0] = pk->keyid[0]; keyid[1] = pk->keyid[1]; - lowbits = keyid[1]; } else { @@ -544,18 +563,25 @@ keyid_from_pk (PKT_public_key *pk, u32 *keyid) if(md) { dp = gcry_md_read ( md, 0 ); - keyid[0] = buf32_to_u32 (dp+12); - keyid[1] = buf32_to_u32 (dp+16); - lowbits = keyid[1]; + if (pk->version == 5) + { + keyid[0] = buf32_to_u32 (dp); + keyid[1] = buf32_to_u32 (dp+4); + } + else + { + keyid[0] = buf32_to_u32 (dp+12); + keyid[1] = buf32_to_u32 (dp+16); + } gcry_md_close (md); pk->keyid[0] = keyid[0]; pk->keyid[1] = keyid[1]; } else - pk->keyid[0]=pk->keyid[1]=keyid[0]=keyid[1]=lowbits=0xFFFFFFFF; + pk->keyid[0] = pk->keyid[1] = keyid[0]= keyid[1] = 0xFFFFFFFF; } - return lowbits; + return keyid[1]; /*FIXME:shortkeyid ist different for v5*/ } @@ -594,8 +620,16 @@ keyid_from_fingerprint (ctrl_t ctrl, const byte *fprint, else { const byte *dp = fprint; - keyid[0] = buf32_to_u32 (dp+12); - keyid[1] = buf32_to_u32 (dp+16); + if (fprint_len == 20) /* v4 key */ + { + keyid[0] = buf32_to_u32 (dp+12); + keyid[1] = buf32_to_u32 (dp+16); + } + else /* v5 key */ + { + keyid[0] = buf32_to_u32 (dp); + keyid[1] = buf32_to_u32 (dp+4); + } } return keyid[1]; @@ -610,7 +644,7 @@ keyid_from_sig (PKT_signature *sig, u32 *keyid) keyid[0] = sig->keyid[0]; keyid[1] = sig->keyid[1]; } - return sig->keyid[1]; + return sig->keyid[1]; /*FIXME:shortkeyid*/ } @@ -800,15 +834,23 @@ fingerprint_from_pk (PKT_public_key *pk, byte *array, size_t *ret_len) size_t len; gcry_md_hd_t md; - md = do_fingerprint_md(pk); - dp = gcry_md_read( md, 0 ); + md = do_fingerprint_md (pk); + dp = gcry_md_read (md, 0); len = gcry_md_get_algo_dlen (gcry_md_get_algo (md)); - log_assert( len <= MAX_FINGERPRINT_LEN ); + log_assert (len <= MAX_FINGERPRINT_LEN); if (!array) array = xmalloc ( len ); memcpy (array, dp, len ); - pk->keyid[0] = buf32_to_u32 (dp+12); - pk->keyid[1] = buf32_to_u32 (dp+16); + if (pk->version == 5) + { + pk->keyid[0] = buf32_to_u32 (dp); + pk->keyid[1] = buf32_to_u32 (dp+4); + } + else + { + pk->keyid[0] = buf32_to_u32 (dp+12); + pk->keyid[1] = buf32_to_u32 (dp+16); + } gcry_md_close( md); if (ret_len) diff --git a/g10/mainproc.c b/g10/mainproc.c index 8c41088cc..6fa30e0d4 100644 --- a/g10/mainproc.c +++ b/g10/mainproc.c @@ -829,6 +829,8 @@ proc_plaintext( CTX c, PACKET *pkt ) PKT_plaintext *pt = pkt->pkt.plaintext; int any, clearsig, rc; kbnode_t n; + unsigned char *extrahash; + size_t extrahashlen; /* This is a literal data packet. Bump a counter for later checks. */ literals_seen++; @@ -948,8 +950,33 @@ proc_plaintext( CTX c, PACKET *pkt ) c->last_was_session_key = 0; /* We add a marker control packet instead of the plaintext packet. - * This is so that we can later detect invalid packet sequences. */ - n = new_kbnode (create_gpg_control (CTRLPKT_PLAINTEXT_MARK, NULL, 0)); + * This is so that we can later detect invalid packet sequences. + * The apcket is further used to convey extra data from the + * plaintext packet to the signature verification. */ + extrahash = xtrymalloc (6 + pt->namelen); + if (!extrahash) + { + /* No way to return an error. */ + rc = gpg_error_from_syserror (); + log_error ("malloc failed in %s: %s\n", __func__, gpg_strerror (rc)); + extrahashlen = 0; + } + else + { + extrahash[0] = pt->mode; + extrahash[1] = pt->namelen; + if (pt->namelen) + memcpy (extrahash+2, pt->name, pt->namelen); + extrahashlen = 2 + pt->namelen; + extrahash[extrahashlen++] = pt->timestamp >> 24; + extrahash[extrahashlen++] = pt->timestamp >> 16; + extrahash[extrahashlen++] = pt->timestamp >> 8; + extrahash[extrahashlen++] = pt->timestamp ; + } + + n = new_kbnode (create_gpg_control (CTRLPKT_PLAINTEXT_MARK, + extrahash, extrahashlen)); + xfree (extrahash); if (c->list) add_kbnode (c->list, n); else @@ -1019,7 +1046,8 @@ proc_compressed (CTX c, PACKET *pkt) * found. Returns: 0 = valid signature or an error code */ static int -do_check_sig (CTX c, kbnode_t node, int *is_selfsig, +do_check_sig (CTX c, kbnode_t node, const void *extrahash, size_t extrahashlen, + int *is_selfsig, int *is_expkey, int *is_revkey, PKT_public_key **r_pk) { PKT_signature *sig; @@ -1105,14 +1133,16 @@ do_check_sig (CTX c, kbnode_t node, int *is_selfsig, /* We only get here if we are checking the signature of a binary (0x00) or text document (0x01). */ - rc = check_signature2 (c->ctrl, sig, md, NULL, is_expkey, is_revkey, r_pk); + rc = check_signature2 (c->ctrl, sig, md, extrahash, extrahashlen, + NULL, is_expkey, is_revkey, r_pk); if (! rc) md_good = md; else if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE && md2) { PKT_public_key *pk2; - rc = check_signature2 (c->ctrl, sig, md2, NULL, is_expkey, is_revkey, + rc = check_signature2 (c->ctrl, sig, md2, extrahash, extrahashlen, + NULL, is_expkey, is_revkey, r_pk? &pk2 : NULL); if (!rc) { @@ -1275,7 +1305,7 @@ list_node (CTX c, kbnode_t node) if (opt.check_sigs) { fflush (stdout); - rc2 = do_check_sig (c, node, &is_selfsig, NULL, NULL, NULL); + rc2 = do_check_sig (c, node, NULL, 0, &is_selfsig, NULL, NULL, NULL); switch (gpg_err_code (rc2)) { case 0: sigrc = '!'; break; @@ -1738,7 +1768,7 @@ akl_has_wkd_method (void) } -/* Return the ISSUER fingerprint buffer and its lenbgth at R_LEN. +/* Return the ISSUER fingerprint buffer and its length at R_LEN. * Returns NULL if not available. The returned buffer is valid as * long as SIG is not modified. */ const byte * @@ -1748,7 +1778,7 @@ issuer_fpr_raw (PKT_signature *sig, size_t *r_len) size_t n; p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_ISSUER_FPR, &n); - if (p && n == 21 && p[0] == 4) + if (p && ((n == 21 && p[0] == 4) || (n == 33 && p[0] == 5))) { *r_len = n - 1; return p+1; @@ -1811,6 +1841,8 @@ check_sig_and_print (CTX c, kbnode_t node) char *issuer_fpr = NULL; PKT_public_key *pk = NULL; /* The public key for the signature or NULL. */ int tried_ks_by_fpr; + const void *extrahash = NULL; + size_t extrahashlen = 0; if (opt.skip_verify) { @@ -1868,6 +1900,8 @@ check_sig_and_print (CTX c, kbnode_t node) { if (n->next) goto ambiguous; /* We only allow one P packet. */ + extrahash = n->pkt->pkt.gpg_control->data; + extrahashlen = n->pkt->pkt.gpg_control->datalen; } else goto ambiguous; @@ -1882,6 +1916,9 @@ check_sig_and_print (CTX c, kbnode_t node) && (n->pkt->pkt.gpg_control->control == CTRLPKT_PLAINTEXT_MARK))) goto ambiguous; + extrahash = n->pkt->pkt.gpg_control->data; + extrahashlen = n->pkt->pkt.gpg_control->datalen; + for (n_sig=0, n = n->next; n && n->pkt->pkttype == PKT_SIGNATURE; n = n->next) n_sig++; @@ -1912,6 +1949,8 @@ check_sig_and_print (CTX c, kbnode_t node) && (n->pkt->pkt.gpg_control->control == CTRLPKT_PLAINTEXT_MARK))) goto ambiguous; + extrahash = n->pkt->pkt.gpg_control->data; + extrahashlen = n->pkt->pkt.gpg_control->datalen; for (n_sig=0, n = n->next; n && n->pkt->pkttype == PKT_SIGNATURE; n = n->next) n_sig++; @@ -1957,7 +1996,8 @@ check_sig_and_print (CTX c, kbnode_t node) if (sig->signers_uid) log_info (_(" issuer \"%s\"\n"), sig->signers_uid); - rc = do_check_sig (c, node, NULL, &is_expkey, &is_revkey, &pk); + rc = do_check_sig (c, node, extrahash, extrahashlen, + NULL, &is_expkey, &is_revkey, &pk); /* If the key isn't found, check for a preferred keyserver. */ if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY && sig->flags.pref_ks) @@ -1992,8 +2032,8 @@ check_sig_and_print (CTX c, kbnode_t node) res = keyserver_import_keyid (c->ctrl, sig->keyid,spec, 1); glo_ctrl.in_auto_key_retrieve--; if (!res) - rc = do_check_sig (c, node, NULL, - &is_expkey, &is_revkey, &pk); + rc = do_check_sig (c, node, extrahash, extrahashlen, + NULL, &is_expkey, &is_revkey, &pk); free_keyserver_spec (spec); if (!rc) @@ -2028,7 +2068,8 @@ check_sig_and_print (CTX c, kbnode_t node) glo_ctrl.in_auto_key_retrieve--; free_keyserver_spec (spec); if (!res) - rc = do_check_sig (c, node, NULL, &is_expkey, &is_revkey, &pk); + rc = do_check_sig (c, node, extrahash, extrahashlen, + NULL, &is_expkey, &is_revkey, &pk); } } } @@ -2050,7 +2091,7 @@ check_sig_and_print (CTX c, kbnode_t node) p = issuer_fpr_raw (sig, &n); if (p) { - /* v4 packet with a SHA-1 fingerprint. */ + /* v4 or v5 packet with a SHA-1/256 fingerprint. */ free_public_key (pk); pk = NULL; glo_ctrl.in_auto_key_retrieve++; @@ -2058,7 +2099,8 @@ check_sig_and_print (CTX c, kbnode_t node) tried_ks_by_fpr = 1; glo_ctrl.in_auto_key_retrieve--; if (!res) - rc = do_check_sig (c, node, NULL, &is_expkey, &is_revkey, &pk); + rc = do_check_sig (c, node, extrahash, extrahashlen, + NULL, &is_expkey, &is_revkey, &pk); } } @@ -2080,7 +2122,8 @@ check_sig_and_print (CTX c, kbnode_t node) /* Fixme: If the fingerprint is embedded in the signature, * compare it to the fingerprint of the returned key. */ if (!res) - rc = do_check_sig (c, node, NULL, &is_expkey, &is_revkey, &pk); + rc = do_check_sig (c, node, extrahash, extrahashlen, + NULL, &is_expkey, &is_revkey, &pk); } /* If the above methods did't work, our next try is to use a @@ -2098,7 +2141,8 @@ check_sig_and_print (CTX c, kbnode_t node) res = keyserver_import_keyid (c->ctrl, sig->keyid, opt.keyserver, 1); glo_ctrl.in_auto_key_retrieve--; if (!res) - rc = do_check_sig (c, node, NULL, &is_expkey, &is_revkey, &pk); + rc = do_check_sig (c, node, extrahash, extrahashlen, + NULL, &is_expkey, &is_revkey, &pk); } if (!rc || gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE) diff --git a/g10/packet.h b/g10/packet.h index 6160d0b01..41dd1a95a 100644 --- a/g10/packet.h +++ b/g10/packet.h @@ -853,7 +853,7 @@ PACKET *create_gpg_control ( ctrlpkttype_t type, /*-- build-packet.c --*/ int build_packet (iobuf_t out, PACKET *pkt); gpg_error_t build_packet_and_meta (iobuf_t out, PACKET *pkt); -gpg_error_t gpg_mpi_write (iobuf_t out, gcry_mpi_t a); +gpg_error_t gpg_mpi_write (iobuf_t out, gcry_mpi_t a, unsigned int *t_nwritten); gpg_error_t gpg_mpi_write_nohdr (iobuf_t out, gcry_mpi_t a); u32 calc_packet_length( PACKET *pkt ); void build_sig_subpkt( PKT_signature *sig, sigsubpkttype_t type, @@ -900,6 +900,7 @@ int check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest); * it and verifying the signature. */ gpg_error_t check_signature2 (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest, + const void *extrahash, size_t extrahashlen, u32 *r_expiredate, int *r_expired, int *r_revoked, PKT_public_key **r_pk); diff --git a/g10/parse-packet.c b/g10/parse-packet.c index 21a26c786..5b4b1c900 100644 --- a/g10/parse-packet.c +++ b/g10/parse-packet.c @@ -1644,7 +1644,7 @@ parse_one_sig_subpkt (const byte * buffer, size_t n, int type) if (n < 8) break; return 0; - case SIGSUBPKT_ISSUER_FPR: /* issuer key ID */ + case SIGSUBPKT_ISSUER_FPR: /* issuer key fingerprint */ if (n < 21) break; return 0; @@ -2078,10 +2078,23 @@ parse_signature (IOBUF inp, int pkttype, unsigned long pktlen, && 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); + /* Set the key id. We first try the issuer fingerprint and if + * it is a v4 signature the fallback to the issuer. Note that + * only the issuer packet is also searched in the unhashed area. */ + p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_ISSUER_FPR, &len); + if (p && len == 21 && p[0] == 4) + { + sig->keyid[0] = buf32_to_u32 (p + 1 + 12); + sig->keyid[1] = buf32_to_u32 (p + 1 + 16); + } + else if (p && len == 33 && p[0] == 5) + { + sig->keyid[0] = buf32_to_u32 (p + 1 ); + sig->keyid[1] = buf32_to_u32 (p + 1 + 4); + } + else if ((p = parse_sig_subpkt2 (sig, SIGSUBPKT_ISSUER))) + { + 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) @@ -2287,6 +2300,9 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, int npkey, nskey; u32 keyid[2]; PKT_public_key *pk; + int is_v5; + unsigned int pkbytes; /* For v5 keys: Number of bytes in the public + * key material. For v4 keys: 0. */ (void) hdr; @@ -2318,12 +2334,13 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, return 0; } else if (version == 4) - { - /* The only supported version. Use an older gpg - version (i.e. gpg 1.4) to parse v3 packets. */ - } + is_v5 = 0; + else if (version == 5) + is_v5 = 1; else if (version == 2 || version == 3) { + /* Not anymore supported since 2.1. Use an older gpg version + * (i.e. gpg 1.4) to parse v3 packets. */ if (opt.verbose > 1) log_info ("packet(%d) with obsolete version %d\n", pkttype, version); if (list_mode) @@ -2341,7 +2358,7 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, goto leave; } - if (pktlen < 11) + if (pktlen < (is_v5? 15:11)) { log_error ("packet(%d) too short\n", pkttype); if (list_mode) @@ -2364,14 +2381,28 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, max_expiredate = 0; algorithm = iobuf_get_noeof (inp); pktlen--; + if (is_v5) + { + pkbytes = read_32 (inp); + pktlen -= 4; + } + else + pkbytes = 0; + 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); + { + es_fprintf (listfp, ":%s key packet:\n" + "\tversion %d, algo %d, created %lu, expires %lu", + 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); + if (is_v5) + es_fprintf (listfp, ", pkbytes %u\n", pkbytes); + else + es_fprintf (listfp, "\n"); + } pk->timestamp = timestamp; pk->expiredate = expiredate; @@ -2446,6 +2477,7 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, struct seckey_info *ski; byte temp[16]; size_t snlen = 0; + unsigned int skbytes; if (pktlen < 1) { @@ -2462,23 +2494,42 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, ski->algo = iobuf_get_noeof (inp); pktlen--; + + if (is_v5) + { + unsigned int protcount = 0; + + /* Read the one octet count of the following key-protection + * material. Only required in case of unknown values. */ + if (!pktlen) + { + err = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + protcount = iobuf_get_noeof (inp); + pktlen--; + if (list_mode) + es_fprintf (listfp, "\tprotbytes: %u\n", protcount); + } + if (ski->algo) { ski->is_protected = 1; ski->s2k.count = 0; if (ski->algo == 254 || ski->algo == 255) { - if (pktlen < 3) + if (pktlen < 3) { err = gpg_error (GPG_ERR_INV_PACKET); goto leave; } - ski->sha1chk = (ski->algo == 254); + + 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. */ + * erroring out 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); @@ -2504,10 +2555,8 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, } /* Read the salt. */ - switch (ski->s2k.mode) + if (ski->s2k.mode == 3 || ski->s2k.mode == 1) { - case 1: - case 3: for (i = 0; i < 8 && pktlen; i++, pktlen--) temp[i] = iobuf_get_noeof (inp); if (i < 8) @@ -2516,7 +2565,6 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, goto leave; } memcpy (ski->s2k.salt, temp, 8); - break; } /* Check the mode. */ @@ -2616,7 +2664,9 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, * 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. */ + * enlarge temp. + * FIXME: For v5 keys we can deduce this info! + */ ski->ivlen = openpgp_cipher_blocklen (ski->algo); log_assert (ski->ivlen <= sizeof (temp)); @@ -2644,6 +2694,20 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, memcpy (ski->iv, temp, ski->ivlen); } + /* Skip count of secret key material. */ + if (is_v5) + { + if (pktlen < 4) + { + err = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + skbytes = read_32 (inp); + pktlen -= 4; + if (list_mode) + es_fprintf (listfp, "\tskbytes: %u\n", skbytes); + } + /* 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 :=(. @@ -2666,13 +2730,14 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, /* Ugly: The length is encrypted too, so we read all stuff * up to the end of the packet into the first SKEY - * element. */ + * element. + * FIXME: We can do better for v5 keys. */ 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. */ + * 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; @@ -3509,11 +3574,13 @@ create_gpg_control (ctrlpkttype_t type, const byte * data, size_t datalen) PACKET *packet; byte *p; + if (!data) + datalen = 0; + 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 = xmalloc (sizeof *packet->pkt.gpg_control + datalen); packet->pkt.gpg_control->control = type; packet->pkt.gpg_control->datalen = datalen; p = packet->pkt.gpg_control->data; diff --git a/g10/sig-check.c b/g10/sig-check.c index e8782f90d..e7f97de65 100644 --- a/g10/sig-check.c +++ b/g10/sig-check.c @@ -37,11 +37,14 @@ static int check_signature_end (PKT_public_key *pk, PKT_signature *sig, gcry_md_hd_t digest, + const void *extrahash, size_t extrahashlen, int *r_expired, int *r_revoked, PKT_public_key *ret_pk); static int check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig, - gcry_md_hd_t digest); + gcry_md_hd_t digest, + const void *extrahash, + size_t extrahashlen); /* Statistics for signature verification. */ @@ -69,7 +72,7 @@ sig_check_dump_stats (void) int check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest) { - return check_signature2 (ctrl, sig, digest, NULL, NULL, NULL, NULL); + return check_signature2 (ctrl, sig, digest, NULL, 0, NULL, NULL, NULL, NULL); } @@ -95,6 +98,9 @@ check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest) * signature data from the version number through the hashed subpacket * data (inclusive) is hashed.") * + * EXTRAHASH and EXTRAHASHLEN is additional data which is hashed with + * v5 signatures. They may be NULL to use the default. + * * If R_EXPIREDATE is not NULL, R_EXPIREDATE is set to the key's * expiry. * @@ -112,7 +118,9 @@ check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest) * Returns 0 on success. An error code otherwise. */ gpg_error_t check_signature2 (ctrl_t ctrl, - PKT_signature *sig, gcry_md_hd_t digest, u32 *r_expiredate, + PKT_signature *sig, gcry_md_hd_t digest, + const void *extrahash, size_t extrahashlen, + u32 *r_expiredate, int *r_expired, int *r_revoked, PKT_public_key **r_pk) { int rc=0; @@ -179,7 +187,8 @@ check_signature2 (ctrl_t ctrl, if (r_expiredate) *r_expiredate = pk->expiredate; - rc = check_signature_end (pk, sig, digest, r_expired, r_revoked, NULL); + rc = check_signature_end (pk, sig, digest, extrahash, extrahashlen, + r_expired, r_revoked, NULL); /* Check the backsig. This is a back signature (0x19) from * the subkey on the primary key. The idea here is that it @@ -424,6 +433,7 @@ check_signature_metadata_validity (PKT_public_key *pk, PKT_signature *sig, static int check_signature_end (PKT_public_key *pk, PKT_signature *sig, gcry_md_hd_t digest, + const void *extrahash, size_t extrahashlen, int *r_expired, int *r_revoked, PKT_public_key *ret_pk) { int rc = 0; @@ -432,7 +442,8 @@ check_signature_end (PKT_public_key *pk, PKT_signature *sig, r_expired, r_revoked))) return rc; - if ((rc = check_signature_end_simple (pk, sig, digest))) + if ((rc = check_signature_end_simple (pk, sig, digest, + extrahash, extrahashlen))) return rc; if (!rc && ret_pk) @@ -447,7 +458,8 @@ check_signature_end (PKT_public_key *pk, PKT_signature *sig, * expiration, revocation, etc. */ static int check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig, - gcry_md_hd_t digest) + gcry_md_hd_t digest, + const void *extrahash, size_t extrahashlen) { gcry_mpi_t result = NULL; int rc = 0; @@ -539,8 +551,13 @@ check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig, /* - One octet content format * - File name (one octet length followed by the name) * - Four octet timestamp */ - memset (buf, 0, 6); - gcry_md_write (digest, buf, 6); + if (extrahash && extrahashlen) + gcry_md_write (digest, extrahash, extrahashlen); + else /* Detached signature. */ + { + memset (buf, 0, 6); + gcry_md_write (digest, buf, 6); + } } /* Add some magic per Section 5.2.4 of RFC 4880. */ i = 0; @@ -790,7 +807,7 @@ check_backsig (PKT_public_key *main_pk,PKT_public_key *sub_pk, { hash_public_key(md,main_pk); hash_public_key(md,sub_pk); - rc = check_signature_end (sub_pk, backsig, md, NULL, NULL, NULL); + rc = check_signature_end (sub_pk, backsig, md, NULL, 0, NULL, NULL, NULL); cache_sig_result(backsig,rc); gcry_md_close(md); } @@ -977,28 +994,28 @@ check_signature_over_key_or_uid (ctrl_t ctrl, PKT_public_key *signer, { log_assert (packet->pkttype == PKT_PUBLIC_KEY); hash_public_key (md, packet->pkt.public_key); - rc = check_signature_end_simple (signer, sig, md); + rc = check_signature_end_simple (signer, sig, md, NULL, 0); } else if (IS_BACK_SIG (sig)) { log_assert (packet->pkttype == PKT_PUBLIC_KEY); hash_public_key (md, packet->pkt.public_key); hash_public_key (md, signer); - rc = check_signature_end_simple (signer, sig, md); + rc = check_signature_end_simple (signer, sig, md, NULL, 0); } else if (IS_SUBKEY_SIG (sig) || IS_SUBKEY_REV (sig)) { log_assert (packet->pkttype == PKT_PUBLIC_SUBKEY); hash_public_key (md, pripk); hash_public_key (md, packet->pkt.public_key); - rc = check_signature_end_simple (signer, sig, md); + rc = check_signature_end_simple (signer, sig, md, NULL, 0); } else if (IS_UID_SIG (sig) || IS_UID_REV (sig)) { log_assert (packet->pkttype == PKT_USER_ID); hash_public_key (md, pripk); hash_uid_packet (packet->pkt.user_id, md, sig); - rc = check_signature_end_simple (signer, sig, md); + rc = check_signature_end_simple (signer, sig, md, NULL, 0); } else { diff --git a/g10/sign.c b/g10/sign.c index 67556d8ba..176940bff 100644 --- a/g10/sign.c +++ b/g10/sign.c @@ -49,6 +49,10 @@ #define LF "\n" #endif +/* Hack */ +static int recipient_digest_algo; + + /* A type for the extra data we hash into v5 signature packets. */ struct pt_extra_hash_data_s { @@ -60,10 +64,6 @@ struct pt_extra_hash_data_s typedef struct pt_extra_hash_data_s *pt_extra_hash_data_t; -/* Hack */ -static int recipient_digest_algo; - - /* * Create notations and other stuff. It is assumed that the strings in * STRLIST are already checked to contain only printable data and have @@ -746,8 +746,8 @@ write_plaintext_packet (iobuf_t out, iobuf_t inp, (*r_extrahash)->mode = pt->mode; (*r_extrahash)->timestamp = pt->timestamp; (*r_extrahash)->namelen = pt->namelen; - /* Note that the last byte or NAME won't be initialized - * becuase we don't need it. */ + /* Note that the last byte of NAME won't be initialized + * because we don't need it. */ memcpy ((*r_extrahash)->name, pt->name, pt->namelen); } pt->buf = NULL; From caf4b3fc16e97eb175c46f45f5770d02becb862d Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 14 Mar 2019 11:23:56 +0100 Subject: [PATCH 227/235] gpg: Make rfc4880bis the default. * g10/gpg.c (set_compliance_option, main): Change CO_GNUPG to include rfc4880bis features. (main): Change rfc4880bis warning to a note. -- Note that the default is CO_GNUPG and not CO_OPENPGP. CO_OPENPGP does not include rfc4880bis yet and has a couple of things we don't like, like --allow-non-selfsigned-uids. Signed-off-by: Werner Koch --- g10/gpg.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/g10/gpg.c b/g10/gpg.c index b15c8eaa3..496c32e5d 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -2148,6 +2148,8 @@ static struct gnupg_compliance_option compliance_options[] = static void set_compliance_option (enum cmd_and_opt_values option) { + opt.flags.rfc4880bis = 0; /* Clear becuase it is initially set. */ + switch (option) { case oRFC4880bis: @@ -2194,7 +2196,10 @@ set_compliance_option (enum cmd_and_opt_values option) break; case oPGP7: opt.compliance = CO_PGP7; break; case oPGP8: opt.compliance = CO_PGP8; break; - case oGnuPG: opt.compliance = CO_GNUPG; break; + case oGnuPG: + opt.compliance = CO_GNUPG; + opt.flags.rfc4880bis = 1; + break; case oDE_VS: set_compliance_option (oOpenPGP); @@ -2441,6 +2446,8 @@ main (int argc, char **argv) opt.passphrase_repeat = 1; opt.emit_version = 0; opt.weak_digests = NULL; + opt.compliance = CO_GNUPG; + opt.flags.rfc4880bis = 1; /* Check whether we have a config file on the command line. */ orig_argc = argc; @@ -3730,7 +3737,7 @@ main (int argc, char **argv) log_info(_("WARNING: program may create a core file!\n")); if (opt.flags.rfc4880bis) - log_info ("WARNING: using experimental features from RFC4880bis!\n"); + log_info ("Note: RFC4880bis features are enabled.\n"); else { opt.mimemode = 0; /* This will use text mode instead. */ From bdda31a26bc69b6ee72e964510db113645de76ef Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 14 Mar 2019 14:55:06 +0100 Subject: [PATCH 228/235] kbx: Unify the fingerprint search modes. * kbx/keybox-search-desc.h (KEYDB_SEARCH_MODE_FPR16) (KEYDB_SEARCH_MODE_FPR20, KEYDB_SEARCH_MODE_FPR32): Remove. Switch all users to KEYDB_SEARCH_MODE_FPR along with the fprlen value. -- These search modes were added over time and there has until recently be no incentive to remove the cruft. With the change for v5 keys I finally went over all places and allowed the generic fingerprint mode along with a given length of the fingerprint at all places. Consequently the other modes can now be removed. Signed-off-by: Werner Koch --- common/userids.c | 19 ++++++--- dirmngr/ks-engine-hkp.c | 27 +++++-------- dirmngr/ks-engine-ldap.c | 2 - g10/delkey.c | 5 +-- g10/export.c | 18 --------- g10/getkey.c | 3 -- g10/gpg.c | 3 -- g10/keydb.c | 29 ++++---------- g10/keyedit.c | 31 +++----------- g10/keyid.c | 27 +++---------- g10/keyring.c | 15 ------- g10/keyserver.c | 87 +++++----------------------------------- kbx/keybox-search-desc.h | 3 -- kbx/keybox-search.c | 12 ------ sm/delete.c | 2 - sm/export.c | 2 - sm/keylist.c | 2 - tools/gpg-wks-client.c | 3 +- tools/wks-util.c | 3 +- 19 files changed, 56 insertions(+), 237 deletions(-) diff --git a/common/userids.c b/common/userids.c index 41cf2876c..181b48866 100644 --- a/common/userids.c +++ b/common/userids.c @@ -327,9 +327,10 @@ classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack) } desc->u.fpr[i] = c; } + desc->fprlen = 16; for (; i < 32; i++) desc->u.fpr[i]= 0; - mode = KEYDB_SEARCH_MODE_FPR16; + mode = KEYDB_SEARCH_MODE_FPR; } else if ((hexlength == 40 && (s[hexlength] == 0 @@ -350,9 +351,10 @@ classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack) } desc->u.fpr[i] = c; } + desc->fprlen = 20; for (; i < 32; i++) desc->u.fpr[i]= 0; - mode = KEYDB_SEARCH_MODE_FPR20; + mode = KEYDB_SEARCH_MODE_FPR; } else if ((hexlength == 64 && (s[hexlength] == 0 @@ -373,7 +375,8 @@ classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack) } desc->u.fpr[i] = c; } - mode = KEYDB_SEARCH_MODE_FPR32; + desc->fprlen = 32; + mode = KEYDB_SEARCH_MODE_FPR; } else if (!hexprefix) { @@ -393,7 +396,10 @@ classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack) desc->u.fpr[i] = c; } if (i == 20) - mode = KEYDB_SEARCH_MODE_FPR20; + { + desc->fprlen = 20; + mode = KEYDB_SEARCH_MODE_FPR; + } for (; i < 32; i++) desc->u.fpr[i]= 0; } @@ -438,7 +444,10 @@ classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack) s += 2; } if (i == 20) - mode = KEYDB_SEARCH_MODE_FPR20; + { + desc->fprlen = 20; + mode = KEYDB_SEARCH_MODE_FPR; + } for (; i < 32; i++) desc->u.fpr[i]= 0; } diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c index 149de9425..4d660b87e 100644 --- a/dirmngr/ks-engine-hkp.c +++ b/dirmngr/ks-engine-hkp.c @@ -1437,7 +1437,7 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, { gpg_error_t err; KEYDB_SEARCH_DESC desc; - char fprbuf[2+40+1]; + char fprbuf[2+64+1]; char *hostport = NULL; char *request = NULL; estream_t fp = NULL; @@ -1456,6 +1456,7 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, err = classify_user_id (pattern, &desc, 1); if (err) return err; + log_assert (desc.fprlen <= 64); switch (desc.mode) { case KEYDB_SEARCH_MODE_EXACT: @@ -1473,17 +1474,10 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, (ulong)desc.u.kid[0], (ulong)desc.u.kid[1]); pattern = fprbuf; break; - case KEYDB_SEARCH_MODE_FPR16: - fprbuf[0] = '0'; - fprbuf[1] = 'x'; - bin2hex (desc.u.fpr, 16, fprbuf+2); - pattern = fprbuf; - break; - case KEYDB_SEARCH_MODE_FPR20: case KEYDB_SEARCH_MODE_FPR: fprbuf[0] = '0'; fprbuf[1] = 'x'; - bin2hex (desc.u.fpr, 20, fprbuf+2); + bin2hex (desc.u.fpr, desc.fprlen, fprbuf+2); pattern = fprbuf; break; default: @@ -1586,7 +1580,7 @@ ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp) { gpg_error_t err; KEYDB_SEARCH_DESC desc; - char kidbuf[2+40+1]; + char kidbuf[2+64+1]; const char *exactname = NULL; char *searchkey = NULL; char *hostport = NULL; @@ -1607,6 +1601,7 @@ ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp) err = classify_user_id (keyspec, &desc, 1); if (err) return err; + log_assert (desc.fprlen <= 64); switch (desc.mode) { case KEYDB_SEARCH_MODE_SHORT_KID: @@ -1616,21 +1611,21 @@ ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp) snprintf (kidbuf, sizeof kidbuf, "0x%08lX%08lX", (ulong)desc.u.kid[0], (ulong)desc.u.kid[1]); break; - case KEYDB_SEARCH_MODE_FPR20: case KEYDB_SEARCH_MODE_FPR: - /* This is a v4 fingerprint. */ + if (desc.fprlen < 20) + { + log_error ("HKP keyservers do not support v3 fingerprints\n"); + return gpg_error (GPG_ERR_INV_USER_ID); + } kidbuf[0] = '0'; kidbuf[1] = 'x'; - bin2hex (desc.u.fpr, 20, kidbuf+2); + bin2hex (desc.u.fpr, desc.fprlen, kidbuf+2); break; case KEYDB_SEARCH_MODE_EXACT: exactname = desc.u.name; break; - case KEYDB_SEARCH_MODE_FPR16: - log_error ("HKP keyservers do not support v3 fingerprints\n"); - /* fall through */ default: return gpg_error (GPG_ERR_INV_USER_ID); } diff --git a/dirmngr/ks-engine-ldap.c b/dirmngr/ks-engine-ldap.c index d94bd5e25..d6af26ec4 100644 --- a/dirmngr/ks-engine-ldap.c +++ b/dirmngr/ks-engine-ldap.c @@ -376,8 +376,6 @@ keyspec_to_ldap_filter (const char *keyspec, char **filter, int only_exact) (ulong) desc.u.kid[0], (ulong) desc.u.kid[1]); break; - case KEYDB_SEARCH_MODE_FPR16: - case KEYDB_SEARCH_MODE_FPR20: case KEYDB_SEARCH_MODE_FPR: case KEYDB_SEARCH_MODE_ISSUER: case KEYDB_SEARCH_MODE_ISSUER_SN: diff --git a/g10/delkey.c b/g10/delkey.c index b4d643f59..cc5673846 100644 --- a/g10/delkey.c +++ b/g10/delkey.c @@ -69,10 +69,7 @@ do_delete_key (ctrl_t ctrl, const char *username, int secret, int force, /* Search the userid. */ err = classify_user_id (username, &desc, 1); - exactmatch = (desc.mode == KEYDB_SEARCH_MODE_FPR - || desc.mode == KEYDB_SEARCH_MODE_FPR16 - || desc.mode == KEYDB_SEARCH_MODE_FPR20 - || desc.mode == KEYDB_SEARCH_MODE_FPR32); + exactmatch = (desc.mode == KEYDB_SEARCH_MODE_FPR); if (!err) err = keydb_search (hd, &desc, 1, NULL); if (err) diff --git a/g10/export.c b/g10/export.c index 2d34c8244..4f6c9137e 100644 --- a/g10/export.c +++ b/g10/export.c @@ -451,9 +451,6 @@ exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, KBNODE node) keyid_from_pk (node->pkt->pkt.public_key, kid); break; - case KEYDB_SEARCH_MODE_FPR16: - case KEYDB_SEARCH_MODE_FPR20: - case KEYDB_SEARCH_MODE_FPR32: case KEYDB_SEARCH_MODE_FPR: fingerprint_from_pk (node->pkt->pkt.public_key, fpr, &fprlen); break; @@ -474,21 +471,6 @@ exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, KBNODE node) result = 1; break; - case KEYDB_SEARCH_MODE_FPR16: - if (fprlen == 16 && !memcmp (desc->u.fpr, fpr, 16)) - result = 1; - break; - - case KEYDB_SEARCH_MODE_FPR20: - if (fprlen == 20 && !memcmp (desc->u.fpr, fpr, 20)) - result = 1; - break; - - case KEYDB_SEARCH_MODE_FPR32: - if (fprlen == 32 && !memcmp (desc->u.fpr, fpr, 32)) - result = 1; - break; - case KEYDB_SEARCH_MODE_FPR: if (fprlen == desc->fprlen && !memcmp (desc->u.fpr, fpr, desc->fprlen)) result = 1; diff --git a/g10/getkey.c b/g10/getkey.c index 1467bc89f..9dae879d2 100644 --- a/g10/getkey.c +++ b/g10/getkey.c @@ -897,9 +897,6 @@ key_byname (ctrl_t ctrl, GETKEY_CTX *retctx, strlist_t namelist, if (!include_unusable && ctx->items[n].mode != KEYDB_SEARCH_MODE_SHORT_KID && ctx->items[n].mode != KEYDB_SEARCH_MODE_LONG_KID - && ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR16 - && ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR20 - && ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR32 && ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR) { ctx->items[n].skipfnc = skip_unusable; diff --git a/g10/gpg.c b/g10/gpg.c index 496c32e5d..1ab7b0497 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -5082,9 +5082,6 @@ main (int argc, char **argv) if (! (desc.mode == KEYDB_SEARCH_MODE_SHORT_KID || desc.mode == KEYDB_SEARCH_MODE_LONG_KID - || desc.mode == KEYDB_SEARCH_MODE_FPR16 - || desc.mode == KEYDB_SEARCH_MODE_FPR20 - || desc.mode == KEYDB_SEARCH_MODE_FPR32 || desc.mode == KEYDB_SEARCH_MODE_FPR || desc.mode == KEYDB_SEARCH_MODE_KEYGRIP)) { diff --git a/g10/keydb.c b/g10/keydb.c index 6ecb4eb8b..8c067e1df 100644 --- a/g10/keydb.c +++ b/g10/keydb.c @@ -559,21 +559,9 @@ keydb_search_desc_dump (struct keydb_search_desc *desc) case KEYDB_SEARCH_MODE_LONG_KID: return xasprintf ("LONG_KID: '%s'", format_keyid (desc->u.kid, KF_LONG, b, sizeof (b))); - case KEYDB_SEARCH_MODE_FPR16: - bin2hex (desc->u.fpr, 16, fpr); - return xasprintf ("FPR16: '%s'", - format_hexfingerprint (fpr, b, sizeof (b))); - case KEYDB_SEARCH_MODE_FPR20: - bin2hex (desc->u.fpr, 20, fpr); - return xasprintf ("FPR20: '%s'", - format_hexfingerprint (fpr, b, sizeof (b))); - case KEYDB_SEARCH_MODE_FPR32: - bin2hex (desc->u.fpr, 20, fpr); - return xasprintf ("FPR32: '%s'", - format_hexfingerprint (fpr, b, sizeof (b))); case KEYDB_SEARCH_MODE_FPR: bin2hex (desc->u.fpr, desc->fprlen, fpr); - return xasprintf ("FPR: '%s'", + return xasprintf ("FPR%02d: '%s'", desc->fprlen, format_hexfingerprint (fpr, b, sizeof (b))); case KEYDB_SEARCH_MODE_ISSUER: return xasprintf ("ISSUER: '%s'", desc->u.name); @@ -1534,10 +1522,11 @@ keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb) memset (&desc, 0, sizeof (desc)); fingerprint_from_pk (pk, desc.u.fpr, &len); - if (len == 20) - desc.mode = KEYDB_SEARCH_MODE_FPR20; - else if (len == 32) - desc.mode = KEYDB_SEARCH_MODE_FPR32; + if (len == 20 || len == 32) + { + desc.mode = KEYDB_SEARCH_MODE_FPR; + desc.fprlen = len; + } else log_bug ("%s: Unsupported key length: %zu\n", __func__, len); @@ -1910,11 +1899,7 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, /* NB: If one of the exact search modes below is used in a loop to walk over all keys (with the same fingerprint) the caching must have been disabled for the handle. */ - if (desc[0].mode == KEYDB_SEARCH_MODE_FPR20) - fprlen = 20; - else if (desc[0].mode == KEYDB_SEARCH_MODE_FPR32) - fprlen = 32; - else if (desc[0].mode == KEYDB_SEARCH_MODE_FPR) + if (desc[0].mode == KEYDB_SEARCH_MODE_FPR) fprlen = desc[0].fprlen; else fprlen = 0; diff --git a/g10/keyedit.c b/g10/keyedit.c index ed1fd8a23..34b7679e4 100644 --- a/g10/keyedit.c +++ b/g10/keyedit.c @@ -2564,10 +2564,7 @@ find_by_primary_fpr (ctrl_t ctrl, const char *fpr, *r_kdbhd = NULL; if (classify_user_id (fpr, &desc, 1) - || !(desc.mode == KEYDB_SEARCH_MODE_FPR - || desc.mode == KEYDB_SEARCH_MODE_FPR16 - || desc.mode == KEYDB_SEARCH_MODE_FPR20 - || desc.mode == KEYDB_SEARCH_MODE_FPR32)) + || desc.mode != KEYDB_SEARCH_MODE_FPR) { log_error (_("\"%s\" is not a fingerprint\n"), fpr); err = gpg_error (GPG_ERR_INV_NAME); @@ -2582,25 +2579,9 @@ find_by_primary_fpr (ctrl_t ctrl, const char *fpr, /* Check that the primary fingerprint has been given. */ fingerprint_from_pk (keyblock->pkt->pkt.public_key, fprbin, &fprlen); - if (fprlen == 16 && desc.mode == KEYDB_SEARCH_MODE_FPR16 - && !memcmp (fprbin, desc.u.fpr, 16)) - ; - else if (fprlen == 16 && desc.mode == KEYDB_SEARCH_MODE_FPR - && !memcmp (fprbin, desc.u.fpr, 16) - && !desc.u.fpr[16] - && !desc.u.fpr[17] - && !desc.u.fpr[18] - && !desc.u.fpr[19]) - ; - else if (fprlen == 20 && desc.mode == KEYDB_SEARCH_MODE_FPR20 - && !memcmp (fprbin, desc.u.fpr, 20)) - ; - else if (fprlen == 32 && desc.mode == KEYDB_SEARCH_MODE_FPR32 - && !memcmp (fprbin, desc.u.fpr, 32)) - ; - else if (desc.mode == KEYDB_SEARCH_MODE_FPR - && fprlen == desc.fprlen - && !memcmp (fprbin, desc.u.fpr, fprlen)) + if (desc.mode == KEYDB_SEARCH_MODE_FPR + && fprlen == desc.fprlen + && !memcmp (fprbin, desc.u.fpr, fprlen)) ; else { @@ -2924,9 +2905,7 @@ keyedit_quick_set_expire (ctrl_t ctrl, const char *fpr, const char *expirestr, /* Parse the fingerprint. */ if (classify_user_id (subkeyfprs[idx], &desc, 1) - || !(desc.mode == KEYDB_SEARCH_MODE_FPR - || desc.mode == KEYDB_SEARCH_MODE_FPR20 - || desc.mode == KEYDB_SEARCH_MODE_FPR32)) + || desc.mode != KEYDB_SEARCH_MODE_FPR) { log_error (_("\"%s\" is not a proper fingerprint\n"), subkeyfprs[idx] ); diff --git a/g10/keyid.c b/g10/keyid.c index 92be95944..aa77b47e2 100644 --- a/g10/keyid.c +++ b/g10/keyid.c @@ -493,24 +493,6 @@ keystr_from_desc(KEYDB_SEARCH_DESC *desc) case KEYDB_SEARCH_MODE_SHORT_KID: return keystr(desc->u.kid); - case KEYDB_SEARCH_MODE_FPR20: - { - u32 keyid[2]; - - keyid[0] = buf32_to_u32 (desc->u.fpr+12); - keyid[1] = buf32_to_u32 (desc->u.fpr+16); - return keystr(keyid); - } - - case KEYDB_SEARCH_MODE_FPR32: - { - u32 keyid[2]; - - keyid[0] = buf32_to_u32 (desc->u.fpr); - keyid[1] = buf32_to_u32 (desc->u.fpr+4); - return keystr(keyid); - } - case KEYDB_SEARCH_MODE_FPR: { u32 keyid[2]; @@ -520,17 +502,18 @@ keystr_from_desc(KEYDB_SEARCH_DESC *desc) keyid[0] = buf32_to_u32 (desc->u.fpr); keyid[1] = buf32_to_u32 (desc->u.fpr+4); } - else + else if (desc->fprlen == 20) { keyid[0] = buf32_to_u32 (desc->u.fpr+12); keyid[1] = buf32_to_u32 (desc->u.fpr+16); } + else if (desc->fprlen == 16) + return "?v3 fpr?"; + else /* oops */ + return "?vx fpr?"; return keystr(keyid); } - case KEYDB_SEARCH_MODE_FPR16: - return "?v3 fpr?"; - default: BUG(); } diff --git a/g10/keyring.c b/g10/keyring.c index bc1d06c09..21791a6ac 100644 --- a/g10/keyring.c +++ b/g10/keyring.c @@ -995,9 +995,6 @@ keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc, case KEYDB_SEARCH_MODE_LONG_KID: need_keyid = 1; break; - case KEYDB_SEARCH_MODE_FPR16: - case KEYDB_SEARCH_MODE_FPR20: - case KEYDB_SEARCH_MODE_FPR32: case KEYDB_SEARCH_MODE_FPR: need_fpr = 1; break; @@ -1182,18 +1179,6 @@ keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc, && desc[n].u.kid[1] == aki[1]) goto found; break; - case KEYDB_SEARCH_MODE_FPR16: - if (pk && !memcmp (desc[n].u.fpr, afp, 16)) - goto found; - break; - case KEYDB_SEARCH_MODE_FPR20: - if (pk && !memcmp (desc[n].u.fpr, afp, 20)) - goto found; - break; - case KEYDB_SEARCH_MODE_FPR32: - if (pk && !memcmp (desc[n].u.fpr, afp, 32)) - goto found; - break; case KEYDB_SEARCH_MODE_FPR: if (pk && desc[n].fprlen >= 16 && desc[n].fprlen <= 32 && !memcmp (desc[n].u.fpr, afp, desc[n].fprlen)) diff --git a/g10/keyserver.c b/g10/keyserver.c index c414e2cb1..66900f7a9 100644 --- a/g10/keyserver.c +++ b/g10/keyserver.c @@ -470,7 +470,6 @@ parse_preferred_keyserver(PKT_signature *sig) static void print_keyrec (ctrl_t ctrl, int number,struct keyrec *keyrec) { - int i; iobuf_writebyte(keyrec->uidbuf,0); iobuf_flush_temp(keyrec->uidbuf); @@ -509,34 +508,6 @@ print_keyrec (ctrl_t ctrl, int number,struct keyrec *keyrec) es_printf ("key %s",keystr(keyrec->desc.u.kid)); break; - /* If it gave us a PGP 2.x fingerprint, not much we can do - beyond displaying it. */ - case KEYDB_SEARCH_MODE_FPR16: - es_printf ("key "); - for(i=0;i<16;i++) - es_printf ("%02X",keyrec->desc.u.fpr[i]); - break; - - /* If we get a modern fingerprint, we have the most - flexibility. */ - case KEYDB_SEARCH_MODE_FPR20: - { - u32 kid[2]; - keyid_from_fingerprint (ctrl, keyrec->desc.u.fpr,20,kid); - es_printf("key %s",keystr(kid)); - } - break; - - /* If we get a modern fingerprint, we have the most - flexibility. */ - case KEYDB_SEARCH_MODE_FPR32: - { - u32 kid[2]; - keyid_from_fingerprint (ctrl, keyrec->desc.u.fpr, 32, kid); - es_printf("key %s",keystr(kid)); - } - break; - case KEYDB_SEARCH_MODE_FPR: { u32 kid[2]; @@ -632,9 +603,6 @@ parse_keyrec(char *keystring) err = classify_user_id (tok, &work->desc, 1); if (err || (work->desc.mode != KEYDB_SEARCH_MODE_SHORT_KID && work->desc.mode != KEYDB_SEARCH_MODE_LONG_KID - && work->desc.mode != KEYDB_SEARCH_MODE_FPR16 - && work->desc.mode != KEYDB_SEARCH_MODE_FPR20 - && work->desc.mode != KEYDB_SEARCH_MODE_FPR32 && work->desc.mode != KEYDB_SEARCH_MODE_FPR)) { work->desc.mode=KEYDB_SEARCH_MODE_NONE; @@ -1016,9 +984,6 @@ keyserver_export (ctrl_t ctrl, strlist_t users) err = classify_user_id (users->d, &desc, 1); if (err || (desc.mode != KEYDB_SEARCH_MODE_SHORT_KID && desc.mode != KEYDB_SEARCH_MODE_LONG_KID - && desc.mode != KEYDB_SEARCH_MODE_FPR16 - && desc.mode != KEYDB_SEARCH_MODE_FPR20 - && desc.mode != KEYDB_SEARCH_MODE_FPR32 && desc.mode != KEYDB_SEARCH_MODE_FPR)) { log_error(_("\"%s\" not a key ID: skipping\n"),users->d); @@ -1088,26 +1053,11 @@ keyserver_retrieval_screener (kbnode_t keyblock, void *opaque) /* Compare requested and returned fingerprints if available. */ for (n = 0; n < ndesc; n++) { - if (desc[n].mode == KEYDB_SEARCH_MODE_FPR20) - { - if (fpr_len == 20 && !memcmp (fpr, desc[n].u.fpr, 20)) - return 0; - } - else if (desc[n].mode == KEYDB_SEARCH_MODE_FPR32) - { - if (fpr_len == 32 && !memcmp (fpr, desc[n].u.fpr, 32)) - return 0; - } - else if (desc[n].mode == KEYDB_SEARCH_MODE_FPR) + if (desc[n].mode == KEYDB_SEARCH_MODE_FPR) { if (fpr_len == desc[n].fprlen && !memcmp (fpr, desc[n].u.fpr, 32)) return 0; } - else if (desc[n].mode == KEYDB_SEARCH_MODE_FPR16) - { - if (fpr_len == 16 && !memcmp (fpr, desc[n].u.fpr, 16)) - return 0; - } else if (desc[n].mode == KEYDB_SEARCH_MODE_LONG_KID) { if (keyid[0] == desc[n].u.kid[0] && keyid[1] == desc[n].u.kid[1]) @@ -1143,9 +1093,6 @@ keyserver_import (ctrl_t ctrl, strlist_t users) err = classify_user_id (users->d, &desc[count], 1); if (err || (desc[count].mode != KEYDB_SEARCH_MODE_SHORT_KID && desc[count].mode != KEYDB_SEARCH_MODE_LONG_KID - && desc[count].mode != KEYDB_SEARCH_MODE_FPR16 - && desc[count].mode != KEYDB_SEARCH_MODE_FPR20 - && desc[count].mode != KEYDB_SEARCH_MODE_FPR32 && desc[count].mode != KEYDB_SEARCH_MODE_FPR)) { log_error (_("\"%s\" not a key ID: skipping\n"), users->d); @@ -1202,12 +1149,8 @@ keyserver_import_fprint (ctrl_t ctrl, const byte *fprint,size_t fprint_len, memset(&desc,0,sizeof(desc)); - if(fprint_len==16) - desc.mode=KEYDB_SEARCH_MODE_FPR16; - else if(fprint_len==20) - desc.mode=KEYDB_SEARCH_MODE_FPR20; - else if(fprint_len==32) - desc.mode=KEYDB_SEARCH_MODE_FPR32; + if (fprint_len == 16 || fprint_len == 20 || fprint_len == 32) + desc.mode = KEYDB_SEARCH_MODE_FPR; else return -1; @@ -1337,14 +1280,12 @@ keyidlist (ctrl_t ctrl, strlist_t users, KEYDB_SEARCH_DESC **klist, } else { - size_t dummy; + size_t fprlen; - if (node->pkt->pkt.public_key->version == 4) - (*klist)[*count].mode = KEYDB_SEARCH_MODE_FPR20; - else - (*klist)[*count].mode = KEYDB_SEARCH_MODE_FPR32; fingerprint_from_pk (node->pkt->pkt.public_key, - (*klist)[*count].u.fpr,&dummy); + (*klist)[*count].u.fpr, &fprlen); + (*klist)[*count].mode = KEYDB_SEARCH_MODE_FPR; + (*klist)[*count].fprlen = fprlen; } /* This is a little hackish, using the skipfncvalue as a @@ -1661,11 +1602,9 @@ keyserver_get_chunk (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, int ndesc, { int quiet = 0; - if (desc[idx].mode == KEYDB_SEARCH_MODE_FPR20 - || desc[idx].mode == KEYDB_SEARCH_MODE_FPR32 - || desc[idx].mode == KEYDB_SEARCH_MODE_FPR16) + if (desc[idx].mode == KEYDB_SEARCH_MODE_FPR) { - n = 1+2+2*32; + n = 1+2+2*desc[idx].fprlen; if (idx && linelen + n > MAX_KS_GET_LINELEN) break; /* Declare end of this chunk. */ linelen += n; @@ -1676,13 +1615,9 @@ keyserver_get_chunk (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, int ndesc, else { strcpy (pattern[npat], "0x"); - bin2hex (desc[idx].u.fpr, - desc[idx].mode == KEYDB_SEARCH_MODE_FPR32? 32 : - desc[idx].mode == KEYDB_SEARCH_MODE_FPR20? 20 : 16, - pattern[npat]+2); + bin2hex (desc[idx].u.fpr, desc[idx].fprlen, pattern[npat]+2); npat++; - if (desc[idx].mode == KEYDB_SEARCH_MODE_FPR20 - || desc[idx].mode == KEYDB_SEARCH_MODE_FPR32) + if (desc[idx].fprlen == 20 || desc[idx].fprlen == 32) npat_fpr++; } } diff --git a/kbx/keybox-search-desc.h b/kbx/keybox-search-desc.h index cde1cf56f..7286d2ae3 100644 --- a/kbx/keybox-search-desc.h +++ b/kbx/keybox-search-desc.h @@ -36,9 +36,6 @@ typedef enum { KEYDB_SEARCH_MODE_WORDS, KEYDB_SEARCH_MODE_SHORT_KID, KEYDB_SEARCH_MODE_LONG_KID, - KEYDB_SEARCH_MODE_FPR16, - KEYDB_SEARCH_MODE_FPR20, - KEYDB_SEARCH_MODE_FPR32, KEYDB_SEARCH_MODE_FPR, /* (Length of fpr in .fprlen) */ KEYDB_SEARCH_MODE_ISSUER, KEYDB_SEARCH_MODE_ISSUER_SN, diff --git a/kbx/keybox-search.c b/kbx/keybox-search.c index db98648f4..101e1b5ea 100644 --- a/kbx/keybox-search.c +++ b/kbx/keybox-search.c @@ -1083,18 +1083,6 @@ keybox_search (KEYBOX_HANDLE hd, KEYBOX_SEARCH_DESC *desc, size_t ndesc, goto found; break; - case KEYDB_SEARCH_MODE_FPR20: - pk_no = has_fingerprint (blob, desc[n].u.fpr, 20); - if (pk_no) - goto found; - break; - - case KEYDB_SEARCH_MODE_FPR32: - pk_no = has_fingerprint (blob, desc[n].u.fpr, 32); - if (pk_no) - goto found; - break; - case KEYDB_SEARCH_MODE_KEYGRIP: if (has_keygrip (blob, desc[n].u.grip)) goto found; diff --git a/sm/delete.c b/sm/delete.c index 56d5b1f8c..f359cc595 100644 --- a/sm/delete.c +++ b/sm/delete.c @@ -64,8 +64,6 @@ delete_one (ctrl_t ctrl, const char *username) /* If the key is specified in a unique way, include ephemeral keys in the search. */ if ( desc.mode == KEYDB_SEARCH_MODE_FPR - || desc.mode == KEYDB_SEARCH_MODE_FPR20 - || desc.mode == KEYDB_SEARCH_MODE_FPR16 || desc.mode == KEYDB_SEARCH_MODE_KEYGRIP ) { is_ephem = 1; diff --git a/sm/export.c b/sm/export.c index 7bea9ccc5..918096e7c 100644 --- a/sm/export.c +++ b/sm/export.c @@ -198,8 +198,6 @@ gpgsm_export (ctrl_t ctrl, strlist_t names, estream_t stream) { for (i=0; (i < ndesc && (desc[i].mode == KEYDB_SEARCH_MODE_FPR - || desc[i].mode == KEYDB_SEARCH_MODE_FPR20 - || desc[i].mode == KEYDB_SEARCH_MODE_FPR16 || desc[i].mode == KEYDB_SEARCH_MODE_KEYGRIP)); i++) ; if (i == ndesc) diff --git a/sm/keylist.c b/sm/keylist.c index 3e377dbb5..e242310be 100644 --- a/sm/keylist.c +++ b/sm/keylist.c @@ -1477,8 +1477,6 @@ list_internal_keys (ctrl_t ctrl, strlist_t names, estream_t fp, for (i=0; (i < ndesc && (desc[i].mode == KEYDB_SEARCH_MODE_FPR - || desc[i].mode == KEYDB_SEARCH_MODE_FPR20 - || desc[i].mode == KEYDB_SEARCH_MODE_FPR16 || desc[i].mode == KEYDB_SEARCH_MODE_KEYGRIP)); i++) ; if (i == ndesc) diff --git a/tools/gpg-wks-client.c b/tools/gpg-wks-client.c index e59d52d36..0f08737c4 100644 --- a/tools/gpg-wks-client.c +++ b/tools/gpg-wks-client.c @@ -853,8 +853,7 @@ command_send (const char *fingerprint, const char *userid) time_t thistime; if (classify_user_id (fingerprint, &desc, 1) - || !(desc.mode == KEYDB_SEARCH_MODE_FPR - || desc.mode == KEYDB_SEARCH_MODE_FPR20)) + || desc.mode != KEYDB_SEARCH_MODE_FPR) { log_error (_("\"%s\" is not a fingerprint\n"), fingerprint); err = gpg_error (GPG_ERR_INV_NAME); diff --git a/tools/wks-util.c b/tools/wks-util.c index 1935d264a..1459045ef 100644 --- a/tools/wks-util.c +++ b/tools/wks-util.c @@ -946,8 +946,7 @@ wks_cmd_install_key (const char *fname, const char *userid) } if (!classify_user_id (fname, &desc, 1) - && (desc.mode == KEYDB_SEARCH_MODE_FPR - || desc.mode == KEYDB_SEARCH_MODE_FPR20)) + && desc.mode == KEYDB_SEARCH_MODE_FPR) { /* FNAME looks like a fingerprint. Get the key from the * standard keyring. */ From 3e1f3df6183b2ed2cadf2af2383063891e2c53bd Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 15 Mar 2019 08:55:06 +0100 Subject: [PATCH 229/235] gpg: Fix recently introduced use after free. * g10/mainproc.c (proc_plaintext): Do not use freed memory. -- GnuPG-bug-id: 4407 Signed-off-by: Werner Koch --- g10/mainproc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/g10/mainproc.c b/g10/mainproc.c index 6fa30e0d4..7acf67b1e 100644 --- a/g10/mainproc.c +++ b/g10/mainproc.c @@ -946,9 +946,6 @@ proc_plaintext( CTX c, PACKET *pkt ) if (rc) log_error ("handle plaintext failed: %s\n", gpg_strerror (rc)); - free_packet (pkt, NULL); - c->last_was_session_key = 0; - /* We add a marker control packet instead of the plaintext packet. * This is so that we can later detect invalid packet sequences. * The apcket is further used to convey extra data from the @@ -974,6 +971,9 @@ proc_plaintext( CTX c, PACKET *pkt ) extrahash[extrahashlen++] = pt->timestamp ; } + free_packet (pkt, NULL); + c->last_was_session_key = 0; + n = new_kbnode (create_gpg_control (CTRLPKT_PLAINTEXT_MARK, extrahash, extrahashlen)); xfree (extrahash); From f06b6fe47f56a15ac426665c3d9661d4b104696f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 15 Mar 2019 13:02:44 +0100 Subject: [PATCH 230/235] gpg: Simplify an interactive import status line. * g10/cpr.c (write_status_printf): Escape CR and LF. * g10/import.c (print_import_check): Simplify by using write_status_printf and hexfingerprint. Signed-off-by: Werner Koch --- g10/cpr.c | 32 ++++++++++++++++++++++++++++---- g10/import.c | 19 +++++-------------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/g10/cpr.c b/g10/cpr.c index ed68b3f15..3d39d6bda 100644 --- a/g10/cpr.c +++ b/g10/cpr.c @@ -142,7 +142,8 @@ write_status ( int no ) /* Write a status line with code NO followed by the string TEXT and - directly followed by the remaining strings up to a NULL. */ + * directly followed by the remaining strings up to a NULL. Embedded + * CR and LFs in the strings (but not in TEXT) are C-style escaped.*/ void write_status_strings (int no, const char *text, ...) { @@ -188,12 +189,12 @@ write_status_text (int no, const char *text) /* Write a status line with code NO followed by the output of the - * printf style FORMAT. The caller needs to make sure that LFs and - * CRs are not printed. */ + * printf style FORMAT. Embedded CR and LFs are C-style escaped. */ void write_status_printf (int no, const char *format, ...) { va_list arg_ptr; + char *buf; if (!statusfp || !status_currently_allowed (no) ) return; /* Not enabled or allowed. */ @@ -204,7 +205,30 @@ write_status_printf (int no, const char *format, ...) { es_putc ( ' ', statusfp); va_start (arg_ptr, format); - es_vfprintf (statusfp, format, arg_ptr); + buf = gpgrt_vbsprintf (format, arg_ptr); + if (!buf) + log_error ("error printing status line: %s\n", + gpg_strerror (gpg_err_code_from_syserror ())); + else + { + if (strpbrk (buf, "\r\n")) + { + const byte *s; + for (s=buf; *s; s++) + { + if (*s == '\n') + es_fputs ("\\n", statusfp); + else if (*s == '\r') + es_fputs ("\\r", statusfp); + else + es_fputc (*s, statusfp); + } + } + else + es_fputs (buf, statusfp); + gpgrt_free (buf); + } + va_end (arg_ptr); } es_putc ('\n', statusfp); diff --git a/g10/import.c b/g10/import.c index 25ccc2fc6..25c9a6632 100644 --- a/g10/import.c +++ b/g10/import.c @@ -1084,23 +1084,14 @@ print_import_ok (PKT_public_key *pk, unsigned int reason) static void print_import_check (PKT_public_key * pk, PKT_user_id * id) { - char * buf; - byte fpr[24]; + byte hexfpr[2*MAX_FINGERPRINT_LEN+1]; u32 keyid[2]; - size_t i, n; - size_t pos = 0; - buf = xmalloc (17+41+id->len+32); keyid_from_pk (pk, keyid); - sprintf (buf, "%08X%08X ", keyid[0], keyid[1]); - pos = 17; - fingerprint_from_pk (pk, fpr, &n); - for (i = 0; i < n; i++, pos += 2) - sprintf (buf+pos, "%02X", fpr[i]); - strcat (buf, " "); - strcat (buf, id->name); - write_status_text (STATUS_IMPORT_CHECK, buf); - xfree (buf); + hexfingerprint (pk, hexfpr, sizeof hexfpr); + write_status_printf (STATUS_IMPORT_CHECK, "%08X%08X %s %s", + keyid[0], keyid[1], hexfpr, id->name); + } From f64477db86568bdc28c313bfeb8b95d8edf05a3c Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 15 Mar 2019 19:11:32 +0100 Subject: [PATCH 231/235] gpg: During secret key import print "sec" instead of "pub". * g10/keyedit.c (show_basic_key_info): New arg 'print_sec'. Remove useless code for "sub" and "ssb". * g10/import.c (import_one): Pass FROM_SK to show_basic_key_info. Do not print the first keyinfo in FROM_SK mode. printing. -- Signed-off-by: Werner Koch --- g10/gpgcompose.c | 5 +++-- g10/import.c | 11 +++++++---- g10/keyedit.c | 23 ++++++++++++++--------- g10/keyedit.h | 2 +- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/g10/gpgcompose.c b/g10/gpgcompose.c index 5c0857590..e882fa8e3 100644 --- a/g10/gpgcompose.c +++ b/g10/gpgcompose.c @@ -3075,10 +3075,11 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr, } void -show_basic_key_info (ctrl_t ctrl, KBNODE keyblock) +show_basic_key_info (ctrl_t ctrl, KBNODE keyblock, int made_from_sec) { (void)ctrl; - (void) keyblock; + (void)keyblock; + (void)made_from_sec; } int diff --git a/g10/import.c b/g10/import.c index 25c9a6632..359a14e20 100644 --- a/g10/import.c +++ b/g10/import.c @@ -1666,7 +1666,8 @@ update_key_origin (kbnode_t keyblock, u32 curtime, int origin, const char *url) * the internal errorcount, so that invalid input can be detected by * programs which called gpg. If SILENT is no messages are printed - * even most error messages are suppressed. ORIGIN is the origin of - * the key (0 for unknown) and URL the corresponding URL. + * the key (0 for unknown) and URL the corresponding URL. FROM_SK + * indicates that the key has been made from a secret key. */ static gpg_error_t import_one (ctrl_t ctrl, @@ -1710,9 +1711,11 @@ import_one (ctrl_t ctrl, keyid_from_pk( pk, keyid ); uidnode = find_next_kbnode( keyblock, PKT_USER_ID ); - if (opt.verbose && !opt.interactive && !silent) + if (opt.verbose && !opt.interactive && !silent && !from_sk) { - log_info( "pub %s/%s %s ", + /* Note that we do not print this info in FROM_SK mode + * because import_one already printed that. */ + log_info ("pub %s/%s %s ", pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr_from_pk(pk), datestr_from_pk(pk) ); if (uidnode) @@ -1745,7 +1748,7 @@ import_one (ctrl_t ctrl, print_import_check (pk, uidnode->pkt->pkt.user_id); merge_keys_and_selfsig (ctrl, keyblock); tty_printf ("\n"); - show_basic_key_info (ctrl, keyblock); + show_basic_key_info (ctrl, keyblock, from_sk); tty_printf ("\n"); if (!cpr_get_answer_is_yes ("import.okay", "Do you want to import this key? (y/N) ")) diff --git a/g10/keyedit.c b/g10/keyedit.c index 34b7679e4..4ec6ce503 100644 --- a/g10/keyedit.c +++ b/g10/keyedit.c @@ -3677,13 +3677,14 @@ show_key_with_all_names (ctrl_t ctrl, estream_t fp, /* Display basic key information. This function is suitable to show - information on the key without any dependencies on the trustdb or - any other internal GnuPG stuff. KEYBLOCK may either be a public or - a secret key. This function may be called with KEYBLOCK containing - secret keys and thus the printing of "pub" vs. "sec" does only - depend on the packet type and not by checking with gpg-agent. */ + * information on the key without any dependencies on the trustdb or + * any other internal GnuPG stuff. KEYBLOCK may either be a public or + * a secret key. This function may be called with KEYBLOCK containing + * secret keys and thus the printing of "pub" vs. "sec" does only + * depend on the packet type and not by checking with gpg-agent. If + * PRINT_SEC ist set "sec" is printed instead of "pub". */ void -show_basic_key_info (ctrl_t ctrl, kbnode_t keyblock) +show_basic_key_info (ctrl_t ctrl, kbnode_t keyblock, int print_sec) { KBNODE node; int i; @@ -3696,13 +3697,17 @@ show_basic_key_info (ctrl_t ctrl, kbnode_t keyblock) || node->pkt->pkttype == PKT_SECRET_KEY) { PKT_public_key *pk = node->pkt->pkt.public_key; + const char *tag; + + if (node->pkt->pkttype == PKT_SECRET_KEY || print_sec) + tag = "sec"; + else + tag = "pub"; /* Note, we use the same format string as in other show functions to make the translation job easier. */ tty_printf ("%s %s/%s ", - node->pkt->pkttype == PKT_PUBLIC_KEY ? "pub" : - node->pkt->pkttype == PKT_PUBLIC_SUBKEY ? "sub" : - node->pkt->pkttype == PKT_SECRET_KEY ? "sec" :"ssb", + tag, pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr_from_pk (pk)); tty_printf (_("created: %s"), datestr_from_pk (pk)); diff --git a/g10/keyedit.h b/g10/keyedit.h index d1f453a6f..af5e99664 100644 --- a/g10/keyedit.h +++ b/g10/keyedit.h @@ -50,7 +50,7 @@ void keyedit_quick_set_expire (ctrl_t ctrl, char **subkeyfprs); void keyedit_quick_set_primary (ctrl_t ctrl, const char *username, const char *primaryuid); -void show_basic_key_info (ctrl_t ctrl, kbnode_t keyblock); +void show_basic_key_info (ctrl_t ctrl, kbnode_t keyblock, int print_sec); int keyedit_print_one_sig (ctrl_t ctrl, estream_t fp, int rc, kbnode_t keyblock, kbnode_t node, int *inv_sigs, int *no_key, From 8c20a363c221438373439cde8c242e04c1bd925e Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 15 Mar 2019 19:40:02 +0100 Subject: [PATCH 232/235] tests: Add sample secret key w/o binding signatures. -- GnuPG-bug-id: 4392 --- tests/openpgp/samplekeys/README | 10 ++++ tests/openpgp/samplekeys/pgp-desktop-skr.asc | 56 ++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 tests/openpgp/samplekeys/pgp-desktop-skr.asc diff --git a/tests/openpgp/samplekeys/README b/tests/openpgp/samplekeys/README index 6f2399fd9..9f1648bdf 100644 --- a/tests/openpgp/samplekeys/README +++ b/tests/openpgp/samplekeys/README @@ -14,8 +14,18 @@ whats-new-in-2.1.asc Collection of sample keys. e2e-p256-1-clr.asc Google End-end-End test key (no protection) e2e-p256-1-prt.asc Ditto, but protected with passphrase "a". E657FB607BB4F21C90BB6651BC067AF28BC90111.asc Key with subkeys (no protection) +pgp-desktop-skr.asc Secret key with subkeys w/o signatures rsa-rsa-sample-1.asc RSA+RSA sample key (no passphrase) ed25519-cv25519-sample-1.asc Ed25519+CV25519 sample key (no passphrase) silent-running.asc Collection of sample secret keys (no passphrases) rsa-primary-auth-only.pub.asc rsa2408 primary only, usage: cert,auth rsa-primary-auth-only.sec.asc Ditto but the secret keyblock. + + +Notes: + +- pgp-desktop-skr.asc is a secret keyblock without the uid and subkey + binding signatures. When exporting a secret key from PGP desktop + such a file is created which is then directly followed by a separate + armored public key block. To create such a sample concatenate + pgp-desktop-skr.asc and E657FB607BB4F21C90BB6651BC067AF28BC90111.asc diff --git a/tests/openpgp/samplekeys/pgp-desktop-skr.asc b/tests/openpgp/samplekeys/pgp-desktop-skr.asc new file mode 100644 index 000000000..58f384caf --- /dev/null +++ b/tests/openpgp/samplekeys/pgp-desktop-skr.asc @@ -0,0 +1,56 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: Made up as if from PGP Encryption Desktop 10.3.2 + +lQHYBFZfWcgBBAC+RQIbTFhpMiRmJPB3XAJQXxpDb5h2sEaNJ/MLIHwPNs+jNgDb +144BrIOD1G56xAYhKYVDphFIg2wCiB019mYq7yNUyn/aZFBHqd5xbg4qR212cAjw +HpBqP9yUEm333RFqFdytcbXd9rSfvZOlFvGZRSxjgpGlsJGbjitH0ABY+wARAQAB +AAP+IvI/yc3C60dXYh9kvzd6AVMGWt5zTVFhE+oDfMaxooW5q0tu6vHzViFeYmcx +B4FbctnSbTNiN0RUIT7oxpGEAAumKRejGAaMwiKZz3bMV05l0LI0Yn10GzXsLtRx ++iKzpUxThZETRU43BJeMqP5/rVqdQAu47pClgTwQWn6bXNkCANe2+XwJgMv9D72p +kMLIi0TmPtfjBFV6f3f190N6m5gCCwstzvKqcNQ7NqNdbLHo/HKCmdGzlzCajbNu +1nLJYoUCAOHNiNhWU/IEC5fRNyxfE5AQAmc7Bm/7d5gVIWDUjWe1ukfwJGQESyNy +GTraOcYQa8X0GskSEktjwZN/dM9yZX8B/02prLprc1+8GjTM7q7ePJJbiOWcvYrB +qcHhqadU/uC/g4lzDAG4RVutIHaqqOPr85J9jPzP/AT6ygsNU5Q5902gYbQjQmFy +cmV0dCBCcm93biA8YmFycmV0dEBleGFtcGxlLm9yZz6dAdgEVl9ZyAEEAMfR5EvR +HsEQXjKwf+LvMD2qXZerKRJYv+Ok6O1nJgYZrxGSXRtGUGrWDb4JERKjmnbIHePa +J42GgpAUibaya0lDkvjKOehX/+dno06Bcn7mbOistFBpvbbyhCcN2mYhjQGeT8r6 +fiX/sSw8L49MRxwI/JRBITkqyKxr6uMsf/p1ABEBAAEAA/wN9hFQZs0SSjV6rzBQ +R8wEEvo1FaVp/b9yhVws8i8K8BJ2VNaiiDgoLsqJA5MozTuGnxbPi7eFwOcwb+7r +T+4E8c8cJlOFiWkYtUyFDAjjo1m0xxFI0GnWuEnl238URxIW+x4k6Bx7g8P/3psH +f5x1ue8pxYzudxEuPTBV8HMp4QIA2p74/ZJafVJAIDcEcbMDoIhTpRgbMyeHaQmR +81gwo2FHd3hlonspwJ37r3LRk2jMgecU+0cK7p7W4HkYD6Xo2QIA6fv/DFn2WVRA +ODQVQQGGxsvO2cM847IFJu96BbbxOLaZJ536RE980c2a9q/9B4hOYzKV4B4NI03u +5/BqoOY8/QH/ZIvWN1fksXhQMypVTLg8R81igqS3GXKmQ+KrVEfTIHnXKxH7tyfD +eJSS6nfpfARhAe2mP3TIrbjX+9PR+Qmkg6GqnQHYBFZfWusBBACoJjGH5zSYYpWQ +1EuJJ7X2tJs6AtUlwvp0fUSdrA7qSXLKkhusOibsM01OWntMyXBD5SwpuZPyPCRT +Tz9rCDpb1arksIAFRK1itVzAkmV/eniUGu7QFJGVoq4iyWmTk+jB+PaU9dfqjV5E +eyfGT0VMP4wZxaSF8v0cX5Gry89yJwARAQABAAP+NPUmd199hJrT8TOzgIRlvkfe +dZRLziNM3yBO2nvEjMxKH3uJxKHh/VUg/VLo72On/HIyiQeeDVYcuLJGTm7edegk +/9C85hT5K4VUF9+LXXDX1Vz/jQdZxq+JwUE/AdlAEC9fkFQzc0ftI832mgjROASw +MVphqYUQERz00ve+NDUCAMmgeUzmQB+ZDcdCzKQfZChafEDqZNpqIKfhcg8SytcK +LA5uLBYGPcj7DY5NZuh3PFaV3EGxpjJUIzdspHp6V0UCANV+jbkookz+pUHAKp6D +wt+yxOj1HqKIRdOYVaEaLTpvv7CHL3u2a4FQbxCxK6umVPH1HglEKDHNs7UBB8gv +tHsB/Rzk2o5+LyWT18v6ubDVoUO6WQx3iXJakorJrSML7gld8DAEDCFK/jlk5Rhz +gmGvBZwZ+z2xOk3rxnQxBPAkHHyb8p0B2ARWX1uVAQQA5Hj2C3jzD8OGtLaw7+P2 +gYdAfR4s4YS3/AK+kYYtbm5EX4srysyUbylbQDQXUvRzw4FPkjXbboF6KjHw8icN +WHCazwSfPTfCDvi0JIildkfNqwBzCmNDRn++X8rvAeDCEJ/BtfcgfgmDTElSJOd+ +3B4XwnmtnBW54KlR42PLobsAEQEAAQAD+gJL6SGioplwMH9xtZtZ5fixAynaOeYK +LK8vF06EGpL3Xl8lHqwpKZU0tbmsfLJjkFL6yD2L750Ge0vcLj5YtxVh+pfzvtgo +HbYvfcU0j3iUQXgrn9r792wILv9LcgfDGYEUTPY+TSQnhju6OA8EYFJC1l9vkeae +HWiNi0VH5leBAgDr3h6mXHrLT4qSNexzz8BAvK/PlHMAMAJcy76lXSkl92+c4Bcp +jFDr5Vpaq/VXoLC4L/IlnEEqY967pAycdLH7AgD3+UB/qEnh0dDq7HkEfwMiarY6 +Nb6wre0jPN/p+lWQ+MO6o5iJ2b44vZIUIlrKZJZ1WraBBhuRx1Fd0YpUlYNBAf41 +4cEsS5z9Vf6HeJ87WPIyWH72dmwcuRDNTKLYeetcnbbhyO+BzfXbC+0FAxTIsBFa +4S4xUwDBah+Nf4ZlcvPSop+dAdgEVl9unAEEAOnl30hwc47rLL9QH6g0TX1BEPdW +MV4Ou6+rQOErIMAr1AOlUzpjwJllvQqf2OHnQWaTr9kbNLn7XUEUhjkH3uHDYMHM +dyAb7YJrk3ECDqnmr34VV/F/H5BH7D6AiFktl1SpUTczPxBxvPNlJ4joPmTm+ahf +g+zL+4pVu6tIhM0LABEBAAEAA/9fHMTxVhkHswZdPZ3B7pLcLktR6NDmaKNVyhP1 +/G2y95+dY+s2QT4eosp+uYWeR0XHCqNla7TDND41qrzyEAtHiAF3OoydMK4lb0lq +fKORRI4tr017wgMxRBLs82Gk5ehtI7AwSca7WvaoAJwKZp42th4MOeykeGRRMagJ +I420QQIA8zdj89HUQ9tIRyhenoqgGWGmYZgO6SlrloxwlVzvbOsxn59A7PpE0CZb +TsVPwFCwEzf3316k7V0oqa8TVL8J2wIA9jEY4AFhxY6kmffl5KiKwHThC06BPk6k +CX90tt5on5iH0q2tjrAt/+ZfTcWAT5huQh9OZ4Hq0N/hFhtcJjIokQH+OcGoGiG3 +pNBeU0bZqnVZNcHhJP9F13chv5jSAOJf6rfyx3HbgTeOqh2BCpyocgzAgQ8JUkX+ +OeRRvDotcfiTGKBc +=VlTT +-----END PGP PRIVATE KEY BLOCK----- From f799e9728bcadb3d4148a47848c78c5647860ea4 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 15 Mar 2019 19:50:37 +0100 Subject: [PATCH 233/235] gpg: Avoid importing secret keys if the keyblock is not valid. * g10/keydb.h (struct kbnode_struct): Replace unused field RECNO by new field TAG. * g10/kbnode.c (alloc_node): Change accordingly. * g10/import.c (import_one): Add arg r_valid. (sec_to_pub_keyblock): Set tags. (resync_sec_with_pub_keyblock): New. (import_secret_one): Change return code to gpg_error_t. Return an error code if sec_to_pub_keyblock failed. Resync secret keyblock. -- When importing an invalid secret key ring for example without key binding signatures or no UIDs, gpg used to let gpg-agent store the secret keys anyway. This is clearly a bug because the diagnostics before claimed that for example the subkeys have been skipped. Importing the secret key parameters then anyway is surprising in particular because a gpg -k does not show the key. After importing the public key the secret keys suddenly showed up. This changes the behaviour of GnuPG-bug-id: 4392 to me more consistent but is not a solution to the actual bug. Caution: The ecc.scm test now fails because two of the sample keys don't have binding signatures. Signed-off-by: Werner Koch --- g10/import.c | 122 ++++++++++++++++++++++++++------ g10/kbnode.c | 2 +- g10/keydb.h | 13 ++-- g10/pubkey-enc.c | 2 +- tests/openpgp/ecc.scm | 2 +- tests/openpgp/samplekeys/README | 2 + 6 files changed, 112 insertions(+), 31 deletions(-) diff --git a/g10/import.c b/g10/import.c index 359a14e20..155792d5a 100644 --- a/g10/import.c +++ b/g10/import.c @@ -109,8 +109,8 @@ static gpg_error_t import_one (ctrl_t ctrl, unsigned char **fpr, size_t *fpr_len, unsigned int options, int from_sk, int silent, import_screener_t screener, void *screener_arg, - int origin, const char *url); -static int import_secret_one (ctrl_t ctrl, kbnode_t keyblock, + int origin, const char *url, int *r_valid); +static gpg_error_t import_secret_one (ctrl_t ctrl, kbnode_t keyblock, struct import_stats_s *stats, int batch, unsigned int options, int for_migration, import_screener_t screener, void *screener_arg); @@ -588,7 +588,7 @@ import (ctrl_t ctrl, IOBUF inp, const char* fname,struct import_stats_s *stats, if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY) rc = import_one (ctrl, keyblock, stats, fpr, fpr_len, options, 0, 0, - screener, screener_arg, origin, url); + screener, screener_arg, origin, url, NULL); else if (keyblock->pkt->pkttype == PKT_SECRET_KEY) rc = import_secret_one (ctrl, keyblock, stats, opt.batch, options, 0, @@ -1667,7 +1667,9 @@ update_key_origin (kbnode_t keyblock, u32 curtime, int origin, const char *url) * programs which called gpg. If SILENT is no messages are printed - * even most error messages are suppressed. ORIGIN is the origin of * the key (0 for unknown) and URL the corresponding URL. FROM_SK - * indicates that the key has been made from a secret key. + * indicates that the key has been made from a secret key. If R_SAVED + * is not NULL a boolean will be stored indicating whether the keyblock + * has valid parts. */ static gpg_error_t import_one (ctrl_t ctrl, @@ -1675,7 +1677,7 @@ import_one (ctrl_t ctrl, unsigned char **fpr, size_t *fpr_len, unsigned int options, int from_sk, int silent, import_screener_t screener, void *screener_arg, - int origin, const char *url) + int origin, const char *url, int *r_valid) { gpg_error_t err = 0; PKT_public_key *pk; @@ -1694,6 +1696,9 @@ import_one (ctrl_t ctrl, int any_filter = 0; KEYDB_HANDLE hd = NULL; + if (r_valid) + *r_valid = 0; + /* If show-only is active we don't won't any extra output. */ if ((options & (IMPORT_SHOW | IMPORT_DRY_RUN))) silent = 1; @@ -1714,7 +1719,7 @@ import_one (ctrl_t ctrl, if (opt.verbose && !opt.interactive && !silent && !from_sk) { /* Note that we do not print this info in FROM_SK mode - * because import_one already printed that. */ + * because import_secret_one already printed that. */ log_info ("pub %s/%s %s ", pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr_from_pk(pk), datestr_from_pk(pk) ); @@ -1849,6 +1854,10 @@ import_one (ctrl_t ctrl, return 0; } + /* The keyblock is valid and ready for real import. */ + if (r_valid) + *r_valid = 1; + /* Show the key in the form it is merged or inserted. We skip this * if "import-export" is also active without --armor or the output * file has explicily been given. */ @@ -2463,14 +2472,21 @@ transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats, /* Walk a secret keyblock and produce a public keyblock out of it. - Returns a new node or NULL on error. */ + * Returns a new node or NULL on error. Modifies the tag field of the + * nodes. */ static kbnode_t sec_to_pub_keyblock (kbnode_t sec_keyblock) { kbnode_t pub_keyblock = NULL; kbnode_t ctx = NULL; kbnode_t secnode, pubnode; + unsigned int tag = 0; + /* Set a tag to all nodes. */ + for (secnode = sec_keyblock; secnode; secnode = secnode->next) + secnode->tag = ++tag; + + /* Copy. */ while ((secnode = walk_kbnode (sec_keyblock, &ctx, 0))) { if (secnode->pkt->pkttype == PKT_SECRET_KEY @@ -2500,6 +2516,7 @@ sec_to_pub_keyblock (kbnode_t sec_keyblock) { pubnode = clone_kbnode (secnode); } + pubnode->tag = secnode->tag; if (!pub_keyblock) pub_keyblock = pubnode; @@ -2510,23 +2527,67 @@ sec_to_pub_keyblock (kbnode_t sec_keyblock) return pub_keyblock; } + +/* Delete all notes in the keyblock at R_KEYBLOCK which are not in + * PUB_KEYBLOCK. Modifies the tags of both keyblock's nodes. */ +static gpg_error_t +resync_sec_with_pub_keyblock (kbnode_t *r_keyblock, kbnode_t pub_keyblock) +{ + kbnode_t sec_keyblock = *r_keyblock; + kbnode_t node; + unsigned int *taglist; + unsigned int ntaglist, n; + + /* Collect all tags in an array for faster searching. */ + for (ntaglist = 0, node = pub_keyblock; node; node = node->next) + ntaglist++; + taglist = xtrycalloc (ntaglist, sizeof *taglist); + if (!taglist) + return gpg_error_from_syserror (); + for (ntaglist = 0, node = pub_keyblock; node; node = node->next) + taglist[ntaglist++] = node->tag; + + /* Walks over the secret keyblock and delete all nodes which are not + * in the tag list. Those nodes have been delete in the + * pub_keyblock. Sequential search is a bit lazt and could be + * optimized by sorting and bsearch; however secret key rings are + * short and there are easier weaus to DoS gpg. */ + for (node = sec_keyblock; node; node = node->next) + { + for (n=0; n < ntaglist; n++) + if (taglist[n] == node->tag) + break; + if (n == ntaglist) + delete_kbnode (node); + } + + xfree (taglist); + + /* Commit the as deleted marked nodes and return the possibly + * modified keyblock. */ + commit_kbnode (&sec_keyblock); + *r_keyblock = sec_keyblock; + return 0; +} + + /**************** * Ditto for secret keys. Handling is simpler than for public keys. * We allow secret key importing only when allow is true, this is so * that a secret key can not be imported accidentally and thereby tampering * with the trust calculation. */ -static int +static gpg_error_t import_secret_one (ctrl_t ctrl, kbnode_t keyblock, - struct import_stats_s *stats, int batch, unsigned int options, - int for_migration, + struct import_stats_s *stats, int batch, + unsigned int options, int for_migration, import_screener_t screener, void *screener_arg) { PKT_public_key *pk; struct seckey_info *ski; kbnode_t node, uidnode; u32 keyid[2]; - int rc = 0; + gpg_error_t err = 0; int nr_prev; kbnode_t pub_keyblock; char pkstrbuf[PUBKEY_STRING_SIZE]; @@ -2550,7 +2611,7 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, if (opt.verbose && !for_migration) { - log_info ("sec %s/%s %s ", + log_info ("sec %s/%s %s ", pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr_from_pk (pk), datestr_from_pk (pk)); if (uidnode) @@ -2610,20 +2671,35 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, /* Make a public key out of the key. */ pub_keyblock = sec_to_pub_keyblock (keyblock); if (!pub_keyblock) - log_error ("key %s: failed to create public key from secret key\n", - keystr_from_pk (pk)); + { + err = gpg_error_from_syserror (); + log_error ("key %s: failed to create public key from secret key\n", + keystr_from_pk (pk)); + } else { + int valid; + /* Note that this outputs an IMPORT_OK status message for the public key block, and below we will output another one for the secret keys. FIXME? */ import_one (ctrl, pub_keyblock, stats, NULL, NULL, options, 1, for_migration, - screener, screener_arg, 0, NULL); + screener, screener_arg, 0, NULL, &valid); - /* Fixme: We should check for an invalid keyblock and - cancel the secret key import in this case. */ - release_kbnode (pub_keyblock); + /* The secret keyblock may not have nodes which are deleted in + * the public keyblock. Otherwise we would import just the + * secret key without having the public key. That would be + * surprising and clutters out private-keys-v1.d. */ + err = resync_sec_with_pub_keyblock (&keyblock, pub_keyblock); + if (err) + goto leave; + + if (!valid) + { + err = gpg_error (GPG_ERR_NO_SECKEY); + goto leave; + } /* At least we cancel the secret key import when the public key import was skipped due to MERGE_ONLY option and a new @@ -2631,7 +2707,8 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, if (!(opt.dry_run || (options & IMPORT_DRY_RUN)) && stats->skipped_new_keys <= nr_prev) { - /* Read the keyblock again to get the effects of a merge. */ + /* Read the keyblock again to get the effects of a merge for + * the public key. */ /* Fixme: we should do this based on the fingerprint or even better let import_one return the merged keyblock. */ @@ -2641,8 +2718,6 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, keystr_from_pk (pk)); else { - gpg_error_t err; - /* transfer_secret_keys collects subkey stats. */ struct import_stats_s subkey_stats = {0}; @@ -2680,6 +2755,7 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, if (is_status_enabled ()) print_import_ok (pk, status); + check_prefs (ctrl, node); } release_kbnode (node); @@ -2687,7 +2763,9 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, } } - return rc; + leave: + release_kbnode (pub_keyblock); + return err; } diff --git a/g10/kbnode.c b/g10/kbnode.c index 64def0509..93035e8f6 100644 --- a/g10/kbnode.c +++ b/g10/kbnode.c @@ -68,8 +68,8 @@ alloc_node (void) n->next = NULL; n->pkt = NULL; n->flag = 0; + n->tag = 0; n->private_flag=0; - n->recno = 0; return n; } diff --git a/g10/keydb.h b/g10/keydb.h index c52856d7f..7cdfe9bbf 100644 --- a/g10/keydb.h +++ b/g10/keydb.h @@ -52,12 +52,13 @@ typedef struct getkey_ctx_s *getkey_ctx_t; * This structure is also used to bind arbitrary packets together. */ -struct kbnode_struct { - KBNODE next; - PACKET *pkt; - int flag; - int private_flag; - ulong recno; /* used while updating the trustdb */ +struct kbnode_struct +{ + kbnode_t next; + PACKET *pkt; + int flag; /* Local use during keyblock processing (not cloned).*/ + unsigned int tag; /* Ditto. */ + int private_flag; }; #define is_deleted_kbnode(a) ((a)->private_flag & 1) diff --git a/g10/pubkey-enc.c b/g10/pubkey-enc.c index e0a6e8ae1..055c39b8f 100644 --- a/g10/pubkey-enc.c +++ b/g10/pubkey-enc.c @@ -117,7 +117,7 @@ get_session_key (ctrl_t ctrl, struct pubkey_enc_list *list, DEK *dek) * - On-card keys of an active card * - On-disk keys with protection * - On-card keys from cards which are not plugged it. Here a - * cancel-all button should stop aksing for other cards. + * cancel-all button should stop asking for other cards. * Without any anonymous keys the sorting can be skipped. */ for (k = list; k; k = k->next) diff --git a/tests/openpgp/ecc.scm b/tests/openpgp/ecc.scm index d7c02a5e2..a63ec45bd 100755 --- a/tests/openpgp/ecc.scm +++ b/tests/openpgp/ecc.scm @@ -175,7 +175,7 @@ Rg== (display "This is one line\n" (fdopen fd "wb"))) (for-each-p - "Checking ECDSA decryption" + "Checking ECDH decryption" (lambda (test) (lettmp (x y) (call-with-output-file diff --git a/tests/openpgp/samplekeys/README b/tests/openpgp/samplekeys/README index 9f1648bdf..f8a7e9ed7 100644 --- a/tests/openpgp/samplekeys/README +++ b/tests/openpgp/samplekeys/README @@ -29,3 +29,5 @@ Notes: such a file is created which is then directly followed by a separate armored public key block. To create such a sample concatenate pgp-desktop-skr.asc and E657FB607BB4F21C90BB6651BC067AF28BC90111.asc +- ecc-sample-2-sec.asc and ecc-sample-3-sec.asc do not have and + binding signatures either. ecc-sample-1-sec.asc has them, though. From 5205512fc092c53c0a52c8379ef2a129ce6e58a9 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 18 Mar 2019 13:07:14 +0100 Subject: [PATCH 234/235] gpg: Allow import of PGP desktop exported secret keys. * g10/import.c (NODE_TRANSFER_SECKEY): New. (import): Add attic kludge. (transfer_secret_keys): Add arg only_marked. (resync_sec_with_pub_keyblock): Return removed seckeys via new arg r_removedsecs. (import_secret_one): New arg r_secattic. Change to take ownership of arg keyblock. Implement extra secret key import logic. Factor some code out to ... (do_transfer): New. (import_matching_seckeys): New. -- The PGP desktops exported secret keys are really stupid. And they even a have kind of exception in rfc4880 which does not rule that out (section 11.2): [...] Implementations SHOULD include self-signatures on any user IDs and subkeys, as this allows for a complete public key to be automatically extracted from the transferable secret key. Implementations MAY choose to omit the self-signatures, especially if a transferable public key accompanies the transferable secret key. Now if they would only put the public key before the secret key. Anyway we now have a workaround for that ugliness. GnuPG-bug-id: 4392 Signed-off-by: Werner Koch --- g10/import.c | 385 +++++++++++++++++++++++++++++++++++++++----------- g10/keyedit.c | 2 +- g10/main.h | 3 +- 3 files changed, 309 insertions(+), 81 deletions(-) diff --git a/g10/import.c b/g10/import.c index 155792d5a..c2a1dd033 100644 --- a/g10/import.c +++ b/g10/import.c @@ -1,6 +1,6 @@ /* import.c - import a key into our key storage. * Copyright (C) 1998-2007, 2010-2011 Free Software Foundation, Inc. - * Copyright (C) 2014, 2016, 2017 Werner Koch + * Copyright (C) 2014, 2016, 2017, 2019 Werner Koch * * This file is part of GnuPG. * @@ -75,6 +75,8 @@ struct import_stats_s #define NODE_DELETION_MARK 4 /* A node flag used to temporary mark a node. */ #define NODE_FLAG_A 8 +/* A flag used by transfer_secret_keys. */ +#define NODE_TRANSFER_SECKEY 16 /* An object and a global instance to store selectors created from @@ -110,10 +112,15 @@ static gpg_error_t import_one (ctrl_t ctrl, unsigned int options, int from_sk, int silent, import_screener_t screener, void *screener_arg, int origin, const char *url, int *r_valid); +static gpg_error_t import_matching_seckeys ( + ctrl_t ctrl, kbnode_t seckeys, + const byte *mainfpr, size_t mainfprlen, + struct import_stats_s *stats, int batch); static gpg_error_t import_secret_one (ctrl_t ctrl, kbnode_t keyblock, struct import_stats_s *stats, int batch, unsigned int options, int for_migration, - import_screener_t screener, void *screener_arg); + import_screener_t screener, void *screener_arg, + kbnode_t *r_secattic); static int import_revoke_cert (ctrl_t ctrl, kbnode_t node, unsigned int options, struct import_stats_s *stats); static int chk_self_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, @@ -566,6 +573,7 @@ import (ctrl_t ctrl, IOBUF inp, const char* fname,struct import_stats_s *stats, kbnode_t keyblock = NULL; /* Need to initialize because gcc can't grasp the return semantics of read_block. */ + kbnode_t secattic = NULL; /* Kludge for PGP desktop percularity */ int rc = 0; int v3keys; @@ -586,18 +594,63 @@ import (ctrl_t ctrl, IOBUF inp, const char* fname,struct import_stats_s *stats, { stats->v3keys += v3keys; if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY) - rc = import_one (ctrl, keyblock, - stats, fpr, fpr_len, options, 0, 0, - screener, screener_arg, origin, url, NULL); + { + rc = import_one (ctrl, keyblock, + stats, fpr, fpr_len, options, 0, 0, + screener, screener_arg, origin, url, NULL); + if (secattic) + { + byte tmpfpr[MAX_FINGERPRINT_LEN]; + size_t tmpfprlen; + + if (!rc && !(opt.dry_run || (options & IMPORT_DRY_RUN))) + { + /* Kudge for PGP desktop - see below. */ + fingerprint_from_pk (keyblock->pkt->pkt.public_key, + tmpfpr, &tmpfprlen); + rc = import_matching_seckeys (ctrl, secattic, + tmpfpr, tmpfprlen, + stats, opt.batch); + } + release_kbnode (secattic); + secattic = NULL; + } + } else if (keyblock->pkt->pkttype == PKT_SECRET_KEY) - rc = import_secret_one (ctrl, keyblock, stats, - opt.batch, options, 0, - screener, screener_arg); + { + release_kbnode (secattic); + secattic = NULL; + rc = import_secret_one (ctrl, keyblock, stats, + opt.batch, options, 0, + screener, screener_arg, &secattic); + keyblock = NULL; /* Ownership was transferred. */ + if (secattic) + { + if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY) + rc = 0; /* Try import after the next pubkey. */ + + /* The attic is a workaround for the peculiar PGP + * Desktop method of exporting a secret key: The + * exported file is the concatenation of two armored + * keyblocks; first the private one and then the public + * one. The strange thing is that the secret one has no + * binding signatures at all and thus we have not + * imported it. The attic stores that secret keys and + * we try to import it once after the very next public + * keyblock. */ + } + } else if (keyblock->pkt->pkttype == PKT_SIGNATURE && IS_KEY_REV (keyblock->pkt->pkt.signature) ) - rc = import_revoke_cert (ctrl, keyblock, options, stats); + { + release_kbnode (secattic); + secattic = NULL; + rc = import_revoke_cert (ctrl, keyblock, options, stats); + } else { + release_kbnode (secattic); + secattic = NULL; log_info (_("skipping block of type %d\n"), keyblock->pkt->pkttype); } release_kbnode (keyblock); @@ -623,6 +676,7 @@ import (ctrl_t ctrl, IOBUF inp, const char* fname,struct import_stats_s *stats, else if (rc && gpg_err_code (rc) != GPG_ERR_INV_KEYRING) log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (rc)); + release_kbnode (secattic); return rc; } @@ -659,8 +713,11 @@ import_old_secring (ctrl_t ctrl, const char *fname) while (!(err = read_block (inp, 0, &pending_pkt, &keyblock, &v3keys))) { if (keyblock->pkt->pkttype == PKT_SECRET_KEY) - err = import_secret_one (ctrl, keyblock, stats, 1, 0, 1, - NULL, NULL); + { + err = import_secret_one (ctrl, keyblock, stats, 1, 0, 1, + NULL, NULL, NULL); + keyblock = NULL; /* Ownership was transferred. */ + } release_kbnode (keyblock); if (err) break; @@ -2181,12 +2238,15 @@ import_one (ctrl_t ctrl, /* Transfer all the secret keys in SEC_KEYBLOCK to the gpg-agent. The - function prints diagnostics and returns an error code. If BATCH is - true the secret keys are stored by gpg-agent in the transfer format - (i.e. no re-protection and aksing for passphrases). */ + * function prints diagnostics and returns an error code. If BATCH is + * true the secret keys are stored by gpg-agent in the transfer format + * (i.e. no re-protection and aksing for passphrases). If ONLY_MARKED + * is set, only those nodes with flag NODE_TRANSFER_SECKEY are + * processed. */ gpg_error_t transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats, - kbnode_t sec_keyblock, int batch, int force) + kbnode_t sec_keyblock, int batch, int force, + int only_marked) { gpg_error_t err = 0; void *kek = NULL; @@ -2227,12 +2287,16 @@ transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats, xfree (kek); kek = NULL; + /* Note: We need to use walk_kbnode so that we skip nodes which are + * marked as deleted. */ main_pk = NULL; while ((node = walk_kbnode (sec_keyblock, &ctx, 0))) { if (node->pkt->pkttype != PKT_SECRET_KEY && node->pkt->pkttype != PKT_SECRET_SUBKEY) continue; + if (only_marked && !(node->flag & NODE_TRANSFER_SECKEY)) + continue; pk = node->pkt->pkt.public_key; if (!main_pk) main_pk = pk; @@ -2531,12 +2595,15 @@ sec_to_pub_keyblock (kbnode_t sec_keyblock) /* Delete all notes in the keyblock at R_KEYBLOCK which are not in * PUB_KEYBLOCK. Modifies the tags of both keyblock's nodes. */ static gpg_error_t -resync_sec_with_pub_keyblock (kbnode_t *r_keyblock, kbnode_t pub_keyblock) +resync_sec_with_pub_keyblock (kbnode_t *r_keyblock, kbnode_t pub_keyblock, + kbnode_t *r_removedsecs) { kbnode_t sec_keyblock = *r_keyblock; - kbnode_t node; + kbnode_t node, prevnode; unsigned int *taglist; unsigned int ntaglist, n; + kbnode_t attic = NULL; + kbnode_t *attic_head = &attic; /* Collect all tags in an array for faster searching. */ for (ntaglist = 0, node = pub_keyblock; node; node = node->next) @@ -2548,40 +2615,188 @@ resync_sec_with_pub_keyblock (kbnode_t *r_keyblock, kbnode_t pub_keyblock) taglist[ntaglist++] = node->tag; /* Walks over the secret keyblock and delete all nodes which are not - * in the tag list. Those nodes have been delete in the - * pub_keyblock. Sequential search is a bit lazt and could be - * optimized by sorting and bsearch; however secret key rings are - * short and there are easier weaus to DoS gpg. */ - for (node = sec_keyblock; node; node = node->next) + * in the tag list. Those nodes have been deleted in the + * pub_keyblock. Sequential search is a bit lazy and could be + * optimized by sorting and bsearch; however secret keyrings are + * short and there are easier ways to DoS the import. */ + again: + for (prevnode=NULL, node=sec_keyblock; node; prevnode=node, node=node->next) { for (n=0; n < ntaglist; n++) if (taglist[n] == node->tag) break; - if (n == ntaglist) - delete_kbnode (node); + if (n == ntaglist) /* Not in public keyblock. */ + { + if (node->pkt->pkttype == PKT_SECRET_KEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY) + { + if (!prevnode) + sec_keyblock = node->next; + else + prevnode->next = node->next; + node->next = NULL; + *attic_head = node; + attic_head = &node->next; + goto again; /* That's lame; I know. */ + } + else + delete_kbnode (node); + } } xfree (taglist); /* Commit the as deleted marked nodes and return the possibly - * modified keyblock. */ + * modified keyblock and a list of removed secret key nodes. */ commit_kbnode (&sec_keyblock); *r_keyblock = sec_keyblock; + *r_removedsecs = attic; return 0; } -/**************** - * Ditto for secret keys. Handling is simpler than for public keys. - * We allow secret key importing only when allow is true, this is so - * that a secret key can not be imported accidentally and thereby tampering - * with the trust calculation. +/* Helper for import_secret_one. */ +static gpg_error_t +do_transfer (ctrl_t ctrl, kbnode_t keyblock, PKT_public_key *pk, + struct import_stats_s *stats, int batch, int only_marked) + +{ + gpg_error_t err; + struct import_stats_s subkey_stats = {0}; + + err = transfer_secret_keys (ctrl, &subkey_stats, keyblock, + batch, 0, only_marked); + if (gpg_err_code (err) == GPG_ERR_NOT_PROCESSED) + { + /* TRANSLATORS: For a smartcard, each private key on host has a + * reference (stub) to a smartcard and actual private key data + * is stored on the card. A single smartcard can have up to + * three private key data. Importing private key stub is always + * skipped in 2.1, and it returns GPG_ERR_NOT_PROCESSED. + * Instead, user should be suggested to run 'gpg --card-status', + * then, references to a card will be automatically created + * again. */ + log_info (_("To migrate '%s', with each smartcard, " + "run: %s\n"), "secring.gpg", "gpg --card-status"); + err = 0; + } + + if (!err) + { + int status = 16; + + if (!opt.quiet) + log_info (_("key %s: secret key imported\n"), keystr_from_pk (pk)); + if (subkey_stats.secret_imported) + { + status |= 1; + stats->secret_imported += 1; + } + if (subkey_stats.secret_dups) + stats->secret_dups += 1; + + if (is_status_enabled ()) + print_import_ok (pk, status); + } + + return err; +} + + +/* If the secret keys (main or subkey) in SECKEYS have a corresponding + * public key in the public key described by (FPR,FPRLEN) import these + * parts. + */ +static gpg_error_t +import_matching_seckeys (ctrl_t ctrl, kbnode_t seckeys, + const byte *mainfpr, size_t mainfprlen, + struct import_stats_s *stats, int batch) +{ + gpg_error_t err; + kbnode_t pub_keyblock = NULL; + kbnode_t node; + struct { byte fpr[MAX_FINGERPRINT_LEN]; size_t fprlen; } *fprlist = NULL; + size_t n, nfprlist; + byte fpr[MAX_FINGERPRINT_LEN]; + size_t fprlen; + PKT_public_key *pk; + + /* Get the entire public key block from our keystore and put all its + * fingerprints into an array. */ + err = get_pubkey_byfprint (ctrl, NULL, &pub_keyblock, mainfpr, mainfprlen); + if (err) + goto leave; + log_assert (pub_keyblock && pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY); + pk = pub_keyblock->pkt->pkt.public_key; + + for (nfprlist = 0, node = pub_keyblock; node; node = node->next) + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + nfprlist++; + log_assert (nfprlist); + fprlist = xtrycalloc (nfprlist, sizeof *fprlist); + if (!fprlist) + { + err = gpg_error_from_syserror (); + goto leave; + } + for (n = 0, node = pub_keyblock; node; node = node->next) + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + fingerprint_from_pk (node->pkt->pkt.public_key, + fprlist[n].fpr, &fprlist[n].fprlen); + n++; + } + log_assert (n == nfprlist); + + /* for (n=0; n < nfprlist; n++) */ + /* log_printhex (fprlist[n].fpr, fprlist[n].fprlen, "pubkey %zu:", n); */ + + /* Mark all secret keys which have a matching public key part in + * PUB_KEYBLOCK. */ + for (node = seckeys; node; node = node->next) + { + if (node->pkt->pkttype != PKT_SECRET_KEY + && node->pkt->pkttype != PKT_SECRET_SUBKEY) + continue; /* Should not happen. */ + fingerprint_from_pk (node->pkt->pkt.public_key, fpr, &fprlen); + node->flag &= ~NODE_TRANSFER_SECKEY; + for (n=0; n < nfprlist; n++) + if (fprlist[n].fprlen == fprlen && !memcmp (fprlist[n].fpr,fpr,fprlen)) + { + node->flag |= NODE_TRANSFER_SECKEY; + /* log_debug ("found matching seckey\n"); */ + break; + } + } + + /* Transfer all marked keys. */ + err = do_transfer (ctrl, seckeys, pk, stats, batch, 1); + + leave: + xfree (fprlist); + release_kbnode (pub_keyblock); + return err; +} + + +/* Import function for a single secret keyblock. Handling is simpler + * than for public keys. We allow secret key importing only when + * allow is true, this is so that a secret key can not be imported + * accidentally and thereby tampering with the trust calculation. + * + * Ownership of KEYBLOCK is transferred to this function! + * + * If R_SECATTIC is not null the last special sec_keyblock is stored + * there. */ static gpg_error_t import_secret_one (ctrl_t ctrl, kbnode_t keyblock, struct import_stats_s *stats, int batch, unsigned int options, int for_migration, - import_screener_t screener, void *screener_arg) + import_screener_t screener, void *screener_arg, + kbnode_t *r_secattic) { PKT_public_key *pk; struct seckey_info *ski; @@ -2590,6 +2805,9 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, gpg_error_t err = 0; int nr_prev; kbnode_t pub_keyblock; + kbnode_t attic = NULL; + byte fpr[MAX_FINGERPRINT_LEN]; + size_t fprlen; char pkstrbuf[PUBKEY_STRING_SIZE]; /* Get the key and print some info about it */ @@ -2599,6 +2817,7 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, pk = node->pkt->pkt.public_key; + fingerprint_from_pk (pk, fpr, &fprlen); keyid_from_pk (pk, keyid); uidnode = find_next_kbnode (keyblock, PKT_USER_ID); @@ -2606,6 +2825,7 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, { log_error (_("secret key %s: %s\n"), keystr_from_pk (pk), _("rejected by import screener")); + release_kbnode (keyblock); return 0; } @@ -2625,6 +2845,7 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, { if (!for_migration) log_error (_("importing secret keys not allowed\n")); + release_kbnode (keyblock); return 0; } @@ -2632,6 +2853,7 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, { if (!for_migration) log_error( _("key %s: no user ID\n"), keystr_from_pk (pk)); + release_kbnode (keyblock); return 0; } @@ -2640,6 +2862,7 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, { /* Actually an internal error. */ log_error ("key %s: secret key info missing\n", keystr_from_pk (pk)); + release_kbnode (keyblock); return 0; } @@ -2650,6 +2873,7 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, if (!for_migration) log_error (_("key %s: secret key with invalid cipher %d" " - skipped\n"), keystr_from_pk (pk), ski->algo); + release_kbnode (keyblock); return 0; } @@ -2660,6 +2884,7 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, to put a secret key into the keyring and the user might later be tricked into signing stuff with that key. */ log_error (_("importing secret keys not allowed\n")); + release_kbnode (keyblock); return 0; } #endif @@ -2691,16 +2916,43 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, * the public keyblock. Otherwise we would import just the * secret key without having the public key. That would be * surprising and clutters out private-keys-v1.d. */ - err = resync_sec_with_pub_keyblock (&keyblock, pub_keyblock); + err = resync_sec_with_pub_keyblock (&keyblock, pub_keyblock, &attic); if (err) goto leave; if (!valid) { - err = gpg_error (GPG_ERR_NO_SECKEY); + /* If the block was not valid the primary key is left in the + * original keyblock because we require that for the first + * node. Move it to ATTIC. */ + if (keyblock && keyblock->pkt->pkttype == PKT_SECRET_KEY) + { + node = keyblock; + keyblock = node->next; + node->next = NULL; + if (attic) + { + node->next = attic; + attic = node; + } + else + attic = node; + } + + /* Try to import the secret key iff we have a public key. */ + if (attic && !(opt.dry_run || (options & IMPORT_DRY_RUN))) + err = import_matching_seckeys (ctrl, attic, fpr, fprlen, + stats, batch); + else + err = gpg_error (GPG_ERR_NO_SECKEY); goto leave; } + /* log_debug ("attic is:\n"); */ + /* dump_kbnode (attic); */ + + /* Proceed with the valid parts of PUBKEYBLOCK. */ + /* At least we cancel the secret key import when the public key import was skipped due to MERGE_ONLY option and a new key. */ @@ -2709,62 +2961,37 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, { /* Read the keyblock again to get the effects of a merge for * the public key. */ - /* Fixme: we should do this based on the fingerprint or - even better let import_one return the merged - keyblock. */ - node = get_pubkeyblock (ctrl, keyid); - if (!node) - log_error ("key %s: failed to re-lookup public key\n", - keystr_from_pk (pk)); + err = get_pubkey_byfprint (ctrl, NULL, &node, fpr, fprlen); + if (err || !node) + log_error ("key %s: failed to re-lookup public key: %s\n", + keystr_from_pk (pk), gpg_strerror (err)); else { - /* transfer_secret_keys collects subkey stats. */ - struct import_stats_s subkey_stats = {0}; - - err = transfer_secret_keys (ctrl, &subkey_stats, keyblock, - batch, 0); - if (gpg_err_code (err) == GPG_ERR_NOT_PROCESSED) - { - /* TRANSLATORS: For smartcard, each private key on - host has a reference (stub) to a smartcard and - actual private key data is stored on the card. A - single smartcard can have up to three private key - data. Importing private key stub is always - skipped in 2.1, and it returns - GPG_ERR_NOT_PROCESSED. Instead, user should be - suggested to run 'gpg --card-status', then, - references to a card will be automatically - created again. */ - log_info (_("To migrate '%s', with each smartcard, " - "run: %s\n"), "secring.gpg", "gpg --card-status"); - err = 0; - } + err = do_transfer (ctrl, keyblock, pk, stats, batch, 0); if (!err) - { - int status = 16; - if (!opt.quiet) - log_info (_("key %s: secret key imported\n"), - keystr_from_pk (pk)); - if (subkey_stats.secret_imported) - { - status |= 1; - stats->secret_imported += 1; - } - if (subkey_stats.secret_dups) - stats->secret_dups += 1; - - if (is_status_enabled ()) - print_import_ok (pk, status); - - check_prefs (ctrl, node); - } + check_prefs (ctrl, node); release_kbnode (node); + + if (!err && attic) + { + /* Try to import invalid subkeys. This can be the + * case if the primary secret key was imported due + * to --allow-non-selfsigned-uid. */ + err = import_matching_seckeys (ctrl, attic, fpr, fprlen, + stats, batch); + } + } } } leave: + release_kbnode (keyblock); release_kbnode (pub_keyblock); + if (r_secattic) + *r_secattic = attic; + else + release_kbnode (attic); return err; } diff --git a/g10/keyedit.c b/g10/keyedit.c index 4ec6ce503..c28a565b1 100644 --- a/g10/keyedit.c +++ b/g10/keyedit.c @@ -1894,7 +1894,7 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr, node = new_kbnode (pkt); /* Transfer it to gpg-agent which handles secret keys. */ - err = transfer_secret_keys (ctrl, NULL, node, 1, 1); + err = transfer_secret_keys (ctrl, NULL, node, 1, 1, 0); /* Treat the pkt as a public key. */ pkt->pkttype = PKT_PUBLIC_KEY; diff --git a/g10/main.h b/g10/main.h index 867f6975b..34a932b16 100644 --- a/g10/main.h +++ b/g10/main.h @@ -394,7 +394,8 @@ struct impex_filter_parm_s const char *impex_filter_getval (void *cookie, const char *propname); gpg_error_t transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats, - kbnode_t sec_keyblock, int batch, int force); + kbnode_t sec_keyblock, int batch, int force, + int only_marked); int collapse_uids( KBNODE *keyblock ); From b98799ce964df1743478dc3d0cc503f51c4b6733 Mon Sep 17 00:00:00 2001 From: Andre Heinecke Date: Mon, 18 Mar 2019 16:08:23 +0100 Subject: [PATCH 235/235] speedo: Fix installer build with NSIS-3 * build-aux/speedo.mk: Add charset for nsis 3. -- NSIS-3 defaults to UTF-8 but for NSIS-2 compatibility we still stay on CP1252 for now. --- build-aux/speedo.mk | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build-aux/speedo.mk b/build-aux/speedo.mk index d0f97f3db..569940099 100644 --- a/build-aux/speedo.mk +++ b/build-aux/speedo.mk @@ -1212,7 +1212,9 @@ extra_installer_options += -DWITH_GUI=1 endif installer: all w32_insthelpers $(w32src)/inst-options.ini $(bdir)/README.txt - $(MAKENSIS) -V2 \ + (nsis3_args=$$(makensis -version | grep -q "^v3" && \ + echo "-INPUTCHARSET CP1252"); \ + $(MAKENSIS) -V2 $$nsis3_args \ -DINST_DIR=$(idir) \ -DINST6_DIR=$(idir6) \ -DBUILD_DIR=$(bdir) \ @@ -1223,7 +1225,7 @@ installer: all w32_insthelpers $(w32src)/inst-options.ini $(bdir)/README.txt -DNAME=$(INST_NAME) \ -DVERSION=$(INST_VERSION) \ -DPROD_VERSION=$(INST_PROD_VERSION) \ - $(extra_installer_options) $(w32src)/inst.nsi + $(extra_installer_options) $(w32src)/inst.nsi) @echo "Ready: $(idir)/$(INST_NAME)-$(INST_VERSION)_$(BUILD_DATESTR).exe"