/* 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.
*
#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 "../common/i18n.h"
#include "../common/ssh-utils.h"
-#include "../common/name-value.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, int update,
- 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;
nvc_t pk = NULL;
gcry_sexp_t key = NULL;
int remove = 0;
+ char *token = NULL;
if (update)
{
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 = nvc_write (pk, fp);
+ if (!err)
+ err = es_fflush (fp);
if (err)
{
log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
xfree (fname);
gcry_sexp_release (key);
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 (first != '(')
{
/* Key is already in the extended format. */
- return write_extended_private_key (fname, fp, 1, buffer, length);
+ 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, buffer, length);
+ 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, buffer, length);
+ 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
+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,
}
+/* 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:
{
/* 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)
{
- assert (arg.unprotected_key);
+ 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
+ {
+ 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);
/* 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)
{
gpg_error_t err;
char *fname;
char first;
*result = NULL;
+ if (r_keymeta)
+ *r_keymeta = NULL;
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");
if (first != '(')
{
/* Key is in extended format. */
- nvc_t pk;
+ nvc_t pk = NULL;
int line;
err = nvc_parse_private_key (&pk, &line, fp);
else
{
err = nvc_get_private_key (pk, result);
- nvc_release (pk);
if (err)
log_error ("error getting private key from '%s': %s\n",
fname, gpg_strerror (err));
+ else
+ nvc_delete_named (pk, "Key:");
}
+ if (!err && r_keymeta)
+ *r_keymeta = pk;
+ else
+ nvc_release (pk);
xfree (fname);
return err;
}
}
+/*
+ * 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)
{
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);
- err = read_key_file (grip, &s_skey);
- if (err)
- {
- if (gpg_err_code (err) == GPG_ERR_ENOENT)
- err = gpg_error (GPG_ERR_NO_SECKEY);
- return err;
- }
+ 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. */
err = make_canon_sexp (s_skey, &buf, &len);
if (err)
- return 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))
{
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)
err = agent_modify_description (desc_text, comment, s_skey,
&desc_text_final);
- gcry_free (comment);
+ gcry_free (comment_buffer);
- if (!err)
- {
- err = unprotect (ctrl, cache_nonce, desc_text_final, &buf, grip,
- cache_mode, lookup_ttl, r_passphrase);
- if (err)
- log_error ("failed to unprotect the secret key: %s\n",
- gpg_strerror (err));
- }
+ 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;
- err = agent_get_shadow_info (buf, &s);
+ err = agent_get_shadow_info_type (buf, &s, &shadow_type);
if (!err)
{
n = gcry_sexp_canon_len (s, 0, NULL,NULL);
log_assert (n);
*shadow_info = xtrymalloc (n);
if (!*shadow_info)
- err = out_of_core ();
+ {
+ err = out_of_core ();
+ goto shadow_error;
+ }
else
{
memcpy (*shadow_info, s, n);
- err = 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 (err)
+ else
+ shadow_error:
log_error ("get_shadow_info failed: %s\n", gpg_strerror (err));
+
+ xfree (shadow_type);
}
else
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
xfree (*r_passphrase);
*r_passphrase = NULL;
}
+ nvc_release (keymeta);
+ xfree (desc_text_buffer);
return err;
}
- buflen = gcry_sexp_canon_len (buf, 0, NULL, NULL);
- err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
- wipememory (buf, buflen);
+ err = sexp_sscan_private_key (result, &erroff, buf);
xfree (buf);
+ nvc_release (keymeta);
+ xfree (desc_text_buffer);
if (err)
{
log_error ("failed to build S-Exp (off=%u): %s\n",
xfree (*r_passphrase);
*r_passphrase = NULL;
}
- return err;
}
- *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. Need
- 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 secret key identified by GRIP is available.
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 ();
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)
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 ();
}
len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL);
- err = agent_write_private_key (grip, shdkey, len, force);
+ 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));