version 1.98, 2013/11/21 00:45:43 |
version 1.99, 2013/12/06 13:34:54 |
|
|
* called by a name other than "ssh" or "Secure Shell". |
* called by a name other than "ssh" or "Secure Shell". |
* |
* |
* |
* |
* Copyright (c) 2000 Markus Friedl. All rights reserved. |
* Copyright (c) 2000, 2013 Markus Friedl. All rights reserved. |
* |
* |
* Redistribution and use in source and binary forms, with or without |
* Redistribution and use in source and binary forms, with or without |
* modification, are permitted provided that the following conditions |
* modification, are permitted provided that the following conditions |
|
|
#include <string.h> |
#include <string.h> |
#include <unistd.h> |
#include <unistd.h> |
|
|
|
#include <util.h> |
|
|
#include "xmalloc.h" |
#include "xmalloc.h" |
#include "cipher.h" |
#include "cipher.h" |
#include "buffer.h" |
#include "buffer.h" |
|
|
#include "rsa.h" |
#include "rsa.h" |
#include "misc.h" |
#include "misc.h" |
#include "atomicio.h" |
#include "atomicio.h" |
|
#include "uuencode.h" |
|
|
|
/* openssh private key file format */ |
|
#define MARK_BEGIN "-----BEGIN OPENSSH PRIVATE KEY-----\n" |
|
#define MARK_END "-----END OPENSSH PRIVATE KEY-----\n" |
|
#define KDFNAME "bcrypt" |
|
#define AUTH_MAGIC "openssh-key-v1" |
|
#define SALT_LEN 16 |
|
#define DEFAULT_CIPHERNAME "aes256-cbc" |
|
#define DEFAULT_ROUNDS 16 |
|
|
#define MAX_KEY_FILE_SIZE (1024 * 1024) |
#define MAX_KEY_FILE_SIZE (1024 * 1024) |
|
|
/* Version identification string for SSH v1 identity files. */ |
/* Version identification string for SSH v1 identity files. */ |
static const char authfile_id_string[] = |
static const char authfile_id_string[] = |
"SSH PRIVATE KEY FILE FORMAT 1.1\n"; |
"SSH PRIVATE KEY FILE FORMAT 1.1\n"; |
|
|
|
static int |
|
key_private_to_blob2(Key *prv, Buffer *blob, const char *passphrase, |
|
const char *comment, const char *ciphername, int rounds) |
|
{ |
|
u_char *key, *cp, salt[SALT_LEN]; |
|
size_t keylen, ivlen, blocksize, authlen; |
|
u_int len, check; |
|
int i, n; |
|
const Cipher *c; |
|
Buffer encoded, b, kdf; |
|
CipherContext ctx; |
|
const char *kdfname = KDFNAME; |
|
|
|
if (rounds <= 0) |
|
rounds = DEFAULT_ROUNDS; |
|
if (passphrase == NULL || !strlen(passphrase)) { |
|
ciphername = "none"; |
|
kdfname = "none"; |
|
} else if (ciphername == NULL) |
|
ciphername = DEFAULT_CIPHERNAME; |
|
else if (cipher_number(ciphername) != SSH_CIPHER_SSH2) |
|
fatal("invalid cipher"); |
|
|
|
if ((c = cipher_by_name(ciphername)) == NULL) |
|
fatal("unknown cipher name"); |
|
buffer_init(&kdf); |
|
blocksize = cipher_blocksize(c); |
|
keylen = cipher_keylen(c); |
|
ivlen = cipher_ivlen(c); |
|
authlen = cipher_authlen(c); |
|
key = xcalloc(1, keylen + ivlen); |
|
if (strcmp(kdfname, "none") != 0) { |
|
arc4random_buf(salt, SALT_LEN); |
|
if (bcrypt_pbkdf(passphrase, strlen(passphrase), |
|
salt, SALT_LEN, key, keylen + ivlen, rounds) < 0) |
|
fatal("bcrypt_pbkdf failed"); |
|
buffer_put_string(&kdf, salt, SALT_LEN); |
|
buffer_put_int(&kdf, rounds); |
|
} |
|
cipher_init(&ctx, c, key, keylen, key + keylen , ivlen, 1); |
|
memset(key, 0, keylen + ivlen); |
|
free(key); |
|
|
|
buffer_init(&encoded); |
|
buffer_append(&encoded, AUTH_MAGIC, sizeof(AUTH_MAGIC)); |
|
buffer_put_cstring(&encoded, ciphername); |
|
buffer_put_cstring(&encoded, kdfname); |
|
buffer_put_string(&encoded, buffer_ptr(&kdf), buffer_len(&kdf)); |
|
buffer_put_int(&encoded, 1); /* number of keys */ |
|
key_to_blob(prv, &cp, &len); /* public key */ |
|
buffer_put_string(&encoded, cp, len); |
|
|
|
memset(cp, 0, len); |
|
free(cp); |
|
|
|
buffer_free(&kdf); |
|
|
|
/* set up the buffer that will be encrypted */ |
|
buffer_init(&b); |
|
|
|
/* Random check bytes */ |
|
check = arc4random(); |
|
buffer_put_int(&b, check); |
|
buffer_put_int(&b, check); |
|
|
|
/* append private key and comment*/ |
|
key_private_serialize(prv, &b); |
|
buffer_put_cstring(&b, comment); |
|
|
|
/* padding */ |
|
i = 0; |
|
while (buffer_len(&b) % blocksize) |
|
buffer_put_char(&b, ++i & 0xff); |
|
|
|
/* length */ |
|
buffer_put_int(&encoded, buffer_len(&b)); |
|
|
|
/* encrypt */ |
|
cp = buffer_append_space(&encoded, buffer_len(&b) + authlen); |
|
if (cipher_crypt(&ctx, 0, cp, buffer_ptr(&b), buffer_len(&b), 0, |
|
authlen) != 0) |
|
fatal("%s: cipher_crypt failed", __func__); |
|
buffer_free(&b); |
|
cipher_cleanup(&ctx); |
|
|
|
/* uuencode */ |
|
len = 2 * buffer_len(&encoded); |
|
cp = xmalloc(len); |
|
n = uuencode(buffer_ptr(&encoded), buffer_len(&encoded), |
|
(char *)cp, len); |
|
if (n < 0) |
|
fatal("%s: uuencode", __func__); |
|
|
|
buffer_clear(blob); |
|
buffer_append(blob, MARK_BEGIN, sizeof(MARK_BEGIN) - 1); |
|
for (i = 0; i < n; i++) { |
|
buffer_put_char(blob, cp[i]); |
|
if (i % 70 == 69) |
|
buffer_put_char(blob, '\n'); |
|
} |
|
if (i % 70 != 69) |
|
buffer_put_char(blob, '\n'); |
|
buffer_append(blob, MARK_END, sizeof(MARK_END) - 1); |
|
free(cp); |
|
|
|
return buffer_len(blob); |
|
} |
|
|
|
static Key * |
|
key_parse_private2(Buffer *blob, int type, const char *passphrase, |
|
char **commentp) |
|
{ |
|
u_char *key = NULL, *cp, *salt = NULL, pad, last; |
|
char *comment = NULL, *ciphername = NULL, *kdfname = NULL, *kdfp; |
|
u_int keylen = 0, ivlen, blocksize, slen, klen, len, rounds, nkeys; |
|
u_int check1, check2, m1len, m2len; |
|
size_t authlen; |
|
const Cipher *c; |
|
Buffer b, encoded, copy, kdf; |
|
CipherContext ctx; |
|
Key *k = NULL; |
|
int dlen, ret, i; |
|
|
|
buffer_init(&b); |
|
buffer_init(&kdf); |
|
buffer_init(&encoded); |
|
buffer_init(©); |
|
|
|
/* uudecode */ |
|
m1len = sizeof(MARK_BEGIN) - 1; |
|
m2len = sizeof(MARK_END) - 1; |
|
cp = buffer_ptr(blob); |
|
len = buffer_len(blob); |
|
if (len < m1len || memcmp(cp, MARK_BEGIN, m1len)) { |
|
debug("%s: missing begin marker", __func__); |
|
goto out; |
|
} |
|
cp += m1len; |
|
len -= m1len; |
|
while (len) { |
|
if (*cp != '\n' && *cp != '\r') |
|
buffer_put_char(&encoded, *cp); |
|
last = *cp; |
|
len--; |
|
cp++; |
|
if (last == '\n') { |
|
if (len >= m2len && !memcmp(cp, MARK_END, m2len)) { |
|
buffer_put_char(&encoded, '\0'); |
|
break; |
|
} |
|
} |
|
} |
|
if (!len) { |
|
debug("%s: no end marker", __func__); |
|
goto out; |
|
} |
|
len = buffer_len(&encoded); |
|
if ((cp = buffer_append_space(©, len)) == NULL) { |
|
error("%s: buffer_append_space", __func__); |
|
goto out; |
|
} |
|
if ((dlen = uudecode(buffer_ptr(&encoded), cp, len)) < 0) { |
|
error("%s: uudecode failed", __func__); |
|
goto out; |
|
} |
|
if ((u_int)dlen > len) { |
|
error("%s: crazy uudecode length %d > %u", __func__, dlen, len); |
|
goto out; |
|
} |
|
buffer_consume_end(©, len - dlen); |
|
if (buffer_len(©) < sizeof(AUTH_MAGIC) || |
|
memcmp(buffer_ptr(©), AUTH_MAGIC, sizeof(AUTH_MAGIC))) { |
|
error("%s: bad magic", __func__); |
|
goto out; |
|
} |
|
buffer_consume(©, sizeof(AUTH_MAGIC)); |
|
|
|
ciphername = buffer_get_cstring_ret(©, NULL); |
|
if (ciphername == NULL || |
|
(c = cipher_by_name(ciphername)) == NULL) { |
|
error("%s: unknown cipher name", __func__); |
|
goto out; |
|
} |
|
if ((passphrase == NULL || !strlen(passphrase)) && |
|
strcmp(ciphername, "none") != 0) { |
|
/* passphrase required */ |
|
goto out; |
|
} |
|
kdfname = buffer_get_cstring_ret(©, NULL); |
|
if (kdfname == NULL || |
|
(!strcmp(kdfname, "none") && !strcmp(kdfname, "bcrypt"))) { |
|
error("%s: unknown kdf name", __func__); |
|
goto out; |
|
} |
|
if (!strcmp(kdfname, "none") && strcmp(ciphername, "none") != 0) { |
|
error("%s: cipher %s requires kdf", __func__, ciphername); |
|
goto out; |
|
} |
|
/* kdf options */ |
|
kdfp = buffer_get_string_ptr_ret(©, &klen); |
|
if (kdfp == NULL) { |
|
error("%s: kdf options not set", __func__); |
|
goto out; |
|
} |
|
if (klen > 0) { |
|
if ((cp = buffer_append_space(&kdf, klen)) == NULL) { |
|
error("%s: kdf alloc failed", __func__); |
|
goto out; |
|
} |
|
memcpy(cp, kdfp, klen); |
|
} |
|
/* number of keys */ |
|
if (buffer_get_int_ret(&nkeys, ©) < 0) { |
|
error("%s: key counter missing", __func__); |
|
goto out; |
|
} |
|
if (nkeys != 1) { |
|
error("%s: only one key supported", __func__); |
|
goto out; |
|
} |
|
/* pubkey */ |
|
if ((cp = buffer_get_string_ret(©, &len)) == NULL) { |
|
error("%s: pubkey not found", __func__); |
|
goto out; |
|
} |
|
free(cp); /* XXX check pubkey against decrypted private key */ |
|
|
|
/* size of encrypted key blob */ |
|
len = buffer_get_int(©); |
|
blocksize = cipher_blocksize(c); |
|
authlen = cipher_authlen(c); |
|
if (len < blocksize) { |
|
error("%s: encrypted data too small", __func__); |
|
goto out; |
|
} |
|
if (len % blocksize) { |
|
error("%s: length not multiple of blocksize", __func__); |
|
goto out; |
|
} |
|
|
|
/* setup key */ |
|
keylen = cipher_keylen(c); |
|
ivlen = cipher_ivlen(c); |
|
key = xcalloc(1, keylen + ivlen); |
|
if (!strcmp(kdfname, "bcrypt")) { |
|
if ((salt = buffer_get_string_ret(&kdf, &slen)) == NULL) { |
|
error("%s: salt not set", __func__); |
|
goto out; |
|
} |
|
if (buffer_get_int_ret(&rounds, &kdf) < 0) { |
|
error("%s: rounds not set", __func__); |
|
goto out; |
|
} |
|
if (bcrypt_pbkdf(passphrase, strlen(passphrase), salt, slen, |
|
key, keylen + ivlen, rounds) < 0) { |
|
error("%s: bcrypt_pbkdf failed", __func__); |
|
goto out; |
|
} |
|
} |
|
|
|
cp = buffer_append_space(&b, len); |
|
cipher_init(&ctx, c, key, keylen, key + keylen, ivlen, 0); |
|
ret = cipher_crypt(&ctx, 0, cp, buffer_ptr(©), len, 0, authlen); |
|
cipher_cleanup(&ctx); |
|
buffer_consume(©, len); |
|
|
|
/* fail silently on decryption errors */ |
|
if (ret != 0) { |
|
debug("%s: decrypt failed", __func__); |
|
goto out; |
|
} |
|
|
|
if (buffer_len(©) != 0) { |
|
error("%s: key blob has trailing data (len = %u)", __func__, |
|
buffer_len(©)); |
|
goto out; |
|
} |
|
|
|
/* check bytes */ |
|
if (buffer_get_int_ret(&check1, &b) < 0 || |
|
buffer_get_int_ret(&check2, &b) < 0) { |
|
error("check bytes missing"); |
|
goto out; |
|
} |
|
if (check1 != check2) { |
|
debug("%s: decrypt failed: 0x%08x != 0x%08x", __func__, |
|
check1, check2); |
|
goto out; |
|
} |
|
|
|
k = key_private_deserialize(&b); |
|
|
|
/* comment */ |
|
comment = buffer_get_cstring_ret(&b, NULL); |
|
|
|
i = 0; |
|
while (buffer_len(&b)) { |
|
if (buffer_get_char_ret(&pad, &b) == -1 || |
|
pad != (++i & 0xff)) { |
|
error("%s: bad padding", __func__); |
|
key_free(k); |
|
k = NULL; |
|
goto out; |
|
} |
|
} |
|
|
|
if (k && commentp) { |
|
*commentp = comment; |
|
comment = NULL; |
|
} |
|
|
|
/* XXX decode pubkey and check against private */ |
|
out: |
|
free(ciphername); |
|
free(kdfname); |
|
free(salt); |
|
free(comment); |
|
if (key) |
|
memset(key, 0, keylen + ivlen); |
|
free(key); |
|
buffer_free(&encoded); |
|
buffer_free(©); |
|
buffer_free(&kdf); |
|
buffer_free(&b); |
|
return k; |
|
} |
|
|
/* |
/* |
* Serialises the authentication (private) key to a blob, encrypting it with |
* Serialises the authentication (private) key to a blob, encrypting it with |
* passphrase. The identification of the blob (lowest 64 bits of n) will |
* passphrase. The identification of the blob (lowest 64 bits of n) will |
|
|
|
|
cipher_set_key_string(&ciphercontext, cipher, passphrase, |
cipher_set_key_string(&ciphercontext, cipher, passphrase, |
CIPHER_ENCRYPT); |
CIPHER_ENCRYPT); |
cipher_crypt(&ciphercontext, 0, cp, |
if (cipher_crypt(&ciphercontext, 0, cp, |
buffer_ptr(&buffer), buffer_len(&buffer), 0, 0); |
buffer_ptr(&buffer), buffer_len(&buffer), 0, 0) != 0) |
|
fatal("%s: cipher_crypt failed", __func__); |
cipher_cleanup(&ciphercontext); |
cipher_cleanup(&ciphercontext); |
memset(&ciphercontext, 0, sizeof(ciphercontext)); |
memset(&ciphercontext, 0, sizeof(ciphercontext)); |
|
|
|
|
/* Serialise "key" to buffer "blob" */ |
/* Serialise "key" to buffer "blob" */ |
static int |
static int |
key_private_to_blob(Key *key, Buffer *blob, const char *passphrase, |
key_private_to_blob(Key *key, Buffer *blob, const char *passphrase, |
const char *comment) |
const char *comment, int force_new_format, const char *new_format_cipher, |
|
int new_format_rounds) |
{ |
{ |
switch (key->type) { |
switch (key->type) { |
case KEY_RSA1: |
case KEY_RSA1: |
|
|
case KEY_DSA: |
case KEY_DSA: |
case KEY_ECDSA: |
case KEY_ECDSA: |
case KEY_RSA: |
case KEY_RSA: |
|
if (force_new_format) { |
|
return key_private_to_blob2(key, blob, passphrase, |
|
comment, new_format_cipher, new_format_rounds); |
|
} |
return key_private_pem_to_blob(key, blob, passphrase, comment); |
return key_private_pem_to_blob(key, blob, passphrase, comment); |
default: |
default: |
error("%s: cannot save key type %d", __func__, key->type); |
error("%s: cannot save key type %d", __func__, key->type); |
|
|
|
|
int |
int |
key_save_private(Key *key, const char *filename, const char *passphrase, |
key_save_private(Key *key, const char *filename, const char *passphrase, |
const char *comment) |
const char *comment, int force_new_format, const char *new_format_cipher, |
|
int new_format_rounds) |
{ |
{ |
Buffer keyblob; |
Buffer keyblob; |
int success = 0; |
int success = 0; |
|
|
buffer_init(&keyblob); |
buffer_init(&keyblob); |
if (!key_private_to_blob(key, &keyblob, passphrase, comment)) |
if (!key_private_to_blob(key, &keyblob, passphrase, comment, |
|
force_new_format, new_format_cipher, new_format_rounds)) |
goto out; |
goto out; |
if (!key_save_private_blob(&keyblob, filename)) |
if (!key_save_private_blob(&keyblob, filename)) |
goto out; |
goto out; |
|
|
/* Rest of the buffer is encrypted. Decrypt it using the passphrase. */ |
/* Rest of the buffer is encrypted. Decrypt it using the passphrase. */ |
cipher_set_key_string(&ciphercontext, cipher, passphrase, |
cipher_set_key_string(&ciphercontext, cipher, passphrase, |
CIPHER_DECRYPT); |
CIPHER_DECRYPT); |
cipher_crypt(&ciphercontext, 0, cp, |
if (cipher_crypt(&ciphercontext, 0, cp, |
buffer_ptr(©), buffer_len(©), 0, 0); |
buffer_ptr(©), buffer_len(©), 0, 0) != 0) |
|
fatal("%s: cipher_crypt failed", __func__); |
cipher_cleanup(&ciphercontext); |
cipher_cleanup(&ciphercontext); |
memset(&ciphercontext, 0, sizeof(ciphercontext)); |
memset(&ciphercontext, 0, sizeof(ciphercontext)); |
buffer_free(©); |
buffer_free(©); |
|
|
key_parse_private_type(Buffer *blob, int type, const char *passphrase, |
key_parse_private_type(Buffer *blob, int type, const char *passphrase, |
char **commentp) |
char **commentp) |
{ |
{ |
|
Key *k; |
|
|
switch (type) { |
switch (type) { |
case KEY_RSA1: |
case KEY_RSA1: |
return key_parse_private_rsa1(blob, passphrase, commentp); |
return key_parse_private_rsa1(blob, passphrase, commentp); |
|
|
case KEY_ECDSA: |
case KEY_ECDSA: |
case KEY_RSA: |
case KEY_RSA: |
case KEY_UNSPEC: |
case KEY_UNSPEC: |
|
if ((k = key_parse_private2(blob, type, passphrase, commentp))) |
|
return k; |
return key_parse_private_pem(blob, type, passphrase, commentp); |
return key_parse_private_pem(blob, type, passphrase, commentp); |
default: |
default: |
error("%s: cannot parse key type %d", __func__, type); |
error("%s: cannot parse key type %d", __func__, type); |
|
|
fclose(f); |
fclose(f); |
return ret; |
return ret; |
} |
} |
|
|