+ errmsg = NULL; /* no error */
+ retval = retkey;
+
+ error:
+ if (blob) {
+ memset(blob, 0, blobsize);
+ sfree(blob);
+ }
+ memset(key->keyblob, 0, key->keyblob_size);
+ sfree(key->keyblob);
+ memset(key, 0, sizeof(*key));
+ sfree(key);
+ if (errmsg_p) *errmsg_p = errmsg;
+ return retval;
+}
+
+int openssh_write(const Filename *filename, struct ssh2_userkey *key,
+ char *passphrase)
+{
+ unsigned char *pubblob, *privblob, *spareblob;
+ int publen, privlen, sparelen = 0;
+ unsigned char *outblob;
+ int outlen;
+ struct mpint_pos numbers[9];
+ int nnumbers, pos, len, seqlen, i;
+ char *header, *footer;
+ char zero[1];
+ unsigned char iv[8];
+ 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);
+ spareblob = 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, dmp1, dmq1;
+ Bignum bd, bp, bq, bdmp1, bdmq1;
+
+ 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);
+
+ assert(e.start && iqmp.start); /* can't go wrong */
+
+ /* We also need d mod (p-1) and d mod (q-1). */
+ bd = bignum_from_bytes(d.start, d.bytes);
+ bp = bignum_from_bytes(p.start, p.bytes);
+ bq = bignum_from_bytes(q.start, q.bytes);
+ decbn(bp);
+ decbn(bq);
+ bdmp1 = bigmod(bd, bp);
+ bdmq1 = bigmod(bd, bq);
+ freebn(bd);
+ freebn(bp);
+ freebn(bq);
+
+ dmp1.bytes = (bignum_bitcount(bdmp1)+8)/8;
+ dmq1.bytes = (bignum_bitcount(bdmq1)+8)/8;
+ sparelen = dmp1.bytes + dmq1.bytes;
+ spareblob = snewn(sparelen, unsigned char);
+ dmp1.start = spareblob;
+ dmq1.start = spareblob + dmp1.bytes;
+ for (i = 0; i < dmp1.bytes; i++)
+ spareblob[i] = bignum_byte(bdmp1, dmp1.bytes-1 - i);
+ for (i = 0; i < dmq1.bytes; i++)
+ spareblob[i+dmp1.bytes] = bignum_byte(bdmq1, dmq1.bytes-1 - i);
+ freebn(bdmp1);
+ freebn(bdmq1);
+
+ numbers[0].start = zero; numbers[0].bytes = 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 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);
+
+ assert(y.start && x.start); /* can't go wrong */
+
+ numbers[0].start = zero; numbers[0].bytes = 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";
+ } else {
+ assert(0); /* zoinks! */
+ exit(1); /* XXX: GCC doesn't understand assert() on some systems. */
+ }
+
+ /*
+ * 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 = snewn(outlen, unsigned char);
+
+ /*
+ * 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.
+ */
+ assert(pos == len);
+ while (pos < outlen) {
+ outblob[pos++] = outlen - len;
+ }
+
+ /*
+ * Encrypt the key.
+ *
+ * For the moment, we still encrypt our OpenSSH keys using
+ * old-style 3DES.
+ */
+ if (passphrase) {
+ /*
+ * 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));
+ }
+
+ /*
+ * 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, outlen, 64);
+ fputs(footer, fp);
+ fclose(fp);
+ ret = 1;
+
+ error:
+ if (outblob) {
+ memset(outblob, 0, outlen);
+ sfree(outblob);
+ }
+ if (spareblob) {
+ memset(spareblob, 0, sparelen);
+ sfree(spareblob);
+ }
+ if (privblob) {
+ memset(privblob, 0, privlen);
+ sfree(privblob);
+ }
+ if (pubblob) {
+ memset(pubblob, 0, publen);
+ sfree(pubblob);
+ }
+ return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * 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;
+ 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 = 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;
+ }
+ memset(line, 0, 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 ----"))
+ 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]);
+
+ memset(line2, 0, 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++;
+ }
+ }
+ memset(line, 0, strlen(line));
+ sfree(line);
+ line = NULL;
+ }
+
+ if (ret->keyblob_len == 0 || !ret->keyblob) {
+ errmsg = "key body not present";
+ goto error;
+ }
+
+ if (errmsg_p) *errmsg_p = NULL;
+ return ret;
+
+ error:
+ if (line) {
+ memset(line, 0, strlen(line));
+ sfree(line);
+ line = NULL;
+ }
+ if (ret) {
+ if (ret->keyblob) {
+ memset(ret->keyblob, 0, ret->keyblob_size);
+ sfree(ret->keyblob);
+ }
+ memset(ret, 0, 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);
+ 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);