/*
  ssh-keygen.c

  Authors:
        Tatu Ylonen <ylo@ssh.com>
        Markku-Juhani Saarinen <mjos@ssh.com>
        Timo J. Rinne <tri@ssh.com>
        Sami Lehtinen <sjl@ssh.com>

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

  A tool for generating and manipulating private keys.
*/

/* 
 *  $Log: ssh-keygen2.c,v $
 *  $EndLog$
 */



#include "sshincludes.h"
#include "ssh2includes.h"
#include "sshuserfiles.h"
#include "sshreadline.h"
#include "sshfileio.h"
#include "readpass.h"
#include "sshuser.h"
#include "sshcrypt.h"
#include "sshcipherlist.h"
#include "ssh2pubkeyencode.h"
#include "sshgetopt.h"
#include "sshappcommon.h"
#include <sys/types.h>
#include <pwd.h>
#include "sshmp.h"
#include "sshfingerprint.h"

#define SSH_DEBUG_MODULE "SshKeyGen"

/* Standard (assumed) choices.. */

#ifndef KEYGEN_ASSUMED_PUBKEY_LEN
#define KEYGEN_ASSUMED_PUBKEY_LEN 1024
#endif /* KEYGEN_ASSUMED_PUBKEY_LEN */

#ifdef HAVE_LIBWRAP
int allow_severity = SSH_LOG_INFORMATIONAL;
int deny_severity = SSH_LOG_WARNING;
#endif /* HAVE_LIBWRAP */

/* helptext */

const char keygen_helptext[] =
  "Usage: ssh-keygen [options] [key1 key2 ...]\n"
  "\n"
  "Where `options' are:\n"
  " -b nnn         Specify key strength in bits (e.g. 1024)\n"
  " -t dsa         Choose the key type (only dsa available).\n"
  " -c comment     Provide the comment.\n"
  " -e file        Edit the comment/passphrase of the key.\n"
  " -p passphrase  Provide passphrase.\n"
  " -P             Assume empty passphrase.\n"
  " -?\n"
  " -h             Print this help text.\n"
  " -q             Suppress the progress indicator.\n"
  " -1             Convert a SSH 1.x key.  (not implemented)\n"
  " -i file        Load and display information on `file'.\n"
  " -D file        Derive the private key given in 'file' to public key.\n"
  " -B number      The number base for displaying key information (default 10).\n"
  " -V             Print ssh-keygen version number.\n"
  " -r file        Stir data from file to random pool.\n"
  " -F file        Dump fingerprint of file.\n\n";

/* A context structure -- we don't like global variables. */

typedef struct 
{
  int keybits;
  Boolean newkey;
  Boolean convert;
  Boolean status;
  Boolean edit_key;
  
  char *keytype;
  char *keytypecommon;
  char *comment;
  char *in_filename;
  char *out_filename;
  char *passphrase;
  char *info_file;
  char *derive_file;
  Boolean pass_empty;

  unsigned int base;
  
  SshPrivateKey private_key;
  SshPublicKey public_key;
  SshUser user;

  Boolean prog_ind;
  Boolean have_prog_ind;

  Boolean read_file;
  Boolean dump_fingerprint;
  SshFingerPrintType fingerprint_type;
  char *entropy_file_name;
} KeyGenCtx;

/* mapping of common names and CryptoLib names. Common names are
   not case sensitive.

   The first entry in this list will be the preferred (standard)
   choice. */

const char *keygen_common_names[][2] = 
{
  /* Digital Signature Standard */
  { "dsa", SSH_CRYPTO_DSS },
  { "dss", SSH_CRYPTO_DSS },


  /* Last entry */
  { NULL, NULL }
};

/* allocate the context */

KeyGenCtx *keygen_init_ctx(void)
{
  KeyGenCtx *kgc;

  kgc = ssh_xcalloc(1, sizeof (KeyGenCtx));
  kgc->keybits = FALSE;
  kgc->newkey = FALSE;
  kgc->convert = FALSE;
  kgc->status = FALSE;
  kgc->read_file = FALSE;
  kgc->edit_key = FALSE;
  
  kgc->keytype = NULL;
  kgc->keytypecommon = NULL;
  kgc->comment = NULL;
  kgc->out_filename = NULL;
  kgc->in_filename = NULL;
  kgc->passphrase = NULL;
  kgc->pass_empty = FALSE;
  kgc->info_file = NULL;
  kgc->derive_file = NULL;
  kgc->dump_fingerprint = FALSE;
  kgc->fingerprint_type = SSH_FINGERPRINT_BABBLE;
  
  kgc->base = 10;
  
  kgc->user = ssh_user_initialize(NULL, FALSE);  
  ssh_randseed_open(kgc->user, NULL);
  kgc->public_key = NULL;
  kgc->private_key = NULL;

  kgc->prog_ind = FALSE;
  kgc->have_prog_ind = TRUE;

  kgc->entropy_file_name = NULL;
  return kgc;
}

/* Zeroize and free a NULL-terminated string, assuming that the pointer
   is non-null */

