/* pubkey.c - Public key API
 *        Copyright (C) 2007 Free Software Foundation, Inc.
 *        Copyright (C) 2002, 2003, 2007 Timo Schulz
 *
 * This file is part of OpenCDK.
 *
 * OpenCDK 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
 * (at your option) any later version.
 *
 * OpenCDK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <gcrypt.h>

#include "opencdk.h"
#include "main.h"
#include "packet.h"


/* Convert the given secret key into a gcrypt SEXP object. */
static int
seckey_to_sexp (gcry_sexp_t *r_skey, cdk_seckey_t sk)
{
  gcry_sexp_t sexp = NULL;
  gcry_mpi_t *mpk = NULL, *msk = NULL;
  gcry_error_t err;
  cdk_pubkey_t pk;
  const char *fmt;

  if (!r_skey || !sk || !sk->pk)
    return CDK_Inv_Value;

  pk = sk->pk;
  mpk = pk->mpi;
  msk = sk->mpi;
  
  *r_skey = NULL;
  if (is_RSA (sk->pubkey_algo))
    {      
      fmt = "(private-key(openpgp-rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))";
      err = gcry_sexp_build (&sexp, NULL, fmt, mpk[0], mpk[1],
			     msk[0], msk[1], msk[2], msk[3]);      
    }
  else if (is_ELG (sk->pubkey_algo))
    {
      fmt = "(private-key(openpgp-elg(p%m)(g%m)(y%m)(x%m)))";
      err = gcry_sexp_build (&sexp, NULL, fmt, mpk[0], mpk[1],
			     mpk[2], msk[0]);
    }
  else if (is_DSA (sk->pubkey_algo))
    {
      fmt = "(private-key(openpgp-dsa(p%m)(q%m)(g%m)(y%m)(x%m)))";
      err = gcry_sexp_build (&sexp, NULL, fmt, mpk[0], mpk[1], mpk[2],
			     mpk[3], msk[0]);
    }
  else
    return CDK_Inv_Algo;
  if (err)
    return map_gcry_error (err);
  *r_skey = sexp;
  return 0;
}


/* Convert the given public key to a gcrypt SEXP object. */
static cdk_error_t
pubkey_to_sexp (gcry_sexp_t *r_key_sexp, cdk_pubkey_t pk)
{
  gcry_mpi_t *m;
  gcry_error_t err;
  const char *fmt = NULL;
  cdk_error_t rc = 0;
  
  if (!r_key_sexp || !pk)
    return CDK_Inv_Value;

  m = pk->mpi;
  if (is_RSA (pk->pubkey_algo))
    {
      fmt = "(public-key(openpgp-rsa(n%m)(e%m)))";
      err = gcry_sexp_build (r_key_sexp, NULL, fmt, m[0], m[1]);
      if (err)
	rc = map_gcry_error (err);
    }
  else if (is_ELG (pk->pubkey_algo)) 
    {
      fmt = "(public-key(openpgp-elg(p%m)(g%m)(y%m)))";
      err = gcry_sexp_build (r_key_sexp, NULL, fmt, m[0], m[1], m[2]);
      if (err)
	rc = map_gcry_error (err);
    }
  else if (is_DSA (pk->pubkey_algo)) 
    {
      fmt = "(public-key(openpgp-dsa(p%m)(q%m)(g%m)(y%m)))";
      err = gcry_sexp_build (r_key_sexp, NULL, fmt, m[0], m[1], m[2], m[3]);
      if (err)
	rc = map_gcry_error (err);
    }
  else
    rc = CDK_Inv_Algo;
  return rc;
}


static cdk_error_t
enckey_to_sexp (gcry_sexp_t *r_sexp, gcry_mpi_t esk)
{
  gcry_error_t err;
  
  if (!r_sexp || !esk)
    return CDK_Inv_Value;
  err = gcry_sexp_build (r_sexp, NULL, "%m", esk);
  if (err)
    return map_gcry_error (err);
  return 0;
}


static cdk_error_t
digest_to_sexp (gcry_sexp_t *r_md_sexp, int digest_algo, 
		const byte *md, size_t mdlen)
{
  gcry_mpi_t m;
  gcry_error_t err;
  
  if (!r_md_sexp || !md)
    return CDK_Inv_Value;

  if (!mdlen)
    mdlen = gcry_md_get_algo_dlen (digest_algo);
  if (!mdlen)
    return CDK_Inv_Algo;
  
  err = gcry_mpi_scan (&m, GCRYMPI_FMT_USG, md, mdlen, &mdlen);
  if (err)
    return map_gcry_error (err);
  
  err = gcry_sexp_build (r_md_sexp, NULL, "%m", m);
  gcry_mpi_release (m);
  if (err)
    return map_gcry_error (err);
  return 0;
}


static cdk_error_t
sexp_to_mpi (gcry_sexp_t sexp, const char *val, gcry_mpi_t *ret_buf)
{
  gcry_sexp_t list;
  
  if (!sexp || !val || !ret_buf)
    return CDK_Inv_Value;
  
  list = gcry_sexp_find_token (sexp, val, 0);
  if (!list)
    return CDK_Inv_Value;
  
  *ret_buf = gcry_sexp_nth_mpi (list, 1, 0);
  gcry_sexp_release (list);
  if (! *ret_buf)
    return CDK_Inv_Value;
  return 0;
}


