mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-10 01:48:00 +00:00
Teach PuTTYgen to import from OpenSSH's new key format.
This is import only, for the moment: I haven't written an exporter yet. Also, we currently don't support the format's full generality - a new-style OpenSSH key file can contain multiple keys, but this code currently only handles files with one key in them. That should be easy to change, though, given only a little UI.
This commit is contained in:
parent
1e453d1f97
commit
38d1db194d
8
Recipe
8
Recipe
@ -233,6 +233,10 @@ WINMISC = MISC winstore winnet winhandl cmdline windefs winmisc winproxy
|
|||||||
UXMISC = MISC uxstore uxsel uxnet cmdline uxmisc uxproxy time
|
UXMISC = MISC uxstore uxsel uxnet cmdline uxmisc uxproxy time
|
||||||
OSXMISC = MISC uxstore uxsel osxsel uxnet uxmisc uxproxy time
|
OSXMISC = MISC uxstore uxsel osxsel uxnet uxmisc uxproxy time
|
||||||
|
|
||||||
|
# import.c and dependencies, for PuTTYgen-like utilities that have to
|
||||||
|
# load foreign key files.
|
||||||
|
IMPORT = import sshbcrypt sshblowf
|
||||||
|
|
||||||
# Character set library, for use in pterm.
|
# Character set library, for use in pterm.
|
||||||
CHARSET = sbcsdat slookup sbcs utf8 toucs fromucs xenc mimeenc macenc localenc
|
CHARSET = sbcsdat slookup sbcs utf8 toucs fromucs xenc mimeenc macenc localenc
|
||||||
|
|
||||||
@ -274,7 +278,7 @@ pageant : [G] winpgnt sshrsa sshpubk sshdes sshbn sshmd5 version tree234
|
|||||||
|
|
||||||
puttygen : [G] winpgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
|
puttygen : [G] winpgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
|
||||||
+ sshrand winnoise sshsha winstore misc winctrls sshrsa sshdss winmisc
|
+ sshrand winnoise sshsha winstore misc winctrls sshrsa sshdss winmisc
|
||||||
+ sshpubk sshaes sshsh256 sshsh512 import winutils puttygen.res
|
+ sshpubk sshaes sshsh256 sshsh512 IMPORT winutils puttygen.res
|
||||||
+ tree234 notiming winhelp winnojmp conf LIBS wintime sshecc
|
+ tree234 notiming winhelp winnojmp conf LIBS wintime sshecc
|
||||||
+ sshecdsag
|
+ sshecdsag
|
||||||
|
|
||||||
@ -293,7 +297,7 @@ plink : [U] uxplink uxcons NONSSH UXSSH U_BE_ALL logging UXMISC uxsignal
|
|||||||
|
|
||||||
puttygen : [U] cmdgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
|
puttygen : [U] cmdgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
|
||||||
+ sshrand uxnoise sshsha misc sshrsa sshdss uxcons uxstore uxmisc
|
+ sshrand uxnoise sshsha misc sshrsa sshdss uxcons uxstore uxmisc
|
||||||
+ sshpubk sshaes sshsh256 sshsh512 import puttygen.res time tree234
|
+ sshpubk sshaes sshsh256 sshsh512 IMPORT puttygen.res time tree234
|
||||||
+ uxgen notiming conf sshecc sshecdsag
|
+ uxgen notiming conf sshecc sshecdsag
|
||||||
|
|
||||||
pscp : [U] pscp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC
|
pscp : [U] pscp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC
|
||||||
|
574
import.c
574
import.c
@ -307,16 +307,166 @@ static int ssh2_read_mpint(void *data, int len, struct mpint_pos *ret)
|
|||||||
* Code to read and write OpenSSH private keys.
|
* Code to read and write OpenSSH private keys.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
enum { OSSH_DSA, OSSH_RSA, OSSH_ECDSA };
|
typedef enum {
|
||||||
enum { OSSH_ENC_3DES, OSSH_ENC_AES };
|
OSSH_DSA, OSSH_RSA, OSSH_ECDSA, OSSH_DUNNO_YET
|
||||||
|
} openssh_keytype;
|
||||||
|
typedef enum {
|
||||||
|
OSSH_FMT_OLD, OSSH_FMT_NEW
|
||||||
|
} openssh_keyfmt;
|
||||||
|
typedef enum {
|
||||||
|
OSSH_ENC_3DES, OSSH_ENC_AES
|
||||||
|
} openssh_old_enc;
|
||||||
|
typedef enum {
|
||||||
|
OSSH_E_NONE, OSSH_E_AES256CBC
|
||||||
|
} openssh_new_cipher;
|
||||||
|
typedef enum {
|
||||||
|
OSSH_K_NONE, OSSH_K_BCRYPT
|
||||||
|
} openssh_new_kdf;
|
||||||
|
|
||||||
struct openssh_key {
|
struct openssh_key {
|
||||||
int type;
|
openssh_keytype keytype;
|
||||||
int encrypted, encryption;
|
openssh_keyfmt keyfmt;
|
||||||
|
int encrypted;
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
openssh_old_enc encryption;
|
||||||
char iv[32];
|
char iv[32];
|
||||||
|
} old;
|
||||||
|
struct {
|
||||||
|
openssh_new_cipher cipher;
|
||||||
|
openssh_new_kdf kdf;
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
int rounds;
|
||||||
|
/* This points to a position within keyblob, not a
|
||||||
|
* separately allocated thing */
|
||||||
|
const unsigned char *salt;
|
||||||
|
int saltlen;
|
||||||
|
} bcrypt;
|
||||||
|
} kdfopts;
|
||||||
|
int nkeys, key_wanted;
|
||||||
|
/* This too points to a position within keyblob */
|
||||||
|
unsigned char *privatestr;
|
||||||
|
int privatelen;
|
||||||
|
} new;
|
||||||
|
} u;
|
||||||
unsigned char *keyblob;
|
unsigned char *keyblob;
|
||||||
int keyblob_len, keyblob_size;
|
int keyblob_len, keyblob_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int decrypt_openssh_key(struct openssh_key *key,
|
||||||
|
const char *passphrase,
|
||||||
|
char **errmsg)
|
||||||
|
{
|
||||||
|
assert(key->encrypted);
|
||||||
|
if (key->keyfmt == OSSH_FMT_OLD) {
|
||||||
|
/*
|
||||||
|
* Derive encryption key from passphrase and iv/salt:
|
||||||
|
*
|
||||||
|
* - let block A equal MD5(passphrase || iv)
|
||||||
|
* - let block B equal MD5(A || passphrase || iv)
|
||||||
|
* - block C would be MD5(B || passphrase || iv) and so on
|
||||||
|
* - encryption key is the first N bytes of A || B
|
||||||
|
*
|
||||||
|
* (Note that only 8 bytes of the iv are used for key
|
||||||
|
* derivation, even when the key is encrypted with AES and
|
||||||
|
* hence there are 16 bytes available.)
|
||||||
|
*/
|
||||||
|
struct MD5Context md5c;
|
||||||
|
unsigned char keybuf[32];
|
||||||
|
|
||||||
|
MD5Init(&md5c);
|
||||||
|
MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
|
||||||
|
MD5Update(&md5c, (unsigned char *)key->u.old.iv, 8);
|
||||||
|
MD5Final(keybuf, &md5c);
|
||||||
|
|
||||||
|
MD5Init(&md5c);
|
||||||
|
MD5Update(&md5c, keybuf, 16);
|
||||||
|
MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
|
||||||
|
MD5Update(&md5c, (unsigned char *)key->u.old.iv, 8);
|
||||||
|
MD5Final(keybuf+16, &md5c);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now decrypt the key blob.
|
||||||
|
*/
|
||||||
|
if (key->u.old.encryption == OSSH_ENC_3DES)
|
||||||
|
des3_decrypt_pubkey_ossh(keybuf, (unsigned char *)key->u.old.iv,
|
||||||
|
key->keyblob, key->keyblob_len);
|
||||||
|
else {
|
||||||
|
void *ctx;
|
||||||
|
assert(key->u.old.encryption == OSSH_ENC_AES);
|
||||||
|
ctx = aes_make_context();
|
||||||
|
aes128_key(ctx, keybuf);
|
||||||
|
aes_iv(ctx, (unsigned char *)key->u.old.iv);
|
||||||
|
aes_ssh2_decrypt_blk(ctx, key->keyblob, key->keyblob_len);
|
||||||
|
aes_free_context(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
smemclr(&md5c, sizeof(md5c));
|
||||||
|
smemclr(keybuf, sizeof(keybuf));
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key->keyfmt == OSSH_FMT_NEW) {
|
||||||
|
unsigned char keybuf[48];
|
||||||
|
int keysize;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Construct the decryption key, and decrypt the string.
|
||||||
|
*/
|
||||||
|
switch (key->u.new.cipher) {
|
||||||
|
case OSSH_E_NONE:
|
||||||
|
keysize = 0;
|
||||||
|
break;
|
||||||
|
case OSSH_E_AES256CBC:
|
||||||
|
keysize = 48; /* 32 byte key + 16 byte IV */
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(0 && "Bad cipher enumeration value");
|
||||||
|
}
|
||||||
|
assert(keysize <= sizeof(keybuf));
|
||||||
|
switch (key->u.new.kdf) {
|
||||||
|
case OSSH_K_NONE:
|
||||||
|
memset(keybuf, 0, keysize);
|
||||||
|
break;
|
||||||
|
case OSSH_K_BCRYPT:
|
||||||
|
openssh_bcrypt(passphrase,
|
||||||
|
key->u.new.kdfopts.bcrypt.salt,
|
||||||
|
key->u.new.kdfopts.bcrypt.saltlen,
|
||||||
|
key->u.new.kdfopts.bcrypt.rounds,
|
||||||
|
keybuf, keysize);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(0 && "Bad kdf enumeration value");
|
||||||
|
}
|
||||||
|
switch (key->u.new.cipher) {
|
||||||
|
case OSSH_E_NONE:
|
||||||
|
break;
|
||||||
|
case OSSH_E_AES256CBC:
|
||||||
|
if (key->u.new.privatelen % 16 != 0) {
|
||||||
|
*errmsg = "private key container length is not a"
|
||||||
|
" multiple of AES block size\n";
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
void *ctx = aes_make_context();
|
||||||
|
aes256_key(ctx, keybuf);
|
||||||
|
aes_iv(ctx, keybuf + 32);
|
||||||
|
aes_ssh2_decrypt_blk(ctx, key->u.new.privatestr,
|
||||||
|
key->u.new.privatelen);
|
||||||
|
aes_free_context(ctx);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(0 && "Bad cipher enumeration value");
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(0 && "Bad key format in decrypt_openssh_key");
|
||||||
|
}
|
||||||
|
|
||||||
static struct openssh_key *load_openssh_key(const Filename *filename,
|
static struct openssh_key *load_openssh_key(const Filename *filename,
|
||||||
const char **errmsg_p)
|
const char **errmsg_p)
|
||||||
{
|
{
|
||||||
@ -331,8 +481,6 @@ static struct openssh_key *load_openssh_key(const Filename *filename,
|
|||||||
ret = snew(struct openssh_key);
|
ret = snew(struct openssh_key);
|
||||||
ret->keyblob = NULL;
|
ret->keyblob = NULL;
|
||||||
ret->keyblob_len = ret->keyblob_size = 0;
|
ret->keyblob_len = ret->keyblob_size = 0;
|
||||||
ret->encrypted = 0;
|
|
||||||
memset(ret->iv, 0, sizeof(ret->iv));
|
|
||||||
|
|
||||||
fp = f_open(filename, "r", FALSE);
|
fp = f_open(filename, "r", FALSE);
|
||||||
if (!fp) {
|
if (!fp) {
|
||||||
@ -350,13 +498,25 @@ static struct openssh_key *load_openssh_key(const Filename *filename,
|
|||||||
errmsg = "file does not begin with OpenSSH key header";
|
errmsg = "file does not begin with OpenSSH key header";
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
if (!strcmp(line, "-----BEGIN RSA PRIVATE KEY-----"))
|
/*
|
||||||
ret->type = OSSH_RSA;
|
* Parse the BEGIN line. For old-format keys, this tells us the
|
||||||
else if (!strcmp(line, "-----BEGIN DSA PRIVATE KEY-----"))
|
* type of the key; for new-format keys, all it tells us is the
|
||||||
ret->type = OSSH_DSA;
|
* format, and we'll find out the key type once we parse the
|
||||||
else if (!strcmp(line, "-----BEGIN EC PRIVATE KEY-----"))
|
* base64.
|
||||||
ret->type = OSSH_ECDSA;
|
*/
|
||||||
else {
|
if (!strcmp(line, "-----BEGIN RSA PRIVATE KEY-----")) {
|
||||||
|
ret->keyfmt = OSSH_FMT_OLD;
|
||||||
|
ret->keytype = OSSH_RSA;
|
||||||
|
} else if (!strcmp(line, "-----BEGIN DSA PRIVATE KEY-----")) {
|
||||||
|
ret->keyfmt = OSSH_FMT_OLD;
|
||||||
|
ret->keytype = OSSH_DSA;
|
||||||
|
} else if (!strcmp(line, "-----BEGIN EC PRIVATE KEY-----")) {
|
||||||
|
ret->keyfmt = OSSH_FMT_OLD;
|
||||||
|
ret->keytype = OSSH_ECDSA;
|
||||||
|
} else if (!strcmp(line, "-----BEGIN OPENSSH PRIVATE KEY-----")) {
|
||||||
|
ret->keyfmt = OSSH_FMT_NEW;
|
||||||
|
ret->keytype = OSSH_DUNNO_YET;
|
||||||
|
} else {
|
||||||
errmsg = "unrecognised key type";
|
errmsg = "unrecognised key type";
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
@ -364,6 +524,11 @@ static struct openssh_key *load_openssh_key(const Filename *filename,
|
|||||||
sfree(line);
|
sfree(line);
|
||||||
line = NULL;
|
line = NULL;
|
||||||
|
|
||||||
|
if (ret->keyfmt == OSSH_FMT_OLD) {
|
||||||
|
ret->encrypted = FALSE;
|
||||||
|
memset(ret->u.old.iv, 0, sizeof(ret->u.old.iv));
|
||||||
|
}
|
||||||
|
|
||||||
headers_done = 0;
|
headers_done = 0;
|
||||||
while (1) {
|
while (1) {
|
||||||
if (!(line = fgetline(fp))) {
|
if (!(line = fgetline(fp))) {
|
||||||
@ -378,6 +543,10 @@ static struct openssh_key *load_openssh_key(const Filename *filename,
|
|||||||
break; /* done */
|
break; /* done */
|
||||||
}
|
}
|
||||||
if ((p = strchr(line, ':')) != NULL) {
|
if ((p = strchr(line, ':')) != NULL) {
|
||||||
|
if (ret->keyfmt != OSSH_FMT_OLD) {
|
||||||
|
errmsg = "expected no headers in new-style OpenSSH key format";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
if (headers_done) {
|
if (headers_done) {
|
||||||
errmsg = "header found in body of key data";
|
errmsg = "header found in body of key data";
|
||||||
goto error;
|
goto error;
|
||||||
@ -391,15 +560,15 @@ static struct openssh_key *load_openssh_key(const Filename *filename,
|
|||||||
}
|
}
|
||||||
p += 2;
|
p += 2;
|
||||||
if (!strcmp(p, "ENCRYPTED"))
|
if (!strcmp(p, "ENCRYPTED"))
|
||||||
ret->encrypted = 1;
|
ret->encrypted = TRUE;
|
||||||
} else if (!strcmp(line, "DEK-Info")) {
|
} else if (!strcmp(line, "DEK-Info")) {
|
||||||
int i, j, ivlen;
|
int i, j, ivlen;
|
||||||
|
|
||||||
if (!strncmp(p, "DES-EDE3-CBC,", 13)) {
|
if (!strncmp(p, "DES-EDE3-CBC,", 13)) {
|
||||||
ret->encryption = OSSH_ENC_3DES;
|
ret->u.old.encryption = OSSH_ENC_3DES;
|
||||||
ivlen = 8;
|
ivlen = 8;
|
||||||
} else if (!strncmp(p, "AES-128-CBC,", 12)) {
|
} else if (!strncmp(p, "AES-128-CBC,", 12)) {
|
||||||
ret->encryption = OSSH_ENC_AES;
|
ret->u.old.encryption = OSSH_ENC_AES;
|
||||||
ivlen = 16;
|
ivlen = 16;
|
||||||
} else {
|
} else {
|
||||||
errmsg = "unsupported cipher";
|
errmsg = "unsupported cipher";
|
||||||
@ -411,7 +580,7 @@ static struct openssh_key *load_openssh_key(const Filename *filename,
|
|||||||
errmsg = "expected more iv data in DEK-Info";
|
errmsg = "expected more iv data in DEK-Info";
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
ret->iv[i] = j;
|
ret->u.old.iv[i] = j;
|
||||||
p += 2;
|
p += 2;
|
||||||
}
|
}
|
||||||
if (*p) {
|
if (*p) {
|
||||||
@ -466,10 +635,163 @@ static struct openssh_key *load_openssh_key(const Filename *filename,
|
|||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret->encrypted && ret->keyblob_len % 8 != 0) {
|
if (ret->keyfmt == OSSH_FMT_NEW) {
|
||||||
errmsg = "encrypted key blob is not a multiple of cipher block size";
|
/*
|
||||||
|
* If we get here, then we haven't boiled down to a private
|
||||||
|
* key blob after all; we've merely stripped the base64
|
||||||
|
* wrapping from a new-style OpenSSH private key file. Now we
|
||||||
|
* have to parse that in turn, or at least get as far as the
|
||||||
|
* encrypted section.
|
||||||
|
*/
|
||||||
|
const void *filedata = ret->keyblob;
|
||||||
|
int filelen = ret->keyblob_len;
|
||||||
|
const void *string, *kdfopts, *bcryptsalt, *pubkey;
|
||||||
|
int stringlen, kdfoptlen, bcryptsaltlen, pubkeylen;
|
||||||
|
unsigned bcryptrounds, nkeys, key_index;
|
||||||
|
|
||||||
|
if (filelen < 15 || 0 != memcmp(filedata, "openssh-key-v1\0", 15)) {
|
||||||
|
errmsg = "new-style OpenSSH magic number missing\n";
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
filedata = (const char *)filedata + 15;
|
||||||
|
filelen -= 15;
|
||||||
|
|
||||||
|
if (!(string = get_ssh_string(&filelen, &filedata, &stringlen))) {
|
||||||
|
errmsg = "encountered EOF before cipher name\n";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (match_ssh_id(stringlen, string, "none")) {
|
||||||
|
ret->u.new.cipher = OSSH_E_NONE;
|
||||||
|
} else if (match_ssh_id(stringlen, string, "aes256-cbc")) {
|
||||||
|
ret->u.new.cipher = OSSH_E_AES256CBC;
|
||||||
|
} else {
|
||||||
|
errmsg = "unrecognised cipher name\n";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(string = get_ssh_string(&filelen, &filedata, &stringlen))) {
|
||||||
|
errmsg = "encountered EOF before kdf name\n";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (match_ssh_id(stringlen, string, "none")) {
|
||||||
|
ret->u.new.kdf = OSSH_K_NONE;
|
||||||
|
} else if (match_ssh_id(stringlen, string, "bcrypt")) {
|
||||||
|
ret->u.new.kdf = OSSH_K_BCRYPT;
|
||||||
|
} else {
|
||||||
|
errmsg = "unrecognised kdf name\n";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(kdfopts = get_ssh_string(&filelen, &filedata, &kdfoptlen))) {
|
||||||
|
errmsg = "encountered EOF before kdf options\n";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
switch (ret->u.new.kdf) {
|
||||||
|
case OSSH_K_NONE:
|
||||||
|
if (kdfoptlen != 0) {
|
||||||
|
errmsg = "expected empty options string for 'none' kdf";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OSSH_K_BCRYPT:
|
||||||
|
if (!(bcryptsalt = get_ssh_string(&kdfoptlen, &kdfopts,
|
||||||
|
&bcryptsaltlen))) {
|
||||||
|
errmsg = "bcrypt options string did not contain salt\n";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (!get_ssh_uint32(&kdfoptlen, &kdfopts, &bcryptrounds)) {
|
||||||
|
errmsg = "bcrypt options string did not contain round count\n";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
ret->u.new.kdfopts.bcrypt.salt = bcryptsalt;
|
||||||
|
ret->u.new.kdfopts.bcrypt.saltlen = bcryptsaltlen;
|
||||||
|
ret->u.new.kdfopts.bcrypt.rounds = bcryptrounds;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret->encrypted = (ret->u.new.cipher != OSSH_E_NONE);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* At this point we expect a uint32 saying how many keys are
|
||||||
|
* stored in this file. OpenSSH new-style key files can
|
||||||
|
* contain more than one. Currently we don't have any user
|
||||||
|
* interface to specify which one we're trying to extract, so
|
||||||
|
* we just bomb out with an error if more than one is found in
|
||||||
|
* the file. However, I've put in all the mechanism here to
|
||||||
|
* extract the nth one for a given n, in case we later connect
|
||||||
|
* up some UI to that mechanism. Just arrange that the
|
||||||
|
* 'key_wanted' field is set to a value in the range [0,
|
||||||
|
* nkeys) by some mechanism.
|
||||||
|
*/
|
||||||
|
if (!get_ssh_uint32(&filelen, &filedata, &nkeys)) {
|
||||||
|
errmsg = "encountered EOF before key count\n";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (nkeys != 1) {
|
||||||
|
errmsg = "multiple keys in new-style OpenSSH key file "
|
||||||
|
"not supported\n";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
ret->u.new.nkeys = nkeys;
|
||||||
|
ret->u.new.key_wanted = 0;
|
||||||
|
|
||||||
|
for (key_index = 0; key_index < nkeys; key_index++) {
|
||||||
|
int this_keytype;
|
||||||
|
if (!(pubkey = get_ssh_string(&filelen, &filedata, &pubkeylen))) {
|
||||||
|
errmsg = "encountered EOF before kdf options\n";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Check the key type, and make sure it's something we
|
||||||
|
* understand.
|
||||||
|
*/
|
||||||
|
if (!(string = get_ssh_string(&pubkeylen, &pubkey,
|
||||||
|
&stringlen))) {
|
||||||
|
errmsg = "public key did not start with type string\n";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (match_ssh_id(stringlen, string, "ssh-rsa")) {
|
||||||
|
this_keytype = OSSH_RSA;
|
||||||
|
} else if (match_ssh_id(stringlen, string, "ssh-dss")) {
|
||||||
|
this_keytype = OSSH_DSA;
|
||||||
|
} else if (match_ssh_id(stringlen, string,
|
||||||
|
"ecdsa-sha2-nistp256") ||
|
||||||
|
match_ssh_id(stringlen, string,
|
||||||
|
"ecdsa-sha2-nistp384") ||
|
||||||
|
match_ssh_id(stringlen, string,
|
||||||
|
"ecdsa-sha2-nistp521")) {
|
||||||
|
this_keytype = OSSH_ECDSA;
|
||||||
|
} else {
|
||||||
|
errmsg = "public key did not start with type string\n";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (key_index == ret->u.new.key_wanted)
|
||||||
|
ret->keytype = this_keytype;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now we expect a string containing the encrypted part of the
|
||||||
|
* key file.
|
||||||
|
*/
|
||||||
|
if (!(string = get_ssh_string(&filelen, &filedata, &stringlen))) {
|
||||||
|
errmsg = "encountered EOF before private key container\n";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
ret->u.new.privatestr = (unsigned char *)string;
|
||||||
|
ret->u.new.privatelen = stringlen;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* And now we're done, until asked to actually decrypt.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret->keyfmt == OSSH_FMT_OLD) {
|
||||||
|
if (ret->encrypted && ret->keyblob_len % 8 != 0) {
|
||||||
|
errmsg = "encrypted key blob is not a multiple of "
|
||||||
|
"cipher block size";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
smemclr(base64_bit, sizeof(base64_bit));
|
smemclr(base64_bit, sizeof(base64_bit));
|
||||||
if (errmsg_p) *errmsg_p = NULL;
|
if (errmsg_p) *errmsg_p = NULL;
|
||||||
@ -531,52 +853,11 @@ struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
|
|||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (key->encrypted) {
|
if (key->encrypted) {
|
||||||
/*
|
if (!decrypt_openssh_key(key, passphrase, &errmsg))
|
||||||
* Derive encryption key from passphrase and iv/salt:
|
goto error;
|
||||||
*
|
|
||||||
* - let block A equal MD5(passphrase || iv)
|
|
||||||
* - let block B equal MD5(A || passphrase || iv)
|
|
||||||
* - block C would be MD5(B || passphrase || iv) and so on
|
|
||||||
* - encryption key is the first N bytes of A || B
|
|
||||||
*
|
|
||||||
* (Note that only 8 bytes of the iv are used for key
|
|
||||||
* derivation, even when the key is encrypted with AES and
|
|
||||||
* hence there are 16 bytes available.)
|
|
||||||
*/
|
|
||||||
struct MD5Context md5c;
|
|
||||||
unsigned char keybuf[32];
|
|
||||||
|
|
||||||
MD5Init(&md5c);
|
|
||||||
MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
|
|
||||||
MD5Update(&md5c, (unsigned char *)key->iv, 8);
|
|
||||||
MD5Final(keybuf, &md5c);
|
|
||||||
|
|
||||||
MD5Init(&md5c);
|
|
||||||
MD5Update(&md5c, keybuf, 16);
|
|
||||||
MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
|
|
||||||
MD5Update(&md5c, (unsigned char *)key->iv, 8);
|
|
||||||
MD5Final(keybuf+16, &md5c);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Now decrypt the key blob.
|
|
||||||
*/
|
|
||||||
if (key->encryption == OSSH_ENC_3DES)
|
|
||||||
des3_decrypt_pubkey_ossh(keybuf, (unsigned char *)key->iv,
|
|
||||||
key->keyblob, key->keyblob_len);
|
|
||||||
else {
|
|
||||||
void *ctx;
|
|
||||||
assert(key->encryption == OSSH_ENC_AES);
|
|
||||||
ctx = aes_make_context();
|
|
||||||
aes128_key(ctx, keybuf);
|
|
||||||
aes_iv(ctx, (unsigned char *)key->iv);
|
|
||||||
aes_ssh2_decrypt_blk(ctx, key->keyblob, key->keyblob_len);
|
|
||||||
aes_free_context(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
smemclr(&md5c, sizeof(md5c));
|
|
||||||
smemclr(keybuf, sizeof(keybuf));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (key->keyfmt == OSSH_FMT_OLD) {
|
||||||
/*
|
/*
|
||||||
* Now we have a decrypted key blob, which contains an ASN.1
|
* Now we have a decrypted key blob, which contains an ASN.1
|
||||||
* encoded private key. We must now untangle the ASN.1.
|
* encoded private key. We must now untangle the ASN.1.
|
||||||
@ -612,16 +893,15 @@ struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Expect a load of INTEGERs. */
|
/* Expect a load of INTEGERs. */
|
||||||
if (key->type == OSSH_RSA)
|
if (key->keytype == OSSH_RSA)
|
||||||
num_integers = 9;
|
num_integers = 9;
|
||||||
else if (key->type == OSSH_DSA)
|
else if (key->keytype == OSSH_DSA)
|
||||||
num_integers = 6;
|
num_integers = 6;
|
||||||
else
|
else
|
||||||
num_integers = 0; /* placate compiler warnings */
|
num_integers = 0; /* placate compiler warnings */
|
||||||
|
|
||||||
|
|
||||||
if (key->type == OSSH_ECDSA)
|
if (key->keytype == OSSH_ECDSA) {
|
||||||
{
|
|
||||||
/* And now for something completely different */
|
/* And now for something completely different */
|
||||||
unsigned char *priv;
|
unsigned char *priv;
|
||||||
int privlen;
|
int privlen;
|
||||||
@ -668,9 +948,11 @@ struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
|
|||||||
}
|
}
|
||||||
if (len == 8 && !memcmp(p, nistp256_oid, nistp256_oid_len)) {
|
if (len == 8 && !memcmp(p, nistp256_oid, nistp256_oid_len)) {
|
||||||
curve = ec_p256();
|
curve = ec_p256();
|
||||||
} else if (len == 5 && !memcmp(p, nistp384_oid, nistp384_oid_len)) {
|
} else if (len == 5 && !memcmp(p, nistp384_oid,
|
||||||
|
nistp384_oid_len)) {
|
||||||
curve = ec_p384();
|
curve = ec_p384();
|
||||||
} else if (len == 5 && !memcmp(p, nistp521_oid, nistp521_oid_len)) {
|
} else if (len == 5 && !memcmp(p, nistp521_oid,
|
||||||
|
nistp521_oid_len)) {
|
||||||
curve = ec_p521();
|
curve = ec_p521();
|
||||||
} else {
|
} else {
|
||||||
errmsg = "Unsupported ECDSA curve.";
|
errmsg = "Unsupported ECDSA curve.";
|
||||||
@ -726,14 +1008,15 @@ struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
|
|||||||
PUT_32BIT(blob+4+19+4+8+4+len, privlen);
|
PUT_32BIT(blob+4+19+4+8+4+len, privlen);
|
||||||
memcpy(blob+4+19+4+8+4+len+4, priv, privlen);
|
memcpy(blob+4+19+4+8+4+len+4, priv, privlen);
|
||||||
retkey->data = retkey->alg->createkey(blob, 4+19+4+8+4+len,
|
retkey->data = retkey->alg->createkey(blob, 4+19+4+8+4+len,
|
||||||
blob+4+19+4+8+4+len, 4+privlen);
|
blob+4+19+4+8+4+len,
|
||||||
|
4+privlen);
|
||||||
if (!retkey->data) {
|
if (!retkey->data) {
|
||||||
sfree(retkey);
|
sfree(retkey);
|
||||||
errmsg = "unable to create key data structure";
|
errmsg = "unable to create key data structure";
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (key->type == OSSH_RSA || key->type == OSSH_DSA) {
|
} else if (key->keytype == OSSH_RSA || key->keytype == OSSH_DSA) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Space to create key blob in.
|
* Space to create key blob in.
|
||||||
@ -741,9 +1024,9 @@ struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
|
|||||||
blobsize = 256+key->keyblob_len;
|
blobsize = 256+key->keyblob_len;
|
||||||
blob = snewn(blobsize, unsigned char);
|
blob = snewn(blobsize, unsigned char);
|
||||||
PUT_32BIT(blob, 7);
|
PUT_32BIT(blob, 7);
|
||||||
if (key->type == OSSH_DSA)
|
if (key->keytype == OSSH_DSA)
|
||||||
memcpy(blob+4, "ssh-dss", 7);
|
memcpy(blob+4, "ssh-dss", 7);
|
||||||
else if (key->type == OSSH_RSA)
|
else if (key->keytype == OSSH_RSA)
|
||||||
memcpy(blob+4, "ssh-rsa", 7);
|
memcpy(blob+4, "ssh-rsa", 7);
|
||||||
blobptr = 4+7;
|
blobptr = 4+7;
|
||||||
privptr = -1;
|
privptr = -1;
|
||||||
@ -768,7 +1051,7 @@ struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
|
|||||||
errmsg = "version number mismatch";
|
errmsg = "version number mismatch";
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
} else if (key->type == OSSH_RSA) {
|
} else if (key->keytype == OSSH_RSA) {
|
||||||
/*
|
/*
|
||||||
* Integers 1 and 2 go into the public blob but in the
|
* Integers 1 and 2 go into the public blob but in the
|
||||||
* opposite order; integers 3, 4, 5 and 8 go into the
|
* opposite order; integers 3, 4, 5 and 8 go into the
|
||||||
@ -789,7 +1072,7 @@ struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
|
|||||||
privptr = blobptr;
|
privptr = blobptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (key->type == OSSH_DSA) {
|
} else if (key->keytype == OSSH_DSA) {
|
||||||
/*
|
/*
|
||||||
* Integers 1-4 go into the public blob; integer 5 goes
|
* Integers 1-4 go into the public blob; integer 5 goes
|
||||||
* into the private blob.
|
* into the private blob.
|
||||||
@ -813,9 +1096,10 @@ struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
|
|||||||
*/
|
*/
|
||||||
assert(privptr > 0); /* should have bombed by now if not */
|
assert(privptr > 0); /* should have bombed by now if not */
|
||||||
retkey = snew(struct ssh2_userkey);
|
retkey = snew(struct ssh2_userkey);
|
||||||
retkey->alg = (key->type == OSSH_RSA ? &ssh_rsa : &ssh_dss);
|
retkey->alg = (key->keytype == OSSH_RSA ? &ssh_rsa : &ssh_dss);
|
||||||
retkey->data = retkey->alg->createkey(blob, privptr,
|
retkey->data = retkey->alg->createkey(blob, privptr,
|
||||||
blob+privptr, blobptr-privptr);
|
blob+privptr,
|
||||||
|
blobptr-privptr);
|
||||||
if (!retkey->data) {
|
if (!retkey->data) {
|
||||||
sfree(retkey);
|
sfree(retkey);
|
||||||
errmsg = "unable to create key data structure";
|
errmsg = "unable to create key data structure";
|
||||||
@ -826,7 +1110,143 @@ struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
|
|||||||
assert(0 && "Bad key type from load_openssh_key");
|
assert(0 && "Bad key type from load_openssh_key");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The old key format doesn't include a comment in the private
|
||||||
|
* key file.
|
||||||
|
*/
|
||||||
retkey->comment = dupstr("imported-openssh-key");
|
retkey->comment = dupstr("imported-openssh-key");
|
||||||
|
} else if (key->keyfmt == OSSH_FMT_NEW) {
|
||||||
|
unsigned checkint0, checkint1;
|
||||||
|
const void *priv, *string;
|
||||||
|
int privlen, stringlen, key_index;
|
||||||
|
const struct ssh_signkey *alg;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* OpenSSH's new key format. Here we must parse the entire
|
||||||
|
* encrypted section, and extract the key identified by
|
||||||
|
* key_wanted.
|
||||||
|
*/
|
||||||
|
priv = key->u.new.privatestr;
|
||||||
|
privlen = key->u.new.privatelen;
|
||||||
|
|
||||||
|
if (!get_ssh_uint32(&privlen, &priv, &checkint0) ||
|
||||||
|
!get_ssh_uint32(&privlen, &priv, &checkint1) ||
|
||||||
|
checkint0 != checkint1) {
|
||||||
|
errmsg = "decryption check failed";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
retkey = NULL;
|
||||||
|
for (key_index = 0; key_index < key->u.new.nkeys; key_index++) {
|
||||||
|
unsigned char *thiskey;
|
||||||
|
openssh_keytype this_keytype;
|
||||||
|
int thiskeylen, npieces;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read the key type, which will tell us how to scan over
|
||||||
|
* the key to get to the next one.
|
||||||
|
*/
|
||||||
|
if (!(string = get_ssh_string(&privlen, &priv, &stringlen))) {
|
||||||
|
errmsg = "expected key type in private string";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Preliminary key type identification, and decide how
|
||||||
|
* many pieces of key we expect to see. Currently
|
||||||
|
* (conveniently) all key types can be seen as some number
|
||||||
|
* of strings, so we just need to know how many of them to
|
||||||
|
* skip over. (The numbers below exclude the key comment.)
|
||||||
|
*/
|
||||||
|
if (match_ssh_id(stringlen, string, "ssh-rsa")) {
|
||||||
|
this_keytype = OSSH_RSA;
|
||||||
|
alg = &ssh_rsa;
|
||||||
|
npieces = 6; /* n,e,d,iqmp,q,p */
|
||||||
|
} else if (match_ssh_id(stringlen, string, "ssh-dss")) {
|
||||||
|
this_keytype = OSSH_DSA;
|
||||||
|
alg = &ssh_dss;
|
||||||
|
npieces = 5; /* p,q,g,y,x */
|
||||||
|
} else if (match_ssh_id(stringlen, string,
|
||||||
|
"ecdsa-sha2-nistp256")) {
|
||||||
|
this_keytype = OSSH_ECDSA;
|
||||||
|
alg = &ssh_ecdsa_nistp256;
|
||||||
|
npieces = 3; /* curve name, point, private exponent */
|
||||||
|
} else if (match_ssh_id(stringlen, string,
|
||||||
|
"ecdsa-sha2-nistp384")) {
|
||||||
|
this_keytype = OSSH_ECDSA;
|
||||||
|
alg = &ssh_ecdsa_nistp384;
|
||||||
|
npieces = 3; /* curve name, point, private exponent */
|
||||||
|
} else if (match_ssh_id(stringlen, string,
|
||||||
|
"ecdsa-sha2-nistp521")) {
|
||||||
|
this_keytype = OSSH_ECDSA;
|
||||||
|
alg = &ssh_ecdsa_nistp521;
|
||||||
|
npieces = 3; /* curve name, point, private exponent */
|
||||||
|
} else {
|
||||||
|
errmsg = "private key did not start with type string\n";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
thiskey = (unsigned char *)priv;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Skip over the pieces of key.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < npieces; i++) {
|
||||||
|
if (!(string = get_ssh_string(&privlen, &priv, &stringlen))) {
|
||||||
|
errmsg = "ran out of data in mid-private-key";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thiskeylen = (int)((const unsigned char *)priv -
|
||||||
|
(const unsigned char *)thiskey);
|
||||||
|
if (key_index == key->u.new.key_wanted) {
|
||||||
|
if (this_keytype != key->keytype) {
|
||||||
|
errmsg = "public and private key types did not match";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
retkey = snew(struct ssh2_userkey);
|
||||||
|
retkey->alg = alg;
|
||||||
|
retkey->data = alg->openssh_createkey(&thiskey, &thiskeylen);
|
||||||
|
if (!retkey->data) {
|
||||||
|
sfree(retkey);
|
||||||
|
errmsg = "unable to create key data structure";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read the key comment.
|
||||||
|
*/
|
||||||
|
if (!(string = get_ssh_string(&privlen, &priv, &stringlen))) {
|
||||||
|
errmsg = "ran out of data at key comment";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (key_index == key->u.new.key_wanted) {
|
||||||
|
assert(retkey);
|
||||||
|
retkey->comment = dupprintf("%.*s", stringlen,
|
||||||
|
(const char *)string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!retkey) {
|
||||||
|
errmsg = "key index out of range";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now we expect nothing left but padding.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < privlen; i++) {
|
||||||
|
if (((const unsigned char *)priv)[i] != (unsigned char)(i+1)) {
|
||||||
|
errmsg = "padding at end of private string did not match";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert(0 && "Bad key format from load_openssh_key");
|
||||||
|
}
|
||||||
|
|
||||||
errmsg = NULL; /* no error */
|
errmsg = NULL; /* no error */
|
||||||
retval = retkey;
|
retval = retkey;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user