/*

  genpkcs.c

  Author: Mika Kojo <mkojo@ssh.fi>

  Copyright (C) 1997-2000 SSH Communications Security Oy, Espoo, Finland
  All rights reserved.

  Created: Mon Jun  2 18:43:45 1997 [mkojo]

  Interface code for public key cryptosystems.

*/

/*
 * $Id: genpkcs.c,v 1.89 2000/05/28 16:20:36 kivinen Exp $
 * $Log: genpkcs.c,v $
 * $EndLog$
 */

#include "sshincludes.h"
#include "sshcrypt.h"
#include "sshcrypti.h"
#include "sshbuffer.h"
#include "sshbufaux.h"
#include "md5.h"
#include "sha.h"
#include "sshgetput.h"
#include "dlglue.h"
#include "sshencode.h"
#include "namelist.h"
#include "sshrgf.h"

/* Magic numbers used for validy checks in SSH public key
   exported octet formats. */
#define SSH_PK_GROUP_RANDOMIZER_MAGIC 0x4c9356fe
#define SSH_PK_GROUP_MAGIC            0x89578271
#define SSH_PUBLIC_KEY_MAGIC          0x65c8b28a
#define SSH_PRIVATE_KEY_MAGIC         0x3f6ff9eb

/************************************************************************/


/* Definitions of schemes. Those who wish to add more algorithms should
   study style used here. */


/* Table of all supported signature schemes for dl-modp keys. */

const SshPkSignature ssh_dl_modp_signature_schemes[] =
{
#ifdef SSHDIST_CRYPT_DSA
  { "dsa-nist-sha1",
    ssh_dlp_dsa_nist,
    &ssh_rgf_std_sha1_def,
    ssh_dlp_dsa_private_key_max_signature_input_len,
    ssh_dlp_dsa_private_key_max_signature_output_len,
    ssh_dlp_dsa_public_key_verify,
    NULL,
    ssh_dlp_dsa_private_key_sign,
    NULL
  },
#endif /* SSHDIST_CRYPT_DSA */
  { NULL }
};

/* Table of all supported encryption schemes for dl-modp keys. */

const SshPkEncryption ssh_dl_modp_encryption_schemes[] =
{
  { NULL }
};

#ifdef SSHDIST_CRYPT_DH
/* Table of all supported diffie-hellman schemes for dl-modp keys. */

const SshPkDiffieHellman ssh_dl_modp_diffie_hellman_schemes[] =
{
  { "plain",
    NULL,
    ssh_dlp_diffie_hellman_exchange_length,
    ssh_dlp_diffie_hellman_shared_secret_length,
    ssh_dlp_diffie_hellman_generate,
    NULL,
    ssh_dlp_diffie_hellman_final,
    NULL,
    ssh_dlp_unified_diffie_hellman_shared_secret_length,
    ssh_dlp_unified_diffie_hellman_final,
    NULL
  },
  { NULL },
};
#endif /* SSHDIST_CRYPT_DH */



/* Action lists. These lists contain most information about generation of
   private keys, and parameters. */


/* DLP special actions. */
const SshPkAction ssh_pk_dl_modp_actions[] =
{
  /* key type */
  { SSH_PKF_KEY_TYPE, NULL,
    SSH_PK_FLAG_KEY_TYPE | SSH_PK_FLAG_PRIVATE_KEY |
    SSH_PK_FLAG_PUBLIC_KEY | SSH_PK_FLAG_PK_GROUP,
    SSH_PK_SCHEME_NONE, 0, NULL },

  /* Schemes */
  { SSH_PKF_SIGN, "sign",
    SSH_PK_FLAG_SCHEME | SSH_PK_FLAG_PRIVATE_KEY | SSH_PK_FLAG_PUBLIC_KEY,
    SSH_PK_SCHEME_SIGN,
    sizeof(SshPkSignature),
    ssh_dl_modp_signature_schemes, NULL },

#if 0
  { SSH_PKF_ENCRYPT, "encrypt",
    SSH_PK_FLAG_SCHEME | SSH_PK_FLAG_PRIVATE_KEY | SSH_PK_FLAG_PUBLIC_KEY,
    SSH_PK_SCHEME_ENCRYPT,
    sizeof(SshPkEncryption),
    ssh_dl_modp_encryption_schemes, NULL },
#endif

#ifdef SSHDIST_CRYPT_DH
  { SSH_PKF_DH, "dh",
    SSH_PK_FLAG_SCHEME | SSH_PK_FLAG_PRIVATE_KEY | SSH_PK_FLAG_PUBLIC_KEY |
    SSH_PK_FLAG_PK_GROUP,
    SSH_PK_SCHEME_DH,
    sizeof(SshPkDiffieHellman),
    ssh_dl_modp_diffie_hellman_schemes, NULL },
#endif /* SSHDIST_CRYPT_DH */

  /* Handling of keys and parameters. */

  /* prime-p (private_key, public_key, pk_group versions) */
  { SSH_PKF_PRIME_P, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PRIVATE_KEY,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_private_key_put,
    ssh_dlp_action_private_key_get },

  { SSH_PKF_PRIME_P, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PUBLIC_KEY | SSH_PK_FLAG_LIST,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_public_key_put,
    ssh_dlp_action_public_key_get },

  { SSH_PKF_PRIME_P, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PK_GROUP | SSH_PK_FLAG_LIST,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_param_put,
    ssh_dlp_action_param_get },

  /* generator-g (private_key, public_key, pk_group versions) */
  { SSH_PKF_GENERATOR_G, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PRIVATE_KEY,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_private_key_put,
    ssh_dlp_action_private_key_get },

  { SSH_PKF_GENERATOR_G, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PUBLIC_KEY | SSH_PK_FLAG_LIST,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_public_key_put,
    ssh_dlp_action_public_key_get },

  { SSH_PKF_GENERATOR_G, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PK_GROUP | SSH_PK_FLAG_LIST,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_param_put,
    ssh_dlp_action_param_get },

  /* prime-q (private_key, public_key, pk_group versions) */
  { SSH_PKF_PRIME_Q, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PRIVATE_KEY,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_private_key_put,
    ssh_dlp_action_private_key_get },

  { SSH_PKF_PRIME_Q, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PUBLIC_KEY | SSH_PK_FLAG_LIST,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_public_key_put,
    ssh_dlp_action_public_key_get },

  { SSH_PKF_PRIME_Q, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PK_GROUP | SSH_PK_FLAG_LIST,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_param_put,
    ssh_dlp_action_param_get },

  /* secret-x (private_key) */
  { SSH_PKF_SECRET_X, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PRIVATE_KEY,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_private_key_put,
    ssh_dlp_action_private_key_get },

  /* public-y (private_key, public_key) */
  { SSH_PKF_PUBLIC_Y, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PRIVATE_KEY,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_private_key_put,
    ssh_dlp_action_private_key_get },

  { SSH_PKF_PUBLIC_Y, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PUBLIC_KEY | SSH_PK_FLAG_LIST,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_public_key_put,
    ssh_dlp_action_public_key_get },

  /* size (private_key, public_key, pk_group) */
  { SSH_PKF_SIZE, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PRIVATE_KEY,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_private_key_put,
    ssh_dlp_action_private_key_get },

  { SSH_PKF_SIZE, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PUBLIC_KEY | SSH_PK_FLAG_LIST,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_public_key_put,
    ssh_dlp_action_public_key_get },

  { SSH_PKF_SIZE, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PK_GROUP | SSH_PK_FLAG_LIST,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_param_put,
    ssh_dlp_action_param_get },

  /* randomizer entropy (private_key, public_key, pk_group) */
  { SSH_PKF_RANDOMIZER_ENTROPY, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PRIVATE_KEY,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_private_key_put,
    ssh_dlp_action_private_key_get },

  { SSH_PKF_RANDOMIZER_ENTROPY, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PUBLIC_KEY | SSH_PK_FLAG_LIST,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_public_key_put,
    ssh_dlp_action_public_key_get },

  { SSH_PKF_RANDOMIZER_ENTROPY, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PK_GROUP | SSH_PK_FLAG_LIST,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_param_put,
    ssh_dlp_action_param_get },

  /* Predefined group. */
  { SSH_PKF_PREDEFINED_GROUP, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PRIVATE_KEY,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_private_key_put,
    ssh_dlp_action_private_key_get },

  { SSH_PKF_PREDEFINED_GROUP, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PUBLIC_KEY | SSH_PK_FLAG_LIST,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_public_key_put,
    ssh_dlp_action_public_key_get },

  { SSH_PKF_PREDEFINED_GROUP, NULL,
    SSH_PK_FLAG_SPECIAL | SSH_PK_FLAG_PK_GROUP | SSH_PK_FLAG_LIST,
    SSH_PK_SCHEME_NONE, 0,
    NULL,
    ssh_dlp_action_param_put,
    ssh_dlp_action_param_get },

  /* End of list. */
  { SSH_PKF_END }
};



/* more actions to come... */

/* Tables of all supported key types. */


#ifdef SSHDIST_CRYPT_DL
const SshPkType ssh_key_type_dl_modp =
/* Key type for discrete log based systems. */
{
  "dl-modp",
  ssh_pk_dl_modp_actions,

  /* Basic group operations. */
  ssh_dlp_action_init,
  ssh_dlp_param_action_make,
  ssh_dlp_action_free,

  ssh_dlp_param_import,
  ssh_dlp_param_export,
  ssh_dlp_param_free,
  ssh_dlp_param_copy,
  ssh_dlp_param_get_predefined_groups,

  /* Precomputation. */
  ssh_dlp_param_precompute,

  /* Randomizer generation. */
  ssh_dlp_param_count_randomizers,
  ssh_dlp_param_generate_randomizer,
  ssh_dlp_param_export_randomizer,
  ssh_dlp_param_import_randomizer,

  /* Public key operations. */
  ssh_dlp_action_public_key_init,
  ssh_dlp_public_key_action_make,
  ssh_dlp_action_free,

  ssh_dlp_public_key_import,
  ssh_dlp_public_key_export,
  ssh_dlp_public_key_free,
  ssh_dlp_public_key_copy,
  ssh_dlp_public_key_derive_param,

  /* Precomputation. */
  ssh_dlp_public_key_precompute,

  /* Private key operations. */
  ssh_dlp_action_init,
  ssh_dlp_private_key_action_make,
  ssh_dlp_action_free,

  ssh_dlp_private_key_import,
  ssh_dlp_private_key_export,
  ssh_dlp_private_key_free,
  ssh_dlp_private_key_derive_public_key,
  ssh_dlp_private_key_copy,
  ssh_dlp_private_key_derive_param,

  /* Precomputation. */
  ssh_dlp_private_key_precompute
};
#endif /* SSHDIST_CRYPT_DL */

SshPkType const * ssh_pk_type_slots[SSH_PK_TYPE_MAX_SLOTS] =
{
  &ssh_key_type_dl_modp,

  NULL, /* ... continued to the end. */
};

/************************************************************************/

SshCryptoStatus
ssh_pk_provider_register(const SshPkType *type)
{
  int i;

  if (type == NULL)
    return SSH_CRYPTO_OPERATION_FAILED;

  for (i = 0; i < SSH_PK_TYPE_MAX_SLOTS; i++)
    {
      if (ssh_pk_type_slots[i] == NULL)
        {
          /* Empty slot detected. */
          ssh_pk_type_slots[i] = type;
          return SSH_CRYPTO_OK;
        }
    }
  return SSH_CRYPTO_PROVIDER_SLOTS_EXHAUSTED;
}


/* Next: genpkcs.c functions ;) */

/* Find action with FLAG_SCHEME set on and matching given identifier. This
   is used when parsing names. */

const SshPkAction *ssh_pk_find_scheme_action(const SshPkAction *list,
                                             const char *identifier,
                                             const SshPkFlag given_flags)
{
  unsigned int i;
  SshPkFlag flags = given_flags | SSH_PK_FLAG_SCHEME;

  for (i = 0; list[i].format != SSH_PKF_END; i++)
    {
      if ((list[i].flags & flags) == flags)
        {
          /* Check for optimization. */
          if (strcmp(list[i].scheme_class, identifier) == 0)
            return &list[i];
        }
    }
  /* Failed to find a match. */
  return NULL;
}

/* Search from action list an entry that has atleast 'flags' on. */

const SshPkAction *ssh_pk_find_action(SshPkFormat format,
                                      const SshPkAction *list,
                                      const SshPkFlag flags)
{
  unsigned int i;
  Boolean prev = FALSE;

  for (i = 0; list[i].format != SSH_PKF_END; i++)
    {
      /* Check for optimization. */
      if (!((list[i].flags & SSH_PK_FLAG_LIST) && prev))
        {
          if (list[i].format == format)
            prev = TRUE;
          else
            continue;
        }

      /* Check whether flags match. */
      if ((list[i].flags & flags) == flags)
        {
          /* Found a correct match (because they are assumed to be unique
             this must be correct). */

          return &list[i];
        }
    }
  /* Failed to find a match. */
  return NULL;
}

/* Generic search for tables where the first element is const char *.
   How else can one do this? */

void *ssh_pk_find_generic(const char *name, const void *list, size_t msize)
{
  const unsigned char *buf = list;
  const char *buf_name;
  unsigned int i;

  /* buf[i] points to start of a structure (which size is msize), buf_name
     is set to the const char * from start of the buf[i]. */
  for (i = 0; (buf_name = *((const char **)(buf + i))) ; i += msize)
    {
      if (strcmp(buf_name, name) == 0)
        {
          return (void *)(buf + i);
        }
    }
  return NULL;
}

/* Advance generically in scheme tables. Returns the first const char *
   pointer from scheme table (i.e. the name). */
const char *ssh_pk_next_generic(const void **list, size_t msize)
{
  const char *name = *((const char **)*list);
  *list = (void *)((unsigned char *)*list + msize);
  return name;
}

/* Routines for getting scheme names from private keys and public keys.
   No other information is reasonable to expect to be gotten from
   schemes, although one could think getting descriptions etc...
   */

SshCryptoStatus ssh_private_key_get_scheme_name(SshPrivateKey key,
                                                const char **name,
                                                SshPkSchemeFlag flag)
{
  switch (flag)
    {
    case SSH_PK_SCHEME_SIGN:
      if (key->signature)
        *name = key->signature->name;
      else
        *name = NULL;
      break;
    case SSH_PK_SCHEME_ENCRYPT:
      if (key->encryption)
        *name = key->encryption->name;
      else
        *name = NULL;
      break;
    case SSH_PK_SCHEME_DH:
      if (key->diffie_hellman)
        *name = key->diffie_hellman->name;
      else
        *name = NULL;
      break;
    default:
      return SSH_CRYPTO_LIBRARY_CORRUPTED;
      break;
    }
  return SSH_CRYPTO_OK;
}

SshCryptoStatus ssh_public_key_get_scheme_name(SshPublicKey key,
                                               const char **name,
                                               SshPkSchemeFlag flag)
{
  switch (flag)
    {
    case SSH_PK_SCHEME_SIGN:
      if (key->signature)
        *name = key->signature->name;
      else
        *name = NULL;
      break;
    case SSH_PK_SCHEME_ENCRYPT:
      if (key->encryption)
        *name = key->encryption->name;
      else
        *name = NULL;
      break;
    case SSH_PK_SCHEME_DH:
      if (key->diffie_hellman)
        *name = key->diffie_hellman->name;
      else
        *name = NULL;
      break;
    default:
      return SSH_CRYPTO_LIBRARY_CORRUPTED;
      break;
    }
  return SSH_CRYPTO_OK;

}

/* Helpful function. */
SshNameNode ssh_pk_add_nnode(SshNameTree tree, SshNameNode node,
                             const char *scheme_type,
                             const char *scheme_identifier,
                             Boolean *flag)
{
  SshNameNode temp;

  if (*flag)
    {
      temp = ssh_ntree_add_next(tree, node,
                                scheme_type);
    }
  else
    {
      temp = ssh_ntree_add_child(tree, node,
                                 scheme_type);
      *flag = TRUE;
    }
  ssh_ntree_add_child(tree, temp,
                      scheme_identifier);
  return temp;
}

