- markus@cvs.openbsd.org 2013/12/06 13:34:54
     [authfile.c authfile.h cipher.c cipher.h key.c packet.c ssh-agent.c]
     [ssh-keygen.c PROTOCOL.key] new private key format, bcrypt as KDF by
     default; details in PROTOCOL.key; feedback and lots help from djm;
     ok djm@
diff --git a/authfile.c b/authfile.c
index d0c1089..e38a3dd 100644
--- a/authfile.c
+++ b/authfile.c
@@ -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 <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, 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
@@ -58,6 +58,8 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <util.h>
+
 #include "xmalloc.h"
 #include "cipher.h"
 #include "buffer.h"
@@ -68,6 +70,16 @@
 #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)
 
@@ -75,6 +87,333 @@
 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(&copy);
+
+	/* 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(&copy, 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(&copy, len - dlen);
+	if (buffer_len(&copy) < sizeof(AUTH_MAGIC) ||
+	    memcmp(buffer_ptr(&copy), AUTH_MAGIC, sizeof(AUTH_MAGIC))) {
+		error("%s: bad magic", __func__);
+		goto out;
+	}
+	buffer_consume(&copy, sizeof(AUTH_MAGIC));
+
+	ciphername = buffer_get_cstring_ret(&copy, 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(&copy, 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(&copy, &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, &copy) < 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(&copy, &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(&copy);
+	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(&copy), len, 0, authlen);
+	cipher_cleanup(&ctx);
+	buffer_consume(&copy, len);
+
+	/* fail silently on decryption errors */
+	if (ret != 0) {
+		debug("%s: decrypt failed", __func__);
+		goto out;
+	}
+
+	if (buffer_len(&copy) != 0) {
+		error("%s: key blob has trailing data (len = %u)", __func__,
+		    buffer_len(&copy));
+		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(&copy);
+	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
@@ -149,8 +488,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));
 
@@ -239,7 +579,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:
@@ -247,6 +588,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);
@@ -256,13 +601,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;
@@ -473,8 +820,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(&copy), buffer_len(&copy), 0, 0);
+	if (cipher_crypt(&ciphercontext, 0, cp,
+	    buffer_ptr(&copy), buffer_len(&copy), 0, 0) != 0)
+		fatal("%s: cipher_crypt failed", __func__);
 	cipher_cleanup(&ciphercontext);
 	memset(&ciphercontext, 0, sizeof(ciphercontext));
 	buffer_free(&copy);
@@ -641,6 +989,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);
@@ -648,6 +998,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);
@@ -943,4 +1295,3 @@
 	fclose(f);
 	return ret;
 }
-