=================================================================== RCS file: /cvsrepo/anoncvs/cvs/src/usr.bin/ssh/authfile.c,v retrieving revision 1.98 retrieving revision 1.99 diff -u -r1.98 -r1.99 --- src/usr.bin/ssh/authfile.c 2013/11/21 00:45:43 1.98 +++ src/usr.bin/ssh/authfile.c 2013/12/06 13:34:54 1.99 @@ -1,4 +1,4 @@ -/* $OpenBSD: authfile.c,v 1.98 2013/11/21 00:45:43 djm Exp $ */ +/* $OpenBSD: authfile.c,v 1.99 2013/12/06 13:34:54 markus Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -13,7 +13,7 @@ * 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 * modification, are permitted provided that the following conditions @@ -53,6 +53,8 @@ #include #include +#include + #include "xmalloc.h" #include "cipher.h" #include "buffer.h" @@ -63,13 +65,350 @@ #include "rsa.h" #include "misc.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) /* Version identification string for SSH v1 identity files. */ static const char authfile_id_string[] = "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 * passphrase. The identification of the blob (lowest 64 bits of n) will @@ -144,8 +483,9 @@ cipher_set_key_string(&ciphercontext, cipher, passphrase, CIPHER_ENCRYPT); - cipher_crypt(&ciphercontext, 0, cp, - buffer_ptr(&buffer), buffer_len(&buffer), 0, 0); + if (cipher_crypt(&ciphercontext, 0, cp, + buffer_ptr(&buffer), buffer_len(&buffer), 0, 0) != 0) + fatal("%s: cipher_crypt failed", __func__); cipher_cleanup(&ciphercontext); memset(&ciphercontext, 0, sizeof(ciphercontext)); @@ -228,7 +568,8 @@ /* Serialise "key" to buffer "blob" */ static int 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) { case KEY_RSA1: @@ -236,6 +577,10 @@ case KEY_DSA: case KEY_ECDSA: 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); default: error("%s: cannot save key type %d", __func__, key->type); @@ -245,13 +590,15 @@ int 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; int success = 0; 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; if (!key_save_private_blob(&keyblob, filename)) goto out; @@ -462,8 +809,9 @@ /* Rest of the buffer is encrypted. Decrypt it using the passphrase. */ cipher_set_key_string(&ciphercontext, cipher, passphrase, CIPHER_DECRYPT); - cipher_crypt(&ciphercontext, 0, cp, - buffer_ptr(©), buffer_len(©), 0, 0); + if (cipher_crypt(&ciphercontext, 0, cp, + buffer_ptr(©), buffer_len(©), 0, 0) != 0) + fatal("%s: cipher_crypt failed", __func__); cipher_cleanup(&ciphercontext); memset(&ciphercontext, 0, sizeof(ciphercontext)); buffer_free(©); @@ -625,6 +973,8 @@ key_parse_private_type(Buffer *blob, int type, const char *passphrase, char **commentp) { + Key *k; + switch (type) { case KEY_RSA1: return key_parse_private_rsa1(blob, passphrase, commentp); @@ -632,6 +982,8 @@ case KEY_ECDSA: case KEY_RSA: case KEY_UNSPEC: + if ((k = key_parse_private2(blob, type, passphrase, commentp))) + return k; return key_parse_private_pem(blob, type, passphrase, commentp); default: error("%s: cannot parse key type %d", __func__, type); @@ -927,4 +1279,3 @@ fclose(f); return ret; } -