static cdk_error_t
sexp_to_sig (cdk_pkt_signature_t sig, gcry_sexp_t sexp)
{
  if (!sig || !sexp)
    return CDK_Inv_Value;

  /* ElGamal signatures are not supported any longer. */
  if (is_ELG (sig->pubkey_algo))
    {
      _cdk_log_debug ("sexp_to_sig: unsupported signature type (ElGamal)\n");
      return CDK_Not_Implemented;
    }  
  
  if (is_RSA (sig->pubkey_algo))
    return sexp_to_mpi (sexp, "s", &sig->mpi[0]);
  else if (is_DSA (sig->pubkey_algo))
    {
      cdk_error_t rc;
      
      rc = sexp_to_mpi (sexp, "r", &sig->mpi[0]);
      if (!rc)
	rc = sexp_to_mpi (sexp, "s", &sig->mpi[1]);
      return rc;
    }
  return CDK_Inv_Algo;
}


static cdk_error_t
sig_to_sexp (gcry_sexp_t *r_sig_sexp, cdk_pkt_signature_t sig)
{
  gcry_error_t err;
  cdk_error_t rc;
  const char *fmt;
  
  if (!r_sig_sexp || !sig)
    return CDK_Inv_Value;  
  if (is_ELG (sig->pubkey_algo))
    return CDK_Not_Implemented;
  
  rc = 0;
  if (is_RSA (sig->pubkey_algo)) 
    {
      fmt = "(sig-val(openpgp-rsa(s%m)))";
      err = gcry_sexp_build (r_sig_sexp, NULL, fmt, sig->mpi[0]);
      if (err)
	rc = map_gcry_error (err);
    }
  else if (is_DSA (sig->pubkey_algo))
    {
      fmt = "(sig-val(openpgp-dsa(r%m)(s%m)))";
      err = gcry_sexp_build (r_sig_sexp, NULL, fmt, sig->mpi[0], sig->mpi[1]);
      if (err)
	rc = map_gcry_error (err);
    }
  else
    rc = CDK_Inv_Algo;
  return rc;
}


static cdk_error_t
sexp_to_pubenc (cdk_pkt_pubkey_enc_t enc, gcry_sexp_t sexp)
{
  if (!sexp || !enc)
    return CDK_Inv_Value;
  
  if (is_RSA (enc->pubkey_algo))
    return sexp_to_mpi (sexp, "a", &enc->mpi[0]);
  else if (is_ELG (enc->pubkey_algo))
    {
      cdk_error_t rc = sexp_to_mpi (sexp, "a", &enc->mpi[0]);
      if (!rc)
	rc = sexp_to_mpi (sexp, "b", &enc->mpi[1]);
      return rc;
    }
  return CDK_Inv_Algo;
}


static cdk_error_t
pubenc_to_sexp (gcry_sexp_t * r_sexp, cdk_pkt_pubkey_enc_t enc)
{
  gcry_sexp_t sexp = NULL;
  gcry_error_t err;
  const char *fmt;
  
  if (!r_sexp || !enc)
    return CDK_Inv_Value;
  
  *r_sexp = NULL;
  if (is_RSA (enc->pubkey_algo))
    {
      fmt = "(enc-val(openpgp-rsa((a%m))))";
      err = gcry_sexp_build (&sexp, NULL, fmt, enc->mpi[0]);
    }
  else if (is_ELG (enc->pubkey_algo))
    {
      fmt = "(enc-val(openpgp-elg((a%m)(b%m))))";
      err = gcry_sexp_build (&sexp, NULL, fmt, enc->mpi[0], enc->mpi[1]);      
    }
  else
    return CDK_Inv_Algo;
  if (err)
    return map_gcry_error (err);
  *r_sexp = sexp;
  return 0;
}


static int
is_unprotected (cdk_seckey_t sk)
{
  if (sk->is_protected && !sk->mpi[0])
    return 0;
  return 1;
}


/**
 * cdk_pk_encrypt:
 * @pk: the public key
 * @pke: the public key encrypted packet
 * @esk: the actual session key
 *
 * Encrypt the session key in @esk and write its encrypted content
 * into the @pke struct.
 **/
cdk_error_t
cdk_pk_encrypt (cdk_pubkey_t pk, cdk_pkt_pubkey_enc_t pke,
                gcry_mpi_t esk)
{
  gcry_sexp_t s_data = NULL, s_pkey = NULL, s_ciph = NULL;
  gcry_error_t err;
  cdk_error_t rc;
  
  if (!pk || !esk || !pke)
    return CDK_Inv_Value;
  
  if (!KEY_CAN_ENCRYPT (pk->pubkey_algo))
    return CDK_Inv_Algo;

  rc = enckey_to_sexp (&s_data, esk);
  if (!rc)
    rc = pubkey_to_sexp (&s_pkey, pk);
  if (!rc)
    {     
      err = gcry_pk_encrypt (&s_ciph, s_data, s_pkey);
      if (err)
	return map_gcry_error (err);
    }
  if (!rc)
    rc = sexp_to_pubenc (pke, s_ciph);
  
  gcry_sexp_release (s_data);
  gcry_sexp_release (s_pkey);
  gcry_sexp_release (s_ciph);
  return rc;
}


/**
 * cdk_pk_decrypt:
 * @sk: the secret key
 * @pke: public key encrypted packet
 * @r_sk: the object to store the plain session key
 *
 * Decrypt the encrypted session key from @pke into @r_sk.
 **/
