diff options
Diffstat (limited to 'keyimport.c')
-rw-r--r-- | keyimport.c | 1730 |
1 files changed, 1730 insertions, 0 deletions
diff --git a/keyimport.c b/keyimport.c new file mode 100644 index 0000000..a0474f3 --- /dev/null +++ b/keyimport.c @@ -0,0 +1,1730 @@ +/* + * Based on PuTTY's import.c for importing/exporting OpenSSH and SSH.com + * keyfiles. + * + * The horribleness of the code is probably mine (matt). + * + * Modifications copyright 2003 Matt Johnston + * + * PuTTY is copyright 1997-2003 Simon Tatham. + * + * Portions copyright Robert de Bath, Joris van Rantwijk, Delian + * Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, + * Justin Bradford, and CORE SDI S.A. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "keyimport.h" +#include "bignum.h" +#include "buffer.h" +#include "dbutil.h" + +#define PUT_32BIT(cp, value) do { \ + (cp)[3] = (unsigned char)(value); \ + (cp)[2] = (unsigned char)((value) >> 8); \ + (cp)[1] = (unsigned char)((value) >> 16); \ + (cp)[0] = (unsigned char)((value) >> 24); } while (0) + +#define GET_32BIT(cp) \ + (((unsigned long)(unsigned char)(cp)[0] << 24) | \ + ((unsigned long)(unsigned char)(cp)[1] << 16) | \ + ((unsigned long)(unsigned char)(cp)[2] << 8) | \ + ((unsigned long)(unsigned char)(cp)[3])) + +static int openssh_encrypted(const char *filename); +static sign_key *openssh_read(const char *filename, char *passphrase); +static int openssh_write(const char *filename, sign_key *key, + char *passphrase); + +static int dropbear_write(const char*filename, sign_key * key); +static sign_key *dropbear_read(const char* filename); + +#if 0 +static int sshcom_encrypted(const char *filename, char **comment); +static struct ssh2_userkey *sshcom_read(const char *filename, char *passphrase); +static int sshcom_write(const char *filename, struct ssh2_userkey *key, + char *passphrase); +#endif + +int import_encrypted(const char* filename, int filetype) { + + if (filetype == KEYFILE_OPENSSH) { + return openssh_encrypted(filename); +#if 0 + } else if (filetype == KEYFILE_SSHCOM) { + return sshcom_encrypted(filename, NULL); +#endif + } + return 0; +} + +sign_key *import_read(const char *filename, char *passphrase, int filetype) { + + if (filetype == KEYFILE_OPENSSH) { + return openssh_read(filename, passphrase); + } else if (filetype == KEYFILE_DROPBEAR) { + return dropbear_read(filename); +#if 0 + } else if (filetype == KEYFILE_SSHCOM) { + return sshcom_read(filename, passphrase); +#endif + } + return NULL; +} + +int import_write(const char *filename, sign_key *key, char *passphrase, + int filetype) { + + if (filetype == KEYFILE_OPENSSH) { + return openssh_write(filename, key, passphrase); + } else if (filetype == KEYFILE_DROPBEAR) { + return dropbear_write(filename, key); +#if 0 + } else if (filetype == KEYFILE_SSHCOM) { + return sshcom_write(filename, key, passphrase); +#endif + } + return 0; +} + +static sign_key *dropbear_read(const char* filename) { + + buffer * buf = NULL; + sign_key *ret = NULL; + int type; + + buf = buf_new(MAX_PRIVKEY_SIZE); + if (buf_readfile(buf, filename) == DROPBEAR_FAILURE) { + goto error; + } + + buf_setpos(buf, 0); + ret = new_sign_key(); + + type = DROPBEAR_SIGNKEY_ANY; + if (buf_get_priv_key(buf, ret, &type) == DROPBEAR_FAILURE){ + goto error; + } + buf_free(buf); + + return ret; + +error: + if (buf) { + buf_free(buf); + } + if (ret) { + sign_key_free(ret); + } + return NULL; +} + +/* returns 0 on fail, 1 on success */ +static int dropbear_write(const char*filename, sign_key * key) { + + int keytype = -1; + buffer * buf; + FILE*fp; + int len; + int ret; + +#ifdef DROPBEAR_RSA + if (key->rsakey != NULL) { + keytype = DROPBEAR_SIGNKEY_RSA; + } +#endif +#ifdef DROPBEAR_DSS + if (key->dsskey != NULL) { + keytype = DROPBEAR_SIGNKEY_DSS; + } +#endif + + buf = buf_new(MAX_PRIVKEY_SIZE); + buf_put_priv_key(buf, key, keytype); + + fp = fopen(filename, "w"); + if (!fp) { + ret = 0; + goto out; + } + + buf_setpos(buf, 0); + do { + len = fwrite(buf_getptr(buf, buf->len - buf->pos), + 1, buf->len - buf->pos, fp); + buf_incrpos(buf, len); + } while (len > 0 && buf->len != buf->pos); + + fclose(fp); + + if (buf->pos != buf->len) { + ret = 0; + } else { + ret = 1; + } +out: + buf_free(buf); + return ret; +} + + +/* ---------------------------------------------------------------------- + * 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) == '=' \ + ) + +/* cpl has to be less than 100 */ +static void base64_encode_fp(FILE * fp, unsigned char *data, + int datalen, int cpl) +{ + char out[100]; + int n; + unsigned long outlen; + int rawcpl; + rawcpl = cpl * 3 / 4; + dropbear_assert((unsigned int)cpl < sizeof(out)); + + while (datalen > 0) { + n = (datalen < rawcpl ? datalen : rawcpl); + outlen = sizeof(out); + base64_encode(data, n, out, &outlen); + data += n; + datalen -= n; + fwrite(out, 1, outlen, fp); + fputc('\n', fp); + } +} +/* + * 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) + +static int ber_read_id_len(void *source, int sourcelen, + int *id, int *length, int *flags) +{ + unsigned char *p = (unsigned char *) source; + + if (sourcelen == 0) + return -1; + + *flags = (*p & 0xE0); + if ((*p & 0x1F) == 0x1F) { + *id = 0; + while (*p & 0x80) { + *id = (*id << 7) | (*p & 0x7F); + p++, sourcelen--; + if (sourcelen == 0) + return -1; + } + *id = (*id << 7) | (*p & 0x7F); + p++, sourcelen--; + } else { + *id = *p & 0x1F; + p++, sourcelen--; + } + + if (sourcelen == 0) + return -1; + + if (*p & 0x80) { + int n = *p & 0x7F; + p++, sourcelen--; + if (sourcelen < n) + return -1; + *length = 0; + while (n--) + *length = (*length << 8) | (*p++); + sourcelen -= n; + } else { + *length = *p; + p++, sourcelen--; + } + + return p - (unsigned char *) source; +} + +/* + * 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 int ber_write_id_len(void *dest, int id, int length, int flags) +{ + unsigned char *d = (unsigned char *)dest; + int len = 0; + + if (id <= 30) { + /* + * Identifier is one byte. + */ + len++; + if (d) *d++ = 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. + */ + len++; + if (d) *d++ = 0x1F | flags; + for (n = 1; (id >> (7*n)) > 0; n++) + continue; /* count the bytes */ + while (n--) { + len++; + if (d) *d++ = (n ? 0x80 : 0) | ((id >> (7*n)) & 0x7F); + } + } + + if (length < 128) { + /* + * Length is one byte. + */ + len++; + if (d) *d++ = 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 */ + len++; + if (d) *d++ = 0x80 | n; + while (n--) { + len++; + if (d) *d++ = (length >> (8*n)) & 0xFF; + } + } + + return len; +} + + +/* Simple structure to point to an mp-int within a blob. */ +struct mpint_pos { void *start; int bytes; }; + +/* ---------------------------------------------------------------------- + * Code to read and write OpenSSH private keys. + */ + +enum { OSSH_DSA, OSSH_RSA }; +struct openssh_key { + int type; + int encrypted; + char iv[32]; + unsigned char *keyblob; + unsigned int keyblob_len, keyblob_size; +}; + +static struct openssh_key *load_openssh_key(const char *filename) +{ + struct openssh_key *ret; + FILE *fp; + char buffer[256]; + char *errmsg = NULL, *p = NULL; + int headers_done; + unsigned long len, outlen; + + ret = (struct openssh_key*)m_malloc(sizeof(struct openssh_key)); + ret->keyblob = NULL; + ret->keyblob_len = ret->keyblob_size = 0; + ret->encrypted = 0; + memset(ret->iv, 0, sizeof(ret->iv)); + + if (strlen(filename) == 1 && filename[0] == '-') { + fp = stdin; + } else { + fp = fopen(filename, "r"); + } + if (!fp) { + errmsg = "Unable to open key file"; + goto error; + } + if (!fgets(buffer, sizeof(buffer), fp) || + 0 != strncmp(buffer, "-----BEGIN ", 11) || + 0 != strcmp(buffer+strlen(buffer)-17, "PRIVATE KEY-----\n")) { + errmsg = "File does not begin with OpenSSH key header"; + goto error; + } + if (!strcmp(buffer, "-----BEGIN RSA PRIVATE KEY-----\n")) + ret->type = OSSH_RSA; + else if (!strcmp(buffer, "-----BEGIN DSA PRIVATE KEY-----\n")) + ret->type = OSSH_DSA; + else { + errmsg = "Unrecognised key type"; + goto error; + } + + headers_done = 0; + while (1) { + if (!fgets(buffer, sizeof(buffer), fp)) { + errmsg = "Unexpected end of file"; + goto error; + } + if (0 == strncmp(buffer, "-----END ", 9) && + 0 == strcmp(buffer+strlen(buffer)-17, "PRIVATE KEY-----\n")) + break; /* done */ + if ((p = strchr(buffer, ':')) != 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(buffer, "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\n")) + ret->encrypted = 1; + } else if (!strcmp(buffer, "DEK-Info")) { + int i, j; + + if (strncmp(p, "DES-EDE3-CBC,", 13)) { + errmsg = "Ciphers other than DES-EDE3-CBC not supported"; + goto error; + } + p += 13; + for (i = 0; i < 8; i++) { + if (1 != sscanf(p, "%2x", &j)) + break; + ret->iv[i] = j; + p += 2; + } + if (i < 8) { + errmsg = "Expected 16-digit iv in DEK-Info"; + goto error; + } + } + } else { + headers_done = 1; + len = strlen(buffer); + outlen = len*4/3; + if (ret->keyblob_len + outlen > ret->keyblob_size) { + ret->keyblob_size = ret->keyblob_len + outlen + 256; + ret->keyblob = (unsigned char*)m_realloc(ret->keyblob, + ret->keyblob_size); + } + outlen = ret->keyblob_size - ret->keyblob_len; + if (base64_decode(buffer, len, + ret->keyblob + ret->keyblob_len, &outlen) != CRYPT_OK){ + errmsg = "Error decoding base64"; + goto error; + } + ret->keyblob_len += outlen; + } + } + + if (ret->keyblob_len == 0 || !ret->keyblob) { + 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; + } + + memset(buffer, 0, sizeof(buffer)); + return ret; + + error: + memset(buffer, 0, sizeof(buffer)); + if (ret) { + if (ret->keyblob) { + memset(ret->keyblob, 0, ret->keyblob_size); + m_free(ret->keyblob); + } + memset(&ret, 0, sizeof(ret)); + m_free(ret); + } + if (errmsg) { + fprintf(stderr, "Error: %s\n", errmsg); + } + return NULL; +} + +static int openssh_encrypted(const char *filename) +{ + struct openssh_key *key = load_openssh_key(filename); + int ret; + + if (!key) + return 0; + ret = key->encrypted; + memset(key->keyblob, 0, key->keyblob_size); + m_free(key->keyblob); + memset(&key, 0, sizeof(key)); + m_free(key); + return ret; +} + +static sign_key *openssh_read(const char *filename, char *passphrase) +{ + struct openssh_key *key; + unsigned char *p; + int ret, id, len, flags; + int i, num_integers = 0; + sign_key *retval = NULL; + char *errmsg; + char *modptr = NULL; + int modlen = -9999; + int type; + + sign_key *retkey; + buffer * blobbuf = NULL; + + key = load_openssh_key(filename); + + if (!key) + return NULL; + + if (key->encrypted) { + errmsg = "encrypted keys not supported currently"; + goto error; +#if 0 + /* matt TODO */ + /* + * 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 + */ + 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. + */ + des3_decrypt_pubkey_ossh(keybuf, (unsigned char *)key->iv, + key->keyblob, key->keyblob_len); + + memset(&md5c, 0, sizeof(md5c)); + memset(keybuf, 0, sizeof(keybuf)); +#endif + } + + /* + * 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. + */ + + p = key->keyblob; + + /* Expect the SEQUENCE header. Take its absence as a failure to decrypt. */ + ret = ber_read_id_len(p, key->keyblob_len, &id, &len, &flags); + p += ret; + if (ret < 0 || id != 16) { + errmsg = "ASN.1 decoding failure - wrong password?"; + goto error; + } + + /* Expect a load of INTEGERs. */ + if (key->type == OSSH_RSA) + num_integers = 9; + else if (key->type == OSSH_DSA) + num_integers = 6; + + /* + * Space to create key blob in. + */ + blobbuf = buf_new(3000); + + if (key->type == OSSH_DSA) { + buf_putstring(blobbuf, "ssh-dss", 7); + } else if (key->type == OSSH_RSA) { + buf_putstring(blobbuf, "ssh-rsa", 7); + } + + for (i = 0; i < num_integers; i++) { + ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p, + &id, &len, &flags); + p += ret; + if (ret < 0 || id != 2 || + key->keyblob+key->keyblob_len-p < len) { + errmsg = "ASN.1 decoding failure"; + goto error; + } + + if (i == 0) { + /* + * The first integer should be zero always (I think + * this is some sort of version indication). + */ + if (len != 1 || p[0] != 0) { + errmsg = "Version number mismatch"; + goto error; + } + } else if (key->type == OSSH_RSA) { + /* + * OpenSSH key order is n, e, d, p, q, dmp1, dmq1, iqmp + * but we want e, n, d, p, q + */ + if (i == 1) { + /* Save the details for after we deal with number 2. */ + modptr = (char *)p; + modlen = len; + } else if (i >= 2 && i <= 5) { + buf_putstring(blobbuf, p, len); + if (i == 2) { + buf_putstring(blobbuf, modptr, modlen); + } + } + } else if (key->type == OSSH_DSA) { + /* + * OpenSSH key order is p, q, g, y, x, + * we want the same. + */ + buf_putstring(blobbuf, p, len); + } + + /* Skip past the number. */ + p += 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. + */ + retkey = new_sign_key(); + buf_setpos(blobbuf, 0); + type = DROPBEAR_SIGNKEY_ANY; + if (buf_get_priv_key(blobbuf, retkey, &type) + != DROPBEAR_SUCCESS) { + errmsg = "unable to create key structure"; + sign_key_free(retkey); + retkey = NULL; + goto error; + } + + errmsg = NULL; /* no error */ + retval = retkey; + + error: + if (blobbuf) { + buf_burn(blobbuf); + buf_free(blobbuf); + } + m_burn(key->keyblob, key->keyblob_size); + m_free(key->keyblob); + m_burn(key, sizeof(key)); + m_free(key); + if (errmsg) { + fprintf(stderr, "Error: %s\n", errmsg); + } + return retval; +} + +static int openssh_write(const char *filename, sign_key *key, + char *passphrase) +{ + buffer * keyblob = NULL; + buffer * extrablob = NULL; /* used for calculated values to write */ + unsigned char *outblob = NULL; + int outlen = -9999; + struct mpint_pos numbers[9]; + int nnumbers = -1, pos, len, seqlen, i; + char *header = NULL, *footer = NULL; + char zero[1]; + unsigned char iv[8]; + int ret = 0; + FILE *fp; + int keytype = -1; + +#ifdef DROPBEAR_RSA + mp_int dmp1, dmq1, iqmp, tmpval; /* for rsa */ + + if (key->rsakey != NULL) { + keytype = DROPBEAR_SIGNKEY_RSA; + } +#endif +#ifdef DROPBEAR_DSS + if (key->dsskey != NULL) { + keytype = DROPBEAR_SIGNKEY_DSS; + } +#endif + + dropbear_assert(keytype != -1); + + /* + * Fetch the key blobs. + */ + keyblob = buf_new(3000); + buf_put_priv_key(keyblob, key, keytype); + + buf_setpos(keyblob, 0); + /* skip the "ssh-rsa" or "ssh-dss" header */ + buf_incrpos(keyblob, buf_getint(keyblob)); + + /* + * Find the sequence of integers to be encoded into the OpenSSH + * key blob, and also decide on the header line. + */ + numbers[0].start = zero; numbers[0].bytes = 1; zero[0] = '\0'; + +#ifdef DROPBEAR_RSA + if (keytype == DROPBEAR_SIGNKEY_RSA) { + + if (key->rsakey->p == NULL || key->rsakey->q == NULL) { + fprintf(stderr, "Pre-0.33 Dropbear keys cannot be converted to OpenSSH keys.\n"); + goto error; + } + + /* e */ + numbers[2].bytes = buf_getint(keyblob); + numbers[2].start = buf_getptr(keyblob, numbers[2].bytes); + buf_incrpos(keyblob, numbers[2].bytes); + + /* n */ + numbers[1].bytes = buf_getint(keyblob); + numbers[1].start = buf_getptr(keyblob, numbers[1].bytes); + buf_incrpos(keyblob, numbers[1].bytes); + + /* d */ + numbers[3].bytes = buf_getint(keyblob); + numbers[3].start = buf_getptr(keyblob, numbers[3].bytes); + buf_incrpos(keyblob, numbers[3].bytes); + + /* p */ + numbers[4].bytes = buf_getint(keyblob); + numbers[4].start = buf_getptr(keyblob, numbers[4].bytes); + buf_incrpos(keyblob, numbers[4].bytes); + + /* q */ + numbers[5].bytes = buf_getint(keyblob); + numbers[5].start = buf_getptr(keyblob, numbers[5].bytes); + buf_incrpos(keyblob, numbers[5].bytes); + + /* now calculate some extra parameters: */ + m_mp_init(&tmpval); + m_mp_init(&dmp1); + m_mp_init(&dmq1); + m_mp_init(&iqmp); + + /* dmp1 = d mod (p-1) */ + if (mp_sub_d(key->rsakey->p, 1, &tmpval) != MP_OKAY) { + fprintf(stderr, "Bignum error for p-1\n"); + goto error; + } + if (mp_mod(key->rsakey->d, &tmpval, &dmp1) != MP_OKAY) { + fprintf(stderr, "Bignum error for dmp1\n"); + goto error; + } + + /* dmq1 = d mod (q-1) */ + if (mp_sub_d(key->rsakey->q, 1, &tmpval) != MP_OKAY) { + fprintf(stderr, "Bignum error for q-1\n"); + goto error; + } + if (mp_mod(key->rsakey->d, &tmpval, &dmq1) != MP_OKAY) { + fprintf(stderr, "Bignum error for dmq1\n"); + goto error; + } + + /* iqmp = (q^-1) mod p */ + if (mp_invmod(key->rsakey->q, key->rsakey->p, &iqmp) != MP_OKAY) { + fprintf(stderr, "Bignum error for iqmp\n"); + goto error; + } + + extrablob = buf_new(2000); + buf_putmpint(extrablob, &dmp1); + buf_putmpint(extrablob, &dmq1); + buf_putmpint(extrablob, &iqmp); + buf_setpos(extrablob, 0); + mp_clear(&dmp1); + mp_clear(&dmq1); + mp_clear(&iqmp); + mp_clear(&tmpval); + + /* dmp1 */ + numbers[6].bytes = buf_getint(extrablob); + numbers[6].start = buf_getptr(extrablob, numbers[6].bytes); + buf_incrpos(extrablob, numbers[6].bytes); + + /* dmq1 */ + numbers[7].bytes = buf_getint(extrablob); + numbers[7].start = buf_getptr(extrablob, numbers[7].bytes); + buf_incrpos(extrablob, numbers[7].bytes); + + /* iqmp */ + numbers[8].bytes = buf_getint(extrablob); + numbers[8].start = buf_getptr(extrablob, numbers[8].bytes); + buf_incrpos(extrablob, numbers[8].bytes); + + nnumbers = 9; + header = "-----BEGIN RSA PRIVATE KEY-----\n"; + footer = "-----END RSA PRIVATE KEY-----\n"; + } +#endif /* DROPBEAR_RSA */ + +#ifdef DROPBEAR_DSS + if (keytype == DROPBEAR_SIGNKEY_DSS) { + + /* p */ + numbers[1].bytes = buf_getint(keyblob); + numbers[1].start = buf_getptr(keyblob, numbers[1].bytes); + buf_incrpos(keyblob, numbers[1].bytes); + + /* q */ + numbers[2].bytes = buf_getint(keyblob); + numbers[2].start = buf_getptr(keyblob, numbers[2].bytes); + buf_incrpos(keyblob, numbers[2].bytes); + + /* g */ + numbers[3].bytes = buf_getint(keyblob); + numbers[3].start = buf_getptr(keyblob, numbers[3].bytes); + buf_incrpos(keyblob, numbers[3].bytes); + + /* y */ + numbers[4].bytes = buf_getint(keyblob); + numbers[4].start = buf_getptr(keyblob, numbers[4].bytes); + buf_incrpos(keyblob, numbers[4].bytes); + + /* x */ + numbers[5].bytes = buf_getint(keyblob); + numbers[5].start = buf_getptr(keyblob, numbers[5].bytes); + buf_incrpos(keyblob, numbers[5].bytes); + + nnumbers = 6; + header = "-----BEGIN DSA PRIVATE KEY-----\n"; + footer = "-----END DSA PRIVATE KEY-----\n"; + } +#endif /* DROPBEAR_DSS */ + + /* + * Now count up the total size of the ASN.1 encoded integers, + * so as to determine the length of the containing SEQUENCE. + */ + len = 0; + for (i = 0; i < nnumbers; i++) { + len += ber_write_id_len(NULL, 2, numbers[i].bytes, 0); + len += numbers[i].bytes; + } + seqlen = len; + /* Now add on the SEQUENCE header. */ + len += ber_write_id_len(NULL, 16, seqlen, ASN1_CONSTRUCTED); + /* Round up to the cipher block size, ensuring we have at least one + * byte of padding (see below). */ + outlen = len; + if (passphrase) + outlen = (outlen+8) &~ 7; + + /* + * Now we know how big outblob needs to be. Allocate it. + */ + outblob = (unsigned char*)m_malloc(outlen); + + /* + * And write the data into it. + */ + pos = 0; + pos += ber_write_id_len(outblob+pos, 16, seqlen, ASN1_CONSTRUCTED); + for (i = 0; i < nnumbers; i++) { + pos += ber_write_id_len(outblob+pos, 2, numbers[i].bytes, 0); + memcpy(outblob+pos, numbers[i].start, numbers[i].bytes); + pos += numbers[i].bytes; + } + + /* + * 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. + */ + dropbear_assert(pos == len); + while (pos < outlen) { + outblob[pos++] = outlen - len; + } + + /* + * Encrypt the key. + */ + if (passphrase) { + fprintf(stderr, "Encrypted keys aren't supported currently\n"); + goto error; +#if 0 + /* + * 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 + */ + struct MD5Context md5c; + unsigned char keybuf[32]; + + for (i = 0; i < 8; i++) iv[i] = random_byte(); + + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Update(&md5c, iv, 8); + MD5Final(keybuf, &md5c); + + MD5Init(&md5c); + MD5Update(&md5c, keybuf, 16); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Update(&md5c, iv, 8); + MD5Final(keybuf+16, &md5c); + + /* + * Now encrypt the key blob. + */ + des3_encrypt_pubkey_ossh(keybuf, iv, outblob, outlen); + + memset(&md5c, 0, sizeof(md5c)); + memset(keybuf, 0, sizeof(keybuf)); +#endif + } + + /* + * And save it. We'll use Unix line endings just in case it's + * subsequently transferred in binary mode. + */ + if (strlen(filename) == 1 && filename[0] == '-') { + fp = stdout; + } else { + fp = fopen(filename, "wb"); /* ensure Unix line endings */ + } + if (!fp) { + fprintf(stderr, "Failed opening output file\n"); + 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(fp, outblob, outlen, 64); + fputs(footer, fp); + fclose(fp); + ret = 1; + + error: + if (outblob) { + memset(outblob, 0, outlen); + m_free(outblob); + } + if (keyblob) { + buf_burn(keyblob); + buf_free(keyblob); + } + if (extrablob) { + buf_burn(extrablob); + buf_free(extrablob); + } + return ret; +} + +#if 0 +/* XXX TODO ssh.com stuff isn't going yet */ + +/* ---------------------------------------------------------------------- + * Code to read ssh.com private keys. + */ + +/* + * The format of the base64 blob is largely ssh2-packet-formatted, + * except that mpints are a bit different: they're more like the + * old ssh1 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 ssh2 + * 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 SSH2-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 char *filename) +{ + struct sshcom_key *ret; + FILE *fp; + char buffer[256]; + int len; + char *errmsg, *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 = fopen(filename, "r"); + if (!fp) { + errmsg = "Unable to open key file"; + goto error; + } + if (!fgets(buffer, sizeof(buffer), fp) || + 0 != strcmp(buffer, "---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----\n")) { + errmsg = "File does not begin with ssh.com key header"; + goto error; + } + + headers_done = 0; + while (1) { + if (!fgets(buffer, sizeof(buffer), fp)) { + errmsg = "Unexpected end of file"; + goto error; + } + if (!strcmp(buffer, "---- END SSH2 ENCRYPTED PRIVATE KEY ----\n")) + break; /* done */ + if ((p = strchr(buffer, ':')) != NULL) { + if (headers_done) { + errmsg = "Header found in body of key data"; + goto error; + } + *p++ = '\0'; + while (*p && isspace((unsigned char)*p)) p++; + /* + * Header lines can end in a trailing backslash for + * continuation. + */ + while ((len = strlen(p)) > (int)(sizeof(buffer) - (p-buffer) -1) || + p[len-1] != '\n' || p[len-2] == '\\') { + if (len > (int)((p-buffer) + sizeof(buffer)-2)) { + errmsg = "Header line too long to deal with"; + goto error; + } + if (!fgets(p+len-2, sizeof(buffer)-(p-buffer)-(len-2), fp)) { + errmsg = "Unexpected end of file"; + goto error; + } + } + p[strcspn(p, "\n")] = '\0'; + if (!strcmp(buffer, "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 = buffer; + 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++; + } + } + } + + if (ret->keyblob_len == 0 || !ret->keyblob) { + errmsg = "Key body not present"; + goto error; + } + + return ret; + + error: + if (ret) { + if (ret->keyblob) { + memset(ret->keyblob, 0, ret->keyblob_size); + m_free(ret->keyblob); + } + memset(&ret, 0, sizeof(ret)); + m_free(ret); + } + return NULL; +} + +int sshcom_encrypted(const char *filename, char **comment) +{ + struct sshcom_key *key = load_sshcom_key(filename); + int pos, len, answer; + + *comment = NULL; + if (!key) + return 0; + + /* + * Check magic number. + */ + if (GET_32BIT(key->keyblob) != 0x3f6ff9eb) + return 0; /* key is invalid */ + + /* + * Find the cipher-type string. + */ + answer = 0; + pos = 8; + if (key->keyblob_len < pos+4) + goto done; /* key is far too short */ + pos += 4 + GET_32BIT(key->keyblob + pos); /* skip key type */ + if (key->keyblob_len < pos+4) + goto done; /* key is far too short */ + len = GET_32BIT(key->keyblob + pos); /* find cipher-type length */ + if (key->keyblob_len < pos+4+len) + goto done; /* cipher type string is incomplete */ + if (len != 4 || 0 != memcmp(key->keyblob + pos + 4, "none", 4)) + answer = 1; + + done: + *comment = dupstr(key->comment); + memset(key->keyblob, 0, key->keyblob_size); + m_free(key->keyblob); + memset(&key, 0, sizeof(key)); + m_free(key); + return answer; +} + +static int sshcom_read_mpint(void *data, int len, struct mpint_pos *ret) +{ + int bits; + int bytes; + unsigned char *d = (unsigned char *) data; + + if (len < 4) + goto error; + bits = GET_32BIT(d); + + bytes = (bits + 7) / 8; + if (len < 4+bytes) + goto error; + + ret->start = d + 4; + ret->bytes = bytes; + return bytes+4; + + error: + ret->start = NULL; + ret->bytes = -1; + return len; /* ensure further calls fail as well */ +} + +static int sshcom_put_mpint(void *target, void *data, int len) +{ + unsigned char *d = (unsigned char *)target; + unsigned char *i = (unsigned char *)data; + int bits = len * 8 - 1; + + while (bits > 0) { + if (*i & (1 << (bits & 7))) + break; + if (!(bits-- & 7)) + i++, len--; + } + + PUT_32BIT(d, bits+1); + memcpy(d+4, i, len); + return len+4; +} + +sign_key *sshcom_read(const char *filename, char *passphrase) +{ + struct sshcom_key *key = load_sshcom_key(filename); + char *errmsg; + int pos, len; + const char prefix_rsa[] = "if-modn{sign{rsa"; + const char prefix_dsa[] = "dl-modp{sign{dsa"; + enum { RSA, DSA } type; + int encrypted; + char *ciphertext; + int cipherlen; + struct ssh2_userkey *ret = NULL, *retkey; + const struct ssh_signkey *alg; + unsigned char *blob = NULL; + int blobsize, publen, privlen; + + if (!key) + return NULL; + + /* + * Check magic number. + */ + if (GET_32BIT(key->keyblob) != SSHCOM_MAGIC_NUMBER) { + errmsg = "Key does not begin with magic number"; + goto error; + } + + /* + * Determine the key type. + */ + pos = 8; + if (key->keyblob_len < pos+4 || + (len = GET_32BIT(key->keyblob + pos)) > key->keyblob_len - pos - 4) { + errmsg = "Key blob does not contain a key type string"; + goto error; + } + if (len > sizeof(prefix_rsa) - 1 && + !memcmp(key->keyblob+pos+4, prefix_rsa, sizeof(prefix_rsa) - 1)) { + type = RSA; + } else if (len > sizeof(prefix_dsa) - 1 && + !memcmp(key->keyblob+pos+4, prefix_dsa, sizeof(prefix_dsa) - 1)) { + type = DSA; + } else { + errmsg = "Key is of unknown type"; + goto error; + } + pos += 4+len; + + /* + * Determine the cipher type. + */ + if (key->keyblob_len < pos+4 || + (len = GET_32BIT(key->keyblob + pos)) > key->keyblob_len - pos - 4) { + errmsg = "Key blob does not contain a cipher type string"; + goto error; + } + if (len == 4 && !memcmp(key->keyblob+pos+4, "none", 4)) + encrypted = 0; + else if (len == 8 && !memcmp(key->keyblob+pos+4, "3des-cbc", 8)) + encrypted = 1; + else { + errmsg = "Key encryption is of unknown type"; + goto error; + } + pos += 4+len; + + /* + * Get hold of the encrypted part of the key. + */ + if (key->keyblob_len < pos+4 || + (len = GET_32BIT(key->keyblob + pos)) > key->keyblob_len - pos - 4) { + errmsg = "Key blob does not contain actual key data"; + goto error; + } + ciphertext = (char *)key->keyblob + pos + 4; + cipherlen = len; + if (cipherlen == 0) { + errmsg = "Length of key data is zero"; + 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 (cipherlen % 8 != 0) { + errmsg = "Encrypted part of key is not a multiple of cipher block" + " size"; + goto error; + } + + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Final(keybuf, &md5c); + + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Update(&md5c, keybuf, 16); + MD5Final(keybuf+16, &md5c); + + /* + * Now decrypt the key blob. + */ + memset(iv, 0, sizeof(iv)); + des3_decrypt_pubkey_ossh(keybuf, iv, (unsigned char *)ciphertext, + cipherlen); + + memset(&md5c, 0, sizeof(md5c)); + memset(keybuf, 0, 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; + } + + /* + * Strip away the containing string to get to the real meat. + */ + len = GET_32BIT(ciphertext); + if (len > cipherlen-4) { + errmsg = "containing string was ill-formed"; + goto error; + } + ciphertext += 4; + cipherlen = 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 alg->createkey(). + */ + blobsize = cipherlen + 256; + blob = snewn(blobsize, unsigned char); + privlen = 0; + if (type == RSA) { + struct mpint_pos n, e, d, u, p, q; + int pos = 0; + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &e); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &d); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &n); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &u); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &p); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &q); + if (!q.start) { + errmsg = "key data did not contain six integers"; + goto error; + } + + alg = &ssh_rsa; + pos = 0; + pos += put_string(blob+pos, "ssh-rsa", 7); + pos += put_mp(blob+pos, e.start, e.bytes); + pos += put_mp(blob+pos, n.start, n.bytes); + publen = pos; + pos += put_string(blob+pos, d.start, d.bytes); + pos += put_mp(blob+pos, q.start, q.bytes); + pos += put_mp(blob+pos, p.start, p.bytes); + pos += put_mp(blob+pos, u.start, u.bytes); + privlen = pos - publen; + } else if (type == DSA) { + struct mpint_pos p, q, g, x, y; + int pos = 4; + if (GET_32BIT(ciphertext) != 0) { + errmsg = "predefined DSA parameters not supported"; + goto error; + } + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &p); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &g); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &q); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &y); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &x); + if (!x.start) { + errmsg = "key data did not contain five integers"; + goto error; + } + + alg = &ssh_dss; + pos = 0; + pos += put_string(blob+pos, "ssh-dss", 7); + pos += put_mp(blob+pos, p.start, p.bytes); + pos += put_mp(blob+pos, q.start, q.bytes); + pos += put_mp(blob+pos, g.start, g.bytes); + pos += put_mp(blob+pos, y.start, y.bytes); + publen = pos; + pos += put_mp(blob+pos, x.start, x.bytes); + privlen = pos - publen; + } + + dropbear_assert(privlen > 0); /* should have bombed by now if not */ + + retkey = snew(struct ssh2_userkey); + retkey->alg = alg; + retkey->data = alg->createkey(blob, publen, blob+publen, privlen); + if (!retkey->data) { + m_free(retkey); + errmsg = "unable to create key data structure"; + goto error; + } + retkey->comment = dupstr(key->comment); + + errmsg = NULL; /* no error */ + ret = retkey; + + error: + if (blob) { + memset(blob, 0, blobsize); + m_free(blob); + } + memset(key->keyblob, 0, key->keyblob_size); + m_free(key->keyblob); + memset(&key, 0, sizeof(key)); + m_free(key); + return ret; +} + +int sshcom_write(const char *filename, sign_key *key, + char *passphrase) +{ + unsigned char *pubblob, *privblob; + int publen, privlen; + unsigned char *outblob; + int outlen; + struct mpint_pos numbers[6]; + int nnumbers, initial_zero, pos, lenpos, i; + char *type; + char *ciphertext; + int cipherlen; + int ret = 0; + FILE *fp; + + /* + * Fetch the key blobs. + */ + pubblob = key->alg->public_blob(key->data, &publen); + privblob = key->alg->private_blob(key->data, &privlen); + outblob = NULL; + + /* + * Find the sequence of integers to be encoded into the OpenSSH + * key blob, and also decide on the header line. + */ + if (key->alg == &ssh_rsa) { + int pos; + struct mpint_pos n, e, d, p, q, iqmp; + + pos = 4 + GET_32BIT(pubblob); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &e); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &n); + pos = 0; + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &d); + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &p); + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &q); + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &iqmp); + + dropbear_assert(e.start && iqmp.start); /* 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 (key->alg == &ssh_dss) { + int pos; + struct mpint_pos p, q, g, y, x; + + pos = 4 + GET_32BIT(pubblob); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &p); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &q); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &g); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &y); + pos = 0; + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &x); + + dropbear_assert(y.start && x.start); /* 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 { + dropbear_assert(0); /* zoinks! */ + } + + /* + * Total size of key blob will be somewhere under 512 plus + * combined length of integers. We'll calculate the more + * precise size as we construct the blob. + */ + outlen = 512; + for (i = 0; i < nnumbers; i++) + outlen += 4 + numbers[i].bytes; + outblob = snewn(outlen, unsigned char); + + /* + * Create the unencrypted key blob. + */ + pos = 0; + PUT_32BIT(outblob+pos, SSHCOM_MAGIC_NUMBER); pos += 4; + pos += 4; /* length field, fill in later */ + pos += put_string(outblob+pos, type, strlen(type)); + { + char *ciphertype = passphrase ? "3des-cbc" : "none"; + pos += put_string(outblob+pos, ciphertype, strlen(ciphertype)); + } + lenpos = pos; /* remember this position */ + pos += 4; /* encrypted-blob size */ + pos += 4; /* encrypted-payload size */ + if (initial_zero) { + PUT_32BIT(outblob+pos, 0); + pos += 4; + } + for (i = 0; i < nnumbers; i++) + pos += sshcom_put_mpint(outblob+pos, + numbers[i].start, numbers[i].bytes); + /* Now wrap up the encrypted payload. */ + PUT_32BIT(outblob+lenpos+4, pos - (lenpos+8)); + /* Pad encrypted blob to a multiple of cipher block size. */ + if (passphrase) { + int padding = -(pos - (lenpos+4)) & 7; + while (padding--) + outblob[pos++] = random_byte(); + } + ciphertext = (char *)outblob+lenpos+4; + cipherlen = pos - (lenpos+4); + dropbear_assert(!passphrase || cipherlen % 8 == 0); + /* Wrap up the encrypted blob string. */ + PUT_32BIT(outblob+lenpos, cipherlen); + /* And finally fill in the total length field. */ + PUT_32BIT(outblob+4, pos); + + dropbear_assert(pos < outlen); + + /* + * 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); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Final(keybuf, &md5c); + + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Update(&md5c, keybuf, 16); + MD5Final(keybuf+16, &md5c); + + /* + * Now decrypt the key blob. + */ + memset(iv, 0, sizeof(iv)); + des3_encrypt_pubkey_ossh(keybuf, iv, (unsigned char *)ciphertext, + cipherlen); + + memset(&md5c, 0, sizeof(md5c)); + memset(keybuf, 0, sizeof(keybuf)); + } + + /* + * And save it. We'll use Unix line endings just in case it's + * subsequently transferred in binary mode. + */ + fp = fopen(filename, "wb"); /* 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(fp, outblob, pos, 70); + fputs("---- END SSH2 ENCRYPTED PRIVATE KEY ----\n", fp); + fclose(fp); + ret = 1; + + error: + if (outblob) { + memset(outblob, 0, outlen); + m_free(outblob); + } + if (privblob) { + memset(privblob, 0, privlen); + m_free(privblob); + } + if (pubblob) { + memset(pubblob, 0, publen); + m_free(pubblob); + } + return ret; +} +#endif /* ssh.com stuff disabled */ |