From: Peter Avalos Date: Sun, 10 Jan 2010 05:15:44 +0000 (+0000) Subject: Merge branch 'vendor/OPENSSH' X-Git-Tag: v2.7.1~229^2~128^2~2 X-Git-Url: https://gitweb.dragonflybsd.org/dragonfly.git/commitdiff_plain/983841bc11a64d3efce987b59c452b5f81c72ffe?hp=6998e795a839d7abdb62db6ba716314177b83761 Merge branch 'vendor/OPENSSH' --- diff --git a/crypto/openssh/README.DELETED b/crypto/openssh/README.DELETED index 335dd8c4d7..82b0a5671a 100644 --- a/crypto/openssh/README.DELETED +++ b/crypto/openssh/README.DELETED @@ -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 index 0000000000..2190916776 --- /dev/null +++ b/crypto/openssh/auth-shadow.c @@ -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 +#include +#include +#include + +#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 index 0000000000..a9e1c258ca --- /dev/null +++ b/crypto/openssh/auth-sia.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 0000000000..27cbb93f12 --- /dev/null +++ b/crypto/openssh/auth-sia.h @@ -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 index 0000000000..5de5506a66 --- /dev/null +++ b/crypto/openssh/auth2-jpake.c @@ -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 +#include + +#include +#include +#include +#include + +#include +#include + +#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 index 0000000000..130661069f --- /dev/null +++ b/crypto/openssh/jpake.c @@ -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 + +#include +#include +#include + +#include +#include + +#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 index 0000000000..c17ff3241b --- /dev/null +++ b/crypto/openssh/schnorr.c @@ -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 + +#include +#include +#include + +#include +#include + +#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 +