cdk_error_t
cdk_pk_decrypt (cdk_seckey_t sk, cdk_pkt_pubkey_enc_t pke,
                gcry_mpi_t *r_sk)
{
  gcry_sexp_t s_data = NULL, s_skey = NULL, s_plain = NULL;
  cdk_error_t rc;
  gcry_error_t err;
  
  if (!sk || !r_sk || !pke)
    return CDK_Inv_Value;
  
  if (!is_unprotected (sk))
    return CDK_Inv_Mode;
  
  *r_sk = NULL;
  rc = seckey_to_sexp (&s_skey, sk);
  if (rc)
    return rc;

  rc = pubenc_to_sexp (&s_data, pke);
  if (rc)
    {
      gcry_sexp_release (s_skey);
      return rc;
    }
  
  err = gcry_pk_decrypt (&s_plain, s_data, s_skey);
  if (err)
    rc = map_gcry_error (err);
  else
    *r_sk  = gcry_sexp_nth_mpi (s_plain, 0, 0);

  gcry_sexp_release (s_data);
  gcry_sexp_release (s_skey);
  gcry_sexp_release (s_plain);
  return rc;
}


/**
 * cdk_pk_sign:
 * @sk: secret key
 * @sig: signature
 * @md: the message digest
 *
 * Sign the message digest from @md and write the result into @sig.
 **/
cdk_error_t
cdk_pk_sign (cdk_seckey_t sk, cdk_pkt_signature_t sig, const byte *md)
{
  gcry_sexp_t s_skey = NULL, s_sig = NULL, s_hash = NULL;
  byte *encmd = NULL;
  size_t enclen = 0;
  int nbits;
  cdk_error_t rc;
  gcry_error_t err;
  
  if (!sk || !sk->pk || !sig || !md)
    return CDK_Inv_Value;
  
  if (!is_unprotected (sk))
    return CDK_Inv_Mode;
  
  if (!KEY_CAN_SIGN (sig->pubkey_algo))
    return CDK_Inv_Algo;
  
  nbits = cdk_pk_get_nbits (sk->pk);
  rc = _cdk_digest_encode_pkcs1 (&encmd, &enclen, sk->pk->pubkey_algo, md,
				 sig->digest_algo, nbits);
  if (rc)
    return rc;

  rc = seckey_to_sexp (&s_skey, sk);
  if (!rc)
    rc = digest_to_sexp (&s_hash, sig->digest_algo, encmd, enclen);
  if (rc)
    {
      cdk_free (encmd);
      gcry_sexp_release (s_skey);
      return rc;
    }  
  
  err = gcry_pk_sign (&s_sig, s_hash, s_skey);
  if (err)
    rc = map_gcry_error (err);
  else
    {      
      rc = sexp_to_sig (sig, s_sig);
      if (!rc)
	{
	  sig->digest_start[0] = md[0];
	  sig->digest_start[1] = md[1];
	}
    }
  
  gcry_sexp_release (s_skey);
  gcry_sexp_release (s_hash);
  gcry_sexp_release (s_sig);
  cdk_free (encmd);
  return rc;
}


/**
 * cdk_pk_verify:
 * @pk: the public key
 * @sig: signature
 * @md: the message digest
 *
 * Verify the signature in @sig and compare it with the message digest in @md.
 **/
cdk_error_t
cdk_pk_verify (cdk_pubkey_t pk, cdk_pkt_signature_t sig, const byte *md)
{
  gcry_sexp_t s_pkey = NULL, s_sig = NULL, s_hash = NULL;
  byte *encmd = NULL;
  size_t enclen;
  cdk_error_t rc;
  
  if (!pk || !sig || !md)
    return CDK_Inv_Value;
  
  rc = pubkey_to_sexp (&s_pkey, pk);
  if (rc)
    return rc;
  
  rc = sig_to_sexp (&s_sig, sig);
  if (rc)
    goto leave;
  
  rc = _cdk_digest_encode_pkcs1 (&encmd, &enclen, pk->pubkey_algo, md,
				 sig->digest_algo, cdk_pk_get_nbits (pk));
  if (rc)
    goto leave;
  
  rc = digest_to_sexp (&s_hash, sig->digest_algo, encmd, enclen);
  if (rc)
    goto leave;
  
  if (gcry_pk_verify (s_sig, s_hash, s_pkey))
    rc = CDK_Bad_Sig;

  leave:
  gcry_sexp_release (s_sig);
  gcry_sexp_release (s_hash);
  gcry_sexp_release (s_pkey);
  cdk_free (encmd);
  return rc;
}


/**
 * cdk_pk_get_nbits:
 * @pk: the public key
 * 
 * Return the length of the public key in bits.
 * The meaning of length is actually the size of the 'prime'
 * object in the key. For RSA keys the modulus, for ElG/DSA
 * the size of the public prime.
 **/
int
cdk_pk_get_nbits (cdk_pubkey_t pk)
{
  if (!pk || !pk->mpi[0])
    return 0;
  return gcry_mpi_get_nbits (pk->mpi[0]);
}


/**
 * cdk_pk_get_npkey:
 * @algo: The public key algorithm.
 * 
 * Return the number of multiprecison integer forming an public
 * key with the given algorithm.
 */
