putty/import.c

2377 строки
69 KiB
C
Исходник Обычный вид История

/*
* Code for PuTTY to import and export private key files in other
* SSH clients' formats.
*/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <ctype.h>
#include "putty.h"
#include "ssh.h"
#include "misc.h"
int openssh_pem_encrypted(const Filename *filename);
int openssh_new_encrypted(const Filename *filename);
struct ssh2_userkey *openssh_pem_read(const Filename *filename,
char *passphrase,
const char **errmsg_p);
struct ssh2_userkey *openssh_new_read(const Filename *filename,
char *passphrase,
const char **errmsg_p);
int openssh_auto_write(const Filename *filename, struct ssh2_userkey *key,
char *passphrase);
int openssh_pem_write(const Filename *filename, struct ssh2_userkey *key,
char *passphrase);
int openssh_new_write(const Filename *filename, struct ssh2_userkey *key,
char *passphrase);
int sshcom_encrypted(const Filename *filename, char **comment);
struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase,
const char **errmsg_p);
int sshcom_write(const Filename *filename, struct ssh2_userkey *key,
char *passphrase);
/*
* Given a key type, determine whether we know how to import it.
*/
int import_possible(int type)
{
if (type == SSH_KEYTYPE_OPENSSH_PEM)
return 1;
if (type == SSH_KEYTYPE_OPENSSH_NEW)
return 1;
if (type == SSH_KEYTYPE_SSHCOM)
return 1;
return 0;
}
/*
* Given a key type, determine what native key type
* (SSH_KEYTYPE_SSH1 or SSH_KEYTYPE_SSH2) it will come out as once
* we've imported it.
*/
int import_target_type(int type)
{
/*
* There are no known foreign SSH-1 key formats.
*/
return SSH_KEYTYPE_SSH2;
}
/*
* Determine whether a foreign key is encrypted.
*/
int import_encrypted(const Filename *filename, int type, char **comment)
{
if (type == SSH_KEYTYPE_OPENSSH_PEM) {
/* OpenSSH PEM format doesn't contain a key comment at all */
*comment = dupstr(filename_to_str(filename));
return openssh_pem_encrypted(filename);
} else if (type == SSH_KEYTYPE_OPENSSH_NEW) {
/* OpenSSH new format does, but it's inside the encrypted
* section for some reason */
*comment = dupstr(filename_to_str(filename));
return openssh_new_encrypted(filename);
} else if (type == SSH_KEYTYPE_SSHCOM) {
return sshcom_encrypted(filename, comment);
}
return 0;
}
/*
* Import an SSH-1 key.
*/
int import_ssh1(const Filename *filename, int type,
struct RSAKey *key, char *passphrase, const char **errmsg_p)
{
return 0;
}
/*
* Import an SSH-2 key.
*/
struct ssh2_userkey *import_ssh2(const Filename *filename, int type,
char *passphrase, const char **errmsg_p)
{
if (type == SSH_KEYTYPE_OPENSSH_PEM)
return openssh_pem_read(filename, passphrase, errmsg_p);
else if (type == SSH_KEYTYPE_OPENSSH_NEW)
return openssh_new_read(filename, passphrase, errmsg_p);
if (type == SSH_KEYTYPE_SSHCOM)
return sshcom_read(filename, passphrase, errmsg_p);
return NULL;
}
/*
* Export an SSH-1 key.
*/
int export_ssh1(const Filename *filename, int type, struct RSAKey *key,
char *passphrase)
{
return 0;
}
/*
* Export an SSH-2 key.
*/
int export_ssh2(const Filename *filename, int type,
struct ssh2_userkey *key, char *passphrase)
{
if (type == SSH_KEYTYPE_OPENSSH_AUTO)
return openssh_auto_write(filename, key, passphrase);
if (type == SSH_KEYTYPE_OPENSSH_NEW)
return openssh_new_write(filename, key, passphrase);
if (type == SSH_KEYTYPE_SSHCOM)
return sshcom_write(filename, key, passphrase);
return 0;
}
/*
* Strip trailing CRs and LFs at the end of a line of text.
*/
void strip_crlf(char *str)
{
char *p = str + strlen(str);
while (p > str && (p[-1] == '\r' || p[-1] == '\n'))
*--p = '\0';
}
/* ----------------------------------------------------------------------
* Helper routines. (The base64 ones are defined in sshpubk.c.)
*/
#define isbase64(c) ( ((c) >= 'A' && (c) <= 'Z') || \
((c) >= 'a' && (c) <= 'z') || \
((c) >= '0' && (c) <= '9') || \
(c) == '+' || (c) == '/' || (c) == '=' \
)
/*
* Read an ASN.1/BER identifier and length pair.
*
* Flags are a combination of the #defines listed below.
*
* Returns -1 if unsuccessful; otherwise returns the number of
* bytes used out of the source data.
*/
/* ASN.1 tag classes. */
#define ASN1_CLASS_UNIVERSAL (0 << 6)
#define ASN1_CLASS_APPLICATION (1 << 6)
#define ASN1_CLASS_CONTEXT_SPECIFIC (2 << 6)
#define ASN1_CLASS_PRIVATE (3 << 6)
#define ASN1_CLASS_MASK (3 << 6)
/* Primitive versus constructed bit. */
#define ASN1_CONSTRUCTED (1 << 5)
/*
* Write an ASN.1/BER identifier and length pair. Returns the
* number of bytes consumed. Assumes dest contains enough space.
* Will avoid writing anything if dest is NULL, but still return
* amount of space required.
*/
static void BinarySink_put_ber_id_len(BinarySink *bs,
int id, int length, int flags)
{
if (id <= 30) {
/*
* Identifier is one byte.
*/
put_byte(bs, id | flags);
} else {
int n;
/*
* Identifier is multiple bytes: the first byte is 11111
* plus the flags, and subsequent bytes encode the value of
* the identifier, 7 bits at a time, with the top bit of
* each byte 1 except the last one which is 0.
*/
put_byte(bs, 0x1F | flags);
for (n = 1; (id >> (7*n)) > 0; n++)
continue; /* count the bytes */
while (n--)
put_byte(bs, (n ? 0x80 : 0) | ((id >> (7*n)) & 0x7F));
}
if (length < 128) {
/*
* Length is one byte.
*/
put_byte(bs, length);
} else {
int n;
/*
* Length is multiple bytes. The first is 0x80 plus the
* number of subsequent bytes, and the subsequent bytes
* encode the actual length.
*/
for (n = 1; (length >> (8*n)) > 0; n++)
continue; /* count the bytes */
put_byte(bs, 0x80 | n);
while (n--)
put_byte(bs, (length >> (8*n)) & 0xFF);
}
}
#define put_ber_id_len(bs, id, len, flags) \
BinarySink_put_ber_id_len(BinarySink_UPCAST(bs), id, len, flags)
typedef struct ber_item {
int id;
int flags;
ptrlen data;
} ber_item;
static ber_item BinarySource_get_ber(BinarySource *src)
{
ber_item toret;
unsigned char leadbyte, lenbyte;
size_t length;
leadbyte = get_byte(src);
toret.flags = (leadbyte & 0xE0);
if ((leadbyte & 0x1F) == 0x1F) {
unsigned char idbyte;
toret.id = 0;
do {
idbyte = get_byte(src);
toret.id = (toret.id << 7) | (idbyte & 0x7F);
} while (idbyte & 0x80);
} else {
toret.id = leadbyte & 0x1F;
}
lenbyte = get_byte(src);
if (lenbyte & 0x80) {
int nbytes = lenbyte & 0x7F;
length = 0;
while (nbytes-- > 0)
length = (length << 8) | get_byte(src);
} else {
length = lenbyte;
}
toret.data = get_data(src, length);
return toret;
}
#define get_ber(bs) BinarySource_get_ber(BinarySource_UPCAST(bs))
/* ----------------------------------------------------------------------
* Code to read and write OpenSSH private keys, in the old-style PEM
* format.
*/
typedef enum {
OP_DSA, OP_RSA, OP_ECDSA
} openssh_pem_keytype;
typedef enum {
OP_E_3DES, OP_E_AES
} openssh_pem_enc;
struct openssh_pem_key {
openssh_pem_keytype keytype;
int encrypted;
openssh_pem_enc encryption;
char iv[32];
strbuf *keyblob;
};
void BinarySink_put_mp_ssh2_from_string(
BinarySink *bs, const void *bytesv, int nbytes)
{
const unsigned char *bytes = (const unsigned char *)bytesv;
while (nbytes > 0 && bytes[0] == 0) {
nbytes--;
bytes++;
}
if (nbytes > 0 && bytes[0] & 0x80) {
put_uint32(bs, nbytes + 1);
put_byte(bs, 0);
} else {
put_uint32(bs, nbytes);
}
put_data(bs, bytes, nbytes);
}
#define put_mp_ssh2_from_string(bs, val, len) \
BinarySink_put_mp_ssh2_from_string(BinarySink_UPCAST(bs), val, len)
static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename,
const char **errmsg_p)
{
struct openssh_pem_key *ret;
FILE *fp = NULL;
char *line = NULL;
const char *errmsg;
char *p;
int headers_done;
char base64_bit[4];
int base64_chars = 0;
ret = snew(struct openssh_pem_key);
ret->keyblob = strbuf_new();
fp = f_open(filename, "r", FALSE);
if (!fp) {
errmsg = "unable to open key file";
goto error;
}
if (!(line = fgetline(fp))) {
errmsg = "unexpected end of file";
goto error;
}
strip_crlf(line);
if (!strstartswith(line, "-----BEGIN ") ||
!strendswith(line, "PRIVATE KEY-----")) {
errmsg = "file does not begin with OpenSSH key header";
goto error;
}
/*
* 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->keytype = OP_RSA;
} else if (!strcmp(line, "-----BEGIN DSA PRIVATE KEY-----")) {
ret->keytype = OP_DSA;
} else if (!strcmp(line, "-----BEGIN EC PRIVATE KEY-----")) {
ret->keytype = OP_ECDSA;
} else if (!strcmp(line, "-----BEGIN OPENSSH PRIVATE KEY-----")) {
errmsg = "this is a new-style OpenSSH key";
goto error;
} else {
errmsg = "unrecognised key type";
goto error;
}
smemclr(line, strlen(line));
sfree(line);
line = NULL;
ret->encrypted = FALSE;
memset(ret->iv, 0, sizeof(ret->iv));
headers_done = 0;
while (1) {
if (!(line = fgetline(fp))) {
errmsg = "unexpected end of file";
goto error;
}
strip_crlf(line);
if (strstartswith(line, "-----END ") &&
strendswith(line, "PRIVATE KEY-----")) {
sfree(line);
line = NULL;
break; /* done */
}
if ((p = strchr(line, ':')) != NULL) {
if (headers_done) {
errmsg = "header found in body of key data";
goto error;
}
*p++ = '\0';
while (*p && isspace((unsigned char)*p)) p++;
if (!strcmp(line, "Proc-Type")) {
if (p[0] != '4' || p[1] != ',') {
errmsg = "Proc-Type is not 4 (only 4 is supported)";
goto error;
}
p += 2;
if (!strcmp(p, "ENCRYPTED"))
ret->encrypted = TRUE;
} else if (!strcmp(line, "DEK-Info")) {
int i, ivlen;
if (!strncmp(p, "DES-EDE3-CBC,", 13)) {
ret->encryption = OP_E_3DES;
ivlen = 8;
} else if (!strncmp(p, "AES-128-CBC,", 12)) {
ret->encryption = OP_E_AES;
ivlen = 16;
} else {
errmsg = "unsupported cipher";
goto error;
}
p = strchr(p, ',') + 1;/* always non-NULL, by above checks */
for (i = 0; i < ivlen; i++) {
unsigned j;
if (1 != sscanf(p, "%2x", &j)) {
errmsg = "expected more iv data in DEK-Info";
goto error;
}
ret->iv[i] = j;
p += 2;
}
if (*p) {
errmsg = "more iv data than expected in DEK-Info";
goto error;
}
}
} else {
headers_done = 1;
p = line;
while (isbase64(*p)) {
base64_bit[base64_chars++] = *p;
if (base64_chars == 4) {
unsigned char out[3];
int len;
base64_chars = 0;
len = base64_decode_atom(base64_bit, out);
if (len <= 0) {
errmsg = "invalid base64 encoding";
goto error;
}
put_data(ret->keyblob, out, len);
smemclr(out, sizeof(out));
}
p++;
}
}
smemclr(line, strlen(line));
sfree(line);
line = NULL;
}
fclose(fp);
fp = NULL;
if (!ret->keyblob || ret->keyblob->len == 0) {
errmsg = "key body not present";
goto error;
}
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;
return ret;
error:
if (line) {
smemclr(line, strlen(line));
sfree(line);
line = NULL;
}
smemclr(base64_bit, sizeof(base64_bit));
if (ret) {
if (ret->keyblob)
strbuf_free(ret->keyblob);
smemclr(ret, sizeof(*ret));
sfree(ret);
}
if (errmsg_p) *errmsg_p = errmsg;
if (fp) fclose(fp);
return NULL;
}
int openssh_pem_encrypted(const Filename *filename)
{
struct openssh_pem_key *key = load_openssh_pem_key(filename, NULL);
int ret;
if (!key)
return 0;
ret = key->encrypted;
strbuf_free(key->keyblob);
smemclr(key, sizeof(*key));
sfree(key);
return ret;
}
struct ssh2_userkey *openssh_pem_read(const Filename *filename,
char *passphrase,
const char **errmsg_p)
{
struct openssh_pem_key *key = load_openssh_pem_key(filename, errmsg_p);
struct ssh2_userkey *retkey;
const ssh_keyalg *alg;
BinarySource src[1];
int i, num_integers;
struct ssh2_userkey *retval = NULL;
const char *errmsg;
strbuf *blob = strbuf_new();
int privptr = 0, publen;
const char *modptr = NULL;
int modlen = 0;
if (!key)
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);
put_data(&md5c, passphrase, strlen(passphrase));
put_data(&md5c, key->iv, 8);
MD5Final(keybuf, &md5c);
MD5Init(&md5c);
put_data(&md5c, keybuf, 16);
put_data(&md5c, passphrase, strlen(passphrase));
put_data(&md5c, key->iv, 8);
MD5Final(keybuf+16, &md5c);
/*
* Now decrypt the key blob.
*/
if (key->encryption == OP_E_3DES)
des3_decrypt_pubkey_ossh(keybuf, key->iv,
key->keyblob->u, key->keyblob->len);
else {
void *ctx;
assert(key->encryption == OP_E_AES);
ctx = aes_make_context();
aes128_key(ctx, keybuf);
aes_iv(ctx, key->iv);
aes_ssh2_decrypt_blk(ctx, key->keyblob->u, key->keyblob->len);
aes_free_context(ctx);
}
smemclr(&md5c, sizeof(md5c));
smemclr(keybuf, sizeof(keybuf));
}
/*
* Now we have a decrypted key blob, which contains an ASN.1
* encoded private key. We must now untangle the ASN.1.
*
* We expect the whole key blob to be formatted as a SEQUENCE
* (0x30 followed by a length code indicating that the rest of
* the blob is part of the sequence). Within that SEQUENCE we
* expect to see a bunch of INTEGERs. What those integers mean
* depends on the key type:
*
* - For RSA, we expect the integers to be 0, n, e, d, p, q,
* dmp1, dmq1, iqmp in that order. (The last three are d mod
* (p-1), d mod (q-1), inverse of q mod p respectively.)
*
* - For DSA, we expect them to be 0, p, q, g, y, x in that
* order.
*
* - In ECDSA the format is totally different: we see the
* SEQUENCE, but beneath is an INTEGER 1, OCTET STRING priv
* EXPLICIT [0] OID curve, EXPLICIT [1] BIT STRING pubPoint
*/
BinarySource_BARE_INIT(src, key->keyblob->u, key->keyblob->len);
{
/* Expect the SEQUENCE header. Take its absence as a failure to
* decrypt, if the key was encrypted. */
ber_item seq = get_ber(src);
if (get_err(src) || seq.id != 16) {
errmsg = "ASN.1 decoding failure";
retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL;
goto error;
}
/* Reinitialise our BinarySource to parse just the inside of that
* SEQUENCE. */
BinarySource_BARE_INIT(src, seq.data.ptr, seq.data.len);
}
/* Expect a load of INTEGERs. */
if (key->keytype == OP_RSA)
num_integers = 9;
else if (key->keytype == OP_DSA)
num_integers = 6;
else
num_integers = 0; /* placate compiler warnings */
if (key->keytype == OP_ECDSA) {
/* And now for something completely different */
ber_item integer, privkey, sub0, sub1, oid, pubkey;
Invent a struct type for polymorphic SSH key data. During last week's work, I made a mistake in which I got the arguments backwards in one of the key-blob-generating functions - mistakenly swapped the 'void *' key instance with the 'BinarySink *' output destination - and I didn't spot the mistake until run time, because in C you can implicitly convert both to and from void * and so there was no compile-time failure of type checking. Now that I've introduced the FROMFIELD macro that downcasts a pointer to one field of a structure to retrieve a pointer to the whole structure, I think I might start using that more widely to indicate this kind of polymorphic subtyping. So now all the public-key functions in the struct ssh_signkey vtable handle their data instance in the form of a pointer to a subfield of a new zero-sized structure type 'ssh_key', which outside the key implementations indicates 'this is some kind of key instance but it could be of any type'; they downcast that pointer internally using FROMFIELD in place of the previous ordinary C cast, and return one by returning &foo->sshk for whatever foo they've just made up. The sshk member is not at the beginning of the structure, which means all those FROMFIELDs and &key->sshk are actually adding and subtracting an offset. Of course I could have put the member at the start anyway, but I had the idea that it's actually a feature _not_ to have the two types start at the same address, because it means you should notice earlier rather than later if you absentmindedly cast from one to the other directly rather than by the approved method (in particular, if you accidentally assign one through a void * and back without even _noticing_ you perpetrated a cast). In particular, this enforces that you can't sfree() the thing even once without realising you should instead of called the right freekey function. (I found several bugs by this method during initial testing, so I think it's already proved its worth!) While I'm here, I've also renamed the vtable structure ssh_signkey to ssh_keyalg, because it was a confusing name anyway - it describes the _algorithm_ for handling all keys of that type, not a specific key. So ssh_keyalg is the collection of code, and ssh_key is one instance of the data it handles.
2018-05-27 10:32:21 +03:00
const ssh_keyalg *alg;
Clean up elliptic curve selection and naming. The ec_name_to_curve and ec_curve_to_name functions shouldn't really have had to exist at all: whenever any part of the PuTTY codebase starts using sshecc.c, it's starting from an ssh_signkey or ssh_kex pointer already found by some other means. So if we make sure not to lose that pointer, we should never need to do any string-based lookups to find the curve we want, and conversely, when we need to know the name of our curve or our algorithm, we should be able to look it up as a straightforward const char * starting from the algorithm pointer. This commit cleans things up so that that is indeed what happens. The ssh_signkey and ssh_kex structures defined in sshecc.c now have 'extra' fields containing pointers to all the necessary stuff; ec_name_to_curve and ec_curve_to_name have been completely removed; struct ec_curve has a string field giving the curve's name (but only for those curves which _have_ a name exposed in the wire protocol, i.e. the three NIST ones); struct ec_key keeps a pointer to the ssh_signkey it started from, and uses that to remember the algorithm name rather than reconstructing it from the curve. And I think I've got rid of all the ad-hockery scattered around the code that switches on curve->fieldBits or manually constructs curve names using stuff like sprintf("nistp%d"); the only remaining switch on fieldBits (necessary because that's the UI for choosing a curve in PuTTYgen) is at least centralised into one place in sshecc.c. One user-visible result is that the format of ed25519 host keys in the registry has changed: there's now no curve name prefix on them, because I think it's not really right to make up a name to use. So any early adopters who've been using snapshot PuTTY in the last week will be inconvenienced; sorry about that.
2015-05-15 12:13:05 +03:00
const struct ec_curve *curve;
/* Parse the outer layer of things inside the containing SEQUENCE */
integer = get_ber(src);
privkey = get_ber(src);
sub0 = get_ber(src);
sub1 = get_ber(src);
/* Now look inside sub0 for the curve OID */
BinarySource_BARE_INIT(src, sub0.data.ptr, sub0.data.len);
oid = get_ber(src);
/* And inside sub1 for the public-key BIT STRING */
BinarySource_BARE_INIT(src, sub1.data.ptr, sub1.data.len);
pubkey = get_ber(src);
if (get_err(src) ||
integer.id != 2 ||
integer.data.len != 1 ||
((const unsigned char *)integer.data.ptr)[0] != 1 ||
privkey.id != 4 ||
sub0.id != 0 ||
sub1.id != 1 ||
oid.id != 6 ||
pubkey.id != 3) {
errmsg = "ASN.1 decoding failure";
retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL;
goto error;
}
alg = ec_alg_by_oid(oid.data.len, oid.data.ptr, &curve);
Clean up elliptic curve selection and naming. The ec_name_to_curve and ec_curve_to_name functions shouldn't really have had to exist at all: whenever any part of the PuTTY codebase starts using sshecc.c, it's starting from an ssh_signkey or ssh_kex pointer already found by some other means. So if we make sure not to lose that pointer, we should never need to do any string-based lookups to find the curve we want, and conversely, when we need to know the name of our curve or our algorithm, we should be able to look it up as a straightforward const char * starting from the algorithm pointer. This commit cleans things up so that that is indeed what happens. The ssh_signkey and ssh_kex structures defined in sshecc.c now have 'extra' fields containing pointers to all the necessary stuff; ec_name_to_curve and ec_curve_to_name have been completely removed; struct ec_curve has a string field giving the curve's name (but only for those curves which _have_ a name exposed in the wire protocol, i.e. the three NIST ones); struct ec_key keeps a pointer to the ssh_signkey it started from, and uses that to remember the algorithm name rather than reconstructing it from the curve. And I think I've got rid of all the ad-hockery scattered around the code that switches on curve->fieldBits or manually constructs curve names using stuff like sprintf("nistp%d"); the only remaining switch on fieldBits (necessary because that's the UI for choosing a curve in PuTTYgen) is at least centralised into one place in sshecc.c. One user-visible result is that the format of ed25519 host keys in the registry has changed: there's now no curve name prefix on them, because I think it's not really right to make up a name to use. So any early adopters who've been using snapshot PuTTY in the last week will be inconvenienced; sorry about that.
2015-05-15 12:13:05 +03:00
if (!alg) {
errmsg = "Unsupported ECDSA curve.";
retval = NULL;
goto error;
}
if (pubkey.data.len != ((((curve->fieldBits + 7) / 8) * 2) + 2)) {
errmsg = "ASN.1 decoding failure";
retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL;
goto error;
}
/* Skip 0x00 before point */
pubkey.data.ptr = (const char *)pubkey.data.ptr + 1;
pubkey.data.len -= 1;
/* Construct the key */
retkey = snew(struct ssh2_userkey);
Clean up elliptic curve selection and naming. The ec_name_to_curve and ec_curve_to_name functions shouldn't really have had to exist at all: whenever any part of the PuTTY codebase starts using sshecc.c, it's starting from an ssh_signkey or ssh_kex pointer already found by some other means. So if we make sure not to lose that pointer, we should never need to do any string-based lookups to find the curve we want, and conversely, when we need to know the name of our curve or our algorithm, we should be able to look it up as a straightforward const char * starting from the algorithm pointer. This commit cleans things up so that that is indeed what happens. The ssh_signkey and ssh_kex structures defined in sshecc.c now have 'extra' fields containing pointers to all the necessary stuff; ec_name_to_curve and ec_curve_to_name have been completely removed; struct ec_curve has a string field giving the curve's name (but only for those curves which _have_ a name exposed in the wire protocol, i.e. the three NIST ones); struct ec_key keeps a pointer to the ssh_signkey it started from, and uses that to remember the algorithm name rather than reconstructing it from the curve. And I think I've got rid of all the ad-hockery scattered around the code that switches on curve->fieldBits or manually constructs curve names using stuff like sprintf("nistp%d"); the only remaining switch on fieldBits (necessary because that's the UI for choosing a curve in PuTTYgen) is at least centralised into one place in sshecc.c. One user-visible result is that the format of ed25519 host keys in the registry has changed: there's now no curve name prefix on them, because I think it's not really right to make up a name to use. So any early adopters who've been using snapshot PuTTY in the last week will be inconvenienced; sorry about that.
2015-05-15 12:13:05 +03:00
put_stringz(blob, alg->ssh_id);
put_stringz(blob, curve->name);
put_stringpl(blob, pubkey.data);
publen = blob->len;
put_mp_ssh2_from_string(blob, privkey.data.ptr, privkey.data.len);
Clean up elliptic curve selection and naming. The ec_name_to_curve and ec_curve_to_name functions shouldn't really have had to exist at all: whenever any part of the PuTTY codebase starts using sshecc.c, it's starting from an ssh_signkey or ssh_kex pointer already found by some other means. So if we make sure not to lose that pointer, we should never need to do any string-based lookups to find the curve we want, and conversely, when we need to know the name of our curve or our algorithm, we should be able to look it up as a straightforward const char * starting from the algorithm pointer. This commit cleans things up so that that is indeed what happens. The ssh_signkey and ssh_kex structures defined in sshecc.c now have 'extra' fields containing pointers to all the necessary stuff; ec_name_to_curve and ec_curve_to_name have been completely removed; struct ec_curve has a string field giving the curve's name (but only for those curves which _have_ a name exposed in the wire protocol, i.e. the three NIST ones); struct ec_key keeps a pointer to the ssh_signkey it started from, and uses that to remember the algorithm name rather than reconstructing it from the curve. And I think I've got rid of all the ad-hockery scattered around the code that switches on curve->fieldBits or manually constructs curve names using stuff like sprintf("nistp%d"); the only remaining switch on fieldBits (necessary because that's the UI for choosing a curve in PuTTYgen) is at least centralised into one place in sshecc.c. One user-visible result is that the format of ed25519 host keys in the registry has changed: there's now no curve name prefix on them, because I think it's not really right to make up a name to use. So any early adopters who've been using snapshot PuTTY in the last week will be inconvenienced; sorry about that.
2015-05-15 12:13:05 +03:00
retkey->key = ssh_key_new_priv(
alg, make_ptrlen(blob->u, publen),
make_ptrlen(blob->u + publen, blob->len - publen));
Clean up elliptic curve selection and naming. The ec_name_to_curve and ec_curve_to_name functions shouldn't really have had to exist at all: whenever any part of the PuTTY codebase starts using sshecc.c, it's starting from an ssh_signkey or ssh_kex pointer already found by some other means. So if we make sure not to lose that pointer, we should never need to do any string-based lookups to find the curve we want, and conversely, when we need to know the name of our curve or our algorithm, we should be able to look it up as a straightforward const char * starting from the algorithm pointer. This commit cleans things up so that that is indeed what happens. The ssh_signkey and ssh_kex structures defined in sshecc.c now have 'extra' fields containing pointers to all the necessary stuff; ec_name_to_curve and ec_curve_to_name have been completely removed; struct ec_curve has a string field giving the curve's name (but only for those curves which _have_ a name exposed in the wire protocol, i.e. the three NIST ones); struct ec_key keeps a pointer to the ssh_signkey it started from, and uses that to remember the algorithm name rather than reconstructing it from the curve. And I think I've got rid of all the ad-hockery scattered around the code that switches on curve->fieldBits or manually constructs curve names using stuff like sprintf("nistp%d"); the only remaining switch on fieldBits (necessary because that's the UI for choosing a curve in PuTTYgen) is at least centralised into one place in sshecc.c. One user-visible result is that the format of ed25519 host keys in the registry has changed: there's now no curve name prefix on them, because I think it's not really right to make up a name to use. So any early adopters who've been using snapshot PuTTY in the last week will be inconvenienced; sorry about that.
2015-05-15 12:13:05 +03:00
if (!retkey->key) {
sfree(retkey);
errmsg = "unable to create key data structure";
goto error;
}
} else if (key->keytype == OP_RSA || key->keytype == OP_DSA) {
put_stringz(blob, key->keytype == OP_DSA ? "ssh-dss" : "ssh-rsa");
for (i = 0; i < num_integers; i++) {
ber_item integer = get_ber(src);
if (get_err(src) || integer.id != 2) {
errmsg = "ASN.1 decoding failure";
retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL;
goto error;
}
if (i == 0) {
/*
* The first integer should be zero always (I think
* this is some sort of version indication).
*/
if (integer.data.len != 1 ||
((const unsigned char *)integer.data.ptr)[0] != 0) {
errmsg = "version number mismatch";
goto error;
}
} else if (key->keytype == OP_RSA) {
/*
* Integers 1 and 2 go into the public blob but in the
* opposite order; integers 3, 4, 5 and 8 go into the
* private blob. The other two (6 and 7) are ignored.
*/
if (i == 1) {
/* Save the details for after we deal with number 2. */
modptr = integer.data.ptr;
modlen = integer.data.len;
} else if (i != 6 && i != 7) {
put_mp_ssh2_from_string(blob, integer.data.ptr,
integer.data.len);
if (i == 2) {
put_mp_ssh2_from_string(blob, modptr, modlen);
privptr = blob->len;
}
}
} else if (key->keytype == OP_DSA) {
/*
* Integers 1-4 go into the public blob; integer 5 goes
* into the private blob.
*/
put_mp_ssh2_from_string(blob, integer.data.ptr,
integer.data.len);
if (i == 4)
privptr = blob->len;
}
}
/*
* Now put together the actual key. Simplest way to do this is
* to assemble our own key blobs and feed them to the createkey
* functions; this is a bit faffy but it does mean we get all
* the sanity checks for free.
*/
assert(privptr > 0); /* should have bombed by now if not */
retkey = snew(struct ssh2_userkey);
alg = (key->keytype == OP_RSA ? &ssh_rsa : &ssh_dss);
retkey->key = ssh_key_new_priv(
alg, make_ptrlen(blob->u, privptr),
make_ptrlen(blob->u+privptr, blob->len-privptr));
if (!retkey->key) {
sfree(retkey);
errmsg = "unable to create key data structure";
goto error;
}
} else {
assert(0 && "Bad key type from load_openssh_pem_key");
errmsg = "Bad key type from load_openssh_pem_key";
goto error;
}
/*
* The old key format doesn't include a comment in the private
* key file.
*/
retkey->comment = dupstr("imported-openssh-key");
errmsg = NULL; /* no error */
retval = retkey;
error:
strbuf_free(blob);
strbuf_free(key->keyblob);
smemclr(key, sizeof(*key));
sfree(key);
if (errmsg_p) *errmsg_p = errmsg;
return retval;
}
int openssh_pem_write(const Filename *filename, struct ssh2_userkey *key,
char *passphrase)
{
strbuf *pubblob, *privblob, *outblob;
unsigned char *spareblob;
int sparelen = 0;
ptrlen numbers[9];
int nnumbers, i;
const char *header, *footer;
char zero[1];
unsigned char iv[8];
int ret = 0;
FILE *fp;
BinarySource src[1];
/*
* Fetch the key blobs.
*/
pubblob = strbuf_new();
ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob));
privblob = strbuf_new();
ssh_key_private_blob(key->key, BinarySink_UPCAST(privblob));
spareblob = NULL;
outblob = strbuf_new();
/*
* Encode the OpenSSH key blob, and also decide on the header
* line.
*/
if (ssh_key_alg(key->key) == &ssh_rsa ||
ssh_key_alg(key->key) == &ssh_dss) {
strbuf *seq;
/*
* The RSA and DSS handlers share some code because the two
* key types have very similar ASN.1 representations, as a
* plain SEQUENCE of big integers. So we set up a list of
* bignums per key type and then construct the actual blob in
* common code after that.
*/
if (ssh_key_alg(key->key) == &ssh_rsa) {
ptrlen n, e, d, p, q, iqmp, dmp1, dmq1;
Bignum bd, bp, bq, bdmp1, bdmq1;
/*
* These blobs were generated from inside PuTTY, so we needn't
* treat them as untrusted.
*/
BinarySource_BARE_INIT(src, pubblob->u, pubblob->len);
get_string(src); /* skip algorithm name */
e = get_string(src);
n = get_string(src);
BinarySource_BARE_INIT(src, privblob->u, privblob->len);
d = get_string(src);
p = get_string(src);
q = get_string(src);
iqmp = get_string(src);
assert(!get_err(src)); /* can't go wrong */
/* We also need d mod (p-1) and d mod (q-1). */
bd = bignum_from_bytes(d.ptr, d.len);
bp = bignum_from_bytes(p.ptr, p.len);
bq = bignum_from_bytes(q.ptr, q.len);
decbn(bp);
decbn(bq);
bdmp1 = bigmod(bd, bp);
bdmq1 = bigmod(bd, bq);
freebn(bd);
freebn(bp);
freebn(bq);
dmp1.len = (bignum_bitcount(bdmp1)+8)/8;
dmq1.len = (bignum_bitcount(bdmq1)+8)/8;
sparelen = dmp1.len + dmq1.len;
spareblob = snewn(sparelen, unsigned char);
dmp1.ptr = spareblob;
dmq1.ptr = spareblob + dmp1.len;
for (i = 0; i < dmp1.len; i++)
spareblob[i] = bignum_byte(bdmp1, dmp1.len-1 - i);
for (i = 0; i < dmq1.len; i++)
spareblob[i+dmp1.len] = bignum_byte(bdmq1, dmq1.len-1 - i);
freebn(bdmp1);
freebn(bdmq1);
numbers[0] = make_ptrlen(zero, 1); zero[0] = '\0';
numbers[1] = n;
numbers[2] = e;
numbers[3] = d;
numbers[4] = p;
numbers[5] = q;
numbers[6] = dmp1;
numbers[7] = dmq1;
numbers[8] = iqmp;
nnumbers = 9;
header = "-----BEGIN RSA PRIVATE KEY-----\n";
footer = "-----END RSA PRIVATE KEY-----\n";
} else { /* ssh-dss */
ptrlen p, q, g, y, x;
/*
* These blobs were generated from inside PuTTY, so we needn't
* treat them as untrusted.
*/
BinarySource_BARE_INIT(src, pubblob->u, pubblob->len);
get_string(src); /* skip algorithm name */
p = get_string(src);
q = get_string(src);
g = get_string(src);
y = get_string(src);
BinarySource_BARE_INIT(src, privblob->u, privblob->len);
x = get_string(src);
assert(!get_err(src)); /* can't go wrong */
numbers[0].ptr = zero; numbers[0].len = 1; zero[0] = '\0';
numbers[1] = p;
numbers[2] = q;
numbers[3] = g;
numbers[4] = y;
numbers[5] = x;
nnumbers = 6;
header = "-----BEGIN DSA PRIVATE KEY-----\n";
footer = "-----END DSA PRIVATE KEY-----\n";
}
seq = strbuf_new();
for (i = 0; i < nnumbers; i++) {
put_ber_id_len(seq, 2, numbers[i].len, 0);
put_data(seq, numbers[i].ptr, numbers[i].len);
}
put_ber_id_len(outblob, 16, seq->len, ASN1_CONSTRUCTED);
put_data(outblob, seq->s, seq->len);
strbuf_free(seq);
} else if (ssh_key_alg(key->key) == &ssh_ecdsa_nistp256 ||
ssh_key_alg(key->key) == &ssh_ecdsa_nistp384 ||
ssh_key_alg(key->key) == &ssh_ecdsa_nistp521) {
Clean up elliptic curve selection and naming. The ec_name_to_curve and ec_curve_to_name functions shouldn't really have had to exist at all: whenever any part of the PuTTY codebase starts using sshecc.c, it's starting from an ssh_signkey or ssh_kex pointer already found by some other means. So if we make sure not to lose that pointer, we should never need to do any string-based lookups to find the curve we want, and conversely, when we need to know the name of our curve or our algorithm, we should be able to look it up as a straightforward const char * starting from the algorithm pointer. This commit cleans things up so that that is indeed what happens. The ssh_signkey and ssh_kex structures defined in sshecc.c now have 'extra' fields containing pointers to all the necessary stuff; ec_name_to_curve and ec_curve_to_name have been completely removed; struct ec_curve has a string field giving the curve's name (but only for those curves which _have_ a name exposed in the wire protocol, i.e. the three NIST ones); struct ec_key keeps a pointer to the ssh_signkey it started from, and uses that to remember the algorithm name rather than reconstructing it from the curve. And I think I've got rid of all the ad-hockery scattered around the code that switches on curve->fieldBits or manually constructs curve names using stuff like sprintf("nistp%d"); the only remaining switch on fieldBits (necessary because that's the UI for choosing a curve in PuTTYgen) is at least centralised into one place in sshecc.c. One user-visible result is that the format of ed25519 host keys in the registry has changed: there's now no curve name prefix on them, because I think it's not really right to make up a name to use. So any early adopters who've been using snapshot PuTTY in the last week will be inconvenienced; sorry about that.
2015-05-15 12:13:05 +03:00
const unsigned char *oid;
struct ec_key *ec = FROMFIELD(key->key, struct ec_key, sshk);
int oidlen;
int pointlen;
strbuf *seq, *sub;
/*
* Structure of asn1:
* SEQUENCE
* INTEGER 1
* OCTET STRING (private key)
* [0]
* OID (curve)
* [1]
* BIT STRING (0x00 public key point)
*/
oid = ec_alg_oid(ssh_key_alg(key->key), &oidlen);
Invent a struct type for polymorphic SSH key data. During last week's work, I made a mistake in which I got the arguments backwards in one of the key-blob-generating functions - mistakenly swapped the 'void *' key instance with the 'BinarySink *' output destination - and I didn't spot the mistake until run time, because in C you can implicitly convert both to and from void * and so there was no compile-time failure of type checking. Now that I've introduced the FROMFIELD macro that downcasts a pointer to one field of a structure to retrieve a pointer to the whole structure, I think I might start using that more widely to indicate this kind of polymorphic subtyping. So now all the public-key functions in the struct ssh_signkey vtable handle their data instance in the form of a pointer to a subfield of a new zero-sized structure type 'ssh_key', which outside the key implementations indicates 'this is some kind of key instance but it could be of any type'; they downcast that pointer internally using FROMFIELD in place of the previous ordinary C cast, and return one by returning &foo->sshk for whatever foo they've just made up. The sshk member is not at the beginning of the structure, which means all those FROMFIELDs and &key->sshk are actually adding and subtracting an offset. Of course I could have put the member at the start anyway, but I had the idea that it's actually a feature _not_ to have the two types start at the same address, because it means you should notice earlier rather than later if you absentmindedly cast from one to the other directly rather than by the approved method (in particular, if you accidentally assign one through a void * and back without even _noticing_ you perpetrated a cast). In particular, this enforces that you can't sfree() the thing even once without realising you should instead of called the right freekey function. (I found several bugs by this method during initial testing, so I think it's already proved its worth!) While I'm here, I've also renamed the vtable structure ssh_signkey to ssh_keyalg, because it was a confusing name anyway - it describes the _algorithm_ for handling all keys of that type, not a specific key. So ssh_keyalg is the collection of code, and ssh_key is one instance of the data it handles.
2018-05-27 10:32:21 +03:00
pointlen = (ec->publicKey.curve->fieldBits + 7) / 8 * 2;
seq = strbuf_new();
/* INTEGER 1 */
put_ber_id_len(seq, 2, 1, 0);
put_byte(seq, 1);
/* OCTET STRING private key */
put_ber_id_len(seq, 4, privblob->len - 4, 0);
put_data(seq, privblob->s + 4, privblob->len - 4);
/* Subsidiary OID */
sub = strbuf_new();
put_ber_id_len(sub, 6, oidlen, 0);
put_data(sub, oid, oidlen);
/* Append the OID to the sequence */
put_ber_id_len(seq, 0, sub->len,
ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED);
put_data(seq, sub->s, sub->len);
strbuf_free(sub);
/* Subsidiary BIT STRING */
sub = strbuf_new();
put_ber_id_len(sub, 3, 2 + pointlen, 0);
put_byte(sub, 0);
put_data(sub, pubblob->s+39, 1 + pointlen);
/* Append the BIT STRING to the sequence */
put_ber_id_len(seq, 1, sub->len,
ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED);
put_data(seq, sub->s, sub->len);
strbuf_free(sub);
/* Write the full sequence with header to the output blob. */
put_ber_id_len(outblob, 16, seq->len, ASN1_CONSTRUCTED);
put_data(outblob, seq->s, seq->len);
strbuf_free(seq);
header = "-----BEGIN EC PRIVATE KEY-----\n";
footer = "-----END EC PRIVATE KEY-----\n";
} else {
assert(0); /* zoinks! */
exit(1); /* XXX: GCC doesn't understand assert() on some systems. */
}
/*
* Encrypt the key.
*
* For the moment, we still encrypt our OpenSSH keys using
* old-style 3DES.
*/
if (passphrase) {
struct MD5Context md5c;
unsigned char keybuf[32];
int origlen, outlen, pad, i;
/*
* Padding on OpenSSH keys is deterministic. The number of
* padding bytes is always more than zero, and always at most
* the cipher block length. The value of each padding byte is
* equal to the number of padding bytes. So a plaintext that's
* an exact multiple of the block size will be padded with 08
* 08 08 08 08 08 08 08 (assuming a 64-bit block cipher); a
* plaintext one byte less than a multiple of the block size
* will be padded with just 01.
*
* This enables the OpenSSL key decryption function to strip
* off the padding algorithmically and return the unpadded
* plaintext to the next layer: it looks at the final byte, and
* then expects to find that many bytes at the end of the data
* with the same value. Those are all removed and the rest is
* returned.
*/
origlen = outblob->len;
outlen = (origlen + 8) &~ 7;
pad = outlen - origlen;
for (i = 0; i < pad; i++)
put_byte(outblob, pad);
/*
* Invent an iv. Then 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
*/
for (i = 0; i < 8; i++) iv[i] = random_byte();
MD5Init(&md5c);
put_data(&md5c, passphrase, strlen(passphrase));
put_data(&md5c, iv, 8);
MD5Final(keybuf, &md5c);
MD5Init(&md5c);
put_data(&md5c, keybuf, 16);
put_data(&md5c, passphrase, strlen(passphrase));
put_data(&md5c, iv, 8);
MD5Final(keybuf+16, &md5c);
/*
* Now encrypt the key blob.
*/
des3_encrypt_pubkey_ossh(keybuf, iv,
outblob->u, outlen);
smemclr(&md5c, sizeof(md5c));
smemclr(keybuf, sizeof(keybuf));
}
/*
* And save it. We'll use Unix line endings just in case it's
* subsequently transferred in binary mode.
*/
fp = f_open(filename, "wb", TRUE); /* ensure Unix line endings */
if (!fp)
goto error;
fputs(header, fp);
if (passphrase) {
fprintf(fp, "Proc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC,");
for (i = 0; i < 8; i++)
fprintf(fp, "%02X", iv[i]);
fprintf(fp, "\n\n");
}
base64_encode(fp, outblob->u, outblob->len, 64);
fputs(footer, fp);
fclose(fp);
ret = 1;
error:
if (outblob)
strbuf_free(outblob);
if (spareblob) {
smemclr(spareblob, sparelen);
sfree(spareblob);
}
if (privblob)
strbuf_free(privblob);
if (pubblob)
strbuf_free(pubblob);
return ret;
}
/* ----------------------------------------------------------------------
* Code to read and write OpenSSH private keys in the new-style format.
*/
typedef enum {
ON_E_NONE, ON_E_AES256CBC, ON_E_AES256CTR
} openssh_new_cipher;
typedef enum {
ON_K_NONE, ON_K_BCRYPT
} openssh_new_kdf;
struct openssh_new_key {
openssh_new_cipher cipher;
openssh_new_kdf kdf;
union {
struct {
int rounds;
/* This points to a position within keyblob, not a
* separately allocated thing */
ptrlen salt;
} bcrypt;
} kdfopts;
int nkeys, key_wanted;
/* This too points to a position within keyblob */
ptrlen private;
unsigned char *keyblob;
int keyblob_len, keyblob_size;
};
static struct openssh_new_key *load_openssh_new_key(const Filename *filename,
const char **errmsg_p)
{
struct openssh_new_key *ret;
FILE *fp = NULL;
char *line = NULL;
const char *errmsg;
char *p;
char base64_bit[4];
int base64_chars = 0;
BinarySource src[1];
ptrlen str;
unsigned key_index;
ret = snew(struct openssh_new_key);
ret->keyblob = NULL;
ret->keyblob_len = ret->keyblob_size = 0;
fp = f_open(filename, "r", FALSE);
if (!fp) {
errmsg = "unable to open key file";
goto error;
}
if (!(line = fgetline(fp))) {
errmsg = "unexpected end of file";
goto error;
}
strip_crlf(line);
if (0 != strcmp(line, "-----BEGIN OPENSSH PRIVATE KEY-----")) {
errmsg = "file does not begin with OpenSSH new-style key header";
goto error;
}
smemclr(line, strlen(line));
sfree(line);
line = NULL;
while (1) {
if (!(line = fgetline(fp))) {
errmsg = "unexpected end of file";
goto error;
}
strip_crlf(line);
if (0 == strcmp(line, "-----END OPENSSH PRIVATE KEY-----")) {
sfree(line);
line = NULL;
break; /* done */
}
p = line;
while (isbase64(*p)) {
base64_bit[base64_chars++] = *p;
if (base64_chars == 4) {
unsigned char out[3];
int len;
base64_chars = 0;
len = base64_decode_atom(base64_bit, out);
if (len <= 0) {
errmsg = "invalid base64 encoding";
goto error;
}
if (ret->keyblob_len + len > ret->keyblob_size) {
ret->keyblob_size = ret->keyblob_len + len + 256;
ret->keyblob = sresize(ret->keyblob, ret->keyblob_size,
unsigned char);
}
memcpy(ret->keyblob + ret->keyblob_len, out, len);
ret->keyblob_len += len;
smemclr(out, sizeof(out));
}
p++;
}
smemclr(line, strlen(line));
sfree(line);
line = NULL;
}
fclose(fp);
fp = NULL;
if (ret->keyblob_len == 0 || !ret->keyblob) {
errmsg = "key body not present";
goto error;
}
BinarySource_BARE_INIT(src, ret->keyblob, ret->keyblob_len);
if (strcmp(get_asciz(src), "openssh-key-v1") != 0) {
errmsg = "new-style OpenSSH magic number missing\n";
goto error;
}
/* Cipher name */
str = get_string(src);
if (ptrlen_eq_string(str, "none")) {
ret->cipher = ON_E_NONE;
} else if (ptrlen_eq_string(str, "aes256-cbc")) {
ret->cipher = ON_E_AES256CBC;
} else if (ptrlen_eq_string(str, "aes256-ctr")) {
ret->cipher = ON_E_AES256CTR;
} else {
errmsg = get_err(src) ? "no cipher name found" :
"unrecognised cipher name\n";
goto error;
}
/* Key derivation function name */
str = get_string(src);
if (ptrlen_eq_string(str, "none")) {
ret->kdf = ON_K_NONE;
} else if (ptrlen_eq_string(str, "bcrypt")) {
ret->kdf = ON_K_BCRYPT;
} else {
errmsg = get_err(src) ? "no kdf name found" :
"unrecognised kdf name\n";
goto error;
}
/* KDF extra options */
str = get_string(src);
switch (ret->kdf) {
case ON_K_NONE:
if (str.len != 0) {
errmsg = "expected empty options string for 'none' kdf";
goto error;
}
break;
case ON_K_BCRYPT:
{
BinarySource opts[1];
BinarySource_BARE_INIT(opts, str.ptr, str.len);
ret->kdfopts.bcrypt.salt = get_string(opts);
ret->kdfopts.bcrypt.rounds = get_uint32(opts);
if (get_err(opts)) {
errmsg = "failed to parse bcrypt options string";
goto error;
}
}
break;
}
/*
* 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.
*/
ret->nkeys = toint(get_uint32(src));
if (ret->nkeys != 1) {
errmsg = get_err(src) ? "no key count found" :
"multiple keys in new-style OpenSSH key file not supported\n";
goto error;
}
ret->key_wanted = 0;
/* Read and ignore a string per public key. */
for (key_index = 0; key_index < ret->nkeys; key_index++)
str = get_string(src);
/*
* Now we expect a string containing the encrypted part of the
* key file.
*/
ret->private = get_string(src);
if (get_err(src)) {
errmsg = "no private key container string found\n";
goto error;
}
/*
* And now we're done, until asked to actually decrypt.
*/
smemclr(base64_bit, sizeof(base64_bit));
if (errmsg_p) *errmsg_p = NULL;
return ret;
error:
if (line) {
smemclr(line, strlen(line));
sfree(line);
line = NULL;
}
smemclr(base64_bit, sizeof(base64_bit));
if (ret) {
if (ret->keyblob) {
smemclr(ret->keyblob, ret->keyblob_size);
sfree(ret->keyblob);
}
smemclr(ret, sizeof(*ret));
sfree(ret);
}
if (errmsg_p) *errmsg_p = errmsg;
if (fp) fclose(fp);
return NULL;
}
int openssh_new_encrypted(const Filename *filename)
{
struct openssh_new_key *key = load_openssh_new_key(filename, NULL);
int ret;
if (!key)
return 0;
ret = (key->cipher != ON_E_NONE);
smemclr(key->keyblob, key->keyblob_size);
sfree(key->keyblob);
smemclr(key, sizeof(*key));
sfree(key);
return ret;
}
struct ssh2_userkey *openssh_new_read(const Filename *filename,
char *passphrase,
const char **errmsg_p)
{
struct openssh_new_key *key = load_openssh_new_key(filename, errmsg_p);
struct ssh2_userkey *retkey = NULL;
struct ssh2_userkey *retval = NULL;
const char *errmsg;
unsigned checkint;
BinarySource src[1];
int key_index;
Invent a struct type for polymorphic SSH key data. During last week's work, I made a mistake in which I got the arguments backwards in one of the key-blob-generating functions - mistakenly swapped the 'void *' key instance with the 'BinarySink *' output destination - and I didn't spot the mistake until run time, because in C you can implicitly convert both to and from void * and so there was no compile-time failure of type checking. Now that I've introduced the FROMFIELD macro that downcasts a pointer to one field of a structure to retrieve a pointer to the whole structure, I think I might start using that more widely to indicate this kind of polymorphic subtyping. So now all the public-key functions in the struct ssh_signkey vtable handle their data instance in the form of a pointer to a subfield of a new zero-sized structure type 'ssh_key', which outside the key implementations indicates 'this is some kind of key instance but it could be of any type'; they downcast that pointer internally using FROMFIELD in place of the previous ordinary C cast, and return one by returning &foo->sshk for whatever foo they've just made up. The sshk member is not at the beginning of the structure, which means all those FROMFIELDs and &key->sshk are actually adding and subtracting an offset. Of course I could have put the member at the start anyway, but I had the idea that it's actually a feature _not_ to have the two types start at the same address, because it means you should notice earlier rather than later if you absentmindedly cast from one to the other directly rather than by the approved method (in particular, if you accidentally assign one through a void * and back without even _noticing_ you perpetrated a cast). In particular, this enforces that you can't sfree() the thing even once without realising you should instead of called the right freekey function. (I found several bugs by this method during initial testing, so I think it's already proved its worth!) While I'm here, I've also renamed the vtable structure ssh_signkey to ssh_keyalg, because it was a confusing name anyway - it describes the _algorithm_ for handling all keys of that type, not a specific key. So ssh_keyalg is the collection of code, and ssh_key is one instance of the data it handles.
2018-05-27 10:32:21 +03:00
const ssh_keyalg *alg = NULL;
if (!key)
return NULL;
if (key->cipher != ON_E_NONE) {
unsigned char keybuf[48];
int keysize;
/*
* Construct the decryption key, and decrypt the string.
*/
switch (key->cipher) {
case ON_E_NONE:
keysize = 0;
break;
case ON_E_AES256CBC:
case ON_E_AES256CTR:
keysize = 48; /* 32 byte key + 16 byte IV */
break;
default:
assert(0 && "Bad cipher enumeration value");
}
assert(keysize <= sizeof(keybuf));
switch (key->kdf) {
case ON_K_NONE:
memset(keybuf, 0, keysize);
break;
case ON_K_BCRYPT:
openssh_bcrypt(passphrase,
key->kdfopts.bcrypt.salt.ptr,
key->kdfopts.bcrypt.salt.len,
key->kdfopts.bcrypt.rounds,
keybuf, keysize);
break;
default:
assert(0 && "Bad kdf enumeration value");
}
switch (key->cipher) {
case ON_E_NONE:
break;
case ON_E_AES256CBC:
case ON_E_AES256CTR:
if (key->private.len % 16 != 0) {
errmsg = "private key container length is not a"
" multiple of AES block size\n";
goto error;
}
{
void *ctx = aes_make_context();
aes256_key(ctx, keybuf);
aes_iv(ctx, keybuf + 32);
/* Decrypt the private section in place, casting away
* the const from key->private being a ptrlen */
if (key->cipher == ON_E_AES256CBC) {
aes_ssh2_decrypt_blk(ctx, (char *)key->private.ptr,
key->private.len);
}
else {
aes_ssh2_sdctr(ctx, (char *)key->private.ptr,
key->private.len);
}
aes_free_context(ctx);
}
break;
default:
assert(0 && "Bad cipher enumeration value");
}
}
/*
* Now parse the entire encrypted section, and extract the key
* identified by key_wanted.
*/
BinarySource_BARE_INIT(src, key->private.ptr, key->private.len);
checkint = get_uint32(src);
if (get_uint32(src) != checkint || get_err(src)) {
errmsg = "decryption check failed";
goto error;
}
retkey = snew(struct ssh2_userkey);
retkey->key = NULL;
retkey->comment = NULL;
for (key_index = 0; key_index < key->nkeys; key_index++) {
ptrlen comment;
/*
* Identify the key type.
*/
alg = find_pubkey_alg_len(get_string(src));
if (!alg) {
errmsg = "private key type not recognised\n";
goto error;
}
/*
* Read the key. We have to do this even if it's not the one
* we want, because it's the only way to find out how much
* data to skip past to get to the next key in the file.
*/
retkey->key = ssh_key_new_priv_openssh(alg, src);
if (get_err(src)) {
errmsg = "unable to read entire private key";
goto error;
}
if (!retkey->key) {
errmsg = "unable to create key data structure";
goto error;
}
if (key_index != key->key_wanted) {
/*
* If this isn't the key we're looking for, throw it away.
*/
ssh_key_free(retkey->key);
retkey->key = NULL;
}
/*
* Read the key comment.
*/
comment = get_string(src);
if (get_err(src)) {
errmsg = "unable to read key comment";
goto error;
}
if (key_index == key->key_wanted) {
assert(retkey);
retkey->comment = mkstr(comment);
}
}
if (!retkey) {
errmsg = "key index out of range";
goto error;
}
/*
* Now we expect nothing left but padding.
*/
{
unsigned char expected_pad_byte = 1;
while (get_avail(src) > 0)
if (get_byte(src) != expected_pad_byte++) {
errmsg = "padding at end of private string did not match";
goto error;
}
}
errmsg = NULL; /* no error */
retval = retkey;
retkey = NULL; /* prevent the free */
error:
if (retkey) {
sfree(retkey->comment);
if (retkey->key)
ssh_key_free(retkey->key);
sfree(retkey);
}
smemclr(key->keyblob, key->keyblob_size);
sfree(key->keyblob);
smemclr(key, sizeof(*key));
sfree(key);
if (errmsg_p) *errmsg_p = errmsg;
return retval;
}
int openssh_new_write(const Filename *filename, struct ssh2_userkey *key,
char *passphrase)
{
strbuf *pubblob, *privblob, *cblob;
int padvalue, i;
unsigned checkint;
int ret = 0;
unsigned char bcrypt_salt[16];
const int bcrypt_rounds = 16;
FILE *fp;
/*
* Fetch the key blobs and find out the lengths of things.
*/
pubblob = strbuf_new();
ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob));
privblob = strbuf_new();
ssh_key_openssh_blob(key->key, BinarySink_UPCAST(privblob));
/*
* Construct the cleartext version of the blob.
*/
cblob = strbuf_new();
/* Magic number. */
put_asciz(cblob, "openssh-key-v1");
/* Cipher and kdf names, and kdf options. */
if (!passphrase) {
memset(bcrypt_salt, 0, sizeof(bcrypt_salt)); /* prevent warnings */
put_stringz(cblob, "none");
put_stringz(cblob, "none");
put_stringz(cblob, "");
} else {
strbuf *substr;
for (i = 0; i < (int)sizeof(bcrypt_salt); i++)
bcrypt_salt[i] = random_byte();
put_stringz(cblob, "aes256-ctr");
put_stringz(cblob, "bcrypt");
substr = strbuf_new();
put_string(substr, bcrypt_salt, sizeof(bcrypt_salt));
put_uint32(substr, bcrypt_rounds);
put_stringsb(cblob, substr);
}
/* Number of keys. */
put_uint32(cblob, 1);
/* Public blob. */
put_string(cblob, pubblob->s, pubblob->len);
/* Private section. */
{
strbuf *cpblob = strbuf_new();
/* checkint. */
checkint = 0;
for (i = 0; i < 4; i++)
checkint = (checkint << 8) + random_byte();
put_uint32(cpblob, checkint);
put_uint32(cpblob, checkint);
/* Private key. The main private blob goes inline, with no string
* wrapper. */
put_stringz(cpblob, ssh_key_ssh_id(key->key));
put_data(cpblob, privblob->s, privblob->len);
/* Comment. */
put_stringz(cpblob, key->comment);
/* Pad out the encrypted section. */
padvalue = 1;
do {
put_byte(cpblob, padvalue++);
} while (cpblob->len & 15);
if (passphrase) {
/*
* Encrypt the private section. We need 48 bytes of key
* material: 32 bytes AES key + 16 bytes iv.
*/
unsigned char keybuf[48];
void *ctx;
openssh_bcrypt(passphrase,
bcrypt_salt, sizeof(bcrypt_salt), bcrypt_rounds,
keybuf, sizeof(keybuf));
ctx = aes_make_context();
aes256_key(ctx, keybuf);
aes_iv(ctx, keybuf + 32);
aes_ssh2_sdctr(ctx, cpblob->u,
cpblob->len);
aes_free_context(ctx);
smemclr(keybuf, sizeof(keybuf));
}
put_stringsb(cblob, cpblob);
}
/*
* And save it. We'll use Unix line endings just in case it's
* subsequently transferred in binary mode.
*/
fp = f_open(filename, "wb", TRUE); /* ensure Unix line endings */
if (!fp)
goto error;
fputs("-----BEGIN OPENSSH PRIVATE KEY-----\n", fp);
base64_encode(fp, cblob->u, cblob->len, 64);
fputs("-----END OPENSSH PRIVATE KEY-----\n", fp);
fclose(fp);
ret = 1;
error:
if (cblob)
strbuf_free(cblob);
if (privblob)
strbuf_free(privblob);
if (pubblob)
strbuf_free(pubblob);
return ret;
}
/* ----------------------------------------------------------------------
* The switch function openssh_auto_write(), which chooses one of the
* concrete OpenSSH output formats based on the key type.
*/
int openssh_auto_write(const Filename *filename, struct ssh2_userkey *key,
char *passphrase)
{
/*
* The old OpenSSH format supports a fixed list of key types. We
* assume that anything not in that fixed list is newer, and hence
* will use the new format.
*/
if (ssh_key_alg(key->key) == &ssh_dss ||
ssh_key_alg(key->key) == &ssh_rsa ||
ssh_key_alg(key->key) == &ssh_ecdsa_nistp256 ||
ssh_key_alg(key->key) == &ssh_ecdsa_nistp384 ||
ssh_key_alg(key->key) == &ssh_ecdsa_nistp521)
return openssh_pem_write(filename, key, passphrase);
else
return openssh_new_write(filename, key, passphrase);
}
/* ----------------------------------------------------------------------
* Code to read ssh.com private keys.
*/
/*
* The format of the base64 blob is largely SSH-2-packet-formatted,
* except that mpints are a bit different: they're more like the
* old SSH-1 mpint. You have a 32-bit bit count N, followed by
* (N+7)/8 bytes of data.
*
* So. The blob contains:
*
* - uint32 0x3f6ff9eb (magic number)
* - uint32 size (total blob size)
* - string key-type (see below)
* - string cipher-type (tells you if key is encrypted)
* - string encrypted-blob
*
* (The first size field includes the size field itself and the
* magic number before it. All other size fields are ordinary SSH-2
* strings, so the size field indicates how much data is to
* _follow_.)
*
* The encrypted blob, once decrypted, contains a single string
* which in turn contains the payload. (This allows padding to be
* added after that string while still making it clear where the
* real payload ends. Also it probably makes for a reasonable
* decryption check.)
*
* The payload blob, for an RSA key, contains:
* - mpint e
* - mpint d
* - mpint n (yes, the public and private stuff is intermixed)
* - mpint u (presumably inverse of p mod q)
* - mpint p (p is the smaller prime)
* - mpint q (q is the larger)
*
* For a DSA key, the payload blob contains:
* - uint32 0
* - mpint p
* - mpint g
* - mpint q
* - mpint y
* - mpint x
*
* Alternatively, if the parameters are `predefined', that
* (0,p,g,q) sequence can be replaced by a uint32 1 and a string
* containing some predefined parameter specification. *shudder*,
* but I doubt we'll encounter this in real life.
*
* The key type strings are ghastly. The RSA key I looked at had a
* type string of
*
* `if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}'
*
* and the DSA key wasn't much better:
*
* `dl-modp{sign{dsa-nist-sha1},dh{plain}}'
*
* It isn't clear that these will always be the same. I think it
* might be wise just to look at the `if-modn{sign{rsa' and
* `dl-modp{sign{dsa' prefixes.
*
* Finally, the encryption. The cipher-type string appears to be
* either `none' or `3des-cbc'. Looks as if this is SSH-2-style
* 3des-cbc (i.e. outer cbc rather than inner). The key is created
* from the passphrase by means of yet another hashing faff:
*
* - first 16 bytes are MD5(passphrase)
* - next 16 bytes are MD5(passphrase || first 16 bytes)
* - if there were more, they'd be MD5(passphrase || first 32),
* and so on.
*/
#define SSHCOM_MAGIC_NUMBER 0x3f6ff9eb
struct sshcom_key {
char comment[256]; /* allowing any length is overkill */
unsigned char *keyblob;
int keyblob_len, keyblob_size;
};
static struct sshcom_key *load_sshcom_key(const Filename *filename,
const char **errmsg_p)
{
struct sshcom_key *ret;
FILE *fp;
char *line = NULL;
int hdrstart, len;
const char *errmsg;
char *p;
int headers_done;
char base64_bit[4];
int base64_chars = 0;
ret = snew(struct sshcom_key);
ret->comment[0] = '\0';
ret->keyblob = NULL;
ret->keyblob_len = ret->keyblob_size = 0;
fp = f_open(filename, "r", FALSE);
if (!fp) {
errmsg = "unable to open key file";
goto error;
}
if (!(line = fgetline(fp))) {
errmsg = "unexpected end of file";
goto error;
}
strip_crlf(line);
if (0 != strcmp(line, "---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----")) {
errmsg = "file does not begin with ssh.com key header";
goto error;
}
smemclr(line, strlen(line));
sfree(line);
line = NULL;
headers_done = 0;
while (1) {
if (!(line = fgetline(fp))) {
errmsg = "unexpected end of file";
goto error;
}
strip_crlf(line);
if (!strcmp(line, "---- END SSH2 ENCRYPTED PRIVATE KEY ----")) {
sfree(line);
line = NULL;
break; /* done */
}
if ((p = strchr(line, ':')) != NULL) {
if (headers_done) {
errmsg = "header found in body of key data";
goto error;
}
*p++ = '\0';
while (*p && isspace((unsigned char)*p)) p++;
hdrstart = p - line;
/*
* Header lines can end in a trailing backslash for
* continuation.
*/
len = hdrstart + strlen(line+hdrstart);
assert(!line[len]);
while (line[len-1] == '\\') {
char *line2;
int line2len;
line2 = fgetline(fp);
if (!line2) {
errmsg = "unexpected end of file";
goto error;
}
strip_crlf(line2);
line2len = strlen(line2);
line = sresize(line, len + line2len + 1, char);
strcpy(line + len - 1, line2);
len += line2len - 1;
assert(!line[len]);
smemclr(line2, strlen(line2));
sfree(line2);
line2 = NULL;
}
p = line + hdrstart;
strip_crlf(p);
if (!strcmp(line, "Comment")) {
/* Strip quotes in comment if present. */
if (p[0] == '"' && p[strlen(p)-1] == '"') {
p++;
p[strlen(p)-1] = '\0';
}
strncpy(ret->comment, p, sizeof(ret->comment));
ret->comment[sizeof(ret->comment)-1] = '\0';
}
} else {
headers_done = 1;
p = line;
while (isbase64(*p)) {
base64_bit[base64_chars++] = *p;
if (base64_chars == 4) {
unsigned char out[3];
base64_chars = 0;
len = base64_decode_atom(base64_bit, out);
if (len <= 0) {
errmsg = "invalid base64 encoding";
goto error;
}
if (ret->keyblob_len + len > ret->keyblob_size) {
ret->keyblob_size = ret->keyblob_len + len + 256;
ret->keyblob = sresize(ret->keyblob, ret->keyblob_size,
unsigned char);
}
memcpy(ret->keyblob + ret->keyblob_len, out, len);
ret->keyblob_len += len;
}
p++;
}
}
smemclr(line, strlen(line));
sfree(line);
line = NULL;
}
if (ret->keyblob_len == 0 || !ret->keyblob) {
errmsg = "key body not present";
goto error;
}
fclose(fp);
if (errmsg_p) *errmsg_p = NULL;
return ret;
error:
if (fp)
fclose(fp);
if (line) {
smemclr(line, strlen(line));
sfree(line);
line = NULL;
}
if (ret) {
if (ret->keyblob) {
smemclr(ret->keyblob, ret->keyblob_size);
sfree(ret->keyblob);
}
smemclr(ret, sizeof(*ret));
sfree(ret);
}
if (errmsg_p) *errmsg_p = errmsg;
return NULL;
}
int sshcom_encrypted(const Filename *filename, char **comment)
{
struct sshcom_key *key = load_sshcom_key(filename, NULL);
BinarySource src[1];
ptrlen str;
int answer = FALSE;
*comment = NULL;
if (!key)
goto done;
BinarySource_BARE_INIT(src, key->keyblob, key->keyblob_len);
if (get_uint32(src) != SSHCOM_MAGIC_NUMBER)
goto done; /* key is invalid */
get_uint32(src); /* skip length field */
get_string(src); /* skip key type */
str = get_string(src); /* cipher type */
if (get_err(src))
goto done; /* key is invalid */
if (!ptrlen_eq_string(str, "none"))
answer = TRUE;
done:
if (key) {
*comment = dupstr(key->comment);
smemclr(key->keyblob, key->keyblob_size);
sfree(key->keyblob);
smemclr(key, sizeof(*key));
sfree(key);
} else {
*comment = dupstr("");
}
return answer;
}
void BinarySink_put_mp_sshcom_from_string(
BinarySink *bs, const void *bytesv, int nbytes)
{
const unsigned char *bytes = (const unsigned char *)bytesv;
int bits = nbytes * 8 - 1;
while (bits > 0) {
if (*bytes & (1 << (bits & 7)))
break;
if (!(bits-- & 7))
bytes++, nbytes--;
}
put_uint32(bs, bits+1);
put_data(bs, bytes, nbytes);
}
#define put_mp_sshcom_from_string(bs, val, len) \
BinarySink_put_mp_sshcom_from_string(BinarySink_UPCAST(bs), val, len)
static ptrlen BinarySource_get_mp_sshcom_as_string(BinarySource *src)
{
unsigned bits = get_uint32(src);
return get_data(src, (bits + 7) / 8);
}
#define get_mp_sshcom_as_string(bs) \
BinarySource_get_mp_sshcom_as_string(BinarySource_UPCAST(bs))
struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase,
const char **errmsg_p)
{
struct sshcom_key *key = load_sshcom_key(filename, errmsg_p);
const char *errmsg;
BinarySource src[1];
ptrlen str, ciphertext;
int publen;
const char prefix_rsa[] = "if-modn{sign{rsa";
const char prefix_dsa[] = "dl-modp{sign{dsa";
enum { RSA, DSA } type;
int encrypted;
struct ssh2_userkey *ret = NULL, *retkey;
Invent a struct type for polymorphic SSH key data. During last week's work, I made a mistake in which I got the arguments backwards in one of the key-blob-generating functions - mistakenly swapped the 'void *' key instance with the 'BinarySink *' output destination - and I didn't spot the mistake until run time, because in C you can implicitly convert both to and from void * and so there was no compile-time failure of type checking. Now that I've introduced the FROMFIELD macro that downcasts a pointer to one field of a structure to retrieve a pointer to the whole structure, I think I might start using that more widely to indicate this kind of polymorphic subtyping. So now all the public-key functions in the struct ssh_signkey vtable handle their data instance in the form of a pointer to a subfield of a new zero-sized structure type 'ssh_key', which outside the key implementations indicates 'this is some kind of key instance but it could be of any type'; they downcast that pointer internally using FROMFIELD in place of the previous ordinary C cast, and return one by returning &foo->sshk for whatever foo they've just made up. The sshk member is not at the beginning of the structure, which means all those FROMFIELDs and &key->sshk are actually adding and subtracting an offset. Of course I could have put the member at the start anyway, but I had the idea that it's actually a feature _not_ to have the two types start at the same address, because it means you should notice earlier rather than later if you absentmindedly cast from one to the other directly rather than by the approved method (in particular, if you accidentally assign one through a void * and back without even _noticing_ you perpetrated a cast). In particular, this enforces that you can't sfree() the thing even once without realising you should instead of called the right freekey function. (I found several bugs by this method during initial testing, so I think it's already proved its worth!) While I'm here, I've also renamed the vtable structure ssh_signkey to ssh_keyalg, because it was a confusing name anyway - it describes the _algorithm_ for handling all keys of that type, not a specific key. So ssh_keyalg is the collection of code, and ssh_key is one instance of the data it handles.
2018-05-27 10:32:21 +03:00
const ssh_keyalg *alg;
strbuf *blob = NULL;
if (!key)
return NULL;
BinarySource_BARE_INIT(src, key->keyblob, key->keyblob_len);
if (get_uint32(src) != SSHCOM_MAGIC_NUMBER) {
errmsg = "key does not begin with magic number";
goto error;
}
get_uint32(src); /* skip length field */
/*
* Determine the key type.
*/
str = get_string(src);
if (str.len > sizeof(prefix_rsa) - 1 &&
!memcmp(str.ptr, prefix_rsa, sizeof(prefix_rsa) - 1)) {
type = RSA;
} else if (str.len > sizeof(prefix_dsa) - 1 &&
!memcmp(str.ptr, prefix_dsa, sizeof(prefix_dsa) - 1)) {
type = DSA;
} else {
errmsg = "key is of unknown type";
goto error;
}
/*
* Determine the cipher type.
*/
str = get_string(src);
if (ptrlen_eq_string(str, "none"))
encrypted = 0;
else if (ptrlen_eq_string(str, "3des-cbc"))
encrypted = 1;
else {
errmsg = "key encryption is of unknown type";
goto error;
}
/*
* Get hold of the encrypted part of the key.
*/
ciphertext = get_string(src);
if (ciphertext.len == 0) {
errmsg = "no key data found";
goto error;
}
/*
* Decrypt it if necessary.
*/
if (encrypted) {
/*
* Derive encryption key from passphrase and iv/salt:
*
* - let block A equal MD5(passphrase)
* - let block B equal MD5(passphrase || A)
* - block C would be MD5(passphrase || A || B) and so on
* - encryption key is the first N bytes of A || B
*/
struct MD5Context md5c;
unsigned char keybuf[32], iv[8];
if (ciphertext.len % 8 != 0) {
errmsg = "encrypted part of key is not a multiple of cipher block"
" size";
goto error;
}
MD5Init(&md5c);
put_data(&md5c, passphrase, strlen(passphrase));
MD5Final(keybuf, &md5c);
MD5Init(&md5c);
put_data(&md5c, passphrase, strlen(passphrase));
put_data(&md5c, keybuf, 16);
MD5Final(keybuf+16, &md5c);
/*
* Now decrypt the key blob in place (casting away const from
* ciphertext being a ptrlen).
*/
memset(iv, 0, sizeof(iv));
des3_decrypt_pubkey_ossh(keybuf, iv,
(char *)ciphertext.ptr, ciphertext.len);
smemclr(&md5c, sizeof(md5c));
smemclr(keybuf, sizeof(keybuf));
/*
* Hereafter we return WRONG_PASSPHRASE for any parsing
* error. (But only if we've just tried to decrypt it!
* Returning WRONG_PASSPHRASE for an unencrypted key is
* automatic doom.)
*/
if (encrypted)
ret = SSH2_WRONG_PASSPHRASE;
}
/*
* Expect the ciphertext to be formatted as a containing string,
* and reinitialise src to start parsing the inside of that string.
*/
BinarySource_BARE_INIT(src, ciphertext.ptr, ciphertext.len);
str = get_string(src);
if (get_err(src)) {
errmsg = "containing string was ill-formed";
goto error;
}
BinarySource_BARE_INIT(src, str.ptr, str.len);
/*
* Now we break down into RSA versus DSA. In either case we'll
* construct public and private blobs in our own format, and
* end up feeding them to ssh_key_new_priv().
*/
blob = strbuf_new();
if (type == RSA) {
ptrlen n, e, d, u, p, q;
e = get_mp_sshcom_as_string(src);
d = get_mp_sshcom_as_string(src);
n = get_mp_sshcom_as_string(src);
u = get_mp_sshcom_as_string(src);
p = get_mp_sshcom_as_string(src);
q = get_mp_sshcom_as_string(src);
if (get_err(src)) {
errmsg = "key data did not contain six integers";
goto error;
}
alg = &ssh_rsa;
put_stringz(blob, "ssh-rsa");
put_mp_ssh2_from_string(blob, e.ptr, e.len);
put_mp_ssh2_from_string(blob, n.ptr, n.len);
publen = blob->len;
put_mp_ssh2_from_string(blob, d.ptr, d.len);
put_mp_ssh2_from_string(blob, q.ptr, q.len);
put_mp_ssh2_from_string(blob, p.ptr, p.len);
put_mp_ssh2_from_string(blob, u.ptr, u.len);
} else {
ptrlen p, q, g, x, y;
assert(type == DSA); /* the only other option from the if above */
if (get_uint32(src) != 0) {
errmsg = "predefined DSA parameters not supported";
goto error;
}
p = get_mp_sshcom_as_string(src);
g = get_mp_sshcom_as_string(src);
q = get_mp_sshcom_as_string(src);
y = get_mp_sshcom_as_string(src);
x = get_mp_sshcom_as_string(src);
if (get_err(src)) {
errmsg = "key data did not contain five integers";
goto error;
}
alg = &ssh_dss;
put_stringz(blob, "ssh-dss");
put_mp_ssh2_from_string(blob, p.ptr, p.len);
put_mp_ssh2_from_string(blob, q.ptr, q.len);
put_mp_ssh2_from_string(blob, g.ptr, g.len);
put_mp_ssh2_from_string(blob, y.ptr, y.len);
publen = blob->len;
put_mp_ssh2_from_string(blob, x.ptr, x.len);
}
retkey = snew(struct ssh2_userkey);
retkey->key = ssh_key_new_priv(
alg, make_ptrlen(blob->u, publen),
make_ptrlen(blob->u + publen, blob->len - publen));
if (!retkey->key) {
sfree(retkey);
errmsg = "unable to create key data structure";
goto error;
}
retkey->comment = dupstr(key->comment);
errmsg = NULL; /* no error */
ret = retkey;
error:
if (blob) {
strbuf_free(blob);
}
smemclr(key->keyblob, key->keyblob_size);
sfree(key->keyblob);
smemclr(key, sizeof(*key));
sfree(key);
if (errmsg_p) *errmsg_p = errmsg;
return ret;
}
int sshcom_write(const Filename *filename, struct ssh2_userkey *key,
char *passphrase)
{
strbuf *pubblob, *privblob, *outblob;
ptrlen numbers[6];
int nnumbers, initial_zero, lenpos, i;
BinarySource src[1];
const char *type;
char *ciphertext;
int cipherlen;
int ret = 0;
FILE *fp;
/*
* Fetch the key blobs.
*/
pubblob = strbuf_new();
ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob));
privblob = strbuf_new();
ssh_key_private_blob(key->key, BinarySink_UPCAST(privblob));
outblob = NULL;
/*
* Find the sequence of integers to be encoded into the OpenSSH
* key blob, and also decide on the header line.
*/
if (ssh_key_alg(key->key) == &ssh_rsa) {
ptrlen n, e, d, p, q, iqmp;
/*
* These blobs were generated from inside PuTTY, so we needn't
* treat them as untrusted.
*/
BinarySource_BARE_INIT(src, pubblob->u, pubblob->len);
get_string(src); /* skip algorithm name */
e = get_string(src);
n = get_string(src);
BinarySource_BARE_INIT(src, privblob->u, privblob->len);
d = get_string(src);
p = get_string(src);
q = get_string(src);
iqmp = get_string(src);
assert(!get_err(src)); /* can't go wrong */
numbers[0] = e;
numbers[1] = d;
numbers[2] = n;
numbers[3] = iqmp;
numbers[4] = q;
numbers[5] = p;
nnumbers = 6;
initial_zero = 0;
type = "if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}";
} else if (ssh_key_alg(key->key) == &ssh_dss) {
ptrlen p, q, g, y, x;
/*
* These blobs were generated from inside PuTTY, so we needn't
* treat them as untrusted.
*/
BinarySource_BARE_INIT(src, pubblob->u, pubblob->len);
get_string(src); /* skip algorithm name */
p = get_string(src);
q = get_string(src);
g = get_string(src);
y = get_string(src);
BinarySource_BARE_INIT(src, privblob->u, privblob->len);
x = get_string(src);
assert(!get_err(src)); /* can't go wrong */
numbers[0] = p;
numbers[1] = g;
numbers[2] = q;
numbers[3] = y;
numbers[4] = x;
nnumbers = 5;
initial_zero = 1;
type = "dl-modp{sign{dsa-nist-sha1},dh{plain}}";
} else {
goto error; /* unsupported key type */
}
outblob = strbuf_new();
/*
* Create the unencrypted key blob.
*/
put_uint32(outblob, SSHCOM_MAGIC_NUMBER);
put_uint32(outblob, 0); /* length field, fill in later */
put_stringz(outblob, type);
put_stringz(outblob, passphrase ? "3des-cbc" : "none");
lenpos = outblob->len; /* remember this position */
put_uint32(outblob, 0); /* encrypted-blob size */
put_uint32(outblob, 0); /* encrypted-payload size */
if (initial_zero)
put_uint32(outblob, 0);
for (i = 0; i < nnumbers; i++)
put_mp_sshcom_from_string(outblob, numbers[i].ptr, numbers[i].len);
/* Now wrap up the encrypted payload. */
PUT_32BIT(outblob->s + lenpos + 4,
outblob->len - (lenpos + 8));
/* Pad encrypted blob to a multiple of cipher block size. */
if (passphrase) {
int padding = -(outblob->len - (lenpos+4)) & 7;
while (padding--)
put_byte(outblob, random_byte());
}
ciphertext = outblob->s + lenpos + 4;
cipherlen = outblob->len - (lenpos + 4);
assert(!passphrase || cipherlen % 8 == 0);
/* Wrap up the encrypted blob string. */
PUT_32BIT(outblob->s + lenpos, cipherlen);
/* And finally fill in the total length field. */
PUT_32BIT(outblob->s + 4, outblob->len);
/*
* Encrypt the key.
*/
if (passphrase) {
/*
* Derive encryption key from passphrase and iv/salt:
*
* - let block A equal MD5(passphrase)
* - let block B equal MD5(passphrase || A)
* - block C would be MD5(passphrase || A || B) and so on
* - encryption key is the first N bytes of A || B
*/
struct MD5Context md5c;
unsigned char keybuf[32], iv[8];
MD5Init(&md5c);
put_data(&md5c, passphrase, strlen(passphrase));
MD5Final(keybuf, &md5c);
MD5Init(&md5c);
put_data(&md5c, passphrase, strlen(passphrase));
put_data(&md5c, keybuf, 16);
MD5Final(keybuf+16, &md5c);
/*
* Now decrypt the key blob.
*/
memset(iv, 0, sizeof(iv));
des3_encrypt_pubkey_ossh(keybuf, iv, ciphertext, cipherlen);
smemclr(&md5c, sizeof(md5c));
smemclr(keybuf, sizeof(keybuf));
}
/*
* And save it. We'll use Unix line endings just in case it's
* subsequently transferred in binary mode.
*/
fp = f_open(filename, "wb", TRUE); /* ensure Unix line endings */
if (!fp)
goto error;
fputs("---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----\n", fp);
fprintf(fp, "Comment: \"");
/*
* Comment header is broken with backslash-newline if it goes
* over 70 chars. Although it's surrounded by quotes, it
* _doesn't_ escape backslashes or quotes within the string.
* Don't ask me, I didn't design it.
*/
{
int slen = 60; /* starts at 60 due to "Comment: " */
char *c = key->comment;
while ((int)strlen(c) > slen) {
fprintf(fp, "%.*s\\\n", slen, c);
c += slen;
slen = 70; /* allow 70 chars on subsequent lines */
}
fprintf(fp, "%s\"\n", c);
}
base64_encode(fp, outblob->u, outblob->len, 70);
fputs("---- END SSH2 ENCRYPTED PRIVATE KEY ----\n", fp);
fclose(fp);
ret = 1;
error:
if (outblob)
strbuf_free(outblob);
if (privblob)
strbuf_free(privblob);
if (pubblob)
strbuf_free(pubblob);
return ret;
}