1
0
mirror of git://git.gnupg.org/gnupg.git synced 2024-09-20 14:51:42 +02:00

Implement unattended OpenPGP secret key import.

* agent/command.c (cmd_import_key): Add option --unattended.
* agent/cvt-openpgp.c (convert_transfer_key): New.
(do_unprotect): Factor some code out to ...
(prepare_unprotect): new function.
(convert_from_openpgp): Factor all code out to ...
(convert_from_openpgp_main): this.  Add arg 'passphrase'.  Implement
openpgp-native protection modes.
(convert_from_openpgp_native): New.
* agent/t-protect.c (convert_from_openpgp_native): New dummy fucntion
* agent/protect-tool.c (convert_from_openpgp_native): Ditto.
* agent/protect.c (agent_unprotect): Add arg CTRL.  Adjust all
callers.  Support openpgp-native protection.
* g10/call-agent.c (agent_import_key): Add arg 'unattended'.
* g10/import.c (transfer_secret_keys): Use unattended in batch mode.
--

With the gpg-agent taking care of the secret keys, the user needs to
migrate existing keys from secring.gpg to the agent.  This and also
the standard import of secret keys required the user to unprotect the
secret keys first, so that gpg-agent was able to re-protected them
using its own scheme.  With many secret keys this is quite some
usability hurdle.  In particular if a passphrase is not instantly
available.

To make this migration smoother, this patch implements an unattended
key import/migration which delays the conversion to the gpg-agent
format until the key is actually used.  For example:

   gpg2 --batch --import mysecretkey.gpg

works without any user interaction due to the use of --batch.  Now if
a key is used (e.g. "gpg2 -su USERID_FROM_MYSECRETKEY foo"), gpg-agent
has to ask for the passphrase anyway, converts the key from the
openpgp format to the internal format, signs, re-encrypts the key and
tries to store it in the gpg-agent format to the disk.  The next time,
the internal format of the key is used.

This patch has only been tested with the old demo keys, more tests
with other protection formats and no protection are needed.

Signed-off-by: Werner Koch <wk@gnupg.org>
This commit is contained in:
Werner Koch 2013-05-22 09:50:12 +01:00
parent cb6a64bb78
commit 7777e68d04
13 changed files with 475 additions and 171 deletions

57
README
View File

