agent: Implement new protection mode openpgp-s2k3-ocb-aes.
authorWerner Koch <wk@gnupg.org>
Tue, 12 Apr 2016 12:37:26 +0000 (14:37 +0200)
committerWerner Koch <wk@gnupg.org>
Tue, 12 Apr 2016 12:38:44 +0000 (14:38 +0200)
* 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>
agent/agent.h
agent/command-ssh.c
agent/command.c
agent/cvt-openpgp.c
agent/genkey.c
agent/keyformat.txt
agent/protect-tool.c
agent/protect.c
agent/t-protect.c

index c2726bb..0dcb201 100644 (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,
index 6e809f6..0e1d9fc 100644 (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:
 
index dfe292d..c94fdd3 100644 (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);
     }
index 8df6b8e..40d9a3e 100644 (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);
         }
index 2eec974..12c3e34 100644 (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);
index 150ea7c..e760412 100644 (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
index 03dbda9..1871ac7 100644 (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;
        }
index f037703..a78d5a5 100644 (file)
 #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);
 
 
+
 \f
 /* 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,
 \f
 /* 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.  */
         }
index 9096cb2..431eccf 100644 (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",