/* Generate the full name of a particular private key. */
char *
ssh_private_key_name(SshPrivateKey key)
{
  SshNameTree tree;
  SshNameNode node;
  char *tmp;
  Boolean flag = FALSE;

  ssh_ntree_allocate(&tree);

  node = ssh_ntree_add_child(tree, NULL,
                             key->type->name);
  if (key->signature)
    node = ssh_pk_add_nnode(tree, node, "sign", key->signature->name, &flag);
  if (key->encryption)
    node = ssh_pk_add_nnode(tree, node, "encrypt", key->encryption->name,
                            &flag);
  if (key->diffie_hellman)
    node = ssh_pk_add_nnode(tree, node, "dh", key->diffie_hellman->name,
                            &flag);
  ssh_ntree_generate_string(tree, &tmp);
  ssh_ntree_free(tree);

  return tmp;
}

/* Generate the full name of a particular public key. */
char *
ssh_public_key_name(SshPublicKey key)
{
  SshNameTree tree;
  SshNameNode node;
  char *tmp;
  Boolean flag = FALSE;

  ssh_ntree_allocate(&tree);

  node = ssh_ntree_add_child(tree, NULL,
                             key->type->name);
  if (key->signature)
    node = ssh_pk_add_nnode(tree, node, "sign", key->signature->name, &flag);
  if (key->encryption)
    node = ssh_pk_add_nnode(tree, node, "encrypt", key->encryption->name,
                            &flag);
  if (key->diffie_hellman)
    node = ssh_pk_add_nnode(tree, node, "dh", key->diffie_hellman->name,
                            &flag);
  ssh_ntree_generate_string(tree, &tmp);
  ssh_ntree_free(tree);

  return tmp;
}

SshCryptoStatus ssh_private_key_set_scheme(SshPrivateKey key,
                                           void *scheme,
                                           SshPkSchemeFlag flag)
{
  /* Set the corresponding scheme. */
  switch (flag)
    {
    case SSH_PK_SCHEME_SIGN:
      key->signature = scheme;
      break;
    case SSH_PK_SCHEME_ENCRYPT:
      key->encryption = scheme;
      break;
    case SSH_PK_SCHEME_DH:
      key->diffie_hellman = scheme;
      break;
    default:
      return SSH_CRYPTO_LIBRARY_CORRUPTED;
      break;
    }
  return SSH_CRYPTO_OK;
}

/* Set scheme to given void pointer. These routines should be used with
   caution because no checking is done to verify that given pointer is
   valid. */

SshCryptoStatus ssh_public_key_set_scheme(SshPublicKey key,
                                          void *scheme,
                                          SshPkSchemeFlag flag)
{
  /* Set the corresponding scheme. */
  switch (flag)
    {
    case SSH_PK_SCHEME_SIGN:
      key->signature = scheme;
      break;
    case SSH_PK_SCHEME_ENCRYPT:
      key->encryption = scheme;
      break;
    case SSH_PK_SCHEME_DH:
      key->diffie_hellman = scheme;
      break;
    default:
      return SSH_CRYPTO_LIBRARY_CORRUPTED;
      break;
    }
  return SSH_CRYPTO_OK;
}

SshCryptoStatus ssh_pk_group_set_scheme(SshPkGroup group,
                                        void *scheme,
                                        SshPkSchemeFlag flag)
{
  switch (flag)
    {
    case SSH_PK_SCHEME_SIGN:
    case SSH_PK_SCHEME_ENCRYPT:
      /* XXX case SSH_PK_SCHEME_UDH: */
      /* Lets just ignore these, not considered errorneous. Main reason for
         this is the fact that some of these might want to add some
         information to the action_make context and we don't want to
         restrict that. */
      break;
    case SSH_PK_SCHEME_DH:
      group->diffie_hellman = scheme;
      break;
    default:
      return SSH_CRYPTO_LIBRARY_CORRUPTED;
      break;
    }
  return SSH_CRYPTO_OK;
}

/* Generate the full name of a particular pk group. */
char *
ssh_pk_group_name(SshPkGroup group)
{
  SshNameTree tree;
  SshNameNode node;
  char *tmp;

  ssh_ntree_allocate(&tree);

  node = ssh_ntree_add_child(tree, NULL,
                             group->type->name);
  if (group->diffie_hellman)
    {
      node = ssh_ntree_add_next(tree, node,
                                "dh");
      ssh_ntree_add_child(tree, node,
                          group->diffie_hellman->name);
    }
  ssh_ntree_generate_string(tree, &tmp);
  ssh_ntree_free(tree);

  return tmp;
}

SshCryptoStatus ssh_pk_group_get_scheme_name(SshPkGroup group,
                                             const char **name,
                                             SshPkSchemeFlag flag)
{
  switch (flag)
    {
    case SSH_PK_SCHEME_DH:
      *name = group->diffie_hellman->name;
      break;
    default:
      return SSH_CRYPTO_LIBRARY_CORRUPTED;
      break;
    }
  return SSH_CRYPTO_OK;
}

/* Function to retrieve a comma separated list of supported predefined
   groups for this particular key type. */

char *
ssh_public_key_get_predefined_groups(const char *key_type)
{
  SshNameTree tree;
  SshNameNode node;
  SshNameTreeStatus nstat;
  const char *tmp;
  unsigned int i;

  /* Generate a name tree from key type. */
  ssh_ntree_allocate(&tree);
  nstat = ssh_ntree_parse(key_type, tree);
  if (nstat != SSH_NTREE_OK)
    {
      ssh_ntree_free(tree);
      return NULL;
    }
  node = ssh_ntree_get_root(tree);
  if (node == NULL)
    {
      ssh_ntree_free(tree);
      return NULL;
    }
  tmp = ssh_nnode_get_identifier(node);

  /* Free the allocated tree now; we are not going to need it later. */
  ssh_ntree_free(tree);

  for (i = 0; ssh_pk_type_slots[i]->name; i++)
    {
      if (strcmp(ssh_pk_type_slots[i]->name, tmp) == 0)
        {
          if (ssh_pk_type_slots[i]->pk_group_get_predefined_groups != NULL)
            return (*ssh_pk_type_slots[i]->pk_group_get_predefined_groups)();
        }
    }
  return NULL;
}

/* This macro will reprocess newly opened ap with instructions given
   at fmt. Each character at fmt indicates the size which must be
   consumed from the ap. E.g. fmt 'isp' would consume one integer, one
   short integer and one pointer from the ap. */
#define PROCESS(ap, fmt)                        \
do {                                            \
  int _i = 0;                                   \
  while ((fmt)[_i] != '\000')                   \
    {                                           \
      switch ((fmt)[_i])                        \
        {                                       \
        case 'b': (void)va_arg((ap), Boolean); break;   \
        case 'c': (void)va_arg((ap), char); break;      \
        case 's': (void)va_arg((ap), short); break;     \
        case 'i': (void)va_arg((ap), int); break;       \
        case 'l': (void)va_arg((ap), long); break;      \
        case 'p': (void)va_arg((ap), void *); break;    \
        }                                       \
      _i++;                                     \
    } \
} while (0)

/* Parameter functions named here as ssh pk group (standing for
   ssh public key group). */

SshCryptoStatus
ssh_pk_group_generate(SshPkGroup *group,
                      const char *group_type, ...)
{
  SshCryptoStatus status;
  unsigned int i;
  const SshPkAction *action;
  SshPkGroup pk_group;
  void *context;
  void *scheme;
  SshPkFormat format;
  SshNameTree tree;
  SshNameNode node, child;
  SshNameTreeStatus nstat;
  const char *name;
  char *tmp, *r, consumed[128];
  va_list ap;

  /* Parse given group type. */
  ssh_ntree_allocate(&tree);
  nstat = ssh_ntree_parse(group_type, tree);
  if (nstat != SSH_NTREE_OK)
    {
      ssh_ntree_free(tree);
      return SSH_CRYPTO_UNKNOWN_GROUP_TYPE;
    }
  node = ssh_ntree_get_root(tree);
  if (node == NULL)
    {
      ssh_ntree_free(tree);
      return SSH_CRYPTO_UNKNOWN_GROUP_TYPE;
    }
  tmp = ssh_nnode_get_identifier(node);

  for (i = 0; ssh_pk_type_slots[i] != NULL && ssh_pk_type_slots[i]->name; i++)
    {
      if (strcmp(ssh_pk_type_slots[i]->name, tmp) != 0)
        continue;

      /* Type matches i.e. we've found our key type, so continue with
         finding schemes and parameters. */

      /* Free allocated name. */
      ssh_xfree(tmp);
      node = ssh_nnode_get_child(node);

      /* Allocate private key context. */
      pk_group = ssh_xmalloc(sizeof(*pk_group));
      pk_group->type = ssh_pk_type_slots[i];

      /* Clear pointers. */
      pk_group->diffie_hellman = NULL;

      /* Initialize actions, and verify that context was allocated. */
      context = (*pk_group->type->pk_group_action_init)();
      if (context == NULL)
        {
          ssh_xfree(pk_group);
          return SSH_CRYPTO_OPERATION_FAILED;
        }

      /* Run through all preselected schemes in the group_type. */
      status = SSH_CRYPTO_OK;
      while (node)
        {
          tmp = ssh_nnode_get_identifier(node);
          action = ssh_pk_find_scheme_action(pk_group->type->action_list,
                                             tmp,
                                             SSH_PK_FLAG_PK_GROUP);
          ssh_xfree(tmp);
          if (!action)
            {
              status = SSH_CRYPTO_SCHEME_UNKNOWN;
              break;
            }
          child = ssh_nnode_get_child(node);
          if (child == NULL)
            /* We are not yet confident that there does not exists
               a method of this name. Thus because for some schemes
               it is easier to just write the scheme class, we
               try to match for a fixed name. */
            tmp = SSH_PK_USUAL_NAME;
          else
            tmp = ssh_nnode_get_identifier(child);

          /* Find the scheme of that name. */
          scheme = ssh_pk_find_generic(tmp, action->type, action->type_size);
          if (child)
            /* Free if there is a need for that. */
            ssh_xfree(tmp);

          if (scheme == NULL)
            {
              status = SSH_CRYPTO_SCHEME_UNKNOWN;
              break;
            }

          /* Call action_scheme if not set to NULL. */
          if (((SshPkGen *)scheme)->action_scheme != NULL)
            (*((SshPkGen *)scheme)->action_scheme)(context);

          /* Set the corresponding scheme to the group. */
          status = ssh_pk_group_set_scheme(pk_group, scheme,
                                           action->scheme_flag);

          if (status != SSH_CRYPTO_OK)
            break;

          /* Move to the next scheme. */
          node = ssh_nnode_get_next(node);
        }

      ssh_ntree_free(tree);
      if (status != SSH_CRYPTO_OK)
        {
          (*pk_group->type->pk_group_action_free)(context);
          ssh_xfree(pk_group);
          return status;
        }

      /* Start reading the vararg list. */
      consumed[0] = '\000';
      while (TRUE)
        {
          va_start(ap, group_type);
          PROCESS(ap, consumed);

          format = va_arg(ap, SshPkFormat);
          strcat(consumed, "i");
          if (format == SSH_PKF_END)
            break;

          /* Search name from command lists. */
          action = ssh_pk_find_action(format,
                                      pk_group->type->action_list,
                                      SSH_PK_FLAG_PK_GROUP);
          if (!action)
            {
              /* Free the action context. */
              (*pk_group->type->pk_group_action_free)(context);
              ssh_xfree(pk_group);
              va_end(ap);
              return SSH_CRYPTO_UNSUPPORTED_IDENTIFIER;
            }

          /* Supported only scheme selection and special operations. */
          switch (action->flags & (SSH_PK_FLAG_SCHEME | SSH_PK_FLAG_SPECIAL))
            {
            case SSH_PK_FLAG_SCHEME:
              name = va_arg(ap, const char *);
              strcat(consumed, "p");
              scheme = ssh_pk_find_generic(name, action->type,
                                           action->type_size);
              if (scheme == NULL)
                {
                  (*pk_group->type->pk_group_action_free)(context);
                  ssh_xfree(pk_group);
                  va_end(ap);
                  return SSH_CRYPTO_SCHEME_UNKNOWN;
                }

              /* Call action_scheme if not set to NULL. */
              if (((SshPkGen *)scheme)->action_scheme != NULL)
                (*((SshPkGen *)scheme)->action_scheme)(context);

              /* Set the corresponding scheme to the group. */
              status = ssh_pk_group_set_scheme(pk_group, scheme,
                                               action->scheme_flag);

              if (status != SSH_CRYPTO_OK)
                {
                  (*pk_group->type->pk_group_action_free)(context);
                  ssh_xfree(pk_group);
                  va_end(ap);
                  return status;
                }
              break;

            case SSH_PK_FLAG_SPECIAL:
              /* Assume no wrappings. */
              if (action->flags & SSH_PK_FLAG_WRAPPED)
                {
                  if (action->action_put)
                    ssh_fatal("ssh_pk_group_generate: cannot wrap.");
                  va_end(ap);
                  (*pk_group->type->pk_group_action_free)(context);
                  ssh_xfree(pk_group);
                  return SSH_CRYPTO_UNSUPPORTED_IDENTIFIER;
                }

              r = (*action->action_put)(context, ap, NULL, format);

              if (r == NULL)
                {
                  (*pk_group->type->pk_group_action_free)(context);
                  ssh_xfree(pk_group);
                  va_end(ap);
                  return SSH_CRYPTO_LIBRARY_CORRUPTED;
                }
              else
                strcat(consumed, r);
              break;
            default:
              ssh_fatal("ssh_pk_group_generate: internal error.");
              break;
            }
          va_end(ap);
        }

      /* Make the key and remove context. (One could incorporate making
         and freeing, however this way things seem to work also). */
      pk_group->context =
        (*pk_group->type->pk_group_action_make)(context);
      (*pk_group->type->pk_group_action_free)(context);

      /* Quit unhappily. */
      if (pk_group->context == NULL)
        {
          ssh_xfree(pk_group);
          va_end(ap);
          return SSH_CRYPTO_OPERATION_FAILED;
        }

      /* Quit happily. */
      *group = pk_group;
      va_end(ap);

      return SSH_CRYPTO_OK;

    }

  ssh_ntree_free(tree);
  va_end(ap);

  return SSH_CRYPTO_UNKNOWN_GROUP_TYPE;
}

void
ssh_pk_group_free(SshPkGroup group)
{
  if (group == NULL || group->context == NULL)
    ssh_fatal("ssh_pk_group_free: undefined group.");
  if (group->type->pk_group_free)
    (*group->type->pk_group_free)(group->context);
  group->context = NULL;
  ssh_xfree(group);
}

SshCryptoStatus
ssh_pk_group_select_scheme(SshPkGroup group, ...)
{
  SshCryptoStatus status;
  const SshPkAction *action;
  void *scheme;
  SshPkFormat format;
  const char *name;
  va_list ap;

  if (group->type == NULL)
    return SSH_CRYPTO_KEY_UNINITIALIZED;

  va_start(ap, group);

  while ((format = va_arg(ap, SshPkFormat)) != SSH_PKF_END)
    {
      action = ssh_pk_find_action(format, group->type->action_list,
                                  SSH_PK_FLAG_SCHEME |
                                  SSH_PK_FLAG_PK_GROUP);
      if (!action)
        {
          va_end(ap);
          return SSH_CRYPTO_UNSUPPORTED_IDENTIFIER;
        }

      /* Find the new scheme. */
      name = va_arg(ap, const char *);
      scheme = ssh_pk_find_generic(name, action->type,
                                   action->type_size);
      /* Check that scheme exists. */
      if (scheme == NULL)
        {
          va_end(ap);
          return SSH_CRYPTO_SCHEME_UNKNOWN;
        }

      status = ssh_pk_group_set_scheme(group, scheme, action->scheme_flag);
      if (status != SSH_CRYPTO_OK)
        {
          va_end(ap);
          return status;
        }
    }
  va_end(ap);
  return SSH_CRYPTO_OK;
}

