- djm@cvs.openbsd.org 2008/11/04 08:22:13
     [auth.h auth2.c monitor.c monitor.h monitor_wrap.c monitor_wrap.h]
     [readconf.c readconf.h servconf.c servconf.h ssh2.h ssh_config.5]
     [sshconnect2.c sshd_config.5 jpake.c jpake.h schnorr.c auth2-jpake.c]
     [Makefile.in]
     Add support for an experimental zero-knowledge password authentication
     method using the J-PAKE protocol described in F. Hao, P. Ryan,
     "Password Authenticated Key Exchange by Juggling", 16th Workshop on
     Security Protocols, Cambridge, April 2008.

     This method allows password-based authentication without exposing
     the password to the server. Instead, the client and server exchange
     cryptographic proofs to demonstrate of knowledge of the password while
     revealing nothing useful to an attacker or compromised endpoint.

     This is experimental, work-in-progress code and is presently
     compiled-time disabled (turn on -DJPAKE in Makefile.inc).

     "just commit it.  It isn't too intrusive." deraadt@
diff --git a/sshconnect2.c b/sshconnect2.c
index 7d0c5e8..a762eec 100644
--- a/sshconnect2.c
+++ b/sshconnect2.c
@@ -1,6 +1,7 @@
-/* $OpenBSD: sshconnect2.c,v 1.169 2008/11/01 04:50:08 djm Exp $ */
+/* $OpenBSD: sshconnect2.c,v 1.170 2008/11/04 08:22:13 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
+ * Copyright (c) 2008 Damien Miller.  All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -67,6 +68,7 @@
 #include "msg.h"
 #include "pathnames.h"
 #include "uidswap.h"
+#include "jpake.h"
 
 #ifdef GSSAPI
 #include "ssh-gss.h"
@@ -201,6 +203,7 @@
 struct Authmethod {
 	char	*name;		/* string to compare against server's list */
 	int	(*userauth)(Authctxt *authctxt);
+	void	(*cleanup)(Authctxt *authctxt);
 	int	*enabled;	/* flag in option struct that enables method */
 	int	*batch_flag;	/* flag in option struct that disables method */
 };
@@ -212,12 +215,18 @@
 void	input_userauth_info_req(int, u_int32_t, void *);
 void	input_userauth_pk_ok(int, u_int32_t, void *);
 void	input_userauth_passwd_changereq(int, u_int32_t, void *);
+void	input_userauth_jpake_server_step1(int, u_int32_t, void *);
+void	input_userauth_jpake_server_step2(int, u_int32_t, void *);
+void	input_userauth_jpake_server_confirm(int, u_int32_t, void *);
 
 int	userauth_none(Authctxt *);
 int	userauth_pubkey(Authctxt *);
 int	userauth_passwd(Authctxt *);
 int	userauth_kbdint(Authctxt *);
 int	userauth_hostbased(Authctxt *);
+int	userauth_jpake(Authctxt *);
+
+void	userauth_jpake_cleanup(Authctxt *);
 
 #ifdef GSSAPI
 int	userauth_gssapi(Authctxt *authctxt);
@@ -243,30 +252,43 @@
 #ifdef GSSAPI
 	{"gssapi-with-mic",
 		userauth_gssapi,
+		NULL,
 		&options.gss_authentication,
 		NULL},
 #endif
 	{"hostbased",
 		userauth_hostbased,
+		NULL,
 		&options.hostbased_authentication,
 		NULL},
 	{"publickey",
 		userauth_pubkey,
+		NULL,
 		&options.pubkey_authentication,
 		NULL},
+#ifdef JPAKE
+	{"jpake-01@openssh.com",
+		userauth_jpake,
+		userauth_jpake_cleanup,
+		&options.zero_knowledge_password_authentication,
+		&options.batch_mode},
+#endif
 	{"keyboard-interactive",
 		userauth_kbdint,
+		NULL,
 		&options.kbd_interactive_authentication,
 		&options.batch_mode},
 	{"password",
 		userauth_passwd,
+		NULL,
 		&options.password_authentication,
 		&options.batch_mode},
 	{"none",
 		userauth_none,
 		NULL,
+		NULL,
 		NULL},
