Support a confirm flag for ssh.

This implements the suggestion from bug#1349.  With this change the
fingerprint of the ssh key is also displayed in the pinentry prompts.
This commit is contained in:
Werner Koch 2011-07-20 20:49:41 +02:00
parent 69f8a7f15d
commit d479906991
7 changed files with 217 additions and 66 deletions

2
NEWS
View File

@ -5,6 +5,8 @@ Noteworthy changes in version 2.1.0beta3
* Allow generation of card keys up to 4096 bit.
* Support the SSH confirm flag.
Noteworthy changes in version 2.1.0beta2 (2011-03-08)
-----------------------------------------------------

View File

@ -1,3 +1,17 @@
2011-07-20 Werner Koch <wk@g10code.com>
* command-ssh.c (ssh_identity_register): Display the ssh
fingerprint in the prompt.
(add_control_entry): Add arg FMTFPR and use it as comment in
sshcontrol.
(confirm_flag_from_sshcontrol): New.
(data_sign): Ask for confirmaton if requested.
(search_control_file): Add new arg R_CONFIRM and enhance parser.
* findkey.c (agent_raw_key_from_file): New.
(modify_description): Add format letter %F.
* findkey.c (agent_key_from_file): Simplify comment extraction by
using gcry_sexp_nth_string.
2011-06-28 Ben Kibbey <bjk@luxsci.net>
* command.c (option_handler): Add option s2k-count.

View File

@ -268,6 +268,8 @@ gpg_error_t agent_key_from_file (ctrl_t ctrl,
lookup_ttl_t lookup_ttl,
gcry_sexp_t *result,
char **r_passphrase);
gpg_error_t agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t *result);
gpg_error_t agent_public_key_from_file (ctrl_t ctrl,
const unsigned char *grip,
gcry_sexp_t *result);

View File

