2004-01-22 22:15:32 +03:00
|
|
|
/*
|
|
|
|
* cmdgen.c - command-line form of PuTTYgen
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define PUTTY_DO_GLOBALS
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <time.h>
|
Add command-line passphrase-file options to command-line PuTTYgen.
Patch due to Colin Watson.
Putting the passphrase in a file avoids exposing it to 'ps' which can
print out every process's command line, while at the same time not
being as platform-specific as the approach of providing an fd number
(since cmdgen.c is in principle a potential cross-platform PuTTYgen,
not just a Unix one, which is why it's not in the 'unix' directory).
Of course it introduces its own risks if someone can read the file
from your disk after you delete it; probably the best approach to
avoiding this, if possible, is to point the option at a file on an
in-memory tmpfs type file system. Or better still, use bash-style
/dev/fd options such as
puttygen --new-passphrase <(echo -n "my passphrase") [options]
Failing that, try a secure file-wipe utility, as the man page change
mentions.
(And a use case not to be overlooked, of course, is the one where you
actually want to generate an unprotected key - in which case, just
pass /dev/null as the filename.)
2016-03-17 21:42:46 +03:00
|
|
|
#include <errno.h>
|
|
|
|
#include <string.h>
|
2004-01-22 22:15:32 +03:00
|
|
|
|
|
|
|
#include "putty.h"
|
|
|
|
#include "ssh.h"
|
|
|
|
|
2004-01-24 20:16:37 +03:00
|
|
|
#ifdef TEST_CMDGEN
|
|
|
|
/*
|
|
|
|
* This section overrides some definitions below for test purposes.
|
2016-03-30 10:25:25 +03:00
|
|
|
* When compiled with -DTEST_CMDGEN (as cgtest.c will do):
|
2019-09-08 22:29:00 +03:00
|
|
|
*
|
2004-01-24 20:16:37 +03:00
|
|
|
* - Calls to get_random_data() are replaced with the diagnostic
|
|
|
|
* function below (I #define the name so that I can still link
|
|
|
|
* with the original set of modules without symbol clash), in
|
|
|
|
* order to avoid depleting the test system's /dev/random
|
|
|
|
* unnecessarily.
|
2019-09-08 22:29:00 +03:00
|
|
|
*
|
2005-10-30 23:24:09 +03:00
|
|
|
* - Calls to console_get_userpass_input() are replaced with the
|
|
|
|
* diagnostic function below, so that I can run tests in an
|
|
|
|
* automated manner and provide their interactive passphrase
|
|
|
|
* inputs.
|
2019-09-08 22:29:00 +03:00
|
|
|
*
|
2004-01-24 20:16:37 +03:00
|
|
|
* - main() is renamed to cmdgen_main(); at the bottom of the file
|
|
|
|
* I define another main() which calls the former repeatedly to
|
|
|
|
* run tests.
|
|
|
|
*/
|
2019-03-24 16:48:38 +03:00
|
|
|
bool cgtest_verbose = false;
|
2004-01-22 22:15:32 +03:00
|
|
|
#define get_random_data get_random_data_diagnostic
|
2016-03-30 10:17:03 +03:00
|
|
|
char *get_random_data(int len, const char *device)
|
2004-01-22 22:15:32 +03:00
|
|
|
{
|
|
|
|
char *buf = snewn(len, char);
|
|
|
|
memset(buf, 'x', len);
|
|
|
|
return buf;
|
|
|
|
}
|
2005-10-30 23:24:09 +03:00
|
|
|
#define console_get_userpass_input console_get_userpass_input_diagnostic
|
2004-01-24 20:16:37 +03:00
|
|
|
int nprompts, promptsgot;
|
|
|
|
const char *prompts[3];
|
2018-05-18 09:22:56 +03:00
|
|
|
int console_get_userpass_input(prompts_t *p)
|
2004-01-24 20:16:37 +03:00
|
|
|
{
|
2005-10-30 23:24:09 +03:00
|
|
|
size_t i;
|
|
|
|
int ret = 1;
|
|
|
|
for (i = 0; i < p->n_prompts; i++) {
|
2019-09-08 22:29:00 +03:00
|
|
|
if (promptsgot < nprompts) {
|
2020-01-21 23:19:47 +03:00
|
|
|
prompt_set_result(p->prompts[i], prompts[promptsgot++]);
|
2019-03-24 16:48:38 +03:00
|
|
|
if (cgtest_verbose)
|
|
|
|
printf(" prompt \"%s\": response \"%s\"\n",
|
2020-01-21 23:19:47 +03:00
|
|
|
p->prompts[i]->prompt, p->prompts[i]->result->s);
|
2019-09-08 22:29:00 +03:00
|
|
|
} else {
|
|
|
|
promptsgot++; /* track number of requests anyway */
|
|
|
|
ret = 0;
|
2019-03-24 16:48:38 +03:00
|
|
|
if (cgtest_verbose)
|
|
|
|
printf(" prompt \"%s\": no response preloaded\n",
|
|
|
|
p->prompts[i]->prompt);
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
2004-01-24 20:16:37 +03:00
|
|
|
}
|
2005-10-30 23:24:09 +03:00
|
|
|
return ret;
|
2004-01-24 20:16:37 +03:00
|
|
|
}
|
|
|
|
#define main cmdgen_main
|
2004-01-22 22:15:32 +03:00
|
|
|
#endif
|
|
|
|
|
|
|
|
struct progress {
|
|
|
|
int phase, current;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void progress_update(void *param, int action, int phase, int iprogress)
|
|
|
|
{
|
|
|
|
struct progress *p = (struct progress *)param;
|
|
|
|
if (action != PROGFN_PROGRESS)
|
2019-09-08 22:29:00 +03:00
|
|
|
return;
|
2004-01-22 22:15:32 +03:00
|
|
|
if (phase > p->phase) {
|
2019-09-08 22:29:00 +03:00
|
|
|
if (p->phase >= 0)
|
|
|
|
fputc('\n', stderr);
|
|
|
|
p->phase = phase;
|
|
|
|
if (iprogress >= 0)
|
|
|
|
p->current = iprogress - 1;
|
|
|
|
else
|
|
|
|
p->current = iprogress;
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
|
|
|
while (p->current < iprogress) {
|
2019-09-08 22:29:00 +03:00
|
|
|
fputc('+', stdout);
|
|
|
|
p->current++;
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
|
|
|
fflush(stdout);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void no_progress(void *param, int action, int phase, int iprogress)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Stubs to let everything else link sensibly.
|
|
|
|
*/
|
|
|
|
char *x_get_default(const char *key)
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
void sk_cleanup(void)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void showversion(void)
|
|
|
|
{
|
2017-01-21 17:55:53 +03:00
|
|
|
char *buildinfo_text = buildinfo("\n");
|
2017-02-11 23:12:55 +03:00
|
|
|
printf("puttygen: %s\n%s\n", ver, buildinfo_text);
|
2017-01-21 17:55:53 +03:00
|
|
|
sfree(buildinfo_text);
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
|
|
|
|
Convert a lot of 'int' variables to 'bool'.
My normal habit these days, in new code, is to treat int and bool as
_almost_ completely separate types. I'm still willing to use C's
implicit test for zero on an integer (e.g. 'if (!blob.len)' is fine,
no need to spell it out as blob.len != 0), but generally, if a
variable is going to be conceptually a boolean, I like to declare it
bool and assign to it using 'true' or 'false' rather than 0 or 1.
PuTTY is an exception, because it predates the C99 bool, and I've
stuck to its existing coding style even when adding new code to it.
But it's been annoying me more and more, so now that I've decided C99
bool is an acceptable thing to require from our toolchain in the first
place, here's a quite thorough trawl through the source doing
'boolification'. Many variables and function parameters are now typed
as bool rather than int; many assignments of 0 or 1 to those variables
are now spelled 'true' or 'false'.
I managed this thorough conversion with the help of a custom clang
plugin that I wrote to trawl the AST and apply heuristics to point out
where things might want changing. So I've even managed to do a decent
job on parts of the code I haven't looked at in years!
To make the plugin's work easier, I pushed platform front ends
generally in the direction of using standard 'bool' in preference to
platform-specific boolean types like Windows BOOL or GTK's gboolean;
I've left the platform booleans in places they _have_ to be for the
platform APIs to work right, but variables only used by my own code
have been converted wherever I found them.
In a few places there are int values that look very like booleans in
_most_ of the places they're used, but have a rarely-used third value,
or a distinction between different nonzero values that most users
don't care about. In these cases, I've _removed_ uses of 'true' and
'false' for the return values, to emphasise that there's something
more subtle going on than a simple boolean answer:
- the 'multisel' field in dialog.h's list box structure, for which
the GTK front end in particular recognises a difference between 1
and 2 but nearly everything else treats as boolean
- the 'urgent' parameter to plug_receive, where 1 vs 2 tells you
something about the specific location of the urgent pointer, but
most clients only care about 0 vs 'something nonzero'
- the return value of wc_match, where -1 indicates a syntax error in
the wildcard.
- the return values from SSH-1 RSA-key loading functions, which use
-1 for 'wrong passphrase' and 0 for all other failures (so any
caller which already knows it's not loading an _encrypted private_
key can treat them as boolean)
- term->esc_query, and the 'query' parameter in toggle_mode in
terminal.c, which _usually_ hold 0 for ESC[123h or 1 for ESC[?123h,
but can also hold -1 for some other intervening character that we
don't support.
In a few places there's an integer that I haven't turned into a bool
even though it really _can_ only take values 0 or 1 (and, as above,
tried to make the call sites consistent in not calling those values
true and false), on the grounds that I thought it would make it more
confusing to imply that the 0 value was in some sense 'negative' or
bad and the 1 positive or good:
- the return value of plug_accepting uses the POSIXish convention of
0=success and nonzero=error; I think if I made it bool then I'd
also want to reverse its sense, and that's a job for a separate
piece of work.
- the 'screen' parameter to lineptr() in terminal.c, where 0 and 1
represent the default and alternate screens. There's no obvious
reason why one of those should be considered 'true' or 'positive'
or 'success' - they're just indices - so I've left it as int.
ssh_scp_recv had particularly confusing semantics for its previous int
return value: its call sites used '<= 0' to check for error, but it
never actually returned a negative number, just 0 or 1. Now the
function and its call sites agree that it's a bool.
In a couple of places I've renamed variables called 'ret', because I
don't like that name any more - it's unclear whether it means the
return value (in preparation) for the _containing_ function or the
return value received from a subroutine call, and occasionally I've
accidentally used the same variable for both and introduced a bug. So
where one of those got in my way, I've renamed it to 'toret' or 'retd'
(the latter short for 'returned') in line with my usual modern
practice, but I haven't done a thorough job of finding all of them.
Finally, one amusing side effect of doing this is that I've had to
separate quite a few chained assignments. It used to be perfectly fine
to write 'a = b = c = TRUE' when a,b,c were int and TRUE was just a
the 'true' defined by stdbool.h, that idiom provokes a warning from
gcc: 'suggest parentheses around assignment used as truth value'!
2018-11-02 22:23:19 +03:00
|
|
|
void usage(bool standalone)
|
2004-01-22 22:15:32 +03:00
|
|
|
{
|
2017-02-11 23:12:55 +03:00
|
|
|
fprintf(standalone ? stderr : stdout,
|
2019-09-08 22:29:00 +03:00
|
|
|
"Usage: puttygen ( keyfile | -t type [ -b bits ] )\n"
|
|
|
|
" [ -C comment ] [ -P ] [ -q ]\n"
|
|
|
|
" [ -o output-keyfile ] [ -O type | -l | -L"
|
|
|
|
" | -p ]\n");
|
2006-06-17 17:01:04 +04:00
|
|
|
if (standalone)
|
2019-09-08 22:29:00 +03:00
|
|
|
fprintf(stderr,
|
|
|
|
"Use \"puttygen --help\" for more detail.\n");
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void help(void)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Help message is an extended version of the usage message. So
|
|
|
|
* start with that, plus a version heading.
|
|
|
|
*/
|
2017-02-11 23:12:55 +03:00
|
|
|
printf("PuTTYgen: key generator and converter for the PuTTY tools\n"
|
2019-09-08 22:29:00 +03:00
|
|
|
"%s\n", ver);
|
2018-10-29 22:50:29 +03:00
|
|
|
usage(false);
|
2017-02-11 23:12:55 +03:00
|
|
|
printf(" -t specify key type when generating (ed25519, ecdsa, rsa, "
|
2019-09-08 22:29:00 +03:00
|
|
|
"dsa, rsa1)\n"
|
|
|
|
" -b specify number of bits when generating key\n"
|
|
|
|
" -C change or specify key comment\n"
|
|
|
|
" -P change key passphrase\n"
|
|
|
|
" -q quiet: do not display progress bar\n"
|
|
|
|
" -O specify output type:\n"
|
|
|
|
" private output PuTTY private key format\n"
|
|
|
|
" private-openssh export OpenSSH private key\n"
|
|
|
|
" private-openssh-new export OpenSSH private key "
|
2017-02-11 23:12:55 +03:00
|
|
|
"(force new format)\n"
|
2019-09-08 22:29:00 +03:00
|
|
|
" private-sshcom export ssh.com private key\n"
|
|
|
|
" public RFC 4716 / ssh.com public key\n"
|
|
|
|
" public-openssh OpenSSH public key\n"
|
|
|
|
" fingerprint output the key fingerprint\n"
|
|
|
|
" -o specify output file\n"
|
|
|
|
" -l equivalent to `-O fingerprint'\n"
|
|
|
|
" -L equivalent to `-O public-openssh'\n"
|
|
|
|
" -p equivalent to `-O public'\n"
|
|
|
|
" --old-passphrase file\n"
|
|
|
|
" specify file containing old key passphrase\n"
|
|
|
|
" --new-passphrase file\n"
|
|
|
|
" specify file containing new key passphrase\n"
|
|
|
|
" --random-device device\n"
|
|
|
|
" specify device to read entropy from (e.g. /dev/urandom)\n"
|
|
|
|
);
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
|
|
|
|
Convert a lot of 'int' variables to 'bool'.
My normal habit these days, in new code, is to treat int and bool as
_almost_ completely separate types. I'm still willing to use C's
implicit test for zero on an integer (e.g. 'if (!blob.len)' is fine,
no need to spell it out as blob.len != 0), but generally, if a
variable is going to be conceptually a boolean, I like to declare it
bool and assign to it using 'true' or 'false' rather than 0 or 1.
PuTTY is an exception, because it predates the C99 bool, and I've
stuck to its existing coding style even when adding new code to it.
But it's been annoying me more and more, so now that I've decided C99
bool is an acceptable thing to require from our toolchain in the first
place, here's a quite thorough trawl through the source doing
'boolification'. Many variables and function parameters are now typed
as bool rather than int; many assignments of 0 or 1 to those variables
are now spelled 'true' or 'false'.
I managed this thorough conversion with the help of a custom clang
plugin that I wrote to trawl the AST and apply heuristics to point out
where things might want changing. So I've even managed to do a decent
job on parts of the code I haven't looked at in years!
To make the plugin's work easier, I pushed platform front ends
generally in the direction of using standard 'bool' in preference to
platform-specific boolean types like Windows BOOL or GTK's gboolean;
I've left the platform booleans in places they _have_ to be for the
platform APIs to work right, but variables only used by my own code
have been converted wherever I found them.
In a few places there are int values that look very like booleans in
_most_ of the places they're used, but have a rarely-used third value,
or a distinction between different nonzero values that most users
don't care about. In these cases, I've _removed_ uses of 'true' and
'false' for the return values, to emphasise that there's something
more subtle going on than a simple boolean answer:
- the 'multisel' field in dialog.h's list box structure, for which
the GTK front end in particular recognises a difference between 1
and 2 but nearly everything else treats as boolean
- the 'urgent' parameter to plug_receive, where 1 vs 2 tells you
something about the specific location of the urgent pointer, but
most clients only care about 0 vs 'something nonzero'
- the return value of wc_match, where -1 indicates a syntax error in
the wildcard.
- the return values from SSH-1 RSA-key loading functions, which use
-1 for 'wrong passphrase' and 0 for all other failures (so any
caller which already knows it's not loading an _encrypted private_
key can treat them as boolean)
- term->esc_query, and the 'query' parameter in toggle_mode in
terminal.c, which _usually_ hold 0 for ESC[123h or 1 for ESC[?123h,
but can also hold -1 for some other intervening character that we
don't support.
In a few places there's an integer that I haven't turned into a bool
even though it really _can_ only take values 0 or 1 (and, as above,
tried to make the call sites consistent in not calling those values
true and false), on the grounds that I thought it would make it more
confusing to imply that the 0 value was in some sense 'negative' or
bad and the 1 positive or good:
- the return value of plug_accepting uses the POSIXish convention of
0=success and nonzero=error; I think if I made it bool then I'd
also want to reverse its sense, and that's a job for a separate
piece of work.
- the 'screen' parameter to lineptr() in terminal.c, where 0 and 1
represent the default and alternate screens. There's no obvious
reason why one of those should be considered 'true' or 'positive'
or 'success' - they're just indices - so I've left it as int.
ssh_scp_recv had particularly confusing semantics for its previous int
return value: its call sites used '<= 0' to check for error, but it
never actually returned a negative number, just 0 or 1. Now the
function and its call sites agree that it's a bool.
In a couple of places I've renamed variables called 'ret', because I
don't like that name any more - it's unclear whether it means the
return value (in preparation) for the _containing_ function or the
return value received from a subroutine call, and occasionally I've
accidentally used the same variable for both and introduced a bug. So
where one of those got in my way, I've renamed it to 'toret' or 'retd'
(the latter short for 'returned') in line with my usual modern
practice, but I haven't done a thorough job of finding all of them.
Finally, one amusing side effect of doing this is that I've had to
separate quite a few chained assignments. It used to be perfectly fine
to write 'a = b = c = TRUE' when a,b,c were int and TRUE was just a
the 'true' defined by stdbool.h, that idiom provokes a warning from
gcc: 'suggest parentheses around assignment used as truth value'!
2018-11-02 22:23:19 +03:00
|
|
|
static bool move(char *from, char *to)
|
2004-01-22 22:15:32 +03:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = rename(from, to);
|
|
|
|
if (ret) {
|
2019-09-08 22:29:00 +03:00
|
|
|
/*
|
|
|
|
* This OS may require us to remove the original file first.
|
|
|
|
*/
|
|
|
|
remove(to);
|
|
|
|
ret = rename(from, to);
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
|
|
|
if (ret) {
|
2019-09-08 22:29:00 +03:00
|
|
|
perror("puttygen: cannot move new file on to old one");
|
|
|
|
return false;
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
2018-10-29 22:50:29 +03:00
|
|
|
return true;
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
|
|
|
|
Add command-line passphrase-file options to command-line PuTTYgen.
Patch due to Colin Watson.
Putting the passphrase in a file avoids exposing it to 'ps' which can
print out every process's command line, while at the same time not
being as platform-specific as the approach of providing an fd number
(since cmdgen.c is in principle a potential cross-platform PuTTYgen,
not just a Unix one, which is why it's not in the 'unix' directory).
Of course it introduces its own risks if someone can read the file
from your disk after you delete it; probably the best approach to
avoiding this, if possible, is to point the option at a file on an
in-memory tmpfs type file system. Or better still, use bash-style
/dev/fd options such as
puttygen --new-passphrase <(echo -n "my passphrase") [options]
Failing that, try a secure file-wipe utility, as the man page change
mentions.
(And a use case not to be overlooked, of course, is the one where you
actually want to generate an unprotected key - in which case, just
pass /dev/null as the filename.)
2016-03-17 21:42:46 +03:00
|
|
|
static char *readpassphrase(const char *filename)
|
|
|
|
{
|
|
|
|
FILE *fp;
|
|
|
|
char *line;
|
|
|
|
|
|
|
|
fp = fopen(filename, "r");
|
|
|
|
if (!fp) {
|
2019-09-08 22:29:00 +03:00
|
|
|
fprintf(stderr, "puttygen: cannot open %s: %s\n",
|
|
|
|
filename, strerror(errno));
|
|
|
|
return NULL;
|
Add command-line passphrase-file options to command-line PuTTYgen.
Patch due to Colin Watson.
Putting the passphrase in a file avoids exposing it to 'ps' which can
print out every process's command line, while at the same time not
being as platform-specific as the approach of providing an fd number
(since cmdgen.c is in principle a potential cross-platform PuTTYgen,
not just a Unix one, which is why it's not in the 'unix' directory).
Of course it introduces its own risks if someone can read the file
from your disk after you delete it; probably the best approach to
avoiding this, if possible, is to point the option at a file on an
in-memory tmpfs type file system. Or better still, use bash-style
/dev/fd options such as
puttygen --new-passphrase <(echo -n "my passphrase") [options]
Failing that, try a secure file-wipe utility, as the man page change
mentions.
(And a use case not to be overlooked, of course, is the one where you
actually want to generate an unprotected key - in which case, just
pass /dev/null as the filename.)
2016-03-17 21:42:46 +03:00
|
|
|
}
|
|
|
|
line = fgetline(fp);
|
|
|
|
if (line)
|
2019-09-08 22:29:00 +03:00
|
|
|
line[strcspn(line, "\r\n")] = '\0';
|
Add command-line passphrase-file options to command-line PuTTYgen.
Patch due to Colin Watson.
Putting the passphrase in a file avoids exposing it to 'ps' which can
print out every process's command line, while at the same time not
being as platform-specific as the approach of providing an fd number
(since cmdgen.c is in principle a potential cross-platform PuTTYgen,
not just a Unix one, which is why it's not in the 'unix' directory).
Of course it introduces its own risks if someone can read the file
from your disk after you delete it; probably the best approach to
avoiding this, if possible, is to point the option at a file on an
in-memory tmpfs type file system. Or better still, use bash-style
/dev/fd options such as
puttygen --new-passphrase <(echo -n "my passphrase") [options]
Failing that, try a secure file-wipe utility, as the man page change
mentions.
(And a use case not to be overlooked, of course, is the one where you
actually want to generate an unprotected key - in which case, just
pass /dev/null as the filename.)
2016-03-17 21:42:46 +03:00
|
|
|
else if (ferror(fp))
|
2019-09-08 22:29:00 +03:00
|
|
|
fprintf(stderr, "puttygen: error reading from %s: %s\n",
|
|
|
|
filename, strerror(errno));
|
|
|
|
else /* empty file */
|
|
|
|
line = dupstr("");
|
Add command-line passphrase-file options to command-line PuTTYgen.
Patch due to Colin Watson.
Putting the passphrase in a file avoids exposing it to 'ps' which can
print out every process's command line, while at the same time not
being as platform-specific as the approach of providing an fd number
(since cmdgen.c is in principle a potential cross-platform PuTTYgen,
not just a Unix one, which is why it's not in the 'unix' directory).
Of course it introduces its own risks if someone can read the file
from your disk after you delete it; probably the best approach to
avoiding this, if possible, is to point the option at a file on an
in-memory tmpfs type file system. Or better still, use bash-style
/dev/fd options such as
puttygen --new-passphrase <(echo -n "my passphrase") [options]
Failing that, try a secure file-wipe utility, as the man page change
mentions.
(And a use case not to be overlooked, of course, is the one where you
actually want to generate an unprotected key - in which case, just
pass /dev/null as the filename.)
2016-03-17 21:42:46 +03:00
|
|
|
fclose(fp);
|
|
|
|
return line;
|
|
|
|
}
|
|
|
|
|
2016-04-02 10:00:37 +03:00
|
|
|
#define DEFAULT_RSADSA_BITS 2048
|
|
|
|
|
2017-02-23 01:10:05 +03:00
|
|
|
/* For Unix in particular, but harmless if this main() is reused elsewhere */
|
Convert a lot of 'int' variables to 'bool'.
My normal habit these days, in new code, is to treat int and bool as
_almost_ completely separate types. I'm still willing to use C's
implicit test for zero on an integer (e.g. 'if (!blob.len)' is fine,
no need to spell it out as blob.len != 0), but generally, if a
variable is going to be conceptually a boolean, I like to declare it
bool and assign to it using 'true' or 'false' rather than 0 or 1.
PuTTY is an exception, because it predates the C99 bool, and I've
stuck to its existing coding style even when adding new code to it.
But it's been annoying me more and more, so now that I've decided C99
bool is an acceptable thing to require from our toolchain in the first
place, here's a quite thorough trawl through the source doing
'boolification'. Many variables and function parameters are now typed
as bool rather than int; many assignments of 0 or 1 to those variables
are now spelled 'true' or 'false'.
I managed this thorough conversion with the help of a custom clang
plugin that I wrote to trawl the AST and apply heuristics to point out
where things might want changing. So I've even managed to do a decent
job on parts of the code I haven't looked at in years!
To make the plugin's work easier, I pushed platform front ends
generally in the direction of using standard 'bool' in preference to
platform-specific boolean types like Windows BOOL or GTK's gboolean;
I've left the platform booleans in places they _have_ to be for the
platform APIs to work right, but variables only used by my own code
have been converted wherever I found them.
In a few places there are int values that look very like booleans in
_most_ of the places they're used, but have a rarely-used third value,
or a distinction between different nonzero values that most users
don't care about. In these cases, I've _removed_ uses of 'true' and
'false' for the return values, to emphasise that there's something
more subtle going on than a simple boolean answer:
- the 'multisel' field in dialog.h's list box structure, for which
the GTK front end in particular recognises a difference between 1
and 2 but nearly everything else treats as boolean
- the 'urgent' parameter to plug_receive, where 1 vs 2 tells you
something about the specific location of the urgent pointer, but
most clients only care about 0 vs 'something nonzero'
- the return value of wc_match, where -1 indicates a syntax error in
the wildcard.
- the return values from SSH-1 RSA-key loading functions, which use
-1 for 'wrong passphrase' and 0 for all other failures (so any
caller which already knows it's not loading an _encrypted private_
key can treat them as boolean)
- term->esc_query, and the 'query' parameter in toggle_mode in
terminal.c, which _usually_ hold 0 for ESC[123h or 1 for ESC[?123h,
but can also hold -1 for some other intervening character that we
don't support.
In a few places there's an integer that I haven't turned into a bool
even though it really _can_ only take values 0 or 1 (and, as above,
tried to make the call sites consistent in not calling those values
true and false), on the grounds that I thought it would make it more
confusing to imply that the 0 value was in some sense 'negative' or
bad and the 1 positive or good:
- the return value of plug_accepting uses the POSIXish convention of
0=success and nonzero=error; I think if I made it bool then I'd
also want to reverse its sense, and that's a job for a separate
piece of work.
- the 'screen' parameter to lineptr() in terminal.c, where 0 and 1
represent the default and alternate screens. There's no obvious
reason why one of those should be considered 'true' or 'positive'
or 'success' - they're just indices - so I've left it as int.
ssh_scp_recv had particularly confusing semantics for its previous int
return value: its call sites used '<= 0' to check for error, but it
never actually returned a negative number, just 0 or 1. Now the
function and its call sites agree that it's a bool.
In a couple of places I've renamed variables called 'ret', because I
don't like that name any more - it's unclear whether it means the
return value (in preparation) for the _containing_ function or the
return value received from a subroutine call, and occasionally I've
accidentally used the same variable for both and introduced a bug. So
where one of those got in my way, I've renamed it to 'toret' or 'retd'
(the latter short for 'returned') in line with my usual modern
practice, but I haven't done a thorough job of finding all of them.
Finally, one amusing side effect of doing this is that I've had to
separate quite a few chained assignments. It used to be perfectly fine
to write 'a = b = c = TRUE' when a,b,c were int and TRUE was just a
the 'true' defined by stdbool.h, that idiom provokes a warning from
gcc: 'suggest parentheses around assignment used as truth value'!
2018-11-02 22:23:19 +03:00
|
|
|
const bool buildinfo_gtk_relevant = false;
|
2017-02-23 01:10:05 +03:00
|
|
|
|
2004-01-22 22:15:32 +03:00
|
|
|
int main(int argc, char **argv)
|
|
|
|
{
|
|
|
|
char *infile = NULL;
|
2012-03-05 22:32:27 +04:00
|
|
|
Filename *infilename = NULL, *outfilename = NULL;
|
2015-05-09 17:02:54 +03:00
|
|
|
enum { NOKEYGEN, RSA1, RSA2, DSA, ECDSA, ED25519 } keytype = NOKEYGEN;
|
2004-01-22 22:15:32 +03:00
|
|
|
char *outfile = NULL, *outfiletmp = NULL;
|
2015-05-10 09:42:48 +03:00
|
|
|
enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH_AUTO,
|
2015-04-28 21:46:58 +03:00
|
|
|
OPENSSH_NEW, SSHCOM } outtype = PRIVATE;
|
2015-05-09 17:02:47 +03:00
|
|
|
int bits = -1;
|
2019-07-06 19:54:56 +03:00
|
|
|
const char *comment = NULL;
|
|
|
|
char *origcomment = NULL;
|
Convert a lot of 'int' variables to 'bool'.
My normal habit these days, in new code, is to treat int and bool as
_almost_ completely separate types. I'm still willing to use C's
implicit test for zero on an integer (e.g. 'if (!blob.len)' is fine,
no need to spell it out as blob.len != 0), but generally, if a
variable is going to be conceptually a boolean, I like to declare it
bool and assign to it using 'true' or 'false' rather than 0 or 1.
PuTTY is an exception, because it predates the C99 bool, and I've
stuck to its existing coding style even when adding new code to it.
But it's been annoying me more and more, so now that I've decided C99
bool is an acceptable thing to require from our toolchain in the first
place, here's a quite thorough trawl through the source doing
'boolification'. Many variables and function parameters are now typed
as bool rather than int; many assignments of 0 or 1 to those variables
are now spelled 'true' or 'false'.
I managed this thorough conversion with the help of a custom clang
plugin that I wrote to trawl the AST and apply heuristics to point out
where things might want changing. So I've even managed to do a decent
job on parts of the code I haven't looked at in years!
To make the plugin's work easier, I pushed platform front ends
generally in the direction of using standard 'bool' in preference to
platform-specific boolean types like Windows BOOL or GTK's gboolean;
I've left the platform booleans in places they _have_ to be for the
platform APIs to work right, but variables only used by my own code
have been converted wherever I found them.
In a few places there are int values that look very like booleans in
_most_ of the places they're used, but have a rarely-used third value,
or a distinction between different nonzero values that most users
don't care about. In these cases, I've _removed_ uses of 'true' and
'false' for the return values, to emphasise that there's something
more subtle going on than a simple boolean answer:
- the 'multisel' field in dialog.h's list box structure, for which
the GTK front end in particular recognises a difference between 1
and 2 but nearly everything else treats as boolean
- the 'urgent' parameter to plug_receive, where 1 vs 2 tells you
something about the specific location of the urgent pointer, but
most clients only care about 0 vs 'something nonzero'
- the return value of wc_match, where -1 indicates a syntax error in
the wildcard.
- the return values from SSH-1 RSA-key loading functions, which use
-1 for 'wrong passphrase' and 0 for all other failures (so any
caller which already knows it's not loading an _encrypted private_
key can treat them as boolean)
- term->esc_query, and the 'query' parameter in toggle_mode in
terminal.c, which _usually_ hold 0 for ESC[123h or 1 for ESC[?123h,
but can also hold -1 for some other intervening character that we
don't support.
In a few places there's an integer that I haven't turned into a bool
even though it really _can_ only take values 0 or 1 (and, as above,
tried to make the call sites consistent in not calling those values
true and false), on the grounds that I thought it would make it more
confusing to imply that the 0 value was in some sense 'negative' or
bad and the 1 positive or good:
- the return value of plug_accepting uses the POSIXish convention of
0=success and nonzero=error; I think if I made it bool then I'd
also want to reverse its sense, and that's a job for a separate
piece of work.
- the 'screen' parameter to lineptr() in terminal.c, where 0 and 1
represent the default and alternate screens. There's no obvious
reason why one of those should be considered 'true' or 'positive'
or 'success' - they're just indices - so I've left it as int.
ssh_scp_recv had particularly confusing semantics for its previous int
return value: its call sites used '<= 0' to check for error, but it
never actually returned a negative number, just 0 or 1. Now the
function and its call sites agree that it's a bool.
In a couple of places I've renamed variables called 'ret', because I
don't like that name any more - it's unclear whether it means the
return value (in preparation) for the _containing_ function or the
return value received from a subroutine call, and occasionally I've
accidentally used the same variable for both and introduced a bug. So
where one of those got in my way, I've renamed it to 'toret' or 'retd'
(the latter short for 'returned') in line with my usual modern
practice, but I haven't done a thorough job of finding all of them.
Finally, one amusing side effect of doing this is that I've had to
separate quite a few chained assignments. It used to be perfectly fine
to write 'a = b = c = TRUE' when a,b,c were int and TRUE was just a
the 'true' defined by stdbool.h, that idiom provokes a warning from
gcc: 'suggest parentheses around assignment used as truth value'!
2018-11-02 22:23:19 +03:00
|
|
|
bool change_passphrase = false;
|
|
|
|
bool errs = false, nogo = false;
|
2004-01-22 22:15:32 +03:00
|
|
|
int intype = SSH_KEYTYPE_UNOPENABLE;
|
|
|
|
int sshver = 0;
|
2019-01-04 09:51:44 +03:00
|
|
|
ssh2_userkey *ssh2key = NULL;
|
|
|
|
RSAKey *ssh1key = NULL;
|
2018-05-24 12:59:39 +03:00
|
|
|
strbuf *ssh2blob = NULL;
|
2006-12-31 02:00:14 +03:00
|
|
|
char *ssh2alg = NULL;
|
Add command-line passphrase-file options to command-line PuTTYgen.
Patch due to Colin Watson.
Putting the passphrase in a file avoids exposing it to 'ps' which can
print out every process's command line, while at the same time not
being as platform-specific as the approach of providing an fd number
(since cmdgen.c is in principle a potential cross-platform PuTTYgen,
not just a Unix one, which is why it's not in the 'unix' directory).
Of course it introduces its own risks if someone can read the file
from your disk after you delete it; probably the best approach to
avoiding this, if possible, is to point the option at a file on an
in-memory tmpfs type file system. Or better still, use bash-style
/dev/fd options such as
puttygen --new-passphrase <(echo -n "my passphrase") [options]
Failing that, try a secure file-wipe utility, as the man page change
mentions.
(And a use case not to be overlooked, of course, is the one where you
actually want to generate an unprotected key - in which case, just
pass /dev/null as the filename.)
2016-03-17 21:42:46 +03:00
|
|
|
char *old_passphrase = NULL, *new_passphrase = NULL;
|
Convert a lot of 'int' variables to 'bool'.
My normal habit these days, in new code, is to treat int and bool as
_almost_ completely separate types. I'm still willing to use C's
implicit test for zero on an integer (e.g. 'if (!blob.len)' is fine,
no need to spell it out as blob.len != 0), but generally, if a
variable is going to be conceptually a boolean, I like to declare it
bool and assign to it using 'true' or 'false' rather than 0 or 1.
PuTTY is an exception, because it predates the C99 bool, and I've
stuck to its existing coding style even when adding new code to it.
But it's been annoying me more and more, so now that I've decided C99
bool is an acceptable thing to require from our toolchain in the first
place, here's a quite thorough trawl through the source doing
'boolification'. Many variables and function parameters are now typed
as bool rather than int; many assignments of 0 or 1 to those variables
are now spelled 'true' or 'false'.
I managed this thorough conversion with the help of a custom clang
plugin that I wrote to trawl the AST and apply heuristics to point out
where things might want changing. So I've even managed to do a decent
job on parts of the code I haven't looked at in years!
To make the plugin's work easier, I pushed platform front ends
generally in the direction of using standard 'bool' in preference to
platform-specific boolean types like Windows BOOL or GTK's gboolean;
I've left the platform booleans in places they _have_ to be for the
platform APIs to work right, but variables only used by my own code
have been converted wherever I found them.
In a few places there are int values that look very like booleans in
_most_ of the places they're used, but have a rarely-used third value,
or a distinction between different nonzero values that most users
don't care about. In these cases, I've _removed_ uses of 'true' and
'false' for the return values, to emphasise that there's something
more subtle going on than a simple boolean answer:
- the 'multisel' field in dialog.h's list box structure, for which
the GTK front end in particular recognises a difference between 1
and 2 but nearly everything else treats as boolean
- the 'urgent' parameter to plug_receive, where 1 vs 2 tells you
something about the specific location of the urgent pointer, but
most clients only care about 0 vs 'something nonzero'
- the return value of wc_match, where -1 indicates a syntax error in
the wildcard.
- the return values from SSH-1 RSA-key loading functions, which use
-1 for 'wrong passphrase' and 0 for all other failures (so any
caller which already knows it's not loading an _encrypted private_
key can treat them as boolean)
- term->esc_query, and the 'query' parameter in toggle_mode in
terminal.c, which _usually_ hold 0 for ESC[123h or 1 for ESC[?123h,
but can also hold -1 for some other intervening character that we
don't support.
In a few places there's an integer that I haven't turned into a bool
even though it really _can_ only take values 0 or 1 (and, as above,
tried to make the call sites consistent in not calling those values
true and false), on the grounds that I thought it would make it more
confusing to imply that the 0 value was in some sense 'negative' or
bad and the 1 positive or good:
- the return value of plug_accepting uses the POSIXish convention of
0=success and nonzero=error; I think if I made it bool then I'd
also want to reverse its sense, and that's a job for a separate
piece of work.
- the 'screen' parameter to lineptr() in terminal.c, where 0 and 1
represent the default and alternate screens. There's no obvious
reason why one of those should be considered 'true' or 'positive'
or 'success' - they're just indices - so I've left it as int.
ssh_scp_recv had particularly confusing semantics for its previous int
return value: its call sites used '<= 0' to check for error, but it
never actually returned a negative number, just 0 or 1. Now the
function and its call sites agree that it's a bool.
In a couple of places I've renamed variables called 'ret', because I
don't like that name any more - it's unclear whether it means the
return value (in preparation) for the _containing_ function or the
return value received from a subroutine call, and occasionally I've
accidentally used the same variable for both and introduced a bug. So
where one of those got in my way, I've renamed it to 'toret' or 'retd'
(the latter short for 'returned') in line with my usual modern
practice, but I haven't done a thorough job of finding all of them.
Finally, one amusing side effect of doing this is that I've had to
separate quite a few chained assignments. It used to be perfectly fine
to write 'a = b = c = TRUE' when a,b,c were int and TRUE was just a
the 'true' defined by stdbool.h, that idiom provokes a warning from
gcc: 'suggest parentheses around assignment used as truth value'!
2018-11-02 22:23:19 +03:00
|
|
|
bool load_encrypted;
|
2004-01-22 22:15:32 +03:00
|
|
|
progfn_t progressfn = is_interactive() ? progress_update : no_progress;
|
2016-03-30 10:17:03 +03:00
|
|
|
const char *random_device = NULL;
|
2020-01-14 01:14:02 +03:00
|
|
|
int exit_status = 0;
|
|
|
|
|
|
|
|
#define RETURN(status) do { exit_status = (status); goto out; } while (0)
|
2004-01-22 22:15:32 +03:00
|
|
|
|
|
|
|
/* ------------------------------------------------------------------
|
|
|
|
* Parse the command line to figure out what we've been asked to do.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If run with no arguments at all, print the usage message and
|
|
|
|
* return success.
|
|
|
|
*/
|
|
|
|
if (argc <= 1) {
|
2019-09-08 22:29:00 +03:00
|
|
|
usage(true);
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(0);
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Parse command line arguments.
|
|
|
|
*/
|
|
|
|
while (--argc) {
|
2019-09-08 22:29:00 +03:00
|
|
|
char *p = *++argv;
|
|
|
|
if (*p == '-') {
|
|
|
|
/*
|
|
|
|
* An option.
|
|
|
|
*/
|
|
|
|
while (p && *++p) {
|
|
|
|
char c = *p;
|
|
|
|
switch (c) {
|
|
|
|
case '-':
|
|
|
|
/*
|
|
|
|
* Long option.
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
char *opt, *val;
|
|
|
|
opt = p++; /* opt will have _one_ leading - */
|
|
|
|
while (*p && *p != '=')
|
|
|
|
p++; /* find end of option */
|
|
|
|
if (*p == '=') {
|
|
|
|
*p++ = '\0';
|
|
|
|
val = p;
|
|
|
|
} else
|
2011-05-07 14:57:19 +04:00
|
|
|
val = NULL;
|
|
|
|
|
2019-09-08 22:29:00 +03:00
|
|
|
if (!strcmp(opt, "-help")) {
|
2011-05-07 14:57:19 +04:00
|
|
|
if (val) {
|
2018-10-29 22:50:29 +03:00
|
|
|
errs = true;
|
2011-05-07 14:57:19 +04:00
|
|
|
fprintf(stderr, "puttygen: option `-%s'"
|
|
|
|
" expects no argument\n", opt);
|
|
|
|
} else {
|
|
|
|
help();
|
2018-10-29 22:50:29 +03:00
|
|
|
nogo = true;
|
2011-05-07 14:57:19 +04:00
|
|
|
}
|
2019-09-08 22:29:00 +03:00
|
|
|
} else if (!strcmp(opt, "-version")) {
|
2011-05-07 14:57:19 +04:00
|
|
|
if (val) {
|
2018-10-29 22:50:29 +03:00
|
|
|
errs = true;
|
2011-05-07 14:57:19 +04:00
|
|
|
fprintf(stderr, "puttygen: option `-%s'"
|
|
|
|
" expects no argument\n", opt);
|
|
|
|
} else {
|
|
|
|
showversion();
|
2018-10-29 22:50:29 +03:00
|
|
|
nogo = true;
|
2011-05-07 14:57:19 +04:00
|
|
|
}
|
2019-09-08 22:29:00 +03:00
|
|
|
} else if (!strcmp(opt, "-pgpfp")) {
|
2011-05-07 14:57:19 +04:00
|
|
|
if (val) {
|
2018-10-29 22:50:29 +03:00
|
|
|
errs = true;
|
2011-05-07 14:57:19 +04:00
|
|
|
fprintf(stderr, "puttygen: option `-%s'"
|
|
|
|
" expects no argument\n", opt);
|
|
|
|
} else {
|
|
|
|
/* support --pgpfp for consistency */
|
|
|
|
pgp_fingerprints();
|
2018-10-29 22:50:29 +03:00
|
|
|
nogo = true;
|
2011-05-07 14:57:19 +04:00
|
|
|
}
|
2019-09-08 22:29:00 +03:00
|
|
|
} else if (!strcmp(opt, "-old-passphrase")) {
|
|
|
|
if (!val && argc > 1)
|
|
|
|
--argc, val = *++argv;
|
|
|
|
if (!val) {
|
|
|
|
errs = true;
|
|
|
|
fprintf(stderr, "puttygen: option `-%s'"
|
|
|
|
" expects an argument\n", opt);
|
|
|
|
} else {
|
|
|
|
old_passphrase = readpassphrase(val);
|
|
|
|
if (!old_passphrase)
|
|
|
|
errs = true;
|
|
|
|
}
|
|
|
|
} else if (!strcmp(opt, "-new-passphrase")) {
|
|
|
|
if (!val && argc > 1)
|
|
|
|
--argc, val = *++argv;
|
|
|
|
if (!val) {
|
|
|
|
errs = true;
|
|
|
|
fprintf(stderr, "puttygen: option `-%s'"
|
|
|
|
" expects an argument\n", opt);
|
|
|
|
} else {
|
|
|
|
new_passphrase = readpassphrase(val);
|
|
|
|
if (!new_passphrase)
|
|
|
|
errs = true;
|
|
|
|
}
|
|
|
|
} else if (!strcmp(opt, "-random-device")) {
|
|
|
|
if (!val && argc > 1)
|
|
|
|
--argc, val = *++argv;
|
|
|
|
if (!val) {
|
|
|
|
errs = true;
|
|
|
|
fprintf(stderr, "puttygen: option `-%s'"
|
|
|
|
" expects an argument\n", opt);
|
|
|
|
} else {
|
2016-03-30 10:17:03 +03:00
|
|
|
random_device = val;
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
errs = true;
|
|
|
|
fprintf(stderr,
|
|
|
|
"puttygen: no such option `-%s'\n", opt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
p = NULL;
|
|
|
|
break;
|
|
|
|
case 'h':
|
|
|
|
case 'V':
|
|
|
|
case 'P':
|
|
|
|
case 'l':
|
|
|
|
case 'L':
|
|
|
|
case 'p':
|
|
|
|
case 'q':
|
|
|
|
/*
|
|
|
|
* Option requiring no parameter.
|
|
|
|
*/
|
|
|
|
switch (c) {
|
|
|
|
case 'h':
|
|
|
|
help();
|
|
|
|
nogo = true;
|
|
|
|
break;
|
|
|
|
case 'V':
|
|
|
|
showversion();
|
|
|
|
nogo = true;
|
|
|
|
break;
|
|
|
|
case 'P':
|
|
|
|
change_passphrase = true;
|
|
|
|
break;
|
|
|
|
case 'l':
|
|
|
|
outtype = FP;
|
|
|
|
break;
|
|
|
|
case 'L':
|
|
|
|
outtype = PUBLICO;
|
|
|
|
break;
|
|
|
|
case 'p':
|
|
|
|
outtype = PUBLIC;
|
|
|
|
break;
|
|
|
|
case 'q':
|
|
|
|
progressfn = no_progress;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 't':
|
|
|
|
case 'b':
|
|
|
|
case 'C':
|
|
|
|
case 'O':
|
|
|
|
case 'o':
|
|
|
|
/*
|
|
|
|
* Option requiring parameter.
|
|
|
|
*/
|
|
|
|
p++;
|
|
|
|
if (!*p && argc > 1)
|
|
|
|
--argc, p = *++argv;
|
|
|
|
else if (!*p) {
|
|
|
|
fprintf(stderr, "puttygen: option `-%c' expects a"
|
|
|
|
" parameter\n", c);
|
|
|
|
errs = true;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Now c is the option and p is the parameter.
|
|
|
|
*/
|
|
|
|
switch (c) {
|
|
|
|
case 't':
|
|
|
|
if (!strcmp(p, "rsa") || !strcmp(p, "rsa2"))
|
|
|
|
keytype = RSA2, sshver = 2;
|
|
|
|
else if (!strcmp(p, "rsa1"))
|
|
|
|
keytype = RSA1, sshver = 1;
|
|
|
|
else if (!strcmp(p, "dsa") || !strcmp(p, "dss"))
|
|
|
|
keytype = DSA, sshver = 2;
|
2014-11-01 12:45:20 +03:00
|
|
|
else if (!strcmp(p, "ecdsa"))
|
|
|
|
keytype = ECDSA, sshver = 2;
|
2015-05-09 17:02:54 +03:00
|
|
|
else if (!strcmp(p, "ed25519"))
|
|
|
|
keytype = ED25519, sshver = 2;
|
2019-09-08 22:29:00 +03:00
|
|
|
else {
|
|
|
|
fprintf(stderr,
|
|
|
|
"puttygen: unknown key type `%s'\n", p);
|
|
|
|
errs = true;
|
|
|
|
}
|
2004-01-22 22:15:32 +03:00
|
|
|
break;
|
2019-09-08 22:29:00 +03:00
|
|
|
case 'b':
|
|
|
|
bits = atoi(p);
|
2004-01-22 22:15:32 +03:00
|
|
|
break;
|
2019-09-08 22:29:00 +03:00
|
|
|
case 'C':
|
|
|
|
comment = p;
|
2004-01-22 22:15:32 +03:00
|
|
|
break;
|
2019-09-08 22:29:00 +03:00
|
|
|
case 'O':
|
|
|
|
if (!strcmp(p, "public"))
|
|
|
|
outtype = PUBLIC;
|
|
|
|
else if (!strcmp(p, "public-openssh"))
|
|
|
|
outtype = PUBLICO;
|
|
|
|
else if (!strcmp(p, "private"))
|
|
|
|
outtype = PRIVATE;
|
|
|
|
else if (!strcmp(p, "fingerprint"))
|
|
|
|
outtype = FP;
|
|
|
|
else if (!strcmp(p, "private-openssh"))
|
|
|
|
outtype = OPENSSH_AUTO, sshver = 2;
|
|
|
|
else if (!strcmp(p, "private-openssh-new"))
|
|
|
|
outtype = OPENSSH_NEW, sshver = 2;
|
|
|
|
else if (!strcmp(p, "private-sshcom"))
|
|
|
|
outtype = SSHCOM, sshver = 2;
|
|
|
|
else {
|
|
|
|
fprintf(stderr,
|
|
|
|
"puttygen: unknown output type `%s'\n", p);
|
|
|
|
errs = true;
|
|
|
|
}
|
2004-01-22 22:15:32 +03:00
|
|
|
break;
|
2019-09-08 22:29:00 +03:00
|
|
|
case 'o':
|
|
|
|
outfile = p;
|
2004-01-22 22:15:32 +03:00
|
|
|
break;
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
|
|
|
p = NULL; /* prevent continued processing */
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/*
|
|
|
|
* Unrecognised option.
|
|
|
|
*/
|
|
|
|
errs = true;
|
|
|
|
fprintf(stderr, "puttygen: no such option `-%c'\n", c);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* A non-option argument.
|
|
|
|
*/
|
|
|
|
if (!infile)
|
|
|
|
infile = p;
|
|
|
|
else {
|
|
|
|
errs = true;
|
|
|
|
fprintf(stderr, "puttygen: cannot handle more than one"
|
|
|
|
" input file\n");
|
|
|
|
}
|
|
|
|
}
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
|
|
|
|
2015-05-09 17:02:47 +03:00
|
|
|
if (bits == -1) {
|
|
|
|
/*
|
|
|
|
* No explicit key size was specified. Default varies
|
|
|
|
* depending on key type.
|
|
|
|
*/
|
|
|
|
switch (keytype) {
|
|
|
|
case ECDSA:
|
|
|
|
bits = 384;
|
|
|
|
break;
|
2015-05-09 17:02:54 +03:00
|
|
|
case ED25519:
|
2020-01-14 09:39:32 +03:00
|
|
|
bits = 255;
|
2015-05-09 17:02:54 +03:00
|
|
|
break;
|
2015-05-09 17:02:47 +03:00
|
|
|
default:
|
2016-04-02 10:00:37 +03:00
|
|
|
bits = DEFAULT_RSADSA_BITS;
|
2015-05-09 17:02:47 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-01 12:45:20 +03:00
|
|
|
if (keytype == ECDSA && (bits != 256 && bits != 384 && bits != 521)) {
|
|
|
|
fprintf(stderr, "puttygen: invalid bits for ECDSA, choose 256, 384 or 521\n");
|
2018-10-29 22:50:29 +03:00
|
|
|
errs = true;
|
2014-11-01 12:45:20 +03:00
|
|
|
}
|
|
|
|
|
2020-01-14 09:39:32 +03:00
|
|
|
if (keytype == ED25519 && (bits != 255) && (bits != 256)) {
|
|
|
|
fprintf(stderr, "puttygen: invalid bits for ED25519, choose 255\n");
|
2018-10-29 22:50:29 +03:00
|
|
|
errs = true;
|
2015-05-09 17:02:54 +03:00
|
|
|
}
|
|
|
|
|
2016-03-30 13:41:11 +03:00
|
|
|
if (keytype == RSA2 || keytype == RSA1 || keytype == DSA) {
|
|
|
|
if (bits < 256) {
|
|
|
|
fprintf(stderr, "puttygen: cannot generate %s keys shorter than"
|
|
|
|
" 256 bits\n", (keytype == DSA ? "DSA" : "RSA"));
|
2018-10-29 22:50:29 +03:00
|
|
|
errs = true;
|
2016-04-02 10:00:37 +03:00
|
|
|
} else if (bits < DEFAULT_RSADSA_BITS) {
|
|
|
|
fprintf(stderr, "puttygen: warning: %s keys shorter than"
|
|
|
|
" %d bits are probably not secure\n",
|
|
|
|
(keytype == DSA ? "DSA" : "RSA"), DEFAULT_RSADSA_BITS);
|
|
|
|
/* but this is just a warning, so proceed anyway */
|
2016-03-30 13:41:11 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2004-01-22 22:15:32 +03:00
|
|
|
if (errs)
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1);
|
2004-01-22 22:15:32 +03:00
|
|
|
|
|
|
|
if (nogo)
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(0);
|
2004-01-22 22:15:32 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If run with at least one argument _but_ not the required
|
|
|
|
* ones, print the usage message and return failure.
|
|
|
|
*/
|
|
|
|
if (!infile && keytype == NOKEYGEN) {
|
2019-09-08 22:29:00 +03:00
|
|
|
usage(true);
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1);
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------
|
|
|
|
* Figure out further details of exactly what we're going to do.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Bomb out if we've been asked to both load and generate a
|
|
|
|
* key.
|
|
|
|
*/
|
2006-07-07 18:18:47 +04:00
|
|
|
if (keytype != NOKEYGEN && infile) {
|
2019-09-08 22:29:00 +03:00
|
|
|
fprintf(stderr, "puttygen: cannot both load and generate a key\n");
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1);
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
|
|
|
|
2019-09-08 22:29:00 +03:00
|
|
|
/*
|
2006-07-07 18:18:47 +04:00
|
|
|
* We must save the private part when generating a new key.
|
|
|
|
*/
|
|
|
|
if (keytype != NOKEYGEN &&
|
2019-09-08 22:29:00 +03:00
|
|
|
(outtype != PRIVATE && outtype != OPENSSH_AUTO &&
|
2015-04-28 21:46:58 +03:00
|
|
|
outtype != OPENSSH_NEW && outtype != SSHCOM)) {
|
2019-09-08 22:29:00 +03:00
|
|
|
fprintf(stderr, "puttygen: this would generate a new key but "
|
|
|
|
"discard the private part\n");
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1);
|
2006-07-07 18:18:47 +04:00
|
|
|
}
|
|
|
|
|
2004-01-22 22:15:32 +03:00
|
|
|
/*
|
|
|
|
* Analyse the type of the input file, in case this affects our
|
|
|
|
* course of action.
|
|
|
|
*/
|
|
|
|
if (infile) {
|
2019-09-08 22:29:00 +03:00
|
|
|
infilename = filename_from_str(infile);
|
2004-01-22 22:15:32 +03:00
|
|
|
|
2019-09-08 22:29:00 +03:00
|
|
|
intype = key_type(infilename);
|
2004-01-22 22:15:32 +03:00
|
|
|
|
2019-09-08 22:29:00 +03:00
|
|
|
switch (intype) {
|
|
|
|
case SSH_KEYTYPE_UNOPENABLE:
|
|
|
|
case SSH_KEYTYPE_UNKNOWN:
|
|
|
|
fprintf(stderr, "puttygen: unable to load file `%s': %s\n",
|
|
|
|
infile, key_type_to_str(intype));
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1);
|
2004-01-22 22:15:32 +03:00
|
|
|
|
2019-09-08 22:29:00 +03:00
|
|
|
case SSH_KEYTYPE_SSH1:
|
2015-05-12 14:19:57 +03:00
|
|
|
case SSH_KEYTYPE_SSH1_PUBLIC:
|
2019-09-08 22:29:00 +03:00
|
|
|
if (sshver == 2) {
|
|
|
|
fprintf(stderr, "puttygen: conversion from SSH-1 to SSH-2 keys"
|
|
|
|
" not supported\n");
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1);
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
|
|
|
sshver = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SSH_KEYTYPE_SSH2:
|
2015-05-12 14:19:57 +03:00
|
|
|
case SSH_KEYTYPE_SSH2_PUBLIC_RFC4716:
|
|
|
|
case SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH:
|
2019-09-08 22:29:00 +03:00
|
|
|
case SSH_KEYTYPE_OPENSSH_PEM:
|
|
|
|
case SSH_KEYTYPE_OPENSSH_NEW:
|
|
|
|
case SSH_KEYTYPE_SSHCOM:
|
|
|
|
if (sshver == 1) {
|
|
|
|
fprintf(stderr, "puttygen: conversion from SSH-2 to SSH-1 keys"
|
|
|
|
" not supported\n");
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1);
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
|
|
|
sshver = 2;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SSH_KEYTYPE_OPENSSH_AUTO:
|
2015-05-10 09:42:48 +03:00
|
|
|
default:
|
2019-01-03 11:12:19 +03:00
|
|
|
unreachable("Should never see these types on an input file");
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Determine the default output file, if none is provided.
|
2019-09-08 22:29:00 +03:00
|
|
|
*
|
2004-01-22 22:15:32 +03:00
|
|
|
* This will usually be equal to stdout, except that if the
|
|
|
|
* input and output file formats are the same then the default
|
|
|
|
* output is to overwrite the input.
|
2019-09-08 22:29:00 +03:00
|
|
|
*
|
2004-01-22 22:15:32 +03:00
|
|
|
* Also in this code, we bomb out if the input and output file
|
|
|
|
* formats are the same and no other action is performed.
|
|
|
|
*/
|
|
|
|
if ((intype == SSH_KEYTYPE_SSH1 && outtype == PRIVATE) ||
|
2019-09-08 22:29:00 +03:00
|
|
|
(intype == SSH_KEYTYPE_SSH2 && outtype == PRIVATE) ||
|
|
|
|
(intype == SSH_KEYTYPE_OPENSSH_PEM && outtype == OPENSSH_AUTO) ||
|
|
|
|
(intype == SSH_KEYTYPE_OPENSSH_NEW && outtype == OPENSSH_NEW) ||
|
|
|
|
(intype == SSH_KEYTYPE_SSHCOM && outtype == SSHCOM)) {
|
|
|
|
if (!outfile) {
|
|
|
|
outfile = infile;
|
Make dupcat() into a variadic macro.
Up until now, it's been a variadic _function_, whose argument list
consists of 'const char *' ASCIZ strings to concatenate, terminated by
one containing a null pointer. Now, that function is dupcat_fn(), and
it's wrapped by a C99 variadic _macro_ called dupcat(), which
automatically suffixes the null-pointer terminating argument.
This has three benefits. Firstly, it's just less effort at every call
site. Secondly, it protects against the risk of accidentally leaving
off the NULL, causing arbitrary words of stack memory to be
dereferenced as char pointers. And thirdly, it protects against the
more subtle risk of writing a bare 'NULL' as the terminating argument,
instead of casting it explicitly to a pointer. That last one is
necessary because C permits the macro NULL to expand to an integer
constant such as 0, so NULL by itself may not have pointer type, and
worse, it may not be marshalled in a variadic argument list in the
same way as a pointer. (For example, on a 64-bit machine it might only
occupy 32 bits. And yet, on another 64-bit platform, it might work
just fine, so that you don't notice the mistake!)
I was inspired to do this by happening to notice one of those bare
NULL terminators, and thinking I'd better check if there were any
more. Turned out there were quite a few. Now there are none.
2019-10-14 21:42:37 +03:00
|
|
|
outfiletmp = dupcat(outfile, ".tmp");
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!change_passphrase && !comment) {
|
|
|
|
fprintf(stderr, "puttygen: this command would perform no useful"
|
|
|
|
" action\n");
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1);
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
2004-01-22 22:15:32 +03:00
|
|
|
} else {
|
2019-09-08 22:29:00 +03:00
|
|
|
if (!outfile) {
|
|
|
|
/*
|
|
|
|
* Bomb out rather than automatically choosing to write
|
|
|
|
* a private key file to stdout.
|
|
|
|
*/
|
|
|
|
if (outtype == PRIVATE || outtype == OPENSSH_AUTO ||
|
2015-04-28 21:46:58 +03:00
|
|
|
outtype == OPENSSH_NEW || outtype == SSHCOM) {
|
2019-09-08 22:29:00 +03:00
|
|
|
fprintf(stderr, "puttygen: need to specify an output file\n");
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1);
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
|
|
|
}
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Figure out whether we need to load the encrypted part of the
|
|
|
|
* key. This will be the case if either (a) we need to write
|
|
|
|
* out a private key format, or (b) the entire input key file
|
|
|
|
* is encrypted.
|
|
|
|
*/
|
2015-05-10 09:42:48 +03:00
|
|
|
if (outtype == PRIVATE || outtype == OPENSSH_AUTO ||
|
2015-04-28 21:46:58 +03:00
|
|
|
outtype == OPENSSH_NEW || outtype == SSHCOM ||
|
2019-09-08 22:29:00 +03:00
|
|
|
intype == SSH_KEYTYPE_OPENSSH_PEM ||
|
|
|
|
intype == SSH_KEYTYPE_OPENSSH_NEW ||
|
2015-04-28 21:46:58 +03:00
|
|
|
intype == SSH_KEYTYPE_SSHCOM)
|
2019-09-08 22:29:00 +03:00
|
|
|
load_encrypted = true;
|
2004-01-22 22:15:32 +03:00
|
|
|
else
|
2019-09-08 22:29:00 +03:00
|
|
|
load_encrypted = false;
|
2004-01-22 22:15:32 +03:00
|
|
|
|
2015-05-12 14:19:57 +03:00
|
|
|
if (load_encrypted && (intype == SSH_KEYTYPE_SSH1_PUBLIC ||
|
|
|
|
intype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
|
|
|
|
intype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)) {
|
|
|
|
fprintf(stderr, "puttygen: cannot perform this action on a "
|
|
|
|
"public-key-only input file\n");
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1);
|
2015-05-12 14:19:57 +03:00
|
|
|
}
|
|
|
|
|
2004-01-22 22:15:32 +03:00
|
|
|
/* ------------------------------------------------------------------
|
|
|
|
* Now we're ready to actually do some stuff.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Either load or generate a key.
|
|
|
|
*/
|
|
|
|
if (keytype != NOKEYGEN) {
|
2019-09-08 22:29:00 +03:00
|
|
|
char *entropy;
|
|
|
|
char default_comment[80];
|
|
|
|
struct tm tm;
|
|
|
|
struct progress prog;
|
2004-01-22 22:15:32 +03:00
|
|
|
|
2019-09-08 22:29:00 +03:00
|
|
|
prog.phase = -1;
|
|
|
|
prog.current = -1;
|
2004-01-22 22:15:32 +03:00
|
|
|
|
2019-09-08 22:29:00 +03:00
|
|
|
tm = ltime();
|
|
|
|
if (keytype == DSA)
|
|
|
|
strftime(default_comment, 30, "dsa-key-%Y%m%d", &tm);
|
2014-11-01 12:45:20 +03:00
|
|
|
else if (keytype == ECDSA)
|
|
|
|
strftime(default_comment, 30, "ecdsa-key-%Y%m%d", &tm);
|
2015-05-09 17:02:54 +03:00
|
|
|
else if (keytype == ED25519)
|
|
|
|
strftime(default_comment, 30, "ed25519-key-%Y%m%d", &tm);
|
2019-09-08 22:29:00 +03:00
|
|
|
else
|
|
|
|
strftime(default_comment, 30, "rsa-key-%Y%m%d", &tm);
|
|
|
|
|
|
|
|
entropy = get_random_data(bits / 8, random_device);
|
|
|
|
if (!entropy) {
|
|
|
|
fprintf(stderr, "puttygen: failed to collect entropy, "
|
|
|
|
"could not generate key\n");
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1);
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
|
|
|
random_setup_special();
|
Replace PuTTY's PRNG with a Fortuna-like system.
This tears out the entire previous random-pool system in sshrand.c. In
its place is a system pretty close to Ferguson and Schneier's
'Fortuna' generator, with the main difference being that I use SHA-256
instead of AES for the generation side of the system (rationale given
in comment).
The PRNG implementation lives in sshprng.c, and defines a self-
contained data type with no state stored outside the object, so you
can instantiate however many of them you like. The old sshrand.c still
exists, but in place of the previous random pool system, it's just
become a client of sshprng.c, whose job is to hold a single global
instance of the PRNG type, and manage its reference count, save file,
noise-collection timers and similar administrative business.
Advantages of this change include:
- Fortuna is designed with a more varied threat model in mind than my
old home-grown random pool. For example, after any request for
random numbers, it automatically re-seeds itself, so that if the
state of the PRNG should be leaked, it won't give enough
information to find out what past outputs _were_.
- The PRNG type can be instantiated with any hash function; the
instance used by the main tools is based on SHA-256, an improvement
on the old pool's use of SHA-1.
- The new PRNG only uses the completely standard interface to the
hash function API, instead of having to have privileged access to
the internal SHA-1 block transform function. This will make it
easier to revamp the hash code in general, and also it means that
hardware-accelerated versions of SHA-256 will automatically be used
for the PRNG as well as for everything else.
- The new PRNG can be _tested_! Because it has an actual (if not
quite explicit) specification for exactly what the output numbers
_ought_ to be derived from the hashes of, I can (and have) put
tests in cryptsuite that ensure the output really is being derived
in the way I think it is. The old pool could have been returning
any old nonsense and it would have been very hard to tell for sure.
2019-01-23 01:42:41 +03:00
|
|
|
random_reseed(make_ptrlen(entropy, bits / 8));
|
2019-09-08 22:29:00 +03:00
|
|
|
smemclr(entropy, bits/8);
|
|
|
|
sfree(entropy);
|
|
|
|
|
|
|
|
if (keytype == DSA) {
|
|
|
|
struct dss_key *dsskey = snew(struct dss_key);
|
|
|
|
dsa_generate(dsskey, bits, progressfn, &prog);
|
|
|
|
ssh2key = snew(ssh2_userkey);
|
|
|
|
ssh2key->key = &dsskey->sshk;
|
|
|
|
ssh1key = NULL;
|
2014-11-01 12:45:20 +03:00
|
|
|
} else if (keytype == ECDSA) {
|
Complete rewrite of PuTTY's bignum library.
The old 'Bignum' data type is gone completely, and so is sshbn.c. In
its place is a new thing called 'mp_int', handled by an entirely new
library module mpint.c, with API differences both large and small.
The main aim of this change is that the new library should be free of
timing- and cache-related side channels. I've written the code so that
it _should_ - assuming I haven't made any mistakes - do all of its
work without either control flow or memory addressing depending on the
data words of the input numbers. (Though, being an _arbitrary_
precision library, it does have to at least depend on the sizes of the
numbers - but there's a 'formal' size that can vary separately from
the actual magnitude of the represented integer, so if you want to
keep it secret that your number is actually small, it should work fine
to have a very long mp_int and just happen to store 23 in it.) So I've
done all my conditionalisation by means of computing both answers and
doing bit-masking to swap the right one into place, and all loops over
the words of an mp_int go up to the formal size rather than the actual
size.
I haven't actually tested the constant-time property in any rigorous
way yet (I'm still considering the best way to do it). But this code
is surely at the very least a big improvement on the old version, even
if I later find a few more things to fix.
I've also completely rewritten the low-level elliptic curve arithmetic
from sshecc.c; the new ecc.c is closer to being an adjunct of mpint.c
than it is to the SSH end of the code. The new elliptic curve code
keeps all coordinates in Montgomery-multiplication transformed form to
speed up all the multiplications mod the same prime, and only converts
them back when you ask for the affine coordinates. Also, I adopted
extended coordinates for the Edwards curve implementation.
sshecc.c has also had a near-total rewrite in the course of switching
it over to the new system. While I was there, I've separated ECDSA and
EdDSA more completely - they now have separate vtables, instead of a
single vtable in which nearly every function had a big if statement in
it - and also made the externally exposed types for an ECDSA key and
an ECDH context different.
A minor new feature: since the new arithmetic code includes a modular
square root function, we can now support the compressed point
representation for the NIST curves. We seem to have been getting along
fine without that so far, but it seemed a shame not to put it in,
since it was suddenly easy.
In sshrsa.c, one major change is that I've removed the RSA blinding
step in rsa_privkey_op, in which we randomise the ciphertext before
doing the decryption. The purpose of that was to avoid timing leaks
giving away the plaintext - but the new arithmetic code should take
that in its stride in the course of also being careful enough to avoid
leaking the _private key_, which RSA blinding had no way to do
anything about in any case.
Apart from those specific points, most of the rest of the changes are
more or less mechanical, just changing type names and translating code
into the new API.
2018-12-31 16:53:41 +03:00
|
|
|
struct ecdsa_key *ek = snew(struct ecdsa_key);
|
|
|
|
ecdsa_generate(ek, bits, progressfn, &prog);
|
2019-01-04 09:51:44 +03:00
|
|
|
ssh2key = snew(ssh2_userkey);
|
Complete rewrite of PuTTY's bignum library.
The old 'Bignum' data type is gone completely, and so is sshbn.c. In
its place is a new thing called 'mp_int', handled by an entirely new
library module mpint.c, with API differences both large and small.
The main aim of this change is that the new library should be free of
timing- and cache-related side channels. I've written the code so that
it _should_ - assuming I haven't made any mistakes - do all of its
work without either control flow or memory addressing depending on the
data words of the input numbers. (Though, being an _arbitrary_
precision library, it does have to at least depend on the sizes of the
numbers - but there's a 'formal' size that can vary separately from
the actual magnitude of the represented integer, so if you want to
keep it secret that your number is actually small, it should work fine
to have a very long mp_int and just happen to store 23 in it.) So I've
done all my conditionalisation by means of computing both answers and
doing bit-masking to swap the right one into place, and all loops over
the words of an mp_int go up to the formal size rather than the actual
size.
I haven't actually tested the constant-time property in any rigorous
way yet (I'm still considering the best way to do it). But this code
is surely at the very least a big improvement on the old version, even
if I later find a few more things to fix.
I've also completely rewritten the low-level elliptic curve arithmetic
from sshecc.c; the new ecc.c is closer to being an adjunct of mpint.c
than it is to the SSH end of the code. The new elliptic curve code
keeps all coordinates in Montgomery-multiplication transformed form to
speed up all the multiplications mod the same prime, and only converts
them back when you ask for the affine coordinates. Also, I adopted
extended coordinates for the Edwards curve implementation.
sshecc.c has also had a near-total rewrite in the course of switching
it over to the new system. While I was there, I've separated ECDSA and
EdDSA more completely - they now have separate vtables, instead of a
single vtable in which nearly every function had a big if statement in
it - and also made the externally exposed types for an ECDSA key and
an ECDH context different.
A minor new feature: since the new arithmetic code includes a modular
square root function, we can now support the compressed point
representation for the NIST curves. We seem to have been getting along
fine without that so far, but it seemed a shame not to put it in,
since it was suddenly easy.
In sshrsa.c, one major change is that I've removed the RSA blinding
step in rsa_privkey_op, in which we randomise the ciphertext before
doing the decryption. The purpose of that was to avoid timing leaks
giving away the plaintext - but the new arithmetic code should take
that in its stride in the course of also being careful enough to avoid
leaking the _private key_, which RSA blinding had no way to do
anything about in any case.
Apart from those specific points, most of the rest of the changes are
more or less mechanical, just changing type names and translating code
into the new API.
2018-12-31 16:53:41 +03:00
|
|
|
ssh2key->key = &ek->sshk;
|
2014-11-01 12:45:20 +03:00
|
|
|
ssh1key = NULL;
|
2015-05-09 17:02:54 +03:00
|
|
|
} else if (keytype == ED25519) {
|
Complete rewrite of PuTTY's bignum library.
The old 'Bignum' data type is gone completely, and so is sshbn.c. In
its place is a new thing called 'mp_int', handled by an entirely new
library module mpint.c, with API differences both large and small.
The main aim of this change is that the new library should be free of
timing- and cache-related side channels. I've written the code so that
it _should_ - assuming I haven't made any mistakes - do all of its
work without either control flow or memory addressing depending on the
data words of the input numbers. (Though, being an _arbitrary_
precision library, it does have to at least depend on the sizes of the
numbers - but there's a 'formal' size that can vary separately from
the actual magnitude of the represented integer, so if you want to
keep it secret that your number is actually small, it should work fine
to have a very long mp_int and just happen to store 23 in it.) So I've
done all my conditionalisation by means of computing both answers and
doing bit-masking to swap the right one into place, and all loops over
the words of an mp_int go up to the formal size rather than the actual
size.
I haven't actually tested the constant-time property in any rigorous
way yet (I'm still considering the best way to do it). But this code
is surely at the very least a big improvement on the old version, even
if I later find a few more things to fix.
I've also completely rewritten the low-level elliptic curve arithmetic
from sshecc.c; the new ecc.c is closer to being an adjunct of mpint.c
than it is to the SSH end of the code. The new elliptic curve code
keeps all coordinates in Montgomery-multiplication transformed form to
speed up all the multiplications mod the same prime, and only converts
them back when you ask for the affine coordinates. Also, I adopted
extended coordinates for the Edwards curve implementation.
sshecc.c has also had a near-total rewrite in the course of switching
it over to the new system. While I was there, I've separated ECDSA and
EdDSA more completely - they now have separate vtables, instead of a
single vtable in which nearly every function had a big if statement in
it - and also made the externally exposed types for an ECDSA key and
an ECDH context different.
A minor new feature: since the new arithmetic code includes a modular
square root function, we can now support the compressed point
representation for the NIST curves. We seem to have been getting along
fine without that so far, but it seemed a shame not to put it in,
since it was suddenly easy.
In sshrsa.c, one major change is that I've removed the RSA blinding
step in rsa_privkey_op, in which we randomise the ciphertext before
doing the decryption. The purpose of that was to avoid timing leaks
giving away the plaintext - but the new arithmetic code should take
that in its stride in the course of also being careful enough to avoid
leaking the _private key_, which RSA blinding had no way to do
anything about in any case.
Apart from those specific points, most of the rest of the changes are
more or less mechanical, just changing type names and translating code
into the new API.
2018-12-31 16:53:41 +03:00
|
|
|
struct eddsa_key *ek = snew(struct eddsa_key);
|
|
|
|
eddsa_generate(ek, bits, progressfn, &prog);
|
2019-01-04 09:51:44 +03:00
|
|
|
ssh2key = snew(ssh2_userkey);
|
Complete rewrite of PuTTY's bignum library.
The old 'Bignum' data type is gone completely, and so is sshbn.c. In
its place is a new thing called 'mp_int', handled by an entirely new
library module mpint.c, with API differences both large and small.
The main aim of this change is that the new library should be free of
timing- and cache-related side channels. I've written the code so that
it _should_ - assuming I haven't made any mistakes - do all of its
work without either control flow or memory addressing depending on the
data words of the input numbers. (Though, being an _arbitrary_
precision library, it does have to at least depend on the sizes of the
numbers - but there's a 'formal' size that can vary separately from
the actual magnitude of the represented integer, so if you want to
keep it secret that your number is actually small, it should work fine
to have a very long mp_int and just happen to store 23 in it.) So I've
done all my conditionalisation by means of computing both answers and
doing bit-masking to swap the right one into place, and all loops over
the words of an mp_int go up to the formal size rather than the actual
size.
I haven't actually tested the constant-time property in any rigorous
way yet (I'm still considering the best way to do it). But this code
is surely at the very least a big improvement on the old version, even
if I later find a few more things to fix.
I've also completely rewritten the low-level elliptic curve arithmetic
from sshecc.c; the new ecc.c is closer to being an adjunct of mpint.c
than it is to the SSH end of the code. The new elliptic curve code
keeps all coordinates in Montgomery-multiplication transformed form to
speed up all the multiplications mod the same prime, and only converts
them back when you ask for the affine coordinates. Also, I adopted
extended coordinates for the Edwards curve implementation.
sshecc.c has also had a near-total rewrite in the course of switching
it over to the new system. While I was there, I've separated ECDSA and
EdDSA more completely - they now have separate vtables, instead of a
single vtable in which nearly every function had a big if statement in
it - and also made the externally exposed types for an ECDSA key and
an ECDH context different.
A minor new feature: since the new arithmetic code includes a modular
square root function, we can now support the compressed point
representation for the NIST curves. We seem to have been getting along
fine without that so far, but it seemed a shame not to put it in,
since it was suddenly easy.
In sshrsa.c, one major change is that I've removed the RSA blinding
step in rsa_privkey_op, in which we randomise the ciphertext before
doing the decryption. The purpose of that was to avoid timing leaks
giving away the plaintext - but the new arithmetic code should take
that in its stride in the course of also being careful enough to avoid
leaking the _private key_, which RSA blinding had no way to do
anything about in any case.
Apart from those specific points, most of the rest of the changes are
more or less mechanical, just changing type names and translating code
into the new API.
2018-12-31 16:53:41 +03:00
|
|
|
ssh2key->key = &ek->sshk;
|
2015-05-09 17:02:54 +03:00
|
|
|
ssh1key = NULL;
|
2019-09-08 22:29:00 +03:00
|
|
|
} else {
|
|
|
|
RSAKey *rsakey = snew(RSAKey);
|
|
|
|
rsa_generate(rsakey, bits, progressfn, &prog);
|
|
|
|
rsakey->comment = NULL;
|
|
|
|
if (keytype == RSA1) {
|
|
|
|
ssh1key = rsakey;
|
|
|
|
} else {
|
|
|
|
ssh2key = snew(ssh2_userkey);
|
|
|
|
ssh2key->key = &rsakey->sshk;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
progressfn(&prog, PROGFN_PROGRESS, INT_MAX, -1);
|
|
|
|
|
|
|
|
if (ssh2key)
|
|
|
|
ssh2key->comment = dupstr(default_comment);
|
|
|
|
if (ssh1key)
|
|
|
|
ssh1key->comment = dupstr(default_comment);
|
2004-01-22 22:15:32 +03:00
|
|
|
|
|
|
|
} else {
|
2019-09-08 22:29:00 +03:00
|
|
|
const char *error = NULL;
|
|
|
|
bool encrypted;
|
|
|
|
|
|
|
|
assert(infile != NULL);
|
|
|
|
|
|
|
|
sfree(origcomment);
|
|
|
|
origcomment = NULL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find out whether the input key is encrypted.
|
|
|
|
*/
|
|
|
|
if (intype == SSH_KEYTYPE_SSH1)
|
2020-01-05 13:28:45 +03:00
|
|
|
encrypted = rsa1_encrypted_f(infilename, &origcomment);
|
2019-09-08 22:29:00 +03:00
|
|
|
else if (intype == SSH_KEYTYPE_SSH2)
|
2020-01-05 13:28:45 +03:00
|
|
|
encrypted = ppk_encrypted_f(infilename, &origcomment);
|
2019-09-08 22:29:00 +03:00
|
|
|
else
|
|
|
|
encrypted = import_encrypted(infilename, intype, &origcomment);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If so, ask for a passphrase.
|
|
|
|
*/
|
|
|
|
if (encrypted && load_encrypted) {
|
|
|
|
if (!old_passphrase) {
|
|
|
|
prompts_t *p = new_prompts();
|
|
|
|
int ret;
|
|
|
|
p->to_server = false;
|
|
|
|
p->from_server = false;
|
|
|
|
p->name = dupstr("SSH key passphrase");
|
|
|
|
add_prompt(p, dupstr("Enter passphrase to load key: "), false);
|
|
|
|
ret = console_get_userpass_input(p);
|
|
|
|
assert(ret >= 0);
|
|
|
|
if (!ret) {
|
|
|
|
free_prompts(p);
|
|
|
|
perror("puttygen: unable to read passphrase");
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1);
|
2019-09-08 22:29:00 +03:00
|
|
|
} else {
|
2020-01-21 23:19:47 +03:00
|
|
|
old_passphrase = prompt_get_result(p->prompts[0]);
|
2019-09-08 22:29:00 +03:00
|
|
|
free_prompts(p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
old_passphrase = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (intype) {
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
case SSH_KEYTYPE_SSH1:
|
2015-05-12 14:19:57 +03:00
|
|
|
case SSH_KEYTYPE_SSH1_PUBLIC:
|
2019-09-08 22:29:00 +03:00
|
|
|
ssh1key = snew(RSAKey);
|
2020-01-14 01:14:02 +03:00
|
|
|
memset(ssh1key, 0, sizeof(RSAKey));
|
2019-09-08 22:29:00 +03:00
|
|
|
if (!load_encrypted) {
|
|
|
|
strbuf *blob;
|
2018-05-29 22:36:21 +03:00
|
|
|
BinarySource src[1];
|
2004-01-22 22:15:32 +03:00
|
|
|
|
2019-07-06 19:44:15 +03:00
|
|
|
sfree(origcomment);
|
|
|
|
origcomment = NULL;
|
|
|
|
|
2018-05-24 12:59:39 +03:00
|
|
|
blob = strbuf_new();
|
2019-07-06 19:44:15 +03:00
|
|
|
|
2020-01-05 13:28:45 +03:00
|
|
|
ret = rsa1_loadpub_f(infilename, BinarySink_UPCAST(blob),
|
|
|
|
&origcomment, &error);
|
2018-05-29 22:36:21 +03:00
|
|
|
BinarySource_BARE_INIT(src, blob->u, blob->len);
|
2018-06-03 10:23:07 +03:00
|
|
|
get_rsa_ssh1_pub(src, ssh1key, RSA_SSH1_EXPONENT_FIRST);
|
2018-05-24 12:59:39 +03:00
|
|
|
strbuf_free(blob);
|
|
|
|
|
2019-09-08 22:29:00 +03:00
|
|
|
ssh1key->comment = dupstr(origcomment);
|
|
|
|
ssh1key->private_exponent = NULL;
|
|
|
|
ssh1key->p = NULL;
|
|
|
|
ssh1key->q = NULL;
|
|
|
|
ssh1key->iqmp = NULL;
|
|
|
|
} else {
|
2020-01-05 13:28:45 +03:00
|
|
|
ret = rsa1_load_f(infilename, ssh1key, old_passphrase, &error);
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
|
|
|
if (ret > 0)
|
|
|
|
error = NULL;
|
|
|
|
else if (!error)
|
|
|
|
error = "unknown error";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SSH_KEYTYPE_SSH2:
|
2015-05-12 14:19:57 +03:00
|
|
|
case SSH_KEYTYPE_SSH2_PUBLIC_RFC4716:
|
|
|
|
case SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH:
|
2019-09-08 22:29:00 +03:00
|
|
|
if (!load_encrypted) {
|
2019-07-06 19:44:15 +03:00
|
|
|
sfree(origcomment);
|
|
|
|
origcomment = NULL;
|
2018-05-24 12:59:39 +03:00
|
|
|
ssh2blob = strbuf_new();
|
2020-01-05 13:28:45 +03:00
|
|
|
if (ppk_loadpub_f(infilename, &ssh2alg,
|
|
|
|
BinarySink_UPCAST(ssh2blob),
|
|
|
|
&origcomment, &error)) {
|
2018-06-03 14:58:05 +03:00
|
|
|
const ssh_keyalg *alg = find_pubkey_alg(ssh2alg);
|
|
|
|
if (alg)
|
|
|
|
bits = ssh_key_public_bits(
|
2018-10-13 18:30:59 +03:00
|
|
|
alg, ptrlen_from_strbuf(ssh2blob));
|
2013-02-23 01:39:02 +04:00
|
|
|
else
|
|
|
|
bits = -1;
|
2018-05-24 12:59:39 +03:00
|
|
|
} else {
|
|
|
|
strbuf_free(ssh2blob);
|
2019-01-29 23:12:37 +03:00
|
|
|
ssh2blob = NULL;
|
2013-02-23 01:39:02 +04:00
|
|
|
}
|
2015-05-15 12:12:06 +03:00
|
|
|
sfree(ssh2alg);
|
2019-09-08 22:29:00 +03:00
|
|
|
} else {
|
2020-01-05 13:28:45 +03:00
|
|
|
ssh2key = ppk_load_f(infilename, old_passphrase, &error);
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
|
|
|
if ((ssh2key && ssh2key != SSH2_WRONG_PASSPHRASE) || ssh2blob)
|
|
|
|
error = NULL;
|
|
|
|
else if (!error) {
|
|
|
|
if (ssh2key == SSH2_WRONG_PASSPHRASE)
|
|
|
|
error = "wrong passphrase";
|
|
|
|
else
|
|
|
|
error = "unknown error";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SSH_KEYTYPE_OPENSSH_PEM:
|
|
|
|
case SSH_KEYTYPE_OPENSSH_NEW:
|
|
|
|
case SSH_KEYTYPE_SSHCOM:
|
|
|
|
ssh2key = import_ssh2(infilename, intype, old_passphrase, &error);
|
|
|
|
if (ssh2key) {
|
|
|
|
if (ssh2key != SSH2_WRONG_PASSPHRASE)
|
|
|
|
error = NULL;
|
|
|
|
else
|
|
|
|
error = "wrong passphrase";
|
|
|
|
} else if (!error)
|
|
|
|
error = "unknown error";
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
unreachable("bad input key type");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
fprintf(stderr, "puttygen: error loading `%s': %s\n",
|
|
|
|
infile, error);
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1);
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Change the comment if asked to.
|
|
|
|
*/
|
|
|
|
if (comment) {
|
2019-09-08 22:29:00 +03:00
|
|
|
if (sshver == 1) {
|
|
|
|
assert(ssh1key);
|
|
|
|
sfree(ssh1key->comment);
|
|
|
|
ssh1key->comment = dupstr(comment);
|
|
|
|
} else {
|
|
|
|
assert(ssh2key);
|
|
|
|
sfree(ssh2key->comment);
|
|
|
|
ssh2key->comment = dupstr(comment);
|
|
|
|
}
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
|
|
|
|
Add command-line passphrase-file options to command-line PuTTYgen.
Patch due to Colin Watson.
Putting the passphrase in a file avoids exposing it to 'ps' which can
print out every process's command line, while at the same time not
being as platform-specific as the approach of providing an fd number
(since cmdgen.c is in principle a potential cross-platform PuTTYgen,
not just a Unix one, which is why it's not in the 'unix' directory).
Of course it introduces its own risks if someone can read the file
from your disk after you delete it; probably the best approach to
avoiding this, if possible, is to point the option at a file on an
in-memory tmpfs type file system. Or better still, use bash-style
/dev/fd options such as
puttygen --new-passphrase <(echo -n "my passphrase") [options]
Failing that, try a secure file-wipe utility, as the man page change
mentions.
(And a use case not to be overlooked, of course, is the one where you
actually want to generate an unprotected key - in which case, just
pass /dev/null as the filename.)
2016-03-17 21:42:46 +03:00
|
|
|
/*
|
|
|
|
* Unless we're changing the passphrase, the old one (if any) is a
|
|
|
|
* reasonable default.
|
|
|
|
*/
|
|
|
|
if (!change_passphrase && old_passphrase && !new_passphrase)
|
2019-09-08 22:29:00 +03:00
|
|
|
new_passphrase = dupstr(old_passphrase);
|
Add command-line passphrase-file options to command-line PuTTYgen.
Patch due to Colin Watson.
Putting the passphrase in a file avoids exposing it to 'ps' which can
print out every process's command line, while at the same time not
being as platform-specific as the approach of providing an fd number
(since cmdgen.c is in principle a potential cross-platform PuTTYgen,
not just a Unix one, which is why it's not in the 'unix' directory).
Of course it introduces its own risks if someone can read the file
from your disk after you delete it; probably the best approach to
avoiding this, if possible, is to point the option at a file on an
in-memory tmpfs type file system. Or better still, use bash-style
/dev/fd options such as
puttygen --new-passphrase <(echo -n "my passphrase") [options]
Failing that, try a secure file-wipe utility, as the man page change
mentions.
(And a use case not to be overlooked, of course, is the one where you
actually want to generate an unprotected key - in which case, just
pass /dev/null as the filename.)
2016-03-17 21:42:46 +03:00
|
|
|
|
2004-01-22 22:15:32 +03:00
|
|
|
/*
|
|
|
|
* Prompt for a new passphrase if we have been asked to, or if
|
|
|
|
* we have just generated a key.
|
|
|
|
*/
|
Add command-line passphrase-file options to command-line PuTTYgen.
Patch due to Colin Watson.
Putting the passphrase in a file avoids exposing it to 'ps' which can
print out every process's command line, while at the same time not
being as platform-specific as the approach of providing an fd number
(since cmdgen.c is in principle a potential cross-platform PuTTYgen,
not just a Unix one, which is why it's not in the 'unix' directory).
Of course it introduces its own risks if someone can read the file
from your disk after you delete it; probably the best approach to
avoiding this, if possible, is to point the option at a file on an
in-memory tmpfs type file system. Or better still, use bash-style
/dev/fd options such as
puttygen --new-passphrase <(echo -n "my passphrase") [options]
Failing that, try a secure file-wipe utility, as the man page change
mentions.
(And a use case not to be overlooked, of course, is the one where you
actually want to generate an unprotected key - in which case, just
pass /dev/null as the filename.)
2016-03-17 21:42:46 +03:00
|
|
|
if (!new_passphrase && (change_passphrase || keytype != NOKEYGEN)) {
|
2019-09-08 22:29:00 +03:00
|
|
|
prompts_t *p = new_prompts(NULL);
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
p->to_server = false;
|
|
|
|
p->from_server = false;
|
|
|
|
p->name = dupstr("New SSH key passphrase");
|
|
|
|
add_prompt(p, dupstr("Enter passphrase to save key: "), false);
|
|
|
|
add_prompt(p, dupstr("Re-enter passphrase to verify: "), false);
|
|
|
|
ret = console_get_userpass_input(p);
|
|
|
|
assert(ret >= 0);
|
|
|
|
if (!ret) {
|
|
|
|
free_prompts(p);
|
|
|
|
perror("puttygen: unable to read new passphrase");
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1);
|
2019-09-08 22:29:00 +03:00
|
|
|
} else {
|
2020-01-21 23:19:47 +03:00
|
|
|
if (strcmp(prompt_get_result_ref(p->prompts[0]),
|
|
|
|
prompt_get_result_ref(p->prompts[1]))) {
|
2019-09-08 22:29:00 +03:00
|
|
|
free_prompts(p);
|
|
|
|
fprintf(stderr, "puttygen: passphrases do not match\n");
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1);
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
2020-01-21 23:19:47 +03:00
|
|
|
new_passphrase = prompt_get_result(p->prompts[0]);
|
2019-09-08 22:29:00 +03:00
|
|
|
free_prompts(p);
|
|
|
|
}
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
Add command-line passphrase-file options to command-line PuTTYgen.
Patch due to Colin Watson.
Putting the passphrase in a file avoids exposing it to 'ps' which can
print out every process's command line, while at the same time not
being as platform-specific as the approach of providing an fd number
(since cmdgen.c is in principle a potential cross-platform PuTTYgen,
not just a Unix one, which is why it's not in the 'unix' directory).
Of course it introduces its own risks if someone can read the file
from your disk after you delete it; probably the best approach to
avoiding this, if possible, is to point the option at a file on an
in-memory tmpfs type file system. Or better still, use bash-style
/dev/fd options such as
puttygen --new-passphrase <(echo -n "my passphrase") [options]
Failing that, try a secure file-wipe utility, as the man page change
mentions.
(And a use case not to be overlooked, of course, is the one where you
actually want to generate an unprotected key - in which case, just
pass /dev/null as the filename.)
2016-03-17 21:42:46 +03:00
|
|
|
if (new_passphrase && !*new_passphrase) {
|
2019-09-08 22:29:00 +03:00
|
|
|
sfree(new_passphrase);
|
|
|
|
new_passphrase = NULL;
|
Add command-line passphrase-file options to command-line PuTTYgen.
Patch due to Colin Watson.
Putting the passphrase in a file avoids exposing it to 'ps' which can
print out every process's command line, while at the same time not
being as platform-specific as the approach of providing an fd number
(since cmdgen.c is in principle a potential cross-platform PuTTYgen,
not just a Unix one, which is why it's not in the 'unix' directory).
Of course it introduces its own risks if someone can read the file
from your disk after you delete it; probably the best approach to
avoiding this, if possible, is to point the option at a file on an
in-memory tmpfs type file system. Or better still, use bash-style
/dev/fd options such as
puttygen --new-passphrase <(echo -n "my passphrase") [options]
Failing that, try a secure file-wipe utility, as the man page change
mentions.
(And a use case not to be overlooked, of course, is the one where you
actually want to generate an unprotected key - in which case, just
pass /dev/null as the filename.)
2016-03-17 21:42:46 +03:00
|
|
|
}
|
2004-01-22 22:15:32 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Write output.
|
2019-09-08 22:29:00 +03:00
|
|
|
*
|
2004-01-22 22:15:32 +03:00
|
|
|
* (In the case where outfile and outfiletmp are both NULL,
|
|
|
|
* there is no semantic reason to initialise outfilename at
|
|
|
|
* all; but we have to write _something_ to it or some compiler
|
|
|
|
* will probably complain that it might be used uninitialised.)
|
|
|
|
*/
|
|
|
|
if (outfiletmp)
|
2019-09-08 22:29:00 +03:00
|
|
|
outfilename = filename_from_str(outfiletmp);
|
2004-01-22 22:15:32 +03:00
|
|
|
else
|
2019-09-08 22:29:00 +03:00
|
|
|
outfilename = filename_from_str(outfile ? outfile : "");
|
2004-01-22 22:15:32 +03:00
|
|
|
|
|
|
|
switch (outtype) {
|
Convert a lot of 'int' variables to 'bool'.
My normal habit these days, in new code, is to treat int and bool as
_almost_ completely separate types. I'm still willing to use C's
implicit test for zero on an integer (e.g. 'if (!blob.len)' is fine,
no need to spell it out as blob.len != 0), but generally, if a
variable is going to be conceptually a boolean, I like to declare it
bool and assign to it using 'true' or 'false' rather than 0 or 1.
PuTTY is an exception, because it predates the C99 bool, and I've
stuck to its existing coding style even when adding new code to it.
But it's been annoying me more and more, so now that I've decided C99
bool is an acceptable thing to require from our toolchain in the first
place, here's a quite thorough trawl through the source doing
'boolification'. Many variables and function parameters are now typed
as bool rather than int; many assignments of 0 or 1 to those variables
are now spelled 'true' or 'false'.
I managed this thorough conversion with the help of a custom clang
plugin that I wrote to trawl the AST and apply heuristics to point out
where things might want changing. So I've even managed to do a decent
job on parts of the code I haven't looked at in years!
To make the plugin's work easier, I pushed platform front ends
generally in the direction of using standard 'bool' in preference to
platform-specific boolean types like Windows BOOL or GTK's gboolean;
I've left the platform booleans in places they _have_ to be for the
platform APIs to work right, but variables only used by my own code
have been converted wherever I found them.
In a few places there are int values that look very like booleans in
_most_ of the places they're used, but have a rarely-used third value,
or a distinction between different nonzero values that most users
don't care about. In these cases, I've _removed_ uses of 'true' and
'false' for the return values, to emphasise that there's something
more subtle going on than a simple boolean answer:
- the 'multisel' field in dialog.h's list box structure, for which
the GTK front end in particular recognises a difference between 1
and 2 but nearly everything else treats as boolean
- the 'urgent' parameter to plug_receive, where 1 vs 2 tells you
something about the specific location of the urgent pointer, but
most clients only care about 0 vs 'something nonzero'
- the return value of wc_match, where -1 indicates a syntax error in
the wildcard.
- the return values from SSH-1 RSA-key loading functions, which use
-1 for 'wrong passphrase' and 0 for all other failures (so any
caller which already knows it's not loading an _encrypted private_
key can treat them as boolean)
- term->esc_query, and the 'query' parameter in toggle_mode in
terminal.c, which _usually_ hold 0 for ESC[123h or 1 for ESC[?123h,
but can also hold -1 for some other intervening character that we
don't support.
In a few places there's an integer that I haven't turned into a bool
even though it really _can_ only take values 0 or 1 (and, as above,
tried to make the call sites consistent in not calling those values
true and false), on the grounds that I thought it would make it more
confusing to imply that the 0 value was in some sense 'negative' or
bad and the 1 positive or good:
- the return value of plug_accepting uses the POSIXish convention of
0=success and nonzero=error; I think if I made it bool then I'd
also want to reverse its sense, and that's a job for a separate
piece of work.
- the 'screen' parameter to lineptr() in terminal.c, where 0 and 1
represent the default and alternate screens. There's no obvious
reason why one of those should be considered 'true' or 'positive'
or 'success' - they're just indices - so I've left it as int.
ssh_scp_recv had particularly confusing semantics for its previous int
return value: its call sites used '<= 0' to check for error, but it
never actually returned a negative number, just 0 or 1. Now the
function and its call sites agree that it's a bool.
In a couple of places I've renamed variables called 'ret', because I
don't like that name any more - it's unclear whether it means the
return value (in preparation) for the _containing_ function or the
return value received from a subroutine call, and occasionally I've
accidentally used the same variable for both and introduced a bug. So
where one of those got in my way, I've renamed it to 'toret' or 'retd'
(the latter short for 'returned') in line with my usual modern
practice, but I haven't done a thorough job of finding all of them.
Finally, one amusing side effect of doing this is that I've had to
separate quite a few chained assignments. It used to be perfectly fine
to write 'a = b = c = TRUE' when a,b,c were int and TRUE was just a
the 'true' defined by stdbool.h, that idiom provokes a warning from
gcc: 'suggest parentheses around assignment used as truth value'!
2018-11-02 22:23:19 +03:00
|
|
|
bool ret;
|
|
|
|
int real_outtype;
|
2004-01-22 22:15:32 +03:00
|
|
|
|
|
|
|
case PRIVATE:
|
2019-03-24 17:05:35 +03:00
|
|
|
random_ref(); /* we'll need a few random bytes in the save file */
|
2019-09-08 22:29:00 +03:00
|
|
|
if (sshver == 1) {
|
|
|
|
assert(ssh1key);
|
2020-01-05 13:28:45 +03:00
|
|
|
ret = rsa1_save_f(outfilename, ssh1key, new_passphrase);
|
2019-09-08 22:29:00 +03:00
|
|
|
if (!ret) {
|
|
|
|
fprintf(stderr, "puttygen: unable to save SSH-1 private key\n");
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1);
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
assert(ssh2key);
|
2020-01-05 13:28:45 +03:00
|
|
|
ret = ppk_save_f(outfilename, ssh2key, new_passphrase);
|
2019-09-08 22:29:00 +03:00
|
|
|
if (!ret) {
|
|
|
|
fprintf(stderr, "puttygen: unable to save SSH-2 private key\n");
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1);
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (outfiletmp) {
|
|
|
|
if (!move(outfiletmp, outfile))
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1); /* rename failed */
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
|
|
|
break;
|
2004-01-22 22:15:32 +03:00
|
|
|
|
|
|
|
case PUBLIC:
|
|
|
|
case PUBLICO:
|
2015-05-12 15:42:26 +03:00
|
|
|
{
|
|
|
|
FILE *fp;
|
2004-01-22 22:15:32 +03:00
|
|
|
|
2018-10-16 00:26:20 +03:00
|
|
|
if (outfile) {
|
2018-10-29 22:50:29 +03:00
|
|
|
fp = f_open(outfilename, "w", false);
|
2018-10-16 00:26:20 +03:00
|
|
|
if (!fp) {
|
|
|
|
fprintf(stderr, "unable to open output file\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
} else {
|
2015-05-12 15:42:26 +03:00
|
|
|
fp = stdout;
|
2018-10-16 00:26:20 +03:00
|
|
|
}
|
2004-01-22 22:15:32 +03:00
|
|
|
|
2015-05-12 15:42:26 +03:00
|
|
|
if (sshver == 1) {
|
|
|
|
ssh1_write_pubkey(fp, ssh1key);
|
|
|
|
} else {
|
|
|
|
if (!ssh2blob) {
|
|
|
|
assert(ssh2key);
|
2018-05-24 12:59:39 +03:00
|
|
|
ssh2blob = strbuf_new();
|
2018-06-03 14:58:05 +03:00
|
|
|
ssh_key_public_blob(ssh2key->key, BinarySink_UPCAST(ssh2blob));
|
2015-05-12 15:42:26 +03:00
|
|
|
}
|
2004-01-22 22:15:32 +03:00
|
|
|
|
2015-05-12 15:42:26 +03:00
|
|
|
ssh2_write_pubkey(fp, ssh2key ? ssh2key->comment : origcomment,
|
2018-05-24 12:59:39 +03:00
|
|
|
ssh2blob->s, ssh2blob->len,
|
2015-05-12 15:42:26 +03:00
|
|
|
(outtype == PUBLIC ?
|
|
|
|
SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 :
|
|
|
|
SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH));
|
|
|
|
}
|
2004-01-22 22:15:32 +03:00
|
|
|
|
2019-09-08 22:29:00 +03:00
|
|
|
if (outfile)
|
|
|
|
fclose(fp);
|
2015-05-12 15:42:26 +03:00
|
|
|
}
|
2019-09-08 22:29:00 +03:00
|
|
|
break;
|
2004-01-22 22:15:32 +03:00
|
|
|
|
|
|
|
case FP:
|
2019-09-08 22:29:00 +03:00
|
|
|
{
|
|
|
|
FILE *fp;
|
|
|
|
char *fingerprint;
|
|
|
|
|
|
|
|
if (sshver == 1) {
|
|
|
|
assert(ssh1key);
|
|
|
|
fingerprint = rsa_ssh1_fingerprint(ssh1key);
|
|
|
|
} else {
|
|
|
|
if (ssh2key) {
|
|
|
|
fingerprint = ssh2_fingerprint(ssh2key->key);
|
|
|
|
} else {
|
|
|
|
assert(ssh2blob);
|
|
|
|
fingerprint = ssh2_fingerprint_blob(
|
2019-02-06 23:48:03 +03:00
|
|
|
ptrlen_from_strbuf(ssh2blob));
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
|
|
|
}
|
2004-01-22 22:15:32 +03:00
|
|
|
|
2019-09-08 22:29:00 +03:00
|
|
|
if (outfile) {
|
|
|
|
fp = f_open(outfilename, "w", false);
|
2018-10-16 00:26:20 +03:00
|
|
|
if (!fp) {
|
|
|
|
fprintf(stderr, "unable to open output file\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
} else {
|
2019-09-08 22:29:00 +03:00
|
|
|
fp = stdout;
|
2018-10-16 00:26:20 +03:00
|
|
|
}
|
2019-09-08 22:29:00 +03:00
|
|
|
fprintf(fp, "%s\n", fingerprint);
|
|
|
|
if (outfile)
|
|
|
|
fclose(fp);
|
|
|
|
|
|
|
|
sfree(fingerprint);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2015-05-10 09:42:48 +03:00
|
|
|
case OPENSSH_AUTO:
|
2015-04-28 21:46:58 +03:00
|
|
|
case OPENSSH_NEW:
|
2004-01-22 22:15:32 +03:00
|
|
|
case SSHCOM:
|
2019-09-08 22:29:00 +03:00
|
|
|
assert(sshver == 2);
|
|
|
|
assert(ssh2key);
|
|
|
|
random_ref(); /* both foreign key types require randomness,
|
2014-01-16 23:16:19 +04:00
|
|
|
* for IV or padding */
|
2015-04-28 21:46:08 +03:00
|
|
|
switch (outtype) {
|
2015-05-10 09:42:48 +03:00
|
|
|
case OPENSSH_AUTO:
|
|
|
|
real_outtype = SSH_KEYTYPE_OPENSSH_AUTO;
|
2015-04-28 21:46:58 +03:00
|
|
|
break;
|
|
|
|
case OPENSSH_NEW:
|
|
|
|
real_outtype = SSH_KEYTYPE_OPENSSH_NEW;
|
2015-04-28 21:46:08 +03:00
|
|
|
break;
|
|
|
|
case SSHCOM:
|
|
|
|
real_outtype = SSH_KEYTYPE_SSHCOM;
|
|
|
|
break;
|
2015-04-28 21:46:58 +03:00
|
|
|
default:
|
2019-01-03 11:12:19 +03:00
|
|
|
unreachable("control flow goof");
|
2015-04-28 21:46:08 +03:00
|
|
|
}
|
2019-09-08 22:29:00 +03:00
|
|
|
ret = export_ssh2(outfilename, real_outtype, ssh2key, new_passphrase);
|
|
|
|
if (!ret) {
|
|
|
|
fprintf(stderr, "puttygen: unable to export key\n");
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1);
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
|
|
|
if (outfiletmp) {
|
|
|
|
if (!move(outfiletmp, outfile))
|
2020-01-14 01:14:02 +03:00
|
|
|
RETURN(1); /* rename failed */
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
|
|
|
break;
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
|
|
|
|
2020-01-14 01:14:02 +03:00
|
|
|
out:
|
|
|
|
|
|
|
|
#undef RETURN
|
|
|
|
|
Add command-line passphrase-file options to command-line PuTTYgen.
Patch due to Colin Watson.
Putting the passphrase in a file avoids exposing it to 'ps' which can
print out every process's command line, while at the same time not
being as platform-specific as the approach of providing an fd number
(since cmdgen.c is in principle a potential cross-platform PuTTYgen,
not just a Unix one, which is why it's not in the 'unix' directory).
Of course it introduces its own risks if someone can read the file
from your disk after you delete it; probably the best approach to
avoiding this, if possible, is to point the option at a file on an
in-memory tmpfs type file system. Or better still, use bash-style
/dev/fd options such as
puttygen --new-passphrase <(echo -n "my passphrase") [options]
Failing that, try a secure file-wipe utility, as the man page change
mentions.
(And a use case not to be overlooked, of course, is the one where you
actually want to generate an unprotected key - in which case, just
pass /dev/null as the filename.)
2016-03-17 21:42:46 +03:00
|
|
|
if (old_passphrase) {
|
2019-09-08 22:29:00 +03:00
|
|
|
smemclr(old_passphrase, strlen(old_passphrase));
|
|
|
|
sfree(old_passphrase);
|
Add command-line passphrase-file options to command-line PuTTYgen.
Patch due to Colin Watson.
Putting the passphrase in a file avoids exposing it to 'ps' which can
print out every process's command line, while at the same time not
being as platform-specific as the approach of providing an fd number
(since cmdgen.c is in principle a potential cross-platform PuTTYgen,
not just a Unix one, which is why it's not in the 'unix' directory).
Of course it introduces its own risks if someone can read the file
from your disk after you delete it; probably the best approach to
avoiding this, if possible, is to point the option at a file on an
in-memory tmpfs type file system. Or better still, use bash-style
/dev/fd options such as
puttygen --new-passphrase <(echo -n "my passphrase") [options]
Failing that, try a secure file-wipe utility, as the man page change
mentions.
(And a use case not to be overlooked, of course, is the one where you
actually want to generate an unprotected key - in which case, just
pass /dev/null as the filename.)
2016-03-17 21:42:46 +03:00
|
|
|
}
|
|
|
|
if (new_passphrase) {
|
2019-09-08 22:29:00 +03:00
|
|
|
smemclr(new_passphrase, strlen(new_passphrase));
|
|
|
|
sfree(new_passphrase);
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
|
|
|
|
2018-12-30 17:14:59 +03:00
|
|
|
if (ssh1key) {
|
2019-09-08 22:29:00 +03:00
|
|
|
freersakey(ssh1key);
|
2019-06-28 21:22:39 +03:00
|
|
|
sfree(ssh1key);
|
2018-12-30 17:14:59 +03:00
|
|
|
}
|
2020-01-14 01:14:02 +03:00
|
|
|
if (ssh2key && ssh2key != SSH2_WRONG_PASSPHRASE) {
|
2019-09-08 22:29:00 +03:00
|
|
|
sfree(ssh2key->comment);
|
2020-01-14 01:14:02 +03:00
|
|
|
if (ssh2key->key)
|
|
|
|
ssh_key_free(ssh2key->key);
|
2019-09-08 22:29:00 +03:00
|
|
|
sfree(ssh2key);
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
2019-07-06 19:44:15 +03:00
|
|
|
if (ssh2blob)
|
|
|
|
strbuf_free(ssh2blob);
|
2019-02-28 09:19:31 +03:00
|
|
|
sfree(origcomment);
|
|
|
|
if (infilename)
|
|
|
|
filename_free(infilename);
|
2020-01-14 01:14:02 +03:00
|
|
|
if (outfilename)
|
|
|
|
filename_free(outfilename);
|
|
|
|
sfree(outfiletmp);
|
2004-01-22 22:15:32 +03:00
|
|
|
|
2020-01-14 01:14:02 +03:00
|
|
|
return exit_status;
|
2004-01-22 22:15:32 +03:00
|
|
|
}
|
2004-01-24 20:16:37 +03:00
|
|
|
|
|
|
|
#ifdef TEST_CMDGEN
|
|
|
|
|
|
|
|
#undef main
|
|
|
|
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
|
|
|
int passes, fails;
|
|
|
|
|
|
|
|
void setup_passphrases(char *first, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
char *next;
|
|
|
|
|
|
|
|
nprompts = 0;
|
|
|
|
if (first) {
|
2019-09-08 22:29:00 +03:00
|
|
|
prompts[nprompts++] = first;
|
|
|
|
va_start(ap, first);
|
|
|
|
while ((next = va_arg(ap, char *)) != NULL) {
|
|
|
|
assert(nprompts < lenof(prompts));
|
|
|
|
prompts[nprompts++] = next;
|
|
|
|
}
|
|
|
|
va_end(ap);
|
2004-01-24 20:16:37 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void test(int retval, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
int i, argc, ret;
|
|
|
|
char **argv;
|
|
|
|
|
|
|
|
argc = 0;
|
|
|
|
va_start(ap, retval);
|
|
|
|
while (va_arg(ap, char *) != NULL)
|
2019-09-08 22:29:00 +03:00
|
|
|
argc++;
|
2004-01-24 20:16:37 +03:00
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
argv = snewn(argc+1, char *);
|
|
|
|
va_start(ap, retval);
|
|
|
|
for (i = 0; i <= argc; i++)
|
2019-09-08 22:29:00 +03:00
|
|
|
argv[i] = va_arg(ap, char *);
|
2004-01-24 20:16:37 +03:00
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
promptsgot = 0;
|
2019-03-24 16:48:38 +03:00
|
|
|
if (cgtest_verbose) {
|
|
|
|
printf("run:");
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
|
|
static const char okchars[] =
|
|
|
|
"0123456789abcdefghijklmnopqrstuvwxyz"
|
|
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ%+,-./:=[]^_";
|
|
|
|
const char *arg = argv[i];
|
|
|
|
|
|
|
|
printf(" ");
|
|
|
|
if (arg[strspn(arg, okchars)]) {
|
|
|
|
printf("'");
|
|
|
|
for (const char *c = argv[i]; *c; c++) {
|
|
|
|
if (*c == '\'') {
|
|
|
|
printf("'\\''");
|
|
|
|
} else {
|
|
|
|
putchar(*c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
printf("'");
|
|
|
|
} else {
|
|
|
|
fputs(arg, stdout);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
}
|
2004-01-24 20:16:37 +03:00
|
|
|
ret = cmdgen_main(argc, argv);
|
2019-03-24 17:08:11 +03:00
|
|
|
random_clear();
|
2004-01-24 20:16:37 +03:00
|
|
|
|
|
|
|
if (ret != retval) {
|
2019-09-08 22:29:00 +03:00
|
|
|
printf("FAILED retval (exp %d got %d):", retval, ret);
|
|
|
|
for (i = 0; i < argc; i++)
|
|
|
|
printf(" %s", argv[i]);
|
|
|
|
printf("\n");
|
|
|
|
fails++;
|
2004-01-24 20:16:37 +03:00
|
|
|
} else if (promptsgot != nprompts) {
|
2019-09-08 22:29:00 +03:00
|
|
|
printf("FAILED nprompts (exp %d got %d):", nprompts, promptsgot);
|
|
|
|
for (i = 0; i < argc; i++)
|
|
|
|
printf(" %s", argv[i]);
|
|
|
|
printf("\n");
|
|
|
|
fails++;
|
2004-01-24 20:16:37 +03:00
|
|
|
} else {
|
2019-09-08 22:29:00 +03:00
|
|
|
passes++;
|
2004-01-24 20:16:37 +03:00
|
|
|
}
|
2017-02-14 23:42:26 +03:00
|
|
|
|
|
|
|
sfree(argv);
|
2004-01-24 20:16:37 +03:00
|
|
|
}
|
|
|
|
|
2020-01-26 17:49:31 +03:00
|
|
|
PRINTF_LIKE(3, 4) void filecmp(char *file1, char *file2, char *fmt, ...)
|
2004-01-24 20:16:37 +03:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Ideally I should do file comparison myself, to maximise the
|
|
|
|
* portability of this test suite once this application begins
|
|
|
|
* running on non-Unix platforms. For the moment, though,
|
|
|
|
* calling Unix diff is perfectly adequate.
|
|
|
|
*/
|
|
|
|
char *buf;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
buf = dupprintf("diff -q '%s' '%s'", file1, file2);
|
|
|
|
ret = system(buf);
|
|
|
|
sfree(buf);
|
|
|
|
|
|
|
|
if (ret) {
|
2019-09-08 22:29:00 +03:00
|
|
|
va_list ap;
|
2004-01-24 20:16:37 +03:00
|
|
|
|
2019-09-08 22:29:00 +03:00
|
|
|
printf("FAILED diff (ret=%d): ", ret);
|
2004-01-24 20:16:37 +03:00
|
|
|
|
2019-09-08 22:29:00 +03:00
|
|
|
va_start(ap, fmt);
|
|
|
|
vprintf(fmt, ap);
|
|
|
|
va_end(ap);
|
2004-01-24 20:16:37 +03:00
|
|
|
|
2019-09-08 22:29:00 +03:00
|
|
|
printf("\n");
|
2004-01-24 20:16:37 +03:00
|
|
|
|
2019-09-08 22:29:00 +03:00
|
|
|
fails++;
|
2004-01-24 20:16:37 +03:00
|
|
|
} else
|
2019-09-08 22:29:00 +03:00
|
|
|
passes++;
|
2004-01-24 20:16:37 +03:00
|
|
|
}
|
|
|
|
|
2020-01-14 01:32:03 +03:00
|
|
|
/*
|
|
|
|
* General-purpose flags word
|
|
|
|
*/
|
|
|
|
#define CGT_FLAGS(X) \
|
|
|
|
X(CGT_TYPE_KNOWN_EARLY) \
|
|
|
|
X(CGT_OPENSSH) \
|
|
|
|
X(CGT_SSHCOM) \
|
|
|
|
X(CGT_SSH_KEYGEN) \
|
|
|
|
X(CGT_ED25519) \
|
|
|
|
/* end of list */
|
|
|
|
|
|
|
|
#define FLAG_SHIFTS(name) name ## _shift,
|
|
|
|
enum { CGT_FLAGS(FLAG_SHIFTS) CGT_dummy_shift };
|
|
|
|
#define FLAG_VALUES(name) name = 1 << name ## _shift,
|
|
|
|
enum { CGT_FLAGS(FLAG_VALUES) CGT_dummy_flag };
|
|
|
|
|
|
|
|
char *cleanup_fp(char *s, unsigned flags)
|
2004-01-24 20:16:37 +03:00
|
|
|
{
|
2019-03-24 17:06:17 +03:00
|
|
|
ptrlen pl = ptrlen_from_asciz(s);
|
|
|
|
static const char separators[] = " \n\t";
|
2004-01-24 20:16:37 +03:00
|
|
|
|
2019-03-24 17:06:17 +03:00
|
|
|
/* Skip initial key type word if we find one */
|
2020-01-14 01:32:03 +03:00
|
|
|
if (ptrlen_startswith(pl, PTRLEN_LITERAL("ssh-"), NULL) ||
|
|
|
|
ptrlen_startswith(pl, PTRLEN_LITERAL("ecdsa-"), NULL))
|
2019-03-24 17:06:17 +03:00
|
|
|
ptrlen_get_word(&pl, separators);
|
|
|
|
|
|
|
|
/* Expect two words giving the key length and the hash */
|
|
|
|
ptrlen bits = ptrlen_get_word(&pl, separators);
|
|
|
|
ptrlen hash = ptrlen_get_word(&pl, separators);
|
2004-01-24 20:16:37 +03:00
|
|
|
|
2020-01-14 01:32:03 +03:00
|
|
|
if (flags & CGT_SSH_KEYGEN) {
|
|
|
|
/* Strip "MD5:" prefix if it's present, and do nothing if it isn't */
|
|
|
|
ptrlen_startswith(hash, PTRLEN_LITERAL("MD5:"), &hash);
|
|
|
|
|
|
|
|
if (flags & CGT_ED25519) {
|
|
|
|
/* OpenSSH ssh-keygen lists ed25519 keys as 256 bits, not 255 */
|
|
|
|
if (ptrlen_eq_string(bits, "256"))
|
|
|
|
bits = PTRLEN_LITERAL("255");
|
|
|
|
}
|
|
|
|
}
|
2004-01-24 20:16:37 +03:00
|
|
|
|
2019-03-24 17:06:17 +03:00
|
|
|
return dupprintf("%.*s %.*s", PTRLEN_PRINTF(bits), PTRLEN_PRINTF(hash));
|
2004-01-24 20:16:37 +03:00
|
|
|
}
|
|
|
|
|
2020-01-14 01:32:03 +03:00
|
|
|
char *get_line(char *filename)
|
2004-01-24 20:16:37 +03:00
|
|
|
{
|
|
|
|
FILE *fp;
|
2020-01-14 01:32:03 +03:00
|
|
|
char *line;
|
2004-01-24 20:16:37 +03:00
|
|
|
|
|
|
|
fp = fopen(filename, "r");
|
|
|
|
if (!fp)
|
2019-09-08 22:29:00 +03:00
|
|
|
return NULL;
|
2020-01-14 01:32:03 +03:00
|
|
|
line = fgetline(fp);
|
2004-01-24 20:16:37 +03:00
|
|
|
fclose(fp);
|
2020-01-14 01:32:03 +03:00
|
|
|
return line;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *get_fp(char *filename, unsigned flags)
|
|
|
|
{
|
|
|
|
char *orig = get_line(filename);
|
|
|
|
if (!orig)
|
2019-09-08 22:29:00 +03:00
|
|
|
return NULL;
|
2020-01-14 01:32:03 +03:00
|
|
|
char *toret = cleanup_fp(orig, flags);
|
|
|
|
sfree(orig);
|
|
|
|
return toret;
|
2004-01-24 20:16:37 +03:00
|
|
|
}
|
|
|
|
|
2020-01-26 17:49:31 +03:00
|
|
|
PRINTF_LIKE(3, 4) void check_fp(char *filename, char *fp, char *fmt, ...)
|
2004-01-24 20:16:37 +03:00
|
|
|
{
|
|
|
|
char *newfp;
|
|
|
|
|
|
|
|
if (!fp)
|
2019-09-08 22:29:00 +03:00
|
|
|
return;
|
2004-01-24 20:16:37 +03:00
|
|
|
|
2020-01-14 01:32:03 +03:00
|
|
|
newfp = get_fp(filename, 0);
|
2004-01-24 20:16:37 +03:00
|
|
|
|
|
|
|
if (!strcmp(fp, newfp)) {
|
2019-09-08 22:29:00 +03:00
|
|
|
passes++;
|
2004-01-24 20:16:37 +03:00
|
|
|
} else {
|
2019-09-08 22:29:00 +03:00
|
|
|
va_list ap;
|
2004-01-24 20:16:37 +03:00
|
|
|
|
2019-09-08 22:29:00 +03:00
|
|
|
printf("FAILED check_fp ['%s' != '%s']: ", newfp, fp);
|
2004-01-24 20:16:37 +03:00
|
|
|
|
2019-09-08 22:29:00 +03:00
|
|
|
va_start(ap, fmt);
|
|
|
|
vprintf(fmt, ap);
|
|
|
|
va_end(ap);
|
2004-01-24 20:16:37 +03:00
|
|
|
|
2019-09-08 22:29:00 +03:00
|
|
|
printf("\n");
|
2004-01-24 20:16:37 +03:00
|
|
|
|
2019-09-08 22:29:00 +03:00
|
|
|
fails++;
|
2004-01-24 20:16:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
sfree(newfp);
|
|
|
|
}
|
|
|
|
|
2020-01-14 01:32:03 +03:00
|
|
|
static const struct cgtest_keytype {
|
|
|
|
const char *name;
|
|
|
|
unsigned flags;
|
|
|
|
} cgtest_keytypes[] = {
|
|
|
|
{ "rsa1", CGT_TYPE_KNOWN_EARLY },
|
|
|
|
{ "dsa", CGT_OPENSSH | CGT_SSHCOM },
|
|
|
|
{ "rsa", CGT_OPENSSH | CGT_SSHCOM },
|
|
|
|
{ "ecdsa", CGT_OPENSSH },
|
|
|
|
{ "ed25519", CGT_OPENSSH | CGT_ED25519 },
|
|
|
|
};
|
|
|
|
|
2004-01-24 20:16:37 +03:00
|
|
|
int main(int argc, char **argv)
|
|
|
|
{
|
|
|
|
int i;
|
2020-01-14 09:42:26 +03:00
|
|
|
int active[lenof(cgtest_keytypes)], active_value;
|
2020-01-14 22:53:06 +03:00
|
|
|
bool remove_files = true;
|
2020-01-14 09:42:26 +03:00
|
|
|
|
|
|
|
active_value = 0;
|
|
|
|
for (i = 0; i < lenof(cgtest_keytypes); i++)
|
|
|
|
active[i] = active_value;
|
2004-01-24 20:16:37 +03:00
|
|
|
|
2020-01-14 01:04:32 +03:00
|
|
|
while (--argc > 0) {
|
|
|
|
ptrlen arg = ptrlen_from_asciz(*++argv);
|
|
|
|
if (ptrlen_eq_string(arg, "-v") ||
|
|
|
|
ptrlen_eq_string(arg, "--verbose")) {
|
|
|
|
cgtest_verbose = true;
|
2020-01-14 22:53:06 +03:00
|
|
|
} else if (ptrlen_eq_string(arg, "--keep")) {
|
|
|
|
remove_files = false;
|
2020-01-14 09:42:26 +03:00
|
|
|
} else if (ptrlen_eq_string(arg, "--help")) {
|
|
|
|
printf("usage: cgtest [options] [key types]\n");
|
|
|
|
printf("options: -v, --verbose "
|
|
|
|
"print more output during tests\n");
|
2020-01-14 22:53:06 +03:00
|
|
|
printf(" --keep "
|
|
|
|
"do not delete the temporary output files\n");
|
2020-01-14 09:42:26 +03:00
|
|
|
printf(" --help "
|
|
|
|
"display this help text\n");
|
|
|
|
printf("key types: ");
|
|
|
|
for (i = 0; i < lenof(cgtest_keytypes); i++)
|
|
|
|
printf("%s%s", i ? ", " : "", cgtest_keytypes[i].name);
|
|
|
|
printf("\n");
|
|
|
|
return 0;
|
|
|
|
} else if (!ptrlen_startswith(arg, PTRLEN_LITERAL("-"), NULL)) {
|
|
|
|
for (i = 0; i < lenof(cgtest_keytypes); i++)
|
|
|
|
if (ptrlen_eq_string(arg, cgtest_keytypes[i].name))
|
|
|
|
break;
|
|
|
|
if (i == lenof(cgtest_keytypes)) {
|
|
|
|
fprintf(stderr, "cgtest: unrecognised key type '%.*s'\n",
|
|
|
|
PTRLEN_PRINTF(arg));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
active_value = 1; /* disables all keys not explicitly enabled */
|
|
|
|
active[i] = active_value;
|
2020-01-14 01:04:32 +03:00
|
|
|
} else {
|
|
|
|
fprintf(stderr, "cgtest: unrecognised option '%.*s'\n",
|
|
|
|
PTRLEN_PRINTF(arg));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
2004-01-24 20:16:37 +03:00
|
|
|
|
|
|
|
passes = fails = 0;
|
|
|
|
|
2020-01-14 01:32:03 +03:00
|
|
|
for (i = 0; i < lenof(cgtest_keytypes); i++) {
|
2020-01-14 09:42:26 +03:00
|
|
|
if (active[i] != active_value)
|
|
|
|
continue;
|
|
|
|
|
2020-01-14 01:32:03 +03:00
|
|
|
const struct cgtest_keytype *keytype = &cgtest_keytypes[i];
|
|
|
|
bool supports_openssh = keytype->flags & CGT_OPENSSH;
|
|
|
|
bool supports_sshcom = keytype->flags & CGT_SSHCOM;
|
|
|
|
bool type_known_early = keytype->flags & CGT_TYPE_KNOWN_EARLY;
|
|
|
|
|
2019-09-08 22:29:00 +03:00
|
|
|
char filename[128], osfilename[128], scfilename[128];
|
|
|
|
char pubfilename[128], tmpfilename1[128], tmpfilename2[128];
|
2020-01-14 01:14:02 +03:00
|
|
|
char *fp = NULL;
|
2019-09-08 22:29:00 +03:00
|
|
|
|
2020-01-14 01:32:03 +03:00
|
|
|
sprintf(filename, "test-%s.ppk", keytype->name);
|
|
|
|
sprintf(pubfilename, "test-%s.pub", keytype->name);
|
|
|
|
sprintf(osfilename, "test-%s.os", keytype->name);
|
|
|
|
sprintf(scfilename, "test-%s.sc", keytype->name);
|
|
|
|
sprintf(tmpfilename1, "test-%s.tmp1", keytype->name);
|
|
|
|
sprintf(tmpfilename2, "test-%s.tmp2", keytype->name);
|
2019-09-08 22:29:00 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Create an encrypted key.
|
|
|
|
*/
|
|
|
|
setup_passphrases("sponge", "sponge", NULL);
|
2020-01-14 01:32:03 +03:00
|
|
|
test(0, "puttygen", "-t", keytype->name, "-o", filename, NULL);
|
2019-09-08 22:29:00 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* List the public key in OpenSSH format.
|
|
|
|
*/
|
|
|
|
setup_passphrases(NULL);
|
|
|
|
test(0, "puttygen", "-L", filename, "-o", pubfilename, NULL);
|
|
|
|
{
|
|
|
|
char *cmdbuf;
|
|
|
|
fp = NULL;
|
|
|
|
cmdbuf = dupprintf("ssh-keygen -E md5 -l -f '%s' > '%s'",
|
|
|
|
pubfilename, tmpfilename1);
|
2020-01-14 01:32:03 +03:00
|
|
|
if (cgtest_verbose)
|
|
|
|
printf("OpenSSH fp check: %s\n", cmdbuf);
|
2019-09-08 22:29:00 +03:00
|
|
|
if (system(cmdbuf) ||
|
2020-01-14 01:32:03 +03:00
|
|
|
(fp = get_fp(tmpfilename1,
|
|
|
|
CGT_SSH_KEYGEN | keytype->flags)) == NULL) {
|
2019-09-08 22:29:00 +03:00
|
|
|
printf("UNABLE to test fingerprint matching against OpenSSH");
|
|
|
|
}
|
|
|
|
sfree(cmdbuf);
|
2020-01-14 01:32:03 +03:00
|
|
|
if (fp && cgtest_verbose) {
|
|
|
|
char *line = get_line(tmpfilename1);
|
|
|
|
printf("OpenSSH fp: %s\n", line);
|
|
|
|
printf("Cleaned up: %s\n", fp);
|
|
|
|
sfree(line);
|
|
|
|
}
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* List the public key in IETF/ssh.com format.
|
|
|
|
*/
|
|
|
|
setup_passphrases(NULL);
|
|
|
|
test(0, "puttygen", "-p", filename, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* List the fingerprint of the key.
|
|
|
|
*/
|
|
|
|
setup_passphrases(NULL);
|
|
|
|
test(0, "puttygen", "-l", filename, "-o", tmpfilename1, NULL);
|
|
|
|
if (!fp) {
|
|
|
|
/*
|
|
|
|
* If we can't test fingerprints against OpenSSH, we
|
|
|
|
* can at the very least test equality of all the
|
|
|
|
* fingerprints we generate of this key throughout
|
|
|
|
* testing.
|
|
|
|
*/
|
2020-01-14 01:32:03 +03:00
|
|
|
fp = get_fp(tmpfilename1, 0);
|
2019-09-08 22:29:00 +03:00
|
|
|
} else {
|
2020-01-14 01:32:03 +03:00
|
|
|
check_fp(tmpfilename1, fp, "%s initial fp", keytype->name);
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Change the comment of the key; this _does_ require a
|
|
|
|
* passphrase owing to the tamperproofing.
|
|
|
|
*
|
|
|
|
* NOTE: In SSH-1, this only requires a passphrase because
|
|
|
|
* of inadequacies of the loading and saving mechanisms. In
|
|
|
|
* _principle_, it should be perfectly possible to modify
|
|
|
|
* the comment on an SSH-1 key without requiring a
|
|
|
|
* passphrase; the only reason I can't do it is because my
|
|
|
|
* loading and saving mechanisms don't include a method of
|
|
|
|
* loading all the key data without also trying to decrypt
|
|
|
|
* the private section.
|
|
|
|
*
|
|
|
|
* I don't consider this to be a problem worth solving,
|
|
|
|
* because (a) to fix it would probably end up bloating
|
|
|
|
* PuTTY proper, and (b) SSH-1 is on the way out anyway so
|
|
|
|
* it shouldn't be highly significant. If it seriously
|
|
|
|
* bothers anyone then perhaps I _might_ be persuadable.
|
|
|
|
*/
|
|
|
|
setup_passphrases("sponge", NULL);
|
|
|
|
test(0, "puttygen", "-C", "new-comment", filename, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Change the passphrase to nothing.
|
|
|
|
*/
|
|
|
|
setup_passphrases("sponge", "", "", NULL);
|
|
|
|
test(0, "puttygen", "-P", filename, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Change the comment of the key again; this time we expect no
|
|
|
|
* passphrase to be required.
|
|
|
|
*/
|
|
|
|
setup_passphrases(NULL);
|
|
|
|
test(0, "puttygen", "-C", "new-comment-2", filename, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Export the private key into OpenSSH format; no passphrase
|
|
|
|
* should be required since the key is currently unencrypted.
|
|
|
|
*/
|
|
|
|
setup_passphrases(NULL);
|
2020-01-14 01:32:03 +03:00
|
|
|
test(supports_openssh ? 0 : 1,
|
|
|
|
"puttygen", "-O", "private-openssh", "-o", osfilename,
|
2019-09-08 22:29:00 +03:00
|
|
|
filename, NULL);
|
|
|
|
|
2020-01-14 01:32:03 +03:00
|
|
|
if (supports_openssh) {
|
2019-09-08 22:29:00 +03:00
|
|
|
/*
|
|
|
|
* List the fingerprint of the OpenSSH-formatted key.
|
|
|
|
*/
|
|
|
|
setup_passphrases(NULL);
|
|
|
|
test(0, "puttygen", "-l", osfilename, "-o", tmpfilename1, NULL);
|
2020-01-14 01:32:03 +03:00
|
|
|
check_fp(tmpfilename1, fp, "%s openssh clear fp", keytype->name);
|
2019-09-08 22:29:00 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* List the public half of the OpenSSH-formatted key in
|
|
|
|
* OpenSSH format.
|
|
|
|
*/
|
|
|
|
setup_passphrases(NULL);
|
|
|
|
test(0, "puttygen", "-L", osfilename, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* List the public half of the OpenSSH-formatted key in
|
|
|
|
* IETF/ssh.com format.
|
|
|
|
*/
|
|
|
|
setup_passphrases(NULL);
|
|
|
|
test(0, "puttygen", "-p", osfilename, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Export the private key into ssh.com format; no passphrase
|
|
|
|
* should be required since the key is currently unencrypted.
|
|
|
|
*/
|
|
|
|
setup_passphrases(NULL);
|
2020-01-14 01:32:03 +03:00
|
|
|
test(supports_sshcom ? 0 : 1,
|
|
|
|
"puttygen", "-O", "private-sshcom",
|
|
|
|
"-o", scfilename, filename, NULL);
|
2019-09-08 22:29:00 +03:00
|
|
|
|
2020-01-14 01:32:03 +03:00
|
|
|
if (supports_sshcom) {
|
2019-09-08 22:29:00 +03:00
|
|
|
/*
|
|
|
|
* List the fingerprint of the ssh.com-formatted key.
|
|
|
|
*/
|
|
|
|
setup_passphrases(NULL);
|
|
|
|
test(0, "puttygen", "-l", scfilename, "-o", tmpfilename1, NULL);
|
2020-01-14 01:32:03 +03:00
|
|
|
check_fp(tmpfilename1, fp, "%s ssh.com clear fp", keytype->name);
|
2019-09-08 22:29:00 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* List the public half of the ssh.com-formatted key in
|
|
|
|
* OpenSSH format.
|
|
|
|
*/
|
|
|
|
setup_passphrases(NULL);
|
|
|
|
test(0, "puttygen", "-L", scfilename, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* List the public half of the ssh.com-formatted key in
|
|
|
|
* IETF/ssh.com format.
|
|
|
|
*/
|
|
|
|
setup_passphrases(NULL);
|
|
|
|
test(0, "puttygen", "-p", scfilename, NULL);
|
|
|
|
}
|
|
|
|
|
2020-01-14 01:32:03 +03:00
|
|
|
if (supports_openssh && supports_sshcom) {
|
2019-09-08 22:29:00 +03:00
|
|
|
/*
|
|
|
|
* Convert from OpenSSH into ssh.com.
|
|
|
|
*/
|
|
|
|
setup_passphrases(NULL);
|
|
|
|
test(0, "puttygen", osfilename, "-o", tmpfilename1,
|
|
|
|
"-O", "private-sshcom", NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert from ssh.com back into a PuTTY key,
|
|
|
|
* supplying the same comment as we had before we
|
|
|
|
* started to ensure the comparison works.
|
|
|
|
*/
|
|
|
|
setup_passphrases(NULL);
|
|
|
|
test(0, "puttygen", tmpfilename1, "-C", "new-comment-2",
|
|
|
|
"-o", tmpfilename2, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* See if the PuTTY key thus generated is the same as
|
|
|
|
* the original.
|
|
|
|
*/
|
|
|
|
filecmp(filename, tmpfilename2,
|
2020-01-14 01:32:03 +03:00
|
|
|
"p->o->s->p clear %s", keytype->name);
|
2019-09-08 22:29:00 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert from ssh.com to OpenSSH.
|
|
|
|
*/
|
|
|
|
setup_passphrases(NULL);
|
|
|
|
test(0, "puttygen", scfilename, "-o", tmpfilename1,
|
|
|
|
"-O", "private-openssh", NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert from OpenSSH back into a PuTTY key,
|
|
|
|
* supplying the same comment as we had before we
|
|
|
|
* started to ensure the comparison works.
|
|
|
|
*/
|
|
|
|
setup_passphrases(NULL);
|
|
|
|
test(0, "puttygen", tmpfilename1, "-C", "new-comment-2",
|
|
|
|
"-o", tmpfilename2, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* See if the PuTTY key thus generated is the same as
|
|
|
|
* the original.
|
|
|
|
*/
|
|
|
|
filecmp(filename, tmpfilename2,
|
2020-01-14 01:32:03 +03:00
|
|
|
"p->s->o->p clear %s", keytype->name);
|
2019-09-08 22:29:00 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Finally, do a round-trip conversion between PuTTY
|
|
|
|
* and ssh.com without involving OpenSSH, to test that
|
|
|
|
* the key comment is preserved in that case.
|
|
|
|
*/
|
|
|
|
setup_passphrases(NULL);
|
|
|
|
test(0, "puttygen", "-O", "private-sshcom", "-o", tmpfilename1,
|
|
|
|
filename, NULL);
|
|
|
|
setup_passphrases(NULL);
|
|
|
|
test(0, "puttygen", tmpfilename1, "-o", tmpfilename2, NULL);
|
|
|
|
filecmp(filename, tmpfilename2,
|
2020-01-14 01:32:03 +03:00
|
|
|
"p->s->p clear %s", keytype->name);
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check that mismatched passphrases cause an error.
|
|
|
|
*/
|
|
|
|
setup_passphrases("sponge2", "sponge3", NULL);
|
|
|
|
test(1, "puttygen", "-P", filename, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Put a passphrase back on.
|
|
|
|
*/
|
|
|
|
setup_passphrases("sponge2", "sponge2", NULL);
|
|
|
|
test(0, "puttygen", "-P", filename, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Export the private key into OpenSSH format, this time
|
2020-01-14 01:32:03 +03:00
|
|
|
* while encrypted.
|
2019-09-08 22:29:00 +03:00
|
|
|
*/
|
2020-01-14 01:32:03 +03:00
|
|
|
if (!supports_openssh && type_known_early) {
|
|
|
|
/* We'll know far enough in advance that this combination
|
|
|
|
* is going to fail that we never ask for the passphrase */
|
|
|
|
setup_passphrases(NULL);
|
|
|
|
} else {
|
2019-09-08 22:29:00 +03:00
|
|
|
setup_passphrases("sponge2", NULL);
|
2020-01-14 01:32:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
test(supports_openssh ? 0 : 1,
|
|
|
|
"puttygen", "-O", "private-openssh", "-o", osfilename,
|
2019-09-08 22:29:00 +03:00
|
|
|
filename, NULL);
|
|
|
|
|
2020-01-14 01:32:03 +03:00
|
|
|
if (supports_openssh) {
|
2019-09-08 22:29:00 +03:00
|
|
|
/*
|
|
|
|
* List the fingerprint of the OpenSSH-formatted key.
|
|
|
|
*/
|
|
|
|
setup_passphrases("sponge2", NULL);
|
|
|
|
test(0, "puttygen", "-l", osfilename, "-o", tmpfilename1, NULL);
|
2020-01-14 01:32:03 +03:00
|
|
|
check_fp(tmpfilename1, fp, "%s openssh encrypted fp",
|
|
|
|
keytype->name);
|
2019-09-08 22:29:00 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* List the public half of the OpenSSH-formatted key in
|
|
|
|
* OpenSSH format.
|
|
|
|
*/
|
|
|
|
setup_passphrases("sponge2", NULL);
|
|
|
|
test(0, "puttygen", "-L", osfilename, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* List the public half of the OpenSSH-formatted key in
|
|
|
|
* IETF/ssh.com format.
|
|
|
|
*/
|
|
|
|
setup_passphrases("sponge2", NULL);
|
|
|
|
test(0, "puttygen", "-p", osfilename, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Export the private key into ssh.com format, this time
|
|
|
|
* while encrypted. For RSA1 keys, this should give an
|
|
|
|
* error.
|
|
|
|
*/
|
2020-01-14 01:32:03 +03:00
|
|
|
if (!supports_sshcom && type_known_early) {
|
|
|
|
/* We'll know far enough in advance that this combination
|
|
|
|
* is going to fail that we never ask for the passphrase */
|
|
|
|
setup_passphrases(NULL);
|
|
|
|
} else {
|
2019-09-08 22:29:00 +03:00
|
|
|
setup_passphrases("sponge2", NULL);
|
2020-01-14 01:32:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
test(supports_sshcom ? 0 : 1,
|
|
|
|
"puttygen", "-O", "private-sshcom", "-o", scfilename,
|
2019-09-08 22:29:00 +03:00
|
|
|
filename, NULL);
|
|
|
|
|
2020-01-14 01:32:03 +03:00
|
|
|
if (supports_sshcom) {
|
2019-09-08 22:29:00 +03:00
|
|
|
/*
|
|
|
|
* List the fingerprint of the ssh.com-formatted key.
|
|
|
|
*/
|
|
|
|
setup_passphrases("sponge2", NULL);
|
|
|
|
test(0, "puttygen", "-l", scfilename, "-o", tmpfilename1, NULL);
|
2020-01-14 01:32:03 +03:00
|
|
|
check_fp(tmpfilename1, fp, "%s ssh.com encrypted fp",
|
|
|
|
keytype->name);
|
2019-09-08 22:29:00 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* List the public half of the ssh.com-formatted key in
|
|
|
|
* OpenSSH format.
|
|
|
|
*/
|
|
|
|
setup_passphrases("sponge2", NULL);
|
|
|
|
test(0, "puttygen", "-L", scfilename, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* List the public half of the ssh.com-formatted key in
|
|
|
|
* IETF/ssh.com format.
|
|
|
|
*/
|
|
|
|
setup_passphrases("sponge2", NULL);
|
|
|
|
test(0, "puttygen", "-p", scfilename, NULL);
|
|
|
|
}
|
|
|
|
|
2020-01-14 01:32:03 +03:00
|
|
|
if (supports_openssh && supports_sshcom) {
|
2019-09-08 22:29:00 +03:00
|
|
|
/*
|
|
|
|
* Convert from OpenSSH into ssh.com.
|
|
|
|
*/
|
|
|
|
setup_passphrases("sponge2", NULL);
|
|
|
|
test(0, "puttygen", osfilename, "-o", tmpfilename1,
|
|
|
|
"-O", "private-sshcom", NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert from ssh.com back into a PuTTY key,
|
|
|
|
* supplying the same comment as we had before we
|
|
|
|
* started to ensure the comparison works.
|
|
|
|
*/
|
|
|
|
setup_passphrases("sponge2", NULL);
|
|
|
|
test(0, "puttygen", tmpfilename1, "-C", "new-comment-2",
|
|
|
|
"-o", tmpfilename2, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* See if the PuTTY key thus generated is the same as
|
|
|
|
* the original.
|
|
|
|
*/
|
|
|
|
filecmp(filename, tmpfilename2,
|
2020-01-14 01:32:03 +03:00
|
|
|
"p->o->s->p encrypted %s", keytype->name);
|
2019-09-08 22:29:00 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert from ssh.com to OpenSSH.
|
|
|
|
*/
|
|
|
|
setup_passphrases("sponge2", NULL);
|
|
|
|
test(0, "puttygen", scfilename, "-o", tmpfilename1,
|
|
|
|
"-O", "private-openssh", NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert from OpenSSH back into a PuTTY key,
|
|
|
|
* supplying the same comment as we had before we
|
|
|
|
* started to ensure the comparison works.
|
|
|
|
*/
|
|
|
|
setup_passphrases("sponge2", NULL);
|
|
|
|
test(0, "puttygen", tmpfilename1, "-C", "new-comment-2",
|
|
|
|
"-o", tmpfilename2, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* See if the PuTTY key thus generated is the same as
|
|
|
|
* the original.
|
|
|
|
*/
|
|
|
|
filecmp(filename, tmpfilename2,
|
2020-01-14 01:32:03 +03:00
|
|
|
"p->s->o->p encrypted %s", keytype->name);
|
2019-09-08 22:29:00 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Finally, do a round-trip conversion between PuTTY
|
|
|
|
* and ssh.com without involving OpenSSH, to test that
|
|
|
|
* the key comment is preserved in that case.
|
|
|
|
*/
|
|
|
|
setup_passphrases("sponge2", NULL);
|
|
|
|
test(0, "puttygen", "-O", "private-sshcom", "-o", tmpfilename1,
|
|
|
|
filename, NULL);
|
|
|
|
setup_passphrases("sponge2", NULL);
|
|
|
|
test(0, "puttygen", tmpfilename1, "-o", tmpfilename2, NULL);
|
|
|
|
filecmp(filename, tmpfilename2,
|
2020-01-14 01:32:03 +03:00
|
|
|
"p->s->p encrypted %s", keytype->name);
|
2019-09-08 22:29:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Load with the wrong passphrase.
|
|
|
|
*/
|
|
|
|
setup_passphrases("sponge8", NULL);
|
|
|
|
test(1, "puttygen", "-C", "spurious-new-comment", filename, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Load a totally bogus file.
|
|
|
|
*/
|
|
|
|
setup_passphrases(NULL);
|
|
|
|
test(1, "puttygen", "-C", "spurious-new-comment", pubfilename, NULL);
|
2020-01-14 01:14:02 +03:00
|
|
|
|
|
|
|
sfree(fp);
|
2020-01-14 22:53:06 +03:00
|
|
|
|
|
|
|
if (remove_files) {
|
|
|
|
remove(filename);
|
|
|
|
remove(pubfilename);
|
|
|
|
remove(osfilename);
|
|
|
|
remove(scfilename);
|
|
|
|
remove(tmpfilename1);
|
|
|
|
remove(tmpfilename2);
|
|
|
|
}
|
2004-01-24 20:16:37 +03:00
|
|
|
}
|
|
|
|
printf("%d passes, %d fails\n", passes, fails);
|
2019-12-16 12:19:30 +03:00
|
|
|
return fails == 0 ? 0 : 1;
|
2004-01-24 20:16:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|