-	{NULL, NULL, NULL, NULL}
+	{NULL, NULL, NULL, NULL, NULL}
 };
 
 void
@@ -334,6 +356,9 @@
 void
 userauth(Authctxt *authctxt, char *authlist)
 {
+	if (authctxt->method != NULL && authctxt->method->cleanup != NULL)
+		authctxt->method->cleanup(authctxt);
+
 	if (authctxt->methoddata) {
 		xfree(authctxt->methoddata);
 		authctxt->methoddata = NULL;
@@ -851,6 +876,209 @@
 	    &input_userauth_passwd_changereq);
 }
 
+#ifdef JPAKE
+static char *
+pw_encrypt(const char *password, const char *crypt_scheme, const char *salt)
+{
+	/* OpenBSD crypt(3) handles all of these */
+	if (strcmp(crypt_scheme, "crypt") == 0 ||
+	    strcmp(crypt_scheme, "bcrypt") == 0 ||
+	    strcmp(crypt_scheme, "md5crypt") == 0 ||
+	    strcmp(crypt_scheme, "crypt-extended") == 0)
+		return xstrdup(crypt(password, salt));
+	error("%s: unsupported password encryption scheme \"%.100s\"",
+	    __func__, crypt_scheme);
+	return NULL;
+}
+
+static BIGNUM *
+jpake_password_to_secret(Authctxt *authctxt, const char *crypt_scheme,
+    const char *salt)
+{
+	char prompt[256], *password, *crypted;
+	u_char *secret;
+	u_int secret_len;
+	BIGNUM *ret;
+
+	snprintf(prompt, sizeof(prompt), "%.30s@%.128s's password (JPAKE): ",
+	    authctxt->server_user, authctxt->host);
+	password = read_passphrase(prompt, 0);
+
+	if ((crypted = pw_encrypt(password, crypt_scheme, salt)) == NULL) {
+		logit("Disabling %s authentication", authctxt->method->name);
+		authctxt->method->enabled = NULL;
+		/* Continue with an empty password to fail gracefully */
+		crypted = xstrdup("");
+	}
+
+#ifdef JPAKE_DEBUG
+	debug3("%s: salt = %s", __func__, salt);
+	debug3("%s: scheme = %s", __func__, crypt_scheme);
+	debug3("%s: crypted = %s", __func__, crypted);
+#endif
+
+	if (hash_buffer(crypted, strlen(crypted), EVP_sha256(),
+	    &secret, &secret_len) != 0)
+		fatal("%s: hash_buffer", __func__);
+
+	bzero(password, strlen(password));
+	bzero(crypted, strlen(crypted));
+	xfree(password);
+	xfree(crypted);
+
+	if ((ret = BN_bin2bn(secret, secret_len, NULL)) == NULL)
+		fatal("%s: BN_bin2bn (secret)", __func__);
+	bzero(secret, secret_len);
+	xfree(secret);
+
+	return ret;
+}
+
+/* ARGSUSED */
+void
+input_userauth_jpake_server_step1(int type, u_int32_t seq, void *ctxt)
+{
+	Authctxt *authctxt = ctxt;
+	struct jpake_ctx *pctx = authctxt->methoddata;
+	u_char *x3_proof, *x4_proof, *x2_s_proof;
+	u_int x3_proof_len, x4_proof_len, x2_s_proof_len;
+	char *crypt_scheme, *salt;
+
+	/* Disable this message */
+	dispatch_set(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP1, NULL);
+
+	if ((pctx->g_x3 = BN_new()) == NULL ||
+	    (pctx->g_x4 = BN_new()) == NULL)
+		fatal("%s: BN_new", __func__);
+
+	/* Fetch step 1 values */
+	crypt_scheme = packet_get_string(NULL);
+	salt = packet_get_string(NULL);
+	pctx->server_id = packet_get_string(&pctx->server_id_len);
+	packet_get_bignum2(pctx->g_x3);
+	packet_get_bignum2(pctx->g_x4);
+	x3_proof = packet_get_string(&x3_proof_len);
+	x4_proof = packet_get_string(&x4_proof_len);
+	packet_check_eom();
+
+	JPAKE_DEBUG_CTX((pctx, "step 1 received in %s", __func__));
+
+	/* Obtain password and derive secret */
+	pctx->s = jpake_password_to_secret(authctxt, crypt_scheme, salt);
+	bzero(crypt_scheme, strlen(crypt_scheme));
+	bzero(salt, strlen(salt));
+	xfree(crypt_scheme);
+	xfree(salt);
+	JPAKE_DEBUG_BN((pctx->s, "%s: s = ", __func__));
+
+	/* Calculate step 2 values */
+	jpake_step2(pctx->grp, pctx->s, pctx->g_x1,
+	    pctx->g_x3, pctx->g_x4, pctx->x2,
+	    pctx->server_id, pctx->server_id_len,
+	    pctx->client_id, pctx->client_id_len,
+	    x3_proof, x3_proof_len,
+	    x4_proof, x4_proof_len,
+	    &pctx->a,
+	    &x2_s_proof, &x2_s_proof_len);
+
+	bzero(x3_proof, x3_proof_len);
+	bzero(x4_proof, x4_proof_len);
+	xfree(x3_proof);
+	xfree(x4_proof);
+
+	JPAKE_DEBUG_CTX((pctx, "step 2 sending in %s", __func__));
+
+	/* Send values for step 2 */
+	packet_start(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP2);
+	packet_put_bignum2(pctx->a);
+	packet_put_string(x2_s_proof, x2_s_proof_len);
+	packet_send();
+
+	bzero(x2_s_proof, x2_s_proof_len);
+	xfree(x2_s_proof);
+
+	/* Expect step 2 packet from peer */
+	dispatch_set(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP2,
+	    input_userauth_jpake_server_step2);
+}
+
+/* ARGSUSED */
+void
+input_userauth_jpake_server_step2(int type, u_int32_t seq, void *ctxt)
+{
+	Authctxt *authctxt = ctxt;
+	struct jpake_ctx *pctx = authctxt->methoddata;
+	u_char *x4_s_proof;
+	u_int x4_s_proof_len;
+
+	/* Disable this message */
+	dispatch_set(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP2, NULL);
+
+	if ((pctx->b = BN_new()) == NULL)
+		fatal("%s: BN_new", __func__);
+
+	/* Fetch step 2 values */
+	packet_get_bignum2(pctx->b);
+	x4_s_proof = packet_get_string(&x4_s_proof_len);
+	packet_check_eom();
+
+	JPAKE_DEBUG_CTX((pctx, "step 2 received in %s", __func__));
+
+	/* Derive shared key and calculate confirmation hash */
+	jpake_key_confirm(pctx->grp, pctx->s, pctx->b,
+	    pctx->x2, pctx->g_x1, pctx->g_x2, pctx->g_x3, pctx->g_x4,
+	    pctx->client_id, pctx->client_id_len,
+	    pctx->server_id, pctx->server_id_len,
+	    session_id2, session_id2_len,
+	    x4_s_proof, x4_s_proof_len,
+	    &pctx->k,
+	    &pctx->h_k_cid_sessid, &pctx->h_k_cid_sessid_len);
+
+	bzero(x4_s_proof, x4_s_proof_len);
+	xfree(x4_s_proof);
+
+	JPAKE_DEBUG_CTX((pctx, "confirm sending in %s", __func__));
+
+	/* Send key confirmation proof */
+	packet_start(SSH2_MSG_USERAUTH_JPAKE_CLIENT_CONFIRM);
+	packet_put_string(pctx->h_k_cid_sessid, pctx->h_k_cid_sessid_len);
+	packet_send();
+
+	/* Expect confirmation from peer */
+	dispatch_set(SSH2_MSG_USERAUTH_JPAKE_SERVER_CONFIRM,
+	    input_userauth_jpake_server_confirm);
+}
+
+/* ARGSUSED */
+void
+input_userauth_jpake_server_confirm(int type, u_int32_t seq, void *ctxt)
+{
+	Authctxt *authctxt = ctxt;
+	struct jpake_ctx *pctx = authctxt->methoddata;
+
+	/* Disable this message */
+	dispatch_set(SSH2_MSG_USERAUTH_JPAKE_SERVER_CONFIRM, NULL);
+
+	pctx->h_k_sid_sessid = packet_get_string(&pctx->h_k_sid_sessid_len);
+	packet_check_eom();
+
+	JPAKE_DEBUG_CTX((pctx, "confirm received in %s", __func__));
+
+	/* Verify expected confirmation hash */
+	if (jpake_check_confirm(pctx->k,
+	    pctx->server_id, pctx->server_id_len,
+	    session_id2, session_id2_len,
+	    pctx->h_k_sid_sessid, pctx->h_k_sid_sessid_len) == 1)
+		debug("%s: %s success", __func__, authctxt->method->name);
+	else {
+		debug("%s: confirmation mismatch", __func__);
+		/* XXX stash this so if auth succeeds then we can warn/kill */
+	}
+
+	userauth_jpake_cleanup(authctxt);
+}
+#endif /* JPAKE */
+
 static int
 identity_sign(Identity *id, u_char **sigp, u_int *lenp,
     u_char *data, u_int datalen)