@ -33,6 +33,8 @@
#include "agent.h"
#include "i18n.h"
#include "../common/ssh-utils.h"
@ -708,17 +710,23 @@ open_control_file (FILE **r_fp, int append)
/* Search the file at stream FP from the beginning until a matching
HEXGRIP is found; return success in this case and store true at
DISABLED if the found key has been disabled. If R_TTL is not NULL
a specified TTL for that key is stored there. */
a specified TTL for that key is stored there. If R_CONFIRM is not
NULL it is set to 1 if the key has the confirm flag set. */
static gpg_error_t
search_control_file (FILE *fp, const char *hexgrip,
int *r_disabled, int *r_ttl)
int *r_disabled, int *r_ttl, int *r_confirm)
{
int c, i;
int c, i, n;
char *p, *pend, line[256];
long ttl;
int lnr = 0;
const char fname[] = "sshcontrol";
assert (strlen (hexgrip) == 40 );
if (r_confirm)
*r_confirm = 0;
fseek (fp, 0, SEEK_SET);
clearerr (fp);
*r_disabled = 0;
@ -731,6 +739,7 @@ search_control_file (FILE *fp, const char *hexgrip,
return gpg_error (GPG_ERR_EOF);
return gpg_error (gpg_err_code_from_errno (errno));
}
lnr++;
if (!*line || line[strlen(line)-1] != '\n')
{
@ -760,7 +769,7 @@ search_control_file (FILE *fp, const char *hexgrip,
goto next_line;
if (i != 40 || !(spacep (p) || *p == '\n'))
{
log_error ("invalid formatted line in ssh control file\n");
log_error ("invalid formatted line in `%s', line %d\n", fname, lnr);
return gpg_error (GPG_ERR_BAD_DATA);
}
@ -768,13 +777,37 @@ search_control_file (FILE *fp, const char *hexgrip,
p = pend;
if (!(spacep (p) || *p == '\n') || ttl < -1)
{
log_error ("invalid TTL value in ssh control file; assuming 0\n");
log_error ("invalid TTL value in `%s', line %d; assuming 0\n",
fname, lnr);
ttl = 0;
}
if (r_ttl)
*r_ttl = ttl;
/* Here is the place to parse flags if we need them. */
/* Now check for key-value pairs of the form NAME[=VALUE]. */
while (*p)
{
for (; spacep (p) && *p != '\n'; p++)
;
if (!*p || *p == '\n')
break;
n = strcspn (p, "= \t\n");
if (p[n] == '=')
{
log_error ("assigning a value to a flag is not yet supported; "
"in `%s', line %d; flag ignored\n", fname, lnr);
p++;
}
else if (n == 7 && !memcmp (p, "confirm", 7))
{
if (r_confirm)
*r_confirm = 1;
}
else
log_error ("invalid flag `%.*s' in `%s', line %d; ignored\n",
n, p, fname, lnr);
p += n;
}
return 0; /* Okay: found it. */
}
@ -783,11 +816,12 @@ search_control_file (FILE *fp, const char *hexgrip,
/* Add an entry to the control file to mark the key with the keygrip
HEXGRIP as usable for SSH; i.e. it will be returned when ssh asks
for it. This function is in general used to add a key received
through the ssh-add function. We can assume that the user wants to
allow ssh using this key. */
for it. FMTFPR is the fingerprint string. This function is in
general used to add a key received through the ssh-add function.
We can assume that the user wants to allow ssh using this key. */
static gpg_error_t
add_control_entry (ctrl_t ctrl, const char *hexgrip, int ttl)
add_control_entry (ctrl_t ctrl, const char *hexgrip, const char *fmtfpr,
int ttl, int confirm)
{
gpg_error_t err;
FILE *fp;
@ -799,7 +833,7 @@ add_control_entry (ctrl_t ctrl, const char *hexgrip, int ttl)
if (err)
return err;
err = search_control_file (fp, hexgrip, &disabled, NULL);
err = search_control_file (fp, hexgrip, &disabled, NULL, NULL);
if (err && gpg_err_code(err) == GPG_ERR_EOF)
{
struct tm *tp;
@ -808,10 +842,12 @@ add_control_entry (ctrl_t ctrl, const char *hexgrip, int ttl)
/* Not yet in the file - add it. Because the file has been
opened in append mode, we simply need to write to it. */
tp = localtime (&atime);
fprintf (fp, "# Key added on %04d-%02d-%02d %02d:%02d:%02d\n%s %d\n",
fprintf (fp, ("# Key added on: %04d-%02d-%02d %02d:%02d:%02d\n"
"# Fingerprint: %s\n"
"%s %d%s\n"),
1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday,
tp->tm_hour, tp->tm_min, tp->tm_sec,
hexgrip, ttl);
fmtfpr, hexgrip, ttl, confirm? " confirm":"");
}
fclose (fp);
@ -832,7 +868,7 @@ ttl_from_sshcontrol (const char *hexgrip)
if (open_control_file (&fp, 0))
return 0; /* Error: Use the global default TTL. */
if (search_control_file (fp, hexgrip, &disabled, &ttl)
if (search_control_file (fp, hexgrip, &disabled, &ttl, NULL)
|| disabled)
ttl = 0; /* Use the global default if not found or disabled. */
@ -842,6 +878,30 @@ ttl_from_sshcontrol (const char *hexgrip)
}
/* Scan the sshcontrol file and return the confirm flag. */
static int
confirm_flag_from_sshcontrol (const char *hexgrip)
{
FILE *fp;
int disabled, confirm;
if (!hexgrip || strlen (hexgrip) != 40)
return 1; /* Wrong input: Better ask for confirmation. */
if (open_control_file (&fp, 0))
return 1; /* Error: Better ask for confirmation. */
if (search_control_file (fp, hexgrip, &disabled, NULL, &confirm)
|| disabled)
confirm = 0; /* If not found or disabled, there is no reason to
ask for confirmation. */
fclose (fp);
return confirm;
}
@ -1588,6 +1648,7 @@ ssh_key_grip (gcry_sexp_t key, unsigned char *buffer)
return 0;
}
/* Converts the secret key KEY_SECRET into a public key, storing it in
KEY_PUBLIC. SPEC is the according key specification. Returns zero
on success or an error code. */
@ -1909,7 +1970,7 @@ ssh_handler_request_identities (ctrl_t ctrl,
hexgrip[40] = 0;
if ( strlen (hexgrip) != 40 )
continue;
if (search_control_file (ctrl_fp, hexgrip, &disabled, NULL)
if (search_control_file (ctrl_fp, hexgrip, &disabled, NULL, NULL)
|| disabled)
continue;
@ -2044,14 +2105,60 @@ data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder,
const char *elems;
size_t elems_n;
gcry_mpi_t *mpis = NULL;
char hexgrip[40+1];
*sig = NULL;
*sig_n = 0;
/* Quick check to see whether we have a valid keygrip and convert it
to hex. */
if (!ctrl->have_keygrip)
{
err = gpg_error (GPG_ERR_NO_SECKEY);
goto out;
}
bin2hex (ctrl->keygrip, 20, hexgrip);
/* Ask for confirmation if needed. */
if (confirm_flag_from_sshcontrol (hexgrip))
{
gcry_sexp_t key;
char *fpr, *prompt;
char *comment = NULL;
err = agent_raw_key_from_file (ctrl, ctrl->keygrip, &key);
if (err)
goto out;
err = ssh_get_fingerprint_string (key, &fpr);
if (!err)
{
gcry_sexp_t tmpsxp = gcry_sexp_find_token (key, "comment", 0);
if (tmpsxp)
comment = gcry_sexp_nth_string (tmpsxp, 1);
gcry_sexp_release (tmpsxp);
}
gcry_sexp_release (key);
if (err)
goto out;
prompt = xtryasprintf (_("An ssh process requested the use of key%%0A"
" %s%%0A"
" (%s)%%0A"
"Do you want to allow this?"),
fpr, comment? comment:"");
xfree (fpr);
gcry_free (comment);
err = agent_get_confirmation (ctrl, prompt, _("Allow"), _("Deny"), 0);
xfree (prompt);
if (err)
goto out;
}
/* Create signature. */
ctrl->use_auth_call = 1;
err = agent_pksign_do (ctrl, NULL,
_("Please enter the passphrase "
"for the ssh key%0A %c"), &signature_sexp,
"for the ssh key%%0A %F%%0A (%c)"),
&signature_sexp,
CACHE_MODE_SSH, ttl_from_sshcontrol);
ctrl->use_auth_call = 0;
if (err)
@ -2370,7 +2477,7 @@ reenter_compare_cb (struct pin_entry_info_s *pi)
our key storage, don't do anything. When entering a new key also
add an entry to the sshcontrol file. */
static gpg_error_t
ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl)
ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl, int confirm)
{
gpg_error_t err;
unsigned char key_grip_raw[20];
@ -2380,6 +2487,7 @@ ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl)
char *description = NULL;
const char *description2 = _("Please re-enter this passphrase");
char *comment = NULL;
char *key_fpr = NULL;
const char *initial_errtext = NULL;
unsigned int i;
struct pin_entry_info_s *pi = NULL, *pi2;
@ -2393,6 +2501,9 @@ ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl)
if ( !agent_key_available (key_grip_raw) )
goto out; /* Yes, key is available. */
err = ssh_get_fingerprint_string (key, &key_fpr);
if (err)
goto out;
err = ssh_key_extract_comment (key, &comment);
if (err)
@ -2402,8 +2513,9 @@ ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl)
_("Please enter a passphrase to protect"
" the received secret key%%0A"
" %s%%0A"
" %s%%0A"
"within gpg-agent's key storage"),
comment ? comment : "?") < 0)
key_fpr, comment ? comment : "") < 0)
{
err = gpg_error_from_syserror ();
goto out;
@ -2460,7 +2572,7 @@ ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl)
goto out;
/* And add an entry to the sshcontrol file. */
err = add_control_entry (ctrl, key_grip, ttl);
err = add_control_entry (ctrl, key_grip, key_fpr, ttl, confirm);
out:
@ -2469,6 +2581,7 @@ ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl)
xfree (pi);
xfree (buffer);
xfree (comment);
xfree (key_fpr);
xfree (description);
return err;
@ -2553,9 +2666,7 @@ ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response)
if (err)
goto out;
/* FIXME: are constraints used correctly? */
err = ssh_identity_register (ctrl, key, ttl);
err = ssh_identity_register (ctrl, key, ttl, confirm);
out:

