2009-03-05 13:58:22 +00:00
|
|
|
/* $OpenBSD: auth2-jpake.c,v 1.3 2009/03/05 07:18:19 djm Exp $ */
|
2008-11-05 05:20:46 +00:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2008-11-11 05:33:03 +00:00
|
|
|
#ifdef JPAKE
|
|
|
|
|
2008-11-05 05:20:46 +00:00
|
|
|
#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"
|
2009-06-21 09:50:08 +00:00
|
|
|
#include "buffer.h"
|
2008-11-05 05:20:46 +00:00
|
|
|
#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"
|
|
|
|
|
2009-03-05 13:58:22 +00:00
|
|
|
#include "schnorr.h"
|
2008-11-05 05:20:46 +00:00
|
|
|
#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);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2009-03-05 13:58:22 +00:00
|
|
|
* Begin authentication attempt.
|
2008-11-05 05:20:46 +00:00
|
|
|
* 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 */
|
|
|
|
|