gpg,sm: String changes for compliance diagnostics.
[gnupg.git] / g10 / sig-check.c
index f12cfa6..60e988e 100644 (file)
@@ -1,11 +1,13 @@
 /* sig-check.c -  Check a signature
- *     Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc.
+ * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003,
+ *               2004, 2006 Free Software Foundation, Inc.
+ * Copyright (C) 2015, 2016 g10 Code GmbH
  *
  * This file is part of GnuPG.
  *
  * GnuPG is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * the Free Software Foundation; either version 3 of the License, or
  * (at your option) any later version.
  *
  * GnuPG is distributed in the hope that it will be useful,
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 #include <config.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <assert.h>
 
-#include <gcrypt.h>
-#include "util.h"
+#include "gpg.h"
+#include "../common/util.h"
 #include "packet.h"
 #include "keydb.h"
 #include "main.h"
-#include "status.h"
-#include "i18n.h"
+#include "../common/status.h"
+#include "../common/i18n.h"
 #include "options.h"
+#include "pkglue.h"
+#include "../common/compliance.h"
 
-struct cmp_help_context_s {
-    PKT_signature *sig;
-    GCRY_MD_HD md;
-};
+static int check_signature_end (PKT_public_key *pk, PKT_signature *sig,
+                               gcry_md_hd_t digest,
+                               int *r_expired, int *r_revoked,
+                               PKT_public_key *ret_pk);
 
+static int check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig,
+                                       gcry_md_hd_t digest);
 
-static int do_signature_check( PKT_signature *sig, GCRY_MD_HD digest,
-                                        u32 *r_expiredate, int *r_expired );
-static int do_check( PKT_public_key *pk, PKT_signature *sig,
-                                        GCRY_MD_HD digest, int *r_expired );
 
-
-
-/****************
- * Emulate our old PK interface here - sometime in the future we might
- * change the internal design to directly fit to libgcrypt.
- */
-static int
-pk_verify( int algo, MPI hash, MPI *data, MPI *pkey,
-          int (*cmp)(void *, MPI), void *opaque )
+/* Statistics for signature verification.  */
+struct
 {
-    GCRY_SEXP s_sig, s_hash, s_pkey;
-    int rc;
-
-    /* forget about cmp and opaque - we never used it */
-
-    /* make a sexp from pkey */
-    if( algo == GCRY_PK_DSA ) {
-       s_pkey = SEXP_CONS( SEXP_NEW( "public-key", 10 ),
-                         gcry_sexp_vlist( SEXP_NEW( "dsa", 3 ),
-                         gcry_sexp_new_name_mpi( "p", pkey[0] ),
-                         gcry_sexp_new_name_mpi( "q", pkey[1] ),
-                         gcry_sexp_new_name_mpi( "g", pkey[2] ),
-                         gcry_sexp_new_name_mpi( "y", pkey[3] ),
-                         NULL ));
-    }
-    else if( algo == GCRY_PK_ELG || algo == GCRY_PK_ELG_E ) {
-       s_pkey = SEXP_CONS( SEXP_NEW( "public-key", 10 ),
-                         gcry_sexp_vlist( SEXP_NEW( "elg", 3 ),
-                         gcry_sexp_new_name_mpi( "p", pkey[0] ),
-                         gcry_sexp_new_name_mpi( "g", pkey[1] ),
-                         gcry_sexp_new_name_mpi( "y", pkey[2] ),
-                         NULL ));
-    }
-    else if( algo == GCRY_PK_RSA ) {
-       s_pkey = SEXP_CONS( SEXP_NEW( "public-key", 10 ),
-                         gcry_sexp_vlist( SEXP_NEW( "rsa", 3 ),
-                         gcry_sexp_new_name_mpi( "n", pkey[0] ),
-                         gcry_sexp_new_name_mpi( "e", pkey[1] ),
-                         NULL ));
-    }
-    else
-       return GPGERR_PUBKEY_ALGO;
-
-    /* put hash into a S-Exp s_hash */
-    s_hash = gcry_sexp_new_mpi( hash );
-
-    /* put data into a S-Exp s_sig */
-    if( algo == GCRY_PK_DSA ) {
-       s_sig = SEXP_CONS( SEXP_NEW( "sig-val", 0 ),
-                         gcry_sexp_vlist( SEXP_NEW( "dsa", 0 ),
-                         gcry_sexp_new_name_mpi( "r", data[0] ),
-                         gcry_sexp_new_name_mpi( "s", data[1] ),
-                         NULL ));
-    }
-    else if( algo == GCRY_PK_ELG || algo == GCRY_PK_ELG_E ) {
-       s_sig = SEXP_CONS( SEXP_NEW( "sig-val", 0 ),
-                         gcry_sexp_vlist( SEXP_NEW( "elg", 0 ),
-                         gcry_sexp_new_name_mpi( "r", data[0] ),
-                         gcry_sexp_new_name_mpi( "s", data[1] ),
-                         NULL ));
-    }
-    else if( algo == GCRY_PK_RSA ) {
-       s_sig = SEXP_CONS( SEXP_NEW( "sig-val", 0 ),
-                         gcry_sexp_vlist( SEXP_NEW( "rsa", 3 ),
-                         gcry_sexp_new_name_mpi( "s", data[0] ),
-                         NULL ));
-    }
-    else
-       BUG();
+  unsigned int total;  /* Total number of verifications.  */
+  unsigned int cached; /* Number of seen cache entries.  */
+  unsigned int goodsig;/* Number of good verifications from the cache.  */
+  unsigned int badsig; /* Number of bad verifications from the cache.  */
+} cache_stats;
 
 
-    rc = gcry_pk_verify( s_sig, s_hash, s_pkey );
-    gcry_sexp_release( s_sig );
-    gcry_sexp_release( s_hash );
-    gcry_sexp_release( s_pkey );
-    return rc;
+/* Dump verification stats.  */
+void
+sig_check_dump_stats (void)
+{
+  log_info ("sig_cache: total=%u cached=%u good=%u bad=%u\n",
+            cache_stats.total, cache_stats.cached,
+            cache_stats.goodsig, cache_stats.badsig);
 }
 
 
-
-/****************
- * Check the signature which is contained in SIG.
- * The GCRY_MD_HD should be currently open, so that this function
- * is able to append some data, before finalizing the digest.
- */
+/* Check a signature.  This is shorthand for check_signature2 with
+   the unnamed arguments passed as NULL.  */
 int
-signature_check( PKT_signature *sig, GCRY_MD_HD digest )
+check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest)
 {
-    u32 dummy;
-    int dum2;
-    return do_signature_check( sig, digest, &dummy, &dum2 );
+  return check_signature2 (ctrl, sig, digest, NULL, NULL, NULL, NULL);
 }
 
