mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-31 11:41:32 +01:00
* genkey.c (store_key): Protect the key.
(agent_genkey): Ask for the passphrase. * findkey.c (unprotect): Actually unprotect the key. * query.c (agent_askpin): Add an optional start_err_text.
This commit is contained in:
parent
a09c4d0d12
commit
7d9ed16fe6
@ -1,3 +1,16 @@
|
||||
2002-01-31 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* genkey.c (store_key): Protect the key.
|
||||
(agent_genkey): Ask for the passphrase.
|
||||
* findkey.c (unprotect): Actually unprotect the key.
|
||||
* query.c (agent_askpin): Add an optional start_err_text.
|
||||
|
||||
2002-01-30 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* protect.c: New.
|
||||
(hash_passphrase): Based on the GnuPG 1.0.6 version.
|
||||
* protect-tool.c: New
|
||||
|
||||
2002-01-29 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* findkey.c (agent_key_available): New.
|
||||
|
@ -19,6 +19,7 @@
|
||||
## Process this file with automake to produce Makefile.in
|
||||
|
||||
bin_PROGRAMS = gpg-agent
|
||||
noinst_PROGRAMS = protect-tool
|
||||
|
||||
AM_CPPFLAGS = -I$(top_srcdir)/common $(LIBGCRYPT_CFLAGS)
|
||||
LDFLAGS = @LDFLAGS@
|
||||
@ -33,11 +34,16 @@ gpg_agent_SOURCES = \
|
||||
pksign.c \
|
||||
pkdecrypt.c \
|
||||
genkey.c \
|
||||
protect.c \
|
||||
trustlist.c
|
||||
|
||||
gpg_agent_LDADD = ../jnlib/libjnlib.a ../assuan/libassuan.a \
|
||||
../common/libcommon.a $(LIBGCRYPT_LIBS)
|
||||
|
||||
protect_tool_SOURCES = \
|
||||
protect-tool.c \
|
||||
protect.c
|
||||
|
||||
|
||||
protect_tool_LDADD = ../jnlib/libjnlib.a \
|
||||
../common/libcommon.a $(LIBGCRYPT_LIBS)
|
||||
|
||||
|
@ -84,7 +84,7 @@ struct pin_entry_info_s {
|
||||
|
||||
|
||||
/*-- gpg-agent.c --*/
|
||||
void agent_exit (int rc);
|
||||
void agent_exit (int rc); /* also implemented in other tools */
|
||||
|
||||
/*-- trans.c --*/
|
||||
const char *trans (const char *text);
|
||||
@ -97,7 +97,8 @@ GCRY_SEXP agent_key_from_file (const unsigned char *grip);
|
||||
int agent_key_available (const unsigned char *grip);
|
||||
|
||||
/*-- query.c --*/
|
||||
int agent_askpin (const char *desc_text, struct pin_entry_info_s *pininfo);
|
||||
int agent_askpin (const char *desc_text, const char *err_text,
|
||||
struct pin_entry_info_s *pininfo);
|
||||
int agent_get_passphrase (char **retpass,
|
||||
const char *desc, const char *prompt,
|
||||
const char *errtext);
|
||||
@ -119,6 +120,13 @@ int agent_pkdecrypt (CTRL ctrl, const char *ciphertext, size_t ciphertextlen,
|
||||
int agent_genkey (CTRL ctrl,
|
||||
const char *keyparam, size_t keyparmlen, FILE *outfp);
|
||||
|
||||
/*-- protect.c --*/
|
||||
int agent_protect (const unsigned char *plainkey, const char *passphrase,
|
||||
unsigned char **result, size_t *resultlen);
|
||||
int agent_unprotect (const unsigned char *protectedkey, const char *passphrase,
|
||||
unsigned char **result, size_t *resultlen);
|
||||
|
||||
|
||||
/*-- trustlist.c --*/
|
||||
int agent_istrusted (const char *fpr);
|
||||
int agent_listtrusted (void *assuan_context);
|
||||
|
@ -132,7 +132,7 @@ housekeeping (void)
|
||||
|
||||
|
||||
/* Store DATA of length DATALEN in the cache under KEY and mark it
|
||||
with a maxiumum lifetime of TTL seconds. If tehre is already data
|
||||
with a maximum lifetime of TTL seconds. If tehre is already data
|
||||
under this key, it will be replaced. Using a DATA of NULL deletes
|
||||
the entry */
|
||||
int
|
||||
@ -206,7 +206,7 @@ agent_get_cache (const char *key)
|
||||
{
|
||||
if (r->pw && !strcmp (r->key, key))
|
||||
{
|
||||
/* put_cache does onlu put strings into the cache, so we
|
||||
/* put_cache does only put strings into the cache, so we
|
||||
don't need the lengths */
|
||||
r->accessed = time (NULL);
|
||||
return r->pw->data;
|
||||
|
@ -31,24 +31,39 @@
|
||||
#include "agent.h"
|
||||
|
||||
static int
|
||||
unprotect (GCRY_SEXP s_skey)
|
||||
unprotect (unsigned char **keybuf)
|
||||
{
|
||||
struct pin_entry_info_s *pi;
|
||||
int rc;
|
||||
unsigned char *result;
|
||||
size_t resultlen;
|
||||
int tries = 0;
|
||||
|
||||
/* fixme: check whether the key needs unprotection */
|
||||
|
||||
/* fixme: allocate the pin in secure memory */
|
||||
pi = xtrycalloc (1, sizeof (*pi) + 100);
|
||||
pi = gcry_calloc_secure (1, sizeof (*pi) + 100);
|
||||
pi->max_length = 100;
|
||||
pi->min_digits = 4;
|
||||
pi->min_digits = 0; /* we want a real passphrase */
|
||||
pi->max_digits = 8;
|
||||
pi->max_tries = 3;
|
||||
|
||||
rc = agent_askpin (NULL, pi);
|
||||
/* fixme: actually unprotect the key and ask again until we get a valid
|
||||
PIN - agent_askpin takes care of counting failed tries */
|
||||
|
||||
do
|
||||
{
|
||||
rc = agent_askpin (NULL, NULL, pi);
|
||||
if (!rc)
|
||||
{
|
||||
rc = agent_unprotect (*keybuf, pi->pin, &result, &resultlen);
|
||||
if (!rc)
|
||||
{
|
||||
xfree (*keybuf);
|
||||
*keybuf = result;
|
||||
xfree (pi);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
while ((rc == GNUPG_Bad_Passphrase || rc == GNUPG_Bad_PIN)
|
||||
&& tries++ < 3);
|
||||
xfree (pi);
|
||||
return rc;
|
||||
}
|
||||
@ -64,8 +79,8 @@ agent_key_from_file (const unsigned char *grip)
|
||||
char *fname;
|
||||
FILE *fp;
|
||||
struct stat st;
|
||||
char *buf;
|
||||
size_t buflen, erroff;
|
||||
unsigned char *buf;
|
||||
size_t len, buflen, erroff;
|
||||
GCRY_SEXP s_skey;
|
||||
char hexgrip[41];
|
||||
|
||||
@ -111,13 +126,35 @@ agent_key_from_file (const unsigned char *grip)
|
||||
(unsigned int)erroff, gcry_strerror (rc));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rc = unprotect (s_skey);
|
||||
if (rc)
|
||||
len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0);
|
||||
assert (len);
|
||||
buf = xtrymalloc (len);
|
||||
if (!buf)
|
||||
{
|
||||
gcry_sexp_release (s_skey);
|
||||
return NULL;
|
||||
}
|
||||
len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, buf, len);
|
||||
assert (len);
|
||||
gcry_sexp_release (s_skey);
|
||||
|
||||
rc = unprotect (&buf);
|
||||
if (rc)
|
||||
{
|
||||
log_error ("failed to unprotect the secret key: %s\n",
|
||||
gcry_strerror (rc));
|
||||
xfree (buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* arggg FIXME: does scna support secure memory? */
|
||||
rc = gcry_sexp_sscan (&s_skey, &erroff,
|
||||
buf, gcry_sexp_canon_len (buf, 0, NULL, NULL));
|
||||
xfree (buf);
|
||||
if (rc)
|
||||
{
|
||||
log_error ("failed to build S-Exp (off=%u): %s\n",
|
||||
(unsigned int)erroff, gcry_strerror (rc));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -30,8 +30,9 @@
|
||||
|
||||
#include "agent.h"
|
||||
|
||||
|
||||
static int
|
||||
store_key (GCRY_SEXP private)
|
||||
store_key (GCRY_SEXP private, const char *passphrase)
|
||||
{
|
||||
int i;
|
||||
char *fname;
|
||||
@ -58,9 +59,11 @@ store_key (GCRY_SEXP private)
|
||||
xfree (fname);
|
||||
return seterr (General_Error);
|
||||
}
|
||||
fp = fopen (fname, "wbx");
|
||||
if (!fp)
|
||||
{
|
||||
fp = fopen (fname, "wbx"); /* FIXME: the x is a GNU extension - let
|
||||
configure check whether this actually
|
||||
works */
|
||||
if (!fp)
|
||||
{
|
||||
log_error ("can't create `%s': %s\n", fname, strerror (errno));
|
||||
xfree (fname);
|
||||
return seterr (File_Create_Error);
|
||||
@ -79,6 +82,24 @@ store_key (GCRY_SEXP private)
|
||||
len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, buf, len);
|
||||
assert (len);
|
||||
|
||||
if (passphrase)
|
||||
{
|
||||
unsigned char *p;
|
||||
int rc;
|
||||
|
||||
rc = agent_protect (buf, passphrase, &p, &len);
|
||||
if (rc)
|
||||
{
|
||||
fclose (fp);
|
||||
remove (fname);
|
||||
xfree (fname);
|
||||
xfree (buf);
|
||||
return rc;
|
||||
}
|
||||
xfree (buf);
|
||||
buf = p;
|
||||
}
|
||||
|
||||
if (fwrite (buf, len, 1, fp) != 1)
|
||||
{
|
||||
log_error ("error writing `%s': %s\n", fname, strerror (errno));
|
||||
@ -111,6 +132,7 @@ agent_genkey (CTRL ctrl, const char *keyparam, size_t keyparamlen,
|
||||
FILE *outfp)
|
||||
{
|
||||
GCRY_SEXP s_keyparam, s_key, s_private, s_public;
|
||||
struct pin_entry_info_s *pi, *pi2;
|
||||
int rc;
|
||||
size_t len;
|
||||
char *buf;
|
||||
@ -122,13 +144,48 @@ agent_genkey (CTRL ctrl, const char *keyparam, size_t keyparamlen,
|
||||
return seterr (Invalid_Data);
|
||||
}
|
||||
|
||||
/* fixme: Get the passphrase now, cause key generation may take a while */
|
||||
/* Get the passphrase now, cause key generation may take a while */
|
||||
{
|
||||
const char *text1 = trans ("Please enter the passphrase to%0A"
|
||||
"to protect your new key");
|
||||
const char *text2 = trans ("Please re-enter this passphrase");
|
||||
const char *nomatch = trans ("does not match - try again");
|
||||
int tries = 0;
|
||||
|
||||
pi = gcry_calloc_secure (2, sizeof (*pi) + 100);
|
||||
pi2 = pi + sizeof *pi;
|
||||
pi->max_length = 100;
|
||||
pi->max_tries = 3;
|
||||
pi2->max_length = 100;
|
||||
pi2->max_tries = 3;
|
||||
|
||||
rc = agent_askpin (text1, NULL, pi);
|
||||
if (!rc)
|
||||
{
|
||||
do
|
||||
{
|
||||
rc = agent_askpin (text2, tries? nomatch:NULL, pi2);
|
||||
tries++;
|
||||
}
|
||||
while (!rc && tries < 3 && strcmp (pi->pin, pi2->pin));
|
||||
if (!rc && strcmp (pi->pin, pi2->pin))
|
||||
rc = GNUPG_Canceled;
|
||||
}
|
||||
if (rc)
|
||||
return rc;
|
||||
if (!*pi->pin)
|
||||
{
|
||||
xfree (pi);
|
||||
pi = NULL; /* use does not want a passphrase */
|
||||
}
|
||||
}
|
||||
|
||||
rc = gcry_pk_genkey (&s_key, s_keyparam );
|
||||
gcry_sexp_release (s_keyparam);
|
||||
if (rc)
|
||||
{
|
||||
log_error ("key generation failed: %s\n", gcry_strerror (rc));
|
||||
xfree (pi);
|
||||
return map_gcry_err (rc);
|
||||
}
|
||||
|
||||
@ -138,6 +195,7 @@ agent_genkey (CTRL ctrl, const char *keyparam, size_t keyparamlen,
|
||||
{
|
||||
log_error ("key generation failed: invalid return value\n");
|
||||
gcry_sexp_release (s_key);
|
||||
xfree (pi);
|
||||
return seterr (Invalid_Data);
|
||||
}
|
||||
s_public = gcry_sexp_find_token (s_key, "public-key", 0);
|
||||
@ -146,13 +204,15 @@ agent_genkey (CTRL ctrl, const char *keyparam, size_t keyparamlen,
|
||||
log_error ("key generation failed: invalid return value\n");
|
||||
gcry_sexp_release (s_private);
|
||||
gcry_sexp_release (s_key);
|
||||
xfree (pi);
|
||||
return seterr (Invalid_Data);
|
||||
}
|
||||
gcry_sexp_release (s_key); s_key = NULL;
|
||||
|
||||
/* store the secret key */
|
||||
log_debug ("storing private key\n");
|
||||
rc = store_key (s_private);
|
||||
rc = store_key (s_private, pi->pin);
|
||||
xfree (pi); pi = NULL;
|
||||
gcry_sexp_release (s_private);
|
||||
if (rc)
|
||||
{
|
||||
|
@ -6,7 +6,7 @@ Some notes on the format of the secret keys used with gpg-agent.
|
||||
|
||||
|
||||
The secret keys[1] are stored on a per file basis in a directory below
|
||||
the .gnupg home directory. This directory is named
|
||||
the ~/.gnupg home directory. This directory is named
|
||||
|
||||
private-keys-v1.d
|
||||
|
||||
@ -26,19 +26,15 @@ example of an unprotected file:
|
||||
(q #00f7a7c..[some bytes not shown]..61#)
|
||||
(u #304559a..[some bytes not shown]..9b#)
|
||||
)
|
||||
(uri http://foo.bar x-foo:whatever_you_want)
|
||||
)
|
||||
|
||||
Actually this form should not be used for regular purposes and only
|
||||
accepted by gpg-agent with the configuration option:
|
||||
--allow-non-canonical-key-format.
|
||||
--allow-non-canonical-key-format. The regular way to represent the
|
||||
keys is in canonical representation[3]:
|
||||
|
||||
The regular way to represent the keys is in canonical representation
|
||||
with the additional requirement of an extra object container around
|
||||
it[3]:
|
||||
|
||||
(oid.1.3.6.1.4.1.11591.2.2.2
|
||||
(keyinfo human_readable_information_to_decribe_this_key)
|
||||
(private-key
|
||||
(private-key
|
||||
(rsa
|
||||
(n #00e0ce9..[some bytes not shown]..51#)
|
||||
(e #010001#)
|
||||
@ -47,76 +43,79 @@ it[3]:
|
||||
(q #00f7a7c..[some bytes not shown]..61#)
|
||||
(u #304559a..[some bytes not shown]..9b#)
|
||||
)
|
||||
)
|
||||
)
|
||||
(uri http://foo.bar x-foo:whatever_you_want)
|
||||
)
|
||||
|
||||
|
||||
|
||||
This describes an unprotected key; a protected key is like this:
|
||||
|
||||
(oid.1.3.6.1.4.1.11591.2.2.3
|
||||
(keyinfo human_readable_information_to_decribe_this_key)
|
||||
(private-key
|
||||
(protected-private-key
|
||||
(rsa
|
||||
(n #00e0ce9..[some bytes not shown]..51#)
|
||||
(e #010001#)
|
||||
(oid.1.3.6.1.4.1.11591.2.1.1.1 (parms) encrypted_octet_string)
|
||||
(protected mode (parms) encrypted_octet_string)
|
||||
)
|
||||
)
|
||||
)
|
||||
(uri http://foo.bar x-foo:whatever_you_want)
|
||||
)
|
||||
|
||||
|
||||
In this scheme the encrypted_octet_string is encrypted according to
|
||||
the scheme identifier by the OID, most protection algorithms need
|
||||
some parameters, which are given in a list before the
|
||||
the algorithm described after the keyword protected; most protection
|
||||
algorithms need some parameters, which are given in a list before the
|
||||
encrypted_octet_string. The result of the decryption process is a
|
||||
list of the secret key parameters.
|
||||
|
||||
Defined protection methods are:
|
||||
The only available protection mode for now is
|
||||
|
||||
1.3.6.1.4.1.gnu(11591).aegypten(2)
|
||||
.algorithms(1).keyprotection(1).s2k3-sha1-aes-cbc(1)
|
||||
openpgp-s2k3-sha1-aes-cbc
|
||||
|
||||
This uses AES in CBC mode for encryption, SHA-1 for integrity
|
||||
protection and the String to Key algorithm 3 from OpenPGP (rfc2440).
|
||||
which describesan algorithm using using AES in CBC mode for
|
||||
encryption, SHA-1 for integrity protection and the String to Key
|
||||
algorithm 3 from OpenPGP (rfc2440).
|
||||
|
||||
Example:
|
||||
|
||||
(oid.1.3.6.1.4.1.11591.2.1.1.1
|
||||
((salt iterations) iv)
|
||||
(protected openpgp-s2k3-sha1-aes-cbc
|
||||
((sha1 16byte_salt no_of_iterations) 16byte_iv)
|
||||
encrypted_octet_string
|
||||
)
|
||||
|
||||
The encrypted_octet string should yield this S-Exp (in canonical
|
||||
representation) after decryption:
|
||||
|
||||
(sha1_hash
|
||||
(d #046129F..[some bytes not shown]..81#)
|
||||
(p #00e861b..[some bytes not shown]..f1#)
|
||||
(q #00f7a7c..[some bytes not shown]..61#)
|
||||
(u #304559a..[some bytes not shown]..9b#)
|
||||
(
|
||||
(
|
||||
(d #046129F..[some bytes not shown]..81#)
|
||||
(p #00e861b..[some bytes not shown]..f1#)
|
||||
(q #00f7a7c..[some bytes not shown]..61#)
|
||||
(u #304559a..[some bytes not shown]..9b#)
|
||||
)
|
||||
(hash sha1 #...[hashvalue]...#)
|
||||
)
|
||||
|
||||
For padding reasons, random bytes are appended to this list - they can
|
||||
easily be stripped by looking for the end of the list.
|
||||
|
||||
The first element is the SHA-1 hash calculated on the concatenation of the
|
||||
public key and secret key parameter lists: i.e one has to hash the
|
||||
concatenatiohn of these 6 canonical encoded lists for RSA, including
|
||||
the parenthesis.
|
||||
The hash is calculated on the concatenation of the public key and
|
||||
secret key parameter lists: i.e it is required to hash the
|
||||
concatenation of these 6 canonical encoded lists for RSA, including
|
||||
the parenthesis and the algorithm keyword.
|
||||
|
||||
(rsa
|
||||
(n #00e0ce9..[some bytes not shown]..51#)
|
||||
(e #010001#)
|
||||
(d #046129F..[some bytes not shown]..81#)
|
||||
(p #00e861b..[some bytes not shown]..f1#)
|
||||
(q #00f7a7c..[some bytes not shown]..61#)
|
||||
(u #304559a..[some bytes not shown]..9b#)
|
||||
|
||||
)
|
||||
|
||||
After decryption the hash must be recalculated and compared against
|
||||
the stored one - If they don't match the integrity of the key is not
|
||||
given.
|
||||
|
||||
|
||||
TODO: write a more elaborated version.
|
||||
|
||||
|
||||
|
||||
|
355
agent/protect-tool.c
Normal file
355
agent/protect-tool.c
Normal file
@ -0,0 +1,355 @@
|
||||
/* protect-tool.c - A tool to text the secret key protection
|
||||
* Copyright (C) 2002 Free Software Foundation, Inc.
|
||||
*
|
||||
* 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
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (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
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <gcrypt.h>
|
||||
|
||||
#define JNLIB_NEED_LOG_LOGV
|
||||
#include "agent.h"
|
||||
|
||||
#define N_(a) a
|
||||
#define _(a) a
|
||||
|
||||
|
||||
enum cmd_and_opt_values
|
||||
{ aNull = 0,
|
||||
oVerbose = 'v',
|
||||
oArmor = 'a',
|
||||
oPassphrase = 'P',
|
||||
|
||||
oProtect = 'p',
|
||||
oUnprotect = 'u',
|
||||
|
||||
oNoVerbose = 500,
|
||||
|
||||
aTest };
|
||||
|
||||
|
||||
static int opt_armor;
|
||||
static const char *passphrase = "abc";
|
||||
|
||||
static ARGPARSE_OPTS opts[] = {
|
||||
|
||||
{ 301, NULL, 0, N_("@Options:\n ") },
|
||||
|
||||
{ oVerbose, "verbose", 0, "verbose" },
|
||||
{ oArmor, "armor", 0, "write output in advanced format" },
|
||||
{ oPassphrase, "passphrase", 2, "|STRING| Use passphrase STRING" },
|
||||
{ oProtect, "protect", 256, "protect a private key"},
|
||||
{ oUnprotect, "unprotect", 256, "unprotect a private key"},
|
||||
|
||||
{0}
|
||||
};
|
||||
|
||||
static const char *
|
||||
my_strusage (int level)
|
||||
{
|
||||
const char *p;
|
||||
switch (level)
|
||||
{
|
||||
case 11: p = "protect-tool (GnuPG)";
|
||||
break;
|
||||
case 13: p = VERSION; break;
|
||||
case 17: p = PRINTABLE_OS_NAME; break;
|
||||
case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n");
|
||||
break;
|
||||
case 1:
|
||||
case 40: p = _("Usage: protect-tool [options] (-h for help)\n");
|
||||
break;
|
||||
case 41: p = _("Syntax: protect-tool [options] [args]]\n"
|
||||
"INTERNAL USE ONLY!\n");
|
||||
break;
|
||||
|
||||
default: p = NULL;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
i18n_init (void)
|
||||
{
|
||||
#ifdef USE_SIMPLE_GETTEXT
|
||||
set_gettext_file( PACKAGE );
|
||||
#else
|
||||
#ifdef ENABLE_NLS
|
||||
/* gtk_set_locale (); HMMM: We have not yet called gtk_init */
|
||||
bindtextdomain( PACKAGE, GNUPG_LOCALEDIR );
|
||||
textdomain( PACKAGE );
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Used by gcry for logging */
|
||||
static void
|
||||
my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr)
|
||||
{
|
||||
/* translate the log levels */
|
||||
switch (level)
|
||||
{
|
||||
case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break;
|
||||
case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break;
|
||||
case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break;
|
||||
case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break;
|
||||
case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break;
|
||||
case GCRY_LOG_BUG: level = JNLIB_LOG_BUG; break;
|
||||
case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break;
|
||||
default: level = JNLIB_LOG_ERROR; break;
|
||||
}
|
||||
log_logv (level, fmt, arg_ptr);
|
||||
}
|
||||
|
||||
|
||||
static unsigned char *
|
||||
make_canonical (const char *fname, const char *buf, size_t buflen)
|
||||
{
|
||||
int rc;
|
||||
size_t erroff, len;
|
||||
GCRY_SEXP sexp;
|
||||
unsigned char *result;
|
||||
|
||||
rc = gcry_sexp_sscan (&sexp, &erroff, buf, buflen);
|
||||
if (rc)
|
||||
{
|
||||
log_error ("invalid S-Expression in `%s' (off=%u): %s\n",
|
||||
fname, (unsigned int)erroff, gcry_strerror (rc));
|
||||
return NULL;
|
||||
}
|
||||
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0);
|
||||
assert (len);
|
||||
result = xmalloc (len);
|
||||
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, result, len);
|
||||
assert (len);
|
||||
gcry_sexp_release (sexp);
|
||||
return result;
|
||||
}
|
||||
|
||||
static char *
|
||||
make_advanced (const unsigned char *buf, size_t buflen)
|
||||
{
|
||||
int rc;
|
||||
size_t erroff, len;
|
||||
GCRY_SEXP sexp;
|
||||
unsigned char *result;
|
||||
|
||||
rc = gcry_sexp_sscan (&sexp, &erroff, buf, buflen);
|
||||
if (rc)
|
||||
{
|
||||
log_error ("invalid canonical S-Expression (off=%u): %s\n",
|
||||
(unsigned int)erroff, gcry_strerror (rc));
|
||||
return NULL;
|
||||
}
|
||||
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0);
|
||||
assert (len);
|
||||
result = xmalloc (len);
|
||||
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len);
|
||||
assert (len);
|
||||
gcry_sexp_release (sexp);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static unsigned char *
|
||||
read_key (const char *fname)
|
||||
{
|
||||
FILE *fp;
|
||||
struct stat st;
|
||||
char *buf;
|
||||
size_t buflen;
|
||||
unsigned char *key;
|
||||
|
||||
fp = fopen (fname, "rb");
|
||||
if (!fp)
|
||||
{
|
||||
log_error ("can't open `%s': %s\n", fname, strerror (errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fstat (fileno(fp), &st))
|
||||
{
|
||||
log_error ("can't stat `%s': %s\n", fname, strerror (errno));
|
||||
fclose (fp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
buflen = st.st_size;
|
||||
buf = xmalloc (buflen+1);
|
||||
if (fread (buf, buflen, 1, fp) != 1)
|
||||
{
|
||||
log_error ("error reading `%s': %s\n", fname, strerror (errno));
|
||||
fclose (fp);
|
||||
xfree (buf);
|
||||
return NULL;
|
||||
}
|
||||
fclose (fp);
|
||||
|
||||
key = make_canonical (fname, buf, buflen);
|
||||
xfree (buf);
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
read_and_protect (const char *fname)
|
||||
{
|
||||
int rc;
|
||||
unsigned char *key;
|
||||
unsigned char *result;
|
||||
size_t resultlen;
|
||||
|
||||
key = read_key (fname);
|
||||
if (!key)
|
||||
return;
|
||||
|
||||
rc = agent_protect (key, passphrase, &result, &resultlen);
|
||||
xfree (key);
|
||||
if (rc)
|
||||
{
|
||||
log_error ("protecting the key failed: %s\n", gnupg_strerror (rc));
|
||||
return;
|
||||
}
|
||||
|
||||
if (opt_armor)
|
||||
{
|
||||
char *p = make_advanced (result, resultlen);
|
||||
xfree (result);
|
||||
if (!p)
|
||||
return;
|
||||
result = p;
|
||||
resultlen = strlen (p);
|
||||
}
|
||||
|
||||
fwrite (result, resultlen, 1, stdout);
|
||||
xfree (result);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
read_and_unprotect (const char *fname)
|
||||
{
|
||||
int rc;
|
||||
unsigned char *key;
|
||||
unsigned char *result;
|
||||
size_t resultlen;
|
||||
|
||||
key = read_key (fname);
|
||||
if (!key)
|
||||
return;
|
||||
|
||||
rc = agent_unprotect (key, passphrase, &result, &resultlen);
|
||||
xfree (key);
|
||||
if (rc)
|
||||
{
|
||||
log_error ("unprotecting the key failed: %s\n", gnupg_strerror (rc));
|
||||
return;
|
||||
}
|
||||
|
||||
if (opt_armor)
|
||||
{
|
||||
char *p = make_advanced (result, resultlen);
|
||||
xfree (result);
|
||||
if (!p)
|
||||
return;
|
||||
result = p;
|
||||
resultlen = strlen (p);
|
||||
}
|
||||
|
||||
fwrite (result, resultlen, 1, stdout);
|
||||
xfree (result);
|
||||
}
|
||||
|
||||
|
||||
|
||||
int
|
||||
main (int argc, char **argv )
|
||||
{
|
||||
ARGPARSE_ARGS pargs;
|
||||
int cmd = 0;
|
||||
|
||||
set_strusage (my_strusage);
|
||||
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
|
||||
log_set_prefix ("protect-tool", 1);
|
||||
i18n_init ();
|
||||
|
||||
if (!gcry_check_version ( "1.1.5" ) )
|
||||
{
|
||||
log_fatal( _("libgcrypt is too old (need %s, have %s)\n"),
|
||||
"1.1.5", gcry_check_version (NULL) );
|
||||
}
|
||||
|
||||
gcry_set_log_handler (my_gcry_logger, NULL);
|
||||
|
||||
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
|
||||
|
||||
pargs.argc = &argc;
|
||||
pargs.argv = &argv;
|
||||
pargs.flags= 1; /* do not remove the args */
|
||||
while (arg_parse (&pargs, opts) )
|
||||
{
|
||||
switch (pargs.r_opt)
|
||||
{
|
||||
case oVerbose: opt.verbose++; break;
|
||||
case oArmor: opt_armor=1; break;
|
||||
|
||||
case oProtect: cmd = oProtect; break;
|
||||
case oUnprotect: cmd = oUnprotect; break;
|
||||
|
||||
case oPassphrase: passphrase = pargs.r.ret_str; break;
|
||||
|
||||
default : pargs.err = 2; break;
|
||||
}
|
||||
}
|
||||
if (log_get_errorcount(0))
|
||||
exit(2);
|
||||
|
||||
if (argc != 1)
|
||||
usage (1);
|
||||
|
||||
if (cmd == oProtect)
|
||||
read_and_protect (*argv);
|
||||
else if (cmd == oUnprotect)
|
||||
read_and_unprotect (*argv);
|
||||
else
|
||||
log_info ("no action requested\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
agent_exit (int rc)
|
||||
{
|
||||
rc = rc? rc : log_get_errorcount(0)? 2 : 0;
|
||||
exit (rc);
|
||||
}
|
861
agent/protect.c
Normal file
861
agent/protect.c
Normal file
@ -0,0 +1,861 @@
|
||||
/* protect.c - Un/Protect a secret key
|
||||
* Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
|
||||
*
|
||||
* 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
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (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
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "agent.h"
|
||||
|
||||
#define PROT_CIPHER GCRY_CIPHER_AES
|
||||
#define PROT_CIPHER_STRING "aes"
|
||||
#define PROT_CIPHER_KEYLEN (128/8)
|
||||
|
||||
|
||||
/* A table containing the information needed to create a protected
|
||||
private key */
|
||||
static struct {
|
||||
const char *algo;
|
||||
const char *parmlist;
|
||||
int prot_from, prot_to;
|
||||
} protect_info[] = {
|
||||
{ "rsa", "nedpqu", 2, 5 },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
|
||||
static int
|
||||
hash_passphrase (const char *passphrase, int hashalgo,
|
||||
int s2kmode,
|
||||
const unsigned char *s2ksalt, unsigned long s2kcount,
|
||||
unsigned char *key, size_t keylen);
|
||||
|
||||
|
||||
|
||||
/* Return the length of the next S-Exp part and update the pointer to
|
||||
the first data byte. 0 is return on error */
|
||||
static size_t
|
||||
snext (unsigned char const **buf)
|
||||
{
|
||||
const unsigned char *s;
|
||||
int n;
|
||||
|
||||
s = *buf;
|
||||
for (n=0; *s && *s != ':' && digitp (s); s++)
|
||||
n = n*10 + atoi_1 (s);
|
||||
if (!n || *s != ':')
|
||||
return 0; /* we don't allow empty lengths */
|
||||
*buf = s+1;
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Skip over the S-Expression BUF points to and update BUF to point to
|
||||
the chacter right behind. DEPTH gives the initial number of open
|
||||
lists and may be passed as a positive number to skip over the
|
||||
remainder of an S-Expression if the current position is somewhere
|
||||
in an S-Expression. The function may return an error code if it
|
||||
encounters an impossible conditions */
|
||||
static int
|
||||
sskip (unsigned char const **buf, int *depth)
|
||||
{
|
||||
const unsigned char *s = *buf;
|
||||
size_t n;
|
||||
int d = *depth;
|
||||
|
||||
while (d > 0)
|
||||
{
|
||||
if (*s == '(')
|
||||
{
|
||||
d++;
|
||||
s++;
|
||||
}
|
||||
else if (*s == ')')
|
||||
{
|
||||
d--;
|
||||
s++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!d)
|
||||
return GNUPG_Invalid_Sexp;
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
return GNUPG_Invalid_Sexp;
|
||||
s += n;
|
||||
}
|
||||
}
|
||||
*buf = s;
|
||||
*depth = d;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Check whether the the string at the address BUF points to matches
|
||||
the token. Return true on match and update BUF to point behind the
|
||||
token. */
|
||||
static int
|
||||
smatch (unsigned char const **buf, size_t buflen, const char *token)
|
||||
{
|
||||
size_t toklen = strlen (token);
|
||||
|
||||
if (buflen != toklen || memcmp (*buf, token, toklen))
|
||||
return 0;
|
||||
*buf += toklen;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Calculate the MIC for a private key S-Exp. SHA1HASH should pint to
|
||||
a 20 byte buffer. This function is suitable for any algorithms. */
|
||||
static int
|
||||
calculate_mic (const unsigned char *plainkey, unsigned char *sha1hash)
|
||||
{
|
||||
const unsigned char *hash_begin, *hash_end;
|
||||
const unsigned char *s;
|
||||
size_t n;
|
||||
|
||||
s = plainkey;
|
||||
if (*s != '(')
|
||||
return GNUPG_Invalid_Sexp;
|
||||
s++;
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
return GNUPG_Invalid_Sexp;
|
||||
if (!smatch (&s, n, "private-key"))
|
||||
return GNUPG_Unknown_Sexp;
|
||||
if (*s != '(')
|
||||
return GNUPG_Unknown_Sexp;
|
||||
hash_begin = s;
|
||||
s++;
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
return GNUPG_Invalid_Sexp;
|
||||
s += n; /* skip over the algorithm name */
|
||||
|
||||
while (*s == '(')
|
||||
{
|
||||
s++;
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
return GNUPG_Invalid_Sexp;
|
||||
s += n;
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
return GNUPG_Invalid_Sexp;
|
||||
s += n;
|
||||
if ( *s != ')' )
|
||||
return GNUPG_Invalid_Sexp;
|
||||
s++;
|
||||
}
|
||||
if (*s != ')')
|
||||
return GNUPG_Invalid_Sexp;
|
||||
s++;
|
||||
hash_end = s;
|
||||
|
||||
gcry_md_hash_buffer (GCRY_MD_SHA1, sha1hash,
|
||||
hash_begin, hash_end - hash_begin);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Encrypt the parameter block starting at PROTBEGIN with length
|
||||
PROTLEN using the utf8 encoded key PASSPHRASE and return the entire
|
||||
encrypted block in RESULT or ereturn with an error code. SHA1HASH
|
||||
is the 20 byte SHA-1 hash required for the integrity code.
|
||||
|
||||
The parameter block is expected to be an incomplete S-Expression of
|
||||
the form (example in advanced format):
|
||||
|
||||
(d #046129F..[some bytes not shown]..81#)
|
||||
(p #00e861b..[some bytes not shown]..f1#)
|
||||
(q #00f7a7c..[some bytes not shown]..61#)
|
||||
(u #304559a..[some bytes not shown]..9b#)
|
||||
|
||||
the returned block is the S-Expression:
|
||||
|
||||
(protected mode (parms) encrypted_octet_string)
|
||||
|
||||
*/
|
||||
static int
|
||||
do_encryption (const char *protbegin, size_t protlen,
|
||||
const char *passphrase, const unsigned char *sha1hash,
|
||||
unsigned char **result, size_t *resultlen)
|
||||
{
|
||||
GCRY_CIPHER_HD hd;
|
||||
const char *modestr = "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc";
|
||||
int blklen, enclen, outlen;
|
||||
char *iv = NULL;
|
||||
int rc = 0;
|
||||
char *outbuf = NULL;
|
||||
char *p;
|
||||
int saltpos, ivpos, encpos;
|
||||
|
||||
hd = gcry_cipher_open (PROT_CIPHER, GCRY_CIPHER_MODE_CBC,
|
||||
GCRY_CIPHER_SECURE);
|
||||
if (!hd)
|
||||
return map_gcry_err (gcry_errno());
|
||||
|
||||
|
||||
/* We need to work on a copy of the data because this makes it
|
||||
easier to add the trailer and the padding and more important we
|
||||
have to prefix the text with 2 parenthesis, so we have to
|
||||
allocate enough space for:
|
||||
|
||||
((<parameter_list>)(4:hash4:sha120:<hashvalue>)) + padding
|
||||
|
||||
We always append a full block of random bytes as padding but
|
||||
encrypt only what is needed for a full blocksize */
|
||||
blklen = gcry_cipher_get_algo_blklen (PROT_CIPHER);
|
||||
outlen = 2 + protlen + 2 + 6 + 6 + 23 + 2 + blklen;
|
||||
enclen = outlen/blklen * blklen;
|
||||
outbuf = gcry_malloc_secure (outlen);
|
||||
if (!outbuf)
|
||||
rc = GNUPG_Out_Of_Core;
|
||||
if (!rc)
|
||||
{
|
||||
/* allocate random bytes to be used as IV, padding and s2k salt*/
|
||||
iv = gcry_random_bytes (blklen*2+8, GCRY_WEAK_RANDOM);
|
||||
if (!iv)
|
||||
rc = GNUPG_Out_Of_Core;
|
||||
else
|
||||
rc = gcry_cipher_setiv (hd, iv, blklen);
|
||||
}
|
||||
if (!rc)
|
||||
{
|
||||
unsigned char *key;
|
||||
size_t keylen = PROT_CIPHER_KEYLEN;
|
||||
|
||||
key = gcry_malloc_secure (keylen);
|
||||
if (!key)
|
||||
rc = GNUPG_Out_Of_Core;
|
||||
else
|
||||
{
|
||||
rc = hash_passphrase (passphrase, GCRY_MD_SHA1,
|
||||
3, iv+2*blklen, 96, key, keylen);
|
||||
if (!rc)
|
||||
rc = gcry_cipher_setkey (hd, key, keylen);
|
||||
xfree (key);
|
||||
}
|
||||
}
|
||||
if (!rc)
|
||||
{
|
||||
p = outbuf;
|
||||
*p++ = '(';
|
||||
*p++ = '(';
|
||||
memcpy (p, protbegin, protlen);
|
||||
p += protlen;
|
||||
memcpy (p, ")(4:hash4:sha120:", 17);
|
||||
p += 17;
|
||||
memcpy (p, sha1hash, 20);
|
||||
p += 20;
|
||||
*p++ = ')';
|
||||
*p++ = ')';
|
||||
memcpy (p, iv+blklen, blklen);
|
||||
p += blklen;
|
||||
assert ( p - outbuf == outlen);
|
||||
rc = gcry_cipher_encrypt (hd, outbuf, enclen, NULL, 0);
|
||||
}
|
||||
gcry_cipher_close (hd);
|
||||
if (rc)
|
||||
{
|
||||
xfree (iv);
|
||||
xfree (outbuf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Now allocate the buffer we want to return. This is
|
||||
|
||||
(protected openpgp-s2k3-sha1-aes-cbc
|
||||
((sha1 salt no_of_iterations) 16byte_iv)
|
||||
encrypted_octet_string)
|
||||
|
||||
in canoncical format of course. We use asprintf and %n modifier
|
||||
and spaces as palceholders. */
|
||||
asprintf (&p,
|
||||
"(9:protected%d:%s((4:sha18:%n_8bytes_2:96)%d:%n%*s)%d:%n%*s)",
|
||||
(int)strlen (modestr), modestr,
|
||||
&saltpos,
|
||||
blklen, &ivpos, blklen, "",
|
||||
enclen, &encpos, enclen, "");
|
||||
if (p)
|
||||
{ /* asprintf does not use out malloc system */
|
||||
char *psave = p;
|
||||
p = xtrymalloc (strlen (psave)+1);
|
||||
if (p)
|
||||
strcpy (p, psave);
|
||||
free (psave);
|
||||
}
|
||||
if (!p)
|
||||
{
|
||||
xfree (iv);
|
||||
xfree (outbuf);
|
||||
return GNUPG_Out_Of_Core;
|
||||
}
|
||||
*resultlen = strlen (p);
|
||||
*result = p;
|
||||
memcpy (p+saltpos, iv+2*blklen, 8);
|
||||
memcpy (p+ivpos, iv, blklen);
|
||||
memcpy (p+encpos, outbuf, enclen);
|
||||
xfree (iv);
|
||||
xfree (outbuf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Protect the key encoded in canonical format in plainkey. We assume
|
||||
a valid S-Exp here. */
|
||||
int
|
||||
agent_protect (const unsigned char *plainkey, const char *passphrase,
|
||||
unsigned char **result, size_t *resultlen)
|
||||
{
|
||||
int rc;
|
||||
const unsigned char *s;
|
||||
const unsigned char *hash_begin, *hash_end;
|
||||
const unsigned char *prot_begin, *prot_end, *real_end;
|
||||
size_t n;
|
||||
int c, infidx, i;
|
||||
unsigned char hashvalue[20];
|
||||
unsigned char *protected;
|
||||
size_t protectedlen;
|
||||
int depth = 0;
|
||||
unsigned char *p;
|
||||
|
||||
s = plainkey;
|
||||
if (*s != '(')
|
||||
return GNUPG_Invalid_Sexp;
|
||||
depth++;
|
||||
s++;
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
return GNUPG_Invalid_Sexp;
|
||||
if (!smatch (&s, n, "private-key"))
|
||||
return GNUPG_Unknown_Sexp;
|
||||
if (*s != '(')
|
||||
return GNUPG_Unknown_Sexp;
|
||||
depth++;
|
||||
hash_begin = s;
|
||||
s++;
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
return GNUPG_Invalid_Sexp;
|
||||
|
||||
for (infidx=0; protect_info[infidx].algo
|
||||
&& !smatch (&s, n, protect_info[infidx].algo); infidx++)
|
||||
;
|
||||
if (!protect_info[infidx].algo)
|
||||
return GNUPG_Unsupported_Algorithm;
|
||||
|
||||
prot_begin = prot_end = NULL;
|
||||
for (i=0; (c=protect_info[infidx].parmlist[i]); i++)
|
||||
{
|
||||
if (i == protect_info[infidx].prot_from)
|
||||
prot_begin = s;
|
||||
if (*s != '(')
|
||||
return GNUPG_Invalid_Sexp;
|
||||
depth++;
|
||||
s++;
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
return GNUPG_Invalid_Sexp;
|
||||
if (n != 1 || c != *s)
|
||||
return GNUPG_Invalid_Sexp;
|
||||
s += n;
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
return GNUPG_Invalid_Sexp;
|
||||
s +=n; /* skip value */
|
||||
if (*s != ')')
|
||||
return GNUPG_Invalid_Sexp;
|
||||
depth--;
|
||||
if (i == protect_info[infidx].prot_to)
|
||||
prot_end = s;
|
||||
s++;
|
||||
}
|
||||
if (*s != ')' || !prot_begin || !prot_end )
|
||||
return GNUPG_Invalid_Sexp;
|
||||
depth--;
|
||||
hash_end = s;
|
||||
s++;
|
||||
/* skip to the end of the S-exp */
|
||||
assert (depth == 1);
|
||||
rc = sskip (&s, &depth);
|
||||
if (rc)
|
||||
return rc;
|
||||
assert (!depth);
|
||||
real_end = s-1;
|
||||
|
||||
gcry_md_hash_buffer (GCRY_MD_SHA1, hashvalue,
|
||||
hash_begin, hash_end - hash_begin + 1);
|
||||
|
||||
rc = do_encryption (prot_begin, prot_end - prot_begin + 1,
|
||||
passphrase, hashvalue,
|
||||
&protected, &protectedlen);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Now create the protected version of the key. Note that the 10
|
||||
extra bytes are for for the inserted "protected-" string (the
|
||||
beginning of the plaintext reads: "((11:private-key(" ). */
|
||||
*resultlen = (10
|
||||
+ (prot_begin-plainkey)
|
||||
+ protectedlen
|
||||
+ (real_end-prot_end));
|
||||
*result = p = xtrymalloc (*resultlen);
|
||||
if (!p)
|
||||
{
|
||||
xfree (protected);
|
||||
return GNUPG_Out_Of_Core;
|
||||
}
|
||||
memcpy (p, "(21:protected-", 14);
|
||||
p += 14;
|
||||
memcpy (p, plainkey+4, prot_begin - plainkey - 4);
|
||||
p += prot_begin - plainkey - 4;
|
||||
memcpy (p, protected, protectedlen);
|
||||
p += protectedlen;
|
||||
memcpy (p, prot_end+1, real_end - prot_end);
|
||||
p += real_end - prot_end;
|
||||
assert ( p - *result == *resultlen);
|
||||
xfree (protected);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Do the actual decryption and check the return list for consistency. */
|
||||
static int
|
||||
do_decryption (const unsigned char *protected, size_t protectedlen,
|
||||
const char *passphrase,
|
||||
const unsigned char *s2ksalt, unsigned long s2kcount,
|
||||
const unsigned char *iv, size_t ivlen,
|
||||
unsigned char **result)
|
||||
{
|
||||
int rc = 0;
|
||||
int blklen;
|
||||
GCRY_CIPHER_HD hd;
|
||||
unsigned char *outbuf;
|
||||
size_t reallen;
|
||||
|
||||
blklen = gcry_cipher_get_algo_blklen (PROT_CIPHER);
|
||||
if (protectedlen < 4 || (protectedlen%blklen))
|
||||
return GNUPG_Corrupted_Protection;
|
||||
|
||||
hd = gcry_cipher_open (PROT_CIPHER, GCRY_CIPHER_MODE_CBC,
|
||||
GCRY_CIPHER_SECURE);
|
||||
if (!hd)
|
||||
return map_gcry_err (gcry_errno());
|
||||
|
||||
outbuf = gcry_malloc_secure (protectedlen);
|
||||
if (!outbuf)
|
||||
rc = GNUPG_Out_Of_Core;
|
||||
if (!rc)
|
||||
rc = gcry_cipher_setiv (hd, iv, ivlen);
|
||||
if (!rc)
|
||||
{
|
||||
unsigned char *key;
|
||||
size_t keylen = PROT_CIPHER_KEYLEN;
|
||||
|
||||
key = gcry_malloc_secure (keylen);
|
||||
if (!key)
|
||||
rc = GNUPG_Out_Of_Core;
|
||||
else
|
||||
{
|
||||
rc = hash_passphrase (passphrase, GCRY_MD_SHA1,
|
||||
3, s2ksalt, s2kcount, key, keylen);
|
||||
if (!rc)
|
||||
rc = gcry_cipher_setkey (hd, key, keylen);
|
||||
xfree (key);
|
||||
}
|
||||
}
|
||||
if (!rc)
|
||||
rc = gcry_cipher_decrypt (hd, outbuf, protectedlen,
|
||||
protected, protectedlen);
|
||||
gcry_cipher_close (hd);
|
||||
if (rc)
|
||||
{
|
||||
xfree (outbuf);
|
||||
return rc;
|
||||
}
|
||||
/* do a quick check first */
|
||||
if (*outbuf != '(' && outbuf[1] != '(')
|
||||
{
|
||||
xfree (outbuf);
|
||||
return GNUPG_Bad_Passphrase;
|
||||
}
|
||||
/* check that we have a consistent S-Exp */
|
||||
reallen = gcry_sexp_canon_len (outbuf, protectedlen, NULL, NULL);
|
||||
if (!reallen || (reallen + blklen < protectedlen) )
|
||||
{
|
||||
xfree (outbuf);
|
||||
return GNUPG_Bad_Passphrase;
|
||||
}
|
||||
*result = outbuf;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Merge the parameter list contained in CLEARTEXT with the original
|
||||
protect lists PROTECTEDKEY by replacing the list at REPLACEPOS.
|
||||
Return the new list in RESULT and the MIC value in the 20 byte
|
||||
buffer SHA1HASH. */
|
||||
static int
|
||||
merge_lists (const unsigned char *protectedkey,
|
||||
size_t replacepos,
|
||||
const unsigned char *cleartext,
|
||||
unsigned char *sha1hash, unsigned char **result)
|
||||
{
|
||||
size_t n, newlistlen;
|
||||
unsigned char *newlist, *p;
|
||||
const unsigned char *s;
|
||||
const unsigned char *startpos, *endpos;
|
||||
int i, rc;
|
||||
|
||||
if (replacepos < 26)
|
||||
return GNUPG_Bug;
|
||||
|
||||
/* Estimate the required size of the resulting list. We have a large
|
||||
safety margin of >20 bytes (MIC hash from CLEARTEXT and the
|
||||
removed "protected-" */
|
||||
newlistlen = gcry_sexp_canon_len (protectedkey, 0, NULL, NULL);
|
||||
if (!newlistlen)
|
||||
return GNUPG_Bug;
|
||||
n = gcry_sexp_canon_len (cleartext, 0, NULL, NULL);
|
||||
if (!n)
|
||||
return GNUPG_Bug;
|
||||
newlistlen += n;
|
||||
newlist = gcry_malloc_secure (newlistlen);
|
||||
if (!newlist)
|
||||
return GNUPG_Out_Of_Core;
|
||||
|
||||
/* Copy the initial segment */
|
||||
strcpy (newlist, "(11:private-key");
|
||||
p = newlist + 15;
|
||||
memcpy (p, protectedkey+15+10, replacepos-15-10);
|
||||
p += replacepos-15-10;
|
||||
|
||||
/* copy the cleartext */
|
||||
s = cleartext;
|
||||
if (*s != '(' && s[1] != '(')
|
||||
return GNUPG_Bug; /*we already checked this */
|
||||
s += 2;
|
||||
startpos = s;
|
||||
while ( *s == '(' )
|
||||
{
|
||||
s++;
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
goto invalid_sexp;
|
||||
s += n;
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
goto invalid_sexp;
|
||||
s += n;
|
||||
if ( *s != ')' )
|
||||
goto invalid_sexp;
|
||||
s++;
|
||||
}
|
||||
if ( *s != ')' )
|
||||
goto invalid_sexp;
|
||||
endpos = s;
|
||||
s++;
|
||||
/* short intermezzo: Get the MIC */
|
||||
if (*s != '(')
|
||||
goto invalid_sexp;
|
||||
s++;
|
||||
n = snext (&s);
|
||||
if (!smatch (&s, n, "hash"))
|
||||
goto invalid_sexp;
|
||||
n = snext (&s);
|
||||
if (!smatch (&s, n, "sha1"))
|
||||
goto invalid_sexp;
|
||||
n = snext (&s);
|
||||
if (n != 20)
|
||||
goto invalid_sexp;
|
||||
memcpy (sha1hash, s, 20);
|
||||
s += n;
|
||||
if (*s != ')')
|
||||
goto invalid_sexp;
|
||||
/* end intermezzo */
|
||||
|
||||
/* append the parameter list */
|
||||
memcpy (p, startpos, endpos - startpos);
|
||||
p += endpos - startpos;
|
||||
|
||||
/* skip overt the protected list element in the original list */
|
||||
s = protectedkey + replacepos;
|
||||
assert (*s == '(');
|
||||
s++;
|
||||
i = 1;
|
||||
rc = sskip (&s, &i);
|
||||
if (rc)
|
||||
goto failure;
|
||||
startpos = s;
|
||||
i = 2; /* we are inside this level */
|
||||
rc = sskip (&s, &i);
|
||||
if (rc)
|
||||
goto failure;
|
||||
assert (s[-1] == ')');
|
||||
endpos = s; /* one behind the end of the list */
|
||||
|
||||
/* append the rest */
|
||||
memcpy (p, startpos, endpos - startpos);
|
||||
p += endpos - startpos;
|
||||
|
||||
/* ready */
|
||||
*result = newlist;
|
||||
return 0;
|
||||
|
||||
failure:
|
||||
xfree (newlist);
|
||||
return rc;
|
||||
|
||||
invalid_sexp:
|
||||
xfree (newlist);
|
||||
return GNUPG_Invalid_Sexp;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Unprotect the key encoded in canonical format. We assume a valid
|
||||
S-Exp here. */
|
||||
int
|
||||
agent_unprotect (const unsigned char *protectedkey, const char *passphrase,
|
||||
unsigned char **result, size_t *resultlen)
|
||||
{
|
||||
int rc;
|
||||
const unsigned char *s;
|
||||
size_t n;
|
||||
int infidx, i;
|
||||
unsigned char sha1hash[20], sha1hash2[20];
|
||||
const unsigned char *s2ksalt;
|
||||
unsigned long s2kcount;
|
||||
const unsigned char *iv;
|
||||
const unsigned char *prot_begin;
|
||||
unsigned char *cleartext;
|
||||
unsigned char *final;
|
||||
|
||||
s = protectedkey;
|
||||
if (*s != '(')
|
||||
return GNUPG_Invalid_Sexp;
|
||||
s++;
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
return GNUPG_Invalid_Sexp;
|
||||
if (!smatch (&s, n, "protected-private-key"))
|
||||
return GNUPG_Unknown_Sexp;
|
||||
if (*s != '(')
|
||||
return GNUPG_Unknown_Sexp;
|
||||
s++;
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
return GNUPG_Invalid_Sexp;
|
||||
|
||||
for (infidx=0; protect_info[infidx].algo
|
||||
&& !smatch (&s, n, protect_info[infidx].algo); infidx++)
|
||||
;
|
||||
if (!protect_info[infidx].algo)
|
||||
return GNUPG_Unsupported_Algorithm;
|
||||
|
||||
/* now find the list with the protected information. Here is an
|
||||
example for such a list:
|
||||
(protected openpgp-s2k3-sha1-aes-cbc
|
||||
((sha1 <salt> <count>) <Initialization_Vector>)
|
||||
<encrypted_data>)
|
||||
*/
|
||||
for (;;)
|
||||
{
|
||||
if (*s != '(')
|
||||
return GNUPG_Invalid_Sexp;
|
||||
prot_begin = s;
|
||||
s++;
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
return GNUPG_Invalid_Sexp;
|
||||
if (smatch (&s, n, "protected"))
|
||||
break;
|
||||
s += n;
|
||||
i = 1;
|
||||
rc = sskip (&s, &i);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
/* found */
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
return GNUPG_Invalid_Sexp;
|
||||
if (!smatch (&s, n, "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc"))
|
||||
return GNUPG_Unsupported_Protection;
|
||||
if (*s != '(' || s[1] != '(')
|
||||
return GNUPG_Invalid_Sexp;
|
||||
s += 2;
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
return GNUPG_Invalid_Sexp;
|
||||
if (!smatch (&s, n, "sha1"))
|
||||
return GNUPG_Unsupported_Protection;
|
||||
n = snext (&s);
|
||||
if (n != 8)
|
||||
return GNUPG_Corrupted_Protection;
|
||||
s2ksalt = s;
|
||||
s += n;
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
return GNUPG_Corrupted_Protection;
|
||||
/* We expect a list close as next, so we can simply use strtoul()
|
||||
here. We might want to check that we only have digits - but this
|
||||
is nothing we should worry about */
|
||||
if (s[n] != ')' )
|
||||
return GNUPG_Invalid_Sexp;
|
||||
s2kcount = strtoul (s, NULL, 10);
|
||||
if (!s2kcount)
|
||||
return GNUPG_Corrupted_Protection;
|
||||
s += n;
|
||||
s++; /* skip list end */
|
||||
|
||||
n = snext (&s);
|
||||
if (n != 16) /* Wrong blocksize for IV (we support ony aes-128) */
|
||||
return GNUPG_Corrupted_Protection;
|
||||
iv = s;
|
||||
s += n;
|
||||
if (*s != ')' )
|
||||
return GNUPG_Invalid_Sexp;
|
||||
s++;
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
return GNUPG_Invalid_Sexp;
|
||||
|
||||
rc = do_decryption (s, n,
|
||||
passphrase, s2ksalt, s2kcount,
|
||||
iv, 16,
|
||||
&cleartext);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = merge_lists (protectedkey, prot_begin-protectedkey, cleartext,
|
||||
sha1hash, &final);
|
||||
xfree (cleartext);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = calculate_mic (final, sha1hash2);
|
||||
if (!rc && memcmp (sha1hash, sha1hash2, 20))
|
||||
rc = GNUPG_Corrupted_Protection;
|
||||
if (rc)
|
||||
{
|
||||
xfree (final);
|
||||
return rc;
|
||||
}
|
||||
|
||||
*result = final;
|
||||
*resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* Transform a passphrase into a suitable key of length KEYLEN and
|
||||
store this key in the caller provided buffer KEY. The caller must
|
||||
provide an HASHALGO, a valid S2KMODE (see rfc-2440) and depending on
|
||||
that mode an S2KSALT of 8 random bytes and an S2KCOUNT (a suitable
|
||||
value is 96).
|
||||
|
||||
Returns an error code on failure. */
|
||||
static int
|
||||
hash_passphrase (const char *passphrase, int hashalgo,
|
||||
int s2kmode,
|
||||
const unsigned char *s2ksalt,
|
||||
unsigned long s2kcount,
|
||||
unsigned char *key, size_t keylen)
|
||||
{
|
||||
GCRY_MD_HD md;
|
||||
int pass, i;
|
||||
int used = 0;
|
||||
int pwlen = strlen (passphrase);
|
||||
|
||||
if ( (s2kmode != 0 && s2kmode != 1 && s2kmode != 3)
|
||||
|| !hashalgo || !keylen || !key || !passphrase)
|
||||
return GNUPG_Invalid_Value;
|
||||
if ((s2kmode == 1 ||s2kmode == 3) && !s2ksalt)
|
||||
return GNUPG_Invalid_Value;
|
||||
|
||||
md = gcry_md_open (hashalgo, GCRY_MD_FLAG_SECURE);
|
||||
if (!md)
|
||||
return map_gcry_err (gcry_errno());
|
||||
|
||||
for (pass=0; used < keylen; pass++)
|
||||
{
|
||||
if (pass)
|
||||
{
|
||||
gcry_md_reset (md);
|
||||
for (i=0; i < pass; i++) /* preset the hash context */
|
||||
gcry_md_putc (md, 0);
|
||||
}
|
||||
|
||||
if (s2kmode == 1 || s2kmode == 3)
|
||||
{
|
||||
int len2 = pwlen + 8;
|
||||
unsigned long count = len2;
|
||||
|
||||
if (s2kmode == 3)
|
||||
{
|
||||
count = (16ul + (s2kcount & 15)) << ((s2kcount >> 4) + 6);
|
||||
if (count < len2)
|
||||
count = len2;
|
||||
}
|
||||
|
||||
while (count > len2)
|
||||
{
|
||||
gcry_md_write (md, s2ksalt, 8);
|
||||
gcry_md_write (md, passphrase, pwlen);
|
||||
count -= len2;
|
||||
}
|
||||
if (count < 8)
|
||||
gcry_md_write (md, s2ksalt, count);
|
||||
else
|
||||
{
|
||||
gcry_md_write (md, s2ksalt, 8);
|
||||
count -= 8;
|
||||
gcry_md_write (md, passphrase, count);
|
||||
}
|
||||
}
|
||||
else
|
||||
gcry_md_write (md, passphrase, pwlen);
|
||||
|
||||
gcry_md_final (md);
|
||||
i = gcry_md_get_algo_dlen (hashalgo);
|
||||
if (i > keylen - used)
|
||||
i = keylen - used;
|
||||
memcpy (key+used, gcry_md_read (md, hashalgo), i);
|
||||
used += i;
|
||||
}
|
||||
gcry_md_close(md);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -137,13 +137,13 @@ all_digitsp( const char *s)
|
||||
number here and repeat it as long as we have invalid formed
|
||||
numbers. */
|
||||
int
|
||||
agent_askpin (const char *desc_text,
|
||||
agent_askpin (const char *desc_text, const char *start_err_text,
|
||||
struct pin_entry_info_s *pininfo)
|
||||
{
|
||||
int rc;
|
||||
char line[ASSUAN_LINELENGTH];
|
||||
struct entry_parm_s parm;
|
||||
const char *errtext = NULL;
|
||||
const char *errtext = start_err_text;
|
||||
|
||||
if (opt.batch)
|
||||
return 0; /* fixme: we should return BAD PIN */
|
||||
@ -180,8 +180,14 @@ agent_askpin (const char *desc_text,
|
||||
if (errtext)
|
||||
{
|
||||
/* fixme: should we show the try count? It must be translated */
|
||||
snprintf (line, DIM(line)-1, "SETERROR %s (try %d of %d)",
|
||||
errtext, pininfo->failed_tries+1, pininfo->max_tries);
|
||||
if (start_err_text)
|
||||
{
|
||||
snprintf (line, DIM(line)-1, "SETERROR %s", errtext);
|
||||
start_err_text = NULL;
|
||||
}
|
||||
else
|
||||
snprintf (line, DIM(line)-1, "SETERROR %s (try %d of %d)",
|
||||
errtext, pininfo->failed_tries+1, pininfo->max_tries);
|
||||
line[DIM(line)-1] = 0;
|
||||
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL);
|
||||
if (rc)
|
||||
|
Loading…
x
Reference in New Issue
Block a user