void keygen_free_str(char *s)
{
  if (s == NULL)
    return;
  memset(s, 0, strlen(s));
  ssh_xfree(s);
}

/* free the context */

void keygen_free_ctx(KeyGenCtx *kgc)
{
  keygen_free_str(kgc->keytype);
  keygen_free_str(kgc->keytypecommon);
  keygen_free_str(kgc->comment);
  keygen_free_str(kgc->in_filename);
  keygen_free_str(kgc->out_filename);
  keygen_free_str(kgc->passphrase);
  keygen_free_str(kgc->info_file);
  keygen_free_str(kgc->derive_file);
  keygen_free_str(kgc->entropy_file_name);

  if (kgc->public_key != NULL)
    ssh_public_key_free(kgc->public_key);
  if (kgc->private_key != NULL)
    ssh_private_key_free(kgc->private_key);
  ssh_randseed_update(kgc->user, NULL);
  if (kgc->user != NULL)
    ssh_user_free(kgc->user, FALSE);
  if (kgc->prog_ind)
    ssh_crypto_library_register_progress_func(NULL, NULL);

  memset(kgc, 0, sizeof (KeyGenCtx));
  ssh_xfree(kgc);
}


/* Ask for a passphrase twice */

char *ssh_read_passphrase_twice(char *prompt1, char *prompt2, int from_stdin)
{
  char *r1, *r2;

  for (;;)
    {
      r1 = ssh_read_passphrase(prompt1, from_stdin);
      r2 = ssh_read_passphrase(prompt2, from_stdin);

      if (strcmp(r1, r2) != 0)
        {
          keygen_free_str(r1);
          keygen_free_str(r2);
          fprintf(stderr, "Passphrases do not match.\n");
        }
      else
        break;
    }
  keygen_free_str(r2);

  return r1;
}


/* Read a string (with echo) from stdin */

char *ssh_askpass_read_stdin(char *prompt)
{
  char buf[1024], *p;

  if (prompt != NULL)
    {
      printf("%s", prompt);
      fflush(stdout);
    }
  if (fgets(buf, sizeof (buf)-1, stdin) == NULL)
    return ssh_xstrdup("");

  for(p = buf; *p >= 32 || *p < 0; p++); 
  *p = '\0';

  p = ssh_xstrdup(buf);
  memset(buf, 0, sizeof (buf));
  
  return p;
}



/* Keygen error message */

void keygen_error(KeyGenCtx *kgc, char *expl)
{
  fprintf(stderr, "\nError: %s\n", expl);
  keygen_free_ctx(kgc);
  exit(-1);
}

/* The progress indicator */

void keygen_prog_ind(SshCryptoProgressID id, unsigned int time_value,
                     void *incr)
{
  int i;

  i = *((int *) incr);
  (*((int *) incr))++;

  if (i % 13 == 0)
    {
      printf("\r %3d ", i / 13 + 1);
    } 
  else
    {
      switch( i % 4 )
        {
        case 0:   
          printf(".");
          break;
        case 1:
        case 3:
          printf("o");
          break;
        case 2:
          printf("O");
          break;
        }
    }
  fflush(stdout);
}

/* Generate a filename for the private key */

void keygen_choose_filename(KeyGenCtx *kgc)
{
  int i;
  char buf[1024], *udir;
  struct stat st;

  if (kgc->out_filename != NULL)
    return;

  if((udir = ssh_userdir(kgc->user, NULL, TRUE)) == NULL)
    {
      ssh_warning("Unable to open user ssh2 directory. "
                  "Saving to current directory.");
      udir = ssh_xstrdup(".");
    }

  for (i = 'a'; i <= 'z'; i++)
    {
      snprintf(buf, sizeof (buf), "%s/id_%s_%d_%c",
               udir, kgc->keytypecommon, kgc->keybits, i);
      if (stat(buf, &st) == 0)
        continue;
      kgc->out_filename = ssh_xstrdup(buf);
      goto done;
    }
  ssh_fatal("Could not find a suitable file name.");

done:
  ssh_xfree(udir);
}

/* Read private key. */

SshPrivateKey keygen_read_privkey(KeyGenCtx *kgc, char *filename)
{
  SshPrivateKey prvkey = NULL;
  /* Guess that it is a private key. */
  
  if (kgc->passphrase)
    {
      prvkey = ssh_privkey_read(kgc->user,
                                filename,
                                kgc->passphrase, NULL, NULL);
    }

  if (prvkey == NULL)
    {
      prvkey = ssh_privkey_read(kgc->user, filename,
                                "", NULL, NULL);
      if (prvkey == NULL &&
          !kgc->passphrase && kgc->pass_empty == FALSE)
        {
          char *pass;
          pass = ssh_read_passphrase("Passphrase : ", FALSE);

          prvkey = ssh_privkey_read(kgc->user, filename,
                                    pass, NULL, NULL);
          ssh_xfree(pass);
        }
    }

  return prvkey;
}