-static int
-do_signature_check( PKT_signature *sig, GCRY_MD_HD digest,
-                                       u32 *r_expiredate, int *r_expired )
+
+/* Check a signature.
+ *
+ * Looks up the public key that created the signature (SIG->KEYID)
+ * from the key db.  Makes sure that the signature is valid (it was
+ * not created prior to the key, the public key was created in the
+ * past, and the signature does not include any unsupported critical
+ * features), finishes computing the hash of the signature data, and
+ * checks that the signature verifies the digest.  If the key that
+ * generated the signature is a subkey, this function also verifies
+ * that there is a valid backsig from the subkey to the primary key.
+ * Finally, if status fd is enabled and the signature class is 0x00 or
+ * 0x01, then a STATUS_SIG_ID is emitted on the status fd.
+ *
+ * SIG is the signature to check.
+ *
+ * DIGEST contains a valid hash context that already includes the
+ * signed data.  This function adds the relevant meta-data from the
+ * signature packet to compute the final hash.  (See Section 5.2 of
+ * RFC 4880: "The concatenation of the data being signed and the
+ * signature data from the version number through the hashed subpacket
+ * data (inclusive) is hashed.")
+ *
+ * If R_EXPIREDATE is not NULL, R_EXPIREDATE is set to the key's
+ * expiry.
+ *
+ * If R_EXPIRED is not NULL, *R_EXPIRED is set to 1 if PK has expired
+ * (0 otherwise).  Note: PK being expired does not cause this function
+ * to fail.
+ *
+ * If R_REVOKED is not NULL, *R_REVOKED is set to 1 if PK has been
+ * revoked (0 otherwise).  Note: PK being revoked does not cause this
+ * function to fail.
+ *
+ * If R_PK is not NULL, the public key is stored at that address if it
+ * was found; other wise NULL is stored.
+ *
+ * Returns 0 on success.  An error code otherwise.  */
+gpg_error_t
+check_signature2 (ctrl_t ctrl,
+                  PKT_signature *sig, gcry_md_hd_t digest, u32 *r_expiredate,
+                 int *r_expired, int *r_revoked, PKT_public_key **r_pk)
 {
-    PKT_public_key *pk = gcry_xcalloc( 1, sizeof *pk );
     int rc=0;
+    PKT_public_key *pk;
 
-    if( is_RSA(sig->pubkey_algo) )
-       write_status(STATUS_RSA_OR_IDEA);
+    if (r_expiredate)
+      *r_expiredate = 0;
+    if (r_expired)
+      *r_expired = 0;
+    if (r_revoked)
+      *r_revoked = 0;
+    if (r_pk)
+      *r_pk = NULL;
 
-    *r_expiredate = 0;
-    if( get_pubkey( pk, sig->keyid ) )
-       rc = GPGERR_NO_PUBKEY;
-    else {
-       *r_expiredate = pk->expiredate;
-       rc = do_check( pk, sig, digest, r_expired );
-    }
+    pk = xtrycalloc (1, sizeof *pk);
+    if (!pk)
+      return gpg_error_from_syserror ();
+
+    if ( (rc=openpgp_md_test_algo(sig->digest_algo)) )
+      ; /* We don't have this digest. */
+    else if (! gnupg_digest_is_allowed (opt.compliance, 0, sig->digest_algo))
+      {
+       /* Compliance failure.  */
+       log_info (_("digest algorithm '%s' may not be used in %s mode\n"),
+                 gcry_md_algo_name (sig->digest_algo),
+                 gnupg_compliance_option_string (opt.compliance));
+       rc = gpg_error (GPG_ERR_DIGEST_ALGO);
+      }
+    else if ((rc=openpgp_pk_test_algo(sig->pubkey_algo)))
+      ; /* We don't have this pubkey algo. */
+    else if (!gcry_md_is_enabled (digest,sig->digest_algo))
+      {
+       /* Sanity check that the md has a context for the hash that the
+          sig is expecting.  This can happen if a onepass sig header does
+          not match the actual sig, and also if the clearsign "Hash:"
+          header is missing or does not match the actual sig. */
+
+        log_info(_("WARNING: signature digest conflict in message\n"));
+       rc = gpg_error (GPG_ERR_GENERAL);
+      }
+    else if( get_pubkey (ctrl, pk, sig->keyid ) )
+      rc = gpg_error (GPG_ERR_NO_PUBKEY);
+    else if (! gnupg_pk_is_allowed (opt.compliance, PK_USE_VERIFICATION,
+                                   pk->pubkey_algo, pk->pkey,
+                                    nbits_from_pk (pk),
+                                   NULL))
+      {
+       /* Compliance failure.  */
+       log_error (_("key %s may not be used for signing in %s mode\n"),
+                   keystr_from_pk (pk),
+                   gnupg_compliance_option_string (opt.compliance));
+       rc = gpg_error (GPG_ERR_PUBKEY_ALGO);
+      }
+    else if(!pk->flags.valid)
+      {
+        /* You cannot have a good sig from an invalid key.  */
+        rc = gpg_error (GPG_ERR_BAD_PUBKEY);
+      }
+    else
+      {
+        if(r_expiredate)
+         *r_expiredate = pk->expiredate;
+
+       rc = check_signature_end (pk, sig, digest, r_expired, r_revoked, NULL);
 
-    free_public_key( pk );
+       /* Check the backsig.  This is a 0x19 signature from the
+          subkey on the primary key.  The idea here is that it should
+          not be possible for someone to "steal" subkeys and claim
+          them as their own.  The attacker couldn't actually use the
+          subkey, but they could try and claim ownership of any
+          signatures issued by it. */
+       if (!rc && !pk->flags.primary && pk->flags.backsig < 2)
+         {
+           if (!pk->flags.backsig)
+             {
+               log_info(_("WARNING: signing subkey %s is not"
+                          " cross-certified\n"),keystr_from_pk(pk));
+               log_info(_("please see %s for more information\n"),
+                        "https://gnupg.org/faq/subkey-cross-certify.html");
+               /* --require-cross-certification makes this warning an
+                     error.  TODO: change the default to require this
+                     after more keys have backsigs. */
+               if(opt.flags.require_cross_cert)
+                 rc = gpg_error (GPG_ERR_GENERAL);
+             }
+           else if(pk->flags.backsig == 1)
+             {
+               log_info(_("WARNING: signing subkey %s has an invalid"
+                          " cross-certification\n"),keystr_from_pk(pk));
+               rc = gpg_error (GPG_ERR_GENERAL);
+             }
+         }
+
+      }
 
     if( !rc && sig->sig_class < 2 && is_status_enabled() ) {
        /* This signature id works best with DLP algorithms because
@@ -163,175 +214,256 @@ do_signature_check( PKT_signature *sig, GCRY_MD_HD digest,
         * this sig-id we could have also used the hash of the document
         * and the timestamp, but the drawback of this is, that it is
         * not possible to sign more than one identical document within
-        * one second.  Some remote bacth processing applications might
-        * like this feature here */
-       GCRY_MD_HD md;
+        * one second.  Some remote batch processing applications might
+        * like this feature here.
+         *
+         * Note that before 2.0.10, we used RIPE-MD160 for the hash
+         * and accidentally didn't include the timestamp and algorithm
+         * information in the hash.  Given that this feature is not
+         * commonly used and that a replay attacks detection should
+         * not solely be based on this feature (because it does not
+         * work with RSA), we take the freedom and switch to SHA-1
+         * with 2.0.10 to take advantage of hardware supported SHA-1
+         * implementations.  We also include the missing information
+         * in the hash.  Note also the SIG_ID as computed by gpg 1.x
+         * and gpg 2.x didn't matched either because 2.x used to print
+         * MPIs not in PGP format.  */
        u32 a = sig->timestamp;
-       int i, nsig = pubkey_get_nsig( sig->pubkey_algo );
-       byte *p, *buffer;
+       int nsig = pubkey_get_nsig( sig->pubkey_algo );
+       unsigned char *p, *buffer;
+        size_t n, nbytes;
+        int i;
+        char hashbuf[20];
 
-       if( !(md = gcry_md_open( GCRY_MD_RMD160, 0)) )
-           BUG();
-       gcry_md_putc( digest, sig->pubkey_algo );
-       gcry_md_putc( digest, sig->digest_algo );
-       gcry_md_putc( digest, (a >> 24) & 0xff );
-       gcry_md_putc( digest, (a >> 16) & 0xff );
-       gcry_md_putc( digest, (a >>  8) & 0xff );
-       gcry_md_putc( digest,  a        & 0xff );
-       for(i=0; i < nsig; i++ ) {
-           size_t n = gcry_mpi_get_nbits( sig->data[i]);
-
-           gcry_md_putc( md, n>>8);
-           gcry_md_putc( md, n );
-           if( gcry_mpi_aprint( GCRYMPI_FMT_USG, &p, &n, sig->data[i] ) )
-               BUG();
-           gcry_md_write( md, p, n );
-           gcry_free(p);
-       }
-       gcry_md_final( md );
-       p = make_radix64_string( gcry_md_read( md, 0 ), 20 );
-       buffer = gcry_xmalloc( strlen(p) + 60 );
-       sprintf( buffer, "%s %s %lu",
-                p, strtimestamp( sig->timestamp ), (ulong)sig->timestamp );
-       write_status_text( STATUS_SIG_ID, buffer );
-       gcry_free(buffer);
-       gcry_free(p);
-       gcry_md_close(md);
+        nbytes = 6;
+       for (i=0; i < nsig; i++ )
+          {
+           if (gcry_mpi_print (GCRYMPI_FMT_USG, NULL, 0, &n, sig->data[i]))
+              BUG();
+            nbytes += n;
+          }
+
+        /* Make buffer large enough to be later used as output buffer.  */
+        if (nbytes < 100)
+          nbytes = 100;
+        nbytes += 10;  /* Safety margin.  */
+
+        /* Fill and hash buffer.  */
+        buffer = p = xmalloc (nbytes);
+       *p++ = sig->pubkey_algo;
+       *p++ = sig->digest_algo;
+       *p++ = (a >> 24) & 0xff;
+       *p++ = (a >> 16) & 0xff;
+       *p++ = (a >>  8) & 0xff;
+       *p++ =  a & 0xff;
+        nbytes -= 6;
+       for (i=0; i < nsig; i++ )
+          {
+           if (gcry_mpi_print (GCRYMPI_FMT_PGP, p, nbytes, &n, sig->data[i]))
+              BUG();
+            p += n;
+            nbytes -= n;
+          }
+        gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf, buffer, p-buffer);
+
+       p = make_radix64_string (hashbuf, 20);
+       sprintf (buffer, "%s %s %lu",
+                p, strtimestamp (sig->timestamp), (ulong)sig->timestamp);
+       xfree (p);
+       write_status_text (STATUS_SIG_ID, buffer);
+       xfree (buffer);
     }
 