int
cdk_pk_get_npkey (int algo)
{
  size_t bytes;
  
  if (algo == 16)
    algo = 20; /* FIXME: libgcrypt returns 0 for 16 */
  if (gcry_pk_algo_info( algo, GCRYCTL_GET_ALGO_NPKEY, NULL, &bytes))
    return 0;  
  return bytes;
}


/**
 * cdk_pk_get_nskey:
 * @algo: the public key algorithm
 * 
 * Return the number of multiprecision integers forming an
 * secret key with the given algorithm.
 **/
int
cdk_pk_get_nskey (int algo)
{  
  size_t bytes;
  
  if (gcry_pk_algo_info (algo, GCRYCTL_GET_ALGO_NSKEY, NULL, &bytes))
    return 0;  
  bytes -= cdk_pk_get_npkey (algo);
  return bytes;  
}


/**
 * cdk_pk_get_nbits:
 * @algo: the public key algorithm
 * 
 * Return the number of MPIs a signature consists of.
 **/
int
cdk_pk_get_nsig (int algo)
{
  size_t bytes;
  
  if (gcry_pk_algo_info (algo, GCRYCTL_GET_ALGO_NSIGN, NULL, &bytes))
    return 0;
  return bytes;  
}


/**
 * cdk_pk_get_nenc: 
 * @algo: the public key algorithm
 * 
 * Return the number of MPI's the encrypted data consists of.
 **/
int
cdk_pk_get_nenc (int algo)
{
  size_t bytes;
  
  if (gcry_pk_algo_info (algo, GCRYCTL_GET_ALGO_NENCR, NULL, &bytes))
    return 0;
  return bytes;  
}


int
_cdk_pk_algo_usage (int algo)
{
  int usage;

  /* The ElGamal sign+encrypt algorithm is not supported any longer. */
  switch (algo)
    {
    case CDK_PK_RSA  : usage = CDK_KEY_USG_SIGN | CDK_KEY_USG_ENCR; break;
    case CDK_PK_RSA_E: usage = CDK_KEY_USG_ENCR; break;
    case CDK_PK_RSA_S: usage = CDK_KEY_USG_SIGN; break;
    case CDK_PK_ELG_E: usage = CDK_KEY_USG_ENCR; break;
    case CDK_PK_DSA  : usage = CDK_KEY_USG_SIGN; break;
    default: usage = 0;
    }
  return usage;  
}


static cdk_error_t
mpi_to_buffer (gcry_mpi_t a, byte *buf, size_t buflen,
	       size_t *r_nwritten, size_t *r_nbits)
{
  size_t nbits;
  
  if (!a || !buf || !r_nwritten)
    return CDK_Inv_Value;
  
  nbits = gcry_mpi_get_nbits (a);
  if (r_nbits)
    *r_nbits = nbits;
  if ((nbits+7)/8+2 > buflen)
    return CDK_Too_Short;
  *r_nwritten = (nbits+7)/8+2;
  if (gcry_mpi_print (GCRYMPI_FMT_PGP, buf, buflen, r_nwritten, a))
    return CDK_Wrong_Format;
  return 0;
}


/**
 * cdk_pk_get_mpi:
 * @pk: public key
 * @idx: index of the MPI to retrieve
 * @buf: buffer to hold the raw data
 * @r_nwritten: output how large the raw data is
 * @r_nbits: size of the MPI in bits.
 * 
 * Return the MPI with the given index of the public key.
 **/
cdk_error_t
cdk_pk_get_mpi (cdk_pubkey_t pk, size_t idx,
                byte *buf, size_t buflen, size_t *r_nwritten, size_t *r_nbits)
{
  if (!pk || !r_nwritten)
    return CDK_Inv_Value;
  if (idx > cdk_pk_get_npkey (pk->pubkey_algo))
    return CDK_Inv_Value;
  return mpi_to_buffer (pk->mpi[idx], buf, buflen, r_nwritten, r_nbits);
}


/**
 * cdk_sk_get_mpi:
 * @sk: secret key
 * @idx: index of the MPI to retrieve
 * @buf: buffer to hold the raw data
 * @r_nwritten: output length of the raw data
 * @r_nbits: length of the MPI data in bits.
 * 
 * Return the MPI of the given secret key with the
 * index @idx. It is important to check if the key
 * is protected and thus no real MPI data will be returned then.
 **/
cdk_error_t
cdk_sk_get_mpi (cdk_pkt_seckey_t sk, size_t idx,
                byte *buf, size_t buflen, size_t *r_nwritten, size_t *r_nbits)
{
  if (!sk || !r_nwritten)
    return CDK_Inv_Value;
  if (idx > cdk_pk_get_nskey (sk->pubkey_algo))
    return CDK_Inv_Value;
  return mpi_to_buffer (sk->mpi[idx], buf, buflen, r_nwritten, r_nbits);
}


static u16
checksum_mpi (gcry_mpi_t m)
{
  byte buf[MAX_MPI_BYTES+2];
  size_t nread;
  int i;
  u16 chksum = 0;

  if (!m)
    return 0;
  if (gcry_mpi_print (GCRYMPI_FMT_PGP, buf, DIM (buf), &nread, m))
    return 0;
  for (i=0; i < nread; i++)
    chksum += buf[i];
  return chksum;
}


/**
 * cdk_sk_unprotect:
 * @sk: the secret key
 * @pw: the passphrase
 * 
 * Unprotect the given secret key with the passphrase.
 **/
