14ff31490f
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
(cherry picked from commit f193e6840d
)
506 lines
14 KiB
C
506 lines
14 KiB
C
/*
|
|
* keygen2.c - Generate SSH keys using libssh
|
|
* Author: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2019 Red Hat, Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see http://www.gnu.org/licenses/.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <libssh/libssh.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <argp.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
struct arguments_st {
|
|
enum ssh_keytypes_e type;
|
|
unsigned long bits;
|
|
char *file;
|
|
char *passphrase;
|
|
int action_list;
|
|
};
|
|
|
|
static struct argp_option options[] = {
|
|
{
|
|
.name = "bits",
|
|
.key = 'b',
|
|
.arg = "BITS",
|
|
.flags = 0,
|
|
.doc = "The size of the key to be generated. "
|
|
"If omitted, a default value is used depending on the TYPE. "
|
|
"Accepted values are: "
|
|
"1024, 2048, 3072 (default), 4096, and 8192 for TYPE=\"rsa\"; "
|
|
"256 (default), 384, and 521 for TYPE=\"ecdsa\"; "
|
|
"1024 (default) and 2048 for TYPE=\"dsa\"; "
|
|
"can be omitted for TYPE=\"ed25519\" "
|
|
"(it will be ignored if provided).\n",
|
|
.group = 0
|
|
},
|
|
{
|
|
.name = "file",
|
|
.key = 'f',
|
|
.arg = "FILE",
|
|
.flags = 0,
|
|
.doc = "The output file. "
|
|
"If not provided, the used file name will be generated "
|
|
"according to the key type as \"id_TYPE\" "
|
|
"(e.g. \"id_rsa\" for type \"rsa\"). "
|
|
"The public key file name is generated from the private key "
|
|
"file name by appending \".pub\".\n",
|
|
.group = 0
|
|
},
|
|
{
|
|
.name = "passphrase",
|
|
.key = 'p',
|
|
.arg = "PASSPHRASE",
|
|
.flags = 0,
|
|
.doc = "The passphrase used to encrypt the private key. "
|
|
"If omitted the file will not be encrypted.\n",
|
|
.group = 0
|
|
},
|
|
{
|
|
.name = "type",
|
|
.key = 't',
|
|
.arg = "TYPE",
|
|
.flags = 0,
|
|
.doc = "The type of the key to be generated. "
|
|
"Accepted values are: "
|
|
"\"rsa\", \"ecdsa\", \"ed25519\", and \"dsa\".\n",
|
|
.group = 0
|
|
},
|
|
{
|
|
.name = "list",
|
|
.key = 'l',
|
|
.arg = NULL,
|
|
.flags = 0,
|
|
.doc = "List the Fingerprint of the given key\n",
|
|
.group = 0
|
|
},
|
|
{
|
|
/* End of the options */
|
|
0
|
|
},
|
|
};
|
|
|
|
/* Parse a single option. */
|
|
static error_t parse_opt (int key, char *arg, struct argp_state *state)
|
|
{
|
|
/* Get the input argument from argp_parse, which we
|
|
* know is a pointer to our arguments structure.
|
|
*/
|
|
struct arguments_st *arguments = NULL;
|
|
error_t rc = 0;
|
|
|
|
if (state == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
arguments = state->input;
|
|
if (arguments == NULL) {
|
|
fprintf(stderr, "Error: NULL pointer to arguments structure "
|
|
"provided\n");
|
|
rc = EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
switch (key) {
|
|
case 'b':
|
|
errno = 0;
|
|
arguments->bits = strtoul(arg, NULL, 10);
|
|
if (errno != 0) {
|
|
rc = errno;
|
|
goto end;
|
|
}
|
|
break;
|
|
case 'f':
|
|
arguments->file = strdup(arg);
|
|
if (arguments->file == NULL) {
|
|
fprintf(stderr, "Error: Out of memory\n");
|
|
rc = ENOMEM;
|
|
goto end;
|
|
}
|
|
break;
|
|
case 'p':
|
|
arguments->passphrase = strdup(arg);
|
|
if (arguments->passphrase == NULL) {
|
|
fprintf(stderr, "Error: Out of memory\n");
|
|
rc = ENOMEM;
|
|
goto end;
|
|
}
|
|
break;
|
|
case 't':
|
|
if (!strcmp(arg, "rsa")) {
|
|
arguments->type = SSH_KEYTYPE_RSA;
|
|
}
|
|
else if (!strcmp(arg, "dsa")) {
|
|
arguments->type = SSH_KEYTYPE_DSS;
|
|
}
|
|
else if (!strcmp(arg, "ecdsa")) {
|
|
arguments->type = SSH_KEYTYPE_ECDSA;
|
|
}
|
|
else if (!strcmp(arg, "ed25519")) {
|
|
arguments->type = SSH_KEYTYPE_ED25519;
|
|
}
|
|
else {
|
|
fprintf(stderr, "Error: Invalid key type\n");
|
|
argp_usage(state);
|
|
rc = EINVAL;
|
|
goto end;
|
|
}
|
|
break;
|
|
case 'l':
|
|
arguments->action_list = 1;
|
|
break;
|
|
case ARGP_KEY_ARG:
|
|
if (state->arg_num > 0) {
|
|
/* Too many arguments. */
|
|
printf("Error: Too many arguments\n");
|
|
argp_usage(state);
|
|
}
|
|
break;
|
|
case ARGP_KEY_END:
|
|
break;
|
|
default:
|
|
return ARGP_ERR_UNKNOWN;
|
|
}
|
|
|
|
end:
|
|
return rc;
|
|
}
|
|
|
|
static int validate_args(struct arguments_st *args)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (args == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
/* no other arguments needed for listing key fingerprints */
|
|
if (args->action_list) {
|
|
return 0;
|
|
}
|
|
|
|
switch (args->type) {
|
|
case SSH_KEYTYPE_RSA:
|
|
switch (args->bits) {
|
|
case 0:
|
|
/* If not provided, use default value */
|
|
args->bits = 3072;
|
|
break;
|
|
case 1024:
|
|
case 2048:
|
|
case 3072:
|
|
case 4096:
|
|
case 8192:
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Error: Invalid bits parameter provided\n");
|
|
rc = EINVAL;
|
|
break;
|
|
}
|
|
|
|
if (args->file == NULL) {
|
|
args->file = strdup("id_rsa");
|
|
if (args->file == NULL) {
|
|
rc = ENOMEM;
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
case SSH_KEYTYPE_ECDSA:
|
|
switch (args->bits) {
|
|
case 0:
|
|
/* If not provided, use default value */
|
|
args->bits = 256;
|
|
break;
|
|
case 256:
|
|
case 384:
|
|
case 521:
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Error: Invalid bits parameter provided\n");
|
|
rc = EINVAL;
|
|
break;
|
|
}
|
|
if (args->file == NULL) {
|
|
args->file = strdup("id_ecdsa");
|
|
if (args->file == NULL) {
|
|
rc = ENOMEM;
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
case SSH_KEYTYPE_DSS:
|
|
switch (args->bits) {
|
|
case 0:
|
|
/* If not provided, use default value */
|
|
args->bits = 1024;
|
|
break;
|
|
case 1024:
|
|
case 2048:
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Error: Invalid bits parameter provided\n");
|
|
rc = EINVAL;
|
|
break;
|
|
}
|
|
if (args->file == NULL) {
|
|
args->file = strdup("id_dsa");
|
|
if (args->file == NULL) {
|
|
rc = ENOMEM;
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
case SSH_KEYTYPE_ED25519:
|
|
/* Ignore value and overwrite with a zero */
|
|
args->bits = 0;
|
|
|
|
if (args->file == NULL) {
|
|
args->file = strdup("id_ed25519");
|
|
if (args->file == NULL) {
|
|
rc = ENOMEM;
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Error: unknown key type\n");
|
|
rc = EINVAL;
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Program documentation. */
|
|
static char doc[] = "Generate an SSH key pair. "
|
|
"The \"--type\" (short: \"-t\") option is required.";
|
|
|
|
/* Our argp parser */
|
|
static struct argp argp = {options, parse_opt, NULL, doc, NULL, NULL, NULL};
|
|
|
|
static void
|
|
list_fingerprint(char *file)
|
|
{
|
|
ssh_key key = NULL;
|
|
unsigned char *hash = NULL;
|
|
size_t hlen = 0;
|
|
int rc;
|
|
|
|
rc = ssh_pki_import_privkey_file(file, NULL, NULL, NULL, &key);
|
|
if (rc != SSH_OK) {
|
|
fprintf(stderr, "Failed to import private key %s\n", file);
|
|
return;
|
|
}
|
|
|
|
rc = ssh_get_publickey_hash(key, SSH_PUBLICKEY_HASH_SHA256, &hash, &hlen);
|
|
if (rc != SSH_OK) {
|
|
fprintf(stderr, "Failed to get key fingerprint\n");
|
|
return;
|
|
}
|
|
ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen);
|
|
|
|
ssh_clean_pubkey_hash(&hash);
|
|
ssh_key_free(key);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
ssh_key key = NULL;
|
|
int rc = 0;
|
|
char overwrite[1024] = "";
|
|
|
|
char *pubkey_file = NULL;
|
|
|
|
struct arguments_st arguments = {
|
|
.type = SSH_KEYTYPE_UNKNOWN,
|
|
.bits = 0,
|
|
.file = NULL,
|
|
.passphrase = NULL,
|
|
.action_list = 0,
|
|
};
|
|
|
|
if (argc < 2) {
|
|
argp_help(&argp, stdout, ARGP_HELP_DOC | ARGP_HELP_USAGE, argv[0]);
|
|
goto end;
|
|
}
|
|
|
|
rc = argp_parse(&argp, argc, argv, 0, 0, &arguments);
|
|
if (rc != 0) {
|
|
goto end;
|
|
}
|
|
|
|
rc = validate_args(&arguments);
|
|
if (rc != 0) {
|
|
goto end;
|
|
}
|
|
|
|
if (arguments.action_list && arguments.file) {
|
|
list_fingerprint(arguments.file);
|
|
goto end;
|
|
}
|
|
|
|
errno = 0;
|
|
rc = open(arguments.file, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
|
|
if (rc < 0) {
|
|
if (errno == EEXIST) {
|
|
printf("File \"%s\" exists. Overwrite it? (y|n) ", arguments.file);
|
|
rc = scanf("%1023s", overwrite);
|
|
if (rc > 0 && tolower(overwrite[0]) == 'y') {
|
|
rc = open(arguments.file, O_WRONLY);
|
|
if (rc > 0) {
|
|
close(rc);
|
|
errno = 0;
|
|
rc = chmod(arguments.file, S_IRUSR | S_IWUSR);
|
|
if (rc != 0) {
|
|
fprintf(stderr,
|
|
"Error(%d): Could not set file permissions\n",
|
|
errno);
|
|
goto end;
|
|
}
|
|
} else {
|
|
fprintf(stderr,
|
|
"Error: Could not create private key file\n");
|
|
goto end;
|
|
}
|
|
} else {
|
|
goto end;
|
|
}
|
|
} else {
|
|
fprintf(stderr, "Error opening \"%s\" file\n", arguments.file);
|
|
goto end;
|
|
}
|
|
} else {
|
|
close(rc);
|
|
}
|
|
|
|
/* Generate a new private key */
|
|
rc = ssh_pki_generate(arguments.type, arguments.bits, &key);
|
|
if (rc != SSH_OK) {
|
|
fprintf(stderr, "Error: Failed to generate keys");
|
|
goto end;
|
|
}
|
|
|
|
/* Write the private key */
|
|
rc = ssh_pki_export_privkey_file(key, arguments.passphrase, NULL, NULL,
|
|
arguments.file);
|
|
if (rc != SSH_OK) {
|
|
fprintf(stderr, "Error: Failed to write private key file");
|
|
goto end;
|
|
}
|
|
|
|
/* If a passphrase was provided, overwrite and free it as it is not needed
|
|
* anymore */
|
|
if (arguments.passphrase != NULL) {
|
|
#ifdef HAVE_EXPLICIT_BZERO
|
|
explicit_bzero(arguments.passphrase, strlen(arguments.passphrase));
|
|
#else
|
|
bzero(arguments.passphrase, strlen(arguments.passphrase));
|
|
#endif
|
|
free(arguments.passphrase);
|
|
arguments.passphrase = NULL;
|
|
}
|
|
|
|
pubkey_file = (char *)malloc(strlen(arguments.file) + 5);
|
|
if (pubkey_file == NULL) {
|
|
rc = ENOMEM;
|
|
goto end;
|
|
}
|
|
|
|
sprintf(pubkey_file, "%s.pub", arguments.file);
|
|
|
|
errno = 0;
|
|
rc = open(pubkey_file,
|
|
O_CREAT | O_EXCL | O_WRONLY,
|
|
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
if (rc < 0) {
|
|
if (errno == EEXIST) {
|
|
printf("File \"%s\" exists. Overwrite it? (y|n) ", pubkey_file);
|
|
rc = scanf("%1023s", overwrite);
|
|
if (rc > 0 && tolower(overwrite[0]) == 'y') {
|
|
rc = open(pubkey_file, O_WRONLY);
|
|
if (rc > 0) {
|
|
close(rc);
|
|
errno = 0;
|
|
rc = chmod(pubkey_file,
|
|
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
if (rc != 0) {
|
|
fprintf(stderr,
|
|
"Error(%d): Could not set file permissions\n",
|
|
errno);
|
|
goto end;
|
|
}
|
|
} else {
|
|
fprintf(stderr,
|
|
"Error: Could not create public key file\n");
|
|
goto end;
|
|
}
|
|
} else {
|
|
goto end;
|
|
}
|
|
} else {
|
|
fprintf(stderr, "Error opening \"%s\" file\n", pubkey_file);
|
|
goto end;
|
|
}
|
|
} else {
|
|
close(rc);
|
|
}
|
|
|
|
/* Write the public key */
|
|
rc = ssh_pki_export_pubkey_file(key, pubkey_file);
|
|
if (rc != SSH_OK) {
|
|
fprintf(stderr, "Error: Failed to write public key file");
|
|
goto end;
|
|
}
|
|
|
|
end:
|
|
if (key != NULL) {
|
|
ssh_key_free(key);
|
|
}
|
|
|
|
if (arguments.file != NULL) {
|
|
free(arguments.file);
|
|
}
|
|
|
|
if (arguments.passphrase != NULL) {
|
|
#ifdef HAVE_EXPLICIT_BZERO
|
|
explicit_bzero(arguments.passphrase, strlen(arguments.passphrase));
|
|
#else
|
|
bzero(arguments.passphrase, strlen(arguments.passphrase));
|
|
#endif
|
|
free(arguments.passphrase);
|
|
}
|
|
|
|
if (pubkey_file != NULL) {
|
|
free(pubkey_file);
|
|
}
|
|
return rc;
|
|
}
|