Newer
Older
}
/*
* Get hold of the encrypted part of the key.
*/
ciphertext = get_string(src);
if (ciphertext.len == 0) {
errmsg = "no key data found";
goto error;
}
/*
* Decrypt it if necessary.
*/
if (encrypted) {
/*
* Derive encryption key from passphrase and iv/salt:
*
* - let block A equal MD5(passphrase)
* - let block B equal MD5(passphrase || A)
* - block C would be MD5(passphrase || A || B) and so on
* - encryption key is the first N bytes of A || B
*/
unsigned char keybuf[32], iv[8];
if (ciphertext.len % 8 != 0) {
errmsg = "encrypted part of key is not a multiple of cipher block"
" size";
goto error;
}
sshcom_derivekey(ptrlen_from_asciz(passphrase), keybuf);
/*
* Now decrypt the key blob in place (casting away const from
* ciphertext being a ptrlen).
*/
memset(iv, 0, sizeof(iv));
des3_decrypt_pubkey_ossh(keybuf, iv,
(char *)ciphertext.ptr, ciphertext.len);
smemclr(keybuf, sizeof(keybuf));
/*
* Hereafter we return WRONG_PASSPHRASE for any parsing
* error. (But only if we've just tried to decrypt it!
* Returning WRONG_PASSPHRASE for an unencrypted key is
* automatic doom.)
*/
if (encrypted)
ret = SSH2_WRONG_PASSPHRASE;
}
/*
* Expect the ciphertext to be formatted as a containing string,
* and reinitialise src to start parsing the inside of that string.
BinarySource_BARE_INIT_PL(src, ciphertext);
str = get_string(src);
if (get_err(src)) {
errmsg = "containing string was ill-formed";
goto error;
}
/*
* Now we break down into RSA versus DSA. In either case we'll
* construct public and private blobs in our own format, and
* end up feeding them to ssh_key_new_priv().
blob = strbuf_new_nm();
ptrlen n, e, d, u, p, q;
e = get_mp_sshcom_as_string(src);
d = get_mp_sshcom_as_string(src);
n = get_mp_sshcom_as_string(src);
u = get_mp_sshcom_as_string(src);
p = get_mp_sshcom_as_string(src);
q = get_mp_sshcom_as_string(src);
if (get_err(src)) {
errmsg = "key data did not contain six integers";
goto error;
}
alg = &ssh_rsa;
put_stringz(blob, "ssh-rsa");
put_mp_ssh2_from_string(blob, e);
put_mp_ssh2_from_string(blob, n);
put_mp_ssh2_from_string(blob, d);
put_mp_ssh2_from_string(blob, q);
put_mp_ssh2_from_string(blob, p);
put_mp_ssh2_from_string(blob, u);
} else {
assert(type == DSA); /* the only other option from the if above */
if (get_uint32(src) != 0) {
errmsg = "predefined DSA parameters not supported";
goto error;
}
p = get_mp_sshcom_as_string(src);
g = get_mp_sshcom_as_string(src);
q = get_mp_sshcom_as_string(src);
y = get_mp_sshcom_as_string(src);
x = get_mp_sshcom_as_string(src);
if (get_err(src)) {
errmsg = "key data did not contain five integers";
goto error;
}
alg = &ssh_dss;
put_stringz(blob, "ssh-dss");
put_mp_ssh2_from_string(blob, p);
put_mp_ssh2_from_string(blob, q);
put_mp_ssh2_from_string(blob, g);
put_mp_ssh2_from_string(blob, y);
retkey = snew(ssh2_userkey);
retkey->key = ssh_key_new_priv(
alg, make_ptrlen(blob->u, publen),
make_ptrlen(blob->u + publen, blob->len - publen));
sfree(retkey);
errmsg = "unable to create key data structure";
goto error;
}
retkey->comment = dupstr(key->comment);
errmsg = NULL; /* no error */
ret = retkey;
error:
if (blob) {
strbuf_free(key->keyblob);
smemclr(key, sizeof(*key));
if (errmsg_p) *errmsg_p = errmsg;
static bool sshcom_write(
const Filename *filename, ssh2_userkey *key, const char *passphrase)
strbuf *pubblob, *privblob, *outblob;
int nnumbers, lenpos, i;
bool initial_zero;
char *ciphertext;
int cipherlen;
FILE *fp;
/*
* Fetch the key blobs.
*/
pubblob = strbuf_new();
ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob));
privblob = strbuf_new_nm();
ssh_key_private_blob(key->key, BinarySink_UPCAST(privblob));
outblob = NULL;
/*
* Find the sequence of integers to be encoded into the OpenSSH
* key blob, and also decide on the header line.
*/
if (ssh_key_alg(key->key) == &ssh_rsa) {
ptrlen n, e, d, p, q, iqmp;
/*
* These blobs were generated from inside PuTTY, so we needn't
* treat them as untrusted.
*/
BinarySource_BARE_INIT(src, pubblob->u, pubblob->len);
get_string(src); /* skip algorithm name */
e = get_string(src);
n = get_string(src);
BinarySource_BARE_INIT(src, privblob->u, privblob->len);
d = get_string(src);
p = get_string(src);
q = get_string(src);
iqmp = get_string(src);
assert(!get_err(src)); /* can't go wrong */
numbers[0] = e;
numbers[1] = d;
numbers[2] = n;
numbers[3] = iqmp;
numbers[4] = q;
numbers[5] = p;
nnumbers = 6;
initial_zero = false;
type = "if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}";
} else if (ssh_key_alg(key->key) == &ssh_dss) {
/*
* These blobs were generated from inside PuTTY, so we needn't
* treat them as untrusted.
*/
BinarySource_BARE_INIT(src, pubblob->u, pubblob->len);
get_string(src); /* skip algorithm name */
p = get_string(src);
q = get_string(src);
g = get_string(src);
y = get_string(src);
BinarySource_BARE_INIT(src, privblob->u, privblob->len);
x = get_string(src);
assert(!get_err(src)); /* can't go wrong */
numbers[0] = p;
numbers[1] = g;
numbers[2] = q;
numbers[3] = y;
numbers[4] = x;
nnumbers = 5;
initial_zero = true;
type = "dl-modp{sign{dsa-nist-sha1},dh{plain}}";
goto error; /* unsupported key type */
outblob = strbuf_new_nm();
/*
* Create the unencrypted key blob.
*/
put_uint32(outblob, SSHCOM_MAGIC_NUMBER);
put_uint32(outblob, 0); /* length field, fill in later */
put_stringz(outblob, type);
put_stringz(outblob, passphrase ? "3des-cbc" : "none");
lenpos = outblob->len; /* remember this position */
put_uint32(outblob, 0); /* encrypted-blob size */
put_uint32(outblob, 0); /* encrypted-payload size */
if (initial_zero)
put_uint32(outblob, 0);
put_mp_sshcom_from_string(outblob, numbers[i]);
/* Now wrap up the encrypted payload. */
PUT_32BIT_MSB_FIRST(outblob->s + lenpos + 4,
outblob->len - (lenpos + 8));
/* Pad encrypted blob to a multiple of cipher block size. */
if (passphrase) {
int padding = -(outblob->len - (lenpos+4)) & 7;
uint8_t padding_buf[8];
random_read(padding_buf, padding);
put_data(outblob, padding_buf, padding);
ciphertext = outblob->s + lenpos + 4;
cipherlen = outblob->len - (lenpos + 4);
assert(!passphrase || cipherlen % 8 == 0);
/* Wrap up the encrypted blob string. */
PUT_32BIT_MSB_FIRST(outblob->s + lenpos, cipherlen);
/* And finally fill in the total length field. */
PUT_32BIT_MSB_FIRST(outblob->s + 4, outblob->len);
/*
* Encrypt the key.
*/
if (passphrase) {
unsigned char keybuf[32], iv[8];
sshcom_derivekey(ptrlen_from_asciz(passphrase), keybuf);
/*
* Now decrypt the key blob.
*/
des3_encrypt_pubkey_ossh(keybuf, iv, ciphertext, cipherlen);
smemclr(keybuf, sizeof(keybuf));
}
/*
* And save it. We'll use Unix line endings just in case it's
* subsequently transferred in binary mode.
*/
fp = f_open(filename, "wb", true); /* ensure Unix line endings */
fputs("---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----\n", fp);
fprintf(fp, "Comment: \"");
/*
* Comment header is broken with backslash-newline if it goes
* over 70 chars. Although it's surrounded by quotes, it
* _doesn't_ escape backslashes or quotes within the string.
* Don't ask me, I didn't design it.
*/
{
int slen = 60; /* starts at 60 due to "Comment: " */
char *c = key->comment;
while ((int)strlen(c) > slen) {
fprintf(fp, "%.*s\\\n", slen, c);
c += slen;
slen = 70; /* allow 70 chars on subsequent lines */
}
fprintf(fp, "%s\"\n", c);
base64_encode(fp, outblob->u, outblob->len, 70);
fputs("---- END SSH2 ENCRYPTED PRIVATE KEY ----\n", fp);
fclose(fp);
if (outblob)
strbuf_free(outblob);
if (privblob)
strbuf_free(privblob);
if (pubblob)
strbuf_free(pubblob);