agent: Update key files by first writing to a temp file.

* agent/findkey.c (fname_from_keygrip): New.
(agent_write_private_key): Use here.  Use temp file for updating.
(agent_update_private_key): Use fname_from_keygrip and use gnupg
rename function instead of a vanilla rename.
This commit is contained in:
Werner Koch 2023-05-26 13:57:36 +02:00
parent 1d23dc9389
commit a216e9c028
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
1 changed files with 100 additions and 69 deletions

View File

@ -50,6 +50,22 @@ struct try_unprotect_arg_s
}; };
/* Return the file name for the 20 byte keygrip GRIP. With FOR_NEW
* create a file name for later renaming to the actual name. Return
* NULL on error. */
static char *
fname_from_keygrip (const unsigned char *grip, int for_new)
{
char hexgrip[40+4+4+1];
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, for_new? ".key.tmp" : ".key");
return make_filename_try (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
hexgrip, NULL);
}
/* Replace all linefeeds in STRING by "%0A" and return a new malloced /* Replace all linefeeds in STRING by "%0A" and return a new malloced
* string. May return NULL on memory error. */ * string. May return NULL on memory error. */
static char * static char *
@ -94,26 +110,22 @@ agent_write_private_key (const unsigned char *grip,
time_t timestamp) time_t timestamp)
{ {
gpg_error_t err; gpg_error_t err;
char *fname; char *fname = NULL;
char *tmpfname = NULL;
estream_t fp; estream_t fp;
char hexgrip[40+4+1];
int update, newkey; int update, newkey;
nvc_t pk = NULL; nvc_t pk = NULL;
gcry_sexp_t key = NULL; gcry_sexp_t key = NULL;
int remove = 0; int removetmp = 0;
char *token0 = NULL; char *token0 = NULL;
char *token = NULL; char *token = NULL;
char *dispserialno_buffer = NULL; char *dispserialno_buffer = NULL;
char **tokenfields = NULL; char **tokenfields = NULL;
int blocksigs = 0;
bin2hex (grip, 20, hexgrip); fname = fname_from_keygrip (grip, 0);
strcpy (hexgrip+40, ".key"); if (!fname)
return gpg_error_from_syserror ();
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)) if (!force && !gnupg_access (fname, F_OK))
{ {
@ -213,7 +225,8 @@ agent_write_private_key (const unsigned char *grip,
goto leave; goto leave;
} }
} }
es_clearerr (fp); es_fclose (fp);
fp = NULL;
/* Turn (BUFFER,LENGTH) into a gcrypt s-expression and set it into /* Turn (BUFFER,LENGTH) into a gcrypt s-expression and set it into
* our name value container. */ * our name value container. */
@ -299,46 +312,55 @@ agent_write_private_key (const unsigned char *grip,
goto leave; goto leave;
} }
/* Back to start and write. */ /* Create a temporary file for writing. */
err = es_fseek (fp, 0, SEEK_SET); tmpfname = fname_from_keygrip (grip, 1);
if (err) fp = tmpfname ? es_fopen (tmpfname, "wbx,mode=-rw") : NULL;
goto leave; if (!fp)
err = nvc_write (pk, fp);
if (!err)
err = es_fflush (fp);
if (err)
{ {
log_error ("error writing '%s': %s\n", fname, gpg_strerror (err)); err = gpg_error_from_syserror ();
remove = 1; log_error ("can't create '%s': %s\n", tmpfname, gpg_strerror (err));
goto leave; goto leave;
} }
if (ftruncate (es_fileno (fp), es_ftello (fp))) err = nvc_write (pk, fp);
if (!err && es_fflush (fp))
err = gpg_error_from_syserror ();
if (err)
{ {
err = gpg_error_from_syserror (); log_error ("error writing '%s': %s\n", tmpfname, gpg_strerror (err));
log_error ("error truncating '%s': %s\n", fname, gpg_strerror (err)); removetmp = 1;
remove = 1;
goto leave; goto leave;
} }
if (es_fclose (fp)) if (es_fclose (fp))
{ {
err = gpg_error_from_syserror (); err = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n", fname, gpg_strerror (err)); log_error ("error closing '%s': %s\n", tmpfname, gpg_strerror (err));
remove = 1; removetmp = 1;
goto leave; goto leave;
} }
else else
fp = NULL; fp = NULL;
err = gnupg_rename_file (tmpfname, fname, &blocksigs);
if (err)
{
err = gpg_error_from_syserror ();
log_error ("error renaming '%s': %s\n", tmpfname, gpg_strerror (err));
removetmp = 1;
goto leave;
}
bump_key_eventcounter (); bump_key_eventcounter ();
leave: leave:
if (blocksigs)
gnupg_unblock_all_signals ();
es_fclose (fp); es_fclose (fp);
if (remove && fname) if (removetmp && fname)
gnupg_remove (fname); gnupg_remove (fname);
xfree (fname); xfree (fname);
xfree (tmpfname);
xfree (token); xfree (token);
xfree (token0); xfree (token0);
xfree (dispserialno_buffer); xfree (dispserialno_buffer);
@ -352,53 +374,63 @@ agent_write_private_key (const unsigned char *grip,
gpg_error_t gpg_error_t
agent_update_private_key (const unsigned char *grip, nvc_t pk) 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; gpg_error_t err;
char *fname0 = NULL; /* The existing file name. */
char *fname = NULL; /* The temporary new file name. */
estream_t fp = NULL;
int removetmp = 0;
int blocksigs = 0;
bin2hex (grip, 20, hexgrip); fname0 = fname_from_keygrip (grip, 0);
strcpy (hexgrip+40, ".key.tmp"); if (!fname)
fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
hexgrip, NULL);
fname0 = xstrdup (fname);
if (!fname0)
{ {
err = gpg_error_from_syserror (); err = gpg_error_from_syserror ();
xfree (fname); goto leave;
return err; }
fname = fname_from_keygrip (grip, 1);
if (!fname)
{
err = gpg_error_from_syserror ();
goto leave;
} }
fname0[strlen (fname)-4] = 0;
fp = es_fopen (fname, "wbx,mode=-rw"); fp = es_fopen (fname, "wbx,mode=-rw");
if (!fp) if (!fp)
{ {
err = gpg_error_from_syserror (); err = gpg_error_from_syserror ();
log_error ("can't create '%s': %s\n", fname, gpg_strerror (err)); log_error ("can't create '%s': %s\n", fname, gpg_strerror (err));
xfree (fname); goto leave;
return err;
} }
err = nvc_write (pk, fp); err = nvc_write (pk, fp);
if (err) 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 writing '%s': %s\n", fname, gpg_strerror (err));
log_error (_("error renaming '%s' to '%s': %s\n"), removetmp = 1;
fname, fname0, strerror (errno)); goto leave;
} }
es_fclose (fp);
fp = NULL;
err = gnupg_rename_file (fname, fname0, &blocksigs);
if (err)
{
err = gpg_error_from_syserror ();
log_error ("error renaming '%s': %s\n", fname, gpg_strerror (err));
removetmp = 1;
goto leave;
}
leave:
if (blocksigs)
gnupg_unblock_all_signals ();
es_fclose (fp);
if (removetmp && fname)
gnupg_remove (fname);
xfree (fname); xfree (fname);
xfree (fname0);
return err; return err;
} }
@ -885,18 +917,17 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta)
unsigned char *buf; unsigned char *buf;
size_t buflen, erroff; size_t buflen, erroff;
gcry_sexp_t s_skey; gcry_sexp_t s_skey;
char hexgrip[40+4+1];
char first; char first;
*result = NULL; *result = NULL;
if (r_keymeta) if (r_keymeta)
*r_keymeta = NULL; *r_keymeta = NULL;
bin2hex (grip, 20, hexgrip); fname = fname_from_keygrip (grip, 0);
strcpy (hexgrip+40, ".key"); if (!fname)
{
fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, return gpg_error_from_syserror ();
hexgrip, NULL); }
fp = es_fopen (fname, "rb"); fp = es_fopen (fname, "rb");
if (!fp) if (!fp)
{ {
@ -1012,12 +1043,12 @@ remove_key_file (const unsigned char *grip)
{ {
gpg_error_t err = 0; gpg_error_t err = 0;
char *fname; char *fname;
char hexgrip[40+4+1];
bin2hex (grip, 20, hexgrip); fname = fname_from_keygrip (grip, 0);
strcpy (hexgrip+40, ".key"); if (!fname)
fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, {
hexgrip, NULL); return gpg_error_from_syserror ();
}
if (gnupg_remove (fname)) if (gnupg_remove (fname))
err = gpg_error_from_syserror (); err = gpg_error_from_syserror ();
xfree (fname); xfree (fname);