cdk_error_t
cdk_sk_unprotect (cdk_pkt_seckey_t sk, const char *pw)
{
  gcry_cipher_hd_t hd;
  cdk_dek_t dek = NULL;
  byte *data = NULL;
  u16 chksum = 0;
  size_t ndata, nbits, nbytes;
  int i, dlen, pos = 0, nskey;
  cdk_error_t rc;
  gcry_error_t err;
  
  if (!sk)
    return CDK_Inv_Value;
  
  nskey = cdk_pk_get_nskey (sk->pubkey_algo);
  if (!sk->is_protected)
    {
      chksum = 0;
      for (i = 0; i < nskey; i++)
	chksum += checksum_mpi (sk->mpi[i]);
      if (chksum != sk->csum)
	return CDK_Chksum_Error;
    } 
      
  rc = cdk_dek_from_passphrase (&dek, sk->protect.algo,
				sk->protect.s2k, 0, pw);
  if (rc)
    return rc;
  err = gcry_cipher_open (&hd, sk->protect.algo, GCRY_CIPHER_MODE_CFB, 
			  GCRY_CIPHER_ENABLE_SYNC);
  if (!err)
    err = gcry_cipher_setiv (hd, sk->protect.iv, sk->protect.ivlen);
  if (!err)
    err = gcry_cipher_setkey (hd, dek->key, dek->keylen);
  if (err)
    {
      cdk_free (dek);
      return map_gcry_error (err);
    }
  cdk_dek_free (dek);
  chksum = 0;
  if (sk->version == 4) 
    {
      ndata = sk->enclen;
      data = cdk_salloc (ndata, 1);
      if (!data)
	return CDK_Out_Of_Core;
      gcry_cipher_decrypt (hd, data, ndata, sk->encdata, ndata);
      if (sk->protect.sha1chk) 
	{
	  /* This is the new SHA1 checksum method to detect tampering
	     with the key as used by the Klima/Rosa attack */
	  sk->csum = 0;
	  chksum = 1;
	  dlen = gcry_md_get_algo_dlen (GCRY_MD_SHA1);
	  if (ndata < dlen) 
	    {
	      cdk_free (data);
	      return CDK_Inv_Packet;
	    }
	  else 
	    {
	      byte mdcheck[20];
	      
	      gcry_md_hash_buffer (GCRY_MD_SHA1, 
				   mdcheck, data, ndata-dlen);
	      if (!memcmp (mdcheck, data + ndata - dlen, dlen))
		chksum = 0;	/* Digest does match */
	    }
	}
      else 
	{
	  for (i = 0; i < ndata - 2; i++)
	    chksum += data[i];
	  sk->csum = data[ndata - 2] << 8 | data[ndata - 1];
	}
      if (sk->csum == chksum) 
	{
	  for (i = 0; i < nskey; i++) 
	    {
	      nbits = data[pos] << 8 | data[pos + 1];
	      
	      if (gcry_mpi_scan (&sk->mpi[i], GCRYMPI_FMT_PGP, data,
			     (nbits+7)/8+2, &nbytes))
		{
		  wipemem (data, sk->enclen);
		  cdk_free (data);
		  return CDK_Wrong_Format;
		}	     
	      gcry_mpi_set_flag (sk->mpi[i], GCRYMPI_FLAG_SECURE);
	      pos += (nbits+7)/8+2;
	    }
	}
      wipemem (data, sk->enclen);
      cdk_free (data);
    }
  else 
    {
      byte buf[MAX_MPI_BYTES+2];
      
      chksum = 0;
      for (i = 0; i < nskey; i++)
	{
	  gcry_cipher_sync (hd);
	  gcry_mpi_print (GCRYMPI_FMT_PGP, buf, DIM (buf), 
			  &nbytes, sk->mpi[i]);
	  gcry_cipher_decrypt (hd, buf+2, nbytes-2, NULL, 0);
	  gcry_mpi_release (sk->mpi[i]);
	  if (gcry_mpi_scan (&sk->mpi[i], GCRYMPI_FMT_PGP,
			     buf, nbytes, &nbytes))
	    return CDK_Wrong_Format;
	  chksum += checksum_mpi (sk->mpi[i]);
	}
    }
  gcry_cipher_close (hd);
  if (chksum != sk->csum)
    return CDK_Chksum_Error;
  sk->is_protected = 0;
  return 0;
}


/**
 * cdk_sk_protect:
 * @sk: the secret key
 * @pw: the passphrase to use
 * 
 * Protect the given secret key with a passphrase.
 **/
