summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog21
-rw-r--r--Makefile.in6
-rw-r--r--auth.h6
-rw-r--r--auth2-jpake.c557
-rw-r--r--auth2.c12
-rw-r--r--jpake.c604
-rw-r--r--jpake.h134
-rw-r--r--monitor.c227
-rw-r--r--monitor.h9
-rw-r--r--monitor_wrap.c167
-rw-r--r--monitor_wrap.h22
-rw-r--r--readconf.c18
-rw-r--r--readconf.h3
-rw-r--r--schnorr.c407
-rw-r--r--servconf.c20
-rw-r--r--servconf.h4
-rw-r--r--ssh2.h9
-rw-r--r--ssh_config.515
-rw-r--r--sshconnect2.c303
-rw-r--r--sshd_config.518
20 files changed, 2537 insertions, 25 deletions
diff --git a/ChangeLog b/ChangeLog
index cf80ff1e..012749b0 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -7,6 +7,25 @@
[auth.c]
need unistd.h for close() prototype
(ID sync only)
+ - 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@
20081103
- OpenBSD CVS Sync
@@ -4857,4 +4876,4 @@
OpenServer 6 and add osr5bigcrypt support so when someone migrates
passwords between UnixWare and OpenServer they will still work. OK dtucker@
-$Id: ChangeLog,v 1.5129 2008/11/05 05:12:54 djm Exp $
+$Id: ChangeLog,v 1.5130 2008/11/05 05:20:46 djm Exp $
diff --git a/Makefile.in b/Makefile.in
index c1b7ab5a..312b8d2b 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -1,4 +1,4 @@
-# $Id: Makefile.in,v 1.297 2008/07/08 14:21:12 djm Exp $
+# $Id: Makefile.in,v 1.298 2008/11/05 05:20:46 djm Exp $
# uncomment if you run a non bourne compatable shell. Ie. csh
#SHELL = @SH@
@@ -71,7 +71,7 @@ LIBSSH_OBJS=acss.o authfd.o authfile.o bufaux.o bufbn.o buffer.o \
atomicio.o key.o dispatch.o kex.o mac.o uidswap.o uuencode.o misc.o \
monitor_fdpass.o rijndael.o ssh-dss.o ssh-rsa.o dh.o kexdh.o \
kexgex.o kexdhc.o kexgexc.o scard.o msg.o progressmeter.o dns.o \
- entropy.o scard-opensc.o gss-genr.o umac.o
+ entropy.o scard-opensc.o gss-genr.o umac.o jpake.o schnorr.o
SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \
sshconnect.o sshconnect1.o sshconnect2.o mux.o
@@ -81,7 +81,7 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o auth-rsa.o auth-rh-rsa.o \
auth.o auth1.o auth2.o auth-options.o session.o \
auth-chall.o auth2-chall.o groupaccess.o \
auth-skey.o auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \
- auth2-none.o auth2-passwd.o auth2-pubkey.o \
+ auth2-none.o auth2-passwd.o auth2-pubkey.o auth2-jpake.o \
monitor_mm.o monitor.o monitor_wrap.o kexdhs.o kexgexs.o \
auth-krb5.o \
auth2-gss.o gss-serv.o gss-serv-krb5.o \
diff --git a/auth.h b/auth.h
index 6a70f0eb..3a70f442 100644
--- a/auth.h
+++ b/auth.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth.h,v 1.61 2008/07/02 12:03:51 dtucker Exp $ */
+/* $OpenBSD: auth.h,v 1.62 2008/11/04 08:22:12 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
@@ -59,6 +59,7 @@ struct Authctxt {
struct passwd *pw; /* set if 'valid' */
char *style;
void *kbdintctxt;
+ void *jpake_ctx;
#ifdef BSD_AUTH
auth_session_t *as;
#endif
@@ -156,6 +157,9 @@ int bsdauth_respond(void *, u_int, char **);
int skey_query(void *, char **, char **, u_int *, char ***, u_int **);
int skey_respond(void *, u_int, char **);
+void auth2_jpake_get_pwdata(Authctxt *, BIGNUM **, char **, char **);
+void auth2_jpake_stop(Authctxt *);
+
int allowed_user(struct passwd *);
struct passwd * getpwnamallow(const char *user);
diff --git a/auth2-jpake.c b/auth2-jpake.c
new file mode 100644
index 00000000..0029ec26
--- /dev/null
+++ b/auth2-jpake.c
@@ -0,0 +1,557 @@
+/* $OpenBSD: auth2-jpake.c,v 1.1 2008/11/04 08:22:12 djm Exp $ */
+/*
+ * Copyright (c) 2008 Damien Miller. All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Server side of zero-knowledge password auth using J-PAKE protocol
+ * as described in:
+ *
+ * F. Hao, P. Ryan, "Password Authenticated Key Exchange by Juggling",
+ * 16th Workshop on Security Protocols, Cambridge, April 2008
+ *
+ * http://grouper.ieee.org/groups/1363/Research/contributions/hao-ryan-2008.pdf
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+
+#include <pwd.h>
+#include <stdio.h>
+#include <string.h>
+#include <login_cap.h>
+
+#include <openssl/bn.h>
+#include <openssl/evp.h>
+
+#include "xmalloc.h"
+#include "ssh2.h"
+#include "key.h"
+#include "hostfile.h"
+#include "buffer.h"
+#include "auth.h"
+#include "packet.h"
+#include "dispatch.h"
+#include "log.h"
+#include "servconf.h"
+#include "auth-options.h"
+#include "canohost.h"
+#ifdef GSSAPI
+#include "ssh-gss.h"
+#endif
+#include "monitor_wrap.h"
+
+#include "jpake.h"
+
+#ifdef JPAKE
+
+/*
+ * XXX options->permit_empty_passwd (at the moment, they will be refused
+ * anyway because they will mismatch on fake salt.
+ */
+
+/* Dispatch handlers */
+static void input_userauth_jpake_client_step1(int, u_int32_t, void *);
+static void input_userauth_jpake_client_step2(int, u_int32_t, void *);
+static void input_userauth_jpake_client_confirm(int, u_int32_t, void *);
+
+static int auth2_jpake_start(Authctxt *);
+
+/* import */
+extern ServerOptions options;
+extern u_char *session_id2;
+extern u_int session_id2_len;
+
+/*
+ * Attempt J-PAKE authentication.
+ */
+static int
+userauth_jpake(Authctxt *authctxt)
+{
+ int authenticated = 0;
+
+ packet_check_eom();
+
+ debug("jpake-01@openssh.com requested");
+
+ if (authctxt->user != NULL) {
+ if (authctxt->jpake_ctx == NULL)
+ authctxt->jpake_ctx = jpake_new();
+ if (options.zero_knowledge_password_authentication)
+ authenticated = auth2_jpake_start(authctxt);
+ }
+
+ return authenticated;
+}
+
+Authmethod method_jpake = {
+ "jpake-01@openssh.com",
+ userauth_jpake,
+ &options.zero_knowledge_password_authentication
+};
+
+/* Clear context and callbacks */
+void
+auth2_jpake_stop(Authctxt *authctxt)
+{
+ /* unregister callbacks */
+ dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP1, NULL);
+ dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP2, NULL);
+ dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_CONFIRM, NULL);
+ if (authctxt->jpake_ctx != NULL) {
+ jpake_free(authctxt->jpake_ctx);
+ authctxt->jpake_ctx = NULL;
+ }
+}
+
+/* Returns 1 if 'c' is a valid crypt(3) salt character, 0 otherwise */
+static int
+valid_crypt_salt(int c)
+{
+ if (c >= 'A' && c <= 'Z')
+ return 1;
+ if (c >= 'a' && c <= 'z')
+ return 1;
+ if (c >= '.' && c <= '9')
+ return 1;
+ return 0;
+}
+
+/*
+ * Derive fake salt as H(username || first_private_host_key)
+ * This provides relatively stable fake salts for non-existent
+ * users and avoids the jpake method becoming an account validity
+ * oracle.
+ */
+static void
+derive_rawsalt(const char *username, u_char *rawsalt, u_int len)
+{
+ u_char *digest;
+ u_int digest_len;
+ Buffer b;
+ Key *k;
+
+ buffer_init(&b);
+ buffer_put_cstring(&b, username);
+ if ((k = get_hostkey_by_index(0)) == NULL ||
+ (k->flags & KEY_FLAG_EXT))
+ fatal("%s: no hostkeys", __func__);
+ switch (k->type) {
+ case KEY_RSA1:
+ case KEY_RSA:
+ if (k->rsa->p == NULL || k->rsa->q == NULL)
+ fatal("%s: RSA key missing p and/or q", __func__);
+ buffer_put_bignum2(&b, k->rsa->p);
+ buffer_put_bignum2(&b, k->rsa->q);
+ break;
+ case KEY_DSA:
+ if (k->dsa->priv_key == NULL)
+ fatal("%s: DSA key missing priv_key", __func__);
+ buffer_put_bignum2(&b, k->dsa->priv_key);
+ break;
+ default:
+ fatal("%s: unknown key type %d", __func__, k->type);
+ }
+ if (hash_buffer(buffer_ptr(&b), buffer_len(&b), EVP_sha256(),
+ &digest, &digest_len) != 0)
+ fatal("%s: hash_buffer", __func__);
+ buffer_free(&b);
+ if (len > digest_len)
+ fatal("%s: not enough bytes for rawsalt (want %u have %u)",
+ __func__, len, digest_len);
+ memcpy(rawsalt, digest, len);
+ bzero(digest, digest_len);
+ xfree(digest);
+}
+
+/* ASCII an integer [0, 64) for inclusion in a password/salt */
+static char
+pw_encode64(u_int i64)
+{
+ const u_char e64[] =
+ "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ return e64[i64 % 64];
+}
+
+/* Generate ASCII salt bytes for user */
+static char *
+makesalt(u_int want, const char *user)
+{
+ u_char rawsalt[32];
+ static char ret[33];
+ u_int i;
+
+ if (want > sizeof(ret) - 1)
+ fatal("%s: want %u", __func__, want);
+
+ derive_rawsalt(user, rawsalt, sizeof(rawsalt));
+ bzero(ret, sizeof(ret));
+ for (i = 0; i < want; i++)
+ ret[i] = pw_encode64(rawsalt[i]);
+ bzero(rawsalt, sizeof(rawsalt));
+
+ return ret;
+}
+
+/*
+ * Select the system's default password hashing scheme and generate
+ * a stable fake salt under it for use by a non-existent account.
+ * Prevents jpake method being used to infer the validity of accounts.
+ */
+static void
+fake_salt_and_scheme(Authctxt *authctxt, char **salt, char **scheme)
+{
+ char *rounds_s, *style;
+ long long rounds;
+ login_cap_t *lc;
+
+
+ if ((lc = login_getclass(authctxt->pw->pw_class)) == NULL &&
+ (lc = login_getclass(NULL)) == NULL)
+ fatal("%s: login_getclass failed", __func__);
+ style = login_getcapstr(lc, "localcipher", NULL, NULL);
+ if (style == NULL)
+ style = xstrdup("blowfish,6");
+ login_close(lc);
+
+ if ((rounds_s = strchr(style, ',')) != NULL)
+ *rounds_s++ = '\0';
+ rounds = strtonum(rounds_s, 1, 1<<31, NULL);
+
+ if (strcmp(style, "md5") == 0) {
+ xasprintf(salt, "$1$%s$", makesalt(8, authctxt->user));
+ *scheme = xstrdup("md5");
+ } else if (strcmp(style, "old") == 0) {
+ *salt = xstrdup(makesalt(2, authctxt->user));
+ *scheme = xstrdup("crypt");
+ } else if (strcmp(style, "newsalt") == 0) {
+ rounds = MAX(rounds, 7250);
+ rounds = MIN(rounds, (1<<24) - 1);
+ xasprintf(salt, "_%c%c%c%c%s",
+ pw_encode64(rounds), pw_encode64(rounds >> 6),
+ pw_encode64(rounds >> 12), pw_encode64(rounds >> 18),
+ makesalt(4, authctxt->user));
+ *scheme = xstrdup("crypt-extended");
+ } else {
+ /* Default to blowfish */
+ rounds = MAX(rounds, 3);
+ rounds = MIN(rounds, 31);
+ xasprintf(salt, "$2a$%02lld$%s", rounds,
+ makesalt(22, authctxt->user));
+ *scheme = xstrdup("bcrypt");
+ }
+ xfree(style);
+ debug3("%s: fake %s salt for user %s: %s",
+ __func__, *scheme, authctxt->user, *salt);
+}
+
+/*
+ * Fetch password hashing scheme, password salt and derive shared secret
+ * for user. If user does not exist, a fake but stable and user-unique
+ * salt will be returned.
+ */
+void
+auth2_jpake_get_pwdata(Authctxt *authctxt, BIGNUM **s,
+ char **hash_scheme, char **salt)
+{
+ char *cp;
+ u_char *secret;
+ u_int secret_len, salt_len;
+
+#ifdef JPAKE_DEBUG
+ debug3("%s: valid %d pw %.5s...", __func__,
+ authctxt->valid, authctxt->pw->pw_passwd);
+#endif
+
+ *salt = NULL;
+ *hash_scheme = NULL;
+ if (authctxt->valid) {
+ if (strncmp(authctxt->pw->pw_passwd, "$2$", 3) == 0 &&
+ strlen(authctxt->pw->pw_passwd) > 28) {
+ /*
+ * old-variant bcrypt:
+ * "$2$", 2 digit rounds, "$", 22 bytes salt
+ */
+ salt_len = 3 + 2 + 1 + 22 + 1;
+ *salt = xmalloc(salt_len);
+ strlcpy(*salt, authctxt->pw->pw_passwd, salt_len);
+ *hash_scheme = xstrdup("bcrypt");
+ } else if (strncmp(authctxt->pw->pw_passwd, "$2a$", 4) == 0 &&
+ strlen(authctxt->pw->pw_passwd) > 29) {
+ /*
+ * current-variant bcrypt:
+ * "$2a$", 2 digit rounds, "$", 22 bytes salt
+ */
+ salt_len = 4 + 2 + 1 + 22 + 1;
+ *salt = xmalloc(salt_len);
+ strlcpy(*salt, authctxt->pw->pw_passwd, salt_len);
+ *hash_scheme = xstrdup("bcrypt");
+ } else if (strncmp(authctxt->pw->pw_passwd, "$1$", 3) == 0 &&
+ strlen(authctxt->pw->pw_passwd) > 5) {
+ /*
+ * md5crypt:
+ * "$1$", salt until "$"
+ */
+ cp = strchr(authctxt->pw->pw_passwd + 3, '$');
+ if (cp != NULL) {
+ salt_len = (cp - authctxt->pw->pw_passwd) + 1;
+ *salt = xmalloc(salt_len);
+ strlcpy(*salt, authctxt->pw->pw_passwd,
+ salt_len);
+ *hash_scheme = xstrdup("md5crypt");
+ }
+ } else if (strncmp(authctxt->pw->pw_passwd, "_", 1) == 0 &&
+ strlen(authctxt->pw->pw_passwd) > 9) {
+ /*
+ * BSDI extended crypt:
+ * "_", 4 digits count, 4 chars salt
+ */
+ salt_len = 1 + 4 + 4 + 1;
+ *salt = xmalloc(salt_len);
+ strlcpy(*salt, authctxt->pw->pw_passwd, salt_len);
+ *hash_scheme = xstrdup("crypt-extended");
+ } else if (strlen(authctxt->pw->pw_passwd) == 13 &&
+ valid_crypt_salt(authctxt->pw->pw_passwd[0]) &&
+ valid_crypt_salt(authctxt->pw->pw_passwd[1])) {
+ /*
+ * traditional crypt:
+ * 2 chars salt
+ */
+ salt_len = 2 + 1;
+ *salt = xmalloc(salt_len);
+ strlcpy(*salt, authctxt->pw->pw_passwd, salt_len);
+ *hash_scheme = xstrdup("crypt");
+ }
+ if (*salt == NULL) {
+ debug("%s: unrecognised crypt scheme for user %s",
+ __func__, authctxt->pw->pw_name);
+ }
+ }
+ if (*salt == NULL)
+ fake_salt_and_scheme(authctxt, salt, hash_scheme);
+
+ if (hash_buffer(authctxt->pw->pw_passwd,
+ strlen(authctxt->pw->pw_passwd), EVP_sha256(),
+ &secret, &secret_len) != 0)
+ fatal("%s: hash_buffer", __func__);
+ if ((*s = BN_bin2bn(secret, secret_len, NULL)) == NULL)
+ fatal("%s: BN_bin2bn (secret)", __func__);
+#ifdef JPAKE_DEBUG
+ debug3("%s: salt = %s (len %u)", __func__,
+ *salt, (u_int)strlen(*salt));
+ debug3("%s: scheme = %s", __func__, *hash_scheme);
+ JPAKE_DEBUG_BN((*s, "%s: s = ", __func__));
+#endif
+ bzero(secret, secret_len);
+ xfree(secret);
+}
+
+/*
+ * Being authentication attempt.
+ * Note, sets authctxt->postponed while in subprotocol
+ */
+static int
+auth2_jpake_start(Authctxt *authctxt)
+{
+ struct jpake_ctx *pctx = authctxt->jpake_ctx;
+ u_char *x3_proof, *x4_proof;
+ u_int x3_proof_len, x4_proof_len;
+ char *salt, *hash_scheme;
+
+ debug("%s: start", __func__);
+
+ PRIVSEP(jpake_step1(pctx->grp,
+ &pctx->server_id, &pctx->server_id_len,
+ &pctx->x3, &pctx->x4, &pctx->g_x3, &pctx->g_x4,
+ &x3_proof, &x3_proof_len,
+ &x4_proof, &x4_proof_len));
+
+ PRIVSEP(auth2_jpake_get_pwdata(authctxt, &pctx->s,
+ &hash_scheme, &salt));
+
+ if (!use_privsep)
+ JPAKE_DEBUG_CTX((pctx, "step 1 sending in %s", __func__));
+
+ packet_start(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP1);
+ packet_put_cstring(hash_scheme);
+ packet_put_cstring(salt);
+ packet_put_string(pctx->server_id, pctx->server_id_len);
+ packet_put_bignum2(pctx->g_x3);
+ packet_put_bignum2(pctx->g_x4);
+ packet_put_string(x3_proof, x3_proof_len);
+ packet_put_string(x4_proof, x4_proof_len);
+ packet_send();
+ packet_write_wait();
+
+ bzero(hash_scheme, strlen(hash_scheme));
+ bzero(salt, strlen(salt));
+ xfree(hash_scheme);
+ xfree(salt);
+ bzero(x3_proof, x3_proof_len);
+ bzero(x4_proof, x4_proof_len);
+ xfree(x3_proof);
+ xfree(x4_proof);
+
+ /* Expect step 1 packet from peer */
+ dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP1,
+ input_userauth_jpake_client_step1);
+
+ authctxt->postponed = 1;
+ return 0;
+}
+
+/* ARGSUSED */
+static void
+input_userauth_jpake_client_step1(int type, u_int32_t seq, void *ctxt)
+{
+ Authctxt *authctxt = ctxt;
+ struct jpake_ctx *pctx = authctxt->jpake_ctx;
+ u_char *x1_proof, *x2_proof, *x4_s_proof;
+ u_int x1_proof_len, x2_proof_len, x4_s_proof_len;
+
+ /* Disable this message */
+ dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP1, NULL);
+
+ /* Fetch step 1 values */
+ if ((pctx->g_x1 = BN_new()) == NULL ||
+ (pctx->g_x2 = BN_new()) == NULL)
+ fatal("%s: BN_new", __func__);
+ pctx->client_id = packet_get_string(&pctx->client_id_len);
+ packet_get_bignum2(pctx->g_x1);
+ packet_get_bignum2(pctx->g_x2);
+ x1_proof = packet_get_string(&x1_proof_len);
+ x2_proof = packet_get_string(&x2_proof_len);
+ packet_check_eom();
+
+ if (!use_privsep)
+ JPAKE_DEBUG_CTX((pctx, "step 1 received in %s", __func__));
+
+ PRIVSEP(jpake_step2(pctx->grp, pctx->s, pctx->g_x3,
+ pctx->g_x1, pctx->g_x2, pctx->x4,
+ pctx->client_id, pctx->client_id_len,
+ pctx->server_id, pctx->server_id_len,
+ x1_proof, x1_proof_len,
+ x2_proof, x2_proof_len,
+ &pctx->b,
+ &x4_s_proof, &x4_s_proof_len));
+
+ bzero(x1_proof, x1_proof_len);
+ bzero(x2_proof, x2_proof_len);
+ xfree(x1_proof);
+ xfree(x2_proof);
+
+ if (!use_privsep)
+ JPAKE_DEBUG_CTX((pctx, "step 2 sending in %s", __func__));
+
+ /* Send values for step 2 */
+ packet_start(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP2);
+ packet_put_bignum2(pctx->b);
+ packet_put_string(x4_s_proof, x4_s_proof_len);
+ packet_send();
+ packet_write_wait();
+
+ bzero(x4_s_proof, x4_s_proof_len);
+ xfree(x4_s_proof);
+
+ /* Expect step 2 packet from peer */
+ dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP2,
+ input_userauth_jpake_client_step2);
+}
+
+/* ARGSUSED */
+static void
+input_userauth_jpake_client_step2(int type, u_int32_t seq, void *ctxt)
+{
+ Authctxt *authctxt = ctxt;
+ struct jpake_ctx *pctx = authctxt->jpake_ctx;
+ u_char *x2_s_proof;
+ u_int x2_s_proof_len;
+
+ /* Disable this message */
+ dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP2, NULL);
+
+ if ((pctx->a = BN_new()) == NULL)
+ fatal("%s: BN_new", __func__);
+
+ /* Fetch step 2 values */
+ packet_get_bignum2(pctx->a);
+ x2_s_proof = packet_get_string(&x2_s_proof_len);
+ packet_check_eom();
+
+ if (!use_privsep)
+ JPAKE_DEBUG_CTX((pctx, "step 2 received in %s", __func__));
+
+ /* Derive shared key and calculate confirmation hash */
+ PRIVSEP(jpake_key_confirm(pctx->grp, pctx->s, pctx->a,
+ pctx->x4, pctx->g_x3, pctx->g_x4, pctx->g_x1, pctx->g_x2,
+ pctx->server_id, pctx->server_id_len,
+ pctx->client_id, pctx->client_id_len,
+ session_id2, session_id2_len,
+ x2_s_proof, x2_s_proof_len,
+ &pctx->k,
+ &pctx->h_k_sid_sessid, &pctx->h_k_sid_sessid_len));
+
+ bzero(x2_s_proof, x2_s_proof_len);
+ xfree(x2_s_proof);
+
+ if (!use_privsep)
+ JPAKE_DEBUG_CTX((pctx, "confirm sending in %s", __func__));
+
+ /* Send key confirmation proof */
+ packet_start(SSH2_MSG_USERAUTH_JPAKE_SERVER_CONFIRM);
+ packet_put_string(pctx->h_k_sid_sessid, pctx->h_k_sid_sessid_len);
+ packet_send();
+ packet_write_wait();
+
+ /* Expect confirmation from peer */
+ dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_CONFIRM,
+ input_userauth_jpake_client_confirm);
+}
+
+/* ARGSUSED */
+static void
+input_userauth_jpake_client_confirm(int type, u_int32_t seq, void *ctxt)
+{
+ Authctxt *authctxt = ctxt;
+ struct jpake_ctx *pctx = authctxt->jpake_ctx;
+ int authenticated = 0;
+
+ /* Disable this message */
+ dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_CONFIRM, NULL);
+
+ pctx->h_k_cid_sessid = packet_get_string(&pctx->h_k_cid_sessid_len);
+ packet_check_eom();
+
+ if (!use_privsep)
+ JPAKE_DEBUG_CTX((pctx, "confirm received in %s", __func__));
+
+ /* Verify expected confirmation hash */
+ if (PRIVSEP(jpake_check_confirm(pctx->k,
+ pctx->client_id, pctx->client_id_len,
+ session_id2, session_id2_len,
+ pctx->h_k_cid_sessid, pctx->h_k_cid_sessid_len)) == 1)
+ authenticated = authctxt->valid ? 1 : 0;
+ else
+ debug("%s: confirmation mismatch", __func__);
+
+ /* done */
+ authctxt->postponed = 0;
+ jpake_free(authctxt->jpake_ctx);
+ authctxt->jpake_ctx = NULL;
+ userauth_finish(authctxt, authenticated, method_jpake.name);
+}
+
+#endif /* JPAKE */
+
diff --git a/auth2.c b/auth2.c
index a835abfc..ecf85705 100644
--- a/auth2.c
+++ b/auth2.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth2.c,v 1.119 2008/07/04 23:30:16 djm Exp $ */
+/* $OpenBSD: auth2.c,v 1.120 2008/11/04 08:22:12 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
*
@@ -71,6 +71,9 @@ extern Authmethod method_hostbased;
#ifdef GSSAPI
extern Authmethod method_gssapi;
#endif
+#ifdef JPAKE
+extern Authmethod method_jpake;
+#endif
Authmethod *authmethods[] = {
&method_none,
@@ -78,6 +81,9 @@ Authmethod *authmethods[] = {
#ifdef GSSAPI
&method_gssapi,
#endif
+#ifdef JPAKE
+ &method_jpake,
+#endif
&method_passwd,
&method_kbdint,
&method_hostbased,
@@ -257,8 +263,12 @@ input_userauth_request(int type, u_int32_t seq, void *ctxt)
}
/* reset state */
auth2_challenge_stop(authctxt);
+#ifdef JPAKE
+ auth2_jpake_stop(authctxt);
+#endif
#ifdef GSSAPI
+ /* XXX move to auth2_gssapi_stop() */
dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL);
dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, NULL);
#endif
diff --git a/jpake.c b/jpake.c
new file mode 100644
index 00000000..565f2e25
--- /dev/null
+++ b/jpake.c
@@ -0,0 +1,604 @@
+/* $OpenBSD: jpake.c,v 1.1 2008/11/04 08:22:12 djm Exp $ */
+/*
+ * Copyright (c) 2008 Damien Miller. All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Shared components of zero-knowledge password auth using J-PAKE protocol
+ * as described in:
+ *
+ * F. Hao, P. Ryan, "Password Authenticated Key Exchange by Juggling",
+ * 16th Workshop on Security Protocols, Cambridge, April 2008
+ *
+ * http://grouper.ieee.org/groups/1363/Research/contributions/hao-ryan-2008.pdf
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <openssl/bn.h>
+#include <openssl/evp.h>
+
+#include "xmalloc.h"
+#include "ssh2.h"
+#include "key.h"
+#include "hostfile.h"
+#include "auth.h"
+#include "buffer.h"
+#include "packet.h"
+#include "dispatch.h"
+#include "log.h"
+
+#include "jpake.h"
+
+#ifdef JPAKE
+
+/* RFC3526 group 5, 1536 bits */
+#define JPAKE_GROUP_G "2"
+#define JPAKE_GROUP_P \
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74" \
+ "020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437" \
+ "4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05" \
+ "98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB" \
+ "9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF"
+
+struct jpake_group *
+jpake_default_group(void)
+{
+ struct jpake_group *ret;
+
+ ret = xmalloc(sizeof(*ret));
+ ret->p = ret->q = ret->g = NULL;
+ if (BN_hex2bn(&ret->p, JPAKE_GROUP_P) == 0 ||
+ BN_hex2bn(&ret->g, JPAKE_GROUP_G) == 0)
+ fatal("%s: BN_hex2bn", __func__);
+ /* Subgroup order is p/2 (p is a safe prime) */
+ if ((ret->q = BN_new()) == NULL)
+ fatal("%s: BN_new", __func__);
+ if (BN_rshift1(ret->q, ret->p) != 1)
+ fatal("%s: BN_rshift1", __func__);
+
+ return ret;
+}
+
+/*
+ * Generate uniformly distributed random number in range (1, high).
+ * Return number on success, NULL on failure.
+ */
+BIGNUM *
+bn_rand_range_gt_one(const BIGNUM *high)
+{
+ BIGNUM *r, *tmp;
+ int success = -1;
+
+ if ((tmp = BN_new()) == NULL) {
+ error("%s: BN_new", __func__);
+ return NULL;
+ }
+ if ((r = BN_new()) == NULL) {
+ error("%s: BN_new failed", __func__);
+ goto out;
+ }
+ if (BN_set_word(tmp, 2) != 1) {
+ error("%s: BN_set_word(tmp, 2)", __func__);
+ goto out;
+ }
+ if (BN_sub(tmp, high, tmp) == -1) {
+ error("%s: BN_sub failed (tmp = high - 2)", __func__);
+ goto out;
+ }
+ if (BN_rand_range(r, tmp) == -1) {
+ error("%s: BN_rand_range failed", __func__);
+ goto out;
+ }
+ if (BN_set_word(tmp, 2) != 1) {
+ error("%s: BN_set_word(tmp, 2)", __func__);
+ goto out;
+ }
+ if (BN_add(r, r, tmp) == -1) {
+ error("%s: BN_add failed (r = r + 2)", __func__);
+ goto out;
+ }
+ success = 0;
+ out:
+ BN_clear_free(tmp);
+ if (success == 0)
+ return r;
+ BN_clear_free(r);
+ return NULL;
+}
+
+/*
+ * Hash contents of buffer 'b' with hash 'md'. Returns 0 on success,
+ * with digest via 'digestp' (caller to free) and length via 'lenp'.
+ * Returns -1 on failure.
+ */
+int
+hash_buffer(const u_char *buf, u_int len, const EVP_MD *md,
+ u_char **digestp, u_int *lenp)
+{
+ u_char digest[EVP_MAX_MD_SIZE];
+ u_int digest_len;
+ EVP_MD_CTX evp_md_ctx;
+ int success = -1;
+
+ EVP_MD_CTX_init(&evp_md_ctx);
+
+ if (EVP_DigestInit_ex(&evp_md_ctx, md, NULL) != 1) {
+ error("%s: EVP_DigestInit_ex", __func__);
+ goto out;
+ }
+ if (EVP_DigestUpdate(&evp_md_ctx, buf, len) != 1) {
+ error("%s: EVP_DigestUpdate", __func__);
+ goto out;
+ }
+ if (EVP_DigestFinal_ex(&evp_md_ctx, digest, &digest_len) != 1) {
+ error("%s: EVP_DigestFinal_ex", __func__);
+ goto out;
+ }
+ *digestp = xmalloc(digest_len);
+ *lenp = digest_len;
+ memcpy(*digestp, digest, *lenp);
+ success = 0;
+ out:
+ EVP_MD_CTX_cleanup(&evp_md_ctx);
+ bzero(digest, sizeof(digest));
+ digest_len = 0;
+ return success;
+}
+
+/* print formatted string followed by bignum */
+void
+jpake_debug3_bn(const BIGNUM *n, const char *fmt, ...)
+{
+ char *out, *h;
+ va_list args;
+
+ out = NULL;
+ va_start(args, fmt);
+ vasprintf(&out, fmt, args);
+ va_end(args);
+ if (out == NULL)
+ fatal("%s: vasprintf failed", __func__);
+
+ if (n == NULL)
+ debug3("%s(null)", out);
+ else {
+ h = BN_bn2hex(n);
+ debug3("%s0x%s", out, h);
+ free(h);
+ }
+ free(out);
+}
+
+/* print formatted string followed by buffer contents in hex */
+void
+jpake_debug3_buf(const u_char *buf, u_int len, const char *fmt, ...)
+{
+ char *out, h[65];
+ u_int i, j;
+ va_list args;
+
+ out = NULL;
+ va_start(args, fmt);
+ vasprintf(&out, fmt, args);
+ va_end(args);
+ if (out == NULL)
+ fatal("%s: vasprintf failed", __func__);
+
+ debug3("%s length %u%s", out, len, buf == NULL ? " (null)" : "");
+ free(out);
+ if (buf == NULL)
+ return;
+
+ *h = '\0';
+ for (i = j = 0; i < len; i++) {
+ snprintf(h + j, sizeof(h) - j, "%02x", buf[i]);
+ j += 2;
+ if (j >= sizeof(h) - 1 || i == len - 1) {
+ debug3(" %s", h);
+ *h = '\0';
+ j = 0;
+ }
+ }
+}
+
+struct jpake_ctx *
+jpake_new(void)
+{
+ struct jpake_ctx *ret;
+
+ ret = xcalloc(1, sizeof(*ret));
+
+ ret->grp = jpake_default_group();
+
+ ret->s = ret->k = NULL;
+ ret->x1 = ret->x2 = ret->x3 = ret->x4 = NULL;
+ ret->g_x1 = ret->g_x2 = ret->g_x3 = ret->g_x4 = NULL;
+ ret->a = ret->b = NULL;
+
+ ret->client_id = ret->server_id = NULL;
+ ret->h_k_cid_sessid = ret->h_k_sid_sessid = NULL;
+
+ debug3("%s: alloc %p", __func__, ret);
+
+ return ret;
+}
+
+
+void
+jpake_free(struct jpake_ctx *pctx)
+{
+ debug3("%s: free %p", __func__, pctx);
+
+#define JPAKE_BN_CLEAR_FREE(v) \
+ do { \
+ if ((v) != NULL) { \
+ BN_clear_free(v); \
+ (v) = NULL; \
+ } \
+ } while (0)
+#define JPAKE_BUF_CLEAR_FREE(v, l) \
+ do { \
+ if ((v) != NULL) { \
+ bzero((v), (l)); \
+ xfree(v); \
+ (v) = NULL; \
+ (l) = 0; \
+ } \
+ } while (0)
+
+ JPAKE_BN_CLEAR_FREE(pctx->s);
+ JPAKE_BN_CLEAR_FREE(pctx->k);
+ JPAKE_BN_CLEAR_FREE(pctx->x1);
+ JPAKE_BN_CLEAR_FREE(pctx->x2);
+ JPAKE_BN_CLEAR_FREE(pctx->x3);
+ JPAKE_BN_CLEAR_FREE(pctx->x4);
+ JPAKE_BN_CLEAR_FREE(pctx->g_x1);
+ JPAKE_BN_CLEAR_FREE(pctx->g_x2);
+ JPAKE_BN_CLEAR_FREE(pctx->g_x3);
+ JPAKE_BN_CLEAR_FREE(pctx->g_x4);
+ JPAKE_BN_CLEAR_FREE(pctx->a);
+ JPAKE_BN_CLEAR_FREE(pctx->b);
+
+ JPAKE_BUF_CLEAR_FREE(pctx->client_id, pctx->client_id_len);
+ JPAKE_BUF_CLEAR_FREE(pctx->server_id, pctx->server_id_len);
+ JPAKE_BUF_CLEAR_FREE(pctx->h_k_cid_sessid, pctx->h_k_cid_sessid_len);
+ JPAKE_BUF_CLEAR_FREE(pctx->h_k_sid_sessid, pctx->h_k_sid_sessid_len);
+
+#undef JPAKE_BN_CLEAR_FREE
+#undef JPAKE_BUF_CLEAR_FREE
+
+ bzero(pctx, sizeof(pctx));
+ xfree(pctx);
+}
+
+/* dump entire jpake_ctx. NB. includes private values! */
+void
+jpake_dump(struct jpake_ctx *pctx, const char *fmt, ...)
+{
+ char *out;
+ va_list args;
+
+ out = NULL;
+ va_start(args, fmt);
+ vasprintf(&out, fmt, args);
+ va_end(args);
+ if (out == NULL)
+ fatal("%s: vasprintf failed", __func__);
+
+ debug3("%s: %s (ctx at %p)", __func__, out, pctx);
+ if (pctx == NULL) {
+ free(out);
+ return;
+ }
+
+#define JPAKE_DUMP_BN(a) do { \
+ if ((a) != NULL) \
+ JPAKE_DEBUG_BN(((a), "%s = ", #a)); \
+ } while (0)
+#define JPAKE_DUMP_BUF(a, b) do { \
+ if ((a) != NULL) \
+ JPAKE_DEBUG_BUF((a, b, "%s", #a)); \
+ } while (0)
+
+ JPAKE_DUMP_BN(pctx->s);
+ JPAKE_DUMP_BN(pctx->k);
+ JPAKE_DUMP_BN(pctx->x1);
+ JPAKE_DUMP_BN(pctx->x2);
+ JPAKE_DUMP_BN(pctx->x3);
+ JPAKE_DUMP_BN(pctx->x4);
+ JPAKE_DUMP_BN(pctx->g_x1);
+ JPAKE_DUMP_BN(pctx->g_x2);
+ JPAKE_DUMP_BN(pctx->g_x3);
+ JPAKE_DUMP_BN(pctx->g_x4);
+ JPAKE_DUMP_BN(pctx->a);
+ JPAKE_DUMP_BN(pctx->b);
+
+ JPAKE_DUMP_BUF(pctx->client_id, pctx->client_id_len);
+ JPAKE_DUMP_BUF(pctx->server_id, pctx->server_id_len);
+ JPAKE_DUMP_BUF(pctx->h_k_cid_sessid, pctx->h_k_cid_sessid_len);
+ JPAKE_DUMP_BUF(pctx->h_k_sid_sessid, pctx->h_k_sid_sessid_len);
+
+ debug3("%s: %s done", __func__, out);
+ free(out);
+}
+
+/* Shared parts of step 1 exchange calculation */
+void
+jpake_step1(struct jpake_group *grp,
+ u_char **id, u_int *id_len,
+ BIGNUM **priv1, BIGNUM **priv2, BIGNUM **g_priv1, BIGNUM **g_priv2,
+ u_char **priv1_proof, u_int *priv1_proof_len,
+ u_char **priv2_proof, u_int *priv2_proof_len)
+{
+ BN_CTX *bn_ctx;
+
+ if ((bn_ctx = BN_CTX_new()) == NULL)
+ fatal("%s: BN_CTX_new", __func__);
+
+ /* Random nonce to prevent replay */
+ *id = xmalloc(KZP_ID_LEN);
+ *id_len = KZP_ID_LEN;
+ arc4random_buf(*id, *id_len);
+
+ /*
+ * x1/x3 is a random element of Zq
+ * x2/x4 is a random element of Z*q
+ * We also exclude [1] from x1/x3 candidates and [0, 1] from
+ * x2/x4 candiates to avoid possible degeneracy (i.e. g^0, g^1).
+ */
+ if ((*priv1 = bn_rand_range_gt_one(grp->q)) == NULL ||
+ (*priv2 = bn_rand_range_gt_one(grp->q)) == NULL)
+ fatal("%s: bn_rand_range_gt_one", __func__);
+
+ /*
+ * client: g_x1 = g^x1 mod p / server: g_x3 = g^x3 mod p
+ * client: g_x2 = g^x2 mod p / server: g_x4 = g^x4 mod p
+ */
+ if ((*g_priv1 = BN_new()) == NULL ||
+ (*g_priv2 = BN_new()) == NULL)
+ fatal("%s: BN_new", __func__);
+ if (BN_mod_exp(*g_priv1, grp->g, *priv1, grp->p, bn_ctx) == -1)
+ fatal("%s: BN_mod_exp", __func__);
+ if (BN_mod_exp(*g_priv2, grp->g, *priv2, grp->p, bn_ctx) == -1)
+ fatal("%s: BN_mod_exp", __func__);
+
+ /* Generate proofs for holding x1/x3 and x2/x4 */
+ if (schnorr_sign(grp->p, grp->q, grp->g,
+ *priv1, *g_priv1, *id, *id_len,
+ priv1_proof, priv1_proof_len) != 0)
+ fatal("%s: schnorr_sign", __func__);
+ if (schnorr_sign(grp->p, grp->q, grp->g,
+ *priv2, *g_priv2, *id, *id_len,
+ priv2_proof, priv2_proof_len) != 0)
+ fatal("%s: schnorr_sign", __func__);
+
+ BN_CTX_free(bn_ctx);
+}
+
+/* Shared parts of step 2 exchange calculation */
+void
+jpake_step2(struct jpake_group *grp, BIGNUM *s,
+ BIGNUM *mypub1, BIGNUM *theirpub1, BIGNUM *theirpub2, BIGNUM *mypriv2,
+ const u_char *theirid, u_int theirid_len,
+ const u_char *myid, u_int myid_len,
+ const u_char *theirpub1_proof, u_int theirpub1_proof_len,
+ const u_char *theirpub2_proof, u_int theirpub2_proof_len,
+ BIGNUM **newpub,
+ u_char **newpub_exponent_proof, u_int *newpub_exponent_proof_len)
+{
+ BN_CTX *bn_ctx;
+ BIGNUM *tmp, *exponent;
+
+ /* Validate peer's step 1 values */
+ if (BN_cmp(theirpub1, BN_value_one()) <= 0)
+ fatal("%s: theirpub1 <= 1", __func__);
+ if (BN_cmp(theirpub2, BN_value_one()) <= 0)
+ fatal("%s: theirpub2 <= 1", __func__);
+
+ if (schnorr_verify(grp->p, grp->q, grp->g, theirpub1,
+ theirid, theirid_len, theirpub1_proof, theirpub1_proof_len) != 1)
+ fatal("%s: schnorr_verify theirpub1 failed", __func__);
+ if (schnorr_verify(grp->p, grp->q, grp->g, theirpub2,
+ theirid, theirid_len, theirpub2_proof, theirpub2_proof_len) != 1)
+ fatal("%s: schnorr_verify theirpub2 failed", __func__);
+
+ if ((bn_ctx = BN_CTX_new()) == NULL)
+ fatal("%s: BN_CTX_new", __func__);
+
+ if ((*newpub = BN_new()) == NULL ||
+ (tmp = BN_new()) == NULL ||
+ (exponent = BN_new()) == NULL)
+ fatal("%s: BN_new", __func__);
+
+ /*
+ * client: exponent = x2 * s mod p
+ * server: exponent = x4 * s mod p
+ */
+ if (BN_mod_mul(exponent, mypriv2, s, grp->q, bn_ctx) != 1)
+ fatal("%s: BN_mod_mul (exponent = mypriv2 * s mod p)",
+ __func__);
+
+ /*
+ * client: tmp = g^(x1 + x3 + x4) mod p
+ * server: tmp = g^(x1 + x2 + x3) mod p
+ */
+ if (BN_mod_mul(tmp, mypub1, theirpub1, grp->p, bn_ctx) != 1)
+ fatal("%s: BN_mod_mul (tmp = mypub1 * theirpub1 mod p)",
+ __func__);
+ if (BN_mod_mul(tmp, tmp, theirpub2, grp->p, bn_ctx) != 1)
+ fatal("%s: BN_mod_mul (tmp = tmp * theirpub2 mod p)", __func__);
+
+ /*
+ * client: a = tmp^exponent = g^((x1+x3+x4) * x2 * s) mod p
+ * server: b = tmp^exponent = g^((x1+x2+x3) * x4 * s) mod p
+ */
+ if (BN_mod_exp(*newpub, tmp, exponent, grp->p, bn_ctx) != 1)
+ fatal("%s: BN_mod_mul (newpub = tmp^exponent mod p)", __func__);
+
+ JPAKE_DEBUG_BN((tmp, "%s: tmp = ", __func__));
+ JPAKE_DEBUG_BN((exponent, "%s: exponent = ", __func__));
+
+ /* Note the generator here is 'tmp', not g */
+ if (schnorr_sign(grp->p, grp->q, tmp, exponent, *newpub,
+ myid, myid_len,
+ newpub_exponent_proof, newpub_exponent_proof_len) != 0)
+ fatal("%s: schnorr_sign newpub", __func__);
+
+ BN_clear_free(tmp); /* XXX stash for later use? */
+ BN_clear_free(exponent); /* XXX stash for later use? (yes, in conf) */
+
+ BN_CTX_free(bn_ctx);
+}
+
+/* Confirmation hash calculation */
+void
+jpake_confirm_hash(const BIGNUM *k,
+ const u_char *endpoint_id, u_int endpoint_id_len,
+ const u_char *sess_id, u_int sess_id_len,
+ u_char **confirm_hash, u_int *confirm_hash_len)
+{
+ Buffer b;
+
+ /*
+ * Calculate confirmation proof:
+ * client: H(k || client_id || session_id)
+ * server: H(k || server_id || session_id)
+ */
+ buffer_init(&b);
+ buffer_put_bignum2(&b, k);
+ buffer_put_string(&b, endpoint_id, endpoint_id_len);
+ buffer_put_string(&b, sess_id, sess_id_len);
+ if (hash_buffer(buffer_ptr(&b), buffer_len(&b), EVP_sha256(),
+ confirm_hash, confirm_hash_len) != 0)
+ fatal("%s: hash_buffer", __func__);
+ buffer_free(&b);
+}
+
+/* Shared parts of key derivation and confirmation calculation */
+void
+jpake_key_confirm(struct jpake_group *grp, BIGNUM *s, BIGNUM *step2_val,
+ BIGNUM *mypriv2, BIGNUM *mypub1, BIGNUM *mypub2,
+ BIGNUM *theirpub1, BIGNUM *theirpub2,
+ const u_char *my_id, u_int my_id_len,
+ const u_char *their_id, u_int their_id_len,
+ const u_char *sess_id, u_int sess_id_len,
+ const u_char *theirpriv2_s_proof, u_int theirpriv2_s_proof_len,
+ BIGNUM **k,
+ u_char **confirm_hash, u_int *confirm_hash_len)
+{
+ BN_CTX *bn_ctx;
+ BIGNUM *tmp;
+
+ if ((bn_ctx = BN_CTX_new()) == NULL)
+ fatal("%s: BN_CTX_new", __func__);
+ if ((tmp = BN_new()) == NULL ||
+ (*k = BN_new()) == NULL)
+ fatal("%s: BN_new", __func__);
+
+ /* Validate step 2 values */
+ if (BN_cmp(step2_val, BN_value_one()) <= 0)
+ fatal("%s: step2_val <= 1", __func__);
+
+ /*
+ * theirpriv2_s_proof is calculated with a different generator:
+ * tmp = g^(mypriv1+mypriv2+theirpub1) = g^mypub1*g^mypub2*g^theirpub1
+ * Calculate it here so we can check the signature.
+ */
+ if (BN_mod_mul(tmp, mypub1, mypub2, grp->p, bn_ctx) != 1)
+ fatal("%s: BN_mod_mul (tmp = mypub1 * mypub2 mod p)", __func__);
+ if (BN_mod_mul(tmp, tmp, theirpub1, grp->p, bn_ctx) != 1)
+ fatal("%s: BN_mod_mul (tmp = tmp * theirpub1 mod p)", __func__);
+
+ JPAKE_DEBUG_BN((tmp, "%s: tmp = ", __func__));
+
+ if (schnorr_verify(grp->p, grp->q, tmp, step2_val,
+ their_id, their_id_len,
+ theirpriv2_s_proof, theirpriv2_s_proof_len) != 1)
+ fatal("%s: schnorr_verify theirpriv2_s_proof failed", __func__);
+
+ /*
+ * Derive shared key:
+ * client: k = (b / g^(x2*x4*s))^x2 = g^((x1+x3)*x2*x4*s)
+ * server: k = (a / g^(x2*x4*s))^x4 = g^((x1+x3)*x2*x4*s)
+ *
+ * Computed as:
+ * client: k = (g_x4^(q - (x2 * s)) * b)^x2 mod p
+ * server: k = (g_x2^(q - (x4 * s)) * b)^x4 mod p
+ */
+ if (BN_mul(tmp, mypriv2, s, bn_ctx) != 1)
+ fatal("%s: BN_mul (tmp = mypriv2 * s)", __func__);
+ if (BN_mod_sub(tmp, grp->q, tmp, grp->q, bn_ctx) != 1)
+ fatal("%s: BN_mod_sub (tmp = q - tmp mod q)", __func__);
+ if (BN_mod_exp(tmp, theirpub2, tmp, grp->p, bn_ctx) != 1)
+ fatal("%s: BN_mod_exp (tmp = theirpub2^tmp) mod p", __func__);
+ if (BN_mod_mul(tmp, tmp, step2_val, grp->p, bn_ctx) != 1)
+ fatal("%s: BN_mod_mul (tmp = tmp * step2_val) mod p", __func__);
+ if (BN_mod_exp(*k, tmp, mypriv2, grp->p, bn_ctx) != 1)
+ fatal("%s: BN_mod_exp (k = tmp^mypriv2) mod p", __func__);
+
+ BN_CTX_free(bn_ctx);
+ BN_clear_free(tmp);
+
+ jpake_confirm_hash(*k, my_id, my_id_len, sess_id, sess_id_len,
+ confirm_hash, confirm_hash_len);
+}
+
+/*
+ * Calculate and check confirmation hash from peer. Returns 1 on success
+ * 0 on failure/mismatch.
+ */
+int
+jpake_check_confirm(const BIGNUM *k,
+ const u_char *peer_id, u_int peer_id_len,
+ const u_char *sess_id, u_int sess_id_len,
+ const u_char *peer_confirm_hash, u_int peer_confirm_hash_len)
+{
+ u_char *expected_confirm_hash;
+ u_int expected_confirm_hash_len;
+ int success = 0;
+
+ /* Calculate and verify expected confirmation hash */
+ jpake_confirm_hash(k, peer_id, peer_id_len, sess_id, sess_id_len,
+ &expected_confirm_hash, &expected_confirm_hash_len);
+
+ JPAKE_DEBUG_BUF((expected_confirm_hash, expected_confirm_hash_len,
+ "%s: expected confirm hash", __func__));
+ JPAKE_DEBUG_BUF((peer_confirm_hash, peer_confirm_hash_len,
+ "%s: received confirm hash", __func__));
+
+ if (peer_confirm_hash_len != expected_confirm_hash_len)
+ error("%s: confirmation length mismatch (my %u them %u)",
+ __func__, expected_confirm_hash_len, peer_confirm_hash_len);
+ else if (memcmp(peer_confirm_hash, expected_confirm_hash,
+ expected_confirm_hash_len) == 0)
+ success = 1;
+ bzero(expected_confirm_hash, expected_confirm_hash_len);
+ xfree(expected_confirm_hash);
+ debug3("%s: success = %d", __func__, success);
+ return success;
+}
+
+/* XXX main() function with tests */
+
+#endif /* JPAKE */
+
diff --git a/jpake.h b/jpake.h
new file mode 100644
index 00000000..a3d800cd
--- /dev/null
+++ b/jpake.h
@@ -0,0 +1,134 @@
+/* $OpenBSD: jpake.h,v 1.1 2008/11/04 08:22:13 djm Exp $ */
+/*
+ * Copyright (c) 2008 Damien Miller. All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef JPAKE_H
+#define JPAKE_H
+
+#include <sys/types.h>
+
+#include <openssl/bn.h>
+
+/* Set JPAKE_DEBUG in CFLAGS for privacy-violating debugging */
+#ifndef JPAKE_DEBUG
+# define JPAKE_DEBUG_BN(a)
+# define JPAKE_DEBUG_BUF(a)
+# define JPAKE_DEBUG_CTX(a)
+#else
+# define JPAKE_DEBUG_BN(a) jpake_debug3_bn a
+# define JPAKE_DEBUG_BUF(a) jpake_debug3_buf a
+# define JPAKE_DEBUG_CTX(a) jpake_dump a
+#endif /* SCHNORR_DEBUG */
+
+struct jpake_group {
+ BIGNUM *p, *q, *g;
+};
+
+#define KZP_ID_LEN 16 /* Length of client and server IDs */
+
+struct jpake_ctx {
+ /* Parameters */
+ struct jpake_group *grp;
+
+ /* Private values shared by client and server */
+ BIGNUM *s; /* Secret (salted, crypted password) */
+ BIGNUM *k; /* Derived key */
+
+ /* Client private values (NULL for server) */
+ BIGNUM *x1; /* random in Zq */
+ BIGNUM *x2; /* random in Z*q */
+
+ /* Server private values (NULL for server) */
+ BIGNUM *x3; /* random in Zq */
+ BIGNUM *x4; /* random in Z*q */
+
+ /* Step 1: C->S */
+ u_char *client_id; /* Anti-replay nonce */
+ u_int client_id_len;
+ BIGNUM *g_x1; /* g^x1 */
+ BIGNUM *g_x2; /* g^x2 */
+
+ /* Step 1: S->C */
+ u_char *server_id; /* Anti-replay nonce */
+ u_int server_id_len;
+ BIGNUM *g_x3; /* g^x3 */
+ BIGNUM *g_x4; /* g^x4 */
+
+ /* Step 2: C->S */
+ BIGNUM *a; /* g^((x1+x3+x4)*x2*s) */
+
+ /* Step 2: S->C */
+ BIGNUM *b; /* g^((x1+x2+x3)*x4*s) */
+
+ /* Confirmation: C->S */
+ u_char *h_k_cid_sessid; /* H(k || client_id || session_id) */
+ u_int h_k_cid_sessid_len;
+
+ /* Confirmation: S->C */
+ u_char *h_k_sid_sessid; /* H(k || server_id || session_id) */
+ u_int h_k_sid_sessid_len;
+};
+
+/* jpake.c */
+struct jpake_group *jpake_default_group(void);
+BIGNUM *bn_rand_range_gt_one(const BIGNUM *high);
+int hash_buffer(const u_char *, u_int, const EVP_MD *, u_char **, u_int *);
+void jpake_debug3_bn(const BIGNUM *, const char *, ...)
+ __attribute__((__nonnull__ (2)))
+ __attribute__((format(printf, 2, 3)));
+void jpake_debug3_buf(const u_char *, u_int, const char *, ...)
+ __attribute__((__nonnull__ (3)))
+ __attribute__((format(printf, 3, 4)));
+void jpake_dump(struct jpake_ctx *, const char *, ...)
+ __attribute__((__nonnull__ (2)))
+ __attribute__((format(printf, 2, 3)));
+struct jpake_ctx *jpake_new(void);
+void jpake_free(struct jpake_ctx *);
+
+void jpake_step1(struct jpake_group *, u_char **, u_int *,
+ BIGNUM **, BIGNUM **, BIGNUM **, BIGNUM **,
+ u_char **, u_int *, u_char **, u_int *);
+
+void jpake_step2(struct jpake_group *, BIGNUM *,
+ BIGNUM *, BIGNUM *, BIGNUM *, BIGNUM *,
+ const u_char *, u_int, const u_char *, u_int,
+ const u_char *, u_int, const u_char *, u_int,
+ BIGNUM **, u_char **, u_int *);
+
+void jpake_confirm_hash(const BIGNUM *,
+ const u_char *, u_int,
+ const u_char *, u_int,
+ u_char **, u_int *);
+
+void jpake_key_confirm(struct jpake_group *, BIGNUM *, BIGNUM *,
+ BIGNUM *, BIGNUM *, BIGNUM *, BIGNUM *, BIGNUM *,
+ const u_char *, u_int, const u_char *, u_int,
+ const u_char *, u_int, const u_char *, u_int,
+ BIGNUM **, u_char **, u_int *);
+
+int jpake_check_confirm(const BIGNUM *, const u_char *, u_int,
+ const u_char *, u_int, const u_char *, u_int);
+
+/* schnorr.c */
+int schnorr_sign(const BIGNUM *, const BIGNUM *, const BIGNUM *,
+ const BIGNUM *, const BIGNUM *, const u_char *, u_int ,
+ u_char **, u_int *);
+int schnorr_verify(const BIGNUM *, const BIGNUM *, const BIGNUM *,
+ const BIGNUM *, const u_char *, u_int,
+ const u_char *, u_int);
+
+#endif /* JPAKE_H */
+
diff --git a/monitor.c b/monitor.c
index 73cf6bc9..39deedc8 100644
--- a/monitor.c
+++ b/monitor.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: monitor.c,v 1.99 2008/07/10 18:08:11 markus Exp $ */
+/* $OpenBSD: monitor.c,v 1.100 2008/11/04 08:22:13 djm Exp $ */
/*
* Copyright 2002 Niels Provos <provos@citi.umich.edu>
* Copyright 2002 Markus Friedl <markus@openbsd.org>
@@ -87,6 +87,7 @@
#include "misc.h"
#include "compat.h"
#include "ssh2.h"
+#include "jpake.h"
#ifdef GSSAPI
static Gssctxt *gsscontext = NULL;
@@ -149,6 +150,11 @@ int mm_answer_rsa_challenge(int, Buffer *);
int mm_answer_rsa_response(int, Buffer *);
int mm_answer_sesskey(int, Buffer *);
int mm_answer_sessid(int, Buffer *);
+int mm_answer_jpake_get_pwdata(int, Buffer *);
+int mm_answer_jpake_step1(int, Buffer *);
+int mm_answer_jpake_step2(int, Buffer *);
+int mm_answer_jpake_key_confirm(int, Buffer *);
+int mm_answer_jpake_check_confirm(int, Buffer *);
#ifdef USE_PAM
int mm_answer_pam_start(int, Buffer *);
@@ -234,6 +240,13 @@ struct mon_table mon_dispatch_proto20[] = {
{MONITOR_REQ_GSSUSEROK, MON_AUTH, mm_answer_gss_userok},
{MONITOR_REQ_GSSCHECKMIC, MON_ISAUTH, mm_answer_gss_checkmic},
#endif
+#ifdef JPAKE
+ {MONITOR_REQ_JPAKE_GET_PWDATA, MON_ONCE, mm_answer_jpake_get_pwdata},
+ {MONITOR_REQ_JPAKE_STEP1, MON_ISAUTH, mm_answer_jpake_step1},
+ {MONITOR_REQ_JPAKE_STEP2, MON_ONCE, mm_answer_jpake_step2},
+ {MONITOR_REQ_JPAKE_KEY_CONFIRM, MON_ONCE, mm_answer_jpake_key_confirm},
+ {MONITOR_REQ_JPAKE_CHECK_CONFIRM, MON_AUTH, mm_answer_jpake_check_confirm},
+#endif
{0, 0, NULL}
};
@@ -379,6 +392,15 @@ monitor_child_preauth(Authctxt *_authctxt, struct monitor *pmonitor)
if (!authenticated)
authctxt->failures++;
}
+#ifdef JPAKE
+ /* Cleanup JPAKE context after authentication */
+ if (ent->flags & MON_AUTHDECIDE) {
+ if (authctxt->jpake_ctx != NULL) {
+ jpake_free(authctxt->jpake_ctx);
+ authctxt->jpake_ctx = NULL;
+ }
+ }
+#endif
}
if (!authctxt->valid)
@@ -1969,3 +1991,206 @@ mm_answer_gss_userok(int sock, Buffer *m)
return (authenticated);
}
#endif /* GSSAPI */
+
+#ifdef JPAKE
+int
+mm_answer_jpake_step1(int sock, Buffer *m)
+{
+ struct jpake_ctx *pctx;
+ u_char *x3_proof, *x4_proof;
+ u_int x3_proof_len, x4_proof_len;
+
+ if (!options.zero_knowledge_password_authentication)
+ fatal("zero_knowledge_password_authentication disabled");
+
+ if (authctxt->jpake_ctx != NULL)
+ fatal("%s: authctxt->jpake_ctx already set (%p)",
+ __func__, authctxt->jpake_ctx);
+ authctxt->jpake_ctx = pctx = jpake_new();
+
+ jpake_step1(pctx->grp,
+ &pctx->server_id, &pctx->server_id_len,
+ &pctx->x3, &pctx->x4, &pctx->g_x3, &pctx->g_x4,
+ &x3_proof, &x3_proof_len,
+ &x4_proof, &x4_proof_len);
+
+ JPAKE_DEBUG_CTX((pctx, "step1 done in %s", __func__));
+
+ buffer_clear(m);
+
+ buffer_put_string(m, pctx->server_id, pctx->server_id_len);
+ buffer_put_bignum2(m, pctx->g_x3);
+ buffer_put_bignum2(m, pctx->g_x4);
+ buffer_put_string(m, x3_proof, x3_proof_len);
+ buffer_put_string(m, x4_proof, x4_proof_len);
+
+ debug3("%s: sending step1", __func__);
+ mm_request_send(sock, MONITOR_ANS_JPAKE_STEP1, m);
+
+ bzero(x3_proof, x3_proof_len);
+ bzero(x4_proof, x4_proof_len);
+ xfree(x3_proof);
+ xfree(x4_proof);
+
+ monitor_permit(mon_dispatch, MONITOR_REQ_JPAKE_GET_PWDATA, 1);
+ monitor_permit(mon_dispatch, MONITOR_REQ_JPAKE_STEP1, 0);
+
+ return 0;
+}
+
+int
+mm_answer_jpake_get_pwdata(int sock, Buffer *m)
+{
+ struct jpake_ctx *pctx = authctxt->jpake_ctx;
+ char *hash_scheme, *salt;
+
+ if (pctx == NULL)
+ fatal("%s: pctx == NULL", __func__);
+
+ auth2_jpake_get_pwdata(authctxt, &pctx->s, &hash_scheme, &salt);
+
+ buffer_clear(m);
+ /* pctx->s is sensitive, not returned to slave */
+ buffer_put_cstring(m, hash_scheme);
+ buffer_put_cstring(m, salt);
+
+ debug3("%s: sending pwdata", __func__);
+ mm_request_send(sock, MONITOR_ANS_JPAKE_GET_PWDATA, m);
+
+ bzero(hash_scheme, strlen(hash_scheme));
+ bzero(salt, strlen(salt));
+ xfree(hash_scheme);
+ xfree(salt);
+
+ monitor_permit(mon_dispatch, MONITOR_REQ_JPAKE_STEP2, 1);
+
+ return 0;
+}
+
+int
+mm_answer_jpake_step2(int sock, Buffer *m)
+{
+ struct jpake_ctx *pctx = authctxt->jpake_ctx;
+ u_char *x1_proof, *x2_proof, *x4_s_proof;
+ u_int x1_proof_len, x2_proof_len, x4_s_proof_len;
+
+ if (pctx == NULL)
+ fatal("%s: pctx == NULL", __func__);
+
+ if ((pctx->g_x1 = BN_new()) == NULL ||
+ (pctx->g_x2 = BN_new()) == NULL)
+ fatal("%s: BN_new", __func__);
+ buffer_get_bignum2(m, pctx->g_x1);
+ buffer_get_bignum2(m, pctx->g_x2);
+ pctx->client_id = buffer_get_string(m, &pctx->client_id_len);
+ x1_proof = buffer_get_string(m, &x1_proof_len);
+ x2_proof = buffer_get_string(m, &x2_proof_len);
+
+ jpake_step2(pctx->grp, pctx->s, pctx->g_x3,
+ pctx->g_x1, pctx->g_x2, pctx->x4,
+ pctx->client_id, pctx->client_id_len,
+ pctx->server_id, pctx->server_id_len,
+ x1_proof, x1_proof_len,
+ x2_proof, x2_proof_len,
+ &pctx->b,
+ &x4_s_proof, &x4_s_proof_len);
+
+ JPAKE_DEBUG_CTX((pctx, "step2 done in %s", __func__));
+
+ bzero(x1_proof, x1_proof_len);
+ bzero(x2_proof, x2_proof_len);
+ xfree(x1_proof);
+ xfree(x2_proof);
+
+ buffer_clear(m);
+
+ buffer_put_bignum2(m, pctx->b);
+ buffer_put_string(m, x4_s_proof, x4_s_proof_len);
+
+ debug3("%s: sending step2", __func__);
+ mm_request_send(sock, MONITOR_ANS_JPAKE_STEP2, m);
+
+ bzero(x4_s_proof, x4_s_proof_len);
+ xfree(x4_s_proof);
+
+ monitor_permit(mon_dispatch, MONITOR_REQ_JPAKE_KEY_CONFIRM, 1);
+
+ return 0;
+}
+
+int
+mm_answer_jpake_key_confirm(int sock, Buffer *m)
+{
+ struct jpake_ctx *pctx = authctxt->jpake_ctx;
+ u_char *x2_s_proof;
+ u_int x2_s_proof_len;
+
+ if (pctx == NULL)
+ fatal("%s: pctx == NULL", __func__);
+
+ if ((pctx->a = BN_new()) == NULL)
+ fatal("%s: BN_new", __func__);
+ buffer_get_bignum2(m, pctx->a);
+ x2_s_proof = buffer_get_string(m, &x2_s_proof_len);
+
+ jpake_key_confirm(pctx->grp, pctx->s, pctx->a,
+ pctx->x4, pctx->g_x3, pctx->g_x4, pctx->g_x1, pctx->g_x2,
+ pctx->server_id, pctx->server_id_len,
+ pctx->client_id, pctx->client_id_len,
+ session_id2, session_id2_len,
+ x2_s_proof, x2_s_proof_len,
+ &pctx->k,
+ &pctx->h_k_sid_sessid, &pctx->h_k_sid_sessid_len);
+
+ JPAKE_DEBUG_CTX((pctx, "key_confirm done in %s", __func__));
+
+ bzero(x2_s_proof, x2_s_proof_len);
+ buffer_clear(m);
+
+ /* pctx->k is sensitive, not sent */
+ buffer_put_string(m, pctx->h_k_sid_sessid, pctx->h_k_sid_sessid_len);
+
+ debug3("%s: sending confirmation hash", __func__);
+ mm_request_send(sock, MONITOR_ANS_JPAKE_KEY_CONFIRM, m);
+
+ monitor_permit(mon_dispatch, MONITOR_REQ_JPAKE_CHECK_CONFIRM, 1);
+
+ return 0;
+}
+
+int
+mm_answer_jpake_check_confirm(int sock, Buffer *m)
+{
+ int authenticated = 0;
+ u_char *peer_confirm_hash;
+ u_int peer_confirm_hash_len;
+ struct jpake_ctx *pctx = authctxt->jpake_ctx;
+
+ if (pctx == NULL)
+ fatal("%s: pctx == NULL", __func__);
+
+ peer_confirm_hash = buffer_get_string(m, &peer_confirm_hash_len);
+
+ authenticated = jpake_check_confirm(pctx->k,
+ pctx->client_id, pctx->client_id_len,
+ session_id2, session_id2_len,
+ peer_confirm_hash, peer_confirm_hash_len) && authctxt->valid;
+
+ JPAKE_DEBUG_CTX((pctx, "check_confirm done in %s", __func__));
+
+ bzero(peer_confirm_hash, peer_confirm_hash_len);
+ xfree(peer_confirm_hash);
+
+ buffer_clear(m);
+ buffer_put_int(m, authenticated);
+
+ debug3("%s: sending result %d", __func__, authenticated);
+ mm_request_send(sock, MONITOR_ANS_JPAKE_CHECK_CONFIRM, m);
+
+ monitor_permit(mon_dispatch, MONITOR_REQ_JPAKE_STEP1, 1);
+
+ auth_method = "jpake-01@openssh.com";
+ return authenticated;
+}
+
+#endif /* JPAKE */
diff --git a/monitor.h b/monitor.h
index 464009ad..a8a2c0c1 100644
--- a/monitor.h
+++ b/monitor.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: monitor.h,v 1.14 2006/03/25 22:22:43 djm Exp $ */
+/* $OpenBSD: monitor.h,v 1.15 2008/11/04 08:22:13 djm Exp $ */
/*
* Copyright 2002 Niels Provos <provos@citi.umich.edu>
@@ -60,7 +60,12 @@ enum monitor_reqtype {
MONITOR_REQ_PAM_RESPOND, MONITOR_ANS_PAM_RESPOND,
MONITOR_REQ_PAM_FREE_CTX, MONITOR_ANS_PAM_FREE_CTX,
MONITOR_REQ_AUDIT_EVENT, MONITOR_REQ_AUDIT_COMMAND,
- MONITOR_REQ_TERM
+ MONITOR_REQ_TERM,
+ MONITOR_REQ_JPAKE_STEP1, MONITOR_ANS_JPAKE_STEP1,
+ MONITOR_REQ_JPAKE_GET_PWDATA, MONITOR_ANS_JPAKE_GET_PWDATA,
+ MONITOR_REQ_JPAKE_STEP2, MONITOR_ANS_JPAKE_STEP2,
+ MONITOR_REQ_JPAKE_KEY_CONFIRM, MONITOR_ANS_JPAKE_KEY_CONFIRM,
+ MONITOR_REQ_JPAKE_CHECK_CONFIRM, MONITOR_ANS_JPAKE_CHECK_CONFIRM,
};
struct mm_master;
diff --git a/monitor_wrap.c b/monitor_wrap.c
index 40463d07..0986fc51 100644
--- a/monitor_wrap.c
+++ b/monitor_wrap.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: monitor_wrap.c,v 1.63 2008/07/10 18:08:11 markus Exp $ */
+/* $OpenBSD: monitor_wrap.c,v 1.64 2008/11/04 08:22:13 djm Exp $ */
/*
* Copyright 2002 Niels Provos <provos@citi.umich.edu>
* Copyright 2002 Markus Friedl <markus@openbsd.org>
@@ -40,6 +40,7 @@
#include <openssl/bn.h>
#include <openssl/dh.h>
+#include <openssl/evp.h>
#include "openbsd-compat/sys-queue.h"
#include "xmalloc.h"
@@ -70,7 +71,7 @@
#include "atomicio.h"
#include "monitor_fdpass.h"
#include "misc.h"
-#include "servconf.h"
+#include "jpake.h"
#include "channels.h"
#include "session.h"
@@ -1256,3 +1257,165 @@ mm_ssh_gssapi_userok(char *user)
return (authenticated);
}
#endif /* GSSAPI */
+
+#ifdef JPAKE
+void
+mm_auth2_jpake_get_pwdata(Authctxt *authctxt, BIGNUM **s,
+ char **hash_scheme, char **salt)
+{
+ Buffer m;
+
+ debug3("%s entering", __func__);
+
+ buffer_init(&m);
+ mm_request_send(pmonitor->m_recvfd,
+ MONITOR_REQ_JPAKE_GET_PWDATA, &m);
+
+ debug3("%s: waiting for MONITOR_ANS_JPAKE_GET_PWDATA", __func__);
+ mm_request_receive_expect(pmonitor->m_recvfd,
+ MONITOR_ANS_JPAKE_GET_PWDATA, &m);
+
+ *hash_scheme = buffer_get_string(&m, NULL);
+ *salt = buffer_get_string(&m, NULL);
+
+ buffer_free(&m);
+}
+
+void
+mm_jpake_step1(struct jpake_group *grp,
+ u_char **id, u_int *id_len,
+ BIGNUM **priv1, BIGNUM **priv2, BIGNUM **g_priv1, BIGNUM **g_priv2,
+ u_char **priv1_proof, u_int *priv1_proof_len,
+ u_char **priv2_proof, u_int *priv2_proof_len)
+{
+ Buffer m;
+
+ debug3("%s entering", __func__);
+
+ buffer_init(&m);
+ mm_request_send(pmonitor->m_recvfd,
+ MONITOR_REQ_JPAKE_STEP1, &m);
+
+ debug3("%s: waiting for MONITOR_ANS_JPAKE_STEP1", __func__);
+ mm_request_receive_expect(pmonitor->m_recvfd,
+ MONITOR_ANS_JPAKE_STEP1, &m);
+
+ if ((*priv1 = BN_new()) == NULL ||
+ (*priv2 = BN_new()) == NULL ||
+ (*g_priv1 = BN_new()) == NULL ||
+ (*g_priv2 = BN_new()) == NULL)
+ fatal("%s: BN_new", __func__);
+
+ *id = buffer_get_string(&m, id_len);
+ /* priv1 and priv2 are, well, private */
+ buffer_get_bignum2(&m, *g_priv1);
+ buffer_get_bignum2(&m, *g_priv2);
+ *priv1_proof = buffer_get_string(&m, priv1_proof_len);
+ *priv2_proof = buffer_get_string(&m, priv2_proof_len);
+
+ buffer_free(&m);
+}
+
+void
+mm_jpake_step2(struct jpake_group *grp, BIGNUM *s,
+ BIGNUM *mypub1, BIGNUM *theirpub1, BIGNUM *theirpub2, BIGNUM *mypriv2,
+ const u_char *theirid, u_int theirid_len,
+ const u_char *myid, u_int myid_len,
+ const u_char *theirpub1_proof, u_int theirpub1_proof_len,
+ const u_char *theirpub2_proof, u_int theirpub2_proof_len,
+ BIGNUM **newpub,
+ u_char **newpub_exponent_proof, u_int *newpub_exponent_proof_len)
+{
+ Buffer m;
+
+ debug3("%s entering", __func__);
+
+ buffer_init(&m);
+ /* monitor already has all bignums except theirpub1, theirpub2 */
+ buffer_put_bignum2(&m, theirpub1);
+ buffer_put_bignum2(&m, theirpub2);
+ /* monitor already knows our id */
+ buffer_put_string(&m, theirid, theirid_len);
+ buffer_put_string(&m, theirpub1_proof, theirpub1_proof_len);
+ buffer_put_string(&m, theirpub2_proof, theirpub2_proof_len);
+
+ mm_request_send(pmonitor->m_recvfd,
+ MONITOR_REQ_JPAKE_STEP2, &m);
+
+ debug3("%s: waiting for MONITOR_ANS_JPAKE_STEP2", __func__);
+ mm_request_receive_expect(pmonitor->m_recvfd,
+ MONITOR_ANS_JPAKE_STEP2, &m);
+
+ if ((*newpub = BN_new()) == NULL)
+ fatal("%s: BN_new", __func__);
+
+ buffer_get_bignum2(&m, *newpub);
+ *newpub_exponent_proof = buffer_get_string(&m,
+ newpub_exponent_proof_len);
+
+ buffer_free(&m);
+}
+
+void
+mm_jpake_key_confirm(struct jpake_group *grp, BIGNUM *s, BIGNUM *step2_val,
+ BIGNUM *mypriv2, BIGNUM *mypub1, BIGNUM *mypub2,
+ BIGNUM *theirpub1, BIGNUM *theirpub2,
+ const u_char *my_id, u_int my_id_len,
+ const u_char *their_id, u_int their_id_len,
+ const u_char *sess_id, u_int sess_id_len,
+ const u_char *theirpriv2_s_proof, u_int theirpriv2_s_proof_len,
+ BIGNUM **k,
+ u_char **confirm_hash, u_int *confirm_hash_len)
+{
+ Buffer m;
+
+ debug3("%s entering", __func__);
+
+ buffer_init(&m);
+ /* monitor already has all bignums except step2_val */
+ buffer_put_bignum2(&m, step2_val);
+ /* monitor already knows all the ids */
+ buffer_put_string(&m, theirpriv2_s_proof, theirpriv2_s_proof_len);
+
+ mm_request_send(pmonitor->m_recvfd,
+ MONITOR_REQ_JPAKE_KEY_CONFIRM, &m);
+
+ debug3("%s: waiting for MONITOR_ANS_JPAKE_KEY_CONFIRM", __func__);
+ mm_request_receive_expect(pmonitor->m_recvfd,
+ MONITOR_ANS_JPAKE_KEY_CONFIRM, &m);
+
+ /* 'k' is sensitive and stays in the monitor */
+ *confirm_hash = buffer_get_string(&m, confirm_hash_len);
+
+ buffer_free(&m);
+}
+
+int
+mm_jpake_check_confirm(const BIGNUM *k,
+ const u_char *peer_id, u_int peer_id_len,
+ const u_char *sess_id, u_int sess_id_len,
+ const u_char *peer_confirm_hash, u_int peer_confirm_hash_len)
+{
+ Buffer m;
+ int success = 0;
+
+ debug3("%s entering", __func__);
+
+ buffer_init(&m);
+ /* k is dummy in slave, ignored */
+ /* monitor knows all the ids */
+ buffer_put_string(&m, peer_confirm_hash, peer_confirm_hash_len);
+ mm_request_send(pmonitor->m_recvfd,
+ MONITOR_REQ_JPAKE_CHECK_CONFIRM, &m);
+
+ debug3("%s: waiting for MONITOR_ANS_JPAKE_CHECK_CONFIRM", __func__);
+ mm_request_receive_expect(pmonitor->m_recvfd,
+ MONITOR_ANS_JPAKE_CHECK_CONFIRM, &m);
+
+ success = buffer_get_int(&m);
+ buffer_free(&m);
+
+ debug3("%s: success = %d", __func__, success);
+ return success;
+}
+#endif /* JPAKE */
diff --git a/monitor_wrap.h b/monitor_wrap.h
index 329189c2..55c4b99f 100644
--- a/monitor_wrap.h
+++ b/monitor_wrap.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: monitor_wrap.h,v 1.20 2006/08/03 03:34:42 deraadt Exp $ */
+/* $OpenBSD: monitor_wrap.h,v 1.21 2008/11/04 08:22:13 djm Exp $ */
/*
* Copyright 2002 Niels Provos <provos@citi.umich.edu>
@@ -101,6 +101,26 @@ int mm_bsdauth_respond(void *, u_int, char **);
int mm_skey_query(void *, char **, char **, u_int *, char ***, u_int **);
int mm_skey_respond(void *, u_int, char **);
+/* jpake */
+struct jpake_group;
+void mm_auth2_jpake_get_pwdata(struct Authctxt *, BIGNUM **, char **, char **);
+void mm_jpake_step1(struct jpake_group *, u_char **, u_int *,
+ BIGNUM **, BIGNUM **, BIGNUM **, BIGNUM **,
+ u_char **, u_int *, u_char **, u_int *);
+void mm_jpake_step2(struct jpake_group *, BIGNUM *,
+ BIGNUM *, BIGNUM *, BIGNUM *, BIGNUM *,
+ const u_char *, u_int, const u_char *, u_int,
+ const u_char *, u_int, const u_char *, u_int,
+ BIGNUM **, u_char **, u_int *);
+void mm_jpake_key_confirm(struct jpake_group *, BIGNUM *, BIGNUM *,
+ BIGNUM *, BIGNUM *, BIGNUM *, BIGNUM *, BIGNUM *,
+ const u_char *, u_int, const u_char *, u_int,
+ const u_char *, u_int, const u_char *, u_int,
+ BIGNUM **, u_char **, u_int *);
+int mm_jpake_check_confirm(const BIGNUM *,
+ const u_char *, u_int, const u_char *, u_int, const u_char *, u_int);
+
+
/* zlib allocation hooks */
void *mm_zalloc(struct mm_master *, u_int, u_int);
diff --git a/readconf.c b/readconf.c
index 7f7bbfee..ba70d9da 100644
--- a/readconf.c
+++ b/readconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.c,v 1.170 2008/11/03 02:44:41 stevesk Exp $ */
+/* $OpenBSD: readconf.c,v 1.171 2008/11/04 08:22:13 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -130,7 +130,7 @@ typedef enum {
oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly,
oSendEnv, oControlPath, oControlMaster, oHashKnownHosts,
oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand,
- oVisualHostKey,
+ oVisualHostKey, oZeroKnowledgePasswordAuthentication,
oDeprecated, oUnsupported
} OpCodes;
@@ -228,6 +228,13 @@ static struct {
{ "localcommand", oLocalCommand },
{ "permitlocalcommand", oPermitLocalCommand },
{ "visualhostkey", oVisualHostKey },
+#ifdef JPAKE
+ { "zeroknowledgepasswordauthentication",
+ oZeroKnowledgePasswordAuthentication },
+#else
+ { "zeroknowledgepasswordauthentication", oUnsupported },
+#endif
+
{ NULL, oBadOption }
};
@@ -412,6 +419,10 @@ parse_flag:
intptr = &options->password_authentication;
goto parse_flag;
+ case oZeroKnowledgePasswordAuthentication:
+ intptr = &options->zero_knowledge_password_authentication;
+ goto parse_flag;
+
case oKbdInteractiveAuthentication:
intptr = &options->kbd_interactive_authentication;
goto parse_flag;
@@ -1054,6 +1065,7 @@ initialize_options(Options * options)
options->local_command = NULL;
options->permit_local_command = -1;
options->visual_host_key = -1;
+ options->zero_knowledge_password_authentication = -1;
}
/*
@@ -1190,6 +1202,8 @@ fill_default_options(Options * options)
options->permit_local_command = 0;
if (options->visual_host_key == -1)
options->visual_host_key = 0;
+ if (options->zero_knowledge_password_authentication == -1)
+ options->zero_knowledge_password_authentication = 0;
/* options->local_command should not be set by default */
/* options->proxy_command should not be set by default */
/* options->user will be set in the main program if appropriate */
diff --git a/readconf.h b/readconf.h
index c1387a89..c9e5f6a4 100644
--- a/readconf.h
+++ b/readconf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.h,v 1.75 2008/11/01 17:40:33 stevesk Exp $ */
+/* $OpenBSD: readconf.h,v 1.76 2008/11/04 08:22:13 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -49,6 +49,7 @@ typedef struct {
* authentication. */
int kbd_interactive_authentication; /* Try keyboard-interactive auth. */
char *kbd_interactive_devices; /* Keyboard-interactive auth devices. */
+ int zero_knowledge_password_authentication; /* Try jpake */
int batch_mode; /* Batch mode: do not ask for passwords. */
int check_host_ip; /* Also keep track of keys for IP address */
int strict_host_key_checking; /* Strict host key checking. */
diff --git a/schnorr.c b/schnorr.c
new file mode 100644
index 00000000..e3abe570
--- /dev/null
+++ b/schnorr.c
@@ -0,0 +1,407 @@
+/* $OpenBSD: schnorr.c,v 1.1 2008/11/04 08:22:13 djm Exp $ */
+/*
+ * Copyright (c) 2008 Damien Miller. All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Implementation of Schnorr signatures / zero-knowledge proofs, based on
+ * description in:
+ *
+ * F. Hao, P. Ryan, "Password Authenticated Key Exchange by Juggling",
+ * 16th Workshop on Security Protocols, Cambridge, April 2008
+ *
+ * http://grouper.ieee.org/groups/1363/Research/contributions/hao-ryan-2008.pdf
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <openssl/evp.h>
+#include <openssl/bn.h>
+
+#include "xmalloc.h"
+#include "buffer.h"
+#include "log.h"
+
+#include "jpake.h"
+
+/* #define SCHNORR_DEBUG */ /* Privacy-violating debugging */
+/* #define SCHNORR_MAIN */ /* Include main() selftest */
+
+/* XXX */
+/* Parametise signature hash? (sha256, sha1, etc.) */
+/* Signature format - include type name, hash type, group params? */
+
+#ifndef SCHNORR_DEBUG
+# define SCHNORR_DEBUG_BN(a)
+# define SCHNORR_DEBUG_BUF(a)
+#else
+# define SCHNORR_DEBUG_BN(a) jpake_debug3_bn a
+# define SCHNORR_DEBUG_BUF(a) jpake_debug3_buf a
+#endif /* SCHNORR_DEBUG */
+
+/*
+ * Calculate hash component of Schnorr signature H(g || g^v || g^x || id)
+ * using SHA1. Returns signature as bignum or NULL on error.
+ */
+static BIGNUM *
+schnorr_hash(const BIGNUM *p, const BIGNUM *q, const BIGNUM *g,
+ const BIGNUM *g_v, const BIGNUM *g_x,
+ const u_char *id, u_int idlen)
+{
+ u_char *digest;
+ u_int digest_len;
+ BIGNUM *h;
+ EVP_MD_CTX evp_md_ctx;
+ Buffer b;
+ int success = -1;
+
+ if ((h = BN_new()) == NULL) {
+ error("%s: BN_new", __func__);
+ return NULL;
+ }
+
+ buffer_init(&b);
+ EVP_MD_CTX_init(&evp_md_ctx);
+
+ /* h = H(g || g^v || g^x || id) */
+ buffer_put_bignum2(&b, g);
+ buffer_put_bignum2(&b, g_v);
+ buffer_put_bignum2(&b, g_x);
+ buffer_put_string(&b, id, idlen);
+
+ SCHNORR_DEBUG_BUF((buffer_ptr(&b), buffer_len(&b),
+ "%s: hashblob", __func__));
+ if (hash_buffer(buffer_ptr(&b), buffer_len(&b), EVP_sha256(),
+ &digest, &digest_len) != 0) {
+ error("%s: hash_buffer", __func__);
+ goto out;
+ }
+ if (BN_bin2bn(digest, (int)digest_len, h) == NULL) {
+ error("%s: BN_bin2bn", __func__);
+ goto out;
+ }
+ success = 0;
+ SCHNORR_DEBUG_BN((h, "%s: h = ", __func__));
+ out:
+ buffer_free(&b);
+ EVP_MD_CTX_cleanup(&evp_md_ctx);
+ bzero(digest, digest_len);
+ xfree(digest);
+ digest_len = 0;
+ if (success == 0)
+ return h;
+ BN_clear_free(h);
+ return NULL;
+}
+
+/*
+ * Generate Schnorr signature to prove knowledge of private value 'x' used
+ * in public exponent g^x, under group defined by 'grp_p', 'grp_q' and 'grp_g'
+ * 'idlen' bytes from 'id' will be included in the signature hash as an anti-
+ * replay salt.
+ * On success, 0 is returned and *siglen bytes of signature are returned in
+ * *sig (caller to free). Returns -1 on failure.
+ */
+int
+schnorr_sign(const BIGNUM *grp_p, const BIGNUM *grp_q, const BIGNUM *grp_g,
+ const BIGNUM *x, const BIGNUM *g_x, const u_char *id, u_int idlen,
+ u_char **sig, u_int *siglen)
+{
+ int success = -1;
+ Buffer b;
+ BIGNUM *h, *tmp, *v, *g_v, *r;
+ BN_CTX *bn_ctx;
+
+ SCHNORR_DEBUG_BN((x, "%s: x = ", __func__));
+ SCHNORR_DEBUG_BN((g_x, "%s: g_x = ", __func__));
+
+ /* Avoid degenerate cases: g^0 yields a spoofable signature */
+ if (BN_cmp(g_x, BN_value_one()) <= 0) {
+ error("%s: g_x < 1", __func__);
+ return -1;
+ }
+
+ h = g_v = r = tmp = v = NULL;
+ if ((bn_ctx = BN_CTX_new()) == NULL) {
+ error("%s: BN_CTX_new", __func__);
+ goto out;
+ }
+ if ((g_v = BN_new()) == NULL ||
+ (r = BN_new()) == NULL ||
+ (tmp = BN_new()) == NULL) {
+ error("%s: BN_new", __func__);
+ goto out;
+ }
+
+ /*
+ * v must be a random element of Zq, so 1 <= v < q
+ * we also exclude v = 1, since g^1 looks dangerous
+ */
+ if ((v = bn_rand_range_gt_one(grp_p)) == NULL) {
+ error("%s: bn_rand_range2", __func__);
+ goto out;
+ }
+ SCHNORR_DEBUG_BN((v, "%s: v = ", __func__));
+
+ /* g_v = g^v mod p */
+ if (BN_mod_exp(g_v, grp_g, v, grp_p, bn_ctx) == -1) {
+ error("%s: BN_mod_exp (g^v mod p)", __func__);
+ goto out;
+ }
+ SCHNORR_DEBUG_BN((g_v, "%s: g_v = ", __func__));
+
+ /* h = H(g || g^v || g^x || id) */
+ if ((h = schnorr_hash(grp_p, grp_q, grp_g, g_v, g_x,
+ id, idlen)) == NULL) {
+ error("%s: schnorr_hash failed", __func__);
+ goto out;
+ }
+
+ /* r = v - xh mod q */
+ if (BN_mod_mul(tmp, x, h, grp_q, bn_ctx) == -1) {
+ error("%s: BN_mod_mul (tmp = xv mod q)", __func__);
+ goto out;
+ }
+ if (BN_mod_sub(r, v, tmp, grp_q, bn_ctx) == -1) {
+ error("%s: BN_mod_mul (r = v - tmp)", __func__);
+ goto out;
+ }
+ SCHNORR_DEBUG_BN((r, "%s: r = ", __func__));
+
+ /* Signature is (g_v, r) */
+ buffer_init(&b);
+ /* XXX sigtype-hash as string? */
+ buffer_put_bignum2(&b, g_v);
+ buffer_put_bignum2(&b, r);
+ *siglen = buffer_len(&b);
+ *sig = xmalloc(*siglen);
+ memcpy(*sig, buffer_ptr(&b), *siglen);
+ SCHNORR_DEBUG_BUF((buffer_ptr(&b), buffer_len(&b),
+ "%s: sigblob", __func__));
+ buffer_free(&b);
+ success = 0;
+ out:
+ BN_CTX_free(bn_ctx);
+ if (h != NULL)
+ BN_clear_free(h);
+ if (v != NULL)
+ BN_clear_free(v);
+ BN_clear_free(r);
+ BN_clear_free(g_v);
+ BN_clear_free(tmp);
+
+ return success;
+}
+
+/*
+ * Verify Schnorr signature 'sig' of length 'siglen' against public exponent
+ * g_x (g^x) under group defined by 'grp_p', 'grp_q' and 'grp_g'.
+ * Signature hash will be salted with 'idlen' bytes from 'id'.
+ * Returns -1 on failure, 0 on incorrect signature or 1 on matching signature.
+ */
+int
+schnorr_verify(const BIGNUM *grp_p, const BIGNUM *grp_q, const BIGNUM *grp_g,
+ const BIGNUM *g_x, const u_char *id, u_int idlen,
+ const u_char *sig, u_int siglen)
+{
+ int success = -1;
+ Buffer b;
+ BIGNUM *g_v, *h, *r, *g_xh, *g_r, *expected;
+ BN_CTX *bn_ctx;
+ u_int rlen;
+
+ SCHNORR_DEBUG_BN((g_x, "%s: g_x = ", __func__));
+
+ /* Avoid degenerate cases: g^0 yields a spoofable signature */
+ if (BN_cmp(g_x, BN_value_one()) <= 0) {
+ error("%s: g_x < 1", __func__);
+ return -1;
+ }
+
+ g_v = h = r = g_xh = g_r = expected = NULL;
+ if ((bn_ctx = BN_CTX_new()) == NULL) {
+ error("%s: BN_CTX_new", __func__);
+ goto out;
+ }
+ if ((g_v = BN_new()) == NULL ||
+ (r = BN_new()) == NULL ||
+ (g_xh = BN_new()) == NULL ||
+ (g_r = BN_new()) == NULL ||
+ (expected = BN_new()) == NULL) {
+ error("%s: BN_new", __func__);
+ goto out;
+ }
+
+ /* Extract g^v and r from signature blob */
+ buffer_init(&b);
+ buffer_append(&b, sig, siglen);
+ SCHNORR_DEBUG_BUF((buffer_ptr(&b), buffer_len(&b),
+ "%s: sigblob", __func__));
+ buffer_get_bignum2(&b, g_v);
+ buffer_get_bignum2(&b, r);
+ rlen = buffer_len(&b);
+ buffer_free(&b);
+ if (rlen != 0) {
+ error("%s: remaining bytes in signature %d", __func__, rlen);
+ goto out;
+ }
+ buffer_free(&b);
+ SCHNORR_DEBUG_BN((g_v, "%s: g_v = ", __func__));
+ SCHNORR_DEBUG_BN((r, "%s: r = ", __func__));
+
+ /* h = H(g || g^v || g^x || id) */
+ if ((h = schnorr_hash(grp_p, grp_q, grp_g, g_v, g_x,
+ id, idlen)) == NULL) {
+ error("%s: schnorr_hash failed", __func__);
+ goto out;
+ }
+
+ /* g_xh = (g^x)^h */
+ if (BN_mod_exp(g_xh, g_x, h, grp_p, bn_ctx) == -1) {
+ error("%s: BN_mod_exp (g_x^h mod p)", __func__);
+ goto out;
+ }
+ SCHNORR_DEBUG_BN((g_xh, "%s: g_xh = ", __func__));
+
+ /* g_r = g^r */
+ if (BN_mod_exp(g_r, grp_g, r, grp_p, bn_ctx) == -1) {
+ error("%s: BN_mod_exp (g_x^h mod p)", __func__);
+ goto out;
+ }
+ SCHNORR_DEBUG_BN((g_r, "%s: g_r = ", __func__));
+
+ /* expected = g^r * g_xh */
+ if (BN_mod_mul(expected, g_r, g_xh, grp_p, bn_ctx) == -1) {
+ error("%s: BN_mod_mul (expected = g_r mod p)", __func__);
+ goto out;
+ }
+ SCHNORR_DEBUG_BN((expected, "%s: expected = ", __func__));
+
+ /* Check g_v == expected */
+ success = BN_cmp(expected, g_v) == 0;
+ out:
+ BN_CTX_free(bn_ctx);
+ if (h != NULL)
+ BN_clear_free(h);
+ BN_clear_free(g_v);
+ BN_clear_free(r);
+ BN_clear_free(g_xh);
+ BN_clear_free(g_r);
+ BN_clear_free(expected);
+ return success;
+}
+
+#ifdef SCHNORR_MAIN
+static void
+schnorr_selftest_one(const BIGNUM *grp_p, const BIGNUM *grp_q,
+ const BIGNUM *grp_g, const BIGNUM *x)
+{
+ BIGNUM *g_x;
+ u_char *sig;
+ u_int siglen;
+ BN_CTX *bn_ctx;
+
+ if ((bn_ctx = BN_CTX_new()) == NULL)
+ fatal("%s: BN_CTX_new", __func__);
+ if ((g_x = BN_new()) == NULL)
+ fatal("%s: BN_new", __func__);
+
+ if (BN_mod_exp(g_x, grp_g, x, grp_p, bn_ctx) == -1)
+ fatal("%s: g_x", __func__);
+ if (schnorr_sign(grp_p, grp_q, grp_g, x, g_x, "junk", 4, &sig, &siglen))
+ fatal("%s: schnorr_sign", __func__);
+ if (schnorr_verify(grp_p, grp_q, grp_g, g_x, "junk", 4,
+ sig, siglen) != 1)
+ fatal("%s: verify fail", __func__);
+ if (schnorr_verify(grp_p, grp_q, grp_g, g_x, "JUNK", 4,
+ sig, siglen) != 0)
+ fatal("%s: verify should have failed (bad ID)", __func__);
+ sig[4] ^= 1;
+ if (schnorr_verify(grp_p, grp_q, grp_g, g_x, "junk", 4,
+ sig, siglen) != 0)
+ fatal("%s: verify should have failed (bit error)", __func__);
+ xfree(sig);
+ BN_free(g_x);
+ BN_CTX_free(bn_ctx);
+}
+
+static void
+schnorr_selftest(void)
+{
+ BIGNUM *x;
+ struct jpake_group *grp;
+ u_int i;
+ char *hh;
+
+ grp = jpake_default_group();
+ if ((x = BN_new()) == NULL)
+ fatal("%s: BN_new", __func__);
+ SCHNORR_DEBUG_BN((grp->p, "%s: grp->p = ", __func__));
+ SCHNORR_DEBUG_BN((grp->q, "%s: grp->q = ", __func__));
+ SCHNORR_DEBUG_BN((grp->g, "%s: grp->g = ", __func__));
+
+ /* [1, 20) */
+ for (i = 1; i < 20; i++) {
+ printf("x = %u\n", i);
+ fflush(stdout);
+ if (BN_set_word(x, i) != 1)
+ fatal("%s: set x word", __func__);
+ schnorr_selftest_one(grp->p, grp->q, grp->g, x);
+ }
+
+ /* 100 x random [0, p) */
+ for (i = 0; i < 100; i++) {
+ if (BN_rand_range(x, grp->p) != 1)
+ fatal("%s: BN_rand_range", __func__);
+ hh = BN_bn2hex(x);
+ printf("x = (random) 0x%s\n", hh);
+ free(hh);
+ fflush(stdout);
+ schnorr_selftest_one(grp->p, grp->q, grp->g, x);
+ }
+
+ /* [q-20, q) */
+ if (BN_set_word(x, 20) != 1)
+ fatal("%s: BN_set_word (x = 20)", __func__);
+ if (BN_sub(x, grp->q, x) != 1)
+ fatal("%s: BN_sub (q - x)", __func__);
+ for (i = 0; i < 19; i++) {
+ hh = BN_bn2hex(x);
+ printf("x = (q - %d) 0x%s\n", 20 - i, hh);
+ free(hh);
+ fflush(stdout);
+ schnorr_selftest_one(grp->p, grp->q, grp->g, x);
+ if (BN_add(x, x, BN_value_one()) != 1)
+ fatal("%s: BN_add (x + 1)", __func__);
+ }
+ BN_free(x);
+}
+
+int
+main(int argc, char **argv)
+{
+ log_init(argv[0], SYSLOG_LEVEL_DEBUG3, SYSLOG_FACILITY_USER, 1);
+
+ schnorr_selftest();
+ return 0;
+}
+#endif
+
diff --git a/servconf.c b/servconf.c
index f2d41433..c7d2d0b9 100644
--- a/servconf.c
+++ b/servconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.c,v 1.189 2008/11/03 08:59:41 djm Exp $ */
+/* $OpenBSD: servconf.c,v 1.190 2008/11/04 08:22:13 djm Exp $ */
/*
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
* All rights reserved
@@ -127,6 +127,7 @@ initialize_server_options(ServerOptions *options)
options->num_permitted_opens = -1;
options->adm_forced_command = NULL;
options->chroot_directory = NULL;
+ options->zero_knowledge_password_authentication = -1;
}
void
@@ -258,6 +259,8 @@ fill_default_server_options(ServerOptions *options)
options->authorized_keys_file = _PATH_SSH_USER_PERMITTED_KEYS;
if (options->permit_tun == -1)
options->permit_tun = SSH_TUNMODE_NO;
+ if (options->zero_knowledge_password_authentication == -1)
+ options->zero_knowledge_password_authentication = 0;
/* Turn privilege separation on by default */
if (use_privsep == -1)
@@ -302,6 +305,7 @@ typedef enum {
sGssAuthentication, sGssCleanupCreds, sAcceptEnv, sPermitTunnel,
sMatch, sPermitOpen, sForceCommand, sChrootDirectory,
sUsePrivilegeSeparation, sAllowAgentForwarding,
+ sZeroKnowledgePasswordAuthentication,
sDeprecated, sUnsupported
} ServerOpCodes;
@@ -368,6 +372,11 @@ static struct {
{ "kbdinteractiveauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL },
{ "challengeresponseauthentication", sChallengeResponseAuthentication, SSHCFG_GLOBAL },
{ "skeyauthentication", sChallengeResponseAuthentication, SSHCFG_GLOBAL }, /* alias */
+#ifdef JPAKE
+ { "zeroknowledgepasswordauthentication", sZeroKnowledgePasswordAuthentication, SSHCFG_ALL },
+#else
+ { "zeroknowledgepasswordauthentication", sUnsupported, SSHCFG_ALL },
+#endif
{ "checkmail", sDeprecated, SSHCFG_GLOBAL },
{ "listenaddress", sListenAddress, SSHCFG_GLOBAL },
{ "addressfamily", sAddressFamily, SSHCFG_GLOBAL },
@@ -890,6 +899,10 @@ process_server_config_line(ServerOptions *options, char *line,
intptr = &options->password_authentication;
goto parse_flag;
+ case sZeroKnowledgePasswordAuthentication:
+ intptr = &options->zero_knowledge_password_authentication;
+ goto parse_flag;
+
case sKbdInteractiveAuthentication:
intptr = &options->kbd_interactive_authentication;
goto parse_flag;
@@ -1377,6 +1390,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth)
M_CP_INTOPT(kerberos_authentication);
M_CP_INTOPT(hostbased_authentication);
M_CP_INTOPT(kbd_interactive_authentication);
+ M_CP_INTOPT(zero_knowledge_password_authentication);
M_CP_INTOPT(permit_root_login);
M_CP_INTOPT(permit_empty_passwd);
@@ -1579,6 +1593,10 @@ dump_config(ServerOptions *o)
dump_cfg_fmtint(sGssAuthentication, o->gss_authentication);
dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds);
#endif
+#ifdef JPAKE
+ dump_cfg_fmtint(sZeroKnowledgePasswordAuthentication,
+ o->zero_knowledge_password_authentication);
+#endif
dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication);
dump_cfg_fmtint(sKbdInteractiveAuthentication,
o->kbd_interactive_authentication);
diff --git a/servconf.h b/servconf.h
index 40ac64f1..1d4c3a01 100644
--- a/servconf.h
+++ b/servconf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.h,v 1.85 2008/06/10 04:50:25 dtucker Exp $ */
+/* $OpenBSD: servconf.h,v 1.86 2008/11/04 08:22:13 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -96,6 +96,8 @@ typedef struct {
* authentication. */
int kbd_interactive_authentication; /* If true, permit */
int challenge_response_authentication;
+ int zero_knowledge_password_authentication;
+ /* If true, permit jpake auth */
int permit_empty_passwd; /* If false, do not permit empty
* passwords. */
int permit_user_env; /* If true, read ~/.ssh/environment */
diff --git a/ssh2.h b/ssh2.h
index cf56bc4e..1c33dc26 100644
--- a/ssh2.h
+++ b/ssh2.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh2.h,v 1.10 2006/03/25 22:22:43 djm Exp $ */
+/* $OpenBSD: ssh2.h,v 1.11 2008/11/04 08:22:13 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
@@ -111,6 +111,12 @@
#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60
#define SSH2_MSG_USERAUTH_INFO_REQUEST 60
#define SSH2_MSG_USERAUTH_INFO_RESPONSE 61
+#define SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP1 60
+#define SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP1 61
+#define SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP2 62
+#define SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP2 63
+#define SSH2_MSG_USERAUTH_JPAKE_CLIENT_CONFIRM 64
+#define SSH2_MSG_USERAUTH_JPAKE_SERVER_CONFIRM 65
/* connection protocol: generic */
@@ -159,3 +165,4 @@
#define SSH2_OPEN_RESOURCE_SHORTAGE 4
#define SSH2_EXTENDED_DATA_STDERR 1
+
diff --git a/ssh_config.5 b/ssh_config.5
index 254940ef..abc3b0b1 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -34,8 +34,8 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
-.\" $OpenBSD: ssh_config.5,v 1.114 2008/10/17 18:36:24 stevesk Exp $
-.Dd $Mdocdate: October 17 2008 $
+.\" $OpenBSD: ssh_config.5,v 1.115 2008/11/04 08:22:13 djm Exp $
+.Dd $Mdocdate: November 4 2008 $
.Dt SSH_CONFIG 5
.Os
.Sh NAME
@@ -1079,6 +1079,17 @@ Specifies the full pathname of the
program.
The default is
.Pa /usr/X11R6/bin/xauth .
+.It Cm ZeroKnowledgePasswordAuthentication
+Specifies whether to use zero knowledge password authentication.
+This authentication method avoids exposure of password to untrusted
+hosts.
+The argument to this keyword must be
+.Dq yes
+or
+.Dq no .
+The default is currently
+.Dq no
+as this method is considered experimental.
.El
.Sh PATTERNS
A
diff --git a/sshconnect2.c b/sshconnect2.c
index 7d0c5e82..a762eec3 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 Authctxt {
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_error(int, u_int32_t, void *);
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 @@ Authmethod authmethods[] = {
#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 @@ ssh_userauth2(const char *local_user, const char *server_user, char *host,
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(int type, u_int32_t seqnr, void *ctxt)
&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 @@ userauth_hostbased(Authctxt *authctxt)
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 @@ authmethods_get(void)
buffer_free(&b);
return list;
}
+
diff --git a/sshd_config.5 b/sshd_config.5
index 06fe5fd3..a4a4be6e 100644
--- a/sshd_config.5
+++ b/sshd_config.5
@@ -34,8 +34,8 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
-.\" $OpenBSD: sshd_config.5,v 1.97 2008/10/09 03:50:54 djm Exp $
-.Dd $Mdocdate: October 9 2008 $
+.\" $OpenBSD: sshd_config.5,v 1.98 2008/11/04 08:22:13 djm Exp $
+.Dd $Mdocdate: November 4 2008 $
.Dt SSHD_CONFIG 5
.Os
.Sh NAME
@@ -612,8 +612,9 @@ Available keywords are
.Cm RSAAuthentication ,
.Cm X11DisplayOffset ,
.Cm X11Forwarding ,
+.Cm X11UseLocalHost ,
and
-.Cm X11UseLocalHost .
+.Cm ZeroKnowledgePasswordAuthentication .
.It Cm MaxAuthTries
Specifies the maximum number of authentication attempts permitted per
connection.
@@ -1004,6 +1005,17 @@ Specifies the full pathname of the
program.
The default is
.Pa /usr/X11R6/bin/xauth .
+.It Cm ZeroKnowledgePasswordAuthentication
+Specifies whether to use zero knowledge password authentication.
+This authentication method avoids exposure of password to untrusted
+hosts.
+The argument to this keyword must be
+.Dq yes
+or
+.Dq no .
+The default is currently
+.Dq no
+as this method is considered experimental.
.El
.Sh TIME FORMATS
.Xr sshd 8