Boolean keygen_out_number(KeyGenCtx *kgc, SshInt *number)
{
  char *buf;
  buf = ssh_mp_get_str(NULL, kgc->base, number);
  printf("%s\n", buf);
  ssh_xfree(buf);
  return FALSE;
}

Boolean keygen_view_prvkey(KeyGenCtx *kgc, SshPrivateKey prv)
{
  const char *name;

  if (ssh_private_key_get_info(prv, SSH_PKF_KEY_TYPE, &name,
                               SSH_PKF_END) != SSH_CRYPTO_OK)
    {
      printf("Private key has no type (corrupted)\n");
      goto failed;
    }

  if (strcmp(name, "if-modn") == 0)
    {
      SshInt n, e, d, p, q, u;

      /* RSA? */
      ssh_mp_init(&n);
      ssh_mp_init(&e);
      ssh_mp_init(&d);
      ssh_mp_init(&p);
      ssh_mp_init(&q);
      ssh_mp_init(&u);

      /* Get the necessary information of the SSH style RSA key. */
      if (ssh_private_key_get_info(prv,
                                   SSH_PKF_MODULO_N,  &n,
                                   SSH_PKF_PUBLIC_E,  &e,
                                   SSH_PKF_SECRET_D,  &d,
                                   SSH_PKF_PRIME_P,   &p,
                                   SSH_PKF_PRIME_Q,   &q,
                                   SSH_PKF_INVERSE_U, &u,
                                   SSH_PKF_END) != SSH_CRYPTO_OK)
        {
          ssh_mp_clear(&n);
          ssh_mp_clear(&e);
          ssh_mp_clear(&d);
          ssh_mp_clear(&p);
          ssh_mp_clear(&q);
          ssh_mp_clear(&u);
          printf("RSA private key does not have all the fields.");
          goto failed;
        }

      printf("RSA Private Key");
      printf("  e = [Encryption exponent]\n");
      keygen_out_number(kgc, &e);

      printf("  n = [Large composite moduli]\n");
      keygen_out_number(kgc, &n);

      printf("  d = [Inverse of e modulo (p-1)*(q-1) (Euler's phi function of n)]\n");
      keygen_out_number(kgc, &d);

      printf("  p = [Large random prime]\n");
      keygen_out_number(kgc, &p);

      printf("  q = [Large random prime]\n");
      keygen_out_number(kgc, &q);

      printf("  u = [Multiplicative inverse of p modulo q, used to speed RSA]\n");
      keygen_out_number(kgc, &u);

      ssh_mp_clear(&n);
      ssh_mp_clear(&e);
      ssh_mp_clear(&d);
      ssh_mp_clear(&p);
      ssh_mp_clear(&q);
      ssh_mp_clear(&u);

      return FALSE;
    }

  if (strcmp(name, "dl-modp") == 0)
    {
      SshInt p, q, g, y, x;

      ssh_mp_init(&p);
      ssh_mp_init(&q);
      ssh_mp_init(&g);
      ssh_mp_init(&y);
      ssh_mp_init(&x);

      if (ssh_private_key_get_info(prv,
                                   SSH_PKF_PRIME_P, &p,
                                   SSH_PKF_PRIME_Q, &q,
                                   SSH_PKF_GENERATOR_G, &g,
                                   SSH_PKF_PUBLIC_Y, &y,
                                   SSH_PKF_SECRET_X, &x,
                                   SSH_PKF_END) != SSH_CRYPTO_OK)
        {
          ssh_mp_clear(&p);
          ssh_mp_clear(&g);
          ssh_mp_clear(&q);
          ssh_mp_clear(&y);
          ssh_mp_clear(&x);

          printf("DSA private key does not have all the fields.");
          goto failed;
        }

      /* DSA */
      printf("DSA Private Key\n");

      printf("  p = [Large prime, characteristic of the finite field]\n");
      keygen_out_number(kgc, &p);

      printf("  g = [Generator of a cyclic subgroup of the multiplicative group F_p]\n");
      keygen_out_number(kgc, &g);

      printf("  q = [Prime number, order of the cyclic group]\n");
      keygen_out_number(kgc, &q);

      printf("  y = [Random power of the generator, the 'actual' public key]\n");
      keygen_out_number(kgc, &y);

      printf("  x = [A random number, the 'actual' secret key]\n");
      keygen_out_number(kgc, &x);

      ssh_mp_clear(&p);
      ssh_mp_clear(&g);
      ssh_mp_clear(&q);
      ssh_mp_clear(&y);
      ssh_mp_clear(&x);

      return FALSE;
    }

failed:
  return TRUE;
}

