[Pkg-gnupg-commit] [gnupg2] 130/205: agent: Implement new protection mode openpgp-s2k3-ocb-aes.
Daniel Kahn Gillmor
dkg at fifthhorseman.net
Wed May 11 08:38:28 UTC 2016
This is an automated email from the git hooks/post-receive script.
dkg pushed a commit to branch experimental
in repository gnupg2.
commit 4159567f7ed7a1139fdc3a6c92988e1648ad84ab
Author: Werner Koch <wk at gnupg.org>
Date: Tue Apr 12 14:37:26 2016 +0200
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 at gnupg.org>
---
agent/agent.h | 2 +-
agent/command-ssh.c | 2 +-
agent/command.c | 2 +-
agent/cvt-openpgp.c | 2 +-
agent/genkey.c | 2 +-
agent/keyformat.txt | 62 +++++---
agent/protect-tool.c | 8 +-
agent/protect.c | 430 ++++++++++++++++++++++++++++++++++++++-------------
agent/t-protect.c | 2 +-
9 files changed, 374 insertions(+), 138 deletions(-)
diff --git a/agent/agent.h b/agent/agent.h
index c2726bb..0dcb201 100644
--- a/agent/agent.h
+++ b/agent/agent.h
@@ -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,
diff --git a/agent/command-ssh.c b/agent/command-ssh.c
index 6e809f6..0e1d9fc 100644
--- a/agent/command-ssh.c
+++ b/agent/command-ssh.c
@@ -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:
diff --git a/agent/command.c b/agent/command.c
index dfe292d..c94fdd3 100644
--- a/agent/command.c
+++ b/agent/command.c
@@ -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);
}
diff --git a/agent/cvt-openpgp.c b/agent/cvt-openpgp.c
index 8df6b8e..40d9a3e 100644
--- a/agent/cvt-openpgp.c
+++ b/agent/cvt-openpgp.c
@@ -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);
}
diff --git a/agent/genkey.c b/agent/genkey.c
index 2eec974..12c3e34 100644
--- a/agent/genkey.c
+++ b/agent/genkey.c
@@ -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);
diff --git a/agent/keyformat.txt b/agent/keyformat.txt
index 150ea7c..e760412 100644
--- a/agent/keyformat.txt
+++ b/agent/keyformat.txt
@@ -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
diff --git a/agent/protect-tool.c b/agent/protect-tool.c
index 03dbda9..1871ac7 100644
--- a/agent/protect-tool.c
+++ b/agent/protect-tool.c
@@ -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;
}
diff --git a/agent/protect.c b/agent/protect.c
index f037703..a78d5a5 100644
--- a/agent/protect.c
+++ b/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;
+ if (is_ocb && !OCB_MODE_SUPPORTED)
+ return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION);
+
blklen = gcry_cipher_get_algo_blklen (prot_cipher);
- if (protectedlen < 4 || (protectedlen%blklen))
- return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
+ 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, GCRY_CIPHER_MODE_CBC,
+ 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. */
}
diff --git a/agent/t-protect.c b/agent/t-protect.c
index 9096cb2..431eccf 100644
--- a/agent/t-protect.c
+++ b/agent/t-protect.c
@@ -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",
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-gnupg/gnupg2.git
More information about the Pkg-gnupg-commit
mailing list