/* findkey.c - Locate the secret key
* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007,
* 2010, 2011 Free Software Foundation, Inc.
- * Copyright (C) 2014 Werner Koch
+ * Copyright (C) 2014, 2019 Werner Koch
*
* This file is part of GnuPG.
*
* 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 <http://www.gnu.org/licenses/>.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
-#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
-#include <assert.h>
#include <npth.h> /* (we use pth_sleep) */
#include "agent.h"
-#include "i18n.h"
+#include "../common/i18n.h"
#include "../common/ssh-utils.h"
-#include "../common/private-keys.h"
#ifndef O_BINARY
#define O_BINARY 0
};
+/* Replace all linefeeds in STRING by "%0A" and return a new malloced
+ * string. May return NULL on memory error. */
+static char *
+linefeed_to_percent0A (const char *string)
+{
+ const char *s;
+ size_t n;
+ char *buf, *p;
+
+ for (n=0, s=string; *s; s++)
+ if (*s == '\n')
+ n += 3;
+ else
+ n++;
+ p = buf = xtrymalloc (n+1);
+ if (!buf)
+ return NULL;
+ for (s=string; *s; s++)
+ if (*s == '\n')
+ {
+ memcpy (p, "%0A", 3);
+ p += 3;
+ }
+ else
+ *p++ = *s;
+ *p = 0;
+ return buf;
+}
+
+
+/* Note: Ownership of FNAME and FP are moved to this function. */
static gpg_error_t
-write_extended_private_key (char *fname, estream_t fp,
- const void *buf, size_t len)
+write_extended_private_key (char *fname, estream_t fp, int update, int newkey,
+ const void *buf, size_t len,
+ const char *serialno, const char *keyref,
+ time_t timestamp)
{
gpg_error_t err;
- pkc_t pk = NULL;
+ nvc_t pk = NULL;
gcry_sexp_t key = NULL;
int remove = 0;
- int line;
+ char *token = NULL;
- err = pkc_parse (&pk, &line, fp);
- if (err)
+ if (update)
{
- log_error ("error parsing '%s' line %d: %s\n",
- fname, line, gpg_strerror (err));
- goto leave;
+ int line;
+
+ err = nvc_parse_private_key (&pk, &line, fp);
+ if (err && gpg_err_code (err) != GPG_ERR_ENOENT)
+ {
+ log_error ("error parsing '%s' line %d: %s\n",
+ fname, line, gpg_strerror (err));
+ goto leave;
+ }
}
+ else
+ {
+ pk = nvc_new_private_key ();
+ if (!pk)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ }
+ es_clearerr (fp);
err = gcry_sexp_sscan (&key, NULL, buf, len);
if (err)
goto leave;
- err = pkc_set_private_key (pk, key);
+ err = nvc_set_private_key (pk, key);
if (err)
goto leave;
+ /* If requested write a Token line. */
+ if (serialno && keyref)
+ {
+ nve_t item;
+ const char *s;
+
+ token = strconcat (serialno, " ", keyref, NULL);
+ if (!token)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ /* fixme: the strcmp should compare only the first two strings. */
+ for (item = nvc_lookup (pk, "Token:");
+ item;
+ item = nve_next_value (item, "Token:"))
+ if ((s = nve_value (item)) && !strcmp (s, token))
+ break;
+ if (!item)
+ {
+ /* No token or no token with that value exists. Add a new
+ * one so that keys which have been stored on several cards
+ * are well supported. */
+ err = nvc_add (pk, "Token:", token);
+ if (err)
+ goto leave;
+ }
+ }
+
+ /* If a timestamp has been supplied and the key is new write a
+ * creation timestamp. (We douple check that there is no Created
+ * item yet.)*/
+ if (timestamp && newkey && !nvc_lookup (pk, "Created:"))
+ {
+ gnupg_isotime_t timebuf;
+
+ epoch2isotime (timebuf, timestamp);
+ err = nvc_add (pk, "Created:", timebuf);
+ if (err)
+ goto leave;
+ }
+
+
err = es_fseek (fp, 0, SEEK_SET);
if (err)
goto leave;
- err = pkc_write (pk, fp);
+ err = nvc_write (pk, fp);
+ if (!err)
+ err = es_fflush (fp);
if (err)
{
log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
bump_key_eventcounter ();
leave:
- if (fp)
- es_fclose (fp);
+ es_fclose (fp);
if (remove)
gnupg_remove (fname);
xfree (fname);
gcry_sexp_release (key);
- pkc_release (pk);
+ nvc_release (pk);
+ xfree (token);
return err;
}
/* Write an S-expression formatted key to our key storage. With FORCE
- passed as true an existing key with the given GRIP will get
- overwritten. */
+ * passed as true an existing key with the given GRIP will get
+ * overwritten. If SERIALNO and KEYREF are given a Token line is
+ * added to the key if the extended format is used. If TIMESTAMP is
+ * not zero and the key doies not yet exists it will be recorded as
+ * creation date. */
int
agent_write_private_key (const unsigned char *grip,
- const void *buffer, size_t length, int force)
+ const void *buffer, size_t length, int force,
+ const char *serialno, const char *keyref,
+ time_t timestamp)
{
char *fname;
estream_t fp;
/* FIXME: Write to a temp file first so that write failures during
key updates won't lead to a key loss. */
- if (!force && !access (fname, F_OK))
+ if (!force && !gnupg_access (fname, F_OK))
{
log_error ("secret key file '%s' already exists\n", fname);
xfree (fname);
if (!fp)
{
gpg_error_t tmperr = gpg_error_from_syserror ();
- log_error ("can't create '%s': %s\n", fname, gpg_strerror (tmperr));
- xfree (fname);
- return tmperr;
- }
- /* See if an existing key is in extended format. */
- if (force)
+ if (force && gpg_err_code (tmperr) == GPG_ERR_ENOENT)
+ {
+ fp = es_fopen (fname, "wbx,mode=-rw");
+ if (!fp)
+ tmperr = gpg_error_from_syserror ();
+ }
+ if (!fp)
+ {
+ log_error ("can't create '%s': %s\n", fname, gpg_strerror (tmperr));
+ xfree (fname);
+ return tmperr;
+ }
+ }
+ else if (force)
{
gpg_error_t rc;
char first;
+ /* See if an existing key is in extended format. */
if (es_fread (&first, 1, 1, fp) != 1)
{
rc = gpg_error_from_syserror ();
if (first != '(')
{
- /* Key is in extended format. */
- return write_extended_private_key (fname, fp, buffer, length);
+ /* Key is already in the extended format. */
+ return write_extended_private_key (fname, fp, 1, 0, buffer, length,
+ serialno, keyref, timestamp);
+ }
+ if (first == '(' && opt.enable_extended_key_format)
+ {
+ /* Key is in the old format - but we want the extended format. */
+ return write_extended_private_key (fname, fp, 0, 0, buffer, length,
+ serialno, keyref, timestamp);
}
}
+ if (opt.enable_extended_key_format)
+ return write_extended_private_key (fname, fp, 0, 1, buffer, length,
+ serialno, keyref, timestamp);
+
if (es_fwrite (buffer, length, 1, fp) != 1)
{
gpg_error_t tmperr = gpg_error_from_syserror ();
}
+gpg_error_t
+agent_update_private_key (const unsigned char *grip, nvc_t pk)
+{
+ char *fname, *fname0;
+ estream_t fp;
+ char hexgrip[40+8+1];
+ gpg_error_t err;
+
+ bin2hex (grip, 20, hexgrip);
+ strcpy (hexgrip+40, ".key.tmp");
+
+ fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
+ hexgrip, NULL);
+ fname0 = xstrdup (fname);
+ if (!fname0)
+ {
+ err = gpg_error_from_syserror ();
+ xfree (fname);
+ return err;
+ }
+ fname0[strlen (fname)-4] = 0;
+
+ fp = es_fopen (fname, "wbx,mode=-rw");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+
+ log_error ("can't create '%s': %s\n", fname, gpg_strerror (err));
+ xfree (fname);
+ return err;
+ }
+
+ err = nvc_write (pk, fp);
+ if (err)
+ log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
+
+ es_fclose (fp);
+
+#ifdef HAVE_W32_SYSTEM
+ /* No atomic mv on W32 systems. */
+ gnupg_remove (fname0);
+#endif
+ if (rename (fname, fname0))
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("error renaming '%s' to '%s': %s\n"),
+ fname, fname0, strerror (errno));
+ }
+
+ xfree (fname);
+ return err;
+}
+
/* Callback function to try the unprotection from the passphrase query
code. */
static gpg_error_t
gnupg_isotime_t now, protected_at, tmptime;
char *desc = NULL;
- assert (!arg->unprotected_key);
+ log_assert (!arg->unprotected_key);
arg->change_required = 0;
err = agent_unprotect (ctrl, arg->protected_key, pi->pin, protected_at,
xfree (desc);
}
+ return err;
+}
+
+
+/* Return true if the STRING has an %C or %c expando. */
+static int
+has_comment_expando (const char *string)
+{
+ const char *s;
+ int percent = 0;
+
+ if (!string)
+ return 0;
+
+ for (s = string; *s; s++)
+ {
+ if (percent)
+ {
+ if (*s == 'c' || *s == 'C')
+ return 1;
+ percent = 0;
+ }
+ else if (*s == '%')
+ percent = 1;
+ }
return 0;
}
+
+
/* Modify a Key description, replacing certain special format
characters. List of currently supported replacements:
The functions returns 0 on success or an error code. On success a
newly allocated string is stored at the address of RESULT.
*/
-static gpg_error_t
-modify_description (const char *in, const char *comment, const gcry_sexp_t key,
- char **result)
+gpg_error_t
+agent_modify_description (const char *in, const char *comment,
+ const gcry_sexp_t key, char **result)
{
size_t comment_length;
size_t in_len;
size_t i;
int special, pass;
char *ssh_fpr = NULL;
+ char *p;
+
+ *result = NULL;
+
+ if (!comment)
+ comment = "";
comment_length = strlen (comment);
in_len = strlen (in);
/* First pass calculates the length, second pass does the actual
copying. */
+ /* FIXME: This can be simplified by using es_fopenmem. */
out = NULL;
out_len = 0;
for (pass=0; pass < 2; pass++)
case 'F': /* SSH style fingerprint. */
if (!ssh_fpr && key)
- ssh_get_fingerprint_string (key, &ssh_fpr);
+ ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest,
+ &ssh_fpr);
if (ssh_fpr)
{
if (out)
}
*out = 0;
- assert (*result + out_len == out);
+ log_assert (*result + out_len == out);
xfree (ssh_fpr);
+
+ /* The ssh prompt may sometimes end in
+ * "...%0A ()"
+ * The empty parentheses doesn't look very good. We use this hack
+ * here to remove them as well as the indentation spaces. */
+ p = *result;
+ i = strlen (p);
+ if (i > 2 && !strcmp (p + i - 2, "()"))
+ {
+ p += i - 2;
+ *p-- = 0;
+ while (p > *result && spacep (p))
+ *p-- = 0;
+ }
+
return 0;
}
passphrase (entered or from the cache) is stored there; if not NULL
will be stored. The caller needs to free the returned
passphrase. */
-static int
+static gpg_error_t
unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
unsigned char **keybuf, const unsigned char *grip,
cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
{
char *pw;
- pw = agent_get_cache (cache_nonce, CACHE_MODE_NONCE);
+ pw = agent_get_cache (ctrl, cache_nonce, CACHE_MODE_NONCE);
if (pw)
{
rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
char *pw;
retry:
- pw = agent_get_cache (hexgrip, cache_mode);
+ pw = agent_get_cache (ctrl, hexgrip, cache_mode);
if (pw)
{
rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
return 0;
}
xfree (pw);
- rc = 0;
}
else if (cache_mode == CACHE_MODE_NORMAL)
{
We can often avoid the passphrase entry in the second
step. We do this only in normal mode, so not to
interfere with unrelated cache entries. */
- pw = agent_get_cache (NULL, cache_mode);
+ pw = agent_get_cache (ctrl, NULL, cache_mode);
if (pw)
{
rc = agent_unprotect (ctrl, *keybuf, pw, NULL,
return 0;
}
xfree (pw);
- rc = 0;
}
}
{
/* We need to give the other thread a chance to actually put
it into the cache. */
- npth_sleep (1);
+ gnupg_sleep (1);
goto retry;
}
/* Timeout - better call pinentry now the plain way. */
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);
+ log_assert (arg.unprotected_key);
if (arg.change_required)
{
/* The callback told as that the user should change their
size_t canlen, erroff;
gcry_sexp_t s_skey;
- assert (arg.unprotected_key);
+ log_assert (arg.unprotected_key);
canlen = gcry_sexp_canon_len (arg.unprotected_key, 0, NULL, NULL);
rc = gcry_sexp_sscan (&s_skey, &erroff,
(char*)arg.unprotected_key, canlen);
else
{
/* Passphrase is fine. */
- agent_put_cache (hexgrip, cache_mode, pi->pin,
+ agent_put_cache (ctrl, hexgrip, cache_mode, pi->pin,
lookup_ttl? lookup_ttl (hexgrip) : 0);
agent_store_cache_hit (hexgrip);
if (r_passphrase && *pi->pin)
/* Read the key identified by GRIP from the private key directory and
- return it as an gcrypt S-expression object in RESULT. On failure
- returns an error code and stores NULL at RESULT. */
+ * return it as an gcrypt S-expression object in RESULT. If R_KEYMETA
+ * is not NULl and the extended key format is used, the meta data
+ * items are stored there. However the "Key:" item is removed from
+ * it. On failure returns an error code and stores NULL at RESULT and
+ * R_KEYMETA. */
static gpg_error_t
-read_key_file (const unsigned char *grip, gcry_sexp_t *result)
+read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta)
{
- int rc;
+ gpg_error_t err;
char *fname;
estream_t fp;
struct stat st;
char first;
*result = NULL;
+ if (r_keymeta)
+ *r_keymeta = NULL;
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");
fp = es_fopen (fname, "rb");
if (!fp)
{
- rc = gpg_error_from_syserror ();
- if (gpg_err_code (rc) != GPG_ERR_ENOENT)
- log_error ("can't open '%s': %s\n", fname, strerror (errno));
+ err = gpg_error_from_syserror ();
+ if (gpg_err_code (err) != GPG_ERR_ENOENT)
+ log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
xfree (fname);
- return rc;
+ return err;
}
if (es_fread (&first, 1, 1, fp) != 1)
{
- rc = gpg_error_from_syserror ();
+ err = gpg_error_from_syserror ();
log_error ("error reading first byte from '%s': %s\n",
- fname, strerror (errno));
+ fname, gpg_strerror (err));
xfree (fname);
es_fclose (fp);
- return rc;
+ return err;
}
- rc = es_fseek (fp, 0, SEEK_SET);
- if (rc)
+ if (es_fseek (fp, 0, SEEK_SET))
{
- log_error ("error seeking in '%s': %s\n", fname, strerror (errno));
+ err = gpg_error_from_syserror ();
+ log_error ("error seeking in '%s': %s\n", fname, gpg_strerror (err));
xfree (fname);
es_fclose (fp);
- return rc;
+ return err;
}
if (first != '(')
{
/* Key is in extended format. */
- pkc_t pk;
+ nvc_t pk = NULL;
int line;
- rc = pkc_parse (&pk, &line, fp);
+ err = nvc_parse_private_key (&pk, &line, fp);
es_fclose (fp);
- if (rc)
+ if (err)
log_error ("error parsing '%s' line %d: %s\n",
- fname, line, gpg_strerror (rc));
+ fname, line, gpg_strerror (err));
else
{
- rc = pkc_get_private_key (pk, result);
- pkc_release (pk);
- if (rc)
+ err = nvc_get_private_key (pk, result);
+ if (err)
log_error ("error getting private key from '%s': %s\n",
- fname, gpg_strerror (rc));
+ fname, gpg_strerror (err));
+ else
+ nvc_delete_named (pk, "Key:");
}
+ if (!err && r_keymeta)
+ *r_keymeta = pk;
+ else
+ nvc_release (pk);
xfree (fname);
- return rc;
+ return err;
}
if (fstat (es_fileno (fp), &st))
{
- rc = gpg_error_from_syserror ();
- log_error ("can't stat '%s': %s\n", fname, strerror (errno));
+ err = gpg_error_from_syserror ();
+ log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err));
xfree (fname);
es_fclose (fp);
- return rc;
+ return err;
}
buflen = st.st_size;
buf = xtrymalloc (buflen+1);
if (!buf)
{
- rc = gpg_error_from_syserror ();
+ err = gpg_error_from_syserror ();
log_error ("error allocating %zu bytes for '%s': %s\n",
- buflen, fname, strerror (errno));
+ buflen, fname, gpg_strerror (err));
xfree (fname);
es_fclose (fp);
xfree (buf);
- return rc;
+ return err;
}
if (es_fread (buf, buflen, 1, fp) != 1)
{
- rc = gpg_error_from_syserror ();
+ err = gpg_error_from_syserror ();
log_error ("error reading %zu bytes from '%s': %s\n",
- buflen, fname, strerror (errno));
+ buflen, fname, gpg_strerror (err));
xfree (fname);
es_fclose (fp);
xfree (buf);
- return rc;
+ return err;
}
/* Convert the file into a gcrypt S-expression object. */
- rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
+ err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
xfree (fname);
es_fclose (fp);
xfree (buf);
- if (rc)
+ if (err)
{
log_error ("failed to build S-Exp (off=%u): %s\n",
- (unsigned int)erroff, gpg_strerror (rc));
- return rc;
+ (unsigned int)erroff, gpg_strerror (err));
+ return err;
}
*result = s_skey;
return 0;
}
+/*
+ * Prompt a user the card insertion, when it's not available yet.
+ */
+static gpg_error_t
+prompt_for_card (ctrl_t ctrl, const unsigned char *grip,
+ nvc_t keymeta, const unsigned char *shadow_info)
+{
+ char *serialno;
+ char *desc;
+ char *want_sn = NULL;
+ int len;
+ gpg_error_t err;
+ char hexgrip[41];
+ char *comment_buffer = NULL;
+ const char *comment = NULL;
+ int refuse_prompt = 0;
+
+ bin2hex (grip, 20, hexgrip);
+
+ if (keymeta)
+ {
+ const char *p;
+
+ if ((p = nvc_get_string (keymeta, "Prompt:")) && !strcmp (p, "no"))
+ refuse_prompt = 1;
+
+ if ((p = nvc_get_string (keymeta, "Label:")))
+ {
+ if (strchr (p, '\n')
+ && (comment_buffer = linefeed_to_percent0A (p)))
+ comment = comment_buffer;
+ else
+ comment = p;
+ }
+ }
+
+ err = parse_shadow_info (shadow_info, &want_sn, NULL, NULL);
+ if (err)
+ return err;
+
+ len = want_sn? strlen (want_sn) : 0;
+ if (len == 32 && !strncmp (want_sn, "D27600012401", 12))
+ {
+ /* This is an OpenPGP card - reformat */
+ if (!strncmp (want_sn+16, "0006", 4))
+ {
+ /* This is a Yubikey. Print the s/n as it would be printed
+ * on Yubikey 5. Example: D2760001240100000006120808620000
+ * mmmm^^^^^^^^ */
+ unsigned long sn;
+
+ sn = atoi_4 (want_sn+20) * 10000;
+ sn += atoi_4 (want_sn+24);
+ snprintf (want_sn, 32, "%lu %03lu %03lu",
+ (sn/1000000ul), (sn/1000ul % 1000ul), (sn % 1000ul));
+ }
+ else /* Default is the Zeitcontrol card print format. */
+ {
+ memmove (want_sn, want_sn+16, 4);
+ want_sn[4] = ' ';
+ memmove (want_sn+5, want_sn+20, 8);
+ want_sn[13] = 0;
+ }
+ }
+ else if (len == 20 && want_sn[19] == '0')
+ {
+ /* We assume that a 20 byte serial number is a standard one
+ * which has the property to have a zero in the last nibble (Due
+ * to BCD representation). We don't display this '0' because it
+ * may confuse the user. */
+ want_sn[19] = 0;
+ }
+
+ for (;;)
+ {
+ /* Scan device(s), and check if key for GRIP is available. */
+ err = agent_card_serialno (ctrl, &serialno, NULL);
+ if (!err)
+ {
+ struct card_key_info_s *keyinfo;
+
+ xfree (serialno);
+ err = agent_card_keyinfo (ctrl, hexgrip, 0, &keyinfo);
+ if (!err)
+ {
+ /* Key for GRIP found, use it. */
+ agent_card_free_keyinfo (keyinfo);
+ break;
+ }
+ }
+
+ /* Card is not available. Prompt the insertion. */
+ if (refuse_prompt)
+ {
+ err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+ break;
+ }
+
+ if (asprintf (&desc,
+ "%s:%%0A%%0A"
+ " %s%%0A"
+ " %s",
+ L_("Please insert the card with serial number"),
+ want_sn ? want_sn : "",
+ comment? comment:"") < 0)
+ err = out_of_core ();
+ else
+ {
+ err = agent_get_confirmation (ctrl, desc, NULL, NULL, 0);
+ if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK &&
+ gpg_err_code (err) == GPG_ERR_NO_PIN_ENTRY)
+ err = gpg_error (GPG_ERR_CARD_NOT_PRESENT);
+
+ xfree (desc);
+ }
+
+ if (err)
+ break;
+ }
+
+ xfree (want_sn);
+ gcry_free (comment_buffer);
+ return err;
+}
+
+
/* Return the secret key as an S-Exp in RESULT after locating it using
- the GRIP. If the operation shall be diverted to a token, an
+ the GRIP. Caller should set GRIP=NULL, when a key in a file is
+ intended to be used for cryptographic operation. In this case,
+ CTRL->keygrip is used to locate the file, and it may ask a user for
+ confirmation. If the operation shall be diverted to a token, an
allocated S-expression with the shadow_info part from the file is
stored at SHADOW_INFO; if not NULL will be stored at SHADOW_INFO.
CACHE_MODE defines now the cache shall be used. DESC_TEXT may be
const char *desc_text,
const unsigned char *grip, unsigned char **shadow_info,
cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
- gcry_sexp_t *result, char **r_passphrase)
+ gcry_sexp_t *result, char **r_passphrase,
+ time_t *r_timestamp)
{
- int rc;
+ gpg_error_t err;
unsigned char *buf;
- size_t len, buflen, erroff;
+ size_t len, erroff;
gcry_sexp_t s_skey;
+ nvc_t keymeta = NULL;
+ char *desc_text_buffer = NULL; /* Used in case we extend DESC_TEXT. */
*result = NULL;
if (shadow_info)
*shadow_info = NULL;
if (r_passphrase)
*r_passphrase = NULL;
+ if (r_timestamp)
+ *r_timestamp = (time_t)(-1);
- rc = read_key_file (grip, &s_skey);
- if (rc)
- {
- if (gpg_err_code (rc) == GPG_ERR_ENOENT)
- rc = gpg_error (GPG_ERR_NO_SECKEY);
- return rc;
- }
+ if (!grip && !ctrl->have_keygrip)
+ return gpg_error (GPG_ERR_NO_SECKEY);
+
+ err = read_key_file (grip? grip : ctrl->keygrip, &s_skey, &keymeta);
/* For use with the protection functions we also need the key as an
canonical encoded S-expression in a buffer. Create this buffer
now. */
- rc = make_canon_sexp (s_skey, &buf, &len);
- if (rc)
- return rc;
+ err = make_canon_sexp (s_skey, &buf, &len);
+ if (err)
+ {
+ nvc_release (keymeta);
+ xfree (desc_text_buffer);
+ return err;
+ }
+
+ if (r_timestamp && keymeta)
+ {
+ const char *created = nvc_get_string (keymeta, "Created:");
+
+ if (created)
+ *r_timestamp = isotime2epoch (created);
+ }
+
+ if (!grip && keymeta)
+ {
+ const char *ask_confirmation = nvc_get_string (keymeta, "Confirm:");
+
+ if (ask_confirmation
+ && ((!strcmp (ask_confirmation, "restricted") && ctrl->restricted)
+ || !strcmp (ask_confirmation, "yes")))
+ {
+ char hexgrip[40+4+1];
+ char *prompt;
+ char *comment_buffer = NULL;
+ const char *comment = NULL;
+
+ bin2hex (ctrl->keygrip, 20, hexgrip);
+
+ if ((comment = nvc_get_string (keymeta, "Label:")))
+ {
+ if (strchr (comment, '\n')
+ && (comment_buffer = linefeed_to_percent0A (comment)))
+ comment = comment_buffer;
+ }
+
+ prompt = xtryasprintf (L_("Requested the use of key%%0A"
+ " %s%%0A"
+ " %s%%0A"
+ "Do you want to allow this?"),
+ hexgrip, comment? comment:"");
+
+ gcry_free (comment_buffer);
+
+ err = agent_get_confirmation (ctrl, prompt,
+ L_("Allow"), L_("Deny"), 0);
+ xfree (prompt);
+
+ if (err)
+ return err;
+ }
+ }
switch (agent_private_key_type (buf))
{
unsigned char *buf_new;
size_t buf_newlen;
- rc = agent_unprotect (ctrl, buf, "", NULL, &buf_new, &buf_newlen);
- if (rc)
+ err = agent_unprotect (ctrl, buf, "", NULL, &buf_new, &buf_newlen);
+ if (err)
log_error ("failed to convert unprotected openpgp key: %s\n",
- gpg_strerror (rc));
+ gpg_strerror (err));
else
{
xfree (buf);
case PRIVATE_KEY_PROTECTED:
{
char *desc_text_final;
- char *comment = NULL;
+ char *comment_buffer = NULL;
+ const char *comment = NULL;
/* Note, that we will take the comment as a C string for
- display purposes; i.e. all stuff beyond a Nul character is
- ignored. */
- {
- gcry_sexp_t comment_sexp;
+ * display purposes; i.e. all stuff beyond a Nul character is
+ * ignored. If a "Label" entry is available in the meta data
+ * this is used instead of the s-expression comment. */
+ if (keymeta && (comment = nvc_get_string (keymeta, "Label:")))
+ {
+ if (strchr (comment, '\n')
+ && (comment_buffer = linefeed_to_percent0A (comment)))
+ comment = comment_buffer;
+ /* In case DESC_TEXT has no escape pattern for a comment
+ * we append one. */
+ if (desc_text && !has_comment_expando (desc_text))
+ {
+ desc_text_buffer = strconcat (desc_text, "%0A%C", NULL);
+ if (desc_text_buffer)
+ desc_text = desc_text_buffer;
+ }
+ }
+ else
+ {
+ gcry_sexp_t comment_sexp;
- comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
- if (comment_sexp)
- comment = gcry_sexp_nth_string (comment_sexp, 1);
- gcry_sexp_release (comment_sexp);
- }
+ comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
+ if (comment_sexp)
+ comment_buffer = gcry_sexp_nth_string (comment_sexp, 1);
+ gcry_sexp_release (comment_sexp);
+ comment = comment_buffer;
+ }
desc_text_final = NULL;
if (desc_text)
- rc = modify_description (desc_text, comment? comment:"", s_skey,
- &desc_text_final);
- gcry_free (comment);
-
- if (!rc)
- {
- rc = unprotect (ctrl, cache_nonce, desc_text_final, &buf, grip,
- cache_mode, lookup_ttl, r_passphrase);
- if (rc)
- log_error ("failed to unprotect the secret key: %s\n",
- gpg_strerror (rc));
- }
+ err = agent_modify_description (desc_text, comment, s_skey,
+ &desc_text_final);
+ gcry_free (comment_buffer);
+
+ if (!err)
+ {
+ err = unprotect (ctrl, cache_nonce, desc_text_final, &buf,
+ grip? grip : ctrl->keygrip,
+ cache_mode, lookup_ttl, r_passphrase);
+ if (err)
+ log_error ("failed to unprotect the secret key: %s\n",
+ gpg_strerror (err));
+ }
xfree (desc_text_final);
}
if (shadow_info)
{
const unsigned char *s;
+ unsigned char *shadow_type;
size_t n;
- rc = agent_get_shadow_info (buf, &s);
- if (!rc)
+ err = agent_get_shadow_info_type (buf, &s, &shadow_type);
+ if (!err)
{
n = gcry_sexp_canon_len (s, 0, NULL,NULL);
- assert (n);
+ log_assert (n);
*shadow_info = xtrymalloc (n);
if (!*shadow_info)
- rc = out_of_core ();
+ {
+ err = out_of_core ();
+ goto shadow_error;
+ }
else
{
memcpy (*shadow_info, s, n);
- rc = 0;
+ /*
+ * When it's a key on card (not on tpm2), maks sure
+ * it's available.
+ */
+ if (strcmp (shadow_type, "t1-v1") == 0 && !grip)
+ err = prompt_for_card (ctrl, ctrl->keygrip,
+ keymeta, *shadow_info);
}
}
- if (rc)
- log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc));
+ else
+ shadow_error:
+ log_error ("get_shadow_info failed: %s\n", gpg_strerror (err));
+
+ xfree (shadow_type);
}
else
- rc = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+ err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
break;
default:
log_error ("invalid private key format\n");
- rc = gpg_error (GPG_ERR_BAD_SECKEY);
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
break;
}
gcry_sexp_release (s_skey);
s_skey = NULL;
- if (rc)
+ if (err)
{
xfree (buf);
if (r_passphrase)
xfree (*r_passphrase);
*r_passphrase = NULL;
}
- return rc;
+ nvc_release (keymeta);
+ xfree (desc_text_buffer);
+ return err;
}
- buflen = gcry_sexp_canon_len (buf, 0, NULL, NULL);
- rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
- wipememory (buf, buflen);
+ err = sexp_sscan_private_key (result, &erroff, buf);
xfree (buf);
- if (rc)
+ nvc_release (keymeta);
+ xfree (desc_text_buffer);
+ if (err)
{
log_error ("failed to build S-Exp (off=%u): %s\n",
- (unsigned int)erroff, gpg_strerror (rc));
+ (unsigned int)erroff, gpg_strerror (err));
if (r_passphrase)
{
xfree (*r_passphrase);
*r_passphrase = NULL;
}
- return rc;
- }
-
- *result = s_skey;
- return 0;
-}
-
-
-/* Return the string name from the S-expression S_KEY as well as a
- string describing the names of the parameters. ALGONAMESIZE and
- ELEMSSIZE give the allocated size of the provided buffers. The
- buffers may be NULL if not required. If R_LIST is not NULL the top
- level list will be stored there; the caller needs to release it in
- this case. */
-static gpg_error_t
-key_parms_from_sexp (gcry_sexp_t s_key, gcry_sexp_t *r_list,
- char *r_algoname, size_t algonamesize,
- char *r_elems, size_t elemssize)
-{
- gcry_sexp_t list, l2;
- const char *name, *algoname, *elems;
- size_t n;
-
- if (r_list)
- *r_list = NULL;
-
- list = gcry_sexp_find_token (s_key, "shadowed-private-key", 0 );
- if (!list)
- list = gcry_sexp_find_token (s_key, "protected-private-key", 0 );
- if (!list)
- list = gcry_sexp_find_token (s_key, "private-key", 0 );
- if (!list)
- {
- log_error ("invalid private key format\n");
- return gpg_error (GPG_ERR_BAD_SECKEY);
- }
-
- l2 = gcry_sexp_cadr (list);
- gcry_sexp_release (list);
- list = l2;
- name = gcry_sexp_nth_data (list, 0, &n);
- if (n==3 && !memcmp (name, "rsa", 3))
- {
- algoname = "rsa";
- elems = "ne";
- }
- else if (n==3 && !memcmp (name, "dsa", 3))
- {
- algoname = "dsa";
- elems = "pqgy";
- }
- else if (n==3 && !memcmp (name, "ecc", 3))
- {
- algoname = "ecc";
- elems = "pabgnq";
- }
- else if (n==5 && !memcmp (name, "ecdsa", 5))
- {
- algoname = "ecdsa";
- elems = "pabgnq";
- }
- else if (n==4 && !memcmp (name, "ecdh", 4))
- {
- algoname = "ecdh";
- elems = "pabgnq";
- }
- else if (n==3 && !memcmp (name, "elg", 3))
- {
- algoname = "elg";
- elems = "pgy";
- }
- else
- {
- log_error ("unknown private key algorithm\n");
- gcry_sexp_release (list);
- return gpg_error (GPG_ERR_BAD_SECKEY);
- }
-
- if (r_algoname)
- {
- if (strlen (algoname) >= algonamesize)
- return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
- strcpy (r_algoname, algoname);
- }
- if (r_elems)
- {
- if (strlen (elems) >= elemssize)
- return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
- strcpy (r_elems, elems);
}
- if (r_list)
- *r_list = list;
- else
- gcry_sexp_release (list);
-
- return 0;
-}
-
-
-/* Return true if KEYPARMS holds an EdDSA key. */
-static int
-is_eddsa (gcry_sexp_t keyparms)
-{
- int result = 0;
- gcry_sexp_t list;
- const char *s;
- size_t n;
- int i;
-
- list = gcry_sexp_find_token (keyparms, "flags", 0);
- for (i = list ? gcry_sexp_length (list)-1 : 0; i > 0; i--)
- {
- s = gcry_sexp_nth_data (list, i, &n);
- if (!s)
- continue; /* Not a data element. */
-
- if (n == 5 && !memcmp (s, "eddsa", 5))
- {
- result = 1;
- break;
- }
- }
- gcry_sexp_release (list);
- return result;
-}
-
-
-/* Return the public key algorithm number if S_KEY is a DSA style key.
- If it is not a DSA style key, return 0. */
-int
-agent_is_dsa_key (gcry_sexp_t s_key)
-{
- int result;
- gcry_sexp_t list;
- char algoname[6];
-
- if (!s_key)
- return 0;
-
- if (key_parms_from_sexp (s_key, &list, algoname, sizeof algoname, NULL, 0))
- return 0; /* Error - assume it is not an DSA key. */
-
- if (!strcmp (algoname, "dsa"))
- result = GCRY_PK_DSA;
- else if (!strcmp (algoname, "ecc"))
- {
- if (is_eddsa (list))
- result = 0;
- else
- result = GCRY_PK_ECDSA;
- }
- else if (!strcmp (algoname, "ecdsa"))
- result = GCRY_PK_ECDSA;
- else
- result = 0;
-
- gcry_sexp_release (list);
- return result;
-}
-
-
-/* Return true if S_KEY is an EdDSA key as used with curve Ed25519. */
-int
-agent_is_eddsa_key (gcry_sexp_t s_key)
-{
- int result;
- gcry_sexp_t list;
- char algoname[6];
-
- if (!s_key)
- return 0;
-
- if (key_parms_from_sexp (s_key, &list, algoname, sizeof algoname, NULL, 0))
- return 0; /* Error - assume it is not an EdDSA key. */
-
- if (!strcmp (algoname, "ecc") && is_eddsa (list))
- result = 1;
- else if (!strcmp (algoname, "eddsa")) /* backward compatibility. */
- result = 1;
- else
- result = 0;
-
- gcry_sexp_release (list);
- return result;
+ return err;
}
failure an error code is returned and NULL stored at RESULT. */
gpg_error_t
agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
- gcry_sexp_t *result)
+ gcry_sexp_t *result, nvc_t *r_keymeta)
{
gpg_error_t err;
gcry_sexp_t s_skey;
*result = NULL;
- err = read_key_file (grip, &s_skey);
+ err = read_key_file (grip, &s_skey, r_keymeta);
if (!err)
*result = s_skey;
return err;
at RESULT. This function extracts the public key from the private
key database. On failure an error code is returned and NULL stored
at RESULT. */
-gpg_error_t
-agent_public_key_from_file (ctrl_t ctrl,
- const unsigned char *grip,
- gcry_sexp_t *result)
+static gpg_error_t
+public_key_from_file (ctrl_t ctrl, const unsigned char *grip,
+ gcry_sexp_t *result, int for_ssh)
{
gpg_error_t err;
int i, idx;
gcry_sexp_t s_skey;
+ nvc_t keymeta = NULL;
const char *algoname, *elems;
int npkey;
gcry_mpi_t array[10];
gcry_sexp_t uri_sexp, comment_sexp;
const char *uri, *comment;
size_t uri_length, comment_length;
- char *format, *p;
+ int uri_intlen, comment_intlen;
+ membuf_t format_mb;
+ char *format;
void *args[2+7+2+2+1]; /* Size is 2 + max. # of elements + 2 for uri + 2
for comment + end-of-list. */
int argidx;
*result = NULL;
- err = read_key_file (grip, &s_skey);
+ err = read_key_file (grip, &s_skey, for_ssh? &keymeta : NULL);
if (err)
return err;
+ if (for_ssh)
+ {
+ /* Use-for-ssh: yes */
+ int is_ssh = 0;
+
+ if (keymeta == NULL)
+ return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+
+ is_ssh = nvc_get_boolean (keymeta, "Use-for-ssh:");
+ nvc_release (keymeta);
+ keymeta = NULL;
+
+ if (!is_ssh)
+ return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+ }
+
for (i=0; i < DIM (array); i++)
array[i] = NULL;
s_skey = NULL;
- /* FIXME: The following thing is pretty ugly code; we should
- investigate how to make it cleaner. Probably code to handle
- canonical S-expressions in a memory buffer is better suited for
- such a task. After all that is what we do in protect.c. Neeed
- to find common patterns and write a straightformward API to use
- them. */
- assert (sizeof (size_t) <= sizeof (void*));
-
- format = xtrymalloc (15+4+7*npkey+10+15+1+1);
- if (!format)
- {
- err = gpg_error_from_syserror ();
- for (i=0; array[i]; i++)
- gcry_mpi_release (array[i]);
- gcry_sexp_release (curve);
- gcry_sexp_release (flags);
- gcry_sexp_release (uri_sexp);
- gcry_sexp_release (comment_sexp);
- return err;
- }
+ log_assert (sizeof (size_t) <= sizeof (void*));
+ init_membuf (&format_mb, 256);
argidx = 0;
- p = stpcpy (stpcpy (format, "(public-key("), algoname);
- p = stpcpy (p, "%S%S"); /* curve name and flags. */
+ put_membuf_printf (&format_mb, "(public-key(%s%%S%%S", algoname);
args[argidx++] = &curve;
args[argidx++] = &flags;
for (idx=0, s=elems; idx < npkey; idx++)
{
- *p++ = '(';
- *p++ = *s++;
- p = stpcpy (p, " %m)");
- assert (argidx < DIM (args));
+ put_membuf_printf (&format_mb, "(%c %%m)", *s++);
+ log_assert (argidx < DIM (args));
args[argidx++] = &array[idx];
}
- *p++ = ')';
+ put_membuf_str (&format_mb, ")");
if (uri)
{
- p = stpcpy (p, "(uri %b)");
- assert (argidx+1 < DIM (args));
- args[argidx++] = (void *)&uri_length;
+ put_membuf_str (&format_mb, "(uri %b)");
+ log_assert (argidx+1 < DIM (args));
+ uri_intlen = (int)uri_length;
+ args[argidx++] = (void *)&uri_intlen;
args[argidx++] = (void *)&uri;
}
if (comment)
{
- p = stpcpy (p, "(comment %b)");
- assert (argidx+1 < DIM (args));
- args[argidx++] = (void *)&comment_length;
- args[argidx++] = (void*)&comment;
+ put_membuf_str (&format_mb, "(comment %b)");
+ log_assert (argidx+1 < DIM (args));
+ comment_intlen = (int)comment_length;
+ args[argidx++] = (void *)&comment_intlen;
+ args[argidx++] = (void *)&comment;
}
- *p++ = ')';
- *p = 0;
- assert (argidx < DIM (args));
+ put_membuf (&format_mb, ")", 2);
+ log_assert (argidx < DIM (args));
args[argidx] = NULL;
+ format = get_membuf (&format_mb, NULL);
+ if (!format)
+ {
+ err = gpg_error_from_syserror ();
+ for (i=0; array[i]; i++)
+ gcry_mpi_release (array[i]);
+ gcry_sexp_release (curve);
+ gcry_sexp_release (flags);
+ gcry_sexp_release (uri_sexp);
+ gcry_sexp_release (comment_sexp);
+ return err;
+ }
+
err = gcry_sexp_build_array (&list, NULL, format, args);
xfree (format);
for (i=0; array[i]; i++)
return err;
}
+gpg_error_t
+agent_public_key_from_file (ctrl_t ctrl,
+ const unsigned char *grip,
+ gcry_sexp_t *result)
+{
+ return public_key_from_file (ctrl, grip, result, 0);
+}
+
+gpg_error_t
+agent_ssh_key_from_file (ctrl_t ctrl,
+ const unsigned char *grip,
+ gcry_sexp_t *result)
+{
+ return public_key_from_file (ctrl, grip, result, 1);
+}
-/* Check whether the the secret key identified by GRIP is available.
+/* Check whether the secret key identified by GRIP is available.
Returns 0 is the key is available. */
int
agent_key_available (const unsigned char *grip)
fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
hexgrip, NULL);
- result = !access (fname, R_OK)? 0 : -1;
+ result = !gnupg_access (fname, R_OK)? 0 : -1;
xfree (fname);
return result;
}
S-expression. */
gpg_error_t
agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
- int *r_keytype, unsigned char **r_shadow_info)
+ int *r_keytype, unsigned char **r_shadow_info,
+ unsigned char **r_shadow_info_type)
{
gpg_error_t err;
unsigned char *buf;
{
gcry_sexp_t sexp;
- err = read_key_file (grip, &sexp);
+ err = read_key_file (grip, &sexp, NULL);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_ENOENT)
const unsigned char *s;
size_t n;
- err = agent_get_shadow_info (buf, &s);
+ err = agent_get_shadow_info_type (buf, &s, r_shadow_info_type);
if (!err)
{
n = gcry_sexp_canon_len (s, 0, NULL, NULL);
- assert (n);
+ log_assert (n);
*r_shadow_info = xtrymalloc (n);
if (!*r_shadow_info)
err = gpg_error_from_syserror ();
\f
/* Delete the key with GRIP from the disk after having asked for
- confirmation using DESC_TEXT. If FORCE is set the function won't
- require a confirmation via Pinentry or warns if the key is also
- used by ssh.
-
- Common error codes are:
- GPG_ERR_NO_SECKEY
- GPG_ERR_KEY_ON_CARD
- GPG_ERR_NOT_CONFIRMED
-*/
+ * confirmation using DESC_TEXT. If FORCE is set the function won't
+ * require a confirmation via Pinentry or warns if the key is also
+ * used by ssh. If ONLY_STUBS is set only stub keys (references to
+ * smartcards) will be affected.
+ *
+ * Common error codes are:
+ * GPG_ERR_NO_SECKEY
+ * GPG_ERR_KEY_ON_CARD
+ * GPG_ERR_NOT_CONFIRMED
+ * GPG_ERR_FORBIDDEN - Not a stub key and ONLY_STUBS requested.
+ */
gpg_error_t
agent_delete_key (ctrl_t ctrl, const char *desc_text,
- const unsigned char *grip, int force)
+ const unsigned char *grip, int force, int only_stubs)
{
gpg_error_t err;
gcry_sexp_t s_skey = NULL;
ssh_control_file_t cf = NULL;
char hexgrip[40+4+1];
char *default_desc = NULL;
+ int key_type;
- err = read_key_file (grip, &s_skey);
+ err = read_key_file (grip, &s_skey, NULL);
if (gpg_err_code (err) == GPG_ERR_ENOENT)
err = gpg_error (GPG_ERR_NO_SECKEY);
if (err)
if (err)
goto leave;
- switch (agent_private_key_type (buf))
+ key_type = agent_private_key_type (buf);
+ if (only_stubs && key_type != PRIVATE_KEY_SHADOWED)
+ {
+ err = gpg_error (GPG_ERR_FORBIDDEN);
+ goto leave;
+ }
+
+ switch (key_type)
{
case PRIVATE_KEY_CLEAR:
case PRIVATE_KEY_OPENPGP_NONE:
}
if (desc_text)
- err = modify_description (desc_text, comment? comment:"", s_skey,
- &desc_text_final);
+ err = agent_modify_description (desc_text, comment, s_skey,
+ &desc_text_final);
if (err)
goto leave;
gcry_sexp_release (s_skey);
return err;
}
+
+
+/* Write an S-expression formatted shadow key to our key storage.
+ Shadow key is created by an S-expression public key in PKBUF and
+ card's SERIALNO and the IDSTRING. With FORCE passed as true an
+ existing key with the given GRIP will get overwritten. */
+gpg_error_t
+agent_write_shadow_key (const unsigned char *grip,
+ const char *serialno, const char *keyid,
+ const unsigned char *pkbuf, int force)
+{
+ gpg_error_t err;
+ unsigned char *shadow_info;
+ unsigned char *shdkey;
+ size_t len;
+
+ /* Just in case some caller did not parse the stuff correctly, skip
+ * leading spaces. */
+ while (spacep (serialno))
+ serialno++;
+ while (spacep (keyid))
+ keyid++;
+
+ shadow_info = make_shadow_info (serialno, keyid);
+ if (!shadow_info)
+ return gpg_error_from_syserror ();
+
+ err = agent_shadow_key (pkbuf, shadow_info, &shdkey);
+ xfree (shadow_info);
+ if (err)
+ {
+ log_error ("shadowing the key failed: %s\n", gpg_strerror (err));
+ return err;
+ }
+
+ len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL);
+ err = agent_write_private_key (grip, shdkey, len, force, serialno, keyid, 0);
+ xfree (shdkey);
+ if (err)
+ log_error ("error writing key: %s\n", gpg_strerror (err));
+
+ return err;
+}