cdk_error_t
cdk_sk_protect (cdk_pkt_seckey_t sk, const char *pw)
{
  gcry_cipher_hd_t hd = NULL;
  cdk_dek_t dek = NULL;
  cdk_s2k_t s2k;
  byte *p = NULL, buf[MAX_MPI_BYTES+2];
  size_t enclen = 0, nskey, i, nbytes;
  size_t dlen = gcry_md_get_algo_dlen (GCRY_MD_SHA1);
  gcry_error_t err;
  cdk_error_t rc;
  
  nskey = cdk_pk_get_nskey (sk->pubkey_algo);
  if (!nskey)
    return CDK_Inv_Algo;
  
  rc = cdk_s2k_new (&s2k, CDK_S2K_ITERSALTED, GCRY_MD_SHA256, NULL);
  if (!rc)
    rc = cdk_dek_from_passphrase (&dek, GCRY_CIPHER_AES, s2k, 1, pw);
  if (rc) 
    {
      cdk_s2k_free (s2k);
      return rc;
    }
  
  for (i = 0; i < nskey; i++)
    {
      enclen += 2;
      enclen += (gcry_mpi_get_nbits (sk->mpi[i])+7)/8;
    }
  p = sk->encdata = cdk_calloc (1, enclen + dlen + 1);
  if (!p)
    {
      cdk_s2k_free (s2k);
      return CDK_Out_Of_Core;
    }
  
  enclen = 0;
  for (i = 0; i < nskey; i++) 
    {
      if (gcry_mpi_print (GCRYMPI_FMT_PGP, buf, 
			  DIM (buf), &nbytes, sk->mpi[i]))
	{
	  cdk_free (p);
	  cdk_s2k_free (s2k);
	  return CDK_Wrong_Format;
	}     
      memcpy (p + enclen, buf, nbytes);
      enclen += nbytes;
    }
  
  enclen += dlen;
  sk->enclen = enclen;
  sk->protect.s2k = s2k;
  sk->protect.algo = GCRY_CIPHER_AES;
  sk->protect.ivlen = gcry_cipher_get_algo_blklen (sk->protect.algo);
  gcry_randomize (sk->protect.iv, sk->protect.ivlen, GCRY_STRONG_RANDOM);
  err = gcry_cipher_open (&hd, sk->protect.algo, GCRY_CIPHER_MODE_CFB, 
			  GCRY_CIPHER_ENABLE_SYNC);
  if (err)
    {
      cdk_dek_free (dek);
      rc = map_gcry_error (err);
      goto leave;
    }
  
  err = gcry_cipher_setkey (hd, dek->key, dek->keylen);
  if (!err)
    err = gcry_cipher_setiv (hd, sk->protect.iv, sk->protect.ivlen);
  cdk_dek_free (dek);
  if (err)
    {
      rc = map_gcry_error (err);
      goto leave;
    }
  
  sk->protect.sha1chk = 1;
  sk->is_protected = 1;
  sk->csum = 0;
  
  gcry_md_hash_buffer (GCRY_MD_SHA1, buf, p, enclen-dlen);
  memcpy (p + enclen - dlen, buf, dlen);
  gcry_cipher_encrypt (hd, p, enclen, NULL, 0);
  
  /* FIXME: We should release all MPI's and set the elements to NULL. */
  
  leave:
  gcry_cipher_close (hd);
  return rc;
}


/**
 * cdk_pk_from_secret_key:
 * @sk: the secret key
 * @ret_pk: the new public key
 *
 * Create a new public key from a secret key.
 **/
cdk_error_t
cdk_pk_from_secret_key (cdk_pkt_seckey_t sk, cdk_pubkey_t *ret_pk)
{
  if (!sk)
    return CDK_Inv_Value;
  return _cdk_copy_pubkey (ret_pk, sk->pk);
}


#if 0 /* FIXME: Code is not finished yet. */
cdk_error_t
cdk_pk_revoke_cert_create (cdk_pkt_seckey_t sk, int code, const char *inf,
			   char **ret_revcert)
{
  gcry_md_hd_t md;
  cdk_subpkt_t node;
  cdk_pkt_signature_t sig;
  char *p = NULL, *dat;
  gcry_error_t err;
  cdk_error_t rc = 0;
  size_t n;
  
  if (!sk || !ret_revcert)
    return CDK_Inv_Value;
  if(code < 0 || code > 3)
    return CDK_Inv_Value;
  
  sig = cdk_calloc (1, sizeof *sig);
  if (!sig)
    return CDK_Out_Of_Core;
  _cdk_sig_create (sk->pk, sig);
  n = 1;
  if (inf) 
    {
      n += strlen (p);
      p = cdk_utf8_encode (inf);
    }
  dat = cdk_calloc (1, n+1);
  if (!dat)
    {
      _cdk_free_signature (sig);
      return CDK_Out_Of_Core;
    }
  dat[0] = code;
  if (inf)
    memcpy (dat+1, p, strlen (p));
  cdk_free (p);
  
  node = cdk_subpkt_new (n);
  if (node)
    {
      cdk_subpkt_init (node, CDK_SIGSUBPKT_REVOC_REASON, dat, n);
      cdk_subpkt_add (sig->hashed, node);
    }
  cdk_free (dat);
  
  err = gcry_md_open (&md, GCRY_MD_SHA1, 0);
  if (err)
    rc = map_gcry_error (err);
  else
    _cdk_hash_pubkey (sk->pk, md, 0);
  _cdk_free_signature (sig);
  
  return rc;
}
#endif

int
_cdk_sk_get_csum (cdk_pkt_seckey_t sk)
{
  u16 csum = 0, i;
  
  if (!sk)
    return 0;
  for (i = 0; i < cdk_pk_get_nskey (sk->pubkey_algo); i++)
    csum += checksum_mpi (sk->mpi[i]);
  return csum;
}


/**
 * cdk_pk_get_fingerprint:
 * @pk: the public key
 * @fpr: the buffer to hold the fingerprint
 * 
 * Return the fingerprint of the given public key.
 * The buffer must be at least 20 octets.
 * This function should be considered deprecated and
 * the new cdk_pk_to_fingerprint() should be used whenever
 * possible to avoid overflows.
 **/
