]> andersk Git - gssapi-openssh.git/commitdiff
Initial revision
authorbasney <basney>
Wed, 25 Feb 2009 16:51:55 +0000 (16:51 +0000)
committerbasney <basney>
Wed, 25 Feb 2009 16:51:55 +0000 (16:51 +0000)
openssh/auth2-jpake.c [new file with mode: 0644]
openssh/jpake.c [new file with mode: 0644]
openssh/jpake.h [new file with mode: 0644]
openssh/schnorr.c [new file with mode: 0644]

diff --git a/openssh/auth2-jpake.c b/openssh/auth2-jpake.c
new file mode 100644 (file)
index 0000000..efe7ff2
--- /dev/null
@@ -0,0 +1,557 @@
+/* $OpenBSD: auth2-jpake.c,v 1.2 2008/11/07 23:34:48 dtucker 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
+ */
+
+#ifdef JPAKE
+
+#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"
+
+/*
+ * 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/openssh/jpake.c b/openssh/jpake.c
new file mode 100644 (file)
index 0000000..565f2e2
--- /dev/null
@@ -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/openssh/jpake.h b/openssh/jpake.h
new file mode 100644 (file)
index 0000000..a3d800c
--- /dev/null
@@ -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/openssh/schnorr.c b/openssh/schnorr.c
new file mode 100644 (file)
index 0000000..5469750
--- /dev/null
@@ -0,0 +1,409 @@
+/* $OpenBSD: schnorr.c,v 1.2 2009/02/18 04:31:21 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 || p || q || g^v || g^x || id) */
+       buffer_put_bignum2(&b, g);
+       buffer_put_bignum2(&b, p);
+       buffer_put_bignum2(&b, q);
+       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
+
This page took 0.824886 seconds and 5 git commands to generate.