SshCryptoStatus
ssh_pk_group_get_info(SshPkGroup group, ...)
{
  SshCryptoStatus status;
  const SshPkAction *action;
  SshPkFormat format;
  const char **name_ptr;
  char *r, consumed[128];
  va_list ap;

  consumed[0] = '\000';
  while (TRUE)
    {
      va_start(ap, group);
      PROCESS(ap, consumed);

      format = va_arg(ap, SshPkFormat);
      strcat(consumed, "i");
      if (format == SSH_PKF_END)
        break;

      /* Seek for the action. */
      action = ssh_pk_find_action(format,
                                  group->type->action_list,
                                  SSH_PK_FLAG_PK_GROUP);
      if (!action)
        {
          va_end(ap);
          return SSH_CRYPTO_UNSUPPORTED_IDENTIFIER;
        }

      switch (action->flags & (SSH_PK_FLAG_SCHEME | SSH_PK_FLAG_SPECIAL |
                               SSH_PK_FLAG_KEY_TYPE))
        {
        case SSH_PK_FLAG_KEY_TYPE:
          name_ptr = va_arg(ap, const char **);
          strcat(consumed, "p");
          *name_ptr = strchr(group->type->name, ':');
          if (*name_ptr)
            (*name_ptr)++;
          else
            *name_ptr = group->type->name;
          break;

        case SSH_PK_FLAG_SCHEME:
          name_ptr = va_arg(ap, const char **);
          strcat(consumed, "p");
          status = ssh_pk_group_get_scheme_name(group,
                                                name_ptr,
                                                action->scheme_flag);
          if (status != SSH_CRYPTO_OK)
            {
              va_end(ap);
              return status;
            }
          break;

        case SSH_PK_FLAG_SPECIAL:
          if (action->flags & SSH_PK_FLAG_WRAPPED)
            {
              if (action->action_get)
                ssh_fatal("ssh_pk_group_get_info: cannot wrap.");
              va_end(ap);
              return SSH_CRYPTO_UNSUPPORTED_IDENTIFIER;
            }

          r = (*action->action_get)(group->context, ap, NULL, format);
          if (r == NULL)
            {
              va_end(ap);
              return SSH_CRYPTO_LIBRARY_CORRUPTED;
            }
          strcat(consumed, r);
          break;

        default:
          ssh_fatal("ssh_pk_group_get_info: internal error.");
          break;
        }
      va_end(ap);
    }

  va_end(ap);
  return SSH_CRYPTO_OK;
}

SshCryptoStatus
ssh_pk_group_precompute(SshPkGroup group)
{
  if (group == NULL)
    return SSH_CRYPTO_OPERATION_FAILED;
  if (group->type->pk_group_precompute)
    (*group->type->pk_group_precompute)(group->context);
  return SSH_CRYPTO_OK;
}

SshCryptoStatus
ssh_public_key_precompute(SshPublicKey key)
{
  if (key == NULL)
    return SSH_CRYPTO_OPERATION_FAILED;
  if (key->type->public_key_precompute)
    (*key->type->public_key_precompute)(key->context);
  return SSH_CRYPTO_OK;
}

SshCryptoStatus
ssh_private_key_precompute(SshPrivateKey key)
{
  if (key == NULL)
    return SSH_CRYPTO_OPERATION_FAILED;
  if (key->type->private_key_precompute)
    (*key->type->private_key_precompute)(key->context);
  return SSH_CRYPTO_OK;
}

SshCryptoStatus
ssh_pk_group_generate_randomizer(SshPkGroup group)
{
  if (group == NULL)
    return SSH_CRYPTO_OPERATION_FAILED;

  if (group->type->pk_group_generate_randomizer)
    (*group->type->pk_group_generate_randomizer)(group->context);
  return SSH_CRYPTO_OK;
}

unsigned int
ssh_pk_group_count_randomizers(SshPkGroup group)
{
  if (group == NULL)
    return SSH_CRYPTO_OPERATION_FAILED;
  if (group->type->pk_group_count_randomizers)
    return (*group->type->pk_group_count_randomizers)(group->context);
  return 0;
}

/* Returns atleast one randomizer if buffer is long enough, else some
   appropriate error message.

   Output buffer contains magic cookie which is computed either with
   a hash function or some other means. Other very suitable possibility
   is to add the parameter information into the buffer.
   */

SshCryptoStatus
ssh_pk_group_export_randomizers(SshPkGroup group,
                                unsigned char **buf,
                                size_t *buf_length)
{
  SshBufferStruct buffer;
  unsigned char *tmp_buffer;
  size_t tmp_buf_len;

  if (group->type->pk_group_export_randomizer == NULL)
    return SSH_CRYPTO_UNSUPPORTED;

  ssh_buffer_init(&buffer);

  ssh_encode_buffer(&buffer,
                    SSH_FORMAT_UINT32,
                    (SshUInt32) SSH_PK_GROUP_RANDOMIZER_MAGIC,
                    SSH_FORMAT_UINT32, (SshUInt32) 0,
                    SSH_FORMAT_END);

  while (1)
    {
      (*group->type->pk_group_export_randomizer)(group,
                                                 &tmp_buffer,
                                                 &tmp_buf_len);
      if (tmp_buffer == NULL)
        break;

      ssh_encode_buffer(&buffer,
                        SSH_FORMAT_UINT32_STR, tmp_buffer, tmp_buf_len,
                        SSH_FORMAT_END);
    }

  *buf_length = ssh_buffer_len(&buffer);
  *buf = ssh_xmalloc(*buf_length);
  memcpy(*buf, ssh_buffer_ptr(&buffer), *buf_length);

  /* Set total length. */
  SSH_PUT_32BIT((*buf) + 4, *buf_length);

  ssh_buffer_uninit(&buffer);

  return SSH_CRYPTO_OK;
}

/* Add randomizers to randomizer list. */
SshCryptoStatus
ssh_pk_group_import_randomizers(SshPkGroup group,
                                unsigned char *buf,
                                size_t buf_length)
{
  SshInt32 total_length, length;
  SshUInt32 magic;
  int buf_consumed = 0, len_decoded = 0;

  if (group->type->pk_group_import_randomizer == NULL)
    return SSH_CRYPTO_UNSUPPORTED;

  if ((buf_consumed = ssh_decode_array(buf, buf_length,
                                       SSH_FORMAT_UINT32, &magic,
                                       SSH_FORMAT_UINT32, &total_length,
                                       SSH_FORMAT_END)) == 0)
    return SSH_CRYPTO_OPERATION_FAILED;

  if (magic != SSH_PK_GROUP_RANDOMIZER_MAGIC)
    {
      return SSH_CRYPTO_OPERATION_FAILED;
    }

  total_length -= 8;

  while (total_length > 0)
    {
      if ((len_decoded =
           ssh_decode_array(buf + buf_consumed, buf_length - buf_consumed,
                            SSH_FORMAT_UINT32, &length,
                            SSH_FORMAT_END)) == 0)
        {
          return SSH_CRYPTO_OPERATION_FAILED;
        }

      buf_consumed += len_decoded;

      if ((*group->type->pk_group_import_randomizer)(group->context,
                                                     buf + buf_consumed,
                                                     length) == FALSE)
        {
          return SSH_CRYPTO_OPERATION_FAILED;
        }

      buf_consumed += length;
      total_length -= (length + 4);
    }

  return SSH_CRYPTO_OK;
}

/* Pk group format:

   uint32   magic
   uint32   total length
   uint32   group type name length n
   n bytes  group type name (contains also information on schemes)

   uint32   type specific part length n
   n bytes  type specific part
   */

SshCryptoStatus
ssh_pk_group_export(SshPkGroup group, unsigned char **buf,
                    size_t *buf_length)
{
  SshBufferStruct buffer;
  unsigned char *tmp_buf;
  size_t tmp_buf_len;
  char *name;

  if (group->type->pk_group_export == NULL)
    return SSH_CRYPTO_UNSUPPORTED;

  ssh_buffer_init(&buffer);

  name = ssh_pk_group_name(group);

  ssh_encode_buffer(&buffer,
                    SSH_FORMAT_UINT32, (SshUInt32) SSH_PK_GROUP_MAGIC,
                    SSH_FORMAT_UINT32, (SshUInt32) 0,
                    SSH_FORMAT_UINT32_STR, name, strlen(name),
                    SSH_FORMAT_END);

  ssh_xfree(name);

  if ((*group->type->pk_group_export)(group->context,
                                      &tmp_buf, &tmp_buf_len) == FALSE)
    {
      ssh_buffer_uninit(&buffer);
      return SSH_CRYPTO_OPERATION_FAILED;
    }

  ssh_encode_buffer(&buffer,
                    SSH_FORMAT_UINT32_STR, tmp_buf, tmp_buf_len,
                    SSH_FORMAT_END);

  ssh_xfree(tmp_buf);

  *buf_length = ssh_buffer_len(&buffer);
  *buf = ssh_xmalloc(*buf_length);
  memcpy(*buf, ssh_buffer_ptr(&buffer), *buf_length);

  /* Set total length. */
  SSH_PUT_32BIT((*buf) + 4, *buf_length);

  ssh_buffer_uninit(&buffer);

  return SSH_CRYPTO_OK;
}

SshCryptoStatus
ssh_pk_group_import(unsigned char *buf,
                    size_t buf_length,
                    SshPkGroup *group)
{
  SshBufferStruct buffer;
  SshUInt32 magic, total_length, length;
  size_t key_type_len;
  char *key_type, *name;
  const SshPkAction *action;
  void *scheme;
  SshPkGroup pk_group;
  SshNameTree tree;
  SshNameNode node;
  SshCryptoStatus status;
  unsigned int i;

  ssh_buffer_init(&buffer);
  ssh_buffer_append(&buffer, buf, buf_length);

  if (ssh_decode_buffer(&buffer,
                        SSH_FORMAT_UINT32, &magic,
                        SSH_FORMAT_UINT32, &total_length,
                        SSH_FORMAT_UINT32_STR,
                        &key_type, &key_type_len,
                        SSH_FORMAT_END) == 0)
    {
      ssh_buffer_uninit(&buffer);
      return SSH_CRYPTO_OPERATION_FAILED;
    }

  if (magic != SSH_PK_GROUP_MAGIC)
    {
      ssh_buffer_uninit(&buffer);
      return SSH_CRYPTO_OPERATION_FAILED;
    }

  ssh_ntree_allocate(&tree);
  ssh_ntree_parse(key_type, tree);
  node = ssh_ntree_get_root(tree);
  name = ssh_nnode_get_identifier(node);

  /* Find correct key type. We could use action lists now, but for
     simplicity don't. However with some other formats action lists and
     ssh_pk_group_generate might allow simpler implementation. */

  for (i = 0, pk_group = NULL; ssh_pk_type_slots[i] != NULL &&
         ssh_pk_type_slots[i]->name; i++)
    {
      if (strcmp(ssh_pk_type_slots[i]->name, name) == 0)
        {
          /* Allocate */
          pk_group = ssh_xmalloc(sizeof(*pk_group));
          pk_group->type = ssh_pk_type_slots[i];

          /* Initialize. */
          pk_group->diffie_hellman = NULL;

          break;
        }
    }
  ssh_xfree(name);
  node = ssh_nnode_get_child(node);

  if (pk_group == NULL)
    {
      ssh_ntree_free(tree);
      ssh_buffer_uninit(&buffer);
      return SSH_CRYPTO_UNKNOWN_KEY_TYPE;
    }

  status = SSH_CRYPTO_OK;
  /* Check the name tree for schemes. */
  while (node)
    {
      name = ssh_nnode_get_identifier(node);
      action = ssh_pk_find_scheme_action(pk_group->type->action_list,
                                         name,
                                         SSH_PK_FLAG_PK_GROUP);
      ssh_xfree(name);
      if (!action)
        {
          status = SSH_CRYPTO_SCHEME_UNKNOWN;
          break;
        }
      name = ssh_nnode_get_identifier(node);
      scheme = ssh_pk_find_generic(name, action->type,
                                   action->type_size);
      ssh_xfree(name);
      if (scheme == NULL)
        {
          status = SSH_CRYPTO_SCHEME_UNKNOWN;
          break;
        }
      status = ssh_pk_group_set_scheme(pk_group, scheme,
                                       action->scheme_flag);
      if (status != SSH_CRYPTO_OK)
        {
          break;
        }
      node = ssh_nnode_get_parent(node);
      if (node)
        node = ssh_nnode_get_next(node);
    }

  ssh_ntree_free(tree);
  if (status != SSH_CRYPTO_OK)
    {
      ssh_xfree(pk_group);
      ssh_buffer_uninit(&buffer);
      return status;
    }

  /* Read the final part and generate internal context. */
  if (ssh_decode_buffer(&buffer,
                        SSH_FORMAT_UINT32, &length,
                        SSH_FORMAT_END) == 0 ||
      length > ssh_buffer_len(&buffer))
    {
      ssh_buffer_uninit(&buffer);
      ssh_xfree(pk_group);
      return SSH_CRYPTO_OPERATION_FAILED;
    }

  if ((*pk_group->type->pk_group_import)(ssh_buffer_ptr(&buffer),
                                         length,
                                         &pk_group->context) == FALSE)
    {
      ssh_buffer_uninit(&buffer);
      ssh_xfree(pk_group);
      return SSH_CRYPTO_OPERATION_FAILED;
    }

  ssh_buffer_consume(&buffer, length);

  /* Set for output. */
  *group = pk_group;

  ssh_buffer_uninit(&buffer);

  return SSH_CRYPTO_OK;
}

/* Private key functions. */

/* Private key generation and initialization. this interface allows several
   operations. Most noteworthy uses are:

     initialization of keys (with given values)
     generation of keys (with generated values)

   also

     selection of used schemes (although this can be done also with
       ssh_private_key_select_scheme(...) interface, which is probably
       more suitable).

   We use vararg lists, although not easy to debug they make this interface
   very flexible (atleast considering these few algorithm families).

   */