cdk_error_t
cdk_pk_get_fingerprint (cdk_pubkey_t pk, byte *fpr)
{
  gcry_md_hd_t hd;
  int md_algo;
  int dlen = 0;
  gcry_error_t err;

  if (!pk || !fpr)
    return CDK_Inv_Value;
  
  if (pk->version < 4 && is_RSA (pk->pubkey_algo))
    md_algo = GCRY_MD_MD5; /* special */
  else
    md_algo = GCRY_MD_SHA1;
  dlen = gcry_md_get_algo_dlen (md_algo);
  err = gcry_md_open (&hd, md_algo, 0);
  if (err)
    return map_gcry_error (err);
  _cdk_hash_pubkey (pk, hd, 1);
  gcry_md_final (hd);
  memcpy (fpr, gcry_md_read (hd, md_algo), dlen);
  gcry_md_close (hd);
  if (dlen == 16)
    memset (fpr + 16, 0, 4);
  return 0;
}


/**
 * cdk_pk_to_fingerprint:
 * @pk: the public key
 * @fprbuf: buffer to save the fingerprint
 * @fprbuflen: buffer size
 * @r_nout: actual length of the fingerprint.
 * 
 * Calculate a fingerprint of the given key and
 * return it in the given byte array.
 **/
cdk_error_t
cdk_pk_to_fingerprint (cdk_pubkey_t pk, 
		       byte *fprbuf, size_t fprbuflen, size_t *r_nout)
{
  size_t key_fprlen;
  cdk_error_t err;
  
  if (!pk)
    return CDK_Inv_Value;
    
  if (pk->version < 4)
    key_fprlen = 16;
  else
    key_fprlen = 20;
  
  /* Only return the required buffer size for the fingerprint. */
  if (!fprbuf && !fprbuflen && r_nout)
    {      
      *r_nout = key_fprlen;
      return 0;
    }
  
  if (!fprbuf || key_fprlen > fprbuflen)
    return CDK_Too_Short;

  err = cdk_pk_get_fingerprint (pk, fprbuf);
  if (r_nout)
    *r_nout = key_fprlen;
  
  return err;
}


/**
 * cdk_pk_fingerprint_get_keyid:
 * @fpr: the key fingerprint
 * @fprlen: the length of the fingerprint
 * 
 * Derive the key ID from the key fingerprint.
 * For version 3 keys, this is not working.
 **/
u32
cdk_pk_fingerprint_get_keyid (const byte *fpr, size_t fprlen, u32 *keyid)
{
    u32 lowbits = 0;

  /* In this case we say the key is a V3 RSA key and we can't
     use the fingerprint to get the keyid. */
  if (fpr && fprlen == 16)
    {
      keyid[0] = 0;
      keyid[1] = 0;
      return 0;
    }
  else if (keyid && fpr)
    {
      keyid[0] = _cdk_buftou32 (fpr + 12);
      keyid[1] = _cdk_buftou32 (fpr + 16);
      lowbits = keyid[1];
    }
  else if (fpr)
    lowbits = _cdk_buftou32 (fpr + 16);
  return lowbits;
}


/**
 * cdk_pk_get_keyid:
 * @pk: the public key
 * @keyid: buffer to store the key ID
 * 
 * Calculate the key ID of the given public key.
 **/
u32
cdk_pk_get_keyid (cdk_pubkey_t pk, u32 *keyid)
{
  u32 lowbits = 0;
  byte buf[24];
  
  if (pk && (!pk->keyid[0] || !pk->keyid[1])) 
    {
      if (pk->version < 4 && is_RSA (pk->pubkey_algo)) 
	{
	  byte p[MAX_MPI_BYTES];
	  size_t n;
	  
	  gcry_mpi_print (GCRYMPI_FMT_USG, p, MAX_MPI_BYTES, &n, pk->mpi[0]);
	  pk->keyid[0] = p[n-8] << 24 | p[n-7] << 16 | p[n-6] << 8 | p[n-5];
	  pk->keyid[1] = p[n-4] << 24 | p[n-3] << 16 | p[n-2] << 8 | p[n-1];
	}
      else if (pk->version == 4)
	{
	  cdk_pk_get_fingerprint (pk, buf);
	  pk->keyid[0] = _cdk_buftou32 (buf + 12);
	  pk->keyid[1] = _cdk_buftou32 (buf + 16);
	}
    }
  lowbits = pk ? pk->keyid[1] : 0;
  if (keyid && pk)
    {
      keyid[0] = pk->keyid[0];
      keyid[1] = pk->keyid[1];
    }
  
  return lowbits;
}


/**
 * cdk_sk_get_keyid:
 * @sk: the secret key
 * @keyid: buffer to hold the key ID
 * 
 * Calculate the key ID of the secret key, actually the public key.
 **/
u32
cdk_sk_get_keyid (cdk_pkt_seckey_t sk, u32 *keyid)
{
  u32 lowbits = 0;
  
  if (sk && sk->pk)
    {
      lowbits = cdk_pk_get_keyid (sk->pk, keyid);
      sk->keyid[0] = sk->pk->keyid[0];
      sk->keyid[1] = sk->pk->keyid[1];
    }
  
  return lowbits;
}


