|  | /* $OpenBSD: auth2-jpake.c,v 1.6 2013/05/17 00:13:13 djm Exp $ */ | 
|  | /* | 
|  | * Copyright (c) 2008 Damien Miller.  All rights reserved. | 
|  | * | 
|  | * Permission to use, copy, modify, and distribute this software for any | 
|  | * purpose with or without fee is hereby granted, provided that the above | 
|  | * copyright notice and this permission notice appear in all copies. | 
|  | * | 
|  | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | 
|  | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | 
|  | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | 
|  | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | 
|  | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | 
|  | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | 
|  | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * 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; | 
|  | case KEY_ECDSA: | 
|  | if (EC_KEY_get0_private_key(k->ecdsa) == NULL) | 
|  | fatal("%s: ECDSA key missing priv_key", __func__); | 
|  | buffer_put_bignum2(&b, EC_KEY_get0_private_key(k->ecdsa)); | 
|  | 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); | 
|  | free(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"); | 
|  | } | 
|  | free(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); | 
|  | free(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)); | 
|  | free(hash_scheme); | 
|  | free(salt); | 
|  | bzero(x3_proof, x3_proof_len); | 
|  | bzero(x4_proof, x4_proof_len); | 
|  | free(x3_proof); | 
|  | free(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); | 
|  | free(x1_proof); | 
|  | free(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); | 
|  | free(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); | 
|  | free(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, NULL); | 
|  | } | 
|  |  | 
|  | #endif /* JPAKE */ | 
|  |  |