Boolean keygen_view_pubkey(KeyGenCtx *kgc, SshPublicKey prv)
{
  const char *name;

  if (ssh_public_key_get_info(prv, SSH_PKF_KEY_TYPE, &name,
                               SSH_PKF_END) != SSH_CRYPTO_OK)
    {
      printf("Public key has no type (corrupted)\n");
      goto failed;
    }

  if (strcmp(name, "if-modn") == 0)
    {
      SshInt n, e, d, p, q, u;

      /* RSA? */
      ssh_mp_init(&n);
      ssh_mp_init(&e);
      ssh_mp_init(&d);
      ssh_mp_init(&p);
      ssh_mp_init(&q);
      ssh_mp_init(&u);

      /* Get the necessary information of the SSH style RSA key. */
      if (ssh_public_key_get_info(prv,
                                  SSH_PKF_MODULO_N,  &n,
                                  SSH_PKF_PUBLIC_E,  &e,
                                  SSH_PKF_END) != SSH_CRYPTO_OK)
        {
          ssh_mp_clear(&n);
          ssh_mp_clear(&e);
          ssh_mp_clear(&d);
          ssh_mp_clear(&p);
          ssh_mp_clear(&q);
          ssh_mp_clear(&u);
          printf("RSA public key does not have all the fields.");
          goto failed;
        }

      printf("RSA Public Key");
      printf("  e = [Encryption exponent]\n");
      keygen_out_number(kgc, &e);

      printf("  n = [Large composite moduli]\n");
      keygen_out_number(kgc, &n);

      ssh_mp_clear(&n);
      ssh_mp_clear(&e);
      ssh_mp_clear(&d);
      ssh_mp_clear(&p);
      ssh_mp_clear(&q);
      ssh_mp_clear(&u);

      return FALSE;
    }

  if (strcmp(name, "dl-modp") == 0)
    {
      SshInt p, q, g, y, x;

      ssh_mp_init(&p);
      ssh_mp_init(&q);
      ssh_mp_init(&g);
      ssh_mp_init(&y);
      ssh_mp_init(&x);

      if (ssh_public_key_get_info(prv,
                                  SSH_PKF_PRIME_P, &p,
                                  SSH_PKF_PRIME_Q, &q,
                                  SSH_PKF_GENERATOR_G, &g,
                                  SSH_PKF_PUBLIC_Y, &y,
                                  SSH_PKF_END) != SSH_CRYPTO_OK)
        {
          ssh_mp_clear(&p);
          ssh_mp_clear(&g);
          ssh_mp_clear(&q);
          ssh_mp_clear(&y);
          ssh_mp_clear(&x);

          printf("DSA public key does not have all the fields.");
          goto failed;
        }

      /* DSA */
      printf("DSA Public Key\n");

      printf("  p = [Large prime, characteristic of the finite field]\n");
      keygen_out_number(kgc, &p);

      printf("  g = [Generator of a cyclic subgroup of the multiplicative group F_p]\n");
      keygen_out_number(kgc, &g);

      printf("  q = [Prime number, order of the cyclic group]\n");
      keygen_out_number(kgc, &q);

      printf("  y = [Random power of the generator, the 'actual' public key]\n");
      keygen_out_number(kgc, &y);

      ssh_mp_clear(&p);
      ssh_mp_clear(&g);
      ssh_mp_clear(&q);
      ssh_mp_clear(&y);
      ssh_mp_clear(&x);

      return FALSE;
    }

failed:
  return TRUE;
}


/* Generate the key. this is done when kgc->newkey is TRUE. */

int keygen_keygen(KeyGenCtx *kgc)
{
  SshCryptoStatus code;
  char buf[1024];
  int incr;
  char *pass = NULL;
  int r = 0;

  /* Register our progress indicator. */

  incr = 0;
  if (kgc->prog_ind == FALSE && kgc->have_prog_ind == TRUE)
    {
      ssh_crypto_library_register_progress_func(keygen_prog_ind, &incr);
      kgc->prog_ind = TRUE;
    }

  printf("Generating %d-bit %s key pair\n",
         kgc->keybits,
         kgc->keytypecommon);

  if ((code = ssh_private_key_generate(&(kgc->private_key),
                                       kgc->keytype,
                                       SSH_PKF_SIZE, kgc->keybits,
                                       SSH_PKF_END)) != SSH_CRYPTO_OK)
    {
      keygen_error(kgc, (char *) ssh_crypto_status_message(code));
    }
  printf("\nKey generated.\n");

  printf("%s\n", kgc->comment);

  /* Ok, now save the private key. */

  keygen_choose_filename(kgc);

  if ((! kgc->passphrase) || (! (*(kgc->passphrase)))) {
    if (!(kgc->pass_empty))
      {
        pass = ssh_read_passphrase_twice("Passphrase : ", 
                                         "Again      : ", 
                                         FALSE);
      }
    else
      {
        pass = ssh_xstrdup("");
      }
    keygen_free_str(kgc->passphrase);
    kgc->passphrase = pass ? pass : ssh_xstrdup("");
  }

  if (!(*(kgc->passphrase)))
    {
      ssh_informational("Key is stored with NULL passphrase.\n");
      ssh_informational("(This is not recommended. \n");
      ssh_informational(" Don't do this unless you know what you're doing.\n");
      ssh_informational(" If file system protections fail (someone can "
                        " access the keyfile), \n");
      ssh_informational(" or if the super-user is malicious, your key can "
                        " be used without \n");
      ssh_informational(" the deciphering effort.)\n");
    }

  if (ssh_privkey_write(kgc->user,
                        kgc->out_filename, 
                        kgc->passphrase, 
                        kgc->comment,
                        kgc->private_key, NULL))
    {
      ssh_warning("Private key not written !");
      r++;
    }
  else 
    {
      printf("Private key saved to %s\n", kgc->out_filename);
    }

  /* Save the public key */

  snprintf(buf, sizeof (buf), "%s.pub", kgc->out_filename);
  kgc->public_key = ssh_private_key_derive_public_key(kgc->private_key);
  if (kgc->public_key == NULL)
    {
      ssh_warning("Could not derive public key from private key.");
      return r + 1;
    }
  
  if (ssh_pubkey_write(kgc->user, buf, kgc->comment, kgc->public_key, NULL))
    {
      ssh_warning("Public key not written !");
      r++;
    }
  else
    {
      printf("Public key saved to %s\n", buf);
    }
  return r;
}

