From 41c6ee5f4f628f34a9542cbcd2da8ae03084bf4d Mon Sep 17 00:00:00 2001 From: David Goodwin Date: Fri, 2 Sep 2022 20:11:57 +1200 Subject: [PATCH] #83 - SSH KEY CREATE now works --- doc/ssh-readme.md | 2 + kermit/k95/ckossh.c | 235 ++++++++++++++++++++++++++++++++++++++++++-- kermit/k95/ckuus2.c | 31 +++--- kermit/k95/ckuusr.c | 38 ++++--- kermit/k95/ckuusr.h | 20 ++++ 5 files changed, 295 insertions(+), 31 deletions(-) diff --git a/doc/ssh-readme.md b/doc/ssh-readme.md index 9e54ef29..ef2a5b94 100644 --- a/doc/ssh-readme.md +++ b/doc/ssh-readme.md @@ -49,6 +49,8 @@ SSH [OPEN] host [port] Just reports an error if version is 1 (SSH-1 not supported) /SUBSYSTEM:name Implemented though doesn't appear to be working at the moment +SSH KEY CREATE [ /BITS:bits /PASSPHRASE:passphrase /TYPE:{ DSS, ECDSA, ED25519, RSA } ] filename + Creates an SSH key pair SET SSH COMPRESSION {ON,OFF} HEARTBEAT-INTERVAL interval diff --git a/kermit/k95/ckossh.c b/kermit/k95/ckossh.c index 199958e9..5ee8c6f8 100644 --- a/kermit/k95/ckossh.c +++ b/kermit/k95/ckossh.c @@ -41,13 +41,15 @@ char *cksshv = "SSH support, 10.0.0, 28 July 2022"; * where all the work is done (using libssh) lives in ckoshs.c. */ +#include #include "ckcdeb.h" #include "ckossh.h" #include "ckcker.h" - +#include "ckuusr.h" #include "ckoshs.h" + /* Global Variables: * These are all declared in ckuus3.c around like 8040 * @@ -132,12 +134,11 @@ char *cksshv = "SSH support, 10.0.0, 28 July 2022"; * TODO: /NEW-PASSPHRASE:passphrase * TODO: /OLD-PASSPHRASE:passphrase * TODO: filename - * TODO: CREATE - * TODO: /BITS:bits - * TODO: /PASSPHRASE: passphrase - * TODO: /TYPE:{V1-RSA,V2-DSA,V2-RSA} - * TODO: /V1-RSA-COMMENT: comment - * TODO: filename + * CREATE + * /BITS:bits + * /PASSPHRASE: passphrase + * /TYPE:{ DSS, ECDSA, ED25519, RSA } + * filename * TODO: DISPLAY * TODO: /FORMAT:{fingerprint, ietf,openssh,ssh.com} * TODO: filename @@ -157,6 +158,7 @@ char *cksshv = "SSH support, 10.0.0, 28 July 2022"; * TODO: KERBEROS5 TGT-PASSING {ON,OFF} -- delete * TODO: PRIVILEGED-PORT {ON,OFF} * TODO: QUIET {ON,OFF} + * -> This should suppress all printfs * STRICT-HOST-KEY-CHECK {ASK, ON, OFF} * USE-OPENSSH-CONFIG {ON,OFF} * Value is stored in ssh_cfg @@ -962,8 +964,225 @@ int ssh_fwd_remote_port(int port, char * host, int host_port) return SSH_ERR_NOT_IMPLEMENTED; /* TODO */ } +/* These live in ckoreg.c */ +char* GetHomePath(); +char* GetHomeDrive(); + +/** Creates a + * + * @param filename File to write the private key to + * @param bits Length of the key in bits. Valid options vary by key type + * @param pp Passphrase + * @param type Key type + * @param cmd_comment SSH V1 RSA Comment (obsolete) + * @return + */ int sshkey_create(char * filename, int bits, char * pp, int type, char * cmd_comment) { - return SSH_ERR_NOT_IMPLEMENTED; /* TODO */ + enum ssh_keytypes_e ktype; + char *output_filename = NULL, *pubkey_output_filename = NULL, + *passphrase = NULL, *default_filename = NULL; + ssh_key key = NULL; + int rc; + + debug(F100, "sshkey_create", "", 0); + + /* By default, openssh searches for id_rsa, id_ecdsa, id_ecdsa_sk, + * id_ed25519, id_ed25519_sk and id_dsa */ + switch(type) { + case SSHKT_DSS: + default_filename = "id_dsa"; + ktype = SSH_KEYTYPE_DSS; + if (bits == 0) bits = 1024; + if (bits != 1024 && bits != 2048) { + printf("Invalid key length %d - valid options are: 1024, 2048\n"); + } + break; + case SSHKT_RSA: + default_filename = "id_rsa"; + ktype = SSH_KEYTYPE_RSA; + if (bits == 0) bits = 3072; + if (bits != 1024 && bits != 2048 && bits != 3072 && + bits != 2048 && bits != 8192) { + printf("Invalid key length %d - valid options are: 1024, 2048, 3072, 4096, 8192\n"); + return SSH_ERR_UNSPECIFIED; + } + break; + case SSHKT_RSA1: + default_filename = NULL; + ktype = SSH_KEYTYPE_RSA1; + if (bits == 0) bits = 3072; + if (bits != 1024 && bits != 2048 && bits != 3072 && + bits != 2048 && bits != 8192) { + printf("Invalid key length %d - valid options are: 1024, 2048, 3072, 4096, 8192\n"); + return SSH_ERR_UNSPECIFIED; + } + break; + case SSHKT_ECDSA: + default_filename = "id_ecdsa"; + ktype = SSH_KEYTYPE_ECDSA; + if (bits == 0) bits = 256; + if (bits != 256 && bits != 384 && bits != 521) { + printf("Invalid key length %d - valid options are: 256, 384, 521\n"); + return SSH_ERR_UNSPECIFIED; + } + break; + case SSHKT_ED25519: + default_filename = "id_ed25519"; + ktype = SSH_KEYTYPE_ED25519; + bits = 0; /* No bits */ + break; + case SSHKT_DSS_CERT01: + default_filename = NULL; + ktype = SSH_KEYTYPE_DSS_CERT01; + break; + case SSHKT_RSA_CERT01: + default_filename = NULL; + ktype = SSH_KEYTYPE_RSA_CERT01; + break; + case SSHKT_ECDSA_P256: + default_filename = NULL; + ktype = SSH_KEYTYPE_ECDSA_P256; + break; + case SSHKT_ECDSA_P384: + default_filename = NULL; + ktype = SSH_KEYTYPE_ECDSA_P384; + break; + case SSHKT_ECDSA_P521: + default_filename = NULL; + ktype = SSH_KEYTYPE_ECDSA_P521; + break; + case SSHKT_ECDSA_P256_CERT01: + default_filename = NULL; + ktype = SSH_KEYTYPE_ECDSA_P256_CERT01; + break; + case SSHKT_ECDSA_P384_CERT01: + default_filename = NULL; + ktype = SSH_KEYTYPE_ECDSA_P384_CERT01; + break; + case SSHKT_ECDSA_P521_CERT01: + default_filename = NULL; + ktype = SSH_KEYTYPE_ECDSA_P521_CERT01; + break; + case SSHKT_ED25519_CERT01: + default_filename = NULL; + ktype = SSH_KEYTYPE_ED25519_CERT01; + break; + default: + printf("Unrecognised or unsupported key type\n"); + return SSH_ERR_UNSPECIFIED; + } + + printf("Bits: %d\n", bits); + + if (filename) { + output_filename = _strdup(filename); + } else { + char* default_pathname; + output_filename = malloc(MAX_PATH * sizeof(char)); + default_pathname = malloc(MAX_PATH * sizeof(char)); + + /* We'll suggest the user save in %USERPROFILE%\.ssh by default as thats + * where both C-Kermit and the windows builds of OpenSSH look */ + snprintf(default_pathname, MAX_PATH, "%s%s.ssh/%s", + GetHomeDrive(), GetHomePath(), default_filename); +#ifdef CK_MKDIR + /* Make the .ssh directory if it doesn't already exist */ + zmkdir(default_pathname); +#endif + + /* GetHomePath gives unix-style directory separators which the windows + * file dialog doesn't seem to like. So convert to DOS separators */ + for (int i = 0; i < MAX_PATH; i++) { + if (default_pathname[i] == '\0') break; + if (default_pathname[i] == '/') default_pathname[i] = '\\'; + } + + int rc = uq_file( + "Enter a filename to save the generated keys to:", /* Text mode only, text above the prompt */ + "Save As", /* file dialog title or text-mode prompt*/ + 5, /* New file, don't append */ + NULL, /* Help text - not used */ + default_pathname, + output_filename, + MAX_PATH + ); + free(default_pathname); + + if (rc == 0) { + free(output_filename); + return SSH_ERR_USER_CANCELED; + } else if (rc < 0 ) { + free(output_filename); + return SSH_ERR_UNSPECIFIED; + } + } + + if (pp) { + passphrase = _strdup(pp); + } + else { + /* Prompt for passphrase. Two fields to get confirmation. */ + char pp1[250], pp2[250]; + struct txtbox fields[2]; + + fields[0].t_buf = pp1; + fields[0].t_len = sizeof(pp1); + fields[0].t_lbl = "New passphrase: "; + fields[0].t_dflt = NULL; + fields[0].t_echo = 2; + + fields[1].t_buf = pp2; + fields[1].t_len = sizeof(pp2); + fields[1].t_lbl = "New passphrase (again): "; + fields[1].t_dflt = NULL; + fields[1].t_echo = 2; + + rc = uq_mtxt("Enter SSH Key passphrase. Leave both fields empty empty " + "for no passphrase.", + NULL, 2, fields); + + if ( !rc ) { + printf("User cancelled\n"); + free(output_filename); + return(SSH_ERR_USER_CANCELED); + } + + if (strcmp(pp1, pp2) != 0) { + printf("Passphrase mismatch, no action taken\n"); + free(output_filename); + return(SSH_ERR_UNSPECIFIED); + } + + if (strlen(pp1) > 0) { + passphrase = _strdup(pp1); + } + } + + printf("Generating private key...\n"); + rc = ssh_pki_generate(ktype, bits, &key); + if (rc != SSH_OK) { + printf("Failed to generate private key\n"); + return SSH_ERR_UNSPECIFIED; + } + + rc = ssh_pki_export_privkey_file(key, passphrase, NULL, NULL, output_filename); + if (rc != SSH_OK) { + printf("Failed to write private key to %s - error %d\n", output_filename, rc); + } else { + pubkey_output_filename = malloc(MAX_PATH); + + snprintf(pubkey_output_filename, MAX_PATH, "%s.pub", output_filename); + rc = ssh_pki_export_pubkey_file(key, pubkey_output_filename); + if (rc != SSH_OK) { + printf("Failed to write public key to %s\n", pubkey_output_filename); + } + + free(pubkey_output_filename); + } + + free(output_filename); + free(passphrase); + return SSH_ERR_NO_ERROR; } int sshkey_display_fingerprint(char * filename, int babble) { diff --git a/kermit/k95/ckuus2.c b/kermit/k95/ckuus2.c index 6cb8d693..5bc3b58d 100644 --- a/kermit/k95/ckuus2.c +++ b/kermit/k95/ckuus2.c @@ -724,13 +724,14 @@ static char * hmxxssh[] = { " ", "SSH KEY commands:", " The SSH KEY commands create and manage public and private key pairs", -" (identities). There are three forms of SSH keys. Each key pair is", +" (identities). There are four forms of SSH keys. Each key pair is", " stored in its own set of files:", " ", " Key Type Private Key File Public Key File", -" v1 RSA keys \\v(appdata)ssh/identity \\v(appdata)ssh/identity.pub", -" v2 RSA keys \\v(appdata)ssh/id_rsa \\v(appdata)ssh/id_rsa.pub", -" v2 DSA keys \\v(appdata)ssh/id_dsa \\v(appdata)ssh/id_dsa.pub", +" RSA keys \\v(home).ssh/id_rsa \\v(home).ssh/id_rsa.pub", +" DSA keys \\v(home).ssh/id_dsa \\v(home).ssh/id_dsa.pub", +" ECDSA keys \\v(home).ssh/id_ecdsa \\v(home).ssh/id_ecdsa.pub", +" ED25519 keys \\v(home).ssh/id_ed25519 \\v(home).ssh/id_ed25519.pub", " ", " Keys are stored using the OpenSSH keyfile format. The private key", " files can be (optionally) protected by specifying a passphrase. A", @@ -751,21 +752,29 @@ static char * hmxxssh[] = { " not provided Kermit prompts your for them.", " ", "SSH KEY CREATE [ /BITS:bits /PASSPHRASE:passphrase", -" /TYPE:{ V1-RSA, V2-DSA, V2-RSA } /V1-RSA-COMMENT:comment ] filename", -" This command creates a new private/public key pair. The defaults are:", -" BITS:1024 and TYPE:V2-RSA. The filename is the name of the private", -" key file. The public key is created with the same name with .pub", -" appended to it. If a filename is not specified Kermit prompts you for", -" it. V1 RSA key files may have an optional comment, which is ignored", -" for other key types.", +" /TYPE:{ DSS, ECDSA, ED25519, RSA } ] filename", +" This command creates a new private/public key pair. The defaults is", +" TYPE:ED25519. The filename is the name of the private key file. The", +" The public key is created with the same name with .pub appended to it.", +" If a filename is not specified Kermit prompts you for it. Key length ", +" options (/BITS:) depends on the key type:", +" ", +" ECDSA: 256 (default), 384, 521", +" RSA: 1024, 2048, 3072 (default), 4096, 8192", +" DSS: 1024 (default), 2048", +" ", +" ED25519 does not support being given a key length and any value supplied", +" via /BITS: will be ignored.", " ", "SSH KEY DISPLAY [ /FORMAT:{FINGERPRINT,IETF,OPENSSH,SSH.COM} ] filename", " This command displays the contents of a public or private key file.", " The default format is OPENSSH.", " ", +#ifdef COMMENT "SSH KEY V1 SET-COMMENT filename comment", " This command replaces the comment associated with a V1 RSA key file.", " ", +#endif "SSH [ OPEN ] host [ port ] [ /COMMAND:command /USER:username", " /PASSWORD:pwd /VERSION:{ 1, 2 } /X11-FORWARDING:{ ON, OFF } ]", " This command establishes a new connection using SSH version 1 or", diff --git a/kermit/k95/ckuusr.c b/kermit/k95/ckuusr.c index f7b5f53f..e64612a5 100644 --- a/kermit/k95/ckuusr.c +++ b/kermit/k95/ckuusr.c @@ -2362,7 +2362,7 @@ char * ssh_tmpuid = NULL, *ssh_tmpcmd = NULL, *ssh_tmpport = NULL, * ssh_tmpstr = NULL; int - sshk_type = SSHKT_2D, /* SSH KEY CREATE /TYPE:x */ + sshk_type = SSHKT_ED25519, /* SSH KEY CREATE /TYPE:x */ sshk_bits = 1024, /* SSH KEY CREATE /BITS:n */ sshk_din = SKDF_OSSH, /* SSH KEY DISPLAY /IN-FORMAT: */ sshk_dout = SKDF_OSSH; /* SSH KEY DISPLAY /OUT-FORMAT: */ @@ -2458,15 +2458,33 @@ static struct keytab sshkcrea[] = { /* SSH KEY CREATE table */ { "/bits", SSHKC_BI, CM_ARG }, { "/passphrase", SSHKC_PP, CM_ARG }, { "/type", SSHKC_TY, CM_ARG }, - { "/v1-rsa-comment", SSHKC_1R, CM_ARG } + /*{ "/v1-rsa-comment", SSHKC_1R, CM_ARG }*/ }; static int nsshkcrea = (sizeof(sshkcrea) / sizeof(struct keytab)); static struct keytab sshkcty[] = { /* SSH KEY CREATE /TYPE:xxx */ - { "srp", SSHKT_SRP, 0 }, + /*{ "srp", SSHKT_SRP, 0 }, { "v1-rsa", SSHKT_1R, 0 }, { "v2-dsa", SSHKT_2D, 0 }, - { "v2-rsa", SSHKT_2R, 0 } + { "v2-rsa", SSHKT_2R, 0 }*/ + {"dss", SSHKT_DSS, 0 }, + {"dss-cert01", SSHKT_DSS_CERT01, CM_INV }, + {"ecdsa", SSHKT_ECDSA, 0 }, /* deprecated */ + {"ecdsa-p256", SSHKT_ECDSA_P256, CM_INV }, + {"ecdsa-p256-cert01", SSHKT_ECDSA_P256_CERT01,CM_INV }, + {"ecdsa-p384", SSHKT_ECDSA_P384, CM_INV }, + {"ecdsa-p384-cert01", SSHKT_ECDSA_P384_CERT01,CM_INV }, + {"ecdsa-p521", SSHKT_ECDSA_P521, CM_INV }, + {"ecdsa-p521-cert01", SSHKT_ECDSA_P521_CERT01,CM_INV }, + {"ed25519", SSHKT_ED25519, 0 }, + {"ed25519-cert01", SSHKT_ED25519_CERT01, CM_INV }, + {"rsa", SSHKT_RSA, 0 }, + {"rsa1", SSHKT_RSA1, CM_INV }, + {"rsa-cert01", SSHKT_RSA_CERT01, CM_INV }, + {"sk-ecdsa", SSHKT_SK_ECDSA, CM_INV }, + {"sk-ecdsa-cert01", SSHKT_SK_ECDSA_CERT01, CM_INV }, + {"sk-ed25519", SSHKT_SK_ED25519, CM_INV }, + {"sk-ed25519-cert01", SSHKT_SK_ED25519_CERT01,CM_INV } }; static int nsshkcty = (sizeof(sshkcty) / sizeof(struct keytab)); @@ -11268,7 +11286,7 @@ necessary DLLs did not load. Use SHOW NETWORK to check network status.\n"); return(success); } case SSHK_CREA: { /* SSH KEY CREATE /switches... */ - int bits = 1024, keytype = SSHKT_2R; + int bits = 0, keytype = SSHKT_ED25519; char * pass = NULL, * comment = NULL; struct FDB df, sw; @@ -11316,12 +11334,8 @@ necessary DLLs did not load. Use SHOW NETWORK to check network status.\n"); } switch (cmresult.nresult) { case SSHKC_BI: /* /BITS:n */ - if ((y = cmnum("","1024",10,&z,xxstring)) < 0) - return(y); - if (z < 512 || z > 4096) { - printf("?Out range - min: 512, max: 4096\n"); - return(-9); - } + if ((y = cmnum("","0",10,&z,xxstring)) < 0) + return(y); bits = z; break; case SSHKC_PP: /* /PASSPHRASE:blah */ @@ -11331,7 +11345,7 @@ necessary DLLs did not load. Use SHOW NETWORK to check network status.\n"); break; case SSHKC_TY: /* /TYPE:keyword */ if ((y = cmkey(sshkcty,nsshkcty,"", - "v2-rsa",xxstring)) < 0) + "ed25519",xxstring)) < 0) return(y); keytype = y; break; diff --git a/kermit/k95/ckuusr.h b/kermit/k95/ckuusr.h index e65fde66..10bae1b7 100644 --- a/kermit/k95/ckuusr.h +++ b/kermit/k95/ckuusr.h @@ -2709,10 +2709,30 @@ struct stringint { /* String and (wide) integer */ #define XSSH_CLR 7 #define XSSH_AGT 8 +#ifdef COMMENT #define SSHKT_1R 0 /* SSH KEY TYPE symbols */ #define SSHKT_2R 1 /* must match ssh/key.h values */ #define SSHKT_2D 2 #define SSHKT_SRP 3 +#endif +#define SSHKT_DSS 0 +#define SSHKT_RSA 1 +#define SSHKT_RSA1 2 +#define SSHKT_ECDSA 3 /* deprecated */ +#define SSHKT_ED25519 4 +#define SSHKT_DSS_CERT01 5 +#define SSHKT_RSA_CERT01 6 +#define SSHKT_ECDSA_P256 7 +#define SSHKT_ECDSA_P384 8 +#define SSHKT_ECDSA_P521 9 +#define SSHKT_ECDSA_P256_CERT01 10 +#define SSHKT_ECDSA_P384_CERT01 11 +#define SSHKT_ECDSA_P521_CERT01 12 +#define SSHKT_ED25519_CERT01 13 +#define SSHKT_SK_ECDSA 14 +#define SSHKT_SK_ECDSA_CERT01 15 +#define SSHKT_SK_ED25519 16 +#define SSHKT_SK_ED25519_CERT01 17 #define SSHKD_IN 1 /* SSH KEY DISPLAY /IN-FORMAT */ #define SSHKD_OUT 2 /* SSH KEY DISPLAY /OUT-FORMAT */