2007-08-28 17:48:13 +00:00
|
|
|
|
/* findkey.c - Locate the secret key
|
2010-04-21 16:26:17 +00:00
|
|
|
|
* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007,
|
2011-07-20 20:49:41 +02:00
|
|
|
|
* 2010, 2011 Free Software Foundation, Inc.
|
2019-05-07 11:08:26 +02:00
|
|
|
|
* Copyright (C) 2014, 2019 Werner Koch
|
2023-03-13 08:49:49 +01:00
|
|
|
|
* Copyright (C) 2023 g10 Code GmbH
|
2003-08-05 17:11:04 +00:00
|
|
|
|
*
|
|
|
|
|
* 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
|
2007-07-04 19:49:40 +00:00
|
|
|
|
* the Free Software Foundation; either version 3 of the License, or
|
2003-08-05 17:11:04 +00:00
|
|
|
|
* (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
|
2016-11-05 12:02:19 +01:00
|
|
|
|
* along with this program; if not, see <https://www.gnu.org/licenses/>.
|
2003-08-05 17:11:04 +00:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <ctype.h>
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <sys/stat.h>
|
Port to npth.
* configure.ac: Don't check for PTH but for NPTH.
(AH_BOTTOM): Remove PTH_SYSCALL_SOFT.
(have_pth): Rename to ...
(have_npth): ... this.
(USE_GNU_NPTH): Rename to ...
(USE_GNU_PTH): ... this.
* m4/npth.m4: New file.
* agent/Makefile.am, agent/cache.c, agent/call-pinentry.c,
agent/call-scd.c, agent/findkey.c, agent/gpg-agent.c,
agent/trustlist.c, common/Makefile.am, common/estream.c,
common/exechelp-posix.c, common/exechelp-w32.c,
common/exechelp-w32ce.c, common/http.c, common/init.c,
common/sysutils.c, dirmngr/Makefile.am, dirmngr/crlfetch.c,
dirmngr/dirmngr.c, dirmngr/dirmngr_ldap.c, dirmngr/ldap-wrapper-ce.c,
dirmngr/ldap-wrapper.c, dirmngr/ldap.c, g13/Makefile.am,
g13/call-gpg.c, g13/g13.c, g13/runner.c, scd/Makefile.am,
scd/apdu.c, scd/app.c, scd/ccid-driver.c, scd/command.c,
scd/scdaemon.c, tools/Makefile.am: Port to npth.
2012-01-03 22:12:37 +01:00
|
|
|
|
#include <npth.h> /* (we use pth_sleep) */
|
2003-08-05 17:11:04 +00:00
|
|
|
|
|
|
|
|
|
#include "agent.h"
|
agent: Resolve conflict of util.h.
* agent/Makefile.am (AM_CPPFLAGS): Remove -I$(top_srcdir)/common.
* agent/call-pinentry.c, agent/call-scd.c: Follow the change.
* agent/command-ssh.c, agent/command.c, agent/cvt-openpgp.c: Ditto.
* agent/divert-scd.c, agent/findkey.c, agent/genkey.c: Ditto.
* agent/gpg-agent.c, agent/pksign.c, agent/preset-passphrase.c: Ditto.
* agent/protect-tool.c, agent/protect.c, agent/trustlist.c: Ditto.
* agent/w32main.c: Ditto.
--
For openpty function, we need to include util.h on some OS.
We also have util.h in common/, so this change is needed.
Signed-off-by: NIIBE Yutaka <gniibe@fsij.org>
2017-03-07 19:22:48 +09:00
|
|
|
|
#include "../common/i18n.h"
|
2011-07-20 20:49:41 +02:00
|
|
|
|
#include "../common/ssh-utils.h"
|
2003-08-05 17:11:04 +00:00
|
|
|
|
|
2007-08-22 20:36:33 +00:00
|
|
|
|
#ifndef O_BINARY
|
|
|
|
|
#define O_BINARY 0
|
|
|
|
|
#endif
|
|
|
|
|
|
2003-08-05 17:11:04 +00:00
|
|
|
|
/* Helper to pass data to the check callback of the unprotect function. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
struct try_unprotect_arg_s
|
2007-08-28 17:48:13 +00:00
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
const unsigned char *protected_key;
|
|
|
|
|
unsigned char *unprotected_key;
|
2007-08-28 17:48:13 +00:00
|
|
|
|
int change_required; /* Set by the callback to indicate that the
|
2015-05-08 08:55:57 +02:00
|
|
|
|
user should change the passphrase. */
|
2003-08-05 17:11:04 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2022-08-16 12:33:26 +02:00
|
|
|
|
/* Replace all linefeeds in STRING by "%0A" and return a new malloced
|
2019-05-07 11:08:26 +02:00
|
|
|
|
* 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-03-13 08:49:49 +01:00
|
|
|
|
/* Write the S-expression formatted key (BUFFER,LENGTH) to our key
|
|
|
|
|
* storage. With FORCE 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 char *serialno, const char *keyref,
|
|
|
|
|
time_t timestamp)
|
2016-04-08 19:21:12 +02:00
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
2023-03-13 08:49:49 +01:00
|
|
|
|
char *fname;
|
|
|
|
|
estream_t fp;
|
|
|
|
|
char hexgrip[40+4+1];
|
|
|
|
|
int update, newkey;
|
2016-06-23 12:12:50 +02:00
|
|
|
|
nvc_t pk = NULL;
|
2016-04-08 19:21:12 +02:00
|
|
|
|
gcry_sexp_t key = NULL;
|
|
|
|
|
int remove = 0;
|
2019-05-03 15:54:54 +02:00
|
|
|
|
char *token = NULL;
|
2016-04-08 19:21:12 +02:00
|
|
|
|
|
2023-03-13 08:49:49 +01:00
|
|
|
|
bin2hex (grip, 20, hexgrip);
|
|
|
|
|
strcpy (hexgrip+40, ".key");
|
|
|
|
|
|
|
|
|
|
fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
|
|
|
|
|
hexgrip, NULL);
|
|
|
|
|
|
|
|
|
|
/* FIXME: Write to a temp file first so that write failures during
|
|
|
|
|
key updates won't lead to a key loss. */
|
|
|
|
|
|
|
|
|
|
if (!force && !gnupg_access (fname, F_OK))
|
|
|
|
|
{
|
|
|
|
|
log_error ("secret key file '%s' already exists\n", fname);
|
|
|
|
|
xfree (fname);
|
|
|
|
|
return gpg_error (GPG_ERR_EEXIST);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fp = es_fopen (fname, force? "rb+,mode=-rw" : "wbx,mode=-rw");
|
|
|
|
|
if (!fp)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t tmperr = gpg_error_from_syserror ();
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
update = 0;
|
|
|
|
|
newkey = 1;
|
|
|
|
|
}
|
|
|
|
|
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 ();
|
|
|
|
|
log_error ("error reading first byte from '%s': %s\n",
|
|
|
|
|
fname, strerror (errno));
|
|
|
|
|
xfree (fname);
|
|
|
|
|
es_fclose (fp);
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rc = es_fseek (fp, 0, SEEK_SET);
|
|
|
|
|
if (rc)
|
|
|
|
|
{
|
|
|
|
|
log_error ("error seeking in '%s': %s\n", fname, strerror (errno));
|
|
|
|
|
xfree (fname);
|
|
|
|
|
es_fclose (fp);
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (first == '(')
|
|
|
|
|
{
|
|
|
|
|
/* Key is still in the old format - force it into extended
|
|
|
|
|
* format. We do not request an update here because an
|
|
|
|
|
* existing key is not yet in extended key format and no
|
|
|
|
|
* extended infos are yet available. */
|
|
|
|
|
update = 0;
|
|
|
|
|
newkey = 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Key is already in the extended format. */
|
|
|
|
|
update = 1;
|
|
|
|
|
newkey = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* The key file did not exist: we assume this is a new key and
|
|
|
|
|
* write the Created: entry. */
|
|
|
|
|
update = 0;
|
|
|
|
|
newkey = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-03-24 10:30:17 +01:00
|
|
|
|
if (update)
|
2016-04-08 19:21:12 +02:00
|
|
|
|
{
|
2017-03-24 10:30:17 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
2016-04-08 19:21:12 +02:00
|
|
|
|
}
|
2017-03-24 10:30:17 +01:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
pk = nvc_new_private_key ();
|
|
|
|
|
if (!pk)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
es_clearerr (fp);
|
2016-04-08 19:21:12 +02:00
|
|
|
|
|
2023-03-13 08:49:49 +01:00
|
|
|
|
/* Turn (BUFFER,LENGTH) into a gcrypt s-expression and set it into
|
|
|
|
|
* our name value container. */
|
|
|
|
|
err = gcry_sexp_sscan (&key, NULL, buffer, length);
|
2016-04-08 19:21:12 +02:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
2016-06-23 12:12:50 +02:00
|
|
|
|
err = nvc_set_private_key (pk, key);
|
2016-04-08 19:21:12 +02:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
2019-05-03 15:54:54 +02:00
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-13 08:49:49 +01:00
|
|
|
|
/* If a timestamp has been supplied and the key is new, write a
|
2020-08-17 14:21:00 +02:00
|
|
|
|
* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-13 08:49:49 +01:00
|
|
|
|
/* Back to start and write. */
|
2016-04-08 19:21:12 +02:00
|
|
|
|
err = es_fseek (fp, 0, SEEK_SET);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
2016-06-23 12:12:50 +02:00
|
|
|
|
err = nvc_write (pk, fp);
|
2022-06-23 11:05:51 +09:00
|
|
|
|
if (!err)
|
|
|
|
|
err = es_fflush (fp);
|
2016-04-08 19:21:12 +02:00
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
|
|
|
|
|
remove = 1;
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ftruncate (es_fileno (fp), es_ftello (fp)))
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error truncating '%s': %s\n", fname, gpg_strerror (err));
|
|
|
|
|
remove = 1;
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (es_fclose (fp))
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error closing '%s': %s\n", fname, gpg_strerror (err));
|
|
|
|
|
remove = 1;
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
fp = NULL;
|
|
|
|
|
|
|
|
|
|
bump_key_eventcounter ();
|
|
|
|
|
|
|
|
|
|
leave:
|
2017-03-24 10:30:17 +01:00
|
|
|
|
es_fclose (fp);
|
2016-04-08 19:21:12 +02:00
|
|
|
|
if (remove)
|
|
|
|
|
gnupg_remove (fname);
|
|
|
|
|
xfree (fname);
|
|
|
|
|
gcry_sexp_release (key);
|
2016-06-23 12:12:50 +02:00
|
|
|
|
nvc_release (pk);
|
2019-05-03 15:54:54 +02:00
|
|
|
|
xfree (token);
|
2016-04-08 19:21:12 +02:00
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
2003-08-05 17:11:04 +00:00
|
|
|
|
|
2022-06-22 15:45:18 +09:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-13 08:49:49 +01:00
|
|
|
|
|
2010-10-01 20:33:53 +00:00
|
|
|
|
/* Callback function to try the unprotection from the passphrase query
|
2003-08-05 17:11:04 +00:00
|
|
|
|
code. */
|
2015-10-09 11:33:13 +09:00
|
|
|
|
static gpg_error_t
|
2003-08-05 17:11:04 +00:00
|
|
|
|
try_unprotect_cb (struct pin_entry_info_s *pi)
|
|
|
|
|
{
|
|
|
|
|
struct try_unprotect_arg_s *arg = pi->check_cb_arg;
|
2015-06-30 21:58:02 +02:00
|
|
|
|
ctrl_t ctrl = arg->ctrl;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
size_t dummy;
|
2007-08-28 17:48:13 +00:00
|
|
|
|
gpg_error_t err;
|
|
|
|
|
gnupg_isotime_t now, protected_at, tmptime;
|
|
|
|
|
char *desc = NULL;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
|
2019-05-14 10:31:46 +02:00
|
|
|
|
log_assert (!arg->unprotected_key);
|
2007-08-28 17:48:13 +00:00
|
|
|
|
|
|
|
|
|
arg->change_required = 0;
|
2015-06-30 21:58:02 +02:00
|
|
|
|
err = agent_unprotect (ctrl, arg->protected_key, pi->pin, protected_at,
|
2007-08-28 17:48:13 +00:00
|
|
|
|
&arg->unprotected_key, &dummy);
|
|
|
|
|
if (err)
|
|
|
|
|
return err;
|
2015-06-30 21:58:02 +02:00
|
|
|
|
if (!opt.max_passphrase_days || ctrl->in_passwd)
|
2007-08-28 17:48:13 +00:00
|
|
|
|
return 0; /* No regular passphrase change required. */
|
|
|
|
|
|
|
|
|
|
if (!*protected_at)
|
|
|
|
|
{
|
|
|
|
|
/* No protection date known - must force passphrase change. */
|
2015-06-30 21:58:02 +02:00
|
|
|
|
desc = xtrystrdup (L_("Note: This passphrase has never been changed.%0A"
|
|
|
|
|
"Please change it now."));
|
2007-08-28 17:48:13 +00:00
|
|
|
|
if (!desc)
|
|
|
|
|
return gpg_error_from_syserror ();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
gnupg_get_isotime (now);
|
|
|
|
|
gnupg_copy_time (tmptime, protected_at);
|
|
|
|
|
err = add_days_to_isotime (tmptime, opt.max_passphrase_days);
|
|
|
|
|
if (err)
|
|
|
|
|
return err;
|
|
|
|
|
if (strcmp (now, tmptime) > 0 )
|
|
|
|
|
{
|
|
|
|
|
/* Passphrase "expired". */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
desc = xtryasprintf
|
2015-06-30 21:58:02 +02:00
|
|
|
|
(L_("This passphrase has not been changed%%0A"
|
|
|
|
|
"since %.4s-%.2s-%.2s. Please change it now."),
|
2007-08-28 17:48:13 +00:00
|
|
|
|
protected_at, protected_at+4, protected_at+6);
|
|
|
|
|
if (!desc)
|
|
|
|
|
return gpg_error_from_syserror ();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (desc)
|
|
|
|
|
{
|
|
|
|
|
/* Change required. */
|
|
|
|
|
if (opt.enforce_passphrase_constraints)
|
|
|
|
|
{
|
2015-06-30 21:58:02 +02:00
|
|
|
|
err = agent_get_confirmation (ctrl, desc,
|
|
|
|
|
L_("Change passphrase"), NULL, 0);
|
2007-08-28 17:48:13 +00:00
|
|
|
|
if (!err)
|
|
|
|
|
arg->change_required = 1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-06-30 21:58:02 +02:00
|
|
|
|
err = agent_get_confirmation (ctrl, desc,
|
|
|
|
|
L_("Change passphrase"),
|
|
|
|
|
L_("I'll change it later"), 0);
|
2007-08-28 17:48:13 +00:00
|
|
|
|
if (!err)
|
|
|
|
|
arg->change_required = 1;
|
2010-10-13 15:57:08 +00:00
|
|
|
|
else if (gpg_err_code (err) == GPG_ERR_CANCELED
|
|
|
|
|
|| gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
|
2007-08-28 17:48:13 +00:00
|
|
|
|
err = 0;
|
|
|
|
|
}
|
|
|
|
|
xfree (desc);
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-14 12:54:06 +09:00
|
|
|
|
return err;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2019-05-07 11:50:38 +02:00
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2005-01-26 22:20:21 +00:00
|
|
|
|
/* Modify a Key description, replacing certain special format
|
|
|
|
|
characters. List of currently supported replacements:
|
|
|
|
|
|
2005-02-03 17:40:02 +00:00
|
|
|
|
%% - Replaced by a single %
|
|
|
|
|
%c - Replaced by the content of COMMENT.
|
2014-04-15 16:40:48 +02:00
|
|
|
|
%C - Same as %c but put into parentheses.
|
2011-07-20 20:49:41 +02:00
|
|
|
|
%F - Replaced by an ssh style fingerprint computed from KEY.
|
2005-01-26 22:20:21 +00:00
|
|
|
|
|
2005-02-03 17:40:02 +00:00
|
|
|
|
The functions returns 0 on success or an error code. On success a
|
|
|
|
|
newly allocated string is stored at the address of RESULT.
|
|
|
|
|
*/
|
2017-02-22 11:04:55 +01:00
|
|
|
|
gpg_error_t
|
|
|
|
|
agent_modify_description (const char *in, const char *comment,
|
|
|
|
|
const gcry_sexp_t key, char **result)
|
2005-02-03 17:40:02 +00:00
|
|
|
|
{
|
|
|
|
|
size_t comment_length;
|
|
|
|
|
size_t in_len;
|
|
|
|
|
size_t out_len;
|
|
|
|
|
char *out;
|
|
|
|
|
size_t i;
|
|
|
|
|
int special, pass;
|
2011-07-20 20:49:41 +02:00
|
|
|
|
char *ssh_fpr = NULL;
|
2017-02-22 11:04:55 +01:00
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
|
|
*result = NULL;
|
|
|
|
|
|
|
|
|
|
if (!comment)
|
|
|
|
|
comment = "";
|
2005-02-03 17:40:02 +00:00
|
|
|
|
|
|
|
|
|
comment_length = strlen (comment);
|
|
|
|
|
in_len = strlen (in);
|
|
|
|
|
|
|
|
|
|
/* First pass calculates the length, second pass does the actual
|
|
|
|
|
copying. */
|
2017-02-22 11:04:55 +01:00
|
|
|
|
/* FIXME: This can be simplified by using es_fopenmem. */
|
2005-02-03 17:40:02 +00:00
|
|
|
|
out = NULL;
|
|
|
|
|
out_len = 0;
|
|
|
|
|
for (pass=0; pass < 2; pass++)
|
2005-01-26 22:20:21 +00:00
|
|
|
|
{
|
2005-02-03 17:40:02 +00:00
|
|
|
|
special = 0;
|
|
|
|
|
for (i = 0; i < in_len; i++)
|
|
|
|
|
{
|
2005-02-25 16:14:55 +00:00
|
|
|
|
if (special)
|
2005-02-03 17:40:02 +00:00
|
|
|
|
{
|
|
|
|
|
special = 0;
|
|
|
|
|
switch (in[i])
|
|
|
|
|
{
|
|
|
|
|
case '%':
|
|
|
|
|
if (out)
|
|
|
|
|
*out++ = '%';
|
2005-02-15 16:23:45 +00:00
|
|
|
|
else
|
|
|
|
|
out_len++;
|
2005-02-03 17:40:02 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'c': /* Comment. */
|
2005-02-15 16:23:45 +00:00
|
|
|
|
if (out)
|
2005-02-03 17:40:02 +00:00
|
|
|
|
{
|
|
|
|
|
memcpy (out, comment, comment_length);
|
|
|
|
|
out += comment_length;
|
|
|
|
|
}
|
2005-02-15 16:23:45 +00:00
|
|
|
|
else
|
|
|
|
|
out_len += comment_length;
|
2005-02-03 17:40:02 +00:00
|
|
|
|
break;
|
|
|
|
|
|
2014-04-15 16:40:48 +02:00
|
|
|
|
case 'C': /* Comment. */
|
|
|
|
|
if (!comment_length)
|
|
|
|
|
;
|
|
|
|
|
else if (out)
|
|
|
|
|
{
|
|
|
|
|
*out++ = '(';
|
|
|
|
|
memcpy (out, comment, comment_length);
|
|
|
|
|
out += comment_length;
|
|
|
|
|
*out++ = ')';
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
out_len += comment_length + 2;
|
|
|
|
|
break;
|
|
|
|
|
|
2011-07-20 20:49:41 +02:00
|
|
|
|
case 'F': /* SSH style fingerprint. */
|
|
|
|
|
if (!ssh_fpr && key)
|
2017-05-24 17:48:42 +02:00
|
|
|
|
ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest,
|
|
|
|
|
&ssh_fpr);
|
2011-07-20 20:49:41 +02:00
|
|
|
|
if (ssh_fpr)
|
|
|
|
|
{
|
|
|
|
|
if (out)
|
|
|
|
|
out = stpcpy (out, ssh_fpr);
|
|
|
|
|
else
|
|
|
|
|
out_len += strlen (ssh_fpr);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2005-02-25 16:14:55 +00:00
|
|
|
|
default: /* Invalid special sequences are kept as they are. */
|
|
|
|
|
if (out)
|
|
|
|
|
{
|
|
|
|
|
*out++ = '%';
|
|
|
|
|
*out++ = in[i];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
out_len+=2;
|
2005-02-03 17:40:02 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2005-02-25 16:14:55 +00:00
|
|
|
|
else if (in[i] == '%')
|
|
|
|
|
special = 1;
|
2005-02-03 17:40:02 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (out)
|
|
|
|
|
*out++ = in[i];
|
2005-02-15 16:23:45 +00:00
|
|
|
|
else
|
|
|
|
|
out_len++;
|
2005-02-03 17:40:02 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2005-02-03 17:40:02 +00:00
|
|
|
|
if (!pass)
|
|
|
|
|
{
|
|
|
|
|
*result = out = xtrymalloc (out_len + 1);
|
|
|
|
|
if (!out)
|
2011-07-20 20:49:41 +02:00
|
|
|
|
{
|
|
|
|
|
xfree (ssh_fpr);
|
|
|
|
|
return gpg_error_from_syserror ();
|
|
|
|
|
}
|
2005-02-03 17:40:02 +00:00
|
|
|
|
}
|
2005-01-26 22:20:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
2005-02-03 17:40:02 +00:00
|
|
|
|
*out = 0;
|
2017-02-22 11:04:55 +01:00
|
|
|
|
log_assert (*result + out_len == out);
|
2011-07-20 20:49:41 +02:00
|
|
|
|
xfree (ssh_fpr);
|
2017-02-22 11:04:55 +01:00
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2005-02-03 17:40:02 +00:00
|
|
|
|
return 0;
|
2005-01-26 22:20:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2005-01-26 22:20:21 +00:00
|
|
|
|
|
2003-08-05 17:11:04 +00:00
|
|
|
|
/* Unprotect the canconical encoded S-expression key in KEYBUF. GRIP
|
|
|
|
|
should be the hex encoded keygrip of that key to be used with the
|
2004-02-13 17:06:34 +00:00
|
|
|
|
caching mechanism. DESC_TEXT may be set to override the default
|
2009-05-15 11:16:28 +00:00
|
|
|
|
description used for the pinentry. If LOOKUP_TTL is given this
|
2010-10-01 20:33:53 +00:00
|
|
|
|
function is used to lookup the default ttl. If R_PASSPHRASE is not
|
|
|
|
|
NULL, the function succeeded and the key was protected the used
|
|
|
|
|
passphrase (entered or from the cache) is stored there; if not NULL
|
|
|
|
|
will be stored. The caller needs to free the returned
|
|
|
|
|
passphrase. */
|
2017-07-28 10:37:33 +02:00
|
|
|
|
static gpg_error_t
|
2010-09-01 12:49:05 +00:00
|
|
|
|
unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
|
2011-02-04 12:57:53 +01:00
|
|
|
|
unsigned char **keybuf, const unsigned char *grip,
|
2010-10-01 20:33:53 +00:00
|
|
|
|
cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
|
|
|
|
|
char **r_passphrase)
|
2003-08-05 17:11:04 +00:00
|
|
|
|
{
|
|
|
|
|
struct pin_entry_info_s *pi;
|
|
|
|
|
struct try_unprotect_arg_s arg;
|
2009-03-06 17:31:27 +00:00
|
|
|
|
int rc;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
unsigned char *result;
|
|
|
|
|
size_t resultlen;
|
|
|
|
|
char hexgrip[40+1];
|
2010-10-01 20:33:53 +00:00
|
|
|
|
|
|
|
|
|
if (r_passphrase)
|
|
|
|
|
*r_passphrase = NULL;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2009-03-06 17:31:27 +00:00
|
|
|
|
bin2hex (grip, 20, hexgrip);
|
2003-08-05 17:11:04 +00:00
|
|
|
|
|
2010-09-01 12:49:05 +00:00
|
|
|
|
/* Initially try to get it using a cache nonce. */
|
|
|
|
|
if (cache_nonce)
|
|
|
|
|
{
|
2010-09-02 10:46:23 +00:00
|
|
|
|
char *pw;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2018-03-27 08:40:58 +02:00
|
|
|
|
pw = agent_get_cache (ctrl, cache_nonce, CACHE_MODE_NONCE);
|
2010-09-01 12:49:05 +00:00
|
|
|
|
if (pw)
|
|
|
|
|
{
|
2013-05-22 09:50:12 +01:00
|
|
|
|
rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
|
2010-09-01 12:49:05 +00:00
|
|
|
|
if (!rc)
|
|
|
|
|
{
|
2010-10-01 20:33:53 +00:00
|
|
|
|
if (r_passphrase)
|
|
|
|
|
*r_passphrase = pw;
|
|
|
|
|
else
|
|
|
|
|
xfree (pw);
|
2010-09-01 12:49:05 +00:00
|
|
|
|
xfree (*keybuf);
|
|
|
|
|
*keybuf = result;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2010-10-01 20:33:53 +00:00
|
|
|
|
xfree (pw);
|
2010-09-01 12:49:05 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2004-02-03 16:24:37 +00:00
|
|
|
|
/* First try to get it from the cache - if there is none or we can't
|
2003-08-05 17:11:04 +00:00
|
|
|
|
unprotect it, we fall back to ask the user */
|
2005-06-07 19:09:18 +00:00
|
|
|
|
if (cache_mode != CACHE_MODE_IGNORE)
|
2003-08-05 17:11:04 +00:00
|
|
|
|
{
|
2010-09-02 10:46:23 +00:00
|
|
|
|
char *pw;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2006-10-19 14:22:06 +00:00
|
|
|
|
retry:
|
2018-03-27 08:40:58 +02:00
|
|
|
|
pw = agent_get_cache (ctrl, hexgrip, cache_mode);
|
2003-08-05 17:11:04 +00:00
|
|
|
|
if (pw)
|
|
|
|
|
{
|
2013-05-22 09:50:12 +01:00
|
|
|
|
rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
|
2003-08-05 17:11:04 +00:00
|
|
|
|
if (!rc)
|
|
|
|
|
{
|
2014-09-17 15:12:08 +02:00
|
|
|
|
if (cache_mode == CACHE_MODE_NORMAL)
|
|
|
|
|
agent_store_cache_hit (hexgrip);
|
2010-10-01 20:33:53 +00:00
|
|
|
|
if (r_passphrase)
|
|
|
|
|
*r_passphrase = pw;
|
|
|
|
|
else
|
|
|
|
|
xfree (pw);
|
2003-08-05 17:11:04 +00:00
|
|
|
|
xfree (*keybuf);
|
|
|
|
|
*keybuf = result;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2010-10-01 20:33:53 +00:00
|
|
|
|
xfree (pw);
|
2003-08-05 17:11:04 +00:00
|
|
|
|
}
|
2014-09-17 15:12:08 +02:00
|
|
|
|
else if (cache_mode == CACHE_MODE_NORMAL)
|
|
|
|
|
{
|
|
|
|
|
/* The standard use of GPG keys is to have a signing and an
|
|
|
|
|
encryption subkey. Commonly both use the same
|
|
|
|
|
passphrase. We try to help the user to enter the
|
|
|
|
|
passphrase only once by silently trying the last
|
|
|
|
|
correctly entered passphrase. Checking one additional
|
|
|
|
|
passphrase should be acceptable; despite the S2K
|
|
|
|
|
introduced delays. The assumed workflow is:
|
|
|
|
|
|
|
|
|
|
1. Read encrypted message in a MUA and thus enter a
|
|
|
|
|
passphrase for the encryption subkey.
|
|
|
|
|
|
|
|
|
|
2. Reply to that mail with an encrypted and signed
|
|
|
|
|
mail, thus entering the passphrase for the signing
|
|
|
|
|
subkey.
|
|
|
|
|
|
|
|
|
|
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. */
|
2018-03-27 08:40:58 +02:00
|
|
|
|
pw = agent_get_cache (ctrl, NULL, cache_mode);
|
2014-09-17 15:12:08 +02:00
|
|
|
|
if (pw)
|
|
|
|
|
{
|
|
|
|
|
rc = agent_unprotect (ctrl, *keybuf, pw, NULL,
|
|
|
|
|
&result, &resultlen);
|
|
|
|
|
if (!rc)
|
|
|
|
|
{
|
|
|
|
|
if (r_passphrase)
|
|
|
|
|
*r_passphrase = pw;
|
|
|
|
|
else
|
|
|
|
|
xfree (pw);
|
|
|
|
|
xfree (*keybuf);
|
|
|
|
|
*keybuf = result;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
xfree (pw);
|
|
|
|
|
}
|
|
|
|
|
}
|
2006-10-19 14:22:06 +00:00
|
|
|
|
|
|
|
|
|
/* If the pinentry is currently in use, we wait up to 60 seconds
|
2007-08-28 17:48:13 +00:00
|
|
|
|
for it to close and check the cache again. This solves a common
|
2006-10-19 14:22:06 +00:00
|
|
|
|
situation where several requests for unprotecting a key have
|
|
|
|
|
been made but the user is still entering the passphrase for
|
|
|
|
|
the first request. Because all requests to agent_askpin are
|
|
|
|
|
serialized they would then pop up one after the other to
|
|
|
|
|
request the passphrase - despite that the user has already
|
|
|
|
|
entered it and is then available in the cache. This
|
|
|
|
|
implementation is not race free but in the worst case the
|
|
|
|
|
user has to enter the passphrase only once more. */
|
|
|
|
|
if (pinentry_active_p (ctrl, 0))
|
|
|
|
|
{
|
|
|
|
|
/* Active - wait */
|
|
|
|
|
if (!pinentry_active_p (ctrl, 60))
|
|
|
|
|
{
|
|
|
|
|
/* We need to give the other thread a chance to actually put
|
|
|
|
|
it into the cache. */
|
2021-10-05 14:05:56 +09:00
|
|
|
|
gnupg_sleep (1);
|
2006-10-19 14:22:06 +00:00
|
|
|
|
goto retry;
|
|
|
|
|
}
|
|
|
|
|
/* Timeout - better call pinentry now the plain way. */
|
|
|
|
|
}
|
2003-08-05 17:11:04 +00:00
|
|
|
|
}
|
2007-08-28 17:48:13 +00:00
|
|
|
|
|
2015-08-24 16:14:09 +02:00
|
|
|
|
pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
|
2005-02-23 21:06:32 +00:00
|
|
|
|
if (!pi)
|
2006-09-14 16:50:33 +00:00
|
|
|
|
return gpg_error_from_syserror ();
|
2015-08-24 16:14:09 +02:00
|
|
|
|
pi->max_length = MAX_PASSPHRASE_LEN + 1;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
pi->min_digits = 0; /* we want a real passphrase */
|
2009-03-05 19:19:37 +00:00
|
|
|
|
pi->max_digits = 16;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
pi->max_tries = 3;
|
|
|
|
|
pi->check_cb = try_unprotect_cb;
|
2007-08-28 17:48:13 +00:00
|
|
|
|
arg.ctrl = ctrl;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
arg.protected_key = *keybuf;
|
|
|
|
|
arg.unprotected_key = NULL;
|
2007-08-28 17:48:13 +00:00
|
|
|
|
arg.change_required = 0;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
pi->check_cb_arg = &arg;
|
|
|
|
|
|
2015-04-14 18:41:05 +02:00
|
|
|
|
rc = agent_askpin (ctrl, desc_text, NULL, NULL, pi, hexgrip, cache_mode);
|
2019-01-28 12:58:13 +09:00
|
|
|
|
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
|
2003-08-05 17:11:04 +00:00
|
|
|
|
{
|
2019-05-14 10:31:46 +02:00
|
|
|
|
log_assert (arg.unprotected_key);
|
2007-08-28 17:48:13 +00:00
|
|
|
|
if (arg.change_required)
|
|
|
|
|
{
|
2015-05-08 08:55:57 +02:00
|
|
|
|
/* The callback told as that the user should change their
|
|
|
|
|
passphrase. Present the dialog to do. */
|
2007-08-28 17:48:13 +00:00
|
|
|
|
size_t canlen, erroff;
|
|
|
|
|
gcry_sexp_t s_skey;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2019-05-14 10:31:46 +02:00
|
|
|
|
log_assert (arg.unprotected_key);
|
2007-08-28 17:48:13 +00:00
|
|
|
|
canlen = gcry_sexp_canon_len (arg.unprotected_key, 0, NULL, NULL);
|
|
|
|
|
rc = gcry_sexp_sscan (&s_skey, &erroff,
|
|
|
|
|
(char*)arg.unprotected_key, canlen);
|
|
|
|
|
if (rc)
|
|
|
|
|
{
|
|
|
|
|
log_error ("failed to build S-Exp (off=%u): %s\n",
|
|
|
|
|
(unsigned int)erroff, gpg_strerror (rc));
|
|
|
|
|
wipememory (arg.unprotected_key, canlen);
|
|
|
|
|
xfree (arg.unprotected_key);
|
|
|
|
|
xfree (pi);
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
2010-10-26 09:10:29 +00:00
|
|
|
|
rc = agent_protect_and_store (ctrl, s_skey, NULL);
|
2007-08-28 17:48:13 +00:00
|
|
|
|
gcry_sexp_release (s_skey);
|
|
|
|
|
if (rc)
|
|
|
|
|
{
|
2011-02-04 12:57:53 +01:00
|
|
|
|
log_error ("changing the passphrase failed: %s\n",
|
2007-08-28 17:48:13 +00:00
|
|
|
|
gpg_strerror (rc));
|
|
|
|
|
wipememory (arg.unprotected_key, canlen);
|
|
|
|
|
xfree (arg.unprotected_key);
|
|
|
|
|
xfree (pi);
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-10-01 20:33:53 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
2015-05-08 08:55:57 +02:00
|
|
|
|
/* Passphrase is fine. */
|
2018-03-27 08:40:58 +02:00
|
|
|
|
agent_put_cache (ctrl, hexgrip, cache_mode, pi->pin,
|
2010-10-01 20:33:53 +00:00
|
|
|
|
lookup_ttl? lookup_ttl (hexgrip) : 0);
|
2014-09-17 15:12:08 +02:00
|
|
|
|
agent_store_cache_hit (hexgrip);
|
2010-10-01 20:33:53 +00:00
|
|
|
|
if (r_passphrase && *pi->pin)
|
|
|
|
|
*r_passphrase = xtrystrdup (pi->pin);
|
|
|
|
|
}
|
2003-08-05 17:11:04 +00:00
|
|
|
|
xfree (*keybuf);
|
|
|
|
|
*keybuf = arg.unprotected_key;
|
|
|
|
|
}
|
|
|
|
|
xfree (pi);
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2005-02-23 21:06:32 +00:00
|
|
|
|
/* Read the key identified by GRIP from the private key directory and
|
2019-05-07 11:08:26 +02:00
|
|
|
|
* 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. */
|
2005-02-23 21:06:32 +00:00
|
|
|
|
static gpg_error_t
|
2019-05-07 11:08:26 +02:00
|
|
|
|
read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta)
|
2003-08-05 17:11:04 +00:00
|
|
|
|
{
|
2017-07-28 10:37:33 +02:00
|
|
|
|
gpg_error_t err;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
char *fname;
|
2010-04-14 11:24:02 +00:00
|
|
|
|
estream_t fp;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
struct stat st;
|
|
|
|
|
unsigned char *buf;
|
2005-02-23 21:06:32 +00:00
|
|
|
|
size_t buflen, erroff;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
gcry_sexp_t s_skey;
|
|
|
|
|
char hexgrip[40+4+1];
|
2016-04-08 19:21:12 +02:00
|
|
|
|
char first;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2004-01-16 17:39:58 +00:00
|
|
|
|
*result = NULL;
|
2019-05-07 11:08:26 +02:00
|
|
|
|
if (r_keymeta)
|
|
|
|
|
*r_keymeta = NULL;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
|
2009-03-06 17:31:27 +00:00
|
|
|
|
bin2hex (grip, 20, hexgrip);
|
2003-08-05 17:11:04 +00:00
|
|
|
|
strcpy (hexgrip+40, ".key");
|
|
|
|
|
|
2016-06-07 10:59:46 +02:00
|
|
|
|
fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
|
|
|
|
|
hexgrip, NULL);
|
2010-04-14 11:24:02 +00:00
|
|
|
|
fp = es_fopen (fname, "rb");
|
2003-08-05 17:11:04 +00:00
|
|
|
|
if (!fp)
|
|
|
|
|
{
|
2017-07-28 10:37:33 +02:00
|
|
|
|
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));
|
2003-08-05 17:11:04 +00:00
|
|
|
|
xfree (fname);
|
2017-07-28 10:37:33 +02:00
|
|
|
|
return err;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2016-04-08 19:21:12 +02:00
|
|
|
|
if (es_fread (&first, 1, 1, fp) != 1)
|
|
|
|
|
{
|
2017-07-28 10:37:33 +02:00
|
|
|
|
err = gpg_error_from_syserror ();
|
2016-04-08 19:21:12 +02:00
|
|
|
|
log_error ("error reading first byte from '%s': %s\n",
|
2017-07-28 10:37:33 +02:00
|
|
|
|
fname, gpg_strerror (err));
|
2016-04-08 19:21:12 +02:00
|
|
|
|
xfree (fname);
|
|
|
|
|
es_fclose (fp);
|
2017-07-28 10:37:33 +02:00
|
|
|
|
return err;
|
2016-04-08 19:21:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-28 10:37:33 +02:00
|
|
|
|
if (es_fseek (fp, 0, SEEK_SET))
|
2016-04-08 19:21:12 +02:00
|
|
|
|
{
|
2017-07-28 10:37:33 +02:00
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error seeking in '%s': %s\n", fname, gpg_strerror (err));
|
2016-04-08 19:21:12 +02:00
|
|
|
|
xfree (fname);
|
|
|
|
|
es_fclose (fp);
|
2017-07-28 10:37:33 +02:00
|
|
|
|
return err;
|
2016-04-08 19:21:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (first != '(')
|
|
|
|
|
{
|
|
|
|
|
/* Key is in extended format. */
|
2019-05-07 11:08:26 +02:00
|
|
|
|
nvc_t pk = NULL;
|
2016-04-08 19:21:12 +02:00
|
|
|
|
int line;
|
|
|
|
|
|
2017-07-28 10:37:33 +02:00
|
|
|
|
err = nvc_parse_private_key (&pk, &line, fp);
|
2016-04-08 19:21:12 +02:00
|
|
|
|
es_fclose (fp);
|
|
|
|
|
|
2017-07-28 10:37:33 +02:00
|
|
|
|
if (err)
|
2016-04-08 19:21:12 +02:00
|
|
|
|
log_error ("error parsing '%s' line %d: %s\n",
|
2017-07-28 10:37:33 +02:00
|
|
|
|
fname, line, gpg_strerror (err));
|
2016-04-08 19:21:12 +02:00
|
|
|
|
else
|
|
|
|
|
{
|
2017-07-28 10:37:33 +02:00
|
|
|
|
err = nvc_get_private_key (pk, result);
|
|
|
|
|
if (err)
|
2016-04-08 19:21:12 +02:00
|
|
|
|
log_error ("error getting private key from '%s': %s\n",
|
2017-07-28 10:37:33 +02:00
|
|
|
|
fname, gpg_strerror (err));
|
2019-05-07 11:08:26 +02:00
|
|
|
|
else
|
|
|
|
|
nvc_delete_named (pk, "Key:");
|
2016-04-08 19:21:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-07 11:08:26 +02:00
|
|
|
|
if (!err && r_keymeta)
|
|
|
|
|
*r_keymeta = pk;
|
|
|
|
|
else
|
|
|
|
|
nvc_release (pk);
|
2016-04-08 19:21:12 +02:00
|
|
|
|
xfree (fname);
|
2017-07-28 10:37:33 +02:00
|
|
|
|
return err;
|
2016-04-08 19:21:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
2010-04-14 11:24:02 +00:00
|
|
|
|
if (fstat (es_fileno (fp), &st))
|
2003-08-05 17:11:04 +00:00
|
|
|
|
{
|
2017-07-28 10:37:33 +02:00
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err));
|
2003-08-05 17:11:04 +00:00
|
|
|
|
xfree (fname);
|
2010-04-14 11:24:02 +00:00
|
|
|
|
es_fclose (fp);
|
2017-07-28 10:37:33 +02:00
|
|
|
|
return err;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buflen = st.st_size;
|
2005-02-23 21:06:32 +00:00
|
|
|
|
buf = xtrymalloc (buflen+1);
|
2010-04-14 11:24:02 +00:00
|
|
|
|
if (!buf)
|
|
|
|
|
{
|
2017-07-28 10:37:33 +02:00
|
|
|
|
err = gpg_error_from_syserror ();
|
2012-06-05 19:29:22 +02:00
|
|
|
|
log_error ("error allocating %zu bytes for '%s': %s\n",
|
2017-07-28 10:37:33 +02:00
|
|
|
|
buflen, fname, gpg_strerror (err));
|
2010-04-14 11:24:02 +00:00
|
|
|
|
xfree (fname);
|
|
|
|
|
es_fclose (fp);
|
|
|
|
|
xfree (buf);
|
2017-07-28 10:37:33 +02:00
|
|
|
|
return err;
|
2010-04-14 11:24:02 +00:00
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (es_fread (buf, buflen, 1, fp) != 1)
|
2003-08-05 17:11:04 +00:00
|
|
|
|
{
|
2017-07-28 10:37:33 +02:00
|
|
|
|
err = gpg_error_from_syserror ();
|
2012-06-05 19:29:22 +02:00
|
|
|
|
log_error ("error reading %zu bytes from '%s': %s\n",
|
2017-07-28 10:37:33 +02:00
|
|
|
|
buflen, fname, gpg_strerror (err));
|
2003-08-05 17:11:04 +00:00
|
|
|
|
xfree (fname);
|
2010-04-14 11:24:02 +00:00
|
|
|
|
es_fclose (fp);
|
2003-08-05 17:11:04 +00:00
|
|
|
|
xfree (buf);
|
2017-07-28 10:37:33 +02:00
|
|
|
|
return err;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2005-02-23 21:06:32 +00:00
|
|
|
|
/* Convert the file into a gcrypt S-expression object. */
|
2017-07-28 10:37:33 +02:00
|
|
|
|
err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
|
2003-08-05 17:11:04 +00:00
|
|
|
|
xfree (fname);
|
2010-04-14 11:24:02 +00:00
|
|
|
|
es_fclose (fp);
|
2003-08-05 17:11:04 +00:00
|
|
|
|
xfree (buf);
|
2017-07-28 10:37:33 +02:00
|
|
|
|
if (err)
|
2003-08-05 17:11:04 +00:00
|
|
|
|
{
|
|
|
|
|
log_error ("failed to build S-Exp (off=%u): %s\n",
|
2017-07-28 10:37:33 +02:00
|
|
|
|
(unsigned int)erroff, gpg_strerror (err));
|
|
|
|
|
return err;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
}
|
2005-02-23 21:06:32 +00:00
|
|
|
|
*result = s_skey;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2014-04-15 16:40:48 +02:00
|
|
|
|
/* Remove the key identified by GRIP from the private key directory. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
remove_key_file (const unsigned char *grip)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err = 0;
|
|
|
|
|
char *fname;
|
|
|
|
|
char hexgrip[40+4+1];
|
|
|
|
|
|
|
|
|
|
bin2hex (grip, 20, hexgrip);
|
|
|
|
|
strcpy (hexgrip+40, ".key");
|
2016-06-07 10:59:46 +02:00
|
|
|
|
fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
|
|
|
|
|
hexgrip, NULL);
|
2014-04-15 16:40:48 +02:00
|
|
|
|
if (gnupg_remove (fname))
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
xfree (fname);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2022-05-20 13:43:08 +09:00
|
|
|
|
/*
|
|
|
|
|
* Prompt a user the card insertion, when it's not available yet.
|
|
|
|
|
*/
|
|
|
|
|
static gpg_error_t
|
2022-05-20 14:38:33 +09:00
|
|
|
|
prompt_for_card (ctrl_t ctrl, const unsigned char *grip,
|
|
|
|
|
nvc_t keymeta, const unsigned char *shadow_info)
|
2022-05-20 13:43:08 +09:00
|
|
|
|
{
|
|
|
|
|
char *serialno;
|
|
|
|
|
char *desc;
|
2022-05-20 14:38:33 +09:00
|
|
|
|
char *want_sn = NULL;
|
2022-05-20 13:43:08 +09:00
|
|
|
|
int len;
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
char hexgrip[41];
|
2022-05-20 14:38:33 +09:00
|
|
|
|
char *comment_buffer = NULL;
|
|
|
|
|
const char *comment = NULL;
|
2022-05-27 09:59:54 +09:00
|
|
|
|
int refuse_prompt = 0;
|
2022-05-20 13:43:08 +09:00
|
|
|
|
|
|
|
|
|
bin2hex (grip, 20, hexgrip);
|
|
|
|
|
|
2022-05-27 09:59:54 +09:00
|
|
|
|
if (keymeta)
|
2022-05-20 13:43:08 +09:00
|
|
|
|
{
|
2022-05-27 09:59:54 +09:00
|
|
|
|
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;
|
|
|
|
|
}
|
2022-05-20 13:43:08 +09:00
|
|
|
|
}
|
2022-05-20 14:38:33 +09:00
|
|
|
|
|
|
|
|
|
err = parse_shadow_info (shadow_info, &want_sn, NULL, NULL);
|
|
|
|
|
if (err)
|
|
|
|
|
return err;
|
2022-05-20 13:43:08 +09:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2022-05-20 14:38:33 +09:00
|
|
|
|
/* Key for GRIP found, use it. */
|
2022-05-20 13:43:08 +09:00
|
|
|
|
agent_card_free_keyinfo (keyinfo);
|
2022-05-20 14:38:33 +09:00
|
|
|
|
break;
|
2022-05-20 13:43:08 +09:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-20 14:38:33 +09:00
|
|
|
|
/* Card is not available. Prompt the insertion. */
|
2022-05-27 09:59:54 +09:00
|
|
|
|
if (refuse_prompt)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-20 14:38:33 +09:00
|
|
|
|
if (asprintf (&desc,
|
2022-05-20 13:43:08 +09:00
|
|
|
|
"%s:%%0A%%0A"
|
2022-05-20 14:38:33 +09:00
|
|
|
|
" %s%%0A"
|
2022-05-20 13:43:08 +09:00
|
|
|
|
" %s",
|
|
|
|
|
L_("Please insert the card with serial number"),
|
2022-06-17 12:23:40 +02:00
|
|
|
|
want_sn ? want_sn : "",
|
|
|
|
|
comment? comment:"") < 0)
|
2022-05-20 14:38:33 +09:00
|
|
|
|
err = out_of_core ();
|
2022-05-20 13:43:08 +09:00
|
|
|
|
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)
|
2022-05-20 14:38:33 +09:00
|
|
|
|
break;
|
2022-05-20 13:43:08 +09:00
|
|
|
|
}
|
2022-05-20 14:38:33 +09:00
|
|
|
|
|
|
|
|
|
xfree (want_sn);
|
|
|
|
|
gcry_free (comment_buffer);
|
|
|
|
|
return err;
|
2022-05-20 13:43:08 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2005-02-23 21:06:32 +00:00
|
|
|
|
/* Return the secret key as an S-Exp in RESULT after locating it using
|
2022-05-19 14:04:33 +09:00
|
|
|
|
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
|
2014-03-04 11:54:59 +09:00
|
|
|
|
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.
|
2009-03-06 17:31:27 +00:00
|
|
|
|
CACHE_MODE defines now the cache shall be used. DESC_TEXT may be
|
2009-05-15 11:16:28 +00:00
|
|
|
|
set to present a custom description for the pinentry. LOOKUP_TTL
|
|
|
|
|
is an optional function to convey a TTL to the cache manager; we do
|
2010-09-01 11:07:16 +00:00
|
|
|
|
not simply pass the TTL value because the value is only needed if
|
|
|
|
|
an unprotect action was needed and looking up the TTL may have some
|
|
|
|
|
overhead (e.g. scanning the sshcontrol file). If a CACHE_NONCE is
|
2010-10-01 20:33:53 +00:00
|
|
|
|
given that cache item is first tried to get a passphrase. If
|
|
|
|
|
R_PASSPHRASE is not NULL, the function succeeded and the key was
|
|
|
|
|
protected the used passphrase (entered or from the cache) is stored
|
|
|
|
|
there; if not NULL will be stored. The caller needs to free the
|
|
|
|
|
returned passphrase. */
|
2005-02-23 21:06:32 +00:00
|
|
|
|
gpg_error_t
|
2010-09-01 11:07:16 +00:00
|
|
|
|
agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
|
|
|
|
|
const char *desc_text,
|
2005-02-23 21:06:32 +00:00
|
|
|
|
const unsigned char *grip, unsigned char **shadow_info,
|
2009-05-15 11:16:28 +00:00
|
|
|
|
cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
|
2022-03-25 14:10:46 +09:00
|
|
|
|
gcry_sexp_t *result, char **r_passphrase,
|
|
|
|
|
time_t *r_timestamp)
|
2005-02-23 21:06:32 +00:00
|
|
|
|
{
|
2017-07-28 10:37:33 +02:00
|
|
|
|
gpg_error_t err;
|
2005-02-23 21:06:32 +00:00
|
|
|
|
unsigned char *buf;
|
2020-06-05 10:35:33 +09:00
|
|
|
|
size_t len, erroff;
|
2005-02-23 21:06:32 +00:00
|
|
|
|
gcry_sexp_t s_skey;
|
2019-05-07 11:08:26 +02:00
|
|
|
|
nvc_t keymeta = NULL;
|
2019-05-07 11:50:38 +02:00
|
|
|
|
char *desc_text_buffer = NULL; /* Used in case we extend DESC_TEXT. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2005-02-23 21:06:32 +00:00
|
|
|
|
*result = NULL;
|
|
|
|
|
if (shadow_info)
|
2009-05-15 11:16:28 +00:00
|
|
|
|
*shadow_info = NULL;
|
2010-10-01 20:33:53 +00:00
|
|
|
|
if (r_passphrase)
|
|
|
|
|
*r_passphrase = NULL;
|
2022-03-25 14:10:46 +09:00
|
|
|
|
if (r_timestamp)
|
|
|
|
|
*r_timestamp = (time_t)(-1);
|
2005-02-23 21:06:32 +00:00
|
|
|
|
|
2022-05-19 14:04:33 +09:00
|
|
|
|
if (!grip && !ctrl->have_keygrip)
|
|
|
|
|
return gpg_error (GPG_ERR_NO_SECKEY);
|
|
|
|
|
|
|
|
|
|
err = read_key_file (grip? grip : ctrl->keygrip, &s_skey, &keymeta);
|
2023-03-01 16:49:40 +01:00
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
if (gpg_err_code (err) == GPG_ERR_ENOENT)
|
|
|
|
|
err = gpg_error (GPG_ERR_NO_SECKEY);
|
|
|
|
|
else
|
|
|
|
|
log_error ("findkey: error reading key file: %s\n",
|
|
|
|
|
gpg_strerror (err));
|
|
|
|
|
return err;
|
|
|
|
|
}
|
2005-02-23 21:06:32 +00:00
|
|
|
|
|
|
|
|
|
/* For use with the protection functions we also need the key as an
|
2009-03-06 17:31:27 +00:00
|
|
|
|
canonical encoded S-expression in a buffer. Create this buffer
|
2005-02-23 21:06:32 +00:00
|
|
|
|
now. */
|
2017-07-28 10:37:33 +02:00
|
|
|
|
err = make_canon_sexp (s_skey, &buf, &len);
|
|
|
|
|
if (err)
|
2019-05-07 11:08:26 +02:00
|
|
|
|
{
|
|
|
|
|
nvc_release (keymeta);
|
2019-05-07 11:50:38 +02:00
|
|
|
|
xfree (desc_text_buffer);
|
2019-05-07 11:08:26 +02:00
|
|
|
|
return err;
|
|
|
|
|
}
|
2003-08-05 17:11:04 +00:00
|
|
|
|
|
2022-03-25 14:10:46 +09:00
|
|
|
|
if (r_timestamp && keymeta)
|
|
|
|
|
{
|
|
|
|
|
const char *created = nvc_get_string (keymeta, "Created:");
|
|
|
|
|
|
|
|
|
|
if (created)
|
|
|
|
|
*r_timestamp = isotime2epoch (created);
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-19 14:37:01 +09:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2003-08-05 17:11:04 +00:00
|
|
|
|
switch (agent_private_key_type (buf))
|
|
|
|
|
{
|
|
|
|
|
case PRIVATE_KEY_CLEAR:
|
|
|
|
|
break; /* no unprotection needed */
|
2015-01-29 16:26:07 +01:00
|
|
|
|
case PRIVATE_KEY_OPENPGP_NONE:
|
|
|
|
|
{
|
|
|
|
|
unsigned char *buf_new;
|
|
|
|
|
size_t buf_newlen;
|
|
|
|
|
|
2017-07-28 10:37:33 +02:00
|
|
|
|
err = agent_unprotect (ctrl, buf, "", NULL, &buf_new, &buf_newlen);
|
|
|
|
|
if (err)
|
2015-01-29 16:26:07 +01:00
|
|
|
|
log_error ("failed to convert unprotected openpgp key: %s\n",
|
2017-07-28 10:37:33 +02:00
|
|
|
|
gpg_strerror (err));
|
2015-01-29 16:26:07 +01:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
xfree (buf);
|
|
|
|
|
buf = buf_new;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
case PRIVATE_KEY_PROTECTED:
|
2005-01-26 22:20:21 +00:00
|
|
|
|
{
|
|
|
|
|
char *desc_text_final;
|
2019-05-07 11:08:26 +02:00
|
|
|
|
char *comment_buffer = NULL;
|
|
|
|
|
const char *comment = NULL;
|
2005-02-03 17:40:02 +00:00
|
|
|
|
|
2005-02-23 21:06:32 +00:00
|
|
|
|
/* Note, that we will take the comment as a C string for
|
2019-05-07 11:08:26 +02:00
|
|
|
|
* display purposes; i.e. all stuff beyond a Nul character is
|
|
|
|
|
* ignored. If a "Label" entry is available in the meta data
|
2020-08-25 10:39:44 +02:00
|
|
|
|
* this is used instead of the s-expression comment. */
|
2019-05-07 11:08:26 +02:00
|
|
|
|
if (keymeta && (comment = nvc_get_string (keymeta, "Label:")))
|
|
|
|
|
{
|
|
|
|
|
if (strchr (comment, '\n')
|
|
|
|
|
&& (comment_buffer = linefeed_to_percent0A (comment)))
|
|
|
|
|
comment = comment_buffer;
|
2019-05-07 11:50:38 +02:00
|
|
|
|
/* 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;
|
|
|
|
|
}
|
2019-05-07 11:08:26 +02:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
gcry_sexp_t comment_sexp;
|
2011-07-20 20:49:41 +02:00
|
|
|
|
|
2019-05-07 11:08:26 +02:00
|
|
|
|
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;
|
|
|
|
|
}
|
2005-01-26 22:20:21 +00:00
|
|
|
|
|
2005-02-03 17:40:02 +00:00
|
|
|
|
desc_text_final = NULL;
|
2005-01-26 22:20:21 +00:00
|
|
|
|
if (desc_text)
|
2017-07-28 10:37:33 +02:00
|
|
|
|
err = agent_modify_description (desc_text, comment, s_skey,
|
|
|
|
|
&desc_text_final);
|
2019-05-07 11:08:26 +02:00
|
|
|
|
gcry_free (comment_buffer);
|
2005-01-26 22:20:21 +00:00
|
|
|
|
|
2022-05-19 14:04:33 +09:00
|
|
|
|
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));
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2005-01-26 22:20:21 +00:00
|
|
|
|
xfree (desc_text_final);
|
|
|
|
|
}
|
2003-08-05 17:11:04 +00:00
|
|
|
|
break;
|
|
|
|
|
case PRIVATE_KEY_SHADOWED:
|
|
|
|
|
if (shadow_info)
|
|
|
|
|
{
|
|
|
|
|
const unsigned char *s;
|
2022-05-20 13:43:08 +09:00
|
|
|
|
unsigned char *shadow_type;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
size_t n;
|
|
|
|
|
|
2022-05-20 13:43:08 +09:00
|
|
|
|
err = agent_get_shadow_info_type (buf, &s, &shadow_type);
|
2017-07-28 10:37:33 +02:00
|
|
|
|
if (!err)
|
2003-08-05 17:11:04 +00:00
|
|
|
|
{
|
|
|
|
|
n = gcry_sexp_canon_len (s, 0, NULL,NULL);
|
2017-07-28 10:37:33 +02:00
|
|
|
|
log_assert (n);
|
2003-08-05 17:11:04 +00:00
|
|
|
|
*shadow_info = xtrymalloc (n);
|
|
|
|
|
if (!*shadow_info)
|
2022-05-20 13:43:08 +09:00
|
|
|
|
{
|
|
|
|
|
err = out_of_core ();
|
|
|
|
|
goto shadow_error;
|
|
|
|
|
}
|
2003-08-05 17:11:04 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
memcpy (*shadow_info, s, n);
|
2022-05-20 13:43:08 +09:00
|
|
|
|
/*
|
|
|
|
|
* When it's a key on card (not on tpm2), maks sure
|
|
|
|
|
* it's available.
|
|
|
|
|
*/
|
|
|
|
|
if (strcmp (shadow_type, "t1-v1") == 0 && !grip)
|
2022-05-20 14:38:33 +09:00
|
|
|
|
err = prompt_for_card (ctrl, ctrl->keygrip,
|
|
|
|
|
keymeta, *shadow_info);
|
2003-08-05 17:11:04 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-20 13:43:08 +09:00
|
|
|
|
else
|
|
|
|
|
shadow_error:
|
2017-07-28 10:37:33 +02:00
|
|
|
|
log_error ("get_shadow_info failed: %s\n", gpg_strerror (err));
|
2022-05-20 13:43:08 +09:00
|
|
|
|
|
|
|
|
|
xfree (shadow_type);
|
2003-08-05 17:11:04 +00:00
|
|
|
|
}
|
2004-01-16 17:39:58 +00:00
|
|
|
|
else
|
2017-07-28 10:37:33 +02:00
|
|
|
|
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
|
2003-08-05 17:11:04 +00:00
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
log_error ("invalid private key format\n");
|
2017-07-28 10:37:33 +02:00
|
|
|
|
err = gpg_error (GPG_ERR_BAD_SECKEY);
|
2003-08-05 17:11:04 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
2005-02-23 21:06:32 +00:00
|
|
|
|
gcry_sexp_release (s_skey);
|
|
|
|
|
s_skey = NULL;
|
2017-07-28 10:37:33 +02:00
|
|
|
|
if (err)
|
2003-08-05 17:11:04 +00:00
|
|
|
|
{
|
|
|
|
|
xfree (buf);
|
2010-10-01 20:33:53 +00:00
|
|
|
|
if (r_passphrase)
|
|
|
|
|
{
|
|
|
|
|
xfree (*r_passphrase);
|
|
|
|
|
*r_passphrase = NULL;
|
|
|
|
|
}
|
2019-05-07 11:08:26 +02:00
|
|
|
|
nvc_release (keymeta);
|
2019-05-07 11:50:38 +02:00
|
|
|
|
xfree (desc_text_buffer);
|
2017-07-28 10:37:33 +02:00
|
|
|
|
return err;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-05 10:35:33 +09:00
|
|
|
|
err = sexp_sscan_private_key (result, &erroff, buf);
|
2003-08-05 17:11:04 +00:00
|
|
|
|
xfree (buf);
|
2020-06-05 10:35:33 +09:00
|
|
|
|
nvc_release (keymeta);
|
|
|
|
|
xfree (desc_text_buffer);
|
2017-07-28 10:37:33 +02:00
|
|
|
|
if (err)
|
2003-08-05 17:11:04 +00:00
|
|
|
|
{
|
|
|
|
|
log_error ("failed to build S-Exp (off=%u): %s\n",
|
2017-07-28 10:37:33 +02:00
|
|
|
|
(unsigned int)erroff, gpg_strerror (err));
|
2010-10-01 20:33:53 +00:00
|
|
|
|
if (r_passphrase)
|
|
|
|
|
{
|
|
|
|
|
xfree (*r_passphrase);
|
|
|
|
|
*r_passphrase = NULL;
|
|
|
|
|
}
|
2003-08-05 17:11:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-05 10:35:33 +09:00
|
|
|
|
return err;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2005-02-23 21:06:32 +00:00
|
|
|
|
|
2011-07-20 20:49:41 +02:00
|
|
|
|
/* 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,
|
2022-06-22 15:45:18 +09:00
|
|
|
|
gcry_sexp_t *result, nvc_t *r_keymeta)
|
2011-07-20 20:49:41 +02:00
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
gcry_sexp_t s_skey;
|
|
|
|
|
|
|
|
|
|
(void)ctrl;
|
|
|
|
|
|
|
|
|
|
*result = NULL;
|
|
|
|
|
|
2022-06-22 15:45:18 +09:00
|
|
|
|
err = read_key_file (grip, &s_skey, r_keymeta);
|
2011-07-20 20:49:41 +02:00
|
|
|
|
if (!err)
|
|
|
|
|
*result = s_skey;
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2010-04-21 16:26:17 +00:00
|
|
|
|
/* 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
|
2023-02-01 09:27:28 +01:00
|
|
|
|
at RESULT. If R_SSHORDER is not NULL the ordinal from the
|
|
|
|
|
Use-for-ssh attribute is stored at that address. */
|
2022-05-26 17:10:54 +09:00
|
|
|
|
static gpg_error_t
|
|
|
|
|
public_key_from_file (ctrl_t ctrl, const unsigned char *grip,
|
2023-02-01 09:27:28 +01:00
|
|
|
|
gcry_sexp_t *result, int for_ssh, int *r_sshorder)
|
2010-04-21 16:26:17 +00:00
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
int i, idx;
|
|
|
|
|
gcry_sexp_t s_skey;
|
2022-05-26 17:10:54 +09:00
|
|
|
|
nvc_t keymeta = NULL;
|
2015-01-27 09:30:11 +09:00
|
|
|
|
const char *algoname, *elems;
|
|
|
|
|
int npkey;
|
|
|
|
|
gcry_mpi_t array[10];
|
|
|
|
|
gcry_sexp_t curve = NULL;
|
|
|
|
|
gcry_sexp_t flags = NULL;
|
2010-04-21 16:26:17 +00:00
|
|
|
|
gcry_sexp_t uri_sexp, comment_sexp;
|
|
|
|
|
const char *uri, *comment;
|
|
|
|
|
size_t uri_length, comment_length;
|
2019-05-14 00:05:42 -04:00
|
|
|
|
int uri_intlen, comment_intlen;
|
2021-03-18 09:53:09 +01:00
|
|
|
|
membuf_t format_mb;
|
|
|
|
|
char *format;
|
2015-01-27 09:30:11 +09:00
|
|
|
|
void *args[2+7+2+2+1]; /* Size is 2 + max. # of elements + 2 for uri + 2
|
|
|
|
|
for comment + end-of-list. */
|
2010-04-21 16:26:17 +00:00
|
|
|
|
int argidx;
|
2015-01-27 09:30:11 +09:00
|
|
|
|
gcry_sexp_t list = NULL;
|
2010-04-21 16:26:17 +00:00
|
|
|
|
const char *s;
|
|
|
|
|
|
|
|
|
|
(void)ctrl;
|
|
|
|
|
|
|
|
|
|
*result = NULL;
|
2023-02-01 09:27:28 +01:00
|
|
|
|
if (r_sshorder)
|
|
|
|
|
*r_sshorder = 0;
|
2010-04-21 16:26:17 +00:00
|
|
|
|
|
2022-05-26 17:10:54 +09:00
|
|
|
|
err = read_key_file (grip, &s_skey, for_ssh? &keymeta : NULL);
|
2010-04-21 16:26:17 +00:00
|
|
|
|
if (err)
|
|
|
|
|
return err;
|
|
|
|
|
|
2022-05-26 17:34:16 +09:00
|
|
|
|
if (for_ssh)
|
2022-05-26 17:10:54 +09:00
|
|
|
|
{
|
2022-06-28 10:25:03 +09:00
|
|
|
|
/* Use-for-ssh: yes */
|
2022-05-26 17:34:16 +09:00
|
|
|
|
int is_ssh = 0;
|
2022-05-26 17:10:54 +09:00
|
|
|
|
|
2022-05-26 17:34:16 +09:00
|
|
|
|
if (keymeta == NULL)
|
2022-05-26 17:10:54 +09:00
|
|
|
|
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
|
|
|
|
|
|
2022-08-11 10:56:40 +02:00
|
|
|
|
is_ssh = nvc_get_boolean (keymeta, "Use-for-ssh:");
|
2022-05-26 17:10:54 +09:00
|
|
|
|
nvc_release (keymeta);
|
|
|
|
|
keymeta = NULL;
|
2022-05-26 17:34:16 +09:00
|
|
|
|
|
|
|
|
|
if (!is_ssh)
|
|
|
|
|
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
|
2023-02-01 09:27:28 +01:00
|
|
|
|
if (r_sshorder)
|
|
|
|
|
*r_sshorder = is_ssh;
|
2022-05-26 17:10:54 +09:00
|
|
|
|
}
|
|
|
|
|
|
2015-01-27 09:30:11 +09:00
|
|
|
|
for (i=0; i < DIM (array); i++)
|
|
|
|
|
array[i] = NULL;
|
2010-04-21 16:26:17 +00:00
|
|
|
|
|
2015-01-27 09:30:11 +09:00
|
|
|
|
err = extract_private_key (s_skey, 0, &algoname, &npkey, NULL, &elems,
|
2015-01-27 10:22:47 +01:00
|
|
|
|
array, DIM (array), &curve, &flags);
|
2015-01-27 09:30:11 +09:00
|
|
|
|
if (err)
|
2005-02-23 21:06:32 +00:00
|
|
|
|
{
|
|
|
|
|
gcry_sexp_release (s_skey);
|
2010-04-21 16:26:17 +00:00
|
|
|
|
return err;
|
2005-02-23 21:06:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uri = NULL;
|
|
|
|
|
uri_length = 0;
|
|
|
|
|
uri_sexp = gcry_sexp_find_token (s_skey, "uri", 0);
|
|
|
|
|
if (uri_sexp)
|
|
|
|
|
uri = gcry_sexp_nth_data (uri_sexp, 1, &uri_length);
|
|
|
|
|
|
|
|
|
|
comment = NULL;
|
|
|
|
|
comment_length = 0;
|
|
|
|
|
comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
|
|
|
|
|
if (comment_sexp)
|
|
|
|
|
comment = gcry_sexp_nth_data (comment_sexp, 1, &comment_length);
|
|
|
|
|
|
|
|
|
|
gcry_sexp_release (s_skey);
|
|
|
|
|
s_skey = NULL;
|
|
|
|
|
|
|
|
|
|
|
2019-05-14 10:31:46 +02:00
|
|
|
|
log_assert (sizeof (size_t) <= sizeof (void*));
|
2005-02-23 21:06:32 +00:00
|
|
|
|
|
2021-03-18 09:53:09 +01:00
|
|
|
|
init_membuf (&format_mb, 256);
|
2005-02-23 21:06:32 +00:00
|
|
|
|
argidx = 0;
|
2021-03-18 09:53:09 +01:00
|
|
|
|
put_membuf_printf (&format_mb, "(public-key(%s%%S%%S", algoname);
|
2015-01-27 09:30:11 +09:00
|
|
|
|
args[argidx++] = &curve;
|
|
|
|
|
args[argidx++] = &flags;
|
|
|
|
|
for (idx=0, s=elems; idx < npkey; idx++)
|
2005-02-23 21:06:32 +00:00
|
|
|
|
{
|
2021-03-18 09:53:09 +01:00
|
|
|
|
put_membuf_printf (&format_mb, "(%c %%m)", *s++);
|
2019-05-14 10:31:46 +02:00
|
|
|
|
log_assert (argidx < DIM (args));
|
2005-07-25 14:35:04 +00:00
|
|
|
|
args[argidx++] = &array[idx];
|
2005-02-23 21:06:32 +00:00
|
|
|
|
}
|
2021-03-18 09:53:09 +01:00
|
|
|
|
put_membuf_str (&format_mb, ")");
|
2005-02-23 21:06:32 +00:00
|
|
|
|
if (uri)
|
|
|
|
|
{
|
2021-03-18 09:53:09 +01:00
|
|
|
|
put_membuf_str (&format_mb, "(uri %b)");
|
2019-05-14 10:31:46 +02:00
|
|
|
|
log_assert (argidx+1 < DIM (args));
|
2019-05-14 00:05:42 -04:00
|
|
|
|
uri_intlen = (int)uri_length;
|
|
|
|
|
args[argidx++] = (void *)&uri_intlen;
|
2012-10-31 16:09:06 +09:00
|
|
|
|
args[argidx++] = (void *)&uri;
|
2005-02-23 21:06:32 +00:00
|
|
|
|
}
|
|
|
|
|
if (comment)
|
|
|
|
|
{
|
2021-03-18 09:53:09 +01:00
|
|
|
|
put_membuf_str (&format_mb, "(comment %b)");
|
2019-05-14 10:31:46 +02:00
|
|
|
|
log_assert (argidx+1 < DIM (args));
|
2019-05-14 00:05:42 -04:00
|
|
|
|
comment_intlen = (int)comment_length;
|
|
|
|
|
args[argidx++] = (void *)&comment_intlen;
|
2021-03-18 09:53:09 +01:00
|
|
|
|
args[argidx++] = (void *)&comment;
|
2005-02-23 21:06:32 +00:00
|
|
|
|
}
|
2021-03-18 09:53:09 +01:00
|
|
|
|
put_membuf (&format_mb, ")", 2);
|
2019-05-14 10:31:46 +02:00
|
|
|
|
log_assert (argidx < DIM (args));
|
2005-02-23 21:06:32 +00:00
|
|
|
|
args[argidx] = NULL;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2021-03-18 09:53:09 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2010-04-21 16:26:17 +00:00
|
|
|
|
err = gcry_sexp_build_array (&list, NULL, format, args);
|
2005-02-23 21:06:32 +00:00
|
|
|
|
xfree (format);
|
|
|
|
|
for (i=0; array[i]; i++)
|
|
|
|
|
gcry_mpi_release (array[i]);
|
2015-01-27 09:30:11 +09:00
|
|
|
|
gcry_sexp_release (curve);
|
|
|
|
|
gcry_sexp_release (flags);
|
2005-02-23 21:06:32 +00:00
|
|
|
|
gcry_sexp_release (uri_sexp);
|
|
|
|
|
gcry_sexp_release (comment_sexp);
|
|
|
|
|
|
2010-04-21 16:26:17 +00:00
|
|
|
|
if (!err)
|
2005-02-23 21:06:32 +00:00
|
|
|
|
*result = list;
|
2010-04-21 16:26:17 +00:00
|
|
|
|
return err;
|
2005-02-23 21:06:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-05-26 17:10:54 +09:00
|
|
|
|
gpg_error_t
|
|
|
|
|
agent_public_key_from_file (ctrl_t ctrl,
|
|
|
|
|
const unsigned char *grip,
|
|
|
|
|
gcry_sexp_t *result)
|
|
|
|
|
{
|
2023-02-01 09:27:28 +01:00
|
|
|
|
return public_key_from_file (ctrl, grip, result, 0, NULL);
|
2022-05-26 17:10:54 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gpg_error_t
|
|
|
|
|
agent_ssh_key_from_file (ctrl_t ctrl,
|
|
|
|
|
const unsigned char *grip,
|
2023-02-01 09:27:28 +01:00
|
|
|
|
gcry_sexp_t *result, int *r_order)
|
2022-05-26 17:10:54 +09:00
|
|
|
|
{
|
2023-02-01 09:27:28 +01:00
|
|
|
|
return public_key_from_file (ctrl, grip, result, 1, r_order);
|
2022-05-26 17:10:54 +09:00
|
|
|
|
}
|
|
|
|
|
|
2005-02-23 21:06:32 +00:00
|
|
|
|
|
2017-02-20 16:19:50 -05:00
|
|
|
|
/* Check whether the secret key identified by GRIP is available.
|
2010-06-17 15:44:44 +00:00
|
|
|
|
Returns 0 is the key is available. */
|
2003-08-05 17:11:04 +00:00
|
|
|
|
int
|
|
|
|
|
agent_key_available (const unsigned char *grip)
|
|
|
|
|
{
|
2009-03-06 17:31:27 +00:00
|
|
|
|
int result;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
char *fname;
|
|
|
|
|
char hexgrip[40+4+1];
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2009-03-06 17:31:27 +00:00
|
|
|
|
bin2hex (grip, 20, hexgrip);
|
2003-08-05 17:11:04 +00:00
|
|
|
|
strcpy (hexgrip+40, ".key");
|
|
|
|
|
|
2016-06-07 10:59:46 +02:00
|
|
|
|
fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
|
|
|
|
|
hexgrip, NULL);
|
2020-10-20 10:43:55 +02:00
|
|
|
|
result = !gnupg_access (fname, R_OK)? 0 : -1;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
xfree (fname);
|
2009-03-06 17:31:27 +00:00
|
|
|
|
return result;
|
2003-08-05 17:11:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2009-03-06 17:31:27 +00:00
|
|
|
|
/* Return the information about the secret key specified by the binary
|
|
|
|
|
keygrip GRIP. If the key is a shadowed one the shadow information
|
|
|
|
|
will be stored at the address R_SHADOW_INFO as an allocated
|
|
|
|
|
S-expression. */
|
|
|
|
|
gpg_error_t
|
|
|
|
|
agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
|
2020-06-14 10:26:45 -07:00
|
|
|
|
int *r_keytype, unsigned char **r_shadow_info,
|
|
|
|
|
unsigned char **r_shadow_info_type)
|
2009-03-06 17:31:27 +00:00
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
unsigned char *buf;
|
|
|
|
|
size_t len;
|
|
|
|
|
int keytype;
|
|
|
|
|
|
|
|
|
|
(void)ctrl;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2009-03-06 17:31:27 +00:00
|
|
|
|
if (r_keytype)
|
|
|
|
|
*r_keytype = PRIVATE_KEY_UNKNOWN;
|
|
|
|
|
if (r_shadow_info)
|
|
|
|
|
*r_shadow_info = NULL;
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
gcry_sexp_t sexp;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2019-05-07 11:08:26 +02:00
|
|
|
|
err = read_key_file (grip, &sexp, NULL);
|
2009-03-06 17:31:27 +00:00
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
if (gpg_err_code (err) == GPG_ERR_ENOENT)
|
|
|
|
|
return gpg_error (GPG_ERR_NOT_FOUND);
|
|
|
|
|
else
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
err = make_canon_sexp (sexp, &buf, &len);
|
|
|
|
|
gcry_sexp_release (sexp);
|
|
|
|
|
if (err)
|
|
|
|
|
return err;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2009-03-06 17:31:27 +00:00
|
|
|
|
keytype = agent_private_key_type (buf);
|
|
|
|
|
switch (keytype)
|
|
|
|
|
{
|
|
|
|
|
case PRIVATE_KEY_CLEAR:
|
2015-01-29 16:26:07 +01:00
|
|
|
|
case PRIVATE_KEY_OPENPGP_NONE:
|
2011-02-04 12:57:53 +01:00
|
|
|
|
break;
|
2009-03-06 17:31:27 +00:00
|
|
|
|
case PRIVATE_KEY_PROTECTED:
|
|
|
|
|
/* If we ever require it we could retrieve the comment fields
|
|
|
|
|
from such a key. */
|
|
|
|
|
break;
|
|
|
|
|
case PRIVATE_KEY_SHADOWED:
|
|
|
|
|
if (r_shadow_info)
|
|
|
|
|
{
|
|
|
|
|
const unsigned char *s;
|
|
|
|
|
size_t n;
|
|
|
|
|
|
2020-06-14 10:26:45 -07:00
|
|
|
|
err = agent_get_shadow_info_type (buf, &s, r_shadow_info_type);
|
2009-03-06 17:31:27 +00:00
|
|
|
|
if (!err)
|
|
|
|
|
{
|
|
|
|
|
n = gcry_sexp_canon_len (s, 0, NULL, NULL);
|
2019-05-14 10:31:46 +02:00
|
|
|
|
log_assert (n);
|
2009-03-06 17:31:27 +00:00
|
|
|
|
*r_shadow_info = xtrymalloc (n);
|
|
|
|
|
if (!*r_shadow_info)
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
else
|
|
|
|
|
memcpy (*r_shadow_info, s, n);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
err = gpg_error (GPG_ERR_BAD_SECKEY);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!err && r_keytype)
|
|
|
|
|
*r_keytype = keytype;
|
|
|
|
|
|
|
|
|
|
xfree (buf);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
2014-04-15 16:40:48 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Delete the key with GRIP from the disk after having asked for
|
2017-03-24 09:02:02 +01:00
|
|
|
|
* 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.
|
|
|
|
|
*/
|
2014-04-15 16:40:48 +02:00
|
|
|
|
gpg_error_t
|
|
|
|
|
agent_delete_key (ctrl_t ctrl, const char *desc_text,
|
2017-03-24 09:02:02 +01:00
|
|
|
|
const unsigned char *grip, int force, int only_stubs)
|
2014-04-15 16:40:48 +02:00
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
gcry_sexp_t s_skey = NULL;
|
|
|
|
|
unsigned char *buf = NULL;
|
|
|
|
|
size_t len;
|
|
|
|
|
char *desc_text_final = NULL;
|
|
|
|
|
char *comment = NULL;
|
|
|
|
|
ssh_control_file_t cf = NULL;
|
|
|
|
|
char hexgrip[40+4+1];
|
|
|
|
|
char *default_desc = NULL;
|
2017-03-24 09:02:02 +01:00
|
|
|
|
int key_type;
|
2014-04-15 16:40:48 +02:00
|
|
|
|
|
2019-05-07 11:08:26 +02:00
|
|
|
|
err = read_key_file (grip, &s_skey, NULL);
|
2014-04-15 16:40:48 +02:00
|
|
|
|
if (gpg_err_code (err) == GPG_ERR_ENOENT)
|
|
|
|
|
err = gpg_error (GPG_ERR_NO_SECKEY);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = make_canon_sexp (s_skey, &buf, &len);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
2017-03-24 09:02:02 +01:00
|
|
|
|
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)
|
2014-04-15 16:40:48 +02:00
|
|
|
|
{
|
|
|
|
|
case PRIVATE_KEY_CLEAR:
|
2015-01-29 16:26:07 +01:00
|
|
|
|
case PRIVATE_KEY_OPENPGP_NONE:
|
2014-04-15 16:40:48 +02:00
|
|
|
|
case PRIVATE_KEY_PROTECTED:
|
2015-08-07 12:55:29 +02:00
|
|
|
|
bin2hex (grip, 20, hexgrip);
|
|
|
|
|
if (!force)
|
2014-04-15 16:40:48 +02:00
|
|
|
|
{
|
2015-08-07 12:55:29 +02:00
|
|
|
|
if (!desc_text)
|
|
|
|
|
{
|
|
|
|
|
default_desc = xtryasprintf
|
|
|
|
|
(L_("Do you really want to delete the key identified by keygrip%%0A"
|
|
|
|
|
" %s%%0A %%C%%0A?"), hexgrip);
|
|
|
|
|
desc_text = default_desc;
|
|
|
|
|
}
|
2014-04-15 16:40:48 +02:00
|
|
|
|
|
2015-08-07 12:55:29 +02:00
|
|
|
|
/* Note, that we will take the comment as a C string for
|
|
|
|
|
display purposes; i.e. all stuff beyond a Nul character is
|
|
|
|
|
ignored. */
|
2014-04-15 16:40:48 +02:00
|
|
|
|
{
|
2015-08-07 12:55:29 +02:00
|
|
|
|
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);
|
2014-04-15 16:40:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-07 12:55:29 +02:00
|
|
|
|
if (desc_text)
|
2017-02-22 11:04:55 +01:00
|
|
|
|
err = agent_modify_description (desc_text, comment, s_skey,
|
|
|
|
|
&desc_text_final);
|
2015-08-07 12:55:29 +02:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = agent_get_confirmation (ctrl, desc_text_final,
|
|
|
|
|
L_("Delete key"), L_("No"), 0);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
cf = ssh_open_control_file ();
|
|
|
|
|
if (cf)
|
|
|
|
|
{
|
|
|
|
|
if (!ssh_search_control_file (cf, hexgrip, NULL, NULL, NULL))
|
|
|
|
|
{
|
|
|
|
|
err = agent_get_confirmation
|
|
|
|
|
(ctrl,
|
|
|
|
|
L_("Warning: This key is also listed for use with SSH!\n"
|
|
|
|
|
"Deleting the key might remove your ability to "
|
|
|
|
|
"access remote machines."),
|
|
|
|
|
L_("Delete key"), L_("No"), 0);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
err = remove_key_file (grip);
|
2014-04-15 16:40:48 +02:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case PRIVATE_KEY_SHADOWED:
|
2016-03-17 08:37:58 +09:00
|
|
|
|
err = remove_key_file (grip);
|
2014-04-15 16:40:48 +02:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
log_error ("invalid private key format\n");
|
|
|
|
|
err = gpg_error (GPG_ERR_BAD_SECKEY);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
ssh_close_control_file (cf);
|
|
|
|
|
gcry_free (comment);
|
|
|
|
|
xfree (desc_text_final);
|
|
|
|
|
xfree (default_desc);
|
|
|
|
|
xfree (buf);
|
|
|
|
|
gcry_sexp_release (s_skey);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
2016-10-20 12:05:15 +09:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
|
2019-05-03 15:54:54 +02:00
|
|
|
|
/* Just in case some caller did not parse the stuff correctly, skip
|
|
|
|
|
* leading spaces. */
|
|
|
|
|
while (spacep (serialno))
|
|
|
|
|
serialno++;
|
|
|
|
|
while (spacep (keyid))
|
|
|
|
|
keyid++;
|
|
|
|
|
|
2016-10-20 12:05:15 +09:00
|
|
|
|
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);
|
2020-08-17 14:21:00 +02:00
|
|
|
|
err = agent_write_private_key (grip, shdkey, len, force, serialno, keyid, 0);
|
2016-10-20 12:05:15 +09:00
|
|
|
|
xfree (shdkey);
|
|
|
|
|
if (err)
|
|
|
|
|
log_error ("error writing key: %s\n", gpg_strerror (err));
|
|
|
|
|
|
|
|
|
|
return err;
|
|
|
|
|
}
|