+    if (r_pk)
+      *r_pk = pk;
+    else
+      {
+       release_public_key_parts (pk);
+        xfree (pk);
+      }
+
     return rc;
 }
 
 
-/****************
- * This function gets called by pubkey_verify() if the algorithm needs it.
- */
+/* The signature SIG was generated with the public key PK.  Check
+ * whether the signature is valid in the following sense:
+ *
+ *   - Make sure the public key was created before the signature was
+ *     generated.
+ *
+ *   - Make sure the public key was created in the past
+ *
+ *   - Check whether PK has expired (set *R_EXPIRED to 1 if so and 0
+ *     otherwise)
+ *
+ *   - Check whether PK has been revoked (set *R_REVOKED to 1 if so
+ *     and 0 otherwise).
+ *
+ * If either of the first two tests fail, returns an error code.
+ * Otherwise returns 0.  (Thus, this function doesn't fail if the
+ * public key is expired or revoked.)  */
 static int
-cmp_help( void *opaque, MPI result )
+check_signature_metadata_validity (PKT_public_key *pk, PKT_signature *sig,
+                                  int *r_expired, int *r_revoked)
 {
-  #if 0 /* we do not use this anymore */
-    int rc=0, i, j, c, old_enc;
-    byte *dp;
-    const byte *asn;
-    size_t mdlen, asnlen;
-    struct cmp_help_context_s *ctx = opaque;
-    PKT_signature *sig = ctx->sig;
-    GCRY_MD_HD digest = ctx->md;
-
-    old_enc = 0;
-    for(i=j=0; (c=mpi_getbyte(result, i)) != -1; i++ ) {
-       if( !j ) {
-           if( !i && c != 1 )
-               break;
-           else if( i && c == 0xff )
-               ; /* skip the padding */
-           else if( i && !c )
-               j++;
-           else
-               break;
-       }
-       else if( ++j == 18 && c != 1 )
-           break;
-       else if( j == 19 && c == 0 ) {
-           old_enc++;
-           break;
-       }
-    }
-    if( old_enc ) {
-       log_error("old encoding scheme is not supported\n");
-       return GPGERR_GENERAL;
-    }
+    u32 cur_time;
 
-    if( (rc=check_digest_algo(sig->digest_algo)) )
-       return rc; /* unsupported algo */
-    asn = md_asn_oid( sig->digest_algo, &asnlen, &mdlen );
-
-    for(i=mdlen,j=asnlen-1; (c=mpi_getbyte(result, i)) != -1 && j >= 0;
-                                                          i++, j-- )
-       if( asn[j] != c )
-           break;
-    if( j != -1 || mpi_getbyte(result, i) )
-       return GPGERR_BAD_PUBKEY;  /* ASN is wrong */
-    for(i++; (c=mpi_getbyte(result, i)) != -1; i++ )
-       if( c != 0xff  )
-           break;
-    i++;
-    if( c != sig->digest_algo || mpi_getbyte(result, i) ) {
-       /* Padding or leading bytes in signature is wrong */
-       return GPGERR_BAD_PUBKEY;
-    }
-    if( mpi_getbyte(result, mdlen-1) != sig->digest_start[0]
-       || mpi_getbyte(result, mdlen-2) != sig->digest_start[1] ) {
-       /* Wrong key used to check the signature */
-       return GPGERR_BAD_PUBKEY;
-    }
+    if(r_expired)
+      *r_expired = 0;
+    if(r_revoked)
+      *r_revoked = 0;
+
+    if( pk->timestamp > sig->timestamp )
+      {
+       ulong d = pk->timestamp - sig->timestamp;
+        if ( d < 86400 )
+          {
+            log_info
+              (ngettext
+               ("public key %s is %lu second newer than the signature\n",
+                "public key %s is %lu seconds newer than the signature\n",
+                d), keystr_from_pk (pk), d);
+          }
+        else
+          {
+            d /= 86400;
+            log_info
+              (ngettext
+               ("public key %s is %lu day newer than the signature\n",
+                "public key %s is %lu days newer than the signature\n",
+                d), keystr_from_pk (pk), d);
+          }
+       if (!opt.ignore_time_conflict)
+         return GPG_ERR_TIME_CONFLICT; /* pubkey newer than signature.  */
+      }
 
-    dp = md_read( digest, sig->digest_algo );
-    for(i=mdlen-1; i >= 0; i--, dp++ ) {
-       if( mpi_getbyte( result, i ) != *dp )
-           return GPGERR_BAD_SIGN;
+    cur_time = make_timestamp();
+    if( pk->timestamp > cur_time )
+      {
+       ulong d = pk->timestamp - cur_time;
+        if (d < 86400)
+          {
+            log_info (ngettext("key %s was created %lu second"
+                               " in the future (time warp or clock problem)\n",
+                               "key %s was created %lu seconds"
+                               " in the future (time warp or clock problem)\n",
+                               d), keystr_from_pk (pk), d);
+          }
+        else
+          {
+            d /= 86400;
+            log_info (ngettext("key %s was created %lu day"
+                               " in the future (time warp or clock problem)\n",
+                               "key %s was created %lu days"
+                               " in the future (time warp or clock problem)\n",
+                               d), keystr_from_pk (pk), d);
+          }
+       if (!opt.ignore_time_conflict)
+         return GPG_ERR_TIME_CONFLICT;
+      }
+
+    /* Check whether the key has expired.  We check the has_expired
+       flag which is set after a full evaluation of the key (getkey.c)
+       as well as a simple compare to the current time in case the
+       merge has for whatever reasons not been done.  */
+    if( pk->has_expired || (pk->expiredate && pk->expiredate < cur_time)) {
+        char buf[11];
+        if (opt.verbose)
+         log_info(_("Note: signature key %s expired %s\n"),
+                  keystr_from_pk(pk), asctimestamp( pk->expiredate ) );
+       sprintf(buf,"%lu",(ulong)pk->expiredate);
+       write_status_text(STATUS_KEYEXPIRED,buf);
+       if(r_expired)
+         *r_expired = 1;
     }
+
+    if (pk->flags.revoked)
+      {
+        if (opt.verbose)
+         log_info (_("Note: signature key %s has been revoked\n"),
+                    keystr_from_pk(pk));
+        if (r_revoked)
+          *r_revoked=1;
+      }
+
     return 0;
-  #else
-    return -1;
-  #endif
 }
 
 
+/* Finish generating a signature and check it.  Concretely: make sure
+ * that the signature is valid (it was not created prior to the key,
+ * the public key was created in the past, and the signature does not
+ * include any unsupported critical features), finish computing the
+ * digest by adding the relevant data from the signature packet, and
+ * check that the signature verifies the digest.
+ *
+ * DIGEST contains a hash context, which has already hashed the signed
+ * data.  This function adds the relevant meta-data from the signature
+ * packet to compute the final hash.  (See Section 5.2 of RFC 4880:
+ * "The concatenation of the data being signed and the signature data
+ * from the version number through the hashed subpacket data
+ * (inclusive) is hashed.")
+ *
+ * SIG is the signature to check.
+ *
+ * PK is the public key used to generate the signature.
+ *
+ * If R_EXPIRED is not NULL, *R_EXPIRED is set to 1 if PK has expired
+ * (0 otherwise).  Note: PK being expired does not cause this function
+ * to fail.
+ *
+ * If R_REVOKED is not NULL, *R_REVOKED is set to 1 if PK has been
+ * revoked (0 otherwise).  Note: PK being revoked does not cause this
+ * function to fail.
+ *
+ * If RET_PK is not NULL, PK is copied into RET_PK on success.
+ *
+ * Returns 0 on success.  An error code other.  */
 static int
-do_check( PKT_public_key *pk, PKT_signature *sig, GCRY_MD_HD digest,
-                                                   int *r_expired )
+check_signature_end (PKT_public_key *pk, PKT_signature *sig,
+                    gcry_md_hd_t digest,
+                    int *r_expired, int *r_revoked, PKT_public_key *ret_pk)
 {
-    MPI result = NULL;
-    int rc=0;
-    struct cmp_help_context_s ctx;
-    u32 cur_time;
+    int rc = 0;
 
-    *r_expired = 0;
-    if( pk->version == 4 && pk->pubkey_algo == GCRY_PK_ELG_E ) {
-       log_info(_("this is a PGP generated "
-                  "ElGamal key which is NOT secure for signatures!\n"));
-       return GPGERR_PUBKEY_ALGO;
-    }
+    if ((rc = check_signature_metadata_validity (pk, sig,
+                                                r_expired, r_revoked)))
+        return rc;
 
-    if( pk->timestamp > sig->timestamp ) {
-       ulong d = pk->timestamp - sig->timestamp;
-       log_info( d==1
-                 ? _("public key is %lu second newer than the signature\n")
-                 : _("public key is %lu seconds newer than the signature\n"),
-                      d );
-       if( !opt.ignore_time_conflict )
-           return GPGERR_TIME_CONFLICT; /* pubkey newer than signature */
-    }
+    if ((rc = check_signature_end_simple (pk, sig, digest)))
+      return rc;
 
-    cur_time = make_timestamp();
-    if( pk->timestamp > cur_time ) {
-       ulong d = pk->timestamp - cur_time;
-       log_info( d==1 ? _("key has been created %lu second "
-                          "in future (time warp or clock problem)\n")
-                      : _("key has been created %lu seconds "
-                          "in future (time warp or clock problem)\n"), d );
-       if( !opt.ignore_time_conflict )
-           return GPGERR_TIME_CONFLICT;
-    }
+    if(!rc && ret_pk)
+      copy_public_key(ret_pk,pk);
 
-    if( pk->expiredate && pk->expiredate < cur_time ) {
-       log_info(_("NOTE: signature key expired %s\n"),
-                                       asctimestamp( pk->expiredate ) );
-       write_status(STATUS_SIGEXPIRED);
-       *r_expired = 1;
-    }
+    return rc;
+}
 
+/* This function is similar to check_signature_end, but it only checks
+   whether the signature was generated by PK.  It does not check
+   expiration, revocation, etc.  */
+static int
+check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig,
+                            gcry_md_hd_t digest)
+{
+    gcry_mpi_t result = NULL;
+    int rc = 0;
+    const struct weakhash *weak;
 
-    if( (rc=openpgp_md_test_algo(sig->digest_algo)) )
-       return rc;
-    if( (rc=openpgp_pk_test_algo(sig->pubkey_algo, 0)) )
-       return rc;
+    if (!opt.flags.allow_weak_digest_algos)
+      for (weak = opt.weak_digests; weak; weak = weak->next)
+        if (sig->digest_algo == weak->algo)
+          {
+            print_digest_rejected_note(sig->digest_algo);
+            return GPG_ERR_DIGEST_ALGO;
+          }
 
-    /* make sure the digest algo is enabled (in case of a detached signature)*/
-    gcry_md_enable( digest, sig->digest_algo );
+    /* Make sure the digest algo is enabled (in case of a detached
+       signature).  */
+    gcry_md_enable (digest, sig->digest_algo);
 
-    /* complete the digest */
+    /* Complete the digest. */
     if( sig->version >= 4 )
        gcry_md_putc( digest, sig->version );
     gcry_md_putc( digest, sig->sig_class );
@@ -339,22 +471,29 @@ do_check( PKT_public_key *pk, PKT_signature *sig, GCRY_MD_HD digest,
        u32 a = sig->timestamp;
        gcry_md_putc( digest, (a >> 24) & 0xff );
        gcry_md_putc( digest, (a >> 16) & 0xff );
-       gcry_md_putc( digest, (a >>  8) & 0xff );
-       gcry_md_putc( digest,  a        & 0xff );
+       gcry_md_putc( digest, (a >>     8) & 0xff );
+       gcry_md_putc( digest,  a           & 0xff );
     }
     else {
        byte buf[6];
        size_t n;
        gcry_md_putc( digest, sig->pubkey_algo );
        gcry_md_putc( digest, sig->digest_algo );
-       if( sig->hashed_data ) {
-           n = (sig->hashed_data[0] << 8) | sig->hashed_data[1];
-           gcry_md_write( digest, sig->hashed_data, n+2 );
+       if( sig->hashed ) {
+           n = sig->hashed->len;
+            gcry_md_putc (digest, (n >> 8) );
+            gcry_md_putc (digest,  n       );
+           gcry_md_write (digest, sig->hashed->data, n);
            n += 6;
        }
-       else
-           n = 6;
-       /* add some magic */
+       else {
+         /* Two octets for the (empty) length of the hashed
+             section. */
+          gcry_md_putc (digest, 0);
+         gcry_md_putc (digest, 0);
+         n = 6;
+       }
+       /* add some magic per Section 5.2.4 of RFC 4880.  */
        buf[0] = sig->version;
        buf[1] = 0xff;
        buf[2] = n >> 24;
@@ -365,53 +504,42 @@ do_check( PKT_public_key *pk, PKT_signature *sig, GCRY_MD_HD digest,
     }
     gcry_md_final( digest );
 
-    result = encode_md_value( pk->pubkey_algo, digest, sig->digest_algo,
-                             gcry_mpi_get_nbits(pk->pkey[0]), 0);
-    ctx.sig = sig;
-    ctx.md = digest;
-    rc = pk_verify( pk->pubkey_algo, result, sig->data, pk->pkey,
-                       cmp_help, &ctx );
-    mpi_release( result );
-    if( (opt.emulate_bugs & EMUBUG_MDENCODE)
-       && rc == GPGERR_BAD_SIGN && is_ELGAMAL(pk->pubkey_algo) ) {
-       /* In this case we try again because old GnuPG versions didn't encode
-        * the hash right. There is no problem with DSA however  */
-       result = encode_md_value( pk->pubkey_algo, digest, sig->digest_algo,
-                                 gcry_mpi_get_nbits(pk->pkey[0]), (sig->version < 5) );
-       ctx.sig = sig;
-       ctx.md = digest;
-       rc = pk_verify( pk->pubkey_algo, result, sig->data, pk->pkey,
-                       cmp_help, &ctx );
-    }
+    /* Convert the digest to an MPI.  */
+    result = encode_md_value (pk, digest, sig->digest_algo );
+    if (!result)
+        return GPG_ERR_GENERAL;
 
-    if( !rc && sig->flags.unknown_critical ) {
-       log_info(_("assuming bad signature due to an unknown critical bit\n"));
-       rc = GPGERR_BAD_SIGN;
-    }
-    sig->flags.checked = 1;
-    sig->flags.valid = !rc;
+    /* Verify the signature.  */
+    rc = pk_verify( pk->pubkey_algo, result, sig->data, pk->pkey );
+    gcry_mpi_release (result);
+
+    if( !rc && sig->flags.unknown_critical )
+      {
+       log_info(_("assuming bad signature from key %s"
+                  " due to an unknown critical bit\n"),keystr_from_pk(pk));
+       rc = GPG_ERR_BAD_SIGNATURE;
+      }
 
     return rc;
 }
 
 
+/* Add a uid node to a hash context.  See section 5.2.4, paragraph 4
+   of RFC 4880.  */
 static void
-hash_uid_node( KBNODE unode, GCRY_MD_HD md, PKT_signature *sig )
+hash_uid_packet (PKT_user_id *uid, gcry_md_hd_t md, PKT_signature *sig )
 {
-    PKT_user_id *uid = unode->pkt->pkt.user_id;
-
-    assert( unode->pkt->pkttype == PKT_USER_ID );
-    if( uid->photo ) {
+    if( uid->attrib_data ) {
        if( sig->version >=4 ) {
            byte buf[5];
-           buf[0] = 0xd1;                 /* packet of type 17 */
-           buf[1] = uid->photolen >> 24;  /* always use 4 length bytes */
-           buf[2] = uid->photolen >> 16;
-           buf[3] = uid->photolen >>  8;
-           buf[4] = uid->photolen;
+           buf[0] = 0xd1;                   /* packet of type 17 */
+           buf[1] = uid->attrib_len >> 24;  /* always use 4 length bytes */
+           buf[2] = uid->attrib_len >> 16;
+           buf[3] = uid->attrib_len >>  8;
+           buf[4] = uid->attrib_len;
            gcry_md_write( md, buf, 5 );
        }
-       gcry_md_write( md, uid->photo, uid->photolen );
+       gcry_md_write( md, uid->attrib_data, uid->attrib_len );
     }
     else {
        if( sig->version >=4 ) {
@@ -427,123 +555,612 @@ hash_uid_node( KBNODE unode, GCRY_MD_HD md, PKT_signature *sig )
     }
 }
 
-/****************
- * check the signature pointed to by NODE. This is a key signature.
- * If the function detects a self-signature, it uses the PK from
- * ROOT and does not read any public key.
- */
-int
-check_key_signature( KBNODE root, KBNODE node, int *is_selfsig )
+static void
+cache_sig_result ( PKT_signature *sig, int result )
 {
-    u32 dummy;
-    int dum2;
-    return check_key_signature2(root, node, is_selfsig, &dummy, &dum2 );
+    if ( !result ) {
+        sig->flags.checked = 1;
+        sig->flags.valid = 1;
+    }
+    else if ( gpg_err_code (result) == GPG_ERR_BAD_SIGNATURE ) {
+        sig->flags.checked = 1;
+        sig->flags.valid = 0;
+    }
+    else {
+        sig->flags.checked = 0;
+        sig->flags.valid = 0;
+    }
 }
 
+
+/* SIG is a key revocation signature.  Check if this signature was
+ * generated by any of the public key PK's designated revokers.
+ *
+ *   PK is the public key that SIG allegedly revokes.
+ *
+ *   SIG is the revocation signature to check.
+ *
+ * This function avoids infinite recursion, which can happen if two
+ * keys are designed revokers for each other and they revoke each
+ * other.  This is done by observing that if a key A is revoked by key
+ * B we still consider the revocation to be valid even if B is
+ * revoked.  Thus, we don't need to determine whether B is revoked to
+ * determine whether A has been revoked by B, we just need to check
+ * the signature.
+ *
+ * Returns 0 if sig is valid (i.e. pk is revoked), non-0 if not
+ * revoked.  We are careful to make sure that GPG_ERR_NO_PUBKEY is
+ * only returned when a revocation signature is from a valid
+ * revocation key designated in a revkey subpacket, but the revocation
+ * key itself isn't present.
+ *
+ * XXX: This code will need to be modified if gpg ever becomes
+ * multi-threaded.  Note that this guarantees that a designated
+ * revocation sig will never be considered valid unless it is actually
+ * valid, as well as being issued by a revocation key in a valid
+ * direct signature.  Note also that this is written so that a revoked
+ * revoker can still issue revocations: i.e. If A revokes B, but A is
+ * revoked, B is still revoked.  I'm not completely convinced this is
+ * the proper behavior, but it matches how PGP does it. -dms */
 int
-check_key_signature2( KBNODE root, KBNODE node, int *is_selfsig,
-                                      u32 *r_expiredate, int *r_expired )
+check_revocation_keys (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig)
 {
-    GCRY_MD_HD md;
-    PKT_public_key *pk;
-    PKT_signature *sig;
-    int algo;
-    int rc;
+  static int busy=0;
+  int i;
+  int rc = GPG_ERR_GENERAL;
 
-    if( is_selfsig )
-       *is_selfsig = 0;
-    *r_expiredate = 0;
-    *r_expired = 0;
-    assert( node->pkt->pkttype == PKT_SIGNATURE );
-    assert( root->pkt->pkttype == PKT_PUBLIC_KEY );
-
-    pk = root->pkt->pkt.public_key;
-    sig = node->pkt->pkt.signature;
-    algo = sig->digest_algo;
-
-  #if 0 /* I am not sure whether this is a good thing to do */
-    if( sig->flags.checked )
-       log_debug("check_key_signature: already checked: %s\n",
-                     sig->flags.valid? "good":"bad" );
-  #endif
-
-    if( (rc=openpgp_md_test_algo(algo)) )
-       return rc;
-
-    if( sig->sig_class == 0x20 ) {
-       if( !(md = gcry_md_open( algo, 0 )) )
-           BUG();
-       hash_public_key( md, pk );
-       rc = do_check( pk, sig, md, r_expired );
-       gcry_md_close(md);
-    }
-    else if( sig->sig_class == 0x28 ) { /* subkey revocation */
-       KBNODE snode = find_prev_kbnode( root, node, PKT_PUBLIC_SUBKEY );
-
-       if( snode ) {
-           if( !(md = gcry_md_open( algo, 0 )) )
-               BUG();
-           hash_public_key( md, pk );
-           hash_public_key( md, snode->pkt->pkt.public_key );
-           rc = do_check( pk, sig, md, r_expired );
-           gcry_md_close(md);
-       }
-       else {
-           log_error("no subkey for subkey revocation packet\n");
-           rc = GPGERR_SIG_CLASS;
-       }
+  log_assert (IS_KEY_REV(sig));
+  log_assert ((sig->keyid[0]!=pk->keyid[0]) || (sig->keyid[0]!=pk->keyid[1]));
+
+  /* Avoid infinite recursion.  Consider the following:
+   *
+   *   - We want to check if A is revoked.
+   *
+   *   - C is a designated revoker for B and has revoked B.
+   *
+   *   - B is a designated revoker for A and has revoked A.
+   *
+   * When checking if A is revoked (in merge_selfsigs_main), we
+   * observe that A has a designed revoker.  As such, we call this
+   * function.  This function sees that there is a valid revocation
+   * signature, which is signed by B.  It then calls check_signature()
+   * to verify that the signature is good.  To check the sig, we need
+   * to lookup B.  Looking up B means calling merge_selfsigs_main,
+   * which checks whether B is revoked, which calls this function to
+   * see if B was revoked by some key.
+   *
+   * In this case, the added level of indirection doesn't hurt.  It
+   * just means a bit more work.  However, if C == A, then we'd end up
+   * in a loop.  But, it doesn't make sense to look up C anyways: even
+   * if B is revoked, we conservatively consider a valid revocation
+   * signed by B to revoke A.  Since this is the only place where this
+   * type of recursion can occur, we simply cause this function to
+   * fail if it is entered recursively.  */
+  if (busy)
+    {
+      /* Return an error (i.e. not revoked), but mark the pk as
+         uncacheable as we don't really know its revocation status
+         until it is checked directly.  */
+      pk->flags.dont_cache = 1;
+      return rc;
     }
-    else if( sig->sig_class == 0x18 ) {
-       KBNODE snode = find_prev_kbnode( root, node, PKT_PUBLIC_SUBKEY );
 
-       if( snode ) {
-           if( is_selfsig ) {  /* does this make sense????? */
-               u32 keyid[2];   /* it should always be a selfsig */
+  busy=1;
+
+  /*  es_printf("looking at %08lX with a sig from %08lX\n",(ulong)pk->keyid[1],
+      (ulong)sig->keyid[1]); */
+
+  /* is the issuer of the sig one of our revokers? */
+  if( !pk->revkey && pk->numrevkeys )
+     BUG();
+  else
+      for(i=0;i<pk->numrevkeys;i++)
+       {
+         /* The revoker's keyid.  */
+          u32 keyid[2];
+
+          keyid_from_fingerprint (ctrl, pk->revkey[i].fpr,
+                                  MAX_FINGERPRINT_LEN, keyid);
 
-               keyid_from_pk( pk, keyid );
-               if( keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] )
-                   *is_selfsig = 1;
+          if(keyid[0]==sig->keyid[0] && keyid[1]==sig->keyid[1])
+           /* The signature was generated by a designated revoker.
+              Verify the signature.  */
+           {
+              gcry_md_hd_t md;
+
+              if (gcry_md_open (&md, sig->digest_algo, 0))
+                BUG ();
+              hash_public_key(md,pk);
+             /* Note: check_signature only checks that the signature
+                is good.  It does not fail if the key is revoked.  */
+              rc = check_signature (ctrl, sig, md);
+             cache_sig_result(sig,rc);
+              gcry_md_close (md);
+             break;
            }
-           if( !(md = gcry_md_open( algo, 0 )) )
-               BUG();
-           hash_public_key( md, pk );
-           hash_public_key( md, snode->pkt->pkt.public_key );
-           rc = do_check( pk, sig, md, r_expired );
-           gcry_md_close(md);
-       }
-       else {
-           log_error("no subkey for key signature packet\n");
-           rc = GPGERR_SIG_CLASS;
        }
+
+  busy=0;
+
+  return rc;
+}
+
+/* Check that the backsig BACKSIG from the subkey SUB_PK to its
+   primary key MAIN_PK is valid.
+
+   Backsigs (0x19) have the same format as binding sigs (0x18), but
+   this function is simpler than check_key_signature in a few ways.
+   For example, there is no support for expiring backsigs since it is
+   questionable what such a thing actually means.  Note also that the
+   sig cache check here, unlike other sig caches in GnuPG, is not
+   persistent. */
+int
+check_backsig (PKT_public_key *main_pk,PKT_public_key *sub_pk,
+              PKT_signature *backsig)
+{
+  gcry_md_hd_t md;
+  int rc;
+
+  /* Always check whether the algorithm is available.  Although
+     gcry_md_open would throw an error, some libgcrypt versions will
+     print a debug message in that case too. */
+  if ((rc=openpgp_md_test_algo (backsig->digest_algo)))
+    return rc;
+
+  if(!opt.no_sig_cache && backsig->flags.checked)
+    return backsig->flags.valid? 0 : gpg_error (GPG_ERR_BAD_SIGNATURE);
+
+  rc = gcry_md_open (&md, backsig->digest_algo,0);
+  if (!rc)
+    {
+      hash_public_key(md,main_pk);
+      hash_public_key(md,sub_pk);
+      rc = check_signature_end (sub_pk, backsig, md, NULL, NULL, NULL);
+      cache_sig_result(backsig,rc);
+      gcry_md_close(md);
     }
-    else {
-       KBNODE unode = find_prev_kbnode( root, node, PKT_USER_ID );
-
-       if( unode ) {
-           u32 keyid[2];
-
-           keyid_from_pk( pk, keyid );
-           if( !(md = gcry_md_open( algo, 0 )) )
-               BUG();
-           hash_public_key( md, pk );
-           hash_uid_node( unode, md, sig );
-           if( keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] ) {
-               if( is_selfsig )
-                   *is_selfsig = 1;
-               rc = do_check( pk, sig, md, r_expired );
-           }
-           else {
-               rc = do_signature_check( sig, md, r_expiredate, r_expired );
+
+  return rc;
+}
+
+
+/* Check that a signature over a key is valid.  This is a
+ * specialization of check_key_signature2 with the unnamed parameters
+ * passed as NULL.  See the documentation for that function for more
+ * details.  */
+int
+check_key_signature (ctrl_t ctrl, kbnode_t root, kbnode_t node,
+                     int *is_selfsig)
+{
+  return check_key_signature2 (ctrl, root, node, NULL, NULL,
+                               is_selfsig, NULL, NULL);
+}
+
+
+/* Returns whether SIGNER generated the signature SIG over the packet
+   PACKET, which is a key, subkey or uid, and comes from the key block
+   KB.  (KB is PACKET's corresponding keyblock; we don't assume that
+   SIG has been added to the keyblock.)
+
+   If SIGNER is set, then checks whether SIGNER generated the
+   signature.  Otherwise, uses SIG->KEYID to find the alleged signer.
+   This parameter can be used to effectively override the alleged
+   signer that is stored in SIG.
+
+   KB may be NULL if SIGNER is set.
+
+   Unlike check_key_signature, this function ignores any cached
+   results!  That is, it does not consider SIG->FLAGS.CHECKED and
+   SIG->FLAGS.VALID nor does it set them.
+
+   This doesn't check the signature's semantic mean.  Concretely, it
+   doesn't check whether a non-self signed revocation signature was
+   created by a designated revoker.  In fact, it doesn't return an
+   error for a binding generated by a completely different key!
+
+   Returns 0 if the signature is valid.  Returns GPG_ERR_SIG_CLASS if
+   this signature can't be over PACKET.  Returns GPG_ERR_NOT_FOUND if
+   the key that generated the signature (according to SIG) could not
+   be found.  Returns GPG_ERR_BAD_SIGNATURE if the signature is bad.
+   Other errors codes may be returned if something else goes wrong.
+
+   IF IS_SELFSIG is not NULL, sets *IS_SELFSIG to 1 if this is a
+   self-signature (by the key's primary key) or 0 if not.
+
+   If RET_PK is not NULL, returns a copy of the public key that
+   generated the signature (i.e., the signer) on success.  This must
+   be released by the caller using release_public_key_parts ().  */
+gpg_error_t
+check_signature_over_key_or_uid (ctrl_t ctrl, PKT_public_key *signer,
+                                 PKT_signature *sig, KBNODE kb, PACKET *packet,
+                                 int *is_selfsig, PKT_public_key *ret_pk)
+{
+  int rc;
+  PKT_public_key *pripk = kb->pkt->pkt.public_key;
+  gcry_md_hd_t md;
+  int signer_alloced = 0;
+
+  rc = openpgp_pk_test_algo (sig->pubkey_algo);
+  if (rc)
+    return rc;
+  rc = openpgp_md_test_algo (sig->digest_algo);
+  if (rc)
+    return rc;
+
+  /* A signature's class indicates the type of packet that it
+     signs.  */
+  if (/* Primary key binding (made by a subkey).  */
+      sig->sig_class == 0x19
+      /* Direct key signature.  */
+      || sig->sig_class == 0x1f
+      /* Primary key revocation.  */
+      || sig->sig_class == 0x20)
+    {
+      if (packet->pkttype != PKT_PUBLIC_KEY)
+        /* Key revocations can only be over primary keys.  */
+        return gpg_error (GPG_ERR_SIG_CLASS);
+    }
+  else if (/* Subkey binding.  */
+           sig->sig_class == 0x18
+           /* Subkey revocation.  */
+           || sig->sig_class == 0x28)
+    {
+      if (packet->pkttype != PKT_PUBLIC_SUBKEY)
+        return gpg_error (GPG_ERR_SIG_CLASS);
+    }
+  else if (/* Certification.  */
+           sig->sig_class == 0x10
+           || sig->sig_class == 0x11
+           || sig->sig_class == 0x12
+           || sig->sig_class == 0x13
+           /* Certification revocation.  */
+           || sig->sig_class == 0x30)
+    {
+      if (packet->pkttype != PKT_USER_ID)
+        return gpg_error (GPG_ERR_SIG_CLASS);
+    }
+  else
+    return gpg_error (GPG_ERR_SIG_CLASS);
+
+  /* PACKET is the right type for SIG.  */
+
+  if (signer)
+    {
+      if (is_selfsig)
+        {
+          if (signer->keyid[0] == pripk->keyid[0]
+              && signer->keyid[1] == pripk->keyid[1])
+            *is_selfsig = 1;
+          else
+            *is_selfsig = 0;
+        }
+    }
+  else
+    {
+      /* Get the signer.  If possible, avoid a look up.  */
+      if (sig->keyid[0] == pripk->keyid[0]
+          && sig->keyid[1] == pripk->keyid[1])
+        /* Issued by the primary key.  */
+        {
+          signer = pripk;
+          if (is_selfsig)
+            *is_selfsig = 1;
+        }
+      else
+        {
+          kbnode_t ctx = NULL;
+          kbnode_t n;
+
+          /* See if one of the subkeys was the signer (although this
+             is extremely unlikely).  */
+          while ((n = walk_kbnode (kb, &ctx, 0)))
+            {
+              PKT_public_key *subk;
+
+              if (n->pkt->pkttype != PKT_PUBLIC_SUBKEY)
+                continue;
+
+              subk = n->pkt->pkt.public_key;
+              if (sig->keyid[0] == subk->keyid[0]
+                  && sig->keyid[1] == subk->keyid[1])
+                /* Issued by a subkey.  */
+                {
+                  signer = subk;
+                  break;
+                }
+            }
+
+          if (! signer)
+            /* Signer by some other key.  */
+            {
+              if (is_selfsig)
+                *is_selfsig = 0;
+              if (ret_pk)
+                {
+                  signer = ret_pk;
+                  memset (signer, 0, sizeof (*signer));
+                  signer_alloced = 1;
+                }
+              else
+                {
+                  signer = xmalloc_clear (sizeof (*signer));
+                  signer_alloced = 2;
+                }
+
+              rc = get_pubkey (ctrl, signer, sig->keyid);
+              if (rc)
+                {
+                  xfree (signer);
+                  signer = NULL;
+                  signer_alloced = 0;
+                  goto out;
+                }
+            }
+        }
+    }
+
+  /* We checked above that we supported this algo, so an error here is
+     a bug.  */
+  if (gcry_md_open (&md, sig->digest_algo, 0))
+    BUG ();
+
+  /* Hash the relevant data.  */
+
+  if (/* Direct key signature.  */
+      sig->sig_class == 0x1f
+      /* Primary key revocation.  */
+      || sig->sig_class == 0x20)
+    {
+      log_assert (packet->pkttype == PKT_PUBLIC_KEY);
+      hash_public_key (md, packet->pkt.public_key);
+      rc = check_signature_end_simple (signer, sig, md);
+    }
+  else if (/* Primary key binding (made by a subkey).  */
+      sig->sig_class == 0x19)
+    {
+      log_assert (packet->pkttype == PKT_PUBLIC_KEY);
+      hash_public_key (md, packet->pkt.public_key);
+      hash_public_key (md, signer);
+      rc = check_signature_end_simple (signer, sig, md);
+    }
+  else if (/* Subkey binding.  */
+           sig->sig_class == 0x18
+           /* Subkey revocation.  */
+           || sig->sig_class == 0x28)
+    {
+      log_assert (packet->pkttype == PKT_PUBLIC_SUBKEY);
+      hash_public_key (md, pripk);
+      hash_public_key (md, packet->pkt.public_key);
+      rc = check_signature_end_simple (signer, sig, md);
+    }
+  else if (/* Certification.  */
+           sig->sig_class == 0x10
+           || sig->sig_class == 0x11
+           || sig->sig_class == 0x12
+           || sig->sig_class == 0x13
+           /* Certification revocation.  */
+           || sig->sig_class == 0x30)
+    {
+      log_assert (packet->pkttype == PKT_USER_ID);
+      hash_public_key (md, pripk);
+      hash_uid_packet (packet->pkt.user_id, md, sig);
+      rc = check_signature_end_simple (signer, sig, md);
+    }
+  else
+    /* We should never get here.  (The first if above should have
+       already caught this error.)  */
+    BUG ();
+
+  gcry_md_close (md);
+
+ out:
+  if (! rc && ret_pk && (signer_alloced == -1 || ret_pk != signer))
+    copy_public_key (ret_pk, signer);
+  if (signer_alloced == 1)
+    /* We looked up SIGNER; it is not a pointer into KB.  */
+    {
+      release_public_key_parts (signer);
+      if (signer_alloced == 2)
+        /* We also allocated the memory.  */
+        xfree (signer);
+    }
+
+  return rc;
+}
+
+/* Check that a signature over a key (e.g., a key revocation, key
+ * binding, user id certification, etc.) is valid.  If the function
+ * detects a self-signature, it uses the public key from the specified
+ * key block and does not bother looking up the key specified in the
+ * signature packet.
+ *
+ * ROOT is a keyblock.
+ *
+ * NODE references a signature packet that appears in the keyblock
+ * that should be verified.
+ *
+ * If CHECK_PK is set, the specified key is sometimes preferred for
+ * verifying signatures.  See the implementation for details.
+ *
+ * If RET_PK is not NULL, the public key that successfully verified
+ * the signature is copied into *RET_PK.
+ *
+ * If IS_SELFSIG is not NULL, *IS_SELFSIG is set to 1 if NODE is a
+ * self-signature.
+ *
+ * If R_EXPIREDATE is not NULL, *R_EXPIREDATE is set to the expiry
+ * date.
+ *
+ * If R_EXPIRED is not NULL, *R_EXPIRED is set to 1 if PK has been
+ * expired (0 otherwise).  Note: PK being revoked does not cause this
+ * function to fail.
+ *
+ *
+ * If OPT.NO_SIG_CACHE is not set, this function will first check if
+ * the result of a previous verification is already cached in the
+ * signature packet's data structure.
+ *
+ * TODO: add r_revoked here as well.  It has the same problems as
+ * r_expiredate and r_expired and the cache.  */
+int
+check_key_signature2 (ctrl_t ctrl,
+                      kbnode_t root, kbnode_t node, PKT_public_key *check_pk,
+                      PKT_public_key *ret_pk, int *is_selfsig,
+                      u32 *r_expiredate, int *r_expired )
+{
+  PKT_public_key *pk;
+  PKT_signature *sig;
+  int algo;
+  int rc;
+
+  if (is_selfsig)
+    *is_selfsig = 0;
+  if (r_expiredate)
+    *r_expiredate = 0;
+  if (r_expired)
+    *r_expired = 0;
+  log_assert (node->pkt->pkttype == PKT_SIGNATURE);
+  log_assert (root->pkt->pkttype == PKT_PUBLIC_KEY);
+
+  pk = root->pkt->pkt.public_key;
+  sig = node->pkt->pkt.signature;
+  algo = sig->digest_algo;
+
+  /* Check whether we have cached the result of a previous signature
+     check.  Note that we may no longer have the pubkey or hash
+     needed to verify a sig, but can still use the cached value.  A
+     cache refresh detects and clears these cases. */
+  if ( !opt.no_sig_cache )
+    {
+      cache_stats.total++;
+      if (sig->flags.checked) /* Cached status available.  */
+        {
+          cache_stats.cached++;
+          if (is_selfsig)
+            {
+              u32 keyid[2];
+
+              keyid_from_pk (pk, keyid);
+              if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1])
+                *is_selfsig = 1;
            }
-           gcry_md_close(md);
-       }
-       else {
-           log_error("no user ID for key signature packet\n");
-           rc = GPGERR_SIG_CLASS;
-       }
+          /* BUG: This is wrong for non-self-sigs... needs to be the
+             actual pk.  */
+          rc = check_signature_metadata_validity (pk, sig, r_expired, NULL);
+          if (rc)
+            return rc;
+          if (sig->flags.valid)
+            {
+              cache_stats.goodsig++;
+              return 0;
+            }
+          cache_stats.badsig++;
+          return gpg_error (GPG_ERR_BAD_SIGNATURE);
+        }
     }
 
+  rc = openpgp_pk_test_algo(sig->pubkey_algo);
+  if (rc)
+    return rc;
+  rc = openpgp_md_test_algo(algo);
+  if (rc)
     return rc;
-}
 
+  if (sig->sig_class == 0x20) /* key revocation */
+    {
+      u32 keyid[2];
+      keyid_from_pk( pk, keyid );
 
+      /* Is it a designated revoker? */
+      if (keyid[0] != sig->keyid[0] || keyid[1] != sig->keyid[1])
+        rc = check_revocation_keys (ctrl, pk, sig);
+      else
+        {
+          rc = check_signature_metadata_validity (pk, sig,
+                                                  r_expired, NULL);
+          if (! rc)
+            rc = check_signature_over_key_or_uid (ctrl, pk, sig,
+                                                  root, root->pkt,
+                                                  is_selfsig, ret_pk);
+        }
+    }
+  else if (sig->sig_class == 0x28  /* subkey revocation */
+           || sig->sig_class == 0x18) /* key binding */
+    {
+      kbnode_t snode = find_prev_kbnode (root, node, PKT_PUBLIC_SUBKEY);
+
+      if (snode)
+        {
+          rc = check_signature_metadata_validity (pk, sig,
+                                                  r_expired, NULL);
+          if (! rc)
+            /* 0x28 must be a self-sig, but 0x18 needn't be.  */
+            rc = check_signature_over_key_or_uid (ctrl,
+                                                  sig->sig_class == 0x18
+                                                  ? NULL : pk,
+                                                  sig, root, snode->pkt,
+                                                  is_selfsig, ret_pk);
+       }
+      else
+        {
+          if (opt.verbose)
+            {
+              if (sig->sig_class == 0x28)
+                log_info (_("key %s: no subkey for subkey"
+                            " revocation signature\n"), keystr_from_pk(pk));
+              else if (sig->sig_class == 0x18)
+                log_info(_("key %s: no subkey for subkey"
+                           " binding signature\n"), keystr_from_pk(pk));
+            }
+          rc = GPG_ERR_SIG_CLASS;
+        }
+    }
+    else if (sig->sig_class == 0x1f) /* direct key signature */
+      {
+        rc = check_signature_metadata_validity (pk, sig,
+                                                r_expired, NULL);
+        if (! rc)
+          rc = check_signature_over_key_or_uid (ctrl, pk, sig, root, root->pkt,
+                                                is_selfsig, ret_pk);
+      }
+    else if (/* Certification.  */
+             sig->sig_class == 0x10
+             || sig->sig_class == 0x11
+             || sig->sig_class == 0x12
+             || sig->sig_class == 0x13
+             /* Certification revocation.  */
+             || sig->sig_class == 0x30)
+      {
+       kbnode_t unode = find_prev_kbnode (root, node, PKT_USER_ID);
+
+       if (unode)
+          {
+            rc = check_signature_metadata_validity (pk, sig, r_expired, NULL);
+            if (! rc)
+              /* If this is a self-sig, ignore check_pk.  */
+              rc = check_signature_over_key_or_uid
+                (ctrl,
+                 keyid_cmp (pk_keyid (pk), sig->keyid) == 0 ? pk : check_pk,
+                 sig, root, unode->pkt, NULL, ret_pk);
+          }
+       else
+         {
+            if (!opt.quiet)
+             log_info ("key %s: no user ID for key signature packet"
+                       " of class %02x\n",keystr_from_pk(pk),sig->sig_class);
+           rc = GPG_ERR_SIG_CLASS;
+         }
+      }
+  else
+    {
+      log_info ("sig issued by %s with class %d (digest: %02x %02x)"
+                " is not valid over a user id or a key id, ignoring.\n",
+                keystr (sig->keyid), sig->sig_class,
+                sig->digest_start[0], sig->digest_start[1]);
+      rc = gpg_error (GPG_ERR_BAD_SIGNATURE);
+    }
+
+  cache_sig_result  (sig, rc);
+
+  return rc;
+}