/**
 * cdk_sig_get_keyid:
 * @sig: the signature
 * @keyid: buffer to hold the key ID
 * 
 * Retrieve the key ID from the given signature.
 **/
u32
cdk_sig_get_keyid (cdk_pkt_signature_t sig, u32 *keyid)
{
  u32 lowbits = sig ? sig->keyid[1] : 0;
  
  if (keyid && sig)
    {
      keyid[0] = sig->keyid[0];
      keyid[1] = sig->keyid[1];
    }
  return lowbits;
}


/* Return the key ID from the given packet.
   If this is not possible, 0 is returned */
u32
_cdk_pkt_get_keyid (cdk_packet_t pkt, u32 *keyid)
{
  u32 lowbits;
  
  if (!pkt)
    return 0;
  
  switch (pkt->pkttype)
    {
    case CDK_PKT_PUBLIC_KEY:
    case CDK_PKT_PUBLIC_SUBKEY:
      lowbits = cdk_pk_get_keyid (pkt->pkt.public_key, keyid);
      break;
      
    case CDK_PKT_SECRET_KEY:
    case CDK_PKT_SECRET_SUBKEY:
      lowbits = cdk_sk_get_keyid (pkt->pkt.secret_key, keyid);
      break;
      
    case CDK_PKT_SIGNATURE:
      lowbits = cdk_sig_get_keyid (pkt->pkt.signature, keyid);
      break;
      
    default:
      lowbits = 0;
      break;
    }
  
  return lowbits;
}


/* Get the fingerprint of the packet if possible. */
int
_cdk_pkt_get_fingerprint (cdk_packet_t pkt, byte *fpr)
{
  if (!pkt || !fpr)
    return CDK_Inv_Value;
  
  switch (pkt->pkttype)
    {
    case CDK_PKT_PUBLIC_KEY:
    case CDK_PKT_PUBLIC_SUBKEY:
      return cdk_pk_get_fingerprint (pkt->pkt.public_key, fpr);
      
    case CDK_PKT_SECRET_KEY:
    case CDK_PKT_SECRET_SUBKEY:
      return cdk_pk_get_fingerprint (pkt->pkt.secret_key->pk, fpr);
      
    default:
      return CDK_Inv_Mode;
    }
  return 0;
}


/**
 * cdk_pubkey_to_sexp:
 * @pk: the public key
 * @sexp: where to store the S-expression
 * @len: the length of sexp
 *
 * Convert a public key to an S-expression. sexp is allocated by this
 * function, but you have to cdk_free() it yourself.  The S-expression
 * is stored in canonical format as used by libgcrypt
 * (GCRYSEXP_FMT_CANON).
 **/
cdk_error_t
cdk_pubkey_to_sexp (cdk_pubkey_t pk, char **sexp, size_t * len)
{
  char *buf;
  size_t sexp_len;
  gcry_sexp_t pk_sexp;
  cdk_error_t rc;

  if (!pk || !sexp)
    return CDK_Inv_Value;

  rc = pubkey_to_sexp (&pk_sexp, pk);
  if (rc)
    return rc;

  sexp_len = gcry_sexp_sprint (pk_sexp, GCRYSEXP_FMT_CANON, NULL, 0);
  if (!sexp_len)
    return CDK_Wrong_Format;

  buf = (char *)cdk_malloc (sexp_len);
  if (!buf)
    {
      gcry_sexp_release (pk_sexp);
      return CDK_Out_Of_Core;
    }
  
  sexp_len = gcry_sexp_sprint (pk_sexp, GCRYSEXP_FMT_CANON, buf, sexp_len);  
  gcry_sexp_release (pk_sexp);
  if (!sexp_len)
    {
      cdk_free (buf);
      return CDK_Wrong_Format;
    }

  if (len)
    *len = sexp_len;
  *sexp = buf;
  return CDK_Success;
}


/**
 * cdk_seckey_to_sexp:
 * @sk: the secret key
 * @sexp: where to store the S-expression
 * @len: the length of sexp
 *
 * Convert a public key to an S-expression. sexp is allocated by this
 * function, but you have to cdk_free() it yourself.  The S-expression
 * is stored in canonical format as used by libgcrypt
 * (GCRYSEXP_FMT_CANON).
 **/
cdk_error_t
cdk_seckey_to_sexp (cdk_pkt_seckey_t sk, char **sexp, size_t * len)
{
  char *buf;
  size_t sexp_len;
  gcry_sexp_t sk_sexp;
  cdk_error_t rc;

  if (!sk || !sexp)
    return CDK_Inv_Value;
  
  rc = seckey_to_sexp (&sk_sexp, sk);
  if (rc)
    return rc;

  sexp_len = gcry_sexp_sprint (sk_sexp, GCRYSEXP_FMT_CANON, NULL, 0);
  if (!sexp_len)
    return CDK_Wrong_Format;

  buf = (char *) cdk_malloc (sexp_len);
  if (!buf)
    {
      gcry_sexp_release (sk_sexp);
      return CDK_Out_Of_Core;
    }

  sexp_len = gcry_sexp_sprint (sk_sexp, GCRYSEXP_FMT_CANON, buf, sexp_len);    
  gcry_sexp_release (sk_sexp);
  if (!sexp_len)
    {
      cdk_free (buf);
      return CDK_Wrong_Format;
    }

  if (len)
    *len = sexp_len;
  *sexp = buf;

  return CDK_Success;
}