View File

@ -1,6 +1,6 @@
/* findkey.c - Locate the secret key
* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007,
* 2010 Free Software Foundation, Inc.
* 2010, 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
@ -33,6 +33,7 @@
#include "agent.h"
#include "i18n.h"
#include "../common/ssh-utils.h"
#ifndef O_BINARY
#define O_BINARY 0
@ -185,12 +186,14 @@ try_unprotect_cb (struct pin_entry_info_s *pi)
%% - Replaced by a single %
%c - Replaced by the content of COMMENT.
%F - Replaced by an ssh style fingerprint computed from KEY.
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, char **result)
modify_description (const char *in, const char *comment, const gcry_sexp_t key,
char **result)
{
size_t comment_length;
size_t in_len;
@ -198,6 +201,7 @@ modify_description (const char *in, const char *comment, char **result)
char *out;
size_t i;
int special, pass;
char *ssh_fpr = NULL;
comment_length = strlen (comment);
in_len = strlen (in);
@ -233,6 +237,18 @@ modify_description (const char *in, const char *comment, char **result)
out_len += comment_length;
break;
case 'F': /* SSH style fingerprint. */
if (!ssh_fpr && key)
ssh_get_fingerprint_string (key, &ssh_fpr);
if (ssh_fpr)
{
if (out)
out = stpcpy (out, ssh_fpr);
else
out_len += strlen (ssh_fpr);
}
break;
default: /* Invalid special sequences are kept as they are. */
if (out)
{
@ -259,12 +275,16 @@ modify_description (const char *in, const char *comment, char **result)
{
*result = out = xtrymalloc (out_len + 1);
if (!out)
return gpg_error_from_syserror ();
{
xfree (ssh_fpr);
return gpg_error_from_syserror ();
}
}
}
*out = 0;
assert (*result + out_len == out);
xfree (ssh_fpr);
return 0;
}
@ -564,45 +584,26 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
break; /* no unprotection needed */
case PRIVATE_KEY_PROTECTED:
{
gcry_sexp_t comment_sexp;
size_t comment_length;
char *desc_text_final;
const char *comment = NULL;
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. */
comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
if (comment_sexp)
comment = gcry_sexp_nth_data (comment_sexp, 1, &comment_length);
if (!comment)
{
comment = "";
comment_length = 0;
}
{
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);
}
desc_text_final = NULL;
if (desc_text)
{
if (comment[comment_length])
{
/* Not a C-string; create one. We might here allocate
more than actually displayed but well, that
shouldn't be a problem. */
char *tmp = xtrymalloc (comment_length+1);
if (!tmp)
rc = gpg_error_from_syserror ();
else
{
memcpy (tmp, comment, comment_length);
tmp[comment_length] = 0;
rc = modify_description (desc_text, tmp, &desc_text_final);
xfree (tmp);
}
}
else
rc = modify_description (desc_text, comment, &desc_text_final);
}
rc = modify_description (desc_text, comment? comment:"", s_skey,
&desc_text_final);
gcry_free (comment);
if (!rc)
{
@ -613,7 +614,6 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
gpg_strerror (rc));
}
gcry_sexp_release (comment_sexp);
xfree (desc_text_final);
}
break;
@ -793,6 +793,28 @@ agent_is_dsa_key (gcry_sexp_t s_key)
/* Return the key for the keygrip GRIP. The result is stored at
RESULT. This function extracts the key from the private key
database and returns it as an S-expression object as it is. On
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)
{
gpg_error_t err;
gcry_sexp_t s_skey;
(void)ctrl;
*result = NULL;
err = read_key_file (grip, &s_skey);
if (!err)
*result = s_skey;
return err;
}
/* Return the public key for the keygrip GRIP. The result is stored
at RESULT. This function extracts the public key from the private
key database. On failure an error code is returned and NULL stored

View File

@ -27,13 +27,6 @@
#include "util.h"
#include "ssh-utils.h"
#define pass() do { ; } while(0)
#define fail(a,e) \
do { fprintf (stderr, "%s:%d: test %d failed (%s)\n", \
__FILE__,__LINE__, (a), gpg_strerror (e)); \
exit (1); \
} while(0)
static struct { const char *key; const char *fpr; } sample_keys[] = {
{ "(protected-private-key "

View File

@ -646,6 +646,12 @@ digits, optionally followed by the caching TTL in seconds and another
optional field for arbitrary flags. A non-zero TTL overrides the global
default as set by @option{--default-cache-ttl-ssh}.
The only flag support is @code{confirm}. If this flag is found for a
key, each use of the key will pop up a pinentry to confirm the use of
that key. The flag is automatically set if a new key was loaded into
@code{gpg-agent} using the option @option{-c} of the @code{ssh-add}
command.
The keygrip may be prefixed with a @code{!} to disable an entry entry.
The following example lists exactly one key. Note that keys available
@ -653,8 +659,9 @@ through a OpenPGP smartcard in the active smartcard reader are
implicitly added to this list; i.e. there is no need to list them.
@example
# Key added on 2005-02-25 15:08:29
5A6592BF45DC73BD876874A28FD4639282E29B52 0
# Key added on: 2011-07-20 20:38:46
# Fingerprint: 5e:8d:c4:ad:e7:af:6e:27:8a:d6:13:e4:79:ad:0b:81
34B62F25E277CF13D3C6BCEBFD3F85D08F0A864B 0 confirm
@end example
@item private-keys-v1.d/