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:
Werner Koch 2016-04-12 14:37:26 +02:00
parent 100b413d7f
commit 4159567f7e
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
9 changed files with 375 additions and 139 deletions

View File

@ -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,

View File

@ -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:

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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

View File

@ -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;
}

View File

@ -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. */
}

View File

@ -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",