| /* |
| * 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 = NULL; |
| 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 (fp) { |
| fclose(fp); |
| } |
| 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; |
| } |
| |
| /* |
| * 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); |
| 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 */ |