/* $Id: ssh-keygen.c,v 1.36 2001/02/11 03:35:09 tls Exp $ */ /* * Copyright (c) 2000 Jason R. Thorpe. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright 1999 RedBack Networks, Incorporated. * All rights reserved. * * This software is not in the public domain. It is distributed * under the terms of the license in the file LICENSE in the * same directory as this file. If you have received a copy of this * software without the LICENSE file (which means that whoever gave * you this software violated its license) you may obtain a copy from * http://www.panix.com/~tls/LICENSE.txt */ #include #include #include #include #include #include #include #include #include #include #include "options.h" #include "sshd.h" #include "ssh_parse.h" #include "ssh_util.h" #include "ssh_rsakeys.h" #include "ssh_sys.h" #include "ssh_cipher.h" #include "ssh_ui.h" #include "pathnames.h" int whichprime; ssh_context_t g_context; /* * Generate Secure Shell authentication keys. * * Options shared by all commands: * * [-D] Increase debugging level * [-f keyfile[:publickeyfile]] * [-F ] Format is one of: * FreSSH FreSSH format (deprecated) * * F-Secure F-Secure format (default) * fsecure * OpenSSH * * Print a help screen: * * ssh-keygen -h * * Generate a new key: * * ssh-keygen [-q] [-b bits] [-N new-passphrase] [-C comment] * [-t pub|priv|both] * * Change a passphrase or comment on an existing key: * * ssh-keygen [-p|-c] [-P old-passphrase] [-N new-passphrase] [-C comment] * * Remove a passphrase from a key (compatbility with old FreSSH): * * ssh-keygen -u [-P old-passphrase] * * Default filenames are $HOME/.ssh/identity.{,pub} * Default comment is `id -nu`@`hostname` * Default file format is F-Secure. * Default bits is 1024. * Minimum bits this version will generate is 1024. * Automatically rewrites in the same format as read. * Can convert/write to different format with -F. (Default to F-Secure) * Can regenerate a public key from a private. (-t pub) * Can write only a private key. (-t priv) * -F and -t are case insensitive and only check first two characters. * * Examples: * Generate a 2048 bit F-Secure format key with no passphrase: * ssh-keygen -b 2048 -N "" * Generate a FreSSH format key: * ssh-keygen -F fressh * Convert a key in file foo to FreSSH format: * ssh-keygen -f foo -F fr * Generate a F-Secure key in foo and foo.pub: * ssh-keygen -f foo * Change the passphrase from "aaa" to "bbb", set the comment * to "joe@bob.com" and convert to a F-Secure format: * ssh-keygen -c -P "aaa" -N "bbb" -C "joe@bob.com -F FSecure */ struct target_file { char *name; off_t len; char *buf; int type; mode_t mode; }; struct ops { #define KEYFILE_BOTH 0 #define KEYFILE_PRIV 1 #define KEYFILE_PUB 2 int filetype; /* Type of key file to write. */ int format; /* Format it was written as. */ int new_format; /* Format to write it in. */ int change_mode; /* if -c or -p, otherwise new key */ int quiet; /* be quiet when generating key */ struct target_file fpriv; struct target_file fpub; char *passphrase; char *new_passphrase; char *comment; char *new_comment; int newkey; int bits; ssh_RSA *key; } ops; void Usage(const char *); void parse_filename_opt(struct ops *, char *); void keygen_cleanup(struct ops *); void default_filename(struct target_file *, struct target_file *); int ssh_generate_key(struct ops *, ssh_RSA **); int ssh_setdefaultcomment(char **); void genkey_callback(int, int, void *); int write_file(struct target_file *); int read_file(struct target_file *); int write_file(struct target_file * finfo) { int d; SSH_DLOG(4, ("writing %s keyfile: %s %d\n", finfo->type == KEYFILE_PRIV ? "private" : "public", finfo->name, finfo->len)); if ((d = open(finfo->name, O_RDWR | O_CREAT | O_TRUNC, finfo->mode)) < 0) { SSH_ERROR("Unable to open keyfile: %s\n", strerror(errno)); return (-1); } if (write(d, finfo->buf, finfo->len) != finfo->len) { SSH_ERROR("Unable to write keyfile: %s\n", strerror(errno)); close(d); return (-1); } close(d); if (ops.quiet == 0) printf("Your %s key has been saved in %s\n", finfo->type == KEYFILE_PRIV ? "private" : "public", finfo->name); return (0); } int read_file(struct target_file * finfo) { char *buf; buf = ssh_sys_readin(finfo->name, &finfo->len); if (buf == NULL) return (-1); if ((finfo->buf = malloc(finfo->len + 1)) == NULL) { ssh_sys_unreadin(buf, finfo->len); return (-1); } memcpy(finfo->buf, buf, finfo->len); finfo->buf[finfo->len] = '\0'; ssh_sys_unreadin(buf, finfo->len); return (0); } int main(int argc, char *argv[]) { int ret, ch; char string[160]; ssh_context_t *context = &g_context; ret = 0; /* * Initialize the context. This is needed by the keyfile * parsing functions only to know what role we are playing. * We initialize it as CLIENT role, since we are not using * any ciphers that would require knowledge of what we * actually are. */ memset(context, 0, sizeof(*context)); context->running_as_server = SSH_ROLE_NEITHER; /* Initialize logging, only to stderr. */ loginit("ssh-keygen", 2); /* Clear everything. */ memset(&ops, 0, sizeof(struct ops)); ops.fpriv.type = KEYFILE_PRIV; ops.fpriv.mode = 0600; ops.fpub.type = KEYFILE_PUB; ops.fpub.mode = 0644; while ((ch = getopt(argc, argv, "Dt:F:pcuf:b:P:N:C:hq")) != -1) { switch (ch) { case 't': /* Type for keyfile: public or private. */ if (strlen(optarg) < 2) Usage("Unknown keyfile type"); optarg[0] = toupper(optarg[0]); optarg[1] = toupper(optarg[1]); if (strncmp(optarg, "PR", 2) == 0) ops.filetype = KEYFILE_PRIV; else if (strncmp(optarg, "PU", 2) == 0) ops.filetype = KEYFILE_PUB; else if (strncmp(optarg, "BO", 2) == 0) ops.filetype = KEYFILE_BOTH; else Usage("Unknown keyfile type"); break; case 'F': /* Format of keyfile: FreSSH or F-Secure. */ if (strlen(optarg) < 2) Usage("Unknown keyfile format"); optarg[0] = toupper(optarg[0]); optarg[1] = toupper(optarg[1]); if (strncmp(optarg, "FR", 2) == 0) ops.new_format = FRESSH_PRIV_KEYFILE; else if (strncmp(optarg, "F-", 2) == 0 || strncmp(optarg, "FS", 2) == 0 || strncmp(optarg, "OP", 2) == 0) ops.new_format = FSECURE_PRIV_KEYFILE; else Usage("Unknown keyfile format"); break; case 'D': debug_inc(0); break; case 'u': /* * This is a compatibility option. It is * equivalent to: * * -c -N "" */ ops.change_mode = 1; ops.new_passphrase = strdup(""); break; case 'c': case 'p': ops.change_mode = 1; break; case 'f': parse_filename_opt(&ops, optarg); break; case 'b': ops.bits = atoi(optarg); break; case 'P': ops.passphrase = optarg; break; case 'N': ops.new_passphrase = optarg; break; case 'C': ops.new_comment = optarg; break; case 'q': ops.quiet = 1; break; case 'h': Usage(NULL); /* NOTREACHED */ default: Usage("Unknown option"); /* NOTREACHED */ } } /* Fill in defaults for unset names. */ default_filename(&ops.fpriv, &ops.fpub); SSH_DLOG(4, ("Loading:%s\n", ops.fpriv.name)); if (read_file(&ops.fpriv) < 0) { SSH_DLOG(4, ("read_file(priv) failed: %s\n", strerror(errno))); SSH_DLOG(4, ("setting newkey flag.\n")); ops.newkey = 1; } else if (ops.change_mode == 0) { char *ans; printf("Private key file %s already exists.\n", ops.fpriv.name); if (ssh_ui_prompt(&ans, "Overwrite? (y/n) ", 1) < 0) { fprintf(stderr, "Unable to get response\n"); exit (0); } if (ans[0] == 'y' || ans[0] == 'Y') ops.newkey = 1; else exit(0); free(ans); } if (ops.filetype == KEYFILE_PUB && ops.newkey) Usage("Can't generate public key without a private key."); if (ops.newkey == 0 && ops.bits) Usage("Can't generate new key: file exists."); if (ops.newkey && ops.passphrase) Usage("Use -N to set the passphrase for a new key."); if (ops.newkey) { SSH_DLOG(4, ("Generating new key\n")); if (ssh_generate_key(&ops, &ops.key) != 0) { SSH_ERROR("Unable to generate key: %s\n", strerror(errno)); ret = 1; goto out; } if (ops.new_comment == NULL) ssh_setdefaultcomment(&(ops.new_comment)); snprintf(string, sizeof(string), "Enter passphrase for RSA key '%s': ", ops.new_comment); if (ops.new_passphrase == NULL) { char *verify; int dont_match; for (dont_match = 1; dont_match != 0;) { if (ssh_ui_prompt(&ops.new_passphrase, string, 0) < 0) { SSH_ERROR("Unable to read " "passphrase\n"); ret = 1; goto out; } if (ssh_ui_prompt(&verify, "Enter same passphrase again: ", 0) < 0) { SSH_ERROR("Unable to verify " "passphrase\n"); memset(ops.new_passphrase, 0, strlen(ops.new_passphrase)); free(ops.new_passphrase); ops.new_passphrase = NULL; ret = 1; goto out; } dont_match = strcmp(ops.new_passphrase, verify); memset(verify, 0, strlen(verify)); free(verify); if (dont_match) { ssh_ui_message("Passphrases do not " "match. Try again."); memset(ops.new_passphrase, 0, strlen(ops.new_passphrase)); free(ops.new_passphrase); ops.new_passphrase = NULL; } } } /* nothing */ ; SSH_DLOG(4, ("Comment set to: %s\n", ops.new_comment)); ops.format = FSECURE_PRIV_KEYFILE; } else { /* Try decoding w/o a passphrase first. */ ch = decode_keyfile(context, ops.fpriv.buf, ops.fpriv.len, NULL, 0, &ops.key, &ops.comment, &ops.format); if (ch == DECODE_KEYFILE_FAILED) { SSH_ERROR("Unable to decode keyfile.\n"); ret = 1; goto out; } else if (ch == DECODE_KEYFILE_PASSPHRASE) { snprintf(string, sizeof(string), "Enter passphrase for RSA key '%s': ", ops.comment); if (ops.passphrase == NULL) { if (ssh_ui_prompt(&ops.passphrase, string, 0) < 0) { SSH_ERROR("Unable to read " "passphrase\n"); goto out; } } /* Now try decoding with a passphrase. */ ch = decode_keyfile(context, ops.fpriv.buf, ops.fpriv.len, ops.passphrase, strlen(ops.passphrase), &ops.key, &ops.comment, &ops.format); if (ch == DECODE_KEYFILE_FAILED) { SSH_ERROR("Unable to decode keyfile.\n"); ret = 1; goto out; } else if (ch == DECODE_KEYFILE_PASSPHRASE) { fprintf(stderr, "Bad passphrase.\n"); ret = 1; goto out; } } free(ops.fpriv.buf); ops.fpriv.buf = NULL; } /* We now are guaranteed a key in memory. */ /* Set the comment to write to the file. */ if (ops.new_comment == NULL) ops.new_comment = ops.comment; if (is_debug_level(4)) { if (ops.comment == NULL || strcmp(ops.new_comment, ops.comment) != 0) SSH_DLOG(4, ("new comment: %s\n", ops.new_comment)); } /* Set the format to write with. */ if (ops.new_format == 0) ops.new_format = ops.format; /* * Make sure the new passphrase is set. Note that * encode_keyfile() expects a non-NULL passphrase, * even if it's empty. */ if (ops.new_passphrase == NULL) { if (ops.passphrase == NULL) ops.new_passphrase = strdup(""); else ops.new_passphrase = ops.passphrase; } if (ops.filetype == KEYFILE_PRIV || ops.filetype == KEYFILE_BOTH) { ops.fpriv.len = encode_keyfile(context, ops.new_format, &ops.fpriv.buf, ops.new_passphrase, strlen(ops.new_passphrase), ops.key, ops.new_comment); } if (ops.filetype == KEYFILE_PUB || ops.filetype == KEYFILE_BOTH) { ops.fpub.len = encode_keyfile(context, FRESSH_PUB_KEYFILE, &ops.fpub.buf, ops.new_passphrase, strlen(ops.new_passphrase), ops.key, ops.new_comment); } free(ops.comment); ssh_rsa_free(&ops.key); ops.comment = NULL; ops.key = NULL; if (ops.fpriv.len < 0 || ops.fpub.len < 0) { SSH_ERROR("Unable to encode keyfile.\n"); ret = 1; goto out; } if (ops.filetype == KEYFILE_PRIV || ops.filetype == KEYFILE_BOTH) { ret = write_file(&ops.fpriv); if (ret != 0) goto out; } if (ops.filetype == KEYFILE_PUB || ops.filetype == KEYFILE_BOTH) ret = write_file(&ops.fpub); out: keygen_cleanup(&ops); exit(ret); } void Usage(const char *err) { const char *progname = "ssh-keygen"; /* XXX */ if (err != NULL) fprintf(stderr, "%s\n", err); fprintf(stderr, "%s [-q] [-b] [-N new-passphrase] [-C comment]\n" " [-t pub|priv|both]\n", progname); fprintf(stderr, "%s -p|-c [-P old-passphrase] [-N new-passphrase]\n" " [-C comment]\n", progname); fprintf(stderr, "%s -u [-P passphrase]\n", progname); fprintf(stderr, "%s -h (this help message)\n", progname); fprintf(stderr, "\n"); fprintf(stderr, "Common options:\n"); fprintf(stderr, "\t[-f keyfile[:pubkeyfile]]\n"); fprintf(stderr, "\t[-F format]\n"); fprintf(stderr, "\t\tFreSSH -- FreSSH format (deprecated)\n"); fprintf(stderr, "\t\tF-Secure -- F-Secure format (default)\n"); fprintf(stderr, "\t\tFSecure\n"); fprintf(stderr, "\t\tOpenSSH\n"); fprintf(stderr, "\t-D (increase debug level)\n"); exit(1); } void parse_filename_opt(struct ops * o, char *arg) { char *colon_pos; o->fpub.name = NULL; colon_pos = strchr(arg, ':'); if (colon_pos == arg) o->fpriv.name = NULL; else o->fpriv.name = arg; if (colon_pos != NULL) { *colon_pos = '\0'; arg = colon_pos + 1; if (strlen(arg) > 0) { o->fpub.name = malloc(strlen(arg) + 1); strncpy(o->fpub.name, arg, strlen(arg) + 1); } } } void keygen_cleanup(struct ops * o) { if (o->fpriv.buf) free(o->fpriv.buf); if (o->fpub.buf) free(o->fpub.buf); if (o->key) ssh_rsa_free(&o->key); o->key = NULL; if (o->comment) free(o->comment); o->comment = NULL; } /* * Generate a ssh RSA key with progress display. * Returns 0 on success. */ int ssh_generate_key(struct ops * o, ssh_RSA ** key) { int rbits; if (!key) return (1); if (o->bits < 128) rbits = 1024; else rbits = o->bits; whichprime = 0; *key = ssh_rsa_generate_key(rbits, 3, genkey_callback, NULL); return (*key == NULL); } void genkey_callback(int type, int num, void *arg) { if (whichprime == 0) { if (ops.quiet == 0) fprintf(stderr, "Generating p:"); whichprime = 1; } if (whichprime == 2) { if (ops.quiet == 0) fprintf(stderr, "Generating q:"); whichprime = 3; } switch (type) { case 0: if (ops.quiet == 0) fprintf(stderr, ">"); break; case 1: if (ops.quiet == 0) fprintf(stderr, "*"); break; case 2: if (ops.quiet == 0) fprintf(stderr, "Failed, regenerating.\n"); break; case 3: if (ops.quiet == 0) fprintf(stderr, ":done\n"); whichprime++; break; default: printf("Xunknown %d(%d)X", type, num); } } int ssh_setdefaultcomment(char **new_comment) { char hostname[100]; struct passwd *pwp; int len; if (gethostname(hostname, 100) < 0) return (1); SSH_DLOG(4, ("hostname: %s\n", hostname)); pwp = getpwuid(getuid()); if (pwp == NULL) return (1); SSH_DLOG(4, ("username: %s\n", pwp->pw_name)); len = strlen(hostname) + strlen(pwp->pw_name) + 2; if ((*new_comment = malloc(len)) == NULL) { endpwent(); return (1); } snprintf(*new_comment, len, "%s@%s", pwp->pw_name, hostname); endpwent(); return (0); } void default_filename(struct target_file * fpriv, struct target_file * fpub) { char *homedir; size_t len; homedir = getenv("HOME"); if (homedir == NULL) return; if (fpriv->name == NULL) { len = strlen(_PATH_SSH_CLIENT_PRIVKEY) + strlen(homedir) + 2; if ((fpriv->name = malloc(len)) == NULL) return; sprintf(fpriv->name, "%s/%s", homedir, _PATH_SSH_CLIENT_PRIVKEY); } if (fpub->name == NULL) { len = strlen(fpriv->name) + strlen(homedir) + 2; if ((fpub->name = malloc(len)) == NULL) { free(fpriv->name); fpriv->name = NULL; return; } sprintf(fpub->name, "%s.pub", fpriv->name); } }