@ -5,7 +5,8 @@
THIS IS A DEVELOPMENT VERSION AND NOT INTENDED FOR REGULAR USE.
Copyright 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
2006, 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
2006, 2007, 2008, 2009, 2010, 2011, 2012,
2013 Free Software Foundation, Inc.
INTRODUCTION
@ -32,21 +33,22 @@ BUILD INSTRUCTIONS
GnuPG 2.1 depends on the following packages:
npth (ftp://ftp.gnupg.org/gcrypt/npth/)
libgpg-error (ftp://ftp.gnupg.org/gcrypt/libgpg-error/)
libgcrypt (ftp://ftp.gnupg.org/gcrypt/libgcrypt/)
libksba (ftp://ftp.gnupg.org/gcrypt/libksba/)
libassuan (ftp://ftp.gnupg.org/gcrypt/libassuan/)
You also need the Pinentry package for most function of GnuPG; however
it is not a build requirement. Pinentry is available at
ftp://ftp.gnupg.org/gcrypt/pinentry/ .
You should get the latest versions of course, the GnuPG configure
script complains if a version is not sufficient.
You also need the Pinentry package for most functions of GnuPG;
however it is not a build requirement. Pinentry is available at
ftp://ftp.gnupg.org/gcrypt/pinentry/ .
After building and installing the above packages in the order as given
above, you may now continue with GnuPG installation (you may also just
try to build GnuPG to see whether your already installed versions are
above, you may continue with GnuPG installation (you may also just try
to build GnuPG to see whether your already installed versions are
sufficient).
As with all packages, you just have to do
@ -62,7 +64,8 @@ S/MIME and smartcards. Note that there is no binary gpg but a gpg2 so
that this package won't conflict with a GnuPG 1.4 installation. gpg2
behaves just like gpg.
In case of problem please ask on gnupg-users@gnupg.org for advise.
In case of problem please ask on gnupg-users@gnupg.org mailing list
for advise.
Note that the PKITS tests are always skipped unless you copy the PKITS
test data file into the tests/pkits directory. There is no need to
@ -79,21 +82,24 @@ to view the default directories used by GnuPG.
MIGRATION FROM 1.4 or 2.0 to 2.1
================================
The major change in 2.1 is that gpg-agent now takes care of the
OpenPGP secret keys (those managed by GPG). The former secring.gpg
will not be used anymore. Newly generated keys are generated and
stored in the agent's key store (~/.gnupg/private-keys-v1.d/). To
migrate your existing keys to the agent you should run this command
The major change in 2.1 is gpg-agent taking care of the OpenPGP secret
keys (those managed by GPG). The former file "secring.gpg" will not
be used anymore. Newly generated keys are stored in the agent's key
store directory "~/.gnupg/private-keys-v1.d/".
gpg2 --import ~/.gnupg/secring.gpg
To migrate your existing keys you need to run the command
The agent will you ask for the passphrase of each key. You may use
the Cancel button of the Pinentry to skip importing this key. If you
want to stop the import process and you use one of the latest
pinentries, you should close the pinentry window instead of hitting
the cancel button. Secret keys already imported are skipped by the
import command. It is advisable to keep the secring.gpg for use with
older versions of GPG.
gpg2 --batch --import ~/.gnupg/secring.gpg
Secret keys already imported are skipped by this command. It is
advisable to keep the secring.gpg for use with older versions of GPG.
The use of "--batch" with "--import" is highly recommended. If you do
not use "--batch" the agent would ask for the passphrase of each key.
In this case you may use the Cancel button of the Pinentry to skip
importing this key. If you want to stop the enite import process and
you use a decent version of Pinentry, you should close the Pinentry
window instead of hitting the Cancel button.
Note that gpg-agent now uses a fixed socket by default. All tools
will start the gpg-agent as needed. In general there is no more need
@ -182,8 +188,13 @@ authors directly as we are busy working on improvements and bug fixes.
The English and German mailing lists are watched by the authors and we
try to answer questions when time allows us to do so.
Commercial grade support for GnuPG is available; please see
http://www.gnupg.org/service.html .
Commercial grade support for GnuPG is available; for a listing of
offers see http://www.gnupg.org/service.html . The driving force
behind the development of GnuPG is the company of its principal
author, Werner Koch. Maintenance and improvement of GnuPG and related
software takes up most of their resources. To allow him to continue
his work he asks to either purchase a support contract, engage them
for custom enhancements, or to donate money. See http://g10code.com .
This file is Free Software; as a special exception the authors gives

View File

@ -374,7 +374,8 @@ unsigned char get_standard_s2k_count_rfc4880 (void);
int agent_protect (const unsigned char *plainkey, const char *passphrase,
unsigned char **result, size_t *resultlen,
unsigned long s2k_count);
int agent_unprotect (const unsigned char *protectedkey, const char *passphrase,
int agent_unprotect (ctrl_t ctrl,
const unsigned char *protectedkey, const char *passphrase,
gnupg_isotime_t protected_at,
unsigned char **result, size_t *resultlen);
int agent_private_key_type (const unsigned char *privatekey);

View File

@ -1,6 +1,7 @@
/* command.c - gpg-agent command handler
* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2008, 2009, 2010,
* 2011 Free Software Foundation, Inc.
* Copyright (C) 2013 Werner Koch
*
* This file is part of GnuPG.
*
@ -1807,18 +1808,21 @@ cmd_keywrap_key (assuan_context_t ctx, char *line)
static const char hlp_import_key[] =
"IMPORT_KEY [<cache_nonce>]\n"
"IMPORT_KEY [--unattended] [<cache_nonce>]\n"
"\n"
"Import a secret key into the key store. The key is expected to be\n"
"encrypted using the current session's key wrapping key (cf. command\n"
"KEYWRAP_KEY) using the AESWRAP-128 algorithm. This function takes\n"
"no arguments but uses the inquiry \"KEYDATA\" to ask for the actual\n"
"key data. The unwrapped key must be a canonical S-expression.";
"key data. The unwrapped key must be a canonical S-expression. The\n"
"option --unattended tries to import the key as-is without any\n"
"re-encryption";
static gpg_error_t
cmd_import_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int opt_unattended;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
gcry_cipher_hd_t cipherhd = NULL;
@ -1838,6 +1842,9 @@ cmd_import_key (assuan_context_t ctx, char *line)
goto leave;
}
opt_unattended = has_option (line, "--unattended");
line = skip_options (line);
p = line;
for (p=line; *p && *p != ' ' && *p != '\t'; p++)
;
@ -1921,7 +1928,7 @@ cmd_import_key (assuan_context_t ctx, char *line)
key = NULL;
err = convert_from_openpgp (ctrl, openpgp_sexp, grip,
ctrl->server_local->keydesc, cache_nonce,
&key, &passphrase);
&key, opt_unattended? NULL : &passphrase);
if (err)
goto leave;
realkeylen = gcry_sexp_canon_len (key, 0, NULL, &err);
@ -1929,6 +1936,7 @@ cmd_import_key (assuan_context_t ctx, char *line)
goto leave; /* Invalid canonical encoded S-expression. */
if (passphrase)
{
assert (!opt_unattended);
if (!cache_nonce)
{
char buf[12];
@ -1941,6 +1949,12 @@ cmd_import_key (assuan_context_t ctx, char *line)
assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
}
}
else if (opt_unattended)
{
err = set_error (GPG_ERR_ASS_PARAMETER,
"\"--unattended\" may only be used with OpenPGP keys");
goto leave;
}
else
{
if (!agent_key_available (grip))
@ -1957,7 +1971,7 @@ cmd_import_key (assuan_context_t ctx, char *line)
if (passphrase)
{
err = agent_protect (key, passphrase, &finalkey, &finalkeylen,
ctrl->s2k_count);
ctrl->s2k_count);
if (!err)
err = agent_write_private_key (grip, finalkey, finalkeylen, 0);
}

View File

@ -1,6 +1,7 @@
/* cvt-openpgp.c - Convert an OpenPGP key to our internal format.
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2006, 2009,
* 2010 Free Software Foundation, Inc.
* Copyright (C) 2013 Werner Koch
*
* This file is part of GnuPG.
*
@ -160,6 +161,72 @@ convert_secret_key (gcry_sexp_t *r_key, int pubkey_algo, gcry_mpi_t *skey)
}
/* Convert a secret key given as algorithm id, an array of key
parameters, and an S-expression of the original OpenPGP transfer
key into our s-expression based format. This is a variant of
convert_secret_key which is used for the openpgp-native protection
mode. Note that PUBKEY_ALGO has an gcrypt algorithm number. */
static gpg_error_t
convert_transfer_key (gcry_sexp_t *r_key, int pubkey_algo, gcry_mpi_t *skey,
gcry_sexp_t transfer_key)
{
gpg_error_t err;
gcry_sexp_t s_skey = NULL;
*r_key = NULL;
switch (pubkey_algo)
{
case GCRY_PK_DSA:
err = gcry_sexp_build
(&s_skey, NULL,
"(protected-private-key(dsa(p%m)(q%m)(g%m)(y%m)"
"(protected openpgp-native%S)))",
skey[0], skey[1], skey[2], skey[3], transfer_key);
break;
case GCRY_PK_ELG:
case GCRY_PK_ELG_E:
err = gcry_sexp_build
(&s_skey, NULL,
"(protected-private-key(elg(p%m)(g%m)(y%m)"
"(protected openpgp-native%S)))",
skey[0], skey[1], skey[2], transfer_key);
break;
case GCRY_PK_RSA:
case GCRY_PK_RSA_E:
case GCRY_PK_RSA_S:
err = gcry_sexp_build
(&s_skey, NULL,
"(protected-private-key(rsa(n%m)(e%m)",
"(protected openpgp-native%S)))",
skey[0], skey[1], transfer_key );
break;
case GCRY_PK_ECDSA:
case GCRY_PK_ECDH:
/* Although our code would work with "ecc" we explicitly use
"ecdh" or "ecdsa" to implicitly set the key capabilities. */
err = gcry_sexp_build
(&s_skey, NULL,
"(protected-private-key(%s(p%m)(a%m)(b%m)(g%m)(n%m)(q%m)"
"(protected openpgp-native%S)))",
pubkey_algo == GCRY_PK_ECDSA?"ecdsa":"ecdh",
skey[0], skey[1], skey[2], skey[3], skey[4], skey[5], transfer_key);
break;
default:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
break;
}
if (!err)
*r_key = s_skey;
return err;
}
/* Hash the passphrase and set the key. */
static gpg_error_t
@ -202,30 +269,19 @@ checksum (const unsigned char *p, unsigned int n)
}
/* Note that this function modified SKEY. SKEYSIZE is the allocated
size of the array including the NULL item; this is used for a
bounds check. On success a converted key is stored at R_KEY. */
/* Helper for do_unprotect. PUBKEY_ALOGO is the gcrypt algo number.
On success R_NPKEY and R_NSKEY receive the number or parameters for
the algorithm PUBKEY_ALGO and R_SKEYLEN the used length of
SKEY. */
static int
do_unprotect (const char *passphrase,
int pkt_version, int pubkey_algo, int is_protected,
gcry_mpi_t *skey, size_t skeysize,
int protect_algo, void *protect_iv, size_t protect_ivlen,
int s2k_mode, int s2k_algo, byte *s2k_salt, u32 s2k_count,
u16 desired_csum, gcry_sexp_t *r_key)
prepare_unprotect (int pubkey_algo, gcry_mpi_t *skey, size_t skeysize,
int s2k_mode,
unsigned int *r_npkey, unsigned int *r_nskey,
unsigned int *r_skeylen)
{
gpg_error_t err;
size_t npkey, nskey, skeylen;
gcry_cipher_hd_t cipher_hd = NULL;
u16 actual_csum;
size_t nbytes;
int i;
gcry_mpi_t tmpmpi;
*r_key = NULL;
/* Unfortunately, the OpenPGP PK algorithm numbers need to be
re-mapped for Libgcrypt. */
pubkey_algo = map_pk_openpgp_to_gcry (pubkey_algo);
/* Count the actual number of MPIs is in the array and set the
remainder to NULL for easier processing later on. */
@ -264,6 +320,54 @@ do_unprotect (const char *passphrase,
if (nskey+1 >= skeysize)
return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
/* Check that the public key parameters are all available and not
encrypted. */
for (i=0; i < npkey; i++)
{
if (!skey[i] || gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_OPAQUE))
return gpg_error (GPG_ERR_BAD_SECKEY);
}
if (r_npkey)
*r_npkey = npkey;
if (r_nskey)
*r_nskey = nskey;
if (r_skeylen)
*r_skeylen = skeylen;
return 0;
}
/* Note that this function modifies SKEY. SKEYSIZE is the allocated
size of the array including the NULL item; this is used for a
bounds check. On success a converted key is stored at R_KEY. */
static int
do_unprotect (const char *passphrase,
int pkt_version, int pubkey_algo, int is_protected,
gcry_mpi_t *skey, size_t skeysize,
int protect_algo, void *protect_iv, size_t protect_ivlen,
int s2k_mode, int s2k_algo, byte *s2k_salt, u32 s2k_count,
u16 desired_csum, gcry_sexp_t *r_key)
{
gpg_error_t err;
unsigned int npkey, nskey, skeylen;
gcry_cipher_hd_t cipher_hd = NULL;
u16 actual_csum;
size_t nbytes;
int i;
gcry_mpi_t tmpmpi;
*r_key = NULL;
/* Unfortunately, the OpenPGP PK algorithm numbers need to be
re-mapped for Libgcrypt. */
pubkey_algo = map_pk_openpgp_to_gcry (pubkey_algo);
err = prepare_unprotect (pubkey_algo, skey, skeysize, s2k_mode,
&npkey, &nskey, &skeylen);
if (err)
return err;
/* Check whether SKEY is at all protected. If it is not protected
merely verify the checksum. */
if (!is_protected)
@ -512,7 +616,7 @@ do_unprotect (const char *passphrase,
}
/* Callback function to try the unprotection from the passpharse query
/* Callback function to try the unprotection from the passphrase query
code. */
static int
try_do_unprotect_cb (struct pin_entry_info_s *pi)
@ -536,24 +640,19 @@ try_do_unprotect_cb (struct pin_entry_info_s *pi)
}
/* Convert an OpenPGP transfer key into our internal format. Before
asking for a passphrase we check whether the key already exists in
our key storage. S_PGP is the OpenPGP key in transfer format. If
CACHE_NONCE is given the passphrase will be looked up in the cache.
On success R_KEY will receive a canonical encoded S-expression with
the unprotected key in our internal format; the caller needs to
release that memory. The passphrase used to decrypt the OpenPGP
key will be returned at R_PASSPHRASE; the caller must release this
passphrase. The keygrip will be stored at the 20 byte buffer
pointed to by GRIP. On error NULL is stored at all return
arguments. */
gpg_error_t
convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp,
unsigned char *grip, const char *prompt,
const char *cache_nonce,
unsigned char **r_key, char **r_passphrase)
/* See convert_from_openpgp for the core of the description. This
function adds an optional PASSPHRASE argument and uses this to
silently decrypt the key; CACHE_NONCE and R_PASSPHRASE must both be
NULL in this mode. */
static gpg_error_t
convert_from_openpgp_main (ctrl_t ctrl, gcry_sexp_t s_pgp,
unsigned char *grip, const char *prompt,
const char *cache_nonce, const char *passphrase,
unsigned char **r_key, char **r_passphrase)
{
gpg_error_t err;
int unattended;
int from_native;
gcry_sexp_t top_list;
gcry_sexp_t list = NULL;
const char *value;
@ -573,12 +672,13 @@ convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp,
gcry_mpi_t skey[10]; /* We support up to 9 parameters. */
u16 desired_csum;
int skeyidx = 0;
gcry_sexp_t s_skey;
struct pin_entry_info_s *pi;
struct try_do_unprotect_arg_s pi_arg;
gcry_sexp_t s_skey = NULL;
*r_key = NULL;
*r_passphrase = NULL;
if (r_passphrase)
*r_passphrase = NULL;
unattended = !r_passphrase;
from_native = (!cache_nonce && passphrase && !r_passphrase);
top_list = gcry_sexp_find_token (s_pgp, "openpgp-private-key", 0);
if (!top_list)
@ -607,6 +707,7 @@ convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp,
is_protected = 0;
else
goto bad_seckey;
if (is_protected)
{
string = gcry_sexp_nth_string (list, 2);
@ -755,64 +856,89 @@ convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp,
if (err)
goto leave;
if (!agent_key_available (grip))
if (!from_native && !agent_key_available (grip))
{
err = gpg_error (GPG_ERR_EEXIST);
goto leave;
}
pi = xtrycalloc_secure (1, sizeof (*pi) + 100);
if (!pi)
return gpg_error_from_syserror ();
pi->max_length = 100;
pi->min_digits = 0; /* We want a real passphrase. */
pi->max_digits = 16;
pi->max_tries = 3;
pi->check_cb = try_do_unprotect_cb;
pi->check_cb_arg = &pi_arg;
pi_arg.is_v4 = is_v4;
pi_arg.is_protected = is_protected;
pi_arg.pubkey_algo = pubkey_algo;
pi_arg.protect_algo = protect_algo;
pi_arg.iv = iv;
pi_arg.ivlen = ivlen;
pi_arg.s2k_mode = s2k_mode;
pi_arg.s2k_algo = s2k_algo;
pi_arg.s2k_salt = s2k_salt;
pi_arg.s2k_count = s2k_count;
pi_arg.desired_csum = desired_csum;
pi_arg.skey = skey;
pi_arg.skeysize = DIM (skey);
pi_arg.skeyidx = skeyidx;
pi_arg.r_key = &s_skey;
err = gpg_error (GPG_ERR_BAD_PASSPHRASE);
if (cache_nonce)
if (unattended && !from_native)
{
char *cache_value;
int pubkey_g_algo = map_pk_openpgp_to_gcry (pubkey_algo);
cache_value = agent_get_cache (cache_nonce, CACHE_MODE_NONCE);
if (cache_value)
err = prepare_unprotect (pubkey_g_algo, skey, DIM(skey), s2k_mode,
NULL, NULL, NULL);
if (err)
goto leave;
err = convert_transfer_key (&s_skey, pubkey_g_algo, skey, s_pgp);
if (err)
goto leave;
}
else
{
struct pin_entry_info_s *pi;
struct try_do_unprotect_arg_s pi_arg;
pi = xtrycalloc_secure (1, sizeof (*pi) + 100);
if (!pi)
return gpg_error_from_syserror ();
pi->max_length = 100;
pi->min_digits = 0; /* We want a real passphrase. */
pi->max_digits = 16;
pi->max_tries = 3;
pi->check_cb = try_do_unprotect_cb;
pi->check_cb_arg = &pi_arg;
pi_arg.is_v4 = is_v4;
pi_arg.is_protected = is_protected;
pi_arg.pubkey_algo = pubkey_algo;
pi_arg.protect_algo = protect_algo;
pi_arg.iv = iv;
pi_arg.ivlen = ivlen;
pi_arg.s2k_mode = s2k_mode;
pi_arg.s2k_algo = s2k_algo;
pi_arg.s2k_salt = s2k_salt;
pi_arg.s2k_count = s2k_count;
pi_arg.desired_csum = desired_csum;
pi_arg.skey = skey;
pi_arg.skeysize = DIM (skey);
pi_arg.skeyidx = skeyidx;
pi_arg.r_key = &s_skey;
err = gpg_error (GPG_ERR_BAD_PASSPHRASE);
if (cache_nonce)
{
if (strlen (cache_value) < pi->max_length)
strcpy (pi->pin, cache_value);
xfree (cache_value);
char *cache_value;
cache_value = agent_get_cache (cache_nonce, CACHE_MODE_NONCE);
if (cache_value)
{
if (strlen (cache_value) < pi->max_length)
strcpy (pi->pin, cache_value);
xfree (cache_value);
}
if (*pi->pin)
err = try_do_unprotect_cb (pi);
}
if (*pi->pin)
err = try_do_unprotect_cb (pi);
else if (from_native)
{
if (strlen (passphrase) < pi->max_length)
strcpy (pi->pin, passphrase);
err = try_do_unprotect_cb (pi);
}
if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE && !from_native)
err = agent_askpin (ctrl, prompt, NULL, NULL, pi);
skeyidx = pi_arg.skeyidx;
if (!err && r_passphrase)
{
*r_passphrase = xtrystrdup (pi->pin);
if (!*r_passphrase)
err = gpg_error_from_syserror ();
}
xfree (pi);
if (err)
goto leave;
}
if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE)
err = agent_askpin (ctrl, prompt, NULL, NULL, pi);
skeyidx = pi_arg.skeyidx;
if (!err)
{
*r_passphrase = xtrystrdup (pi->pin);
if (!*r_passphrase)
err = gpg_error_from_syserror ();
}
xfree (pi);
if (err)
goto leave;
/* Save some memory and get rid of the SKEY array now. */
for (idx=0; idx < skeyidx; idx++)
@ -820,16 +946,16 @@ convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp,
skeyidx = 0;
/* Note that the padding is not required - we use it only because
that function allows us to created the result in secure memory. */
that function allows us to create the result in secure memory. */
err = make_canon_sexp_pad (s_skey, 1, r_key, NULL);
gcry_sexp_release (s_skey);
leave:
gcry_sexp_release (s_skey);
gcry_sexp_release (list);
gcry_sexp_release (top_list);
for (idx=0; idx < skeyidx; idx++)
gcry_mpi_release (skey[idx]);
if (err)
if (err && r_passphrase)
{
xfree (*r_passphrase);
*r_passphrase = NULL;
@ -847,6 +973,63 @@ convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp,
}
/* Convert an OpenPGP transfer key into our internal format. Before
asking for a passphrase we check whether the key already exists in
our key storage. S_PGP is the OpenPGP key in transfer format. If
CACHE_NONCE is given the passphrase will be looked up in the cache.
On success R_KEY will receive a canonical encoded S-expression with
the unprotected key in our internal format; the caller needs to
release that memory. The passphrase used to decrypt the OpenPGP
key will be returned at R_PASSPHRASE; the caller must release this
passphrase. If R_PASSPHRASE is NULL the unattended conversion mode
will be used which uses the openpgp-native protection format for
the key. The keygrip will be stored at the 20 byte buffer pointed
to by GRIP. On error NULL is stored at all return arguments. */
gpg_error_t
convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp,
unsigned char *grip, const char *prompt,
const char *cache_nonce,
unsigned char **r_key, char **r_passphrase)
{
return convert_from_openpgp_main (ctrl, s_pgp, grip, prompt,
cache_nonce, NULL,
r_key, r_passphrase);
}
/* This function is called by agent_unprotect to re-protect an
openpgp-native protected private-key into the standard private-key
protection format. */
gpg_error_t
convert_from_openpgp_native (ctrl_t ctrl,
gcry_sexp_t s_pgp, const char *passphrase,
unsigned char **r_key)
{
gpg_error_t err;
unsigned char grip[20];
if (!passphrase)
return gpg_error (GPG_ERR_INTERNAL);
err = convert_from_openpgp_main (ctrl, s_pgp, grip, NULL,
NULL, passphrase,
r_key, NULL);
/* On success try to re-write the key. */
if (!err)
{
unsigned char *protectedkey = NULL;
size_t protectedkeylen;
if (!agent_protect (*r_key, passphrase, &protectedkey, &protectedkeylen,
ctrl->s2k_count))
agent_write_private_key (grip, protectedkey, protectedkeylen, 1);
xfree (protectedkey);
}
return err;
}
static gpg_error_t
key_from_sexp (gcry_sexp_t sexp, const char *elems, gcry_mpi_t *array)

View File

@ -23,6 +23,10 @@ gpg_error_t convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp,
unsigned char *grip, const char *prompt,
const char *cache_nonce,
unsigned char **r_key, char **r_passphrase);
gpg_error_t convert_from_openpgp_native (ctrl_t ctrl,
gcry_sexp_t s_pgp,
const char *passphrase,
unsigned char **r_key);
gpg_error_t convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key,
const char *passphrase,

View File

@ -66,6 +66,9 @@ agent_write_private_key (const unsigned char *grip,
fname = make_filename (opt.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 && !access (fname, F_OK))
{
log_error ("secret key file '%s' already exists\n", fname);
@ -119,7 +122,7 @@ try_unprotect_cb (struct pin_entry_info_s *pi)
assert (!arg->unprotected_key);
arg->change_required = 0;
err = agent_unprotect (arg->protected_key, pi->pin, protected_at,
err = agent_unprotect (arg->ctrl, arg->protected_key, pi->pin, protected_at,
&arg->unprotected_key, &dummy);
if (err)
return err;
@ -325,7 +328,7 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
pw = agent_get_cache (cache_nonce, CACHE_MODE_NONCE);
if (pw)
{
rc = agent_unprotect (*keybuf, pw, NULL, &result, &resultlen);
rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
if (!rc)
{
if (r_passphrase)
@ -350,7 +353,7 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
pw = agent_get_cache (hexgrip, cache_mode);
if (pw)
{
rc = agent_unprotect (*keybuf, pw, NULL, &result, &resultlen);
rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
if (!rc)
{
if (r_passphrase)

View File

@ -84,56 +84,94 @@ encrypted_octet_string. The result of the decryption process is a
list of the secret key parameters. The protected-at expression is
optional; the isotimestamp is 15 bytes long (e.g. "19610711T172000").
The only available protection mode for now is
The currently defined protection modes are:
openpgp-s2k3-sha1-aes-cbc
1. openpgp-s2k3-sha1-aes-cbc
which describes an algorithm using using AES in CBC mode for
encryption, SHA-1 for integrity protection and the String to Key
algorithm 3 from OpenPGP (rfc2440).
This describes an 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:
Example:
(protected openpgp-s2k3-sha1-aes-cbc
((sha1 16byte_salt no_of_iterations) 16byte_iv)
encrypted_octet_string
)
(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:
The encrypted_octet string should yield this S-Exp (in canonical
representation) after decryption:
(
(
(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]...#)
)
(
(
(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.
For padding reasons, random bytes are appended to this list - they can
easily be stripped by looking for the end of the list.
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, the algorithm keyword and (if used) the protected-at
list.
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, the algorithm keyword and (if used) the protected-at
list.
(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#)
(protected-at "18950523T000000")
)
(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#)
(protected-at "18950523T000000")
)
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.
2. openpgp-native
This is a wrapper around the OpenPGP Private Key Transport format
which resembles the standard OpenPGP format and allows the use of an
existing key without re-encrypting to the default protection format.
Example:
(protected openpgp-native
(openpgp-private-key
(version V)
(algo PUBKEYALGO)
(skey _ P1 _ P2 _ P3 ... e PN)
(csum n)
(protection PROTTYPE PROTALGO IV S2KMODE S2KHASH S2KSALT S2KCOUNT)))
Note that the public key paramaters in SKEY are duplicated and
should be identical to their copies in the standard parameter
elements. Here is an example of an entire protected private key
using this format:
(protected-private-key
(rsa
(n #00e0ce9..[some bytes not shown]..51#)
(e #010001#)
(protected openpgp-native
(openpgp-private-key
(version 4)
(algo rsa)
(skey _ #00e0ce9..[some bytes not shown]..51#
_ #010001#
e #.........................#)
(protection sha1 aes #aabbccddeeff00112233445566778899#
3 sha1 #2596f93e85f41e53# 3:190))))
(uri http://foo.bar x-foo:whatever_you_want)
(comment whatever))
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.
Shadowed Private Key Format
@ -184,7 +222,7 @@ This format is used to transfer keys between gpg and gpg-agent.
the secrect key parameters are encrypted if the "protection" list is
given. To make this more explicit each parameter is preceded by a
flag "_" for cleartext or "e" for encrypted text.
* CSUM is the depreciated 16 bit checksum as defined by OpenPGP. This
* CSUM is the deprecated 16 bit checksum as defined by OpenPGP. This
is an optional element.
* If PROTTYPE is "sha1" the new style SHA1 checksum is used if it is "sum"
the old 16 bit checksum (above) is used and if it is "none" no

View File

@ -372,7 +372,7 @@ read_and_unprotect (const char *fname)
if (!key)
return;
rc = agent_unprotect (key, (pw=get_passphrase (1)),
rc = agent_unprotect (NULL, key, (pw=get_passphrase (1)),
protected_at, &result, &resultlen);
release_passphrase (pw);
xfree (key);
@ -728,3 +728,15 @@ release_passphrase (char *pw)
xfree (pw);
}
}
/* Stub function. */
gpg_error_t
convert_from_openpgp_native (gcry_sexp_t s_pgp, const char *passphrase,
unsigned char **r_key)
{
(void)s_pgp;
(void)passphrase;
(void)r_key;
return gpg_error (GPG_ERR_BUG);
}

View File

@ -1,6 +1,7 @@
/* protect.c - Un/Protect a secret key
* Copyright (C) 1998, 1999, 2000, 2001, 2002,
* 2003, 2007, 2009, 2011 Free Software Foundation, Inc.
* Copyright (C) 2013 Werner Koch
*
* This file is part of GnuPG.
*
@ -604,6 +605,7 @@ agent_protect (const unsigned char *plainkey, const char *passphrase,
return 0;
}
/* Do the actual decryption and check the return list for consistency. */
static int
@ -832,9 +834,10 @@ merge_lists (const unsigned char *protectedkey,
/* Unprotect the key encoded in canonical format. We assume a valid
S-Exp here. If a protected-at item is available, its value will
be stored at protocted_at unless this is NULL. */
be stored at protected_at unless this is NULL. */
int
agent_unprotect (const unsigned char *protectedkey, const char *passphrase,
agent_unprotect (ctrl_t ctrl,
const unsigned char *protectedkey, const char *passphrase,
gnupg_isotime_t protected_at,
unsigned char **result, size_t *resultlen)
{
@ -938,7 +941,30 @@ agent_unprotect (const unsigned char *protectedkey, const char *passphrase,
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc"))
return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION);
{
if (smatch (&s, n, "openpgp-native"))
{
gcry_sexp_t s_prot_begin;
rc = gcry_sexp_sscan (&s_prot_begin, NULL,
prot_begin,
gcry_sexp_canon_len (prot_begin, 0,NULL,NULL));
if (rc)
return rc;
rc = convert_from_openpgp_native (ctrl,
s_prot_begin, passphrase, &final);
gcry_sexp_release (s_prot_begin);
if (!rc)
{
*result = final;
*resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL);
}
return rc;
}
else
return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION);
}
if (*s != '(' || s[1] != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s += 2;

View File

@ -337,3 +337,14 @@ main (int argc, char **argv)
return 0;
}
/* Stub function. */
gpg_error_t
convert_from_openpgp_native (gcry_sexp_t s_pgp, const char *passphrase,
unsigned char **r_key)
{
(void)s_pgp;
(void)passphrase;
(void)r_key;
return gpg_error (GPG_ERR_BUG);
}

View File

@ -1998,7 +1998,7 @@ inq_import_key_parms (void *opaque, const char *line)
/* Call the agent to import a key into the agent. */
gpg_error_t
agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr,
const void *key, size_t keylen)
const void *key, size_t keylen, int unattended)
{
gpg_error_t err;
struct import_key_parm_s parm;
@ -2028,7 +2028,8 @@ agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr,
parm.key = key;
parm.keylen = keylen;
snprintf (line, sizeof line, "IMPORT_KEY%s%s",
snprintf (line, sizeof line, "IMPORT_KEY%s%s%s",
unattended? " --unattended":"",
cache_nonce_addr && *cache_nonce_addr? " ":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"");
cn_parm.cache_nonce_addr = cache_nonce_addr;

View File

@ -177,7 +177,7 @@ gpg_error_t agent_keywrap_key (ctrl_t ctrl, int forexport,
/* Send a key to the agent. */
gpg_error_t agent_import_key (ctrl_t ctrl, const char *desc,
char **cache_nonce_addr,
const void *key, size_t keylen);
const void *key, size_t keylen, int unattended);
/* Receive a key from the agent. */
gpg_error_t agent_export_key (ctrl_t ctrl, const char *keygrip,

View File

@ -1432,7 +1432,7 @@ transfer_secret_keys (ctrl_t ctrl, struct stats_s *stats, kbnode_t sec_keyblock)
{
char *desc = gpg_format_keydesc (pk, 1, 1);
err = agent_import_key (ctrl, desc, &cache_nonce,
wrappedkey, wrappedkeylen);
wrappedkey, wrappedkeylen, opt.batch);
xfree (desc);
}
if (!err)