/****************
 * Check whether we can trust this pk which has a trustlevel of TRUSTLEVEL
 * Returns: true if we trust.
 */
static int
do_we_trust( PKT_public_key *pk, unsigned int trustlevel )
{
  /* We should not be able to get here with a revoked or expired
     key */
  if(trustlevel & TRUST_FLAG_REVOKED
     || trustlevel & TRUST_FLAG_SUB_REVOKED
     || (trustlevel & TRUST_MASK) == TRUST_EXPIRED)
    BUG();

  if( opt.trust_model==TM_ALWAYS )
    {
      if( opt.verbose )
	log_info("No trust check due to `--trust-model always' option\n");
      return 1;
    }

  switch(trustlevel & TRUST_MASK)
    {
    default:
      log_error ("invalid trustlevel %u returned from validation layer\n",
		 trustlevel);
      /* fall thru */
    case TRUST_UNKNOWN: 
    case TRUST_UNDEFINED:
      log_info(_("%s: There is no assurance this key belongs"
		 " to the named user\n"),keystr_from_pk(pk));
      return 0; /* no */

    case TRUST_MARGINAL:
      log_info(_("%s: There is limited assurance this key belongs"
		 " to the named user\n"),keystr_from_pk(pk));
      return 1; /* yes */

    case TRUST_FULLY:
      if( opt.verbose )
	log_info(_("This key probably belongs to the named user\n"));
      return 1; /* yes */

    case TRUST_ULTIMATE:
      if( opt.verbose )
	log_info(_("This key belongs to us\n"));
      return 1; /* yes */
    }

  return 1; /*NOTREACHED*/
}
예제 #2
0
파일: pkclist.c 프로젝트: FMayzek/gnupg
/* Print a warning for all keys in PK_LIST missing the MDC feature. */
void
warn_missing_mdc_from_pklist (PK_LIST pk_list)
{
  PK_LIST pkr;

  for (pkr = pk_list; pkr; pkr = pkr->next)
    {
      int mdc;

      if (pkr->pk->user_id) /* selected by user ID */
        mdc = pkr->pk->user_id->flags.mdc;
      else
        mdc = pkr->pk->flags.mdc;
      if (!mdc)
        log_info (_("Note: key %s has no %s feature\n"),
                  keystr_from_pk (pkr->pk), "MDC");
    }
}
예제 #3
0
파일: pkclist.c 프로젝트: FMayzek/gnupg
void
warn_missing_aes_from_pklist (PK_LIST pk_list)
{
  PK_LIST pkr;

  for (pkr = pk_list; pkr; pkr = pkr->next)
    {
      const prefitem_t *prefs;
      int i;
      int gotit = 0;

      prefs = pkr->pk->user_id? pkr->pk->user_id->prefs : pkr->pk->prefs;
      if (prefs)
        {
          for (i=0; !gotit && prefs[i].type; i++ )
            if (prefs[i].type == PREFTYPE_SYM
                && prefs[i].value == CIPHER_ALGO_AES)
              gotit++;
	}
      if (!gotit)
        log_info (_("Note: key %s has no preference for %s\n"),
                  keystr_from_pk (pkr->pk), "AES");
    }
}
예제 #4
0
파일: sig-check.c 프로젝트: CSNW/gnupg
int
signature_check2 (PKT_signature *sig, gcry_md_hd_t digest, u32 *r_expiredate,
		  int *r_expired, int *r_revoked, PKT_public_key *pk )
{
    int rc=0;
    int pk_internal;

    if (pk)
      pk_internal = 0;
    else
      {
	pk_internal = 1;
	pk = xmalloc_clear( sizeof *pk );
      }

    if ( (rc=openpgp_md_test_algo(sig->digest_algo)) )
      ; /* We don't have this digest. */
    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_ERR_GENERAL;
      }
    else if( get_pubkey( pk, sig->keyid ) )
	rc = GPG_ERR_NO_PUBKEY;
    else if(!pk->flags.valid && !pk->flags.primary)
      {
        /* You cannot have a good sig from an invalid subkey.  */
        rc = GPG_ERR_BAD_PUBKEY;
      }
    else
      {
        if(r_expiredate)
	  *r_expiredate = pk->expiredate;

	rc = do_check( pk, sig, digest, r_expired, r_revoked, NULL );

	/* 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
	   signaures issued by it. */
	if(rc==0 && !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_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_ERR_GENERAL;
	      }
	  }
      }

    if (pk_internal || rc)
      {
	release_public_key_parts (pk);
	if (pk_internal)
	  xfree (pk);
	else
	  /* Be very sure that the caller doesn't try to use *PK.  */
	  memset (pk, 0, sizeof (*pk));
      }

    if( !rc && sig->sig_class < 2 && is_status_enabled() ) {
	/* This signature id works best with DLP algorithms because
	 * they use a random parameter for every signature.  Instead of
	 * 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 batch processing applications might
	 * like this feature here.
         *
         * Note that before 2.0.10, we used RIPE-MD160 for the hash
         * and accidently 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 nsig = pubkey_get_nsig( sig->pubkey_algo );
	unsigned char *p, *buffer;
        size_t n, nbytes;
        int i;
        char hashbuf[20];

        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);
    }
예제 #5
0
/*
 * Encrypt the file with the given userids (or ask if none is
 * supplied).  Either FILENAME or FILEFD must be given, but not both.
 * The caller may provide a checked list of public keys in
 * PROVIDED_PKS; if not the function builds a list of keys on its own.
 *
 * Note that FILEFD is currently only used by cmd_encrypt in the
 * not yet finished server.c.
 */
int
encrypt_crypt (ctrl_t ctrl, int filefd, const char *filename,
               strlist_t remusr, int use_symkey, pk_list_t provided_keys,
               int outputfd)
{
  iobuf_t inp = NULL;
  iobuf_t out = NULL;
  PACKET pkt;
  PKT_plaintext *pt = NULL;
  DEK *symkey_dek = NULL;
  STRING2KEY *symkey_s2k = NULL;
  int rc = 0, rc2 = 0;
  u32 filesize;
  cipher_filter_context_t cfx;
  armor_filter_context_t *afx = NULL;
  compress_filter_context_t zfx;
  text_filter_context_t tfx;
  progress_filter_context_t *pfx;
  PK_LIST pk_list;
  int do_compress;
  int compliant;

  if (filefd != -1 && filename)
    return gpg_error (GPG_ERR_INV_ARG);  /* Both given.  */

  do_compress = !!opt.compress_algo;

  pfx = new_progress_context ();
  memset( &cfx, 0, sizeof cfx);
  memset( &zfx, 0, sizeof zfx);
  memset( &tfx, 0, sizeof tfx);
  init_packet(&pkt);

  if (use_symkey
      && (rc=setup_symkey(&symkey_s2k,&symkey_dek)))
    {
      release_progress_context (pfx);
      return rc;
    }

  if (provided_keys)
    pk_list = provided_keys;
  else
    {
      if ((rc = build_pk_list (ctrl, remusr, &pk_list)))
        {
          release_progress_context (pfx);
          return rc;
        }
    }

  /* Prepare iobufs. */
#ifdef HAVE_W32_SYSTEM
  if (filefd == -1)
    inp = iobuf_open (filename);
  else
    {
      inp = NULL;
      gpg_err_set_errno (ENOSYS);
    }
#else
  if (filefd == GNUPG_INVALID_FD)
    inp = iobuf_open (filename);
  else
    inp = iobuf_fdopen_nc (FD2INT(filefd), "rb");
#endif
  if (inp)
    iobuf_ioctl (inp, IOBUF_IOCTL_NO_CACHE, 1, NULL);
  if (inp && is_secured_file (iobuf_get_fd (inp)))
    {
      iobuf_close (inp);
      inp = NULL;
      gpg_err_set_errno (EPERM);
    }
  if (!inp)
    {
      char xname[64];

      rc = gpg_error_from_syserror ();
      if (filefd != -1)
        snprintf (xname, sizeof xname, "[fd %d]", filefd);
      else if (!filename)
        strcpy (xname, "[stdin]");
      else
        *xname = 0;
      log_error (_("can't open '%s': %s\n"),
                 *xname? xname : filename, gpg_strerror (rc) );
      goto leave;
    }

  if (opt.verbose)
    log_info (_("reading from '%s'\n"), iobuf_get_fname_nonnull (inp));

  handle_progress (pfx, inp, filename);

  if (opt.textmode)
    iobuf_push_filter (inp, text_filter, &tfx);

  rc = open_outfile (outputfd, filename, opt.armor? 1:0, 0, &out);
  if (rc)
    goto leave;

  if (opt.armor)
    {
      afx = new_armor_context ();
      push_armor_filter (afx, out);
    }

  /* Create a session key. */
  cfx.dek = xmalloc_secure_clear (sizeof *cfx.dek);
  if (!opt.def_cipher_algo)
    {
      /* Try to get it from the prefs.  */
      cfx.dek->algo = select_algo_from_prefs (pk_list, PREFTYPE_SYM, -1, NULL);
      /* The only way select_algo_from_prefs can fail here is when
         mixing v3 and v4 keys, as v4 keys have an implicit preference
         entry for 3DES, and the pk_list cannot be empty.  In this
         case, use 3DES anyway as it's the safest choice - perhaps the
         v3 key is being used in an OpenPGP implementation and we know
         that the implementation behind any v4 key can handle 3DES. */
      if (cfx.dek->algo == -1)
        {
          cfx.dek->algo = CIPHER_ALGO_3DES;
        }

      /* In case 3DES has been selected, print a warning if any key
         does not have a preference for AES.  This should help to
         indentify why encrypting to several recipients falls back to
         3DES. */
      if (opt.verbose && cfx.dek->algo == CIPHER_ALGO_3DES)
        warn_missing_aes_from_pklist (pk_list);
    }
  else
    {
      if (!opt.expert
          && (select_algo_from_prefs (pk_list, PREFTYPE_SYM,
                                      opt.def_cipher_algo, NULL)
              != opt.def_cipher_algo))
        {
          log_info(_("WARNING: forcing symmetric cipher %s (%d)"
                     " violates recipient preferences\n"),
                   openpgp_cipher_algo_name (opt.def_cipher_algo),
                   opt.def_cipher_algo);
        }

      cfx.dek->algo = opt.def_cipher_algo;
    }

  /* Check compliance.  */
  if (! gnupg_cipher_is_allowed (opt.compliance, 1, cfx.dek->algo,
                                 GCRY_CIPHER_MODE_CFB))
    {
      log_error (_("cipher algorithm '%s' may not be used in %s mode\n"),
		 openpgp_cipher_algo_name (cfx.dek->algo),
		 gnupg_compliance_option_string (opt.compliance));
      rc = gpg_error (GPG_ERR_CIPHER_ALGO);
      goto leave;
    }

  if (!gnupg_rng_is_compliant (opt.compliance))
    {
      rc = gpg_error (GPG_ERR_FORBIDDEN);
      log_error (_("%s is not compliant with %s mode\n"),
                 "RNG",
                 gnupg_compliance_option_string (opt.compliance));
      write_status_error ("random-compliance", rc);
      goto leave;
    }

  compliant = gnupg_cipher_is_compliant (CO_DE_VS, cfx.dek->algo,
                                         GCRY_CIPHER_MODE_CFB);

  {
    pk_list_t pkr;

    for (pkr = pk_list; pkr; pkr = pkr->next)
      {
        PKT_public_key *pk = pkr->pk;
        unsigned int nbits = nbits_from_pk (pk);

        if (!gnupg_pk_is_compliant (opt.compliance,
                                    pk->pubkey_algo, pk->pkey, nbits, NULL))
          log_info (_("WARNING: key %s is not suitable for encryption"
                      " in %s mode\n"),
                    keystr_from_pk (pk),
                    gnupg_compliance_option_string (opt.compliance));

        if (compliant
            && !gnupg_pk_is_compliant (CO_DE_VS, pk->pubkey_algo, pk->pkey,
                                       nbits, NULL))
          compliant = 0;
      }

  }

  if (compliant)
    write_status_strings (STATUS_ENCRYPTION_COMPLIANCE_MODE,
                          gnupg_status_compliance_flag (CO_DE_VS),
                          NULL);

  cfx.dek->use_aead = use_aead (pk_list, cfx.dek->algo);
  if (!cfx.dek->use_aead)
    cfx.dek->use_mdc = !!use_mdc (pk_list, cfx.dek->algo);

  /* Only do the is-file-already-compressed check if we are using a
   * MDC or AEAD.  This forces compressed files to be re-compressed if
   * we do not have a MDC to give some protection against chosen
   * ciphertext attacks. */
  if (do_compress
      && (cfx.dek->use_mdc || cfx.dek->use_aead)
      && is_file_compressed (filename, &rc2))
    {
      if (opt.verbose)
        log_info(_("'%s' already compressed\n"), filename);
      do_compress = 0;
    }
  if (rc2)
    {
      rc = rc2;
      goto leave;
    }

  make_session_key (cfx.dek);
  if (DBG_CRYPTO)
    log_printhex (cfx.dek->key, cfx.dek->keylen, "DEK is: ");

  rc = write_pubkey_enc_from_list (ctrl, pk_list, cfx.dek, out);
  if (rc)
    goto leave;

  /* We put the passphrase (if any) after any public keys as this
   * seems to be the most useful on the recipient side - there is no
   * point in prompting a user for a passphrase if they have the
   * secret key needed to decrypt.  */
  if (use_symkey && (rc = write_symkey_enc (symkey_s2k, cfx.dek->use_aead,
                                            symkey_dek, cfx.dek, out)))
    goto leave;

  if (!opt.no_literal)
    pt = setup_plaintext_name (filename, inp);

  /* Get the size of the file if possible, i.e., if it is a real file.  */
  if (filename && *filename
      && !iobuf_is_pipe_filename (filename) && !opt.textmode )
    {
      off_t tmpsize;
      int overflow;

      if ( !(tmpsize = iobuf_get_filelength(inp, &overflow))
           && !overflow && opt.verbose)
        log_info(_("WARNING: '%s' is an empty file\n"), filename );
      /* We can't encode the length of very large files because
         OpenPGP uses only 32 bit for file sizes.  So if the size
         of a file is larger than 2^32 minus some bytes for packet
         headers, we switch to partial length encoding. */
      if (tmpsize < (IOBUF_FILELENGTH_LIMIT - 65536) )
        filesize = tmpsize;
      else
        filesize = 0;
    }
  else
    filesize = opt.set_filesize ? opt.set_filesize : 0; /* stdin */

  if (!opt.no_literal)
    {
      pt->timestamp = make_timestamp();
      pt->mode = opt.mimemode? 'm' : opt.textmode ? 't' : 'b';
      pt->len = filesize;
      pt->new_ctb = !pt->len;
      pt->buf = inp;
      pkt.pkttype = PKT_PLAINTEXT;
      pkt.pkt.plaintext = pt;
      cfx.datalen = filesize && !do_compress? calc_packet_length( &pkt ) : 0;
    }
  else
    cfx.datalen = filesize && !do_compress ? filesize : 0;

  /* Register the cipher filter. */
  iobuf_push_filter (out,
                     cfx.dek->use_aead? cipher_filter_aead
                     /**/             : cipher_filter_cfb,
                     &cfx);

  /* Register the compress filter. */
  if (do_compress)
    {
      int compr_algo = opt.compress_algo;

      if (compr_algo == -1)
        {
          compr_algo = select_algo_from_prefs (pk_list, PREFTYPE_ZIP, -1, NULL);
          if (compr_algo == -1)
            compr_algo = DEFAULT_COMPRESS_ALGO;
          /* Theoretically impossible to get here since uncompressed
             is implicit.  */
        }
      else if (!opt.expert
               && select_algo_from_prefs(pk_list, PREFTYPE_ZIP,
                                         compr_algo, NULL) != compr_algo)
        {
          log_info (_("WARNING: forcing compression algorithm %s (%d)"
                      " violates recipient preferences\n"),
                    compress_algo_to_string(compr_algo), compr_algo);
        }

      /* Algo 0 means no compression. */
      if (compr_algo)
        {
          if (cfx.dek && (cfx.dek->use_mdc || cfx.dek->use_aead))
            zfx.new_ctb = 1;
          push_compress_filter (out,&zfx,compr_algo);
        }
    }

  /* Do the work. */
  if (!opt.no_literal)
    {
      if ((rc = build_packet( out, &pkt )))
        log_error ("build_packet failed: %s\n", gpg_strerror (rc));
    }
  else
    {
      /* User requested not to create a literal packet, so we copy the
         plain data. */
      byte copy_buffer[4096];
      int  bytes_copied;
      while ((bytes_copied = iobuf_read (inp, copy_buffer, 4096)) != -1)
        {
          rc = iobuf_write (out, copy_buffer, bytes_copied);
          if (rc)
            {
              log_error ("copying input to output failed: %s\n",
                         gpg_strerror (rc));
              break;
            }
        }
      wipememory (copy_buffer, 4096); /* Burn the buffer. */
    }

  /* Finish the stuff. */
 leave:
  iobuf_close (inp);
  if (rc)
    iobuf_cancel (out);
  else
    {
      iobuf_close (out); /* fixme: check returncode */
      write_status (STATUS_END_ENCRYPTION);
    }
  if (pt)
    pt->buf = NULL;
  free_packet (&pkt, NULL);
  xfree (cfx.dek);
  xfree (symkey_dek);
  xfree (symkey_s2k);
  if (!provided_keys)
    release_pk_list (pk_list);
  release_armor_context (afx);
  release_progress_context (pfx);
  return rc;
}
예제 #6
0
파일: pubkey-enc.c 프로젝트: larryv/gnupg
/*
 * Get the session key from a pubkey enc packet and return it in DEK,
 * which should have been allocated in secure memory by the caller.
 */
gpg_error_t
get_session_key (ctrl_t ctrl, PKT_pubkey_enc * k, DEK * dek)
{
  PKT_public_key *sk = NULL;
  int rc;

  if (DBG_CLOCK)
    log_clock ("get_session_key enter");

  rc = openpgp_pk_test_algo2 (k->pubkey_algo, PUBKEY_USAGE_ENC);
  if (rc)
    goto leave;

  if ((k->keyid[0] || k->keyid[1]) && !opt.try_all_secrets)
    {
      sk = xmalloc_clear (sizeof *sk);
      sk->pubkey_algo = k->pubkey_algo; /* We want a pubkey with this algo.  */
      if (!(rc = get_seckey (ctrl, sk, k->keyid)))
        {
          /* Check compliance.  */
          if (! gnupg_pk_is_allowed (opt.compliance, PK_USE_DECRYPTION,
                                     sk->pubkey_algo,
                                     sk->pkey, nbits_from_pk (sk), NULL))
            {
              log_info (_("key %s is not suitable for decryption"
                          " in %s mode\n"),
                        keystr_from_pk (sk),
                        gnupg_compliance_option_string (opt.compliance));
              rc = gpg_error (GPG_ERR_PUBKEY_ALGO);
            }
          else
            rc = get_it (ctrl, k, dek, sk, k->keyid);
        }
    }
  else if (opt.skip_hidden_recipients)
    rc = gpg_error (GPG_ERR_NO_SECKEY);
  else  /* Anonymous receiver: Try all available secret keys.  */
    {
      void *enum_context = NULL;
      u32 keyid[2];

      for (;;)
        {
          free_public_key (sk);
          sk = xmalloc_clear (sizeof *sk);
          rc = enum_secret_keys (ctrl, &enum_context, sk);
          if (rc)
            {
              rc = GPG_ERR_NO_SECKEY;
              break;
            }
          if (sk->pubkey_algo != k->pubkey_algo)
            continue;
          if (!(sk->pubkey_usage & PUBKEY_USAGE_ENC))
            continue;
          keyid_from_pk (sk, keyid);
          if (!opt.quiet)
            log_info (_("anonymous recipient; trying secret key %s ...\n"),
                      keystr (keyid));

          /* Check compliance.  */
          if (! gnupg_pk_is_allowed (opt.compliance, PK_USE_DECRYPTION,
                                     sk->pubkey_algo,
                                     sk->pkey, nbits_from_pk (sk), NULL))
            {
              log_info (_("key %s is not suitable for decryption"
                          " in %s mode\n"),
                          keystr_from_pk (sk),
                          gnupg_compliance_option_string (opt.compliance));
              continue;
            }

          rc = get_it (ctrl, k, dek, sk, keyid);
          if (!rc)
            {
              if (!opt.quiet)
                log_info (_("okay, we are the anonymous recipient.\n"));
              break;
            }
          else if (gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED)
            break; /* Don't try any more secret keys.  */
        }
      enum_secret_keys (ctrl, &enum_context, NULL);  /* free context */
    }

 leave:
  free_public_key (sk);
  if (DBG_CLOCK)
    log_clock ("get_session_key leave");
  return rc;
}
int
signature_check2( PKT_signature *sig, MD_HANDLE digest, u32 *r_expiredate, 
		  int *r_expired, int *r_revoked, PKT_public_key *ret_pk )
{
    PKT_public_key *pk = xmalloc_clear( sizeof *pk );
    int rc=0;

    if( (rc=check_digest_algo(sig->digest_algo)) )
      ; /* we don't have this digest */
    else if((rc=check_pubkey_algo(sig->pubkey_algo)))
      ; /* we don't have this pubkey algo */
    else if(!md_algo_present(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=G10ERR_GENERAL;
      }
    else if( get_pubkey( pk, sig->keyid ) )
	rc = G10ERR_NO_PUBKEY;
    else if(!pk->is_valid && !pk->is_primary)
        rc=G10ERR_BAD_PUBKEY; /* you cannot have a good sig from an
				 invalid subkey */
    else
      {
        if(r_expiredate)
	  *r_expiredate = pk->expiredate;

	rc = do_check( pk, sig, digest, r_expired, r_revoked, ret_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
	   signaures issued by it. */
	if(rc==0 && !pk->is_primary && pk->backsig<2)
	  {
	    if(pk->backsig==0)
	      {
		log_info(_("WARNING: signing subkey %s is not"
			   " cross-certified\n"),keystr_from_pk(pk));
		log_info(_("please see %s for more information\n"),
			 "http://www.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=G10ERR_GENERAL;
	      }
	    else if(pk->backsig==1)
	      {
		log_info(_("WARNING: signing subkey %s has an invalid"
			   " cross-certification\n"),keystr_from_pk(pk));
		rc=G10ERR_GENERAL;
	      }
	  }
      }

    free_public_key( pk );

    if( !rc && sig->sig_class < 2 && is_status_enabled() ) {
	/* This signature id works best with DLP algorithms because
	 * they use a random parameter for every signature.  Instead of
	 * 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 batch processing applications might
	 * like this feature here */
	MD_HANDLE md;
	u32 a = sig->timestamp;
	int i, nsig = pubkey_get_nsig( sig->pubkey_algo );
	byte *p, *buffer;

	md = md_open( DIGEST_ALGO_RMD160, 0);
	md_putc( digest, sig->pubkey_algo );
	md_putc( digest, sig->digest_algo );
	md_putc( digest, (a >> 24) & 0xff );
	md_putc( digest, (a >> 16) & 0xff );
	md_putc( digest, (a >>	8) & 0xff );
	md_putc( digest,  a	   & 0xff );
	for(i=0; i < nsig; i++ ) {
	    unsigned n = mpi_get_nbits( sig->data[i]);

	    md_putc( md, n>>8);
	    md_putc( md, n );
	    p = mpi_get_buffer( sig->data[i], &n, NULL );
	    md_write( md, p, n );
	    xfree(p);
	}
	md_final( md );
	p = make_radix64_string( md_read( md, 0 ), 20 );
	buffer = xmalloc( strlen(p) + 60 );
	sprintf( buffer, "%s %s %lu",
		 p, strtimestamp( sig->timestamp ), (ulong)sig->timestamp );
	write_status_text( STATUS_SIG_ID, buffer );
	xfree(buffer);
	xfree(p);
	md_close(md);
    }