/* Stir in data from file */

void stir_from_file(KeyGenCtx *kgc)
{
  unsigned char *buffer;
  size_t buffer_len;

  if (FALSE == ssh_read_file(kgc->entropy_file_name, &buffer, &buffer_len))
    {
      ssh_fatal("Cannot read file %s.", kgc->entropy_file_name);
      return;
    }
  ssh_random_add_noise(buffer, buffer_len);
  memset(buffer, 0, buffer_len);
  ssh_xfree(buffer);
  ssh_random_stir();
  if (kgc->have_prog_ind)
    printf("Stirred in %lu bytes.\n", (unsigned long) buffer_len);
}

int keygen_convert_key(KeyGenCtx *kgc)
{
  ssh_fatal("Key conversion not yet supported.");
  return -1;
}

int keygen_edit_key(KeyGenCtx *kgc)
{
  SshPrivateKey seckey = NULL;
  SshPublicKey pubkey = NULL;
  char pubfn[1024];
  char outpubfn[1024];
  char pubbu[1024];
  char secbu[1024];
  char *secfn;
  char *outsecfn;
  int ok;
  int ed;
  char *oldcomment = NULL;
  char *newcomment = NULL;
  char *newpass = NULL;

  if (!(*(kgc->in_filename))) 
    {
      ssh_warning("Invalid keyfile.");
      return 1;
    }
  else
    {
      secfn = kgc->in_filename;
      snprintf(pubfn, sizeof (pubfn), "%s.pub", secfn);
    }
  
  if (!(kgc->out_filename))
    {
      kgc->out_filename = ssh_xstrdup(kgc->in_filename);
    }

  outsecfn = kgc->out_filename;
  snprintf(outpubfn, sizeof (outpubfn), "%s.pub", outsecfn);

  snprintf(secbu, sizeof (secbu), "%s~", kgc->in_filename);
  snprintf(pubbu, sizeof (pubbu), "%s~", pubfn);
  (void)unlink(secbu);
  (void)unlink(pubbu);

  pubkey = ssh_pubkey_read(kgc->user, pubfn, &oldcomment, NULL);

  if (! pubkey)
    {
      ssh_warning("Cannot read public keyfile %s.", pubfn);
      return 1;
    }

  if (! oldcomment)
    oldcomment = ssh_xstrdup("");

  if (kgc->passphrase)
    {
      seckey = ssh_privkey_read(kgc->user, secfn, kgc->passphrase, NULL, NULL);
    }

  if (! seckey)
    {
      seckey = ssh_privkey_read(kgc->user, secfn, "", NULL, NULL);
      if (seckey)
        {
          keygen_free_str(kgc->passphrase);
          kgc->passphrase = ssh_xstrdup("");
        }
    }
    
  if (! kgc->pass_empty)
    {
      if (! seckey)
        {
          keygen_free_str(kgc->passphrase);
          printf("Passphrase needed for key \"%s\".\n", oldcomment);
          kgc->passphrase = ssh_read_passphrase("Passphrase : ", FALSE);
          if (kgc->passphrase)
            {
              seckey = ssh_privkey_read(kgc->user, secfn, kgc->passphrase, 
                                        NULL, NULL);
            }
        }
    }

  if (! seckey)
    {
      ssh_warning("Cannot read private keyfile %s.", secfn);
      return 1;
    }

  printf("Do you want to edit key \"%s\" ", oldcomment);
  if (! ssh_read_confirmation("(yes or no)? ", FALSE))
    {
      printf("Key unedited and unsaved.\n");
      keygen_free_str(oldcomment);
      return 0;
    }

  ok = 0;
  ed = 0;

  while (! ok)
    {
      if (! newcomment)
        newcomment = ssh_xstrdup(oldcomment);
      printf("Your key comment is \"%s\". ", newcomment);
      if (ssh_read_confirmation("Do you want to edit it (yes or no)? ", FALSE))
        {
          if (ssh_readline((unsigned char *)"New key comment: ", 
                           (unsigned char **)&newcomment, 
                           fileno(stdin), fileno(stdout)) < 0)
            {
              fprintf(stderr, "Abort!  Key unedited and unsaved.\n");
              keygen_free_str(newpass);
              keygen_free_str(newcomment);
              keygen_free_str(oldcomment);
              return 1;
            }
          putchar('\n');
        }
      if (! newpass)
        newpass = ssh_xstrdup(kgc->passphrase);
      if (! kgc->pass_empty)
        {
          if (ssh_read_confirmation("Do you want to edit passphrase (yes or no)? ", FALSE))
            {
              keygen_free_str(newpass);
              newpass = ssh_read_passphrase_twice("New passphrase : ", 
                                                  "Again          : ", 
                                                  FALSE);
              if (! newpass)
                {
                  fprintf(stderr, "Abort!  Key unedited and unsaved.\n");
                  keygen_free_str(newcomment);
                  keygen_free_str(oldcomment);
                  return 0;
                }
            }
        }
      printf("Do you want to continue editing key \"%s\" ", newcomment);
      if (ssh_read_confirmation("(yes or no)? ", FALSE))
        ok = 0;
      else
        ok = 1;
    }
  if ((strcmp(newpass, kgc->passphrase) == 0) &&
      (strcmp(newcomment, oldcomment) == 0))
    {
      printf("Key unedited and unsaved.\n");
      keygen_free_str(newpass);
      keygen_free_str(newcomment);
      keygen_free_str(oldcomment);
      return 0;
    }
  printf("Do you want to save key \"%s\" to file %s ", newcomment, outsecfn);
  if (! ssh_read_confirmation("(yes or no)? ", FALSE))
    {
      printf("Key unsaved.\n");
      keygen_free_str(newpass);
      keygen_free_str(newcomment);
      keygen_free_str(oldcomment);
      return 0;
    }
  
  if (strcmp(outsecfn, kgc->in_filename) == 0)
    {
      if (rename(outsecfn, secbu) != 0)
        {
          ssh_warning("Unable to backup private key.");
          keygen_free_str(newpass);
          keygen_free_str(newcomment);
          keygen_free_str(oldcomment);
          fprintf(stderr, "Abort!\n");
          return 1;
        }
    }

  if (strcmp(outpubfn, pubfn) == 0)
    {
      if (rename(outpubfn, pubbu) != 0)
        {
          ssh_warning("Unable to backup private key.");
          if (strcmp(outsecfn, secfn) == 0)
            (void)rename(secbu, outsecfn);
          keygen_free_str(newpass);
          keygen_free_str(newcomment);
          keygen_free_str(oldcomment);
          fprintf(stderr, "Abort!\n");
          return 1;
        }
    }

  if (ssh_privkey_write(kgc->user,
                        outsecfn, 
                        newpass, 
                        newcomment,
                        seckey, 
                        NULL))
    {
      ssh_warning("Unable to write private key.!");
      if (strcmp(outsecfn, secfn) == 0)
        (void)rename(secbu, outsecfn);
      if (strcmp(outpubfn, pubfn) == 0)
        (void)rename(pubbu, outpubfn);
      keygen_free_str(newpass);
      keygen_free_str(newcomment);
      keygen_free_str(oldcomment);
      fprintf(stderr, "Abort!\n");
      return 1;
    }

  if (ssh_pubkey_write(kgc->user,
                       outpubfn,
                       newcomment,
                       pubkey, 
                       NULL))
    {
      ssh_warning("Unable to write public key.!");
      unlink(outsecfn);
      if (strcmp(outsecfn, secfn) == 0)
        (void)rename(secbu, outsecfn);
      if (strcmp(outpubfn, pubfn) == 0)
        (void)rename(pubbu, outpubfn);
      keygen_free_str(newpass);
      keygen_free_str(newcomment);
      keygen_free_str(oldcomment);
      fprintf(stderr, "Abort!\n");
      return 1;
    }

  keygen_free_str(newpass);
  keygen_free_str(newcomment);
  keygen_free_str(oldcomment);

  return 0;
}

