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
|
||||
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.
|
||||
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
|
||||
+ 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
|
||||
+ 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
|
||||
+ 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
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
enum { OSSH_DSA, OSSH_RSA, OSSH_ECDSA };
|
||||
enum { OSSH_ENC_3DES, OSSH_ENC_AES };
|
||||
typedef enum {
|
||||
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 {
|
||||
int type;
|
||||
int encrypted, encryption;
|
||||
openssh_keytype keytype;
|
||||
openssh_keyfmt keyfmt;
|
||||
int encrypted;
|
||||
union {
|
||||
struct {
|
||||
openssh_old_enc encryption;
|
||||
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;
|
||||
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,
|
||||
const char **errmsg_p)
|
||||
{
|
||||
@ -331,8 +481,6 @@ static struct openssh_key *load_openssh_key(const Filename *filename,
|
||||
ret = snew(struct openssh_key);
|
||||
ret->keyblob = NULL;
|
||||
ret->keyblob_len = ret->keyblob_size = 0;
|
||||
ret->encrypted = 0;
|
||||
memset(ret->iv, 0, sizeof(ret->iv));
|
||||
|
||||
fp = f_open(filename, "r", FALSE);
|
||||
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";
|
||||
goto error;
|
||||
}
|
||||
if (!strcmp(line, "-----BEGIN RSA PRIVATE KEY-----"))
|
||||
ret->type = OSSH_RSA;
|
||||
else if (!strcmp(line, "-----BEGIN DSA PRIVATE KEY-----"))
|
||||
ret->type = OSSH_DSA;
|
||||
else if (!strcmp(line, "-----BEGIN EC PRIVATE KEY-----"))
|
||||
ret->type = OSSH_ECDSA;
|
||||
else {
|
||||
/*
|
||||
* Parse the BEGIN line. For old-format keys, this tells us the
|
||||
* type of the key; for new-format keys, all it tells us is the
|
||||
* format, and we'll find out the key type once we parse the
|
||||
* base64.
|
||||
*/
|
||||
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";
|
||||
goto error;
|
||||
}
|
||||
@ -364,6 +524,11 @@ static struct openssh_key *load_openssh_key(const Filename *filename,
|
||||
sfree(line);
|
||||
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;
|
||||
while (1) {
|
||||
if (!(line = fgetline(fp))) {
|
||||
@ -378,6 +543,10 @@ static struct openssh_key *load_openssh_key(const Filename *filename,
|
||||
break; /* done */
|
||||
}
|
||||
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) {
|
||||
errmsg = "header found in body of key data";
|
||||
goto error;
|
||||
@ -391,15 +560,15 @@ static struct openssh_key *load_openssh_key(const Filename *filename,
|
||||
}
|
||||
p += 2;
|
||||
if (!strcmp(p, "ENCRYPTED"))
|
||||
ret->encrypted = 1;
|
||||
ret->encrypted = TRUE;
|
||||
} else if (!strcmp(line, "DEK-Info")) {
|
||||
int i, j, ivlen;
|
||||
|
||||
if (!strncmp(p, "DES-EDE3-CBC,", 13)) {
|
||||
ret->encryption = OSSH_ENC_3DES;
|
||||
ret->u.old.encryption = OSSH_ENC_3DES;
|
||||
ivlen = 8;
|
||||
} else if (!strncmp(p, "AES-128-CBC,", 12)) {
|
||||
ret->encryption = OSSH_ENC_AES;
|
||||
ret->u.old.encryption = OSSH_ENC_AES;
|
||||
ivlen = 16;
|
||||
} else {
|
||||
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";
|
||||
goto error;
|
||||
}
|
||||
ret->iv[i] = j;
|
||||
ret->u.old.iv[i] = j;
|
||||
p += 2;
|
||||
}
|
||||
if (*p) {
|
||||
@ -466,10 +635,163 @@ static struct openssh_key *load_openssh_key(const Filename *filename,
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (ret->encrypted && ret->keyblob_len % 8 != 0) {
|
||||
errmsg = "encrypted key blob is not a multiple of cipher block size";
|
||||
if (ret->keyfmt == OSSH_FMT_NEW) {
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
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));
|
||||
if (errmsg_p) *errmsg_p = NULL;
|
||||
@ -531,52 +853,11 @@ struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
|
||||
return NULL;
|
||||
|
||||
if (key->encrypted) {
|
||||
/*
|
||||
* 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->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 (!decrypt_openssh_key(key, passphrase, &errmsg))
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (key->keyfmt == OSSH_FMT_OLD) {
|
||||
/*
|
||||
* Now we have a decrypted key blob, which contains an 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. */
|
||||
if (key->type == OSSH_RSA)
|
||||
if (key->keytype == OSSH_RSA)
|
||||
num_integers = 9;
|
||||
else if (key->type == OSSH_DSA)
|
||||
else if (key->keytype == OSSH_DSA)
|
||||
num_integers = 6;
|
||||
else
|
||||
num_integers = 0; /* placate compiler warnings */
|
||||
|
||||
|
||||
if (key->type == OSSH_ECDSA)
|
||||
{
|
||||
if (key->keytype == OSSH_ECDSA) {
|
||||
/* And now for something completely different */
|
||||
unsigned char *priv;
|
||||
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)) {
|
||||
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();
|
||||
} 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();
|
||||
} else {
|
||||
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);
|
||||
memcpy(blob+4+19+4+8+4+len+4, priv, privlen);
|
||||
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) {
|
||||
sfree(retkey);
|
||||
errmsg = "unable to create key data structure";
|
||||
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.
|
||||
@ -741,9 +1024,9 @@ struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
|
||||
blobsize = 256+key->keyblob_len;
|
||||
blob = snewn(blobsize, unsigned char);
|
||||
PUT_32BIT(blob, 7);
|
||||
if (key->type == OSSH_DSA)
|
||||
if (key->keytype == OSSH_DSA)
|
||||
memcpy(blob+4, "ssh-dss", 7);
|
||||
else if (key->type == OSSH_RSA)
|
||||
else if (key->keytype == OSSH_RSA)
|
||||
memcpy(blob+4, "ssh-rsa", 7);
|
||||
blobptr = 4+7;
|
||||
privptr = -1;
|
||||
@ -768,7 +1051,7 @@ struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
|
||||
errmsg = "version number mismatch";
|
||||
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
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
} else if (key->type == OSSH_DSA) {
|
||||
} else if (key->keytype == OSSH_DSA) {
|
||||
/*
|
||||
* Integers 1-4 go into the public blob; integer 5 goes
|
||||
* 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 */
|
||||
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,
|
||||
blob+privptr, blobptr-privptr);
|
||||
blob+privptr,
|
||||
blobptr-privptr);
|
||||
if (!retkey->data) {
|
||||
sfree(retkey);
|
||||
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");
|
||||
}
|
||||
|
||||
/*
|
||||
* The old key format doesn't include a comment in the private
|
||||
* key file.
|
||||
*/
|
||||
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 */
|
||||
retval = retkey;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user