mirror of
git://git.gnupg.org/gnupg.git
synced 2025-01-03 12:11:33 +01:00
agent: Implement new protection mode openpgp-s2k3-ocb-aes.
* agent/protect.c (agent_protect): Add arg use_ocb. Change all caller to pass -1 for default. * agent/protect-tool.c: New option --debug-use-ocb. (oDebugUseOCB): New. (opt_debug_use_ocb): New. (main): Set option. (read_and_protect): Implement option. * agent/protect.c (OCB_MODE_SUPPORTED): New macro. (PROT_DEFAULT_TO_OCB): New macro. (do_encryption): Add args use_ocb, hashbegin, hashlen, timestamp_exp, and timestamp_exp_len. Implement OCB. (agent_protect): Change to support OCB. (do_decryption): Add new args is_ocb, aadhole_begin, and aadhole_len. Implement OCB. (merge_lists): Allow NULL for sha1hash. (agent_unprotect): Change to support OCB. (agent_private_key_type): Remove debug output. -- Instead of using the old OpenPGP way of appending a hash of the plaintext and encrypt that along with the plaintext, the new scheme uses a proper authenticated encryption mode. See keyformat.txt for a description. Libgcrypt 1.7 is required. This mode is not yet enabled because there would be no way to return to an older GnuPG version. To test the new scheme use gpg-protect-tool: ./gpg-protect-tool -av -P abc -p --debug-use-ocb <plain.key >prot.key ./gpg-protect-tool -av -P abc -u <prot.key Any key from the private key storage should work. Signed-off-by: Werner Koch <wk@gnupg.org>
This commit is contained in:
parent
100b413d7f
commit
4159567f7e
@ -471,7 +471,7 @@ unsigned long get_standard_s2k_count (void);
|
||||
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);
|
||||
unsigned long s2k_count, int use_ocb);
|
||||
int agent_unprotect (ctrl_t ctrl,
|
||||
const unsigned char *protectedkey, const char *passphrase,
|
||||
gnupg_isotime_t protected_at,
|
||||
|
@ -3125,7 +3125,7 @@ ssh_key_to_protected_buffer (gcry_sexp_t key, const char *passphrase,
|
||||
gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, buffer_new, buffer_new_n);
|
||||
/* FIXME: guarantee? */
|
||||
|
||||
err = agent_protect (buffer_new, passphrase, buffer, buffer_n, 0);
|
||||
err = agent_protect (buffer_new, passphrase, buffer, buffer_n, 0, -1);
|
||||
|
||||
out:
|
||||
|
||||
|
@ -2140,7 +2140,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, -1);
|
||||
if (!err)
|
||||
err = agent_write_private_key (grip, finalkey, finalkeylen, force);
|
||||
}
|
||||
|
@ -1066,7 +1066,7 @@ convert_from_openpgp_native (ctrl_t ctrl,
|
||||
|
||||
if (!agent_protect (*r_key, passphrase,
|
||||
&protectedkey, &protectedkeylen,
|
||||
ctrl->s2k_count))
|
||||
ctrl->s2k_count, -1))
|
||||
agent_write_private_key (grip, protectedkey, protectedkeylen, 1);
|
||||
xfree (protectedkey);
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ store_key (gcry_sexp_t private, const char *passphrase, int force,
|
||||
{
|
||||
unsigned char *p;
|
||||
|
||||
rc = agent_protect (buf, passphrase, &p, &len, s2k_count);
|
||||
rc = agent_protect (buf, passphrase, &p, &len, s2k_count, -1);
|
||||
if (rc)
|
||||
{
|
||||
xfree (buf);
|
||||
|
@ -43,23 +43,6 @@ optional but required for some operations to calculate the fingerprint
|
||||
of the key. This timestamp should be a string with the number of
|
||||
seconds since Epoch or an ISO time string (yyyymmddThhmmss).
|
||||
|
||||
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. The regular way to represent the
|
||||
keys is in canonical representation[3]:
|
||||
|
||||
(private-key
|
||||
(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#)
|
||||
)
|
||||
(uri http://foo.bar x-foo:whatever_you_want)
|
||||
)
|
||||
|
||||
|
||||
Protected Private Key Format
|
||||
==============================
|
||||
@ -90,7 +73,7 @@ The currently defined protection modes are:
|
||||
|
||||
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).
|
||||
algorithm 3 from OpenPGP (rfc4880).
|
||||
|
||||
Example:
|
||||
|
||||
@ -116,7 +99,7 @@ The currently defined protection modes are:
|
||||
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
|
||||
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.
|
||||
@ -135,7 +118,46 @@ The currently defined protection modes are:
|
||||
the stored one - If they don't match the integrity of the key is not
|
||||
given.
|
||||
|
||||
2. openpgp-native
|
||||
2. openpgp-s2k3-ocb-aes
|
||||
|
||||
This describes an algorithm using using AES-128 in OCB mode, a nonce
|
||||
of 96 bit, a taglen of 128 bit, and the String to Key algorithm 3
|
||||
from OpenPGP (rfc4880).
|
||||
|
||||
Example:
|
||||
|
||||
(protected openpgp-s2k3-ocb-aes
|
||||
((sha1 16byte_salt no_of_iterations) 12byte_nonce)
|
||||
encrypted_octet_string
|
||||
)
|
||||
|
||||
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#)
|
||||
)
|
||||
)
|
||||
|
||||
For padding reasons, random bytes may be appended to this list -
|
||||
they can easily be stripped by looking for the end of the list.
|
||||
|
||||
The associated data required for this protection mode is the list
|
||||
formiing the public key parameters. For the above example this is
|
||||
is this canonical encoded S-expression:
|
||||
|
||||
(rsa
|
||||
(n #00e0ce9..[some bytes not shown]..51#)
|
||||
(e #010001#)
|
||||
(protected-at "18950523T000000")
|
||||
)
|
||||
|
||||
|
||||
3. 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
|
||||
|
@ -69,6 +69,7 @@ enum cmd_and_opt_values
|
||||
oHomedir,
|
||||
oPrompt,
|
||||
oStatusMsg,
|
||||
oDebugUseOCB,
|
||||
|
||||
oAgentProgram
|
||||
};
|
||||
@ -96,6 +97,7 @@ static const char *opt_passphrase;
|
||||
static char *opt_prompt;
|
||||
static int opt_status_msg;
|
||||
static const char *opt_agent_program;
|
||||
static int opt_debug_use_ocb;
|
||||
|
||||
static char *get_passphrase (int promptno);
|
||||
static void release_passphrase (char *pw);
|
||||
@ -132,6 +134,8 @@ static ARGPARSE_OPTS opts[] = {
|
||||
|
||||
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
|
||||
|
||||
ARGPARSE_s_n (oDebugUseOCB, "debug-use-ocb", "@"), /* For hacking only. */
|
||||
|
||||
ARGPARSE_end ()
|
||||
};
|
||||
|
||||
@ -333,7 +337,8 @@ read_and_protect (const char *fname)
|
||||
return;
|
||||
|
||||
pw = get_passphrase (1);
|
||||
rc = agent_protect (key, pw, &result, &resultlen, 0);
|
||||
rc = agent_protect (key, pw, &result, &resultlen, 0,
|
||||
opt_debug_use_ocb? 1 : -1);
|
||||
release_passphrase (pw);
|
||||
xfree (key);
|
||||
if (rc)
|
||||
@ -598,6 +603,7 @@ main (int argc, char **argv )
|
||||
case oHaveCert: opt_have_cert = 1; break;
|
||||
case oPrompt: opt_prompt = pargs.r.ret_str; break;
|
||||
case oStatusMsg: opt_status_msg = 1; break;
|
||||
case oDebugUseOCB: opt_debug_use_ocb = 1; break;
|
||||
|
||||
default: pargs.err = ARGPARSE_PRINT_ERROR; break;
|
||||
}
|
||||
|
432
agent/protect.c
432
agent/protect.c
@ -41,6 +41,18 @@
|
||||
#include "cvt-openpgp.h"
|
||||
#include "sexp-parse.h"
|
||||
|
||||
|
||||
#if GCRYPT_VERSION_NUMBER < 0x010700
|
||||
# define OCB_MODE_SUPPORTED 0
|
||||
#else
|
||||
# define OCB_MODE_SUPPORTED 1
|
||||
#endif
|
||||
|
||||
/* To use the openpgp-s2k3-ocb-aes scheme by default set the value of
|
||||
* this macro to 1. Note that the caller of agent_protect may
|
||||
* override this default. */
|
||||
#define PROT_DEFAULT_TO_OCB 0
|
||||
|
||||
/* The protection mode for encryption. The supported modes for
|
||||
decryption are listed in agent_unprotect(). */
|
||||
#define PROT_CIPHER GCRY_CIPHER_AES128
|
||||
@ -87,6 +99,7 @@ hash_passphrase (const char *passphrase, int hashalgo,
|
||||
unsigned char *key, size_t keylen);
|
||||
|
||||
|
||||
|
||||
|
||||
/* Get the process time and store it in DATA. */
|
||||
static void
|
||||
@ -302,70 +315,108 @@ calculate_mic (const unsigned char *plainkey, unsigned char *sha1hash)
|
||||
encrypted block in RESULT or return 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):
|
||||
The parameter block is expected to be an incomplete canonical
|
||||
encoded 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:
|
||||
the returned block is the S-Expression:
|
||||
|
||||
(protected mode (parms) encrypted_octet_string)
|
||||
(protected mode (parms) encrypted_octet_string)
|
||||
|
||||
*/
|
||||
static int
|
||||
do_encryption (const unsigned char *protbegin, size_t protlen,
|
||||
const char *passphrase, const unsigned char *sha1hash,
|
||||
do_encryption (const unsigned char *hashbegin, size_t hashlen,
|
||||
const unsigned char *protbegin, size_t protlen,
|
||||
const char *passphrase,
|
||||
const char *timestamp_exp, size_t timestamp_exp_len,
|
||||
unsigned char **result, size_t *resultlen,
|
||||
unsigned long s2k_count)
|
||||
unsigned long s2k_count, int use_ocb)
|
||||
{
|
||||
gcry_cipher_hd_t hd;
|
||||
const char *modestr = "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc";
|
||||
const char *modestr;
|
||||
unsigned char hashvalue[20];
|
||||
int blklen, enclen, outlen;
|
||||
unsigned char *iv = NULL;
|
||||
unsigned int ivsize; /* Size of the buffer allocated for IV. */
|
||||
const unsigned char *s2ksalt; /* Points into IV. */
|
||||
int rc;
|
||||
char *outbuf = NULL;
|
||||
char *p;
|
||||
int saltpos, ivpos, encpos;
|
||||
|
||||
s2ksalt = iv; /* Silence compiler warning. */
|
||||
|
||||
*resultlen = 0;
|
||||
*result = NULL;
|
||||
|
||||
rc = gcry_cipher_open (&hd, PROT_CIPHER, GCRY_CIPHER_MODE_CBC,
|
||||
if (use_ocb && !OCB_MODE_SUPPORTED)
|
||||
return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION);
|
||||
|
||||
modestr = (use_ocb? "openpgp-s2k3-ocb-aes"
|
||||
/* */: "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc");
|
||||
|
||||
rc = gcry_cipher_open (&hd, PROT_CIPHER,
|
||||
#if OCB_MODE_SUPPORTED
|
||||
use_ocb? GCRY_CIPHER_MODE_OCB :
|
||||
#endif
|
||||
GCRY_CIPHER_MODE_CBC,
|
||||
GCRY_CIPHER_SECURE);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
|
||||
/* 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. */
|
||||
* easier to add the trailer and the padding and more important we
|
||||
* have to prefix the text with 2 parenthesis. In CBC mode 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. In OCB mode we
|
||||
* have to allocate enough space for just:
|
||||
*
|
||||
* ((<parameter_list>))
|
||||
*/
|
||||
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 (use_ocb)
|
||||
{
|
||||
/* (( )) */
|
||||
outlen = 2 + protlen + 2 ;
|
||||
enclen = outlen + 16 /* taglen */;
|
||||
outbuf = gcry_malloc_secure (enclen);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* (( )( 4:hash 4:sha1 20:<hash> )) <padding> */
|
||||
outlen = 2 + protlen + 2 + 6 + 6 + 23 + 2 + blklen;
|
||||
enclen = outlen/blklen * blklen;
|
||||
outbuf = gcry_malloc_secure (outlen);
|
||||
}
|
||||
if (!outbuf)
|
||||
rc = out_of_core ();
|
||||
|
||||
/* Allocate a buffer for the nonce and the salt. */
|
||||
if (!rc)
|
||||
{
|
||||
/* Allocate random bytes to be used as IV, padding and s2k salt. */
|
||||
iv = xtrymalloc (blklen*2+8);
|
||||
/* Allocate random bytes to be used as IV, padding and s2k salt
|
||||
* or in OCB mode for a nonce and the s2k salt. The IV/nonce is
|
||||
* set later because for OCB we need to set the key first. */
|
||||
ivsize = (use_ocb? 12 : (blklen*2)) + 8;
|
||||
iv = xtrymalloc (ivsize);
|
||||
if (!iv)
|
||||
rc = gpg_error (GPG_ERR_ENOMEM);
|
||||
rc = gpg_error_from_syserror ();
|
||||
else
|
||||
{
|
||||
gcry_create_nonce (iv, blklen*2+8);
|
||||
rc = gcry_cipher_setiv (hd, iv, blklen);
|
||||
gcry_create_nonce (iv, ivsize);
|
||||
s2ksalt = iv + ivsize - 8;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hash the passphrase and set the key. */
|
||||
if (!rc)
|
||||
{
|
||||
unsigned char *key;
|
||||
@ -377,14 +428,52 @@ do_encryption (const unsigned char *protbegin, size_t protlen,
|
||||
else
|
||||
{
|
||||
rc = hash_passphrase (passphrase, GCRY_MD_SHA1,
|
||||
3, iv+2*blklen,
|
||||
s2k_count ? s2k_count : get_standard_s2k_count(),
|
||||
3, s2ksalt,
|
||||
s2k_count? s2k_count:get_standard_s2k_count(),
|
||||
key, keylen);
|
||||
if (!rc)
|
||||
rc = gcry_cipher_setkey (hd, key, keylen);
|
||||
xfree (key);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the IV/nonce. */
|
||||
if (!rc)
|
||||
{
|
||||
rc = gcry_cipher_setiv (hd, iv, use_ocb? 12 : blklen);
|
||||
}
|
||||
|
||||
if (use_ocb)
|
||||
{
|
||||
/* In OCB Mode we use only the public key parameters as AAD. */
|
||||
rc = gcry_cipher_authenticate (hd, hashbegin, protbegin - hashbegin);
|
||||
if (!rc)
|
||||
rc = gcry_cipher_authenticate (hd, timestamp_exp, timestamp_exp_len);
|
||||
if (!rc)
|
||||
rc = gcry_cipher_authenticate
|
||||
(hd, protbegin+protlen, hashlen - (protbegin+protlen - hashbegin));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Hash the entire expression for CBC mode. Because
|
||||
* TIMESTAMP_EXP won't get protected, we can't simply hash a
|
||||
* continuous buffer but need to call md_write several times. */
|
||||
gcry_md_hd_t md;
|
||||
|
||||
rc = gcry_md_open (&md, GCRY_MD_SHA1, 0 );
|
||||
if (!rc)
|
||||
{
|
||||
gcry_md_write (md, hashbegin, hashlen);
|
||||
gcry_md_write (md, timestamp_exp, timestamp_exp_len);
|
||||
gcry_md_write (md, ")", 1);
|
||||
memcpy (hashvalue, gcry_md_read (md, GCRY_MD_SHA1), 20);
|
||||
gcry_md_close (md);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Encrypt. */
|
||||
if (!rc)
|
||||
{
|
||||
p = outbuf;
|
||||
@ -392,17 +481,42 @@ do_encryption (const unsigned char *protbegin, size_t protlen,
|
||||
*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;
|
||||
if (use_ocb)
|
||||
{
|
||||
*p++ = ')';
|
||||
*p++ = ')';
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy (p, ")(4:hash4:sha120:", 17);
|
||||
p += 17;
|
||||
memcpy (p, hashvalue, 20);
|
||||
p += 20;
|
||||
*p++ = ')';
|
||||
*p++ = ')';
|
||||
memcpy (p, iv+blklen, blklen); /* Add padding. */
|
||||
p += blklen;
|
||||
}
|
||||
assert ( p - outbuf == outlen);
|
||||
rc = gcry_cipher_encrypt (hd, outbuf, enclen, NULL, 0);
|
||||
#if OCB_MODE_SUPPORTED
|
||||
if (use_ocb)
|
||||
{
|
||||
gcry_cipher_final (hd);
|
||||
rc = gcry_cipher_encrypt (hd, outbuf, outlen, NULL, 0);
|
||||
if (!rc)
|
||||
{
|
||||
log_assert (outlen + 16 == enclen);
|
||||
rc = gcry_cipher_gettag (hd, outbuf + outlen, 16);
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif /*OCB_MODE_SUPPORTED*/
|
||||
{
|
||||
rc = gcry_cipher_encrypt (hd, outbuf, enclen, NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Release cipher handle and check for errors. */
|
||||
gcry_cipher_close (hd);
|
||||
if (rc)
|
||||
{
|
||||
@ -429,7 +543,7 @@ do_encryption (const unsigned char *protbegin, size_t protlen,
|
||||
(int)strlen (modestr), modestr,
|
||||
&saltpos,
|
||||
(unsigned int)strlen (countbuf), countbuf,
|
||||
blklen, &ivpos, blklen, "",
|
||||
use_ocb? 12 : blklen, &ivpos, use_ocb? 12 : blklen, "",
|
||||
enclen, &encpos, enclen, "");
|
||||
if (!p)
|
||||
{
|
||||
@ -438,11 +552,12 @@ do_encryption (const unsigned char *protbegin, size_t protlen,
|
||||
xfree (outbuf);
|
||||
return tmperr;
|
||||
}
|
||||
|
||||
}
|
||||
*resultlen = strlen (p);
|
||||
*result = (unsigned char*)p;
|
||||
memcpy (p+saltpos, iv+2*blklen, 8);
|
||||
memcpy (p+ivpos, iv, blklen);
|
||||
memcpy (p+saltpos, s2ksalt, 8);
|
||||
memcpy (p+ivpos, iv, use_ocb? 12 : blklen);
|
||||
memcpy (p+encpos, outbuf, enclen);
|
||||
xfree (iv);
|
||||
xfree (outbuf);
|
||||
@ -452,11 +567,13 @@ do_encryption (const unsigned char *protbegin, size_t protlen,
|
||||
|
||||
|
||||
/* Protect the key encoded in canonical format in PLAINKEY. We assume
|
||||
a valid S-Exp here. */
|
||||
a valid S-Exp here. With USE_UCB set to -1 the default scheme is
|
||||
used (ie. either CBC or OCB), set to 0 the old CBC mode is used,
|
||||
and set to 1 OCB is used. */
|
||||
int
|
||||
agent_protect (const unsigned char *plainkey, const char *passphrase,
|
||||
unsigned char **result, size_t *resultlen,
|
||||
unsigned long s2k_count)
|
||||
unsigned long s2k_count, int use_ocb)
|
||||
{
|
||||
int rc;
|
||||
const char *parmlist;
|
||||
@ -466,15 +583,16 @@ agent_protect (const unsigned char *plainkey, const char *passphrase,
|
||||
const unsigned char *prot_begin, *prot_end, *real_end;
|
||||
size_t n;
|
||||
int c, infidx, i;
|
||||
unsigned char hashvalue[20];
|
||||
char timestamp_exp[35];
|
||||
unsigned char *protected;
|
||||
size_t protectedlen;
|
||||
int depth = 0;
|
||||
unsigned char *p;
|
||||
gcry_md_hd_t md;
|
||||
int have_curve = 0;
|
||||
|
||||
if (use_ocb == -1)
|
||||
use_ocb = PROT_DEFAULT_TO_OCB;
|
||||
|
||||
/* Create an S-expression with the protected-at timestamp. */
|
||||
memcpy (timestamp_exp, "(12:protected-at15:", 19);
|
||||
gnupg_get_isotime (timestamp_exp+19);
|
||||
@ -575,21 +693,10 @@ agent_protect (const unsigned char *plainkey, const char *passphrase,
|
||||
assert (!depth);
|
||||
real_end = s-1;
|
||||
|
||||
/* Hash the stuff. Because the timestamp_exp won't get protected,
|
||||
we can't simply hash a continuous buffer but need to use several
|
||||
md_writes. */
|
||||
rc = gcry_md_open (&md, GCRY_MD_SHA1, 0 );
|
||||
if (rc)
|
||||
return rc;
|
||||
gcry_md_write (md, hash_begin, hash_end - hash_begin);
|
||||
gcry_md_write (md, timestamp_exp, 35);
|
||||
gcry_md_write (md, ")", 1);
|
||||
memcpy (hashvalue, gcry_md_read (md, GCRY_MD_SHA1), 20);
|
||||
gcry_md_close (md);
|
||||
|
||||
rc = do_encryption (prot_begin, prot_end - prot_begin + 1,
|
||||
passphrase, hashvalue,
|
||||
&protected, &protectedlen, s2k_count);
|
||||
rc = do_encryption (hash_begin, hash_end - hash_begin + 1,
|
||||
prot_begin, prot_end - prot_begin + 1,
|
||||
passphrase, timestamp_exp, sizeof (timestamp_exp),
|
||||
&protected, &protectedlen, s2k_count, use_ocb);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
@ -631,11 +738,13 @@ agent_protect (const unsigned char *plainkey, const char *passphrase,
|
||||
|
||||
/* Do the actual decryption and check the return list for consistency. */
|
||||
static int
|
||||
do_decryption (const unsigned char *protected, size_t protectedlen,
|
||||
do_decryption (const unsigned char *aad_begin, size_t aad_len,
|
||||
const unsigned char *aadhole_begin, size_t aadhole_len,
|
||||
const unsigned char *protected, size_t protectedlen,
|
||||
const char *passphrase,
|
||||
const unsigned char *s2ksalt, unsigned long s2kcount,
|
||||
const unsigned char *iv, size_t ivlen,
|
||||
int prot_cipher, int prot_cipher_keylen,
|
||||
int prot_cipher, int prot_cipher_keylen, int is_ocb,
|
||||
unsigned char **result)
|
||||
{
|
||||
int rc = 0;
|
||||
@ -644,11 +753,29 @@ do_decryption (const unsigned char *protected, size_t protectedlen,
|
||||
unsigned char *outbuf;
|
||||
size_t reallen;
|
||||
|
||||
blklen = gcry_cipher_get_algo_blklen (prot_cipher);
|
||||
if (protectedlen < 4 || (protectedlen%blklen))
|
||||
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
|
||||
if (is_ocb && !OCB_MODE_SUPPORTED)
|
||||
return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION);
|
||||
|
||||
rc = gcry_cipher_open (&hd, prot_cipher, GCRY_CIPHER_MODE_CBC,
|
||||
blklen = gcry_cipher_get_algo_blklen (prot_cipher);
|
||||
if (is_ocb)
|
||||
{
|
||||
/* OCB does not require a multiple of the block length but we
|
||||
* check that it is long enough for the 128 bit tag and that we
|
||||
* have the 96 bit nonce. */
|
||||
if (protectedlen < (4 + 16) || ivlen != 12)
|
||||
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (protectedlen < 4 || (protectedlen%blklen))
|
||||
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
|
||||
}
|
||||
|
||||
rc = gcry_cipher_open (&hd, prot_cipher,
|
||||
#if OCB_MODE_SUPPORTED
|
||||
is_ocb? GCRY_CIPHER_MODE_OCB :
|
||||
#endif
|
||||
GCRY_CIPHER_MODE_CBC,
|
||||
GCRY_CIPHER_SECURE);
|
||||
if (rc)
|
||||
return rc;
|
||||
@ -656,8 +783,8 @@ do_decryption (const unsigned char *protected, size_t protectedlen,
|
||||
outbuf = gcry_malloc_secure (protectedlen);
|
||||
if (!outbuf)
|
||||
rc = out_of_core ();
|
||||
if (!rc)
|
||||
rc = gcry_cipher_setiv (hd, iv, ivlen);
|
||||
|
||||
/* Hash the passphrase and set the key. */
|
||||
if (!rc)
|
||||
{
|
||||
unsigned char *key;
|
||||
@ -674,21 +801,60 @@ do_decryption (const unsigned char *protected, size_t protectedlen,
|
||||
xfree (key);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the IV/nonce. */
|
||||
if (!rc)
|
||||
rc = gcry_cipher_decrypt (hd, outbuf, protectedlen,
|
||||
protected, protectedlen);
|
||||
{
|
||||
rc = gcry_cipher_setiv (hd, iv, ivlen);
|
||||
}
|
||||
|
||||
/* Decrypt. */
|
||||
if (!rc)
|
||||
{
|
||||
#if OCB_MODE_SUPPORTED
|
||||
if (is_ocb)
|
||||
{
|
||||
rc = gcry_cipher_authenticate (hd, aad_begin,
|
||||
aadhole_begin - aad_begin);
|
||||
if (!rc)
|
||||
rc = gcry_cipher_authenticate
|
||||
(hd, aadhole_begin + aadhole_len,
|
||||
aad_len - (aadhole_begin+aadhole_len - aad_begin));
|
||||
|
||||
if (!rc)
|
||||
{
|
||||
gcry_cipher_final (hd);
|
||||
rc = gcry_cipher_decrypt (hd, outbuf, protectedlen - 16,
|
||||
protected, protectedlen - 16);
|
||||
}
|
||||
if (!rc)
|
||||
rc = gcry_cipher_checktag (hd, protected + protectedlen - 16, 16);
|
||||
}
|
||||
else
|
||||
#endif /*OCB_MODE_SUPPORTED*/
|
||||
{
|
||||
rc = gcry_cipher_decrypt (hd, outbuf, protectedlen,
|
||||
protected, protectedlen);
|
||||
}
|
||||
}
|
||||
|
||||
/* Release cipher handle and check for errors. */
|
||||
gcry_cipher_close (hd);
|
||||
if (rc)
|
||||
{
|
||||
xfree (outbuf);
|
||||
return rc;
|
||||
}
|
||||
/* Do a quick check first. */
|
||||
|
||||
/* Do a quick check on the data structure. */
|
||||
if (*outbuf != '(' && outbuf[1] != '(')
|
||||
{
|
||||
/* Note that in OCB mode this is actually invalid _encrypted_
|
||||
* data and not a bad passphrase. */
|
||||
xfree (outbuf);
|
||||
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
|
||||
}
|
||||
|
||||
/* Check that we have a consistent S-Exp. */
|
||||
reallen = gcry_sexp_canon_len (outbuf, protectedlen, NULL, NULL);
|
||||
if (!reallen || (reallen + blklen < protectedlen) )
|
||||
@ -702,11 +868,12 @@ do_decryption (const unsigned char *protected, size_t protectedlen,
|
||||
|
||||
|
||||
/* 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. CUTOFF and CUTLEN will receive the offset and the
|
||||
length of the resulting list which should go into the MIC
|
||||
calculation but then be removed. */
|
||||
* 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; if SHA1HASH is NULL no MIC will be computed.
|
||||
* CUTOFF and CUTLEN will receive the offset and the length of the
|
||||
* resulting list which should go into the MIC calculation but then be
|
||||
* removed. */
|
||||
static int
|
||||
merge_lists (const unsigned char *protectedkey,
|
||||
size_t replacepos,
|
||||
@ -730,7 +897,7 @@ merge_lists (const unsigned char *protectedkey,
|
||||
return gpg_error (GPG_ERR_BUG);
|
||||
|
||||
/* Estimate the required size of the resulting list. We have a large
|
||||
safety margin of >20 bytes (MIC hash from CLEARTEXT and the
|
||||
safety margin of >20 bytes (FIXME: MIC hash from CLEARTEXT and the
|
||||
removed "protected-" */
|
||||
newlistlen = gcry_sexp_canon_len (protectedkey, 0, NULL, NULL);
|
||||
if (!newlistlen)
|
||||
@ -749,7 +916,7 @@ merge_lists (const unsigned char *protectedkey,
|
||||
memcpy (p, protectedkey+15+10, replacepos-15-10);
|
||||
p += replacepos-15-10;
|
||||
|
||||
/* copy the cleartext */
|
||||
/* Copy the cleartext. */
|
||||
s = cleartext;
|
||||
if (*s != '(' && s[1] != '(')
|
||||
return gpg_error (GPG_ERR_BUG); /*we already checked this */
|
||||
@ -774,26 +941,29 @@ merge_lists (const unsigned char *protectedkey,
|
||||
goto invalid_sexp;
|
||||
endpos = s;
|
||||
s++;
|
||||
/* 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 */
|
||||
/* Intermezzo: Get the MIC if requested. */
|
||||
if (sha1hash)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/* Append the parameter list. */
|
||||
memcpy (p, startpos, endpos - startpos);
|
||||
p += endpos - startpos;
|
||||
|
||||
@ -867,9 +1037,11 @@ agent_unprotect (ctrl_t ctrl,
|
||||
const char *name; /* Name of the protection method. */
|
||||
int algo; /* (A zero indicates the "openpgp-native" hack.) */
|
||||
int keylen; /* Used key length in bytes. */
|
||||
unsigned int is_ocb:1;
|
||||
} algotable[] = {
|
||||
{ "openpgp-s2k3-sha1-aes-cbc", GCRY_CIPHER_AES128, (128/8)},
|
||||
{ "openpgp-s2k3-sha1-aes256-cbc", GCRY_CIPHER_AES256, (256/8)},
|
||||
{ "openpgp-s2k3-ocb-aes", GCRY_CIPHER_AES128, (128/8), 1},
|
||||
{ "openpgp-native", 0, 0 }
|
||||
};
|
||||
int rc;
|
||||
@ -882,6 +1054,8 @@ agent_unprotect (ctrl_t ctrl,
|
||||
unsigned long s2kcount;
|
||||
const unsigned char *iv;
|
||||
int prot_cipher, prot_cipher_keylen;
|
||||
int is_ocb;
|
||||
const unsigned char *aad_begin, *aad_end, *aadhole_begin, *aadhole_end;
|
||||
const unsigned char *prot_begin;
|
||||
unsigned char *cleartext;
|
||||
unsigned char *final;
|
||||
@ -902,6 +1076,15 @@ agent_unprotect (ctrl_t ctrl,
|
||||
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
|
||||
if (*s != '(')
|
||||
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
|
||||
{
|
||||
aad_begin = aad_end = s;
|
||||
aad_end++;
|
||||
i = 1;
|
||||
rc = sskip (&aad_end, &i);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
|
||||
s++;
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
@ -913,7 +1096,6 @@ agent_unprotect (ctrl_t ctrl,
|
||||
if (!protect_info[infidx].algo)
|
||||
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
|
||||
|
||||
|
||||
/* See wether we have a protected-at timestamp. */
|
||||
protect_list = s; /* Save for later. */
|
||||
if (protected_at)
|
||||
@ -969,6 +1151,14 @@ agent_unprotect (ctrl_t ctrl,
|
||||
return rc;
|
||||
}
|
||||
/* found */
|
||||
{
|
||||
aadhole_begin = aadhole_end = prot_begin;
|
||||
aadhole_end++;
|
||||
i = 1;
|
||||
rc = sskip (&aadhole_end, &i);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
return gpg_error (GPG_ERR_INV_SEXP);
|
||||
@ -976,14 +1166,17 @@ agent_unprotect (ctrl_t ctrl,
|
||||
/* Lookup the protection algo. */
|
||||
prot_cipher = 0; /* (avoid gcc warning) */
|
||||
prot_cipher_keylen = 0; /* (avoid gcc warning) */
|
||||
for (i= 0; i < DIM (algotable); i++)
|
||||
is_ocb = 0;
|
||||
for (i=0; i < DIM (algotable); i++)
|
||||
if (smatch (&s, n, algotable[i].name))
|
||||
{
|
||||
prot_cipher = algotable[i].algo;
|
||||
prot_cipher_keylen = algotable[i].keylen;
|
||||
is_ocb = algotable[i].is_ocb;
|
||||
break;
|
||||
}
|
||||
if (i == DIM (algotable))
|
||||
if (i == DIM (algotable)
|
||||
|| (is_ocb && !OCB_MODE_SUPPORTED))
|
||||
return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION);
|
||||
|
||||
if (!prot_cipher) /* This is "openpgp-native". */
|
||||
@ -1048,8 +1241,16 @@ agent_unprotect (ctrl_t ctrl,
|
||||
s++; /* skip list end */
|
||||
|
||||
n = snext (&s);
|
||||
if (n != 16) /* Wrong blocksize for IV (we support only 128 bit). */
|
||||
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
|
||||
if (is_ocb)
|
||||
{
|
||||
if (n != 12) /* Wrong size of the nonce. */
|
||||
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (n != 16) /* Wrong blocksize for IV (we support only 128 bit). */
|
||||
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
|
||||
}
|
||||
iv = s;
|
||||
s += n;
|
||||
if (*s != ')' )
|
||||
@ -1060,15 +1261,19 @@ agent_unprotect (ctrl_t ctrl,
|
||||
return gpg_error (GPG_ERR_INV_SEXP);
|
||||
|
||||
cleartext = NULL; /* Avoid cc warning. */
|
||||
rc = do_decryption (s, n,
|
||||
rc = do_decryption (aad_begin, aad_end - aad_begin,
|
||||
aadhole_begin, aadhole_end - aadhole_begin,
|
||||
s, n,
|
||||
passphrase, s2ksalt, s2kcount,
|
||||
iv, 16, prot_cipher, prot_cipher_keylen,
|
||||
iv, is_ocb? 12:16,
|
||||
prot_cipher, prot_cipher_keylen, is_ocb,
|
||||
&cleartext);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = merge_lists (protectedkey, prot_begin-protectedkey, cleartext,
|
||||
sha1hash, &final, &finallen, &cutoff, &cutlen);
|
||||
is_ocb? NULL : sha1hash,
|
||||
&final, &finallen, &cutoff, &cutlen);
|
||||
/* Albeit cleartext has been allocated in secure memory and thus
|
||||
xfree will wipe it out, we do an extra wipe just in case
|
||||
somethings goes badly wrong. */
|
||||
@ -1077,15 +1282,19 @@ agent_unprotect (ctrl_t ctrl,
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = calculate_mic (final, sha1hash2);
|
||||
if (!rc && memcmp (sha1hash, sha1hash2, 20))
|
||||
rc = gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
|
||||
if (rc)
|
||||
if (!is_ocb)
|
||||
{
|
||||
wipememory (final, finallen);
|
||||
xfree (final);
|
||||
return rc;
|
||||
rc = calculate_mic (final, sha1hash2);
|
||||
if (!rc && memcmp (sha1hash, sha1hash2, 20))
|
||||
rc = gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
|
||||
if (rc)
|
||||
{
|
||||
wipememory (final, finallen);
|
||||
xfree (final);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now remove the part which is included in the MIC but should not
|
||||
go into the final thing. */
|
||||
if (cutlen)
|
||||
@ -1184,7 +1393,6 @@ agent_private_key_type (const unsigned char *privatekey)
|
||||
n = snext (&s);
|
||||
if (!n)
|
||||
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
|
||||
log_debug ("openpgp-native protection '%.*s'\n", (int)n, s);
|
||||
if (smatch (&s, n, "none"))
|
||||
return PRIVATE_KEY_OPENPGP_NONE; /* Yes. */
|
||||
}
|
||||
|
@ -195,7 +195,7 @@ test_agent_protect (void)
|
||||
{
|
||||
ret = agent_protect ((const unsigned char*)specs[i].key,
|
||||
specs[i].passphrase,
|
||||
&specs[i].result, &specs[i].resultlen, 0);
|
||||
&specs[i].result, &specs[i].resultlen, 0, -1);
|
||||
if (gpg_err_code (ret) != specs[i].ret_expected)
|
||||
{
|
||||
printf ("agent_protect(%d) returned '%i/%s'; expected '%i/%s'\n",
|
||||
|
Loading…
x
Reference in New Issue
Block a user