Boolean keygen_dump_pubkey_fingerprint(KeyGenCtx *kgc, SshPublicKey pubkey)
{
  char *hash_name = "sha1";
  unsigned char *digest;
  SshHash hash_ctx;
  size_t digest_len;
  unsigned char *blob;
  size_t blob_len;
  char *fingerprint;
  
  if (!(blob_len = ssh_encode_pubkeyblob(pubkey, &blob)))
    {
      ssh_warning("keygen_dump_pubkey_fingerprint: Couldn't encode publickey "
                  "to blob.");
      return FALSE;
    }
  
  if (!ssh_hash_supported(hash_name))
    {
      ssh_warning("keygen_dump_pubkey_fingerprint: Hash \"%s\" not supported, "
                  "even though it is required in the draft. Contact "
                  "ssh2-bugs@ssh.com.", hash_name);
      return FALSE;
    }

  if (ssh_hash_allocate(hash_name, &hash_ctx) != SSH_CRYPTO_OK)
    {
      ssh_warning("keygen_dump_pubkey_fingerprint: Couldn't initialize hash "
                  "function.", hash_name);
      return FALSE;
    }  

  digest_len = ssh_hash_digest_length(hash_ctx);
  digest = ssh_xcalloc(digest_len, sizeof(unsigned char));
  ssh_hash_update(hash_ctx, blob, blob_len);
  ssh_hash_final(hash_ctx, digest);
  ssh_hash_free(hash_ctx);

  fingerprint =
    ssh_fingerprint(digest, digest_len, kgc->fingerprint_type);

  fprintf(stderr, "Fingerprint for key:\r\n");
  printf("%s\r\n", fingerprint);

  ssh_xfree(blob);
  ssh_xfree(fingerprint);
  ssh_xfree(digest);
  return TRUE;
}