SshCryptoStatus
ssh_private_key_generate(SshPrivateKey *key,
                         const char *key_type, ...)
{
  SshCryptoStatus status = SSH_CRYPTO_UNKNOWN_KEY_TYPE;
  SshPrivateKey private_key;
  SshPkGroup group;
  const SshPkAction *action;
  SshPkFormat format;
  SshNameTree tree;
  SshNameNode node, child;
  SshNameTreeStatus nstat;
  const char *name;
  void *wrapper;
  char *tmp, *r, consumed[128];
  void *scheme;
  void *context;
  unsigned int i;
  va_list ap;

  /* Parse given group type. */
  ssh_ntree_allocate(&tree);
  nstat = ssh_ntree_parse(key_type, tree);
  if (nstat != SSH_NTREE_OK)
    {
      ssh_ntree_free(tree);
      return SSH_CRYPTO_UNKNOWN_KEY_TYPE;
    }
  node = ssh_ntree_get_root(tree);
  if (node == NULL)
    {
      ssh_ntree_free(tree);
      return SSH_CRYPTO_UNKNOWN_KEY_TYPE;
    }
  tmp = ssh_nnode_get_identifier(node);

  for (i = 0; ssh_pk_type_slots[i] != NULL && ssh_pk_type_slots[i]->name; i++)
    {
      if (strcmp(ssh_pk_type_slots[i]->name, tmp) != 0)
        continue;

      /* Type matches i.e. we've found our key type, so continue with
         finding schemes and parameters. */

      ssh_xfree(tmp);
      node = ssh_nnode_get_child(node);

      /* Allocate private key context. */
      private_key = ssh_xmalloc(sizeof(*private_key));
      private_key->type = ssh_pk_type_slots[i];

      /* Clear pointers. */
      private_key->signature = NULL;
      private_key->encryption = NULL;
      private_key->diffie_hellman = NULL;

      /* Initialize actions, and verify that context was allocated. */
      context = (*private_key->type->private_key_action_init)();
      if (context == NULL)
        {
          ssh_xfree(private_key);
          va_end(ap);
          return SSH_CRYPTO_OPERATION_FAILED;
        }

      status = SSH_CRYPTO_OK;
      /* Run through all preselected schemes in the group_type. */
      while (node)
        {
          tmp = ssh_nnode_get_identifier(node);
          action =
            ssh_pk_find_scheme_action(private_key->type->action_list,
                                      tmp,
                                      SSH_PK_FLAG_PRIVATE_KEY);
          ssh_xfree(tmp);
          if (!action)
            {
              status = SSH_CRYPTO_SCHEME_UNKNOWN;
              break;
            }
          child = ssh_nnode_get_child(node);
          if (child == NULL)
            tmp = SSH_PK_USUAL_NAME;
          else
            tmp = ssh_nnode_get_identifier(child);
          scheme = ssh_pk_find_generic(tmp, action->type,
                                       action->type_size);
          if (child)
            ssh_xfree(tmp);
          if (scheme == NULL)
            {
              status = SSH_CRYPTO_SCHEME_UNKNOWN;
              break;
            }

          /* Call action_scheme if not set to NULL. */
          if (((SshPkGen *)scheme)->action_scheme != NULL)
            (*((SshPkGen *)scheme)->action_scheme)(context);

          /* Set the corresponding scheme to the group. */
          status = ssh_private_key_set_scheme(private_key, scheme,
                                              action->scheme_flag);

          if (status != SSH_CRYPTO_OK)
            {
              break;
            }
          /* Move to the next scheme. */
          node = ssh_nnode_get_next(node);
        }
      ssh_ntree_free(tree);
      if (status != SSH_CRYPTO_OK)
        {
          (*private_key->type->private_key_action_free)(context);
          ssh_xfree(private_key);
          va_end(ap);
          return status;
        }

      /* Parse vararg list. */
      consumed[0] = '\000';
      while (TRUE)
        {
          va_start(ap, key_type);
          PROCESS(ap, consumed);

          format = va_arg(ap, SshPkFormat);
          strcat(consumed, "i");
          if (format == SSH_PKF_END)
            break;

          /* Search name from command lists. */
          action = ssh_pk_find_action(format,
                                      private_key->type->action_list,
                                      SSH_PK_FLAG_PRIVATE_KEY);
          if (!action)
            {
              (*private_key->type->private_key_action_free)(context);
              ssh_xfree(private_key);
              va_end(ap);
              return SSH_CRYPTO_UNSUPPORTED_IDENTIFIER;
            }

          /* Supported only scheme selection and special operations. */
          switch (action->flags & (SSH_PK_FLAG_SCHEME | SSH_PK_FLAG_SPECIAL))
            {
            case SSH_PK_FLAG_SCHEME:
              name = va_arg(ap, const char *);
              strcat(consumed, "p");
              scheme = ssh_pk_find_generic(name, action->type,
                                           action->type_size);
              if (scheme == NULL)
                {
                  (*private_key->type->private_key_action_free)(context);
                  ssh_xfree(private_key);
                  va_end(ap);
                  return SSH_CRYPTO_SCHEME_UNKNOWN;
                }

              /* Call the action_scheme function here if not
                 NULL. */
              if (((SshPkGen *)scheme)->action_scheme != NULL)
                (*((SshPkGen *)scheme)->action_scheme)(context);

              /* Set the corresponding scheme. */
              status = ssh_private_key_set_scheme(private_key, scheme,
                                                  action->scheme_flag);
              if (status != SSH_CRYPTO_OK)
                {
                  (*private_key->type->private_key_action_free)(context);
                  ssh_xfree(private_key);
                  va_end(ap);
                  return status;
                }
              break;

            case SSH_PK_FLAG_SPECIAL:
              /* Assume we don't use wrappings. */
              wrapper = NULL;
              if (action->flags & SSH_PK_FLAG_WRAPPED)
                {
                  /* We assume that parameters are wrapped over group
                     structure. */
                  group = va_arg(ap, SshPkGroup);
                  strcat(consumed, "p");

                  wrapper = group->context;
                  /* For compatibility set also the Diffie-Hellman
                     field.  */
                  private_key->diffie_hellman = group->diffie_hellman;
                }

              r = (*action->action_put)(context, ap, wrapper, format);
              if (r == NULL)
                {
                  (*private_key->type->private_key_action_free)(context);
                  ssh_xfree(private_key);
                  va_end(ap);
                  return SSH_CRYPTO_LIBRARY_CORRUPTED;
                }
              else
                strcat(consumed, r);
              break;
            default:
              ssh_fatal("ssh_private_key_generate: internal error.");
              break;
            }
          va_end(ap);
        }

      /* Make the key and remove context. (One could incorporate making
             and freeing, however this way things seem to work also). */
      private_key->context =
        (*private_key->type->private_key_action_make)(context);
      (*private_key->type->private_key_action_free)(context);

      /* Quit unhappily. */
      if (private_key->context == NULL)
        {
          ssh_xfree(private_key);
          va_end(ap);
          return SSH_CRYPTO_OPERATION_FAILED;
        }

      /* Quit happily. */
      *key = private_key;
      va_end(ap);

      return SSH_CRYPTO_OK;

    }

  ssh_ntree_free(tree);
  va_end(ap);

  return SSH_CRYPTO_UNKNOWN_KEY_TYPE;
}

/* Select new scheme to be used. That is assuming key supports many
   different schemes and/or padding types this can be of some use. Note
   however, that the key stays the same and some method assume keys to be
   of certain form. Such an example is DSA which by standard needs to have
   parameters of certain form, but this function could easily switch to
   DSA with key that is not of that form. Nevertheless I feel that such
   problems do not make switching to other methods unusable (even DSA
   would work with different parameters, although would not conform to
   the digital signature standard). */

SshCryptoStatus
ssh_private_key_select_scheme(SshPrivateKey key, ...)
{
  SshCryptoStatus status;
  const SshPkAction *action;
  SshPkFormat format;
  void *scheme;
  const char *name;
  va_list ap;

  if (key->type == NULL)
    return SSH_CRYPTO_KEY_UNINITIALIZED;

  va_start(ap, key);

  while ((format = va_arg(ap, SshPkFormat)) != SSH_PKF_END)
    {
      action = ssh_pk_find_action(format, key->type->action_list,
                                  SSH_PK_FLAG_SCHEME |
                                  SSH_PK_FLAG_PRIVATE_KEY);
      if (!action)
        {
          va_end(ap);
          return SSH_CRYPTO_UNSUPPORTED_IDENTIFIER;
        }

      /* Find the new scheme. */
      name = va_arg(ap, const char *);
      scheme = ssh_pk_find_generic(name, action->type,
                                   action->type_size);

      /* Quit an awful error! Means that our scheme tables are either
         corrupted or application failed. */
      if (scheme == NULL)
        {
          va_end(ap);
          return SSH_CRYPTO_SCHEME_UNKNOWN;
        }

      status = ssh_private_key_set_scheme(key, scheme, action->scheme_flag);
      if (status != SSH_CRYPTO_OK)
        {
          va_end(ap);
          return status;
        }
    }
  va_end(ap);
  return SSH_CRYPTO_OK;
}

/* This function is needed in X.509 certificate routines. What is
   needed, is a way that creates from a bunch of stuff a valid SshPublicKey
   through sshcrypt header file.

   This will not be the final version, and in any case there should be no
   need for such things like random numbers anyway. We are not actually
   generating anything.
   */

SshCryptoStatus
ssh_public_key_define(SshPublicKey *public_key,
                      const char *key_type, ...)
{
  SshCryptoStatus status = SSH_CRYPTO_UNKNOWN_KEY_TYPE;
  SshPublicKey pub_key;
  SshPkGroup group;
  const SshPkAction *action;
  SshPkFormat format;
  SshNameTree tree;
  SshNameNode node, child;
  SshNameTreeStatus nstat;
  const char *name;
  void *wrapper;
  char *tmp, *r, consumed[128];
  void *scheme;
  void *context;
  unsigned int i;
  va_list ap;

  /* Parse given group type. */
  ssh_ntree_allocate(&tree);
  nstat = ssh_ntree_parse(key_type, tree);
  if (nstat != SSH_NTREE_OK)
    {
      ssh_ntree_free(tree);
      return SSH_CRYPTO_UNKNOWN_KEY_TYPE;
    }
  node = ssh_ntree_get_root(tree);
  if (node == NULL)
    {
      ssh_ntree_free(tree);
      return SSH_CRYPTO_UNKNOWN_KEY_TYPE;
    }
  tmp = ssh_nnode_get_identifier(node);

  /* Find out the key type. */
  for (i = 0; ssh_pk_type_slots[i] != NULL && ssh_pk_type_slots[i]->name; i++)
    {
      if (strcmp(ssh_pk_type_slots[i]->name, tmp) != 0)
        continue;

      /* Type matches i.e. we've found our key type, so continue with
         finding schemes and parameters. */

      ssh_xfree(tmp);
      node = ssh_nnode_get_child(node);

      /* Allocate private key context. */
      pub_key = ssh_xmalloc(sizeof(*pub_key));
      pub_key->type = ssh_pk_type_slots[i];

          /* Clear pointers. */
      pub_key->signature = NULL;
      pub_key->encryption = NULL;
      pub_key->diffie_hellman = NULL;

      /* Initialize actions, and verify that context was allocated. */
      context = (*pub_key->type->public_key_action_init)();
      if (context == NULL)
        {
          ssh_xfree(pub_key);
          va_end(ap);
          return SSH_CRYPTO_OPERATION_FAILED;
        }

      status = SSH_CRYPTO_OK;
      /* Run through all preselected schemes in the group_type. */
      while (node)
        {
          tmp = ssh_nnode_get_identifier(node);
          action =
            ssh_pk_find_scheme_action(pub_key->type->action_list,
                                      tmp,
                                      SSH_PK_FLAG_PUBLIC_KEY);
          ssh_xfree(tmp);
          if (!action)
            {
              status = SSH_CRYPTO_SCHEME_UNKNOWN;
              break;
            }
          child = ssh_nnode_get_child(node);
          if (child == NULL)
            tmp = SSH_PK_USUAL_NAME;
          else
            tmp = ssh_nnode_get_identifier(child);
          scheme = ssh_pk_find_generic(tmp, action->type,
                                       action->type_size);
          if (child)
            ssh_xfree(tmp);
          if (scheme == NULL)
            {
              status = SSH_CRYPTO_SCHEME_UNKNOWN;
              break;
            }

          /* Call action_scheme if not set to NULL. */
          if (((SshPkGen *)scheme)->action_scheme != NULL)
            (*((SshPkGen *)scheme)->action_scheme)(context);

          /* Set the corresponding scheme to the group. */
          status = ssh_public_key_set_scheme(pub_key, scheme,
                                             action->scheme_flag);

          if (status != SSH_CRYPTO_OK)
            {
              break;
            }
          /* Move to the next scheme. */
          node = ssh_nnode_get_next(node);
        }
      ssh_ntree_free(tree);
      if (status != SSH_CRYPTO_OK)
        {
          (*pub_key->type->public_key_action_free)(context);
          ssh_xfree(pub_key);
          va_end(ap);
          return status;
        }

      /* Parse vararg list. */
      consumed[0] = '\000';
      while (TRUE)
        {
          va_start(ap, key_type);
          PROCESS(ap, consumed);

          format = va_arg(ap, SshPkFormat);
          strcat(consumed, "i");
          if (format == SSH_PKF_END)
            break;

          /* Search name from command lists. */
          action = ssh_pk_find_action(format,
                                      pub_key->type->action_list,
                                      SSH_PK_FLAG_PUBLIC_KEY);
          if (!action)
            {
              (*pub_key->type->public_key_action_free)(context);
              ssh_xfree(pub_key);
              va_end(ap);
              return SSH_CRYPTO_UNSUPPORTED_IDENTIFIER;
            }

          /* Supported only scheme selection and special operations. */
          switch (action->flags & (SSH_PK_FLAG_SCHEME | SSH_PK_FLAG_SPECIAL))
            {
            case SSH_PK_FLAG_SCHEME:
              name = va_arg(ap, const char *);
              strcat(consumed, "p");
              scheme = ssh_pk_find_generic(name,
                                           action->type, action->type_size);
              if (scheme == NULL)
                {
                  (*pub_key->type->public_key_action_free)(context);
                  ssh_xfree(pub_key);
                  va_end(ap);
                  return SSH_CRYPTO_SCHEME_UNKNOWN;
                }

              /* Call the action_scheme function here if not
                 NULL. */
              if (((SshPkGen *)scheme)->action_scheme != NULL)
                (*((SshPkGen *)scheme)->action_scheme)(context);

              /* Set the corresponding scheme. */
              status = ssh_public_key_set_scheme(pub_key, scheme,
                                                 action->scheme_flag);
              if (status != SSH_CRYPTO_OK)
                {
                  (*pub_key->type->public_key_action_free)(context);
                  ssh_xfree(pub_key);
                  va_end(ap);
                  return status;
                }
              break;

            case SSH_PK_FLAG_SPECIAL:
              /* Assume we don't use wrappings. */
              wrapper = NULL;
              if (action->flags & SSH_PK_FLAG_WRAPPED)
                {
                  /* We assume that parameters are wrapped over group
                     structure. */
                  group = va_arg(ap, SshPkGroup);
                  strcat(consumed, "p");
                  wrapper = group->context;
                  /* For compatibility set also the Diffie-Hellman
                     field.  */
                  pub_key->diffie_hellman = group->diffie_hellman;
                }

              r = (*action->action_put)(context, ap, wrapper, format);
              if (r == NULL)
                {
                  (*pub_key->type->public_key_action_free)(context);
                  ssh_xfree(pub_key);
                  va_end(ap);
                  return SSH_CRYPTO_LIBRARY_CORRUPTED;
                }
              else
                strcat(consumed, r);
              break;
            default:
              ssh_fatal("ssh_public_key_define: internal error.");
              break;
            }
          va_end(ap);
        }

      /* Make the key and remove context. (One could incorporate
         making and freeing, however this way things seem to work
         also). */
      pub_key->context =
        (*pub_key->type->public_key_action_make)(context);
      (*pub_key->type->public_key_action_free)(context);

      /* Quit unhappily. */
      if (pub_key->context == NULL)
        {
          ssh_xfree(pub_key);
          va_end(ap);
          return SSH_CRYPTO_OPERATION_FAILED;
        }

      /* Quit happily. */
      *public_key = pub_key;
      va_end(ap);

      return SSH_CRYPTO_OK;
    }

  ssh_ntree_free(tree);
  va_end(ap);

  return SSH_CRYPTO_UNKNOWN_KEY_TYPE;
}

/* This is a little bit stupid, maybe same context for private and public
   key (internally) would be a good idea. */

SshCryptoStatus
ssh_public_key_select_scheme(SshPublicKey key, ...)
{
  SshCryptoStatus status;
  const SshPkAction *action;
  SshPkFormat format;
  const char *name;
  void *scheme;
  va_list ap;

  if (key->type == NULL)
    return SSH_CRYPTO_KEY_UNINITIALIZED;

  va_start(ap, key);

  while ((format = va_arg(ap, SshPkFormat)) != SSH_PKF_END)
    {
      action = ssh_pk_find_action(format, key->type->action_list,
                                  SSH_PK_FLAG_SCHEME |
                                  SSH_PK_FLAG_PUBLIC_KEY);

      if (!action)
        {
          va_end(ap);
          return SSH_CRYPTO_UNSUPPORTED_IDENTIFIER;
        }

      /* Find the new scheme. */
      name = va_arg(ap, const char *);
      scheme = ssh_pk_find_generic(name, action->type,
                                   action->type_size);

      /* Quit an awful error! Means that our scheme tables are either
         corrupted or application failed. */
      if (scheme == NULL)
        {
          va_end(ap);
          return SSH_CRYPTO_SCHEME_UNKNOWN;
        }

      status = ssh_public_key_set_scheme(key, scheme, action->scheme_flag);
      if (status != SSH_CRYPTO_OK)
        {
          va_end(ap);
          return status;
        }
    }
  va_end(ap);
  return SSH_CRYPTO_OK;
}

