Merge branch 'vendor/OPENSSH'
authorPeter Avalos <pavalos@dragonflybsd.org>
Sun, 10 Jan 2010 05:15:44 +0000 (05:15 +0000)
committerPeter Avalos <pavalos@dragonflybsd.org>
Sun, 10 Jan 2010 05:15:44 +0000 (05:15 +0000)
crypto/openssh/README.DELETED
crypto/openssh/auth-shadow.c [new file with mode: 0644]
crypto/openssh/auth-sia.c [new file with mode: 0644]
crypto/openssh/auth-sia.h [new file with mode: 0644]
crypto/openssh/auth2-jpake.c [new file with mode: 0644]
crypto/openssh/jpake.c [new file with mode: 0644]
crypto/openssh/schnorr.c [new file with mode: 0644]

index 335dd8c..82b0a56 100644 (file)
@@ -8,10 +8,6 @@ README.smartcard
 TODO
 WARNING.RNG
 aclocal.m4
-auth-shadow.c
-auth-sia.c
-auth-sia.h
-auth2-jpake.c
 buildpkg.sh.in
 config.guess
 config.h.in
@@ -22,7 +18,6 @@ contrib/
 fixpaths
 fixprogs
 install-sh
-jpake.c
 logintest.c
 mdoc2man.awk
 mkinstalldirs
@@ -85,7 +80,6 @@ opensshd.init.in
 regress/
 scard/
 scard.h
-schnorr.c
 scp.0
 sftp-server.0
 sftp.0