/* main for ssh-keygen */

int main(int argc, char **argv)
{
  int ch, i;
  KeyGenCtx *kgc;
  struct passwd *pw;
  char *t;
  SshTime now;
  int r = 0, rr = 0;
  char *progname;

  if (strchr(argv[0], '/'))
    progname = strrchr(argv[0], '/') + 1;
  else
    progname = argv[0];

  /* Initialize the context */
  kgc = keygen_init_ctx();

  /* try to find the user's home directory */

  while ((ch = ssh_getopt(argc, argv, "vhqr:?P1:t:b:p:i:D:B:e:c:VF:", NULL))
         != EOF)
    {
      if (!ssh_optval)
        {
          printf("%s", keygen_helptext);
          keygen_free_ctx(kgc);
          exit(1);
        }
      switch (ch) 
        {
          /* -b: specify the strength of the key */

        case 'b':
          if (kgc->keybits != 0)
            keygen_error(kgc, "Multiple -b parameters");
          kgc->keybits = atoi(ssh_optarg);
          if (kgc->keybits < 512 || kgc->keybits > 0x10000)
            keygen_error(kgc, "Illegal key size.");
          break;

          /* -t: specify the key type. */
        case 't':
          /* the kgc->keytype gets the crypto library name of the alg. */
          
          if (kgc->keytype != NULL)
            keygen_error(kgc, "multiple key types specified.");

          for (i = 0; keygen_common_names[i][0] != NULL; i++)
            {
              if (strcasecmp(keygen_common_names[i][0], ssh_optarg) == 0)
                {
                  kgc->keytypecommon = ssh_xstrdup(keygen_common_names[i][0]);
                  kgc->keytype = ssh_xstrdup(keygen_common_names[i][1]);
                  break;
                }
            }
          if (keygen_common_names[i][0] == NULL)
            keygen_error(kgc, "unknown key type.");
          break;

          /* -c: Comment string. */
        case 'c':
          kgc->comment = ssh_xstrdup(ssh_optarg);
          break;

          /* -e: Edit key file. */
        case 'e':
          kgc->edit_key = TRUE;
          kgc->in_filename = ssh_xstrdup(ssh_optarg);

          /* -p: Provide passphrase. */
        case 'p':
          kgc->passphrase = ssh_xstrdup(ssh_optarg);
          break;

          /* -P: Don't provide passphrase. */
        case 'P':
          kgc->pass_empty = TRUE;
          keygen_free_str(kgc->passphrase);
          kgc->passphrase = NULL;
          break;

          /* -1: Convert a key from SSH1 format to SSH2 format. */
        case '1':
          kgc->convert = TRUE;
          kgc->in_filename = ssh_xstrdup(ssh_optarg);
          break;

          /* -V: print the version number */
        case 'V':
          printf("%s version " SSH2_VERSION
                 ", compiled "__DATE__".\n", progname);
          /* XXX more stuff here possibly */ 
          keygen_free_ctx(kgc);
          exit(0);

        case '?':
        case 'h':
          printf("%s", keygen_helptext);
          keygen_free_ctx(kgc);
          exit(0);

          /* -q: supress the progress indicator */
        case 'q':
          kgc->have_prog_ind = FALSE;
          break;

          /* -r: stir in data from file to the random pool */
        case 'r':
          kgc->read_file = TRUE;
          kgc->entropy_file_name = ssh_xstrdup(ssh_optarg);
          break;

          /* XXX Following are experimental options. */
          
          /* -i: display (all) information about a key */
        case 'i':
          kgc->info_file = ssh_xstrdup(ssh_optarg);
          break;
        case 'D':
          kgc->derive_file = ssh_xstrdup(ssh_optarg);
          break;
        case 'B':
          kgc->base = atoi(ssh_optarg);
          break;
        case 'F':
          kgc->dump_fingerprint = TRUE;
          ssh_xfree(kgc->info_file);
          kgc->info_file = ssh_xstrdup(ssh_optarg);
          break;
        default:
          keygen_error(kgc, "Invalid option.");
          break;
        }
    }

  /* Stir in random data from file, if requested */
  if (kgc->read_file)
    {
      stir_from_file(kgc);
    }

  /* Dump fingerprint */
  if (kgc->dump_fingerprint)
    {
      SshPublicKey pubkey = NULL;

      if (!kgc->info_file)
        ssh_fatal("-F option needs public key file as argument.");
      
      pubkey = ssh_pubkey_read(kgc->user, kgc->info_file,
                               NULL, NULL);
      if (pubkey != NULL)
        {
          if (keygen_dump_pubkey_fingerprint(kgc, pubkey))
            r = 0;
          else
            r = 1;
          
          goto finish;
        }
      else
        {
          ssh_fatal("Couldn't read public key \"%s\".", kgc->info_file);
        }
    }
  
  /* Perhaps we should derive a key? */
  if (kgc->derive_file)
    {
      SshPrivateKey prvkey = NULL;
      SshPublicKey  pubkey = NULL;
      unsigned char buf[256];

      pubkey = ssh_pubkey_read(kgc->user, kgc->derive_file,
                               NULL, NULL);
      if (pubkey != NULL)
        {
          /* Attempt at deriving the private key? */
          printf("\n  Derivation of a private key from the public key.\n"
                 "   Please wait...\n");
          sleep(1);
          printf("\n  Ho ho, just kidding.\n"
                 "   Cryptographically strong keys cannot be broken!\n");
          goto finish;
        }

      /* Perhaps its a private key? */
      prvkey = keygen_read_privkey(kgc, kgc->derive_file);
      
      if (prvkey != NULL)
        {
          /* Derive the appropriate public key. */
          snprintf(buf, sizeof (buf), "%s.pub", kgc->derive_file);
          pubkey = ssh_private_key_derive_public_key(prvkey);
          if (pubkey == NULL)
            {
              keygen_error(kgc, "Could not derive public key from private key.");
            }

          if (ssh_pubkey_write(kgc->user, buf, kgc->comment, pubkey, NULL))
            {
              keygen_error(kgc, "Public key not written !");
            }
          printf("Public key saved to %s\n", buf);

          ssh_public_key_free(pubkey);
          ssh_private_key_free(prvkey);
          goto finish;
        }

      keygen_error(kgc, "Cannot determine the type of the key.");
    }

  if (kgc->info_file)
    {
      SshPrivateKey prvkey = NULL;
      SshPublicKey  pubkey = NULL;

      pubkey = ssh_pubkey_read(kgc->user, kgc->info_file,
                               NULL, NULL);
      if (pubkey != NULL)
        {
          keygen_view_pubkey(kgc, pubkey);
          goto finish;
        }

      /* Perhaps its a private key? */
      prvkey = keygen_read_privkey(kgc, kgc->info_file);
      
      if (prvkey != NULL)
        {
          keygen_view_prvkey(kgc, prvkey);
          goto finish;
        }

      keygen_error(kgc, "Cannot determine the type of the key.");
    }
        
  if (kgc->convert)
    {
      r = keygen_convert_key(kgc);
    }
  else if (kgc->edit_key)
    {
      r = keygen_edit_key(kgc);
    }
  else
    {
      kgc->newkey = TRUE;

      if (kgc->keybits == 0)
        {
          kgc->keybits = KEYGEN_ASSUMED_PUBKEY_LEN;
        }

      if (kgc->keytype == NULL)
        {
          kgc->keytypecommon = ssh_xstrdup(keygen_common_names[0][0]);
          kgc->keytype = ssh_xstrdup(keygen_common_names[0][1]);
        }

      if (kgc->comment == NULL)
        {
          char *time_str;

          pw = getpwuid(getuid());
          if (!pw)
            keygen_error(kgc, "Could not get user's password structure.");
          t = ssh_xmalloc(64);
          gethostname(t, 64);
          kgc->comment = ssh_xmalloc(256);
          now = ssh_time();
          time_str = ssh_readable_time_string(now, TRUE);
          snprintf(kgc->comment, 256, "%d-bit %s, %s@%s, %s", 
                   kgc->keybits, kgc->keytypecommon,
                   pw->pw_name, t, time_str);
          ssh_xfree(time_str);
          ssh_xfree(t);
        }

      if (ssh_optind >= argc)
        {
          /* generate single key. if no file names given, make up one. */
          if (kgc->out_filename == NULL)
            keygen_choose_filename(kgc);
          r = keygen_keygen(kgc);
        }
      else
        { 
          if (kgc->out_filename != NULL)
            keygen_keygen(kgc);

          /* iterate over additional filenames */

          for (i = ssh_optind; i < argc; i++)
            {
              if (kgc->out_filename != NULL)
                ssh_xfree(kgc->out_filename);
              kgc->out_filename = ssh_xstrdup(argv[i]);
              rr = keygen_keygen(kgc);
              if (rr != 0)
                {
                if (r == 0)
                  r = rr;
                else
                  r = -1;
                }
            }
        }
    }

finish:
  
  keygen_free_ctx(kgc);

  return r;
}