SshCryptoStatus
ssh_private_key_get_info(SshPrivateKey key, ...)
{
  SshCryptoStatus status;
  const SshPkAction *action;
  SshPkFormat format;
  const char **name_ptr;
  char consumed[128], *r;
  va_list ap;

  consumed[0] = '\000';
  while (TRUE)
    {
      va_start(ap, key);
      PROCESS(ap, consumed);

      format = va_arg(ap, SshPkFormat);
      strcat(consumed, "i");
      if (format == SSH_PKF_END)
        break;

      /* Seek for the action. */
      action = ssh_pk_find_action(format,
                                  key->type->action_list,
                                  SSH_PK_FLAG_PRIVATE_KEY);
      if (!action)
        {
          va_end(ap);
          return SSH_CRYPTO_UNSUPPORTED_IDENTIFIER;
        }

      switch (action->flags &
              (SSH_PK_FLAG_SCHEME|SSH_PK_FLAG_SPECIAL|SSH_PK_FLAG_KEY_TYPE))
        {
        case SSH_PK_FLAG_KEY_TYPE:
          name_ptr = va_arg(ap, const char **);
          strcat(consumed, "p");
          *name_ptr = strchr(key->type->name, ':');
          if (*name_ptr)
            (*name_ptr)++;
          else
            *name_ptr = key->type->name; /* ssh_private_key_name(key); */
          break;

        case SSH_PK_FLAG_SCHEME:
          name_ptr = va_arg(ap, const char **);
          strcat(consumed, "p");
          status = ssh_private_key_get_scheme_name(key,
                                                   name_ptr,
                                                   action->scheme_flag);
          if (status != SSH_CRYPTO_OK)
            {
              va_end(ap);
              return status;
            }
          break;

        case SSH_PK_FLAG_SPECIAL:
          if (action->flags & SSH_PK_FLAG_WRAPPED)
            {
              if (action->action_get)
                ssh_fatal("ssh_private_key_get_info: cannot wrap.");
              return SSH_CRYPTO_UNSUPPORTED_IDENTIFIER;
            }

          if (action->action_get == NULL)
            return SSH_CRYPTO_UNSUPPORTED;

          r = (*action->action_get)(key->context, ap, NULL, format);
          if (r == NULL)
            return SSH_CRYPTO_LIBRARY_CORRUPTED;
          else
            strcat(consumed, r);
          break;

        default:
          ssh_fatal("ssh_private_key_get_info: internal error.");
          break;
        }

      va_end(ap);
    }

  va_end(ap);
  return SSH_CRYPTO_OK;
}

SshCryptoStatus
ssh_public_key_get_info(SshPublicKey key, ...)
{
  SshCryptoStatus status;
  const SshPkAction *action;
  SshPkFormat format;
  const char **name_ptr;
  char *r, consumed[128];
  va_list ap;

  consumed[0] = '\000';
  while (TRUE)
    {
      va_start(ap, key);
      PROCESS(ap, consumed);

      format = va_arg(ap, SshPkFormat);
      strcat(consumed, "i");
      if (format == SSH_PKF_END)
        break;

      /* Seek for the action. */
      action = ssh_pk_find_action(format,
                                  key->type->action_list,
                                  SSH_PK_FLAG_PUBLIC_KEY);

      if (!action)
        {
          va_end(ap);
          return SSH_CRYPTO_UNSUPPORTED_IDENTIFIER;
        }

      switch (action->flags &
              (SSH_PK_FLAG_SCHEME|SSH_PK_FLAG_SPECIAL|SSH_PK_FLAG_KEY_TYPE))
        {
        case SSH_PK_FLAG_KEY_TYPE:
          name_ptr = va_arg(ap, const char **);
          strcat(consumed, "p");
          *name_ptr = strchr(key->type->name, ':');
          if (*name_ptr)
            (*name_ptr)++;
          else
            *name_ptr = key->type->name; /* ssh_private_key_name(key); */
          break;

        case SSH_PK_FLAG_SCHEME:
          name_ptr = va_arg(ap, const char **);
          strcat(consumed, "p");
          status = ssh_public_key_get_scheme_name(key,
                                                  name_ptr,
                                                  action->scheme_flag);
          if (status != SSH_CRYPTO_OK)
            {
              va_end(ap);
              return status;
            }
          break;

        case SSH_PK_FLAG_SPECIAL:
          if (action->flags & SSH_PK_FLAG_WRAPPED)
            {
              if (action->action_get)
                ssh_fatal("ssh_public_key_get_info: cannot wrap.");
              return SSH_CRYPTO_UNSUPPORTED_IDENTIFIER;
            }

          if (action->action_get == NULL)
            return SSH_CRYPTO_UNSUPPORTED;

          r = (*action->action_get)(key->context, ap, NULL, format);
          if (r == NULL)
            return SSH_CRYPTO_LIBRARY_CORRUPTED;
          else
            strcat(consumed, r);
          break;

        default:
          ssh_fatal("ssh_public_key_get_info: internal error.");
          break;
        }
      va_end(ap);
    }

  va_end(ap);
  return SSH_CRYPTO_OK;
}

#if 0
Boolean
ssh_public_key_type_supported_capability(const char *key_type,
                                         SshPkFormat test)
{
  return FALSE;
}
#endif

/* Names could be given by gathering all possible combinations, however,
   it might be more useful for outsider to get names for some specific
   class of algorithms. Such as signature, encryption or some key exchange
   method. */
char *
ssh_public_key_get_supported(void)
{
  char *list;
  unsigned int i, j, k, l;
  const SshPkAction *action;
  const void *scheme_list;
  const char *scheme_list_name;
  SshNameTree tree;
  SshNameNode node;

  /* Allocate tree. */
  ssh_ntree_allocate(&tree);
  node = NULL;

  for (i = 0; ssh_pk_type_slots[i] != NULL && ssh_pk_type_slots[i]->name; i++)
    {
      /* Add key type node. */
      node = ssh_ntree_add_next(tree, node,
                                ssh_pk_type_slots[i]->name);

      for (action = ssh_pk_type_slots[i]->action_list, j = 0, l = 0;
           action[j].format != SSH_PKF_END; j++)
        {
          if ((action[j].flags & SSH_PK_FLAG_SCHEME) == SSH_PK_FLAG_SCHEME)
            {
              /* Add scheme identifier nodes. */
              if (l == 0)
                node = ssh_ntree_add_child(tree, node,
                                           action[j].scheme_class);
              else
                  node = ssh_ntree_add_next(tree, node,
                                            action[j].scheme_class);
              l++;
              for (scheme_list = action[j].type, k = 0;
                   (scheme_list_name =
                    ssh_pk_next_generic(&scheme_list,
                                        action[j].type_size)) != NULL; k++)
                {
                  /* Add actual algorithm identifiers.

                     XXX Note, here we don't wonder about the *_USUAL_NAME
                     thing. It is more straight forward to just forget
                     it here. Although, it would make things easier to
                     read. */
                  if (k == 0)
                    node = ssh_ntree_add_child(tree, node,
                                               scheme_list_name);
                  else
                    node = ssh_ntree_add_next(tree, node,
                                              scheme_list_name);
                }
              /* Go up if one went down. */
              if (k)
                node = ssh_nnode_get_parent(node);
            }
        }
      /* Go up if one went down. */
      if (l)
        node = ssh_nnode_get_parent(node);
    }

  ssh_ntree_generate_string(tree, &list);
  ssh_ntree_free(tree);

  return list;
}

/* Key format might look like:

   32bit    magic
   32bit    total length
   32bit    key type name length n
   n bytes  key type name (no zero terminator) (with schemes)

   32bit    algorithm specific part length n
   n bytes  algorithm specific part

   One should note, that the following key will be identical to the one
   inputed. Also it would be possible, by extending nametree system, to
   actually output the public key in ascii.

   */

SshCryptoStatus
ssh_public_key_import(const unsigned char *buf,
                      size_t len,
                      SshPublicKey *key)
{
  SshBufferStruct buffer;
  SshUInt32 pk_magic, pk_length, length;
  char *key_type;
  char *name;
  const SshPkAction *action;
  void *scheme;
  SshPublicKey public_key;
  unsigned int i;
  SshCryptoStatus status;
  SshNameTree tree;
  SshNameNode node, child;
  SshNameTreeStatus nstat;

  ssh_buffer_init(&buffer);
  ssh_buffer_append(&buffer, buf, len);

  if (ssh_decode_buffer(&buffer,
                        SSH_FORMAT_UINT32, &pk_magic,
                        SSH_FORMAT_UINT32, &pk_length,
                        SSH_FORMAT_UINT32_STR, &key_type, NULL,
                        SSH_FORMAT_END) == 0)
    {
      ssh_buffer_uninit(&buffer);
      return SSH_CRYPTO_UNKNOWN_KEY_TYPE;
    }

  if (pk_magic != SSH_PUBLIC_KEY_MAGIC || pk_length < 8)
    {
      ssh_buffer_uninit(&buffer);
      return SSH_CRYPTO_UNKNOWN_KEY_TYPE;
    }

  ssh_ntree_allocate(&tree);
  nstat = ssh_ntree_parse(key_type, tree);
  if (nstat != SSH_NTREE_OK)
    {
      ssh_ntree_free(tree);
      ssh_xfree(key_type);
      ssh_buffer_uninit(&buffer);
      return SSH_CRYPTO_UNKNOWN_KEY_TYPE;
    }

  node = ssh_ntree_get_root(tree);
  if (node == NULL)
    {
      ssh_ntree_free(tree);
      ssh_xfree(key_type);
      ssh_buffer_uninit(&buffer);
      return SSH_CRYPTO_UNKNOWN_KEY_TYPE;
    }
  name = ssh_nnode_get_identifier(node);

  /* Find correct key type. Done here, because we don't want to overuse
     vararg lists. */

  for (i = 0, public_key = NULL; ssh_pk_type_slots[i] != NULL &&
         ssh_pk_type_slots[i]->name; i++)
    {
      if (strcmp(ssh_pk_type_slots[i]->name, name) == 0)
        {
          /* Initialize public key. */
          public_key = ssh_xmalloc(sizeof(*public_key));
          public_key->type = ssh_pk_type_slots[i];

          public_key->signature = NULL;
          public_key->encryption = NULL;
          public_key->diffie_hellman = NULL;

          break;
        }
    }
  ssh_xfree(name);
  node = ssh_nnode_get_child(node);
  if (public_key == NULL)
    {
      ssh_ntree_free(tree);
      ssh_xfree(key_type);
      ssh_buffer_uninit(&buffer);
      return SSH_CRYPTO_OPERATION_FAILED;
    }
  status = SSH_CRYPTO_OK;
  /* Run through all preselected schemes in the group_type. */
  while (node)
    {
      name = ssh_nnode_get_identifier(node);
      action =
        ssh_pk_find_scheme_action(public_key->type->action_list,
                                  name,
                                  SSH_PK_FLAG_PUBLIC_KEY);
      ssh_xfree(name);
      if (!action)
        {
          status = SSH_CRYPTO_SCHEME_UNKNOWN;
          break;
        }
      child = ssh_nnode_get_child(node);
      if (child == NULL)
        name = SSH_PK_USUAL_NAME;
      else
        name = ssh_nnode_get_identifier(child);
      scheme = ssh_pk_find_generic(name, action->type,
                                   action->type_size);
      if (child)
        ssh_xfree(name);
      if (scheme == NULL)
        {
          status = SSH_CRYPTO_SCHEME_UNKNOWN;
          break;
        }
      /* Set the corresponding scheme to the group. */
      status = ssh_public_key_set_scheme(public_key, scheme,
                                         action->scheme_flag);

      if (status != SSH_CRYPTO_OK)
        {
          break;
        }
      /* Move to the next scheme. */
      node = ssh_nnode_get_next(node);
    }
  ssh_ntree_free(tree);
  ssh_xfree(key_type);
  if (status != SSH_CRYPTO_OK)
    {
      ssh_buffer_uninit(&buffer);
      ssh_xfree(public_key);
      return status;
    }

  if (ssh_decode_buffer(&buffer,
                        SSH_FORMAT_UINT32, &length,
                        SSH_FORMAT_END) == 0 ||
      length > ssh_buffer_len(&buffer))
    {
      ssh_xfree(public_key);
      ssh_buffer_uninit(&buffer);
      return SSH_CRYPTO_OPERATION_FAILED;
    }

  if (public_key->type->public_key_import != NULL)
    {
      /*Algorithm specific part. */
      if ((*public_key->type->public_key_import)(ssh_buffer_ptr(&buffer),
                                                 length,
                                                 &(public_key->context)) ==
          FALSE)
        {
          ssh_buffer_uninit(&buffer);
          *key = NULL;
          ssh_xfree(public_key);
          return SSH_CRYPTO_OPERATION_FAILED;
        }
    }
  else
    {
      return SSH_CRYPTO_UNSUPPORTED;
    }

      /* Advance */

      ssh_buffer_consume(&buffer, length);

      *key = public_key;
      ssh_buffer_uninit(&buffer);

      return SSH_CRYPTO_OK;
    }

SshCryptoStatus
ssh_public_key_export(SshPublicKey key,
                      unsigned char **buf,
                      size_t *length_return)
{
  SshBufferStruct buffer;
  unsigned char *temp_buf;
  size_t temp_buf_len;
  char *name;

  if (key->type->public_key_export == NULL)
      return SSH_CRYPTO_UNSUPPORTED;

  ssh_buffer_init(&buffer);

  /* Encoding of the public key, in SSH format. */

  name = ssh_public_key_name(key);

  ssh_encode_buffer(&buffer,
                    SSH_FORMAT_UINT32, (SshUInt32) SSH_PUBLIC_KEY_MAGIC,
                    SSH_FORMAT_UINT32, (SshUInt32) 0,
                    SSH_FORMAT_UINT32_STR, name, strlen(name),
                    SSH_FORMAT_END);
  ssh_xfree(name);

  if ((*key->type->public_key_export)(key->context,
                                      &temp_buf, &temp_buf_len) == FALSE)
    {
      ssh_buffer_uninit(&buffer);
      return SSH_CRYPTO_OPERATION_FAILED;
    }

  ssh_encode_buffer(&buffer,
                    SSH_FORMAT_UINT32_STR, temp_buf, temp_buf_len,
                    SSH_FORMAT_END);

  ssh_xfree(temp_buf);

  /* Get the buffer information. */
  *length_return = ssh_buffer_len(&buffer);
  *buf = ssh_xmalloc(*length_return);
  memcpy(*buf, ssh_buffer_ptr(&buffer), *length_return);

  /* Set total length. */
  SSH_PUT_32BIT(*buf + 4, *length_return);

  /* Free buffer. */
  ssh_buffer_uninit(&buffer);

  return SSH_CRYPTO_OK;
}

SshCryptoStatus
ssh_public_key_export_canonical(SshPublicKey key,
                                unsigned char **buf,
                                size_t *length_return)
{
  SshBufferStruct buffer;
  unsigned char *temp_buf;
  size_t temp_buf_len;

  if (key->type->public_key_export == NULL)
      return SSH_CRYPTO_UNSUPPORTED;

  /* Encoding of the public key, in SSH format. */

  if (key == NULL)
    return SSH_CRYPTO_OPERATION_FAILED;
  if (key->type == NULL)
    return SSH_CRYPTO_OPERATION_FAILED;

  if ((*key->type->public_key_export)(key->context,
                                      &temp_buf, &temp_buf_len) == FALSE)
    return SSH_CRYPTO_OPERATION_FAILED;

  ssh_buffer_init(&buffer);

  /* Build the blob. */
  ssh_encode_buffer(&buffer,
                    SSH_FORMAT_UINT32, (SshUInt32) SSH_PUBLIC_KEY_MAGIC,
                    SSH_FORMAT_UINT32, (SshUInt32) 0,
                    SSH_FORMAT_UINT32_STR, key->type->name,
                    strlen(key->type->name),
                    SSH_FORMAT_UINT32_STR, temp_buf, temp_buf_len,
                    SSH_FORMAT_END);

  ssh_xfree(temp_buf);

  /* Get the buffer information. */
  *length_return = ssh_buffer_len(&buffer);
  *buf = ssh_xmalloc(*length_return);
  memcpy(*buf, ssh_buffer_ptr(&buffer), *length_return);

  /* Set total length. */
  SSH_PUT_32BIT(*buf + 4, *length_return);

  /* Free buffer. */
  ssh_buffer_uninit(&buffer);

  return SSH_CRYPTO_OK;
}

/* Doing copy of the key_src, so that both keys can be altered without
   affecting the other. Note, that although keys might seem to be totally
   separate some features might be implemeted with reference counting. */
SshCryptoStatus
ssh_public_key_copy(SshPublicKey key_src,
                    SshPublicKey *key_dest)
{
  SshPublicKey created;

  if (key_src->type->public_key_copy == NULL)
    return SSH_CRYPTO_UNSUPPORTED;

  created = ssh_xmalloc(sizeof(*created));

  /* First copy all basic internal stuff and then the context explicitly. */
  memcpy(created, key_src, sizeof(*created));
  (*key_src->type->public_key_copy)(key_src->context, &created->context);

  *key_dest = created;
  return SSH_CRYPTO_OK;
}

void
ssh_public_key_free(SshPublicKey key)
{
  if (key == NULL || key->context == NULL)
    ssh_fatal("ssh_public_key_free: undefined key.");
  if (key->type->public_key_free)
    (*key->type->public_key_free)(key->context);
  key->context = NULL;
  ssh_xfree(key);
}