@@ -1425,6 +1653,76 @@
 	return 1;
 }
 
+#ifdef JPAKE
+int
+userauth_jpake(Authctxt *authctxt)
+{
+	struct jpake_ctx *pctx;
+	u_char *x1_proof, *x2_proof;
+	u_int x1_proof_len, x2_proof_len;
+	static int attempt = 0; /* XXX share with userauth_password's? */
+
+	if (attempt++ >= options.number_of_password_prompts)
+		return 0;
+	if (attempt != 1)
+		error("Permission denied, please try again.");
+
+	if (authctxt->methoddata != NULL)
+		fatal("%s: authctxt->methoddata already set (%p)",
+		    __func__, authctxt->methoddata);
+
+	authctxt->methoddata = pctx = jpake_new();
+
+	/*
+	 * Send request immediately, to get the protocol going while
+	 * we do the initial computations.
+	 */
+	packet_start(SSH2_MSG_USERAUTH_REQUEST);
+	packet_put_cstring(authctxt->server_user);
+	packet_put_cstring(authctxt->service);
+	packet_put_cstring(authctxt->method->name);
+	packet_send();
+	packet_write_wait();
+
+	jpake_step1(pctx->grp,
+	    &pctx->client_id, &pctx->client_id_len,
+	    &pctx->x1, &pctx->x2, &pctx->g_x1, &pctx->g_x2,
+	    &x1_proof, &x1_proof_len,
+	    &x2_proof, &x2_proof_len);
+
+	JPAKE_DEBUG_CTX((pctx, "step 1 sending in %s", __func__));
+
+	packet_start(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP1);
+	packet_put_string(pctx->client_id, pctx->client_id_len);
+	packet_put_bignum2(pctx->g_x1);
+	packet_put_bignum2(pctx->g_x2);
+	packet_put_string(x1_proof, x1_proof_len);
+	packet_put_string(x2_proof, x2_proof_len);
+	packet_send();
+
+	bzero(x1_proof, x1_proof_len);
+	bzero(x2_proof, x2_proof_len);
+	xfree(x1_proof);
+	xfree(x2_proof);
+
+	/* Expect step 1 packet from peer */
+	dispatch_set(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP1,
+	    input_userauth_jpake_server_step1);
+
+	return 1;
+}
+
+void
+userauth_jpake_cleanup(Authctxt *authctxt)
+{
+	debug3("%s: clean up", __func__);
+	if (authctxt->methoddata != NULL) {
+		jpake_free(authctxt->methoddata);
+		authctxt->methoddata = NULL;
+	}
+}
+#endif /* JPAKE */
+
 /* find auth method */
 
 /*
@@ -1526,3 +1824,4 @@
 	buffer_free(&b);
 	return list;
 }
+