diff --git a/crypto/openssh/auth-shadow.c b/crypto/openssh/auth-shadow.c
new file mode 100644 (file)
index 0000000..2190916
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2004 Darren Tucker.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "includes.h"
+
+#if defined(USE_SHADOW) && defined(HAS_SHADOW_EXPIRE)
+#include <shadow.h>
+#include <stdarg.h>
+#include <string.h>
+#include <time.h>
+
+#include "key.h"
+#include "hostfile.h"
+#include "auth.h"
+#include "buffer.h"
+#include "log.h"
+
+#ifdef DAY
+# undef DAY
+#endif
+#define DAY    (24L * 60 * 60) /* 1 day in seconds */
+
+extern Buffer loginmsg;
+
+/*
+ * For the account and password expiration functions, we assume the expiry
+ * occurs the day after the day specified.
+ */
+
+/*
+ * Check if specified account is expired.  Returns 1 if account is expired,
+ * 0 otherwise.
+ */
+int
+auth_shadow_acctexpired(struct spwd *spw)
+{
+       time_t today;
+       int daysleft;
+       char buf[256];
+
+       today = time(NULL) / DAY;
+       daysleft = spw->sp_expire - today;
+       debug3("%s: today %d sp_expire %d days left %d", __func__, (int)today,
+           (int)spw->sp_expire, daysleft);
+
+       if (spw->sp_expire == -1) {
+               debug3("account expiration disabled");
+       } else if (daysleft < 0) {
+               logit("Account %.100s has expired", spw->sp_namp);
+               return 1;
+       } else if (daysleft <= spw->sp_warn) {
+               debug3("account will expire in %d days", daysleft);
+               snprintf(buf, sizeof(buf),
+                   "Your account will expire in %d day%s.\n", daysleft,
+                   daysleft == 1 ? "" : "s");
+               buffer_append(&loginmsg, buf, strlen(buf));
+       }
+
+       return 0;
+}
+
+/*
+ * Checks password expiry for platforms that use shadow passwd files.
+ * Returns: 1 = password expired, 0 = password not expired
+ */
+int
+auth_shadow_pwexpired(Authctxt *ctxt)
+{
+       struct spwd *spw = NULL;
+       const char *user = ctxt->pw->pw_name;
+       char buf[256];
+       time_t today;
+       int daysleft, disabled = 0;
+
+       if ((spw = getspnam((char *)user)) == NULL) {
+               error("Could not get shadow information for %.100s", user);
+               return 0;
+       }
+
+       today = time(NULL) / DAY;
+       debug3("%s: today %d sp_lstchg %d sp_max %d", __func__, (int)today,
+           (int)spw->sp_lstchg, (int)spw->sp_max);
+
+#if defined(__hpux) && !defined(HAVE_SECUREWARE)
+       if (iscomsec()) {
+               struct pr_passwd *pr;
+
+               pr = getprpwnam((char *)user);
+
+               /* Test for Trusted Mode expiry disabled */
+               if (pr != NULL && pr->ufld.fd_min == 0 &&
+                   pr->ufld.fd_lifetime == 0 && pr->ufld.fd_expire == 0 &&
+                   pr->ufld.fd_pw_expire_warning == 0 &&
+                   pr->ufld.fd_schange != 0)
+                       disabled = 1;
+       }
+#endif
+
+       /* TODO: check sp_inact */
+       daysleft = spw->sp_lstchg + spw->sp_max - today;
+       if (disabled) {
+               debug3("password expiration disabled");
+       } else if (spw->sp_lstchg == 0) {
+               logit("User %.100s password has expired (root forced)", user);
+               return 1;
+       } else if (spw->sp_max == -1) {
+               debug3("password expiration disabled");
+       } else if (daysleft < 0) {
+               logit("User %.100s password has expired (password aged)", user);
+               return 1;
+       } else if (daysleft <= spw->sp_warn) {
+               debug3("password will expire in %d days", daysleft);
+               snprintf(buf, sizeof(buf),
+                   "Your password will expire in %d day%s.\n", daysleft,
+                   daysleft == 1 ? "" : "s");
+               buffer_append(&loginmsg, buf, strlen(buf));
+       }
+
+       return 0;
+}
+#endif /* USE_SHADOW && HAS_SHADOW_EXPIRE */
diff --git a/crypto/openssh/auth-sia.c b/crypto/openssh/auth-sia.c
new file mode 100644 (file)
index 0000000..a9e1c25
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2002 Chris Adams.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "includes.h"
+
+#ifdef HAVE_OSF_SIA
+#include <sia.h>
+#include <siad.h>
+#include <pwd.h>
+#include <signal.h>
+#include <setjmp.h>
+#include <sys/resource.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "ssh.h"
+#include "key.h"
+#include "hostfile.h"
+#include "auth.h"
+#include "auth-sia.h"
+#include "log.h"
+#include "servconf.h"
+#include "canohost.h"
+#include "uidswap.h"
+
+extern ServerOptions options;
+extern int saved_argc;
+extern char **saved_argv;
+
+int
+sys_auth_passwd(Authctxt *authctxt, const char *pass)
+{
+       int ret;
+       SIAENTITY *ent = NULL;
+       const char *host;
+
+       host = get_canonical_hostname(options.use_dns);
+
+       if (!authctxt->user || pass == NULL || pass[0] == '\0')
+               return (0);
+
+       if (sia_ses_init(&ent, saved_argc, saved_argv, host, authctxt->user,
+           NULL, 0, NULL) != SIASUCCESS)
+               return (0);
+
+       if ((ret = sia_ses_authent(NULL, pass, ent)) != SIASUCCESS) {
+               error("Couldn't authenticate %s from %s",
+                   authctxt->user, host);
+               if (ret & SIASTOP)
+                       sia_ses_release(&ent);
+
+               return (0);
+       }
+
+       sia_ses_release(&ent);
+
+       return (1);
+}
+
+void
+session_setup_sia(struct passwd *pw, char *tty)
+{
+       SIAENTITY *ent = NULL;
+       const char *host;
+
+       host = get_canonical_hostname(options.use_dns);
+
+       if (sia_ses_init(&ent, saved_argc, saved_argv, host, pw->pw_name,
+           tty, 0, NULL) != SIASUCCESS)
+               fatal("sia_ses_init failed");
+
+       if (sia_make_entity_pwd(pw, ent) != SIASUCCESS) {
+               sia_ses_release(&ent);
+               fatal("sia_make_entity_pwd failed");
+       }
+
+       ent->authtype = SIA_A_NONE;
+       if (sia_ses_estab(sia_collect_trm, ent) != SIASUCCESS)
+               fatal("Couldn't establish session for %s from %s",
+                   pw->pw_name, host);
+
+       if (sia_ses_launch(sia_collect_trm, ent) != SIASUCCESS)
+               fatal("Couldn't launch session for %s from %s",
+                   pw->pw_name, host);
+
+       sia_ses_release(&ent);
+
+       setuid(0);
+       permanently_set_uid(pw);
+}
+
+#endif /* HAVE_OSF_SIA */
diff --git a/crypto/openssh/auth-sia.h b/crypto/openssh/auth-sia.h
new file mode 100644 (file)
index 0000000..27cbb93
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2002 Chris Adams.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "includes.h"
+
+#ifdef HAVE_OSF_SIA
+
+void   session_setup_sia(struct passwd *, char *);
+
+#endif /* HAVE_OSF_SIA */
diff --git a/crypto/openssh/auth2-jpake.c b/crypto/openssh/auth2-jpake.c
new file mode 100644 (file)
index 0000000..5de5506
--- /dev/null
@@ -0,0 +1,558 @@
+/* $OpenBSD: auth2-jpake.c,v 1.3 2009/03/05 07:18:19 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
+ */
+
+#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 "auth.h"
+#include "buffer.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 "schnorr.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);
+}
+
+/*
+ * Begin 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/crypto/openssh/jpake.c b/crypto/openssh/jpake.c
new file mode 100644 (file)
index 0000000..1306610
--- /dev/null
@@ -0,0 +1,449 @@
+/* $OpenBSD: jpake.c,v 1.2 2009/03/05 07:18:19 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"
+#include "schnorr.h"
+
+#ifdef JPAKE
+
+/* RFC3526 group 5, 1536 bits */
+#define JPAKE_GROUP_G "2"
+#define JPAKE_GROUP_P \
+       "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74" \
+       "020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437" \
+       "4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
+       "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05" \
+       "98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB" \
+       "9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF"
+
+struct modp_group *
+jpake_default_group(void)
+{
+       return modp_group_from_g_and_safe_p(JPAKE_GROUP_G, JPAKE_GROUP_P);
+}
+
+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 modp_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_buf(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_buf(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 modp_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_buf(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_buf(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_buf(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 modp_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_buf(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/crypto/openssh/schnorr.c b/crypto/openssh/schnorr.c
new file mode 100644 (file)
index 0000000..c17ff32
--- /dev/null
@@ -0,0 +1,649 @@
+/* $OpenBSD: schnorr.c,v 1.3 2009/03/05 07:18:19 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 "schnorr.h"
+
+#include "openbsd-compat/openssl-compat.h"
+
+/* #define SCHNORR_DEBUG */            /* Privacy-violating debugging */
+/* #define SCHNORR_MAIN */             /* Include main() selftest */
+
+#ifndef SCHNORR_DEBUG
+# define SCHNORR_DEBUG_BN(a)
+# define SCHNORR_DEBUG_BUF(a)
+#else
+# define SCHNORR_DEBUG_BN(a)   debug3_bn a
+# define SCHNORR_DEBUG_BUF(a)  debug3_buf a
+#endif /* SCHNORR_DEBUG */
+
+/*
+ * Calculate hash component of Schnorr signature H(g || g^v || g^x || id)
+ * using the hash function defined by "evp_md". Returns signature as
+ * bignum or NULL on error.
+ */
+static BIGNUM *
+schnorr_hash(const BIGNUM *p, const BIGNUM *q, const BIGNUM *g,
+    const EVP_MD *evp_md, const BIGNUM *g_v, const BIGNUM *g_x,
+    const u_char *id, u_int idlen)
+{
+       u_char *digest;
+       u_int digest_len;
+       BIGNUM *h;
+       Buffer b;
+       int success = -1;
+
+       if ((h = BN_new()) == NULL) {
+               error("%s: BN_new", __func__);
+               return NULL;
+       }
+
+       buffer_init(&b);
+
+       /* 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_md,
+           &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);
+       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'
+ * using the hash function "evp_md".
+ * 'idlen' bytes from 'id' will be included in the signature hash as an anti-
+ * replay salt.
+ * 
+ * On success, 0 is returned. The signature values are returned as *e_p
+ * (g^v mod p) and *r_p (v - xh mod q). The caller must free these values.
+ * On failure, -1 is returned.
+ */
+int
+schnorr_sign(const BIGNUM *grp_p, const BIGNUM *grp_q, const BIGNUM *grp_g,
+    const EVP_MD *evp_md, const BIGNUM *x, const BIGNUM *g_x,
+    const u_char *id, u_int idlen, BIGNUM **r_p, BIGNUM **e_p)
+{
+       int success = -1;
+       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, evp_md, 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((g_v, "%s: e = ", __func__));
+       SCHNORR_DEBUG_BN((r, "%s: r = ", __func__));
+
+       *e_p = g_v;
+       *r_p = r;
+
+       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(tmp);
+
+       return success;
+}
+
+/*
+ * 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'
+ * using a SHA256 hash.
+ * '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_buf(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)
+{
+       Buffer b;
+       BIGNUM *r, *e;
+
+       if (schnorr_sign(grp_p, grp_q, grp_g, EVP_sha256(),
+           x, g_x, id, idlen, &r, &e) != 0)
+               return -1;
+
+       /* Signature is (e, r) */
+       buffer_init(&b);
+       /* XXX sigtype-hash as string? */
+       buffer_put_bignum2(&b, e);
+       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);
+
+       BN_clear_free(r);
+       BN_clear_free(e);
+
+       return 0;
+}
+
+/*
+ * Verify Schnorr signature { r (v - xh mod q), e (g^v mod p) } against
+ * public exponent g_x (g^x) under group defined by 'grp_p', 'grp_q' and
+ * 'grp_g' using hash "evp_md".
+ * 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 EVP_MD *evp_md, const BIGNUM *g_x, const u_char *id, u_int idlen,
+    const BIGNUM *r, const BIGNUM *e)
+{
+       int success = -1;
+       BIGNUM *h, *g_xh, *g_r, *expected;
+       BN_CTX *bn_ctx;
+
+       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_xh = g_r = expected = NULL;
+       if ((bn_ctx = BN_CTX_new()) == NULL) {
+               error("%s: BN_CTX_new", __func__);
+               goto out;
+       }
+       if ((g_xh = BN_new()) == NULL ||
+           (g_r = BN_new()) == NULL ||
+           (expected = BN_new()) == NULL) {
+               error("%s: BN_new", __func__);
+               goto out;
+       }
+
+       SCHNORR_DEBUG_BN((e, "%s: e = ", __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, evp_md, e, 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 e == expected */
+       success = BN_cmp(expected, e) == 0;
+ out:
+       BN_CTX_free(bn_ctx);
+       if (h != NULL)
+               BN_clear_free(h);
+       BN_clear_free(g_xh);
+       BN_clear_free(g_r);
+       BN_clear_free(expected);
+       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' using a
+ * SHA256 hash.
+ * 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_buf(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)
+{
+       Buffer b;
+       int ret = -1;
+       u_int rlen;
+       BIGNUM *r, *e;
+
+       e = r = NULL;
+       if ((e = BN_new()) == NULL ||
+           (r = 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, e);
+       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;
+       }
+
+       ret = schnorr_verify(grp_p, grp_q, grp_g, EVP_sha256(),
+           g_x, id, idlen, r, e);
+ out:
+       BN_clear_free(e);
+       BN_clear_free(r);
+
+       return ret;
+}
+
+/* Helper functions */
+
+/*
+ * 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
+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
+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;
+               }
+       }
+}
+
+/*
+ * Construct a MODP group from hex strings p (which must be a safe
+ * prime) and g, automatically calculating subgroup q as (p / 2)
+ */
+struct modp_group *
+modp_group_from_g_and_safe_p(const char *grp_g, const char *grp_p)
+{
+       struct modp_group *ret;
+
+       ret = xmalloc(sizeof(*ret));
+       ret->p = ret->q = ret->g = NULL;
+       if (BN_hex2bn(&ret->p, grp_p) == 0 ||
+           BN_hex2bn(&ret->g, grp_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;
+}
+
+void
+modp_group_free(struct modp_group *grp)
+{
+       if (grp->g != NULL)
+               BN_clear_free(grp->g);
+       if (grp->p != NULL)
+               BN_clear_free(grp->p);
+       if (grp->q != NULL)
+               BN_clear_free(grp->q);
+       bzero(grp, sizeof(*grp));
+       xfree(grp);
+}
+
+/* main() function for self-test */
+
+#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_buf(grp_p, grp_q, grp_g, x, g_x, "junk", 4,
+           &sig, &siglen))
+               fatal("%s: schnorr_sign", __func__);
+       if (schnorr_verify_buf(grp_p, grp_q, grp_g, g_x, "junk", 4,
+           sig, siglen) != 1)
+               fatal("%s: verify fail", __func__);
+       if (schnorr_verify_buf(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_buf(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 modp_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
+