/* Derive public key group for public key. */
SshPkGroup
ssh_public_key_derive_pk_group(SshPublicKey key)
{
  SshPkGroup group;

  if (key->type->public_key_derive_pk_group == NULL)
    return NULL;

  group = ssh_xmalloc(sizeof(*group));
  group->type = key->type;
  (*key->type->public_key_derive_pk_group)(key->context,
                                           &group->context);
  /* Set up schemes for compatibility. */
  group->diffie_hellman = key->diffie_hellman;
  return group;
}

/* Report the maximal length of bytes which may be encrypted with this
   public key. Return 0 if encryption not available for this public key. */

size_t
ssh_public_key_max_encrypt_input_len(SshPublicKey key)
{
  if (key->encryption == NULL)
    return 0;

  if (key->encryption->public_key_max_encrypt_input_len == NULL)
    return 0;

  return (*key->encryption->public_key_max_encrypt_input_len)(key->context);
}

/* This is similar to the previous one, but the maximal output length
   is returned instead the of the maximum input length. */

size_t
ssh_public_key_max_encrypt_output_len(SshPublicKey key)
{
  if (key->encryption == NULL)
    return 0;

  return (*key->encryption->public_key_max_encrypt_output_len)(key->context);
}

/* Import private key. */

SshCryptoStatus
ssh_private_key_import_internal(const unsigned char *buf,
                                size_t len,
                                const unsigned char *cipher_key,
                                size_t cipher_keylen,
                                SshPrivateKey *key,
                                Boolean expand_key)
{
  SshBufferStruct buffer;
  SshUInt32 pk_magic, pk_length, length, tmp_length;
  char *key_type, *cipher_name, *name;
  unsigned char *tmp_buf;
  size_t tmp_buf_length;
  unsigned int i;
  SshPrivateKey private_key;
  SshCipher cipher;
  SshCryptoStatus status;
  const SshPkAction *action;
  void *scheme;
  SshNameTree tree;
  SshNameNode node, child;
  SshNameTreeStatus nstat;

  ssh_buffer_init(&buffer);
  ssh_buffer_append(&buffer, buf, len);

  ssh_decode_buffer(&buffer,
                    SSH_FORMAT_UINT32, &pk_magic,
                    SSH_FORMAT_UINT32, &pk_length,
                    SSH_FORMAT_UINT32_STR, &key_type, NULL,
                    SSH_FORMAT_END);

  if (pk_magic != SSH_PRIVATE_KEY_MAGIC || pk_length < 8)
    return SSH_CRYPTO_CORRUPTED_KEY_FORMAT;

  ssh_ntree_allocate(&tree);
  nstat = ssh_ntree_parse(key_type, tree);
  if (nstat != SSH_NTREE_OK)
    {
      ssh_ntree_free(tree);
      ssh_xfree(key_type);
      ssh_buffer_uninit(&buffer);
      return SSH_CRYPTO_UNKNOWN_KEY_TYPE;
    }
  node = ssh_ntree_get_root(tree);
  if (node == NULL)
    {
      ssh_ntree_free(tree);
      ssh_xfree(key_type);
      ssh_buffer_uninit(&buffer);
      return SSH_CRYPTO_UNKNOWN_KEY_TYPE;
    }
  name = ssh_nnode_get_identifier(node);

  /* Find correct key type. */

  for (i = 0, private_key = NULL; ssh_pk_type_slots[i] != NULL &&
         ssh_pk_type_slots[i]->name; i++)
    {
      if (strcmp(ssh_pk_type_slots[i]->name, name) == 0)
        {
          /* Initialize public key. */
          private_key = ssh_xmalloc(sizeof(*private_key));
          private_key->type = ssh_pk_type_slots[i];

          private_key->signature = NULL;
          private_key->encryption = NULL;
          private_key->diffie_hellman = NULL;
          break;
        }
    }

  ssh_xfree(name);
  node = ssh_nnode_get_child(node);
  if (private_key == NULL)
    {
      ssh_ntree_free(tree);
      ssh_xfree(key_type);
      ssh_buffer_uninit(&buffer);
      return SSH_CRYPTO_OPERATION_FAILED;
    }
  status = SSH_CRYPTO_OK;
  /* Run through all preselected schemes in the group_type. */
  while (node)
    {
      name = ssh_nnode_get_identifier(node);
      action =
        ssh_pk_find_scheme_action(private_key->type->action_list,
                                  name,
                                  SSH_PK_FLAG_PRIVATE_KEY);
      ssh_xfree(name);
      if (!action)
        {
          status = SSH_CRYPTO_SCHEME_UNKNOWN;
          break;
        }
      child = ssh_nnode_get_child(node);
      if (child == NULL)
        name = SSH_PK_USUAL_NAME;
      else
        name = ssh_nnode_get_identifier(child);
      scheme = ssh_pk_find_generic(name, action->type,
                                   action->type_size);
      if (child)
        ssh_xfree(name);
      if (scheme == NULL)
        {
          status = SSH_CRYPTO_SCHEME_UNKNOWN;
          break;
        }
      /* Set the corresponding scheme to the group. */
      status = ssh_private_key_set_scheme(private_key, scheme,
                                          action->scheme_flag);

      if (status != SSH_CRYPTO_OK)
        break;
      /* Move to the next scheme. */
      node = ssh_nnode_get_next(node);
    }
  ssh_ntree_free(tree);
  ssh_xfree(key_type);
  if (status != SSH_CRYPTO_OK)
    {
      ssh_buffer_uninit(&buffer);
      ssh_xfree(private_key);
      return status;
    }

  ssh_decode_buffer(&buffer,
                    SSH_FORMAT_UINT32_STR, &cipher_name, NULL,
                    SSH_FORMAT_UINT32, &length,
                    SSH_FORMAT_END);

  tmp_buf_length = ssh_cipher_get_key_length(cipher_name);
  tmp_buf        = NULL;
  if (tmp_buf_length == 0)
    tmp_buf_length = 32;

  /* Check key len and expansion flag. */
  if (tmp_buf_length > cipher_keylen || expand_key)
    {
      /* Expand encryption key. */
      tmp_buf = ssh_xmalloc(tmp_buf_length);
      ssh_hash_expand_key_internal(tmp_buf, tmp_buf_length,
                                   cipher_key, cipher_keylen,
                                   NULL, 0,
                                   &ssh_hash_md5_def);

      cipher_key = tmp_buf;
      cipher_keylen = tmp_buf_length;
    }

  /* Allocate cipher. */
  if ((status = ssh_cipher_allocate(cipher_name, cipher_key,
                                    cipher_keylen,
                                    FALSE, &cipher)) != SSH_CRYPTO_OK)
    {
      ssh_xfree(cipher_name);
      ssh_buffer_uninit(&buffer);
      ssh_xfree(tmp_buf);
      ssh_xfree(private_key);
      return status;
    }
  ssh_xfree(tmp_buf);
  ssh_xfree(cipher_name);

  if (ssh_cipher_transform(cipher,
                           ssh_buffer_ptr(&buffer), ssh_buffer_ptr(&buffer),
                           length) != SSH_CRYPTO_OK)
    {
      ssh_buffer_uninit(&buffer);
      ssh_xfree(private_key);
      ssh_cipher_free(cipher);
      return SSH_CRYPTO_OPERATION_FAILED;
    }

  /* Free cipher immediately. */
  ssh_cipher_free(cipher);

  /* Algorithm specific part. */
  if (ssh_decode_buffer(&buffer,
                        SSH_FORMAT_UINT32, &tmp_length,
                        SSH_FORMAT_END) == 0 ||
      tmp_length > ssh_buffer_len(&buffer))
    {
      ssh_buffer_uninit(&buffer);
      ssh_xfree(private_key);
      return SSH_CRYPTO_OPERATION_FAILED;
    }


  if (private_key->type->private_key_import == NULL ||
      (*private_key->type->private_key_import)
      (ssh_buffer_ptr(&buffer),
       tmp_length,
       &(private_key->context)) == FALSE)
    {
      ssh_buffer_uninit(&buffer);
      ssh_xfree(private_key);
      return SSH_CRYPTO_OPERATION_FAILED;
    }

  /* Advance (over padding etc.) */
  ssh_buffer_consume(&buffer, tmp_length);

  *key = private_key;
  ssh_buffer_uninit(&buffer);

  return SSH_CRYPTO_OK;
}

/* Export a private key. */

SshCryptoStatus
ssh_private_key_export_internal(SshPrivateKey key,
                                const char *cipher_name,
                                const unsigned char *cipher_key,
                                size_t cipher_keylen,
                                unsigned char **bufptr,
                                size_t *length_return,
                                Boolean expand_key)
{
  SshCryptoStatus status;
  SshBufferStruct buffer, encrypted;
  unsigned char byte;
  unsigned char *buf;
  size_t buf_length;
  SshCipher cipher;
  char *name;

  /* Check key len and expansion flag. */
  buf_length = ssh_cipher_get_key_length(cipher_name);
  buf        = NULL;
  if (buf_length == 0)
    buf_length = 32;

  if (buf_length > cipher_keylen || expand_key)
    {
      /* Expand encryption key. */
      buf = ssh_xmalloc(buf_length);
      ssh_hash_expand_key_internal(buf, buf_length,
                                   cipher_key, cipher_keylen,
                                   NULL, 0,
                                   &ssh_hash_md5_def);

      cipher_key    = buf;
      cipher_keylen = buf_length;
    }

  /* Allocate cipher. */
  if ((status = ssh_cipher_allocate(cipher_name,
                                    cipher_key, cipher_keylen,
                                    TRUE,
                                    &cipher)) != SSH_CRYPTO_OK)
    {
      ssh_xfree(buf);
      return status;
    }
  /* Free the key buffer if it exists. */
  ssh_xfree(buf);

  /* Generate private key blob. */

  if (key->type->private_key_export == NULL ||
      (*key->type->private_key_export)(key->context,
                                       &buf, &buf_length) == FALSE)
    {
      ssh_cipher_free(cipher);
      return SSH_CRYPTO_OPERATION_FAILED;
    }

  /* Use buffer to append data. */
  ssh_buffer_init(&encrypted);

  ssh_encode_buffer(&encrypted,
                    SSH_FORMAT_UINT32_STR, buf, buf_length,
                    SSH_FORMAT_END);

  /* Free exact private key information. */
  memset(buf, 0, buf_length);
  ssh_xfree(buf);

  /* Add some padding. */
  while ((ssh_buffer_len(&encrypted) % ssh_cipher_get_block_length(cipher)) !=
         0)
    {
      byte = ssh_random_get_byte();
      ssh_buffer_append(&encrypted, &byte, 1);
    }

  /* Encrypt buffer. */
  if (ssh_cipher_transform(cipher, ssh_buffer_ptr(&encrypted),
                           ssh_buffer_ptr(&encrypted),
                           ssh_buffer_len(&encrypted)) != SSH_CRYPTO_OK)
    {
      ssh_buffer_uninit(&encrypted);
      ssh_cipher_free(cipher);
      return SSH_CRYPTO_OPERATION_FAILED;
    }

  /* Free cipher. */
  ssh_cipher_free(cipher);

  /* Initialize the actual private key buffer. */
  ssh_buffer_init(&buffer);

  name = ssh_private_key_name(key);

  ssh_encode_buffer(&buffer,
                    SSH_FORMAT_UINT32, (SshUInt32) SSH_PRIVATE_KEY_MAGIC,
                    SSH_FORMAT_UINT32, (SshUInt32) 0,
                    SSH_FORMAT_UINT32_STR,
                    name, strlen(name),
                    SSH_FORMAT_UINT32_STR,
                    cipher_name, strlen(cipher_name),
                    SSH_FORMAT_UINT32_STR,
                    ssh_buffer_ptr(&encrypted), ssh_buffer_len(&encrypted),
                    SSH_FORMAT_END);

  ssh_xfree(name);
  /* Free encrypted buffer. */
  ssh_buffer_uninit(&encrypted);

  /* Get the buffer information. */
  *length_return = ssh_buffer_len(&buffer);
  *bufptr = ssh_xmalloc(*length_return);
  memcpy(*bufptr, ssh_buffer_ptr(&buffer), *length_return);

  /* Set total length. */
  SSH_PUT_32BIT(*bufptr + 4, *length_return);

  /* Free buffer. */
  ssh_buffer_uninit(&buffer);

  return SSH_CRYPTO_OK;
}

/* Functions that are used from outside. These tell whether one wants to
   expand the key here or not. */

SshCryptoStatus
ssh_private_key_import(const unsigned char *buf,
                       size_t len,
                       const unsigned char *cipher_key,
                       size_t cipher_keylen,
                       SshPrivateKey *key)
{
  return ssh_private_key_import_internal(buf, len,
                                         cipher_key, cipher_keylen,
                                         key, FALSE);
}

SshCryptoStatus
ssh_private_key_export(SshPrivateKey key,
                       const char *cipher_name,
                       const unsigned char *cipher_key,
                       size_t cipher_keylen,
                       unsigned char **bufptr,
                       size_t *length_return)
{
  return ssh_private_key_export_internal(key, cipher_name,
                                         cipher_key, cipher_keylen,
                                         bufptr, length_return, FALSE);
}


SshCryptoStatus
ssh_private_key_import_with_passphrase(const unsigned char *buf,
                                       size_t len,
                                       const char *passphrase,
                                       SshPrivateKey *key)
{
  return ssh_private_key_import_internal(buf, len,
                                         (unsigned char *) passphrase,
                                         strlen(passphrase),
                                         key, TRUE);
}

SshCryptoStatus
ssh_private_key_export_with_passphrase(SshPrivateKey key,
                                       const char *cipher_name,
                                       const char *passphrase,
                                       unsigned char **bufptr,
                                       size_t *length_return)
{
  if (strcmp(passphrase, "") == 0)
    cipher_name = "none";
  return ssh_private_key_export_internal(key, cipher_name,
                                         (unsigned char *) passphrase,
                                         strlen(passphrase),
                                         bufptr, length_return, TRUE);
}

/* Copy private keys */

SshCryptoStatus
ssh_private_key_copy(SshPrivateKey key_src,
                     SshPrivateKey *key_dest)
{
  SshPrivateKey created;

  if (key_src->type->private_key_copy == NULL)
    return SSH_CRYPTO_UNSUPPORTED;

  created = ssh_xmalloc(sizeof(*created));

  memcpy(created, key_src, sizeof(*created));
  (*key_src->type->private_key_copy)(key_src->context, &created->context);
  *key_dest = created;
  return SSH_CRYPTO_OK;
}

/* Release a private key structure. */

void
ssh_private_key_free(SshPrivateKey key)
{
  if (key == NULL || key->context == NULL)
    ssh_fatal("ssh_private_key_free: undefined key.");
  if (key->type->private_key_free)
    (*key->type->private_key_free)(key->context);
  key->context = NULL;
  ssh_xfree(key);
}

SshPublicKey
ssh_private_key_derive_public_key(SshPrivateKey key)
{
  SshPublicKey pub;
  void *pub_context;

  if (key->type->private_key_derive_public_key == NULL)
    return NULL;

  (*key->type->private_key_derive_public_key)(key->context,
                                              &pub_context);
  if (pub_context == NULL)
    return NULL;

  pub = ssh_xmalloc(sizeof(*pub));
  pub->context = pub_context;

  pub->type = key->type;

  /* Set up all schemes for compatibility. */
  pub->signature = key->signature;
  pub->encryption = key->encryption;
  pub->diffie_hellman = key->diffie_hellman;

  return pub;
}

SshPkGroup
ssh_private_key_derive_pk_group(SshPrivateKey key)
{
  SshPkGroup group;

  if (key->type->private_key_derive_pk_group == NULL)
    return NULL;

  group = ssh_xmalloc(sizeof(*group));

  group->type = key->type;
  (*key->type->private_key_derive_pk_group)(key->context,
                                            &group->context);
  /* Set up schemes for compatibility. */
  group->diffie_hellman = key->diffie_hellman;
  return group;
}

/* Signature hash function derivation functions. */

SshRGFHash
ssh_public_key_derive_signature_hash(SshPublicKey key)
{
  if (key->signature == NULL)
    return NULL;
  return ssh_rgf_hash_allocate_internal(key->signature->rgf_def);
}

SshRGFHash
ssh_private_key_derive_signature_hash(SshPrivateKey key)
{
  if (key->signature == NULL)
    return NULL;
  return ssh_rgf_hash_allocate_internal(key->signature->rgf_def);
}

/* Perform the public key encryption operation. */

SshCryptoStatus
ssh_public_key_encrypt(SshPublicKey key,
                       const unsigned char *plaintext,
                       size_t plaintext_len,
                       unsigned char *ciphertext_buffer,
                       size_t buffer_len,
                       size_t *ciphertext_len_return)
{
  if (key->encryption == NULL ||
      key->encryption->public_key_encrypt == NULL)
    return SSH_CRYPTO_UNSUPPORTED;

  /* If true then encryption succeeded. */
  if ((*key->encryption->public_key_encrypt)(key->context,
                                             plaintext,
                                             plaintext_len,
                                             ciphertext_buffer,
                                             buffer_len,
                                             ciphertext_len_return,
                                             key->encryption->rgf_def))
    return SSH_CRYPTO_OK;

  return SSH_CRYPTO_OPERATION_FAILED;
}

SshCryptoStatus
ssh_public_key_decrypt(SshPublicKey key,
                       const unsigned char *ciphertext_buffer,
                       size_t ciphertext_buffer_len,
                       unsigned char *plaintext_buffer,
                       size_t plaintext_buffer_len,
                       size_t *plaintext_len_return)
{
  if (key->encryption == NULL ||
      key->encryption->public_key_decrypt == NULL)
    return SSH_CRYPTO_UNSUPPORTED;

  /* If true then encryption succeeded. */
  if ((*key->encryption->public_key_decrypt)(key->context,
                                             ciphertext_buffer,
                                             ciphertext_buffer_len,
                                             plaintext_buffer,
                                             plaintext_buffer_len,
                                             plaintext_len_return))
    return SSH_CRYPTO_OK;

  return SSH_CRYPTO_OPERATION_FAILED;
}

/* Verify a signature. In fact, decrypt the given signature with the
   public key, and then compare the decrypted data to the given
   (supposedly original) data. If the decrypted data and the given
   data are identical (in the sense that they are of equal length and
   their contents are bit-wise same) the function returns TRUE,
   otherways FALSE. */

Boolean
ssh_public_key_verify_signature(SshPublicKey key,
                                const unsigned char *signature,
                                size_t signature_len,
                                const unsigned char *data,
                                size_t data_len)
{
  SshRGFHash hash;
  if (key->signature == NULL ||
      key->signature->public_key_verify == NULL)
    return FALSE;

  /* We need to compute the RGFHash ourselves. */
  hash = ssh_rgf_hash_allocate_internal(key->signature->rgf_def);
  if (hash == NULL)
    return FALSE;
  ssh_rgf_hash_update(hash, data, data_len);

  return (*key->signature->public_key_verify)(key->context,
                                              signature,
                                              signature_len,
                                              hash,
                                              key->signature->rgf_def);
}

Boolean
ssh_public_key_verify_signature_with_digest(SshPublicKey key,
                                            const unsigned char *signature,
                                            size_t signature_len,
                                            const unsigned char *digest,
                                            size_t digest_len)
{
  SshRGFHash hash;
  if (key->signature == NULL || key->signature->public_key_verify == NULL)
    return FALSE;

  hash = ssh_rgf_hash_allocate_internal(key->signature->rgf_def);
  if (hash == NULL)
    return SSH_CRYPTO_OPERATION_FAILED;
  if (ssh_rgf_hash_update_with_digest(hash,
                                      digest, digest_len) == FALSE)
    {
      ssh_rgf_hash_free(hash);
      return SSH_CRYPTO_OPERATION_FAILED;
    }

  return (*key->signature->public_key_verify)(key->context,
                                              signature, signature_len,
                                              hash,
                                              key->signature->rgf_def);
}

Boolean
ssh_public_key_verify_signature_with_hash(SshPublicKey key,
                                          const unsigned char *signature,
                                          size_t signature_len,
                                          SshRGFHash hash)
{
  if (key->signature == NULL || key->signature->public_key_verify == NULL)
    return FALSE;

  return (*key->signature->public_key_verify)(key->context,
                                              signature, signature_len,
                                              hash,
                                              key->signature->rgf_def);
}



size_t
ssh_private_key_max_signature_input_len(SshPrivateKey key)
{
  if (key->signature == NULL ||
      key->signature->private_key_max_signature_input_len == NULL)
    return 0;

  return (*key->signature->private_key_max_signature_input_len)(key->context);
}

size_t
ssh_private_key_max_signature_output_len(SshPrivateKey key)
{
  if (key->signature == NULL ||
      key->signature->private_key_max_signature_output_len == NULL)
    return 0;
  return (*key->signature->private_key_max_signature_output_len)(key->context);
}

/* Return the maximal lenght of bytes which may be decrypted with this
   private key. The result is queried from the corresponding private key
   cryptosystem package with a type-specific function. */

size_t
ssh_private_key_max_decrypt_input_len(SshPrivateKey key)
{
  if (key->encryption == NULL ||
      key->encryption->private_key_max_decrypt_input_len == NULL)
    return 0;

  return (*key->encryption->private_key_max_decrypt_input_len)(key->context);
}

/* Similar to the previous function except this will return the maximum
   output lenght with decryption. */

size_t
ssh_private_key_max_decrypt_output_len(SshPrivateKey key)
{
  if (key->encryption == NULL ||
      key->encryption->private_key_max_decrypt_output_len == NULL)
    return 0;

  return (*key->encryption->private_key_max_decrypt_output_len)(key->context);
}

/* Private key decrypt and encrypt */
SshCryptoStatus
ssh_private_key_decrypt(SshPrivateKey key,
                        const unsigned char *ciphertext,
                        size_t ciphertext_len,
                        unsigned char *plaintext_buffer,
                        size_t buffer_len,
                        size_t *plaintext_length_return)
{
  if (key->encryption == NULL ||
      key->encryption->private_key_decrypt == NULL)
    return SSH_CRYPTO_UNSUPPORTED;

  if ((*key->encryption->private_key_decrypt)(key->context,
                                              ciphertext,
                                              ciphertext_len,
                                              plaintext_buffer,
                                              buffer_len,
                                              plaintext_length_return,
                                              key->encryption->rgf_def))

    return SSH_CRYPTO_OK;

  return SSH_CRYPTO_OPERATION_FAILED;
}

SshCryptoStatus
ssh_private_key_encrypt(SshPrivateKey key,
                        const unsigned char *plaintext_buffer,
                        size_t plaintext_buffer_len,
                        unsigned char *ciphertext,
                        size_t ciphertext_len,
                        size_t *ciphertext_length_return)
{
  if (key->encryption == NULL ||
      key->encryption->private_key_encrypt == NULL)
    return SSH_CRYPTO_UNSUPPORTED;

  if ((*key->encryption->private_key_encrypt)(key->context,
                                              plaintext_buffer,
                                              plaintext_buffer_len,
                                              ciphertext,
                                              ciphertext_len,
                                              ciphertext_length_return))

    return SSH_CRYPTO_OK;

  return SSH_CRYPTO_OPERATION_FAILED;
}

SshCryptoStatus
ssh_private_key_sign(SshPrivateKey key,
                     const unsigned char *data,
                     size_t data_len,
                     unsigned char *signature_buffer,
                     size_t buffer_len,
                     size_t *signature_length_return)
{
  SshRGFHash hash;
  if (key->signature == NULL ||
      key->signature->private_key_sign == NULL)
    return SSH_CRYPTO_UNSUPPORTED;

  hash = ssh_rgf_hash_allocate_internal(key->signature->rgf_def);
  if (hash == NULL)
    return SSH_CRYPTO_OPERATION_FAILED;
  ssh_rgf_hash_update(hash, data, data_len);

  if ((*key->signature->private_key_sign)(key->context,
                                          hash,
                                          signature_buffer, buffer_len,
                                          signature_length_return,
                                          key->signature->rgf_def))
    return SSH_CRYPTO_OK;

  return SSH_CRYPTO_OPERATION_FAILED;
}


SshCryptoStatus
ssh_private_key_sign_digest(SshPrivateKey key,
                            const unsigned char *digest,
                            size_t digest_len,
                            unsigned char *signature_buffer,
                            size_t buffer_len,
                            size_t *signature_length_return)
{
  SshRGFHash hash;
  if (key->signature == NULL ||
      key->signature->private_key_sign == NULL)
    return SSH_CRYPTO_UNSUPPORTED;

  hash = ssh_rgf_hash_allocate_internal(key->signature->rgf_def);
  if (hash == NULL)
    return SSH_CRYPTO_OPERATION_FAILED;
  if (ssh_rgf_hash_update_with_digest(hash,
                                      digest, digest_len) == FALSE)
    {
      ssh_rgf_hash_free(hash);
      return SSH_CRYPTO_OPERATION_FAILED;
    }

  if ((*key->signature->private_key_sign)(key->context,
                                          hash,
                                          signature_buffer, buffer_len,
                                          signature_length_return,
                                          key->signature->rgf_def))
    return SSH_CRYPTO_OK;

  return SSH_CRYPTO_OPERATION_FAILED;
}

SshCryptoStatus
ssh_private_key_sign_hash(SshPrivateKey key,
                          SshRGFHash    hash,
                          unsigned char *signature_buffer,
                          size_t buffer_len,
                          size_t *signature_length_return)
{
  if (key->signature == NULL ||
      key->signature->private_key_sign == NULL)
    return SSH_CRYPTO_UNSUPPORTED;

  if ((*key->signature->private_key_sign)(key->context,
                                          hash,
                                          signature_buffer, buffer_len,
                                          signature_length_return,
                                          key->signature->rgf_def))
    return SSH_CRYPTO_OK;

  return SSH_CRYPTO_OPERATION_FAILED;
}


/* Diffie-Hellman key exchange method. */

size_t
ssh_pk_group_diffie_hellman_setup_max_output_length(SshPkGroup group)
{
  if (group->diffie_hellman == NULL ||
      group->diffie_hellman->diffie_hellman_exchange_max_length == NULL)
    return 0;

  return (*group->diffie_hellman->
          diffie_hellman_exchange_max_length)(group->context);
}

size_t
ssh_pk_group_diffie_hellman_agree_max_output_length(SshPkGroup group)
{
  if (group->diffie_hellman == NULL ||
      group->diffie_hellman->diffie_hellman_secret_value_max_length == NULL)
    return 0;

  return (*group->diffie_hellman->
          diffie_hellman_secret_value_max_length)(group->context);
}

void
ssh_pk_group_diffie_hellman_secret_free(SshPkGroupDHSecret secret)
{
  /* Remark. Currently we can just free the secret using ssh_xfree.
     It is possible that in future more elaborate methods are needed. */
  ssh_xfree(secret);
}

SshCryptoStatus
ssh_pk_group_diffie_hellman_setup(SshPkGroup group,
                                  SshPkGroupDHSecret *secret,
                                  unsigned char *exchange_buffer,
                                  size_t exchange_buffer_length,
                                  size_t *return_length)
{
  if (group->diffie_hellman == NULL ||
      group->diffie_hellman->diffie_hellman_setup == NULL)
    return SSH_CRYPTO_UNSUPPORTED;

  if ((*group->diffie_hellman->diffie_hellman_setup)(group->context,
                                                     (void **) secret,
                                                     exchange_buffer,
                                                     exchange_buffer_length,
                                                     return_length))
    return SSH_CRYPTO_OK;

  return SSH_CRYPTO_OPERATION_FAILED;
}

SshCryptoStatus
ssh_pk_group_diffie_hellman_agree(SshPkGroup group,
                                  SshPkGroupDHSecret secret,
                                  unsigned char *exchange_buffer,
                                  size_t exchange_buffer_length,
                                  unsigned char *secret_value_buffer,
                                  size_t secret_value_buffer_length,
                                  size_t *return_length)
{
  if (group->diffie_hellman == NULL ||
      group->diffie_hellman->diffie_hellman_agree == NULL)
    return SSH_CRYPTO_UNSUPPORTED;

  if ((*group->diffie_hellman->
       diffie_hellman_agree)(group->context,
                             (void *) secret,
                             exchange_buffer,
                             exchange_buffer_length,
                             secret_value_buffer,
                             secret_value_buffer_length,
                             return_length))
    return SSH_CRYPTO_OK;
  return SSH_CRYPTO_OPERATION_FAILED;
}

/* Unified Diffie-Hellman. */

size_t
ssh_pk_group_unified_diffie_hellman_agree_max_output_length(SshPkGroup group)
{
  if (group->diffie_hellman == NULL ||
      group->diffie_hellman->udh_secret_value_max_length == NULL)
    return 0;
  return (*group->diffie_hellman->
          udh_secret_value_max_length)(group->context);
}

SshCryptoStatus
ssh_pk_group_unified_diffie_hellman_agree(SshPublicKey public_key,
                                          SshPrivateKey private_key,
                                          void *secret,
                                          unsigned char *exchange_buffer,
                                          size_t exchange_buffer_length,
                                          unsigned char *secret_value_buffer,
                                          size_t secret_value_buffer_length,
                                          size_t *return_length)
{
  if (private_key->diffie_hellman == NULL ||
      public_key->diffie_hellman == NULL ||
      private_key->diffie_hellman->udh_agree == NULL)
    return SSH_CRYPTO_UNSUPPORTED;

  if ((*private_key->diffie_hellman->
       udh_agree)(public_key->context,
                  private_key->context,
                  secret,
                  exchange_buffer,
                  exchange_buffer_length,
                  secret_value_buffer,
                  secret_value_buffer_length,
                  return_length))
    return SSH_CRYPTO_OK;
  return SSH_CRYPTO_OPERATION_FAILED;
}

/* ... more to come. */

/* Asyncronous operations implemented using the syncronous software
   implementation if no asynchronous callback was defined for the key. */

/* Start asyncronous public key encryption operation. The library will call
   given callback when operation is done. Callback may be called immediately
   during this call. The function ssh_operation_abort function may be called to
   abort this operation before it finishes, in which case the callback is not
   called and the SshOperationHandle will be NULL. */
SshOperationHandle
ssh_public_key_encrypt_async(SshPublicKey key,
                             const unsigned char *plaintext,
                             size_t plaintext_length,
                             SshPublicKeyEncryptCB callback,
                             void *context)
{
  unsigned char *ciphertext_buffer;
  size_t return_length, ciphertext_buffer_length;
  SshCryptoStatus status;

  if (key->encryption && key->encryption->public_key_encrypt_async)
      return (*key->encryption->
              public_key_encrypt_async)(key->context,
                                        plaintext,
                                        plaintext_length,
                                        key->encryption->rgf_def,
                                        callback,
                                        context);

  /* Do it using the synchronous code. */
  ciphertext_buffer_length = ssh_public_key_max_encrypt_output_len(key);
  ciphertext_buffer = ssh_xmalloc(ciphertext_buffer_length);
  status = ssh_public_key_encrypt(key, plaintext, plaintext_length,
                                  ciphertext_buffer, ciphertext_buffer_length,
                                  &return_length);
  (*callback)(status, ciphertext_buffer, return_length, context);
  ssh_xfree(ciphertext_buffer);
  return NULL;
}

/* Start asyncronous public key decrypt operation. The library will call given
   callback when operation is done. Callback may be called immediately during
   this call. The function ssh_operation_abort function may be called to abort
   this operation before it finishes, in which case the callback is not called
   and the SshOperationHandle will be NULL. Key and the ciphertext must remain
   constant during the operation, and can only be freed after the callback is
   called. This operation will  remove padding from the resultant decrypted
   data, if the scheme requires so. */
SshOperationHandle
ssh_public_key_decrypt_async(SshPublicKey key,
                             const unsigned char *ciphertext_buffer,
                             size_t ciphertext_buffer_len,
                             SshPublicKeyDecryptCB callback,
                             void *context)
{
  unsigned char *plaintext_buffer;
  size_t return_length, plaintext_buffer_length;
  SshCryptoStatus status;

  if (key->encryption &&
      key->encryption->public_key_decrypt_async != NULL)
    {
      /* Asyncronous operation. */
      return (*key->encryption->
              public_key_decrypt_async)(key->context,
                                        ciphertext_buffer,
                                        ciphertext_buffer_len,
                                        callback, context);
    }

  plaintext_buffer_length = ssh_public_key_max_encrypt_input_len(key);
  plaintext_buffer = ssh_xmalloc(plaintext_buffer_length);
  status = ssh_public_key_decrypt(key, ciphertext_buffer,
                                  ciphertext_buffer_len,
                                  plaintext_buffer, plaintext_buffer_length,
                                  &return_length);
  (*callback)(status, plaintext_buffer, return_length, context);
  ssh_xfree(plaintext_buffer);
  return NULL;
}

/* Start asyncronous public key signature verify operation. The
   library will call given callback when operation is done. Callback
   may be called immediately during this call. The function
   ssh_operation_abort function may be called to abort this operation
   before it finishes, in which case the callback is not called and
   the SshOperationHandle will be NULL. */
SshOperationHandle
ssh_public_key_verify_async(SshPublicKey key,
                            const unsigned char *signature,
                            size_t signature_length,
                            const unsigned char *data,
                            size_t data_length,
                            SshPublicKeyVerifyCB callback,
                            void *context)
{
  if (key->signature && key->signature->public_key_verify_async)
    {
      SshRGFHash hash;
      hash = ssh_rgf_hash_allocate_internal(key->signature->rgf_def);
      if (hash == NULL)
        {
          (*callback)(SSH_CRYPTO_OPERATION_FAILED, NULL, context);
          return NULL;
        }
      ssh_rgf_hash_update(hash, data, data_length);

      return (*key->signature->
              public_key_verify_async)(key->context,
                                       signature,
                                       signature_length,
                                       hash,
                                       key->signature->rgf_def,
                                       callback,
                                       context);
    }

  if (ssh_public_key_verify_signature(key, signature,  signature_length,
                                      data, data_length))
    {
      (*callback)(SSH_CRYPTO_OK, NULL, context);
    }
  else
    {
      (*callback)(SSH_CRYPTO_SIGNATURE_CHECK_FAILED, NULL, context);
    }
  return NULL;

}

/* Start asyncronous public key signature verify operation. As
   ssh_public_key_verify_asycn but with this interface one can give
   the exact digest one self. The library will call given callback
   when operation is done. Callback may be called immediately during
   this call. The function ssh_operation_abort function may be called to
   abort this operation before it finishes, in which case the callback
   is not called and the SshOperationHandle will be NULL. */
SshOperationHandle
ssh_public_key_verify_digest_async(SshPublicKey key,
                                   const unsigned char *signature,
                                   size_t signature_length,
                                   const unsigned char *digest,
                                   size_t digest_length,
                                   SshPublicKeyVerifyCB callback,
                                   void *context)
{
  if (key->signature && key->signature->public_key_verify_async)
    {
      SshRGFHash hash;
      hash = ssh_rgf_hash_allocate_internal(key->signature->rgf_def);
      if (hash == NULL)
        {
          (*callback)(SSH_CRYPTO_OPERATION_FAILED, NULL, context);
          return NULL;
        }
      if (ssh_rgf_hash_update_with_digest(hash,
                                          digest, digest_length) == FALSE)
        {
          (*callback)(SSH_CRYPTO_OPERATION_FAILED, NULL, context);
          ssh_rgf_hash_free(hash);
          return NULL;
        }

      return (*key->signature->
              public_key_verify_async)(key->context,
                                       signature,
                                       signature_length,
                                       hash,
                                       key->signature->rgf_def,
                                       callback,
                                       context);
    }

  if (ssh_public_key_verify_signature_with_digest(key,
                                                  signature,  signature_length,
                                                  digest, digest_length))
    {
      (*callback)(SSH_CRYPTO_OK, NULL, context);
    }
  else
    {
      (*callback)(SSH_CRYPTO_SIGNATURE_CHECK_FAILED, NULL, context);
    }
  return NULL;
}

SshOperationHandle
ssh_public_key_verify_hash_async(SshPublicKey key,
                                 const unsigned char *signature,
                                 size_t signature_length,
                                 SshRGFHash hash,
                                 SshPublicKeyVerifyCB callback,
                                 void *context)
{
  if (key->signature && key->signature->public_key_verify_async)
    {
      return (*key->signature->
              public_key_verify_async)(key->context,
                                       signature,
                                       signature_length,
                                       hash,
                                       key->signature->rgf_def,
                                       callback,
                                       context);
    }

  if (ssh_public_key_verify_signature_with_hash(key,
                                                signature,
                                                signature_length,
                                                hash))
    {
      (*callback)(SSH_CRYPTO_OK, NULL, context);
    }
  else
    {
      (*callback)(SSH_CRYPTO_SIGNATURE_CHECK_FAILED, NULL, context);
    }
  return NULL;
}


/* Start asyncronous private key decryption operation. The library will call
   given callback when operation is done. Callback may be called immediately
   during this call. The function ssh_operation_abort function may be called to
   abort this operation before it finishes, in which case the callback is not
   called and the SshOperationHandle will be NULL. */
SshOperationHandle
ssh_private_key_decrypt_async(SshPrivateKey key,
                              const unsigned char *ciphertext,
                              size_t ciphertext_length,
                              SshPrivateKeyDecryptCB callback,
                              void *context)
{
  unsigned char *plaintext_buffer;
  size_t return_length, plaintext_buffer_length;
  SshCryptoStatus status;


  if (key->encryption &&
      key->encryption->private_key_decrypt_async != NULL)
    {
      /* Asyncronous operation. */
      return (*key->encryption->
              private_key_decrypt_async)(key->context,
                                         ciphertext, ciphertext_length,
                                         key->encryption->rgf_def,
                                         callback, context);
    }


  plaintext_buffer_length = ssh_private_key_max_decrypt_output_len(key);
  plaintext_buffer = ssh_xmalloc(plaintext_buffer_length);
  status = ssh_private_key_decrypt(key, ciphertext, ciphertext_length,
                                   plaintext_buffer, plaintext_buffer_length,
                                   &return_length);
  (*callback)(status, plaintext_buffer, return_length, context);
  ssh_xfree(plaintext_buffer);
  return NULL;
}

/* Start asyncronous private key encrypt operation. The library will call given
   callback when operation is done. Callback may be called immediately during
   this call. The function ssh_operation_abort function may be called to abort
   this operation before it finishes, in which case the callback is not called
   and the SshOperationHandle will be NULL. Key and the data must remain
   constant during the operation, and can only be freed after the callback is
   called. This operation does not do any kind of hashing for the data, it may
   do padding. */
SshOperationHandle
ssh_private_key_encrypt_async(SshPrivateKey key,
                              const unsigned char *data,
                              size_t data_len,
                              SshPrivateKeyEncryptCB callback,
                              void *context)
{
  unsigned char *ciphertext_buffer;
  size_t return_length, ciphertext_buffer_length;
  SshCryptoStatus status;

  if (key->encryption && key->encryption->private_key_encrypt_async)
    return (*key->encryption->
            private_key_encrypt_async)(key->context,
                                       data, data_len,
                                       callback, context);

  /* Do it using the synchronous code. */
  ciphertext_buffer_length = ssh_private_key_max_decrypt_input_len(key);
  ciphertext_buffer = ssh_xmalloc(ciphertext_buffer_length);
  status = ssh_private_key_encrypt(key, data, data_len,
                                   ciphertext_buffer, ciphertext_buffer_length,
                                   &return_length);
  (*callback)(status, ciphertext_buffer, return_length, context);
  ssh_xfree(ciphertext_buffer);
  return NULL;
}

/* Start asyncronous private key signing operation. The library will
   call given callback when operation is done. Callback may be called
   immediately during this call. The function ssh_operation_abort
   function may be called to abort this operation before it finishes,
   in which case the callback is not called and the SshOperationHandle
   will be NULL. */
SshOperationHandle
ssh_private_key_sign_async(SshPrivateKey key,
                           const unsigned char *data,
                           size_t data_length,
                           SshPrivateKeySignCB callback,
                           void *context)
{
  unsigned char *signature_buffer;
  size_t return_length, signature_buffer_length;
  SshCryptoStatus status;

  if (key->signature &&
      key->signature->private_key_sign_async != NULL)
    {
      SshRGFHash hash;
      hash = ssh_rgf_hash_allocate_internal(key->signature->rgf_def);
      if (hash == NULL)
        {
          (*callback)(SSH_CRYPTO_OPERATION_FAILED, NULL, 0, context);
          return NULL;
        }
      ssh_rgf_hash_update(hash, data, data_length);

      /* Asyncronous operation. */
      return (*key->signature->
              private_key_sign_async)(key->context,
                                      hash,
                                      key->signature->rgf_def,
                                      callback, context);
    }

  signature_buffer_length = ssh_private_key_max_signature_output_len(key);
  signature_buffer = ssh_xmalloc(signature_buffer_length);
  status = ssh_private_key_sign(key, data, data_length,
                                signature_buffer, signature_buffer_length,
                                &return_length);
  (*callback)(status, signature_buffer, return_length, context);
  ssh_xfree(signature_buffer);
  return NULL;
}

/* As ssh_private_key_sign but here one can give the hash digest directly. The
   hash which to use can be requested using
   ssh_private_key_derive_signature_hash function. */

SshOperationHandle
ssh_private_key_sign_digest_async(SshPrivateKey key,
                                  const unsigned char *digest,
                                  size_t digest_length,
                                  SshPrivateKeySignCB callback,
                                  void *context)
{
  unsigned char *signature_buffer;
  size_t return_length, signature_buffer_length;
  SshCryptoStatus status;

  if (key->signature &&
      key->signature->private_key_sign_async != NULL)
    {
      SshRGFHash hash;
      hash = ssh_rgf_hash_allocate_internal(key->signature->rgf_def);
      if (hash == NULL)
        {
          (*callback)(SSH_CRYPTO_OPERATION_FAILED, NULL, 0, context);
          return NULL;
        }
      if (ssh_rgf_hash_update_with_digest(hash,
                                          digest, digest_length) == FALSE)
        {
          (*callback)(SSH_CRYPTO_OPERATION_FAILED, NULL, 0, context);
          ssh_rgf_hash_free(hash);
          return NULL;
        }

      /* Asyncronous operation. */
      return (*key->signature->
              private_key_sign_async)(key->context,
                                      hash,
                                      key->signature->rgf_def,
                                      callback, context);
    }

  signature_buffer_length = ssh_private_key_max_signature_output_len(key);
  signature_buffer        = ssh_xmalloc(signature_buffer_length);
  status =
    ssh_private_key_sign_digest(key, digest, digest_length,
                                signature_buffer, signature_buffer_length,
                                &return_length);
  (*callback)(status, signature_buffer, return_length, context);
  ssh_xfree(signature_buffer);
  return NULL;
}

SshOperationHandle
ssh_private_key_sign_hash_async(SshPrivateKey key,
                                SshRGFHash hash,
                                SshPrivateKeySignCB callback,
                                void *context)
{
  unsigned char *signature_buffer;
  size_t return_length, signature_buffer_length;
  SshCryptoStatus status;

  if (key->signature &&
      key->signature->private_key_sign_async != NULL)
    {
      /* Asyncronous operation. */
      return (*key->signature->
              private_key_sign_async)(key->context,
                                      hash,
                                      key->signature->rgf_def,
                                      callback, context);
    }

  signature_buffer_length = ssh_private_key_max_signature_output_len(key);
  signature_buffer = ssh_xmalloc(signature_buffer_length);
  status = ssh_private_key_sign_hash(key, hash,
                                       signature_buffer,
                                       signature_buffer_length,
                                       &return_length);
  (*callback)(status, signature_buffer, return_length, context);
  ssh_xfree(signature_buffer);
  return NULL;
}

/* Start asyncronous Diffie-Hellman setup operation. The library will call
   given callback when operation is done. Callback may be called immediately
   during this call. The function ssh_operation_abort function may be called to
   abort this operation before it finishes, in which case the callback is not
   called and the SshOperationHandle will be NULL. */
SshOperationHandle
ssh_pk_group_diffie_hellman_setup_async(SshPkGroup group,
                                        SshPkGroupDHSetup callback,
                                        void *context)
{
  SshPkGroupDHSecret secret;
  unsigned char *exchange_buffer;
  size_t exchange_buffer_length;
  size_t return_length;
  SshCryptoStatus status;

  if (group->diffie_hellman &&
      group->diffie_hellman->diffie_hellman_setup_async)
    {
      return (*group->diffie_hellman->
              diffie_hellman_setup_async)
        (group->context,
         callback,
         context);
    }

  exchange_buffer_length =
    ssh_pk_group_diffie_hellman_setup_max_output_length(group);
  exchange_buffer = ssh_xmalloc(exchange_buffer_length);
  status = ssh_pk_group_diffie_hellman_setup(group, &secret,
                                             exchange_buffer,
                                             exchange_buffer_length,
                                             &return_length);
  (*callback)(status, secret, exchange_buffer, return_length, context);
  ssh_xfree(exchange_buffer);

  return NULL;
}


/* Start asyncronous Diffie-Hellman agree operation. The library will call
   given callback when operation is done. Callback may be called immediately
   during this call. The function ssh_operation_abort function may be called to
   abort this operation before it finishes, in which case the callback is not
   called and the SshOperationHandle will be NULL. */
SshOperationHandle
ssh_pk_group_diffie_hellman_agree_async(SshPkGroup group,
                                        SshPkGroupDHSecret secret,
                                        unsigned char *exchange_buffer,
                                        size_t exchange_buffer_length,
                                        SshPkGroupDHAgree callback,
                                        void *context)
{
  unsigned char *secret_buffer;
  size_t secret_buffer_length;
  size_t return_length;
  SshCryptoStatus status;

  if (group->diffie_hellman &&
      group->diffie_hellman->diffie_hellman_agree_async)
    {
      return (*group->diffie_hellman->
              diffie_hellman_agree_async)
        (group->context,
         (void*)secret,
         exchange_buffer,
         exchange_buffer_length,
         callback,
         context);
    }

  secret_buffer_length =
    ssh_pk_group_diffie_hellman_agree_max_output_length(group);
  secret_buffer = ssh_xmalloc(secret_buffer_length);
  status = ssh_pk_group_diffie_hellman_agree(group, secret,
                                             exchange_buffer,
                                             exchange_buffer_length,
                                             secret_buffer,
                                             secret_buffer_length,
                                             &return_length);
  (*callback)(status, secret_buffer, return_length, context);
  ssh_xfree(secret_buffer);

  return NULL;
}

/* Compute a secret value from an exchange value and a secret. Secret will
   be free and deleted (thus no need to free it otherways). This function
   destroys the secret even when an error occurs.

   Callback is called in the same way than in
   ssh_pk_group_diffie_hellman_agree_async.

   As with Diffie-Hellman the returned secret_value_buffer contains bits known
   only by participants of the exchange. However, the buffer contains lots of
   redundancy, thus some function should be run over it before use as keying
   material etc. */

SshOperationHandle
ssh_pk_group_unified_diffie_hellman_agree_async(SshPublicKey public_key,
                                                SshPrivateKey private_key,
                                                SshPkGroupDHSecret secret,
                                                unsigned char *exchange_buffer,
                                                size_t exchange_buffer_length,
                                                SshPkGroupDHAgree callback,
                                                void *context)
{
  unsigned char *secret_buffer;
  size_t secret_buffer_length;
  size_t return_length;
  SshCryptoStatus status;

    if (private_key->diffie_hellman &&
      private_key->diffie_hellman->udh_agree_async)
      {
        return (*private_key->diffie_hellman->udh_agree_async)
          (public_key->context,
           private_key->context,
           secret,
           exchange_buffer,
           exchange_buffer_length,
           callback,
           context);
      }

  secret_buffer_length =
    ssh_pk_group_unified_diffie_hellman_agree_max_output_length(ssh_public_key_derive_pk_group(public_key));
  secret_buffer = ssh_xmalloc(secret_buffer_length);
  status = ssh_pk_group_unified_diffie_hellman_agree(public_key,
                                                     private_key,
                                                     secret,
                                                     exchange_buffer,
                                                     exchange_buffer_length,
                                                     secret_buffer,
                                                     secret_buffer_length,
                                                     &return_length);
  (*callback)(status, secret_buffer, return_length, context);
  ssh_xfree(secret_buffer);
  return NULL;
}
