Newer
Older
ssh2_write_pubkey(fp, ssh2key ? ssh2key->comment : origcomment,
ssh2blob->s, ssh2blob->len,
(outtype == PUBLIC ?
SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 :
SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH));
}
if (outfile)
fclose(fp);
case FP:
{
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(
if (outfile) {
fp = f_open(outfilename, "w", false);
if (!fp) {
fprintf(stderr, "unable to open output file\n");
exit(1);
}
} else {
fprintf(fp, "%s\n", fingerprint);
if (outfile)
fclose(fp);
sfree(fingerprint);
}
break;
case OPENSSH_NEW:
case SSHCOM:
assert(sshver == 2);
assert(ssh2key);
random_ref(); /* both foreign key types require randomness,
case OPENSSH_AUTO:
real_outtype = SSH_KEYTYPE_OPENSSH_AUTO;
break;
case OPENSSH_NEW:
real_outtype = SSH_KEYTYPE_OPENSSH_NEW;
break;
case SSHCOM:
real_outtype = SSH_KEYTYPE_SSHCOM;
break;
unreachable("control flow goof");
ret = export_ssh2(outfilename, real_outtype, ssh2key, new_passphrase);
if (!ret) {
fprintf(stderr, "puttygen: unable to export key\n");
}
if (outfiletmp) {
if (!move(outfiletmp, outfile))
}
if (old_passphrase) {
smemclr(old_passphrase, strlen(old_passphrase));
sfree(old_passphrase);
}
if (new_passphrase) {
smemclr(new_passphrase, strlen(new_passphrase));
sfree(new_passphrase);
}
if (ssh2key && ssh2key != SSH2_WRONG_PASSPHRASE) {
sfree(ssh2key->comment);
if (ssh2key->key)
ssh_key_free(ssh2key->key);
sfree(origcomment);
if (infilename)
filename_free(infilename);
if (outfilename)
filename_free(outfilename);
sfree(outfiletmp);
#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) {
prompts[nprompts++] = first;
va_start(ap, first);
while ((next = va_arg(ap, char *)) != NULL) {
assert(nprompts < lenof(prompts));
prompts[nprompts++] = next;
}
va_end(ap);
}
}
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)
va_end(ap);
argv = snewn(argc+1, char *);
va_start(ap, retval);
for (i = 0; i <= argc; i++)
argv[i] = va_arg(ap, char *);
va_end(ap);
promptsgot = 0;
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
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");
}
ret = cmdgen_main(argc, argv);
random_clear();
if (ret != retval) {
printf("FAILED retval (exp %d got %d):", retval, ret);
for (i = 0; i < argc; i++)
printf(" %s", argv[i]);
printf("\n");
fails++;
} else if (promptsgot != nprompts) {
printf("FAILED nprompts (exp %d got %d):", nprompts, promptsgot);
for (i = 0; i < argc; i++)
printf(" %s", argv[i]);
printf("\n");
fails++;
PRINTF_LIKE(3, 4) void filecmp(char *file1, char *file2, char *fmt, ...)
{
/*
* 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) {
printf("FAILED diff (ret=%d): ", ret);
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
/*
* 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)
ptrlen pl = ptrlen_from_asciz(s);
static const char separators[] = " \n\t";
/* Skip initial key type word if we find one */
if (ptrlen_startswith(pl, PTRLEN_LITERAL("ssh-"), NULL) ||
ptrlen_startswith(pl, PTRLEN_LITERAL("ecdsa-"), NULL))
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);
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");
}
}
return dupprintf("%.*s %.*s", PTRLEN_PRINTF(bits), PTRLEN_PRINTF(hash));
char *get_line(char *filename)
{
FILE *fp;
fp = fopen(filename, "r");
if (!fp)
fclose(fp);
return line;
}
char *get_fp(char *filename, unsigned flags)
{
char *orig = get_line(filename);
if (!orig)
char *toret = cleanup_fp(orig, flags);
sfree(orig);
return toret;
PRINTF_LIKE(3, 4) void check_fp(char *filename, char *fp, char *fmt, ...)
{
char *newfp;
if (!fp)
if (!strcmp(fp, newfp)) {
printf("FAILED check_fp ['%s' != '%s']: ", newfp, fp);
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
}
sfree(newfp);
}
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 },
};
int main(int argc, char **argv)
{
int i;
int active[lenof(cgtest_keytypes)], active_value;
bool remove_files = true;
active_value = 0;
for (i = 0; i < lenof(cgtest_keytypes); i++)
active[i] = active_value;
while (--argc > 0) {
ptrlen arg = ptrlen_from_asciz(*++argv);
if (ptrlen_eq_string(arg, "-v") ||
ptrlen_eq_string(arg, "--verbose")) {
cgtest_verbose = true;
} else if (ptrlen_eq_string(arg, "--keep")) {
remove_files = false;
} else if (ptrlen_eq_string(arg, "--help")) {
printf("usage: cgtest [options] [key types]\n");
printf("options: -v, --verbose "
"print more output during tests\n");
printf(" --keep "
"do not delete the temporary output files\n");
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;
} else {
fprintf(stderr, "cgtest: unrecognised option '%.*s'\n",
PTRLEN_PRINTF(arg));
return 1;
}
}
passes = fails = 0;
for (i = 0; i < lenof(cgtest_keytypes); i++) {
if (active[i] != active_value)
continue;
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;
char filename[128], osfilename[128], scfilename[128];
char pubfilename[128], tmpfilename1[128], tmpfilename2[128];
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);
/*
* Create an encrypted key.
*/
setup_passphrases("sponge", "sponge", NULL);
test(0, "puttygen", "-t", keytype->name, "-o", filename, NULL);
/*
* 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);
if (cgtest_verbose)
printf("OpenSSH fp check: %s\n", cmdbuf);
(fp = get_fp(tmpfilename1,
CGT_SSH_KEYGEN | keytype->flags)) == NULL) {
printf("UNABLE to test fingerprint matching against OpenSSH");
}
sfree(cmdbuf);
if (fp && cgtest_verbose) {
char *line = get_line(tmpfilename1);
printf("OpenSSH fp: %s\n", line);
printf("Cleaned up: %s\n", fp);
sfree(line);
}
}
/*
* 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.
*/
fp = get_fp(tmpfilename1, 0);
check_fp(tmpfilename1, fp, "%s initial fp", keytype->name);
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
}
/*
* 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);
test(supports_openssh ? 0 : 1,
"puttygen", "-O", "private-openssh", "-o", osfilename,
/*
* List the fingerprint of the OpenSSH-formatted key.
*/
setup_passphrases(NULL);
test(0, "puttygen", "-l", osfilename, "-o", tmpfilename1, NULL);
check_fp(tmpfilename1, fp, "%s openssh clear fp", keytype->name);
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
/*
* 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);
test(supports_sshcom ? 0 : 1,
"puttygen", "-O", "private-sshcom",
"-o", scfilename, filename, NULL);
/*
* List the fingerprint of the ssh.com-formatted key.
*/
setup_passphrases(NULL);
test(0, "puttygen", "-l", scfilename, "-o", tmpfilename1, NULL);
check_fp(tmpfilename1, fp, "%s ssh.com clear fp", keytype->name);
/*
* 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);
}
if (supports_openssh && supports_sshcom) {
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
/*
* 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,
"p->o->s->p clear %s", keytype->name);
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
/*
* 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,
"p->s->o->p clear %s", keytype->name);
/*
* 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,
"p->s->p clear %s", keytype->name);
}
/*
* 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
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 {
setup_passphrases("sponge2", NULL);
}
test(supports_openssh ? 0 : 1,
"puttygen", "-O", "private-openssh", "-o", osfilename,
/*
* List the fingerprint of the OpenSSH-formatted key.
*/
setup_passphrases("sponge2", NULL);
test(0, "puttygen", "-l", osfilename, "-o", tmpfilename1, NULL);
check_fp(tmpfilename1, fp, "%s openssh encrypted fp",
keytype->name);
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
/*
* 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.
*/
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 {
setup_passphrases("sponge2", NULL);
}
test(supports_sshcom ? 0 : 1,
"puttygen", "-O", "private-sshcom", "-o", scfilename,
/*
* List the fingerprint of the ssh.com-formatted key.
*/
setup_passphrases("sponge2", NULL);
test(0, "puttygen", "-l", scfilename, "-o", tmpfilename1, NULL);
check_fp(tmpfilename1, fp, "%s ssh.com encrypted fp",
keytype->name);
/*
* 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);
}
if (supports_openssh && supports_sshcom) {
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
/*
* 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,
"p->o->s->p encrypted %s", keytype->name);
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
/*
* 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,
"p->s->o->p encrypted %s", keytype->name);
/*
* 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,
"p->s->p encrypted %s", keytype->name);
}
/*
* 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);
if (remove_files) {
remove(filename);
remove(pubfilename);
remove(osfilename);
remove(scfilename);
remove(tmpfilename1);
remove(tmpfilename2);
}
}
printf("%d passes, %d fails\n", passes, fails);
return fails == 0 ? 0 : 1;
}
#endif