Damien Miller | eb8b60e | 2010-08-31 22:41:14 +1000 | [diff] [blame] | 1 | /* $OpenBSD: auth2-jpake.c,v 1.4 2010/08/31 11:54:45 djm Exp $ */ |
Damien Miller | 01ed227 | 2008-11-05 16:20:46 +1100 | [diff] [blame] | 2 | /* |
| 3 | * Copyright (c) 2008 Damien Miller. All rights reserved. |
| 4 | * |
| 5 | * Permission to use, copy, modify, and distribute this software for any |
| 6 | * purpose with or without fee is hereby granted, provided that the above |
| 7 | * copyright notice and this permission notice appear in all copies. |
| 8 | * |
| 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| 16 | */ |
| 17 | |
| 18 | /* |
| 19 | * Server side of zero-knowledge password auth using J-PAKE protocol |
| 20 | * as described in: |
| 21 | * |
| 22 | * F. Hao, P. Ryan, "Password Authenticated Key Exchange by Juggling", |
| 23 | * 16th Workshop on Security Protocols, Cambridge, April 2008 |
| 24 | * |
| 25 | * http://grouper.ieee.org/groups/1363/Research/contributions/hao-ryan-2008.pdf |
| 26 | */ |
| 27 | |
Darren Tucker | c6d744e | 2008-11-11 16:33:03 +1100 | [diff] [blame] | 28 | #ifdef JPAKE |
| 29 | |
Damien Miller | 01ed227 | 2008-11-05 16:20:46 +1100 | [diff] [blame] | 30 | #include <sys/types.h> |
| 31 | #include <sys/param.h> |
| 32 | |
| 33 | #include <pwd.h> |
| 34 | #include <stdio.h> |
| 35 | #include <string.h> |
| 36 | #include <login_cap.h> |
| 37 | |
| 38 | #include <openssl/bn.h> |
| 39 | #include <openssl/evp.h> |
| 40 | |
| 41 | #include "xmalloc.h" |
| 42 | #include "ssh2.h" |
| 43 | #include "key.h" |
| 44 | #include "hostfile.h" |
Damien Miller | 01ed227 | 2008-11-05 16:20:46 +1100 | [diff] [blame] | 45 | #include "auth.h" |
Darren Tucker | 43e7a35 | 2009-06-21 19:50:08 +1000 | [diff] [blame] | 46 | #include "buffer.h" |
Damien Miller | 01ed227 | 2008-11-05 16:20:46 +1100 | [diff] [blame] | 47 | #include "packet.h" |
| 48 | #include "dispatch.h" |
| 49 | #include "log.h" |
| 50 | #include "servconf.h" |
| 51 | #include "auth-options.h" |
| 52 | #include "canohost.h" |
| 53 | #ifdef GSSAPI |
| 54 | #include "ssh-gss.h" |
| 55 | #endif |
| 56 | #include "monitor_wrap.h" |
| 57 | |
Damien Miller | cee8523 | 2009-03-06 00:58:22 +1100 | [diff] [blame] | 58 | #include "schnorr.h" |
Damien Miller | 01ed227 | 2008-11-05 16:20:46 +1100 | [diff] [blame] | 59 | #include "jpake.h" |
| 60 | |
Damien Miller | 01ed227 | 2008-11-05 16:20:46 +1100 | [diff] [blame] | 61 | /* |
| 62 | * XXX options->permit_empty_passwd (at the moment, they will be refused |
| 63 | * anyway because they will mismatch on fake salt. |
| 64 | */ |
| 65 | |
| 66 | /* Dispatch handlers */ |
| 67 | static void input_userauth_jpake_client_step1(int, u_int32_t, void *); |
| 68 | static void input_userauth_jpake_client_step2(int, u_int32_t, void *); |
| 69 | static void input_userauth_jpake_client_confirm(int, u_int32_t, void *); |
| 70 | |
| 71 | static int auth2_jpake_start(Authctxt *); |
| 72 | |
| 73 | /* import */ |
| 74 | extern ServerOptions options; |
| 75 | extern u_char *session_id2; |
| 76 | extern u_int session_id2_len; |
| 77 | |
| 78 | /* |
| 79 | * Attempt J-PAKE authentication. |
| 80 | */ |
| 81 | static int |
| 82 | userauth_jpake(Authctxt *authctxt) |
| 83 | { |
| 84 | int authenticated = 0; |
| 85 | |
| 86 | packet_check_eom(); |
| 87 | |
| 88 | debug("jpake-01@openssh.com requested"); |
| 89 | |
| 90 | if (authctxt->user != NULL) { |
| 91 | if (authctxt->jpake_ctx == NULL) |
| 92 | authctxt->jpake_ctx = jpake_new(); |
| 93 | if (options.zero_knowledge_password_authentication) |
| 94 | authenticated = auth2_jpake_start(authctxt); |
| 95 | } |
| 96 | |
| 97 | return authenticated; |
| 98 | } |
| 99 | |
| 100 | Authmethod method_jpake = { |
| 101 | "jpake-01@openssh.com", |
| 102 | userauth_jpake, |
| 103 | &options.zero_knowledge_password_authentication |
| 104 | }; |
| 105 | |
| 106 | /* Clear context and callbacks */ |
| 107 | void |
| 108 | auth2_jpake_stop(Authctxt *authctxt) |
| 109 | { |
| 110 | /* unregister callbacks */ |
| 111 | dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP1, NULL); |
| 112 | dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP2, NULL); |
| 113 | dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_CONFIRM, NULL); |
| 114 | if (authctxt->jpake_ctx != NULL) { |
| 115 | jpake_free(authctxt->jpake_ctx); |
| 116 | authctxt->jpake_ctx = NULL; |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | /* Returns 1 if 'c' is a valid crypt(3) salt character, 0 otherwise */ |
| 121 | static int |
| 122 | valid_crypt_salt(int c) |
| 123 | { |
| 124 | if (c >= 'A' && c <= 'Z') |
| 125 | return 1; |
| 126 | if (c >= 'a' && c <= 'z') |
| 127 | return 1; |
| 128 | if (c >= '.' && c <= '9') |
| 129 | return 1; |
| 130 | return 0; |
| 131 | } |
| 132 | |
| 133 | /* |
| 134 | * Derive fake salt as H(username || first_private_host_key) |
| 135 | * This provides relatively stable fake salts for non-existent |
| 136 | * users and avoids the jpake method becoming an account validity |
| 137 | * oracle. |
| 138 | */ |
| 139 | static void |
| 140 | derive_rawsalt(const char *username, u_char *rawsalt, u_int len) |
| 141 | { |
| 142 | u_char *digest; |
| 143 | u_int digest_len; |
| 144 | Buffer b; |
| 145 | Key *k; |
| 146 | |
| 147 | buffer_init(&b); |
| 148 | buffer_put_cstring(&b, username); |
| 149 | if ((k = get_hostkey_by_index(0)) == NULL || |
| 150 | (k->flags & KEY_FLAG_EXT)) |
| 151 | fatal("%s: no hostkeys", __func__); |
| 152 | switch (k->type) { |
| 153 | case KEY_RSA1: |
| 154 | case KEY_RSA: |
| 155 | if (k->rsa->p == NULL || k->rsa->q == NULL) |
| 156 | fatal("%s: RSA key missing p and/or q", __func__); |
| 157 | buffer_put_bignum2(&b, k->rsa->p); |
| 158 | buffer_put_bignum2(&b, k->rsa->q); |
| 159 | break; |
| 160 | case KEY_DSA: |
| 161 | if (k->dsa->priv_key == NULL) |
| 162 | fatal("%s: DSA key missing priv_key", __func__); |
| 163 | buffer_put_bignum2(&b, k->dsa->priv_key); |
| 164 | break; |
Damien Miller | eb8b60e | 2010-08-31 22:41:14 +1000 | [diff] [blame] | 165 | case KEY_ECDSA: |
| 166 | if (EC_KEY_get0_private_key(k->ecdsa) == NULL) |
| 167 | fatal("%s: ECDSA key missing priv_key", __func__); |
| 168 | buffer_put_bignum2(&b, EC_KEY_get0_private_key(k->ecdsa)); |
| 169 | break; |
Damien Miller | 01ed227 | 2008-11-05 16:20:46 +1100 | [diff] [blame] | 170 | default: |
| 171 | fatal("%s: unknown key type %d", __func__, k->type); |
| 172 | } |
| 173 | if (hash_buffer(buffer_ptr(&b), buffer_len(&b), EVP_sha256(), |
| 174 | &digest, &digest_len) != 0) |
| 175 | fatal("%s: hash_buffer", __func__); |
| 176 | buffer_free(&b); |
| 177 | if (len > digest_len) |
| 178 | fatal("%s: not enough bytes for rawsalt (want %u have %u)", |
| 179 | __func__, len, digest_len); |
| 180 | memcpy(rawsalt, digest, len); |
| 181 | bzero(digest, digest_len); |
| 182 | xfree(digest); |
| 183 | } |
| 184 | |
| 185 | /* ASCII an integer [0, 64) for inclusion in a password/salt */ |
| 186 | static char |
| 187 | pw_encode64(u_int i64) |
| 188 | { |
| 189 | const u_char e64[] = |
| 190 | "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; |
| 191 | return e64[i64 % 64]; |
| 192 | } |
| 193 | |
| 194 | /* Generate ASCII salt bytes for user */ |
| 195 | static char * |
| 196 | makesalt(u_int want, const char *user) |
| 197 | { |
| 198 | u_char rawsalt[32]; |
| 199 | static char ret[33]; |
| 200 | u_int i; |
| 201 | |
| 202 | if (want > sizeof(ret) - 1) |
| 203 | fatal("%s: want %u", __func__, want); |
| 204 | |
| 205 | derive_rawsalt(user, rawsalt, sizeof(rawsalt)); |
| 206 | bzero(ret, sizeof(ret)); |
| 207 | for (i = 0; i < want; i++) |
| 208 | ret[i] = pw_encode64(rawsalt[i]); |
| 209 | bzero(rawsalt, sizeof(rawsalt)); |
| 210 | |
| 211 | return ret; |
| 212 | } |
| 213 | |
| 214 | /* |
| 215 | * Select the system's default password hashing scheme and generate |
| 216 | * a stable fake salt under it for use by a non-existent account. |
| 217 | * Prevents jpake method being used to infer the validity of accounts. |
| 218 | */ |
| 219 | static void |
| 220 | fake_salt_and_scheme(Authctxt *authctxt, char **salt, char **scheme) |
| 221 | { |
| 222 | char *rounds_s, *style; |
| 223 | long long rounds; |
| 224 | login_cap_t *lc; |
| 225 | |
| 226 | |
| 227 | if ((lc = login_getclass(authctxt->pw->pw_class)) == NULL && |
| 228 | (lc = login_getclass(NULL)) == NULL) |
| 229 | fatal("%s: login_getclass failed", __func__); |
| 230 | style = login_getcapstr(lc, "localcipher", NULL, NULL); |
| 231 | if (style == NULL) |
| 232 | style = xstrdup("blowfish,6"); |
| 233 | login_close(lc); |
| 234 | |
| 235 | if ((rounds_s = strchr(style, ',')) != NULL) |
| 236 | *rounds_s++ = '\0'; |
| 237 | rounds = strtonum(rounds_s, 1, 1<<31, NULL); |
| 238 | |
| 239 | if (strcmp(style, "md5") == 0) { |
| 240 | xasprintf(salt, "$1$%s$", makesalt(8, authctxt->user)); |
| 241 | *scheme = xstrdup("md5"); |
| 242 | } else if (strcmp(style, "old") == 0) { |
| 243 | *salt = xstrdup(makesalt(2, authctxt->user)); |
| 244 | *scheme = xstrdup("crypt"); |
| 245 | } else if (strcmp(style, "newsalt") == 0) { |
| 246 | rounds = MAX(rounds, 7250); |
| 247 | rounds = MIN(rounds, (1<<24) - 1); |
| 248 | xasprintf(salt, "_%c%c%c%c%s", |
| 249 | pw_encode64(rounds), pw_encode64(rounds >> 6), |
| 250 | pw_encode64(rounds >> 12), pw_encode64(rounds >> 18), |
| 251 | makesalt(4, authctxt->user)); |
| 252 | *scheme = xstrdup("crypt-extended"); |
| 253 | } else { |
| 254 | /* Default to blowfish */ |
| 255 | rounds = MAX(rounds, 3); |
| 256 | rounds = MIN(rounds, 31); |
| 257 | xasprintf(salt, "$2a$%02lld$%s", rounds, |
| 258 | makesalt(22, authctxt->user)); |
| 259 | *scheme = xstrdup("bcrypt"); |
| 260 | } |
| 261 | xfree(style); |
| 262 | debug3("%s: fake %s salt for user %s: %s", |
| 263 | __func__, *scheme, authctxt->user, *salt); |
| 264 | } |
| 265 | |
| 266 | /* |
| 267 | * Fetch password hashing scheme, password salt and derive shared secret |
| 268 | * for user. If user does not exist, a fake but stable and user-unique |
| 269 | * salt will be returned. |
| 270 | */ |
| 271 | void |
| 272 | auth2_jpake_get_pwdata(Authctxt *authctxt, BIGNUM **s, |
| 273 | char **hash_scheme, char **salt) |
| 274 | { |
| 275 | char *cp; |
| 276 | u_char *secret; |
| 277 | u_int secret_len, salt_len; |
| 278 | |
| 279 | #ifdef JPAKE_DEBUG |
| 280 | debug3("%s: valid %d pw %.5s...", __func__, |
| 281 | authctxt->valid, authctxt->pw->pw_passwd); |
| 282 | #endif |
| 283 | |
| 284 | *salt = NULL; |
| 285 | *hash_scheme = NULL; |
| 286 | if (authctxt->valid) { |
| 287 | if (strncmp(authctxt->pw->pw_passwd, "$2$", 3) == 0 && |
| 288 | strlen(authctxt->pw->pw_passwd) > 28) { |
| 289 | /* |
| 290 | * old-variant bcrypt: |
| 291 | * "$2$", 2 digit rounds, "$", 22 bytes salt |
| 292 | */ |
| 293 | salt_len = 3 + 2 + 1 + 22 + 1; |
| 294 | *salt = xmalloc(salt_len); |
| 295 | strlcpy(*salt, authctxt->pw->pw_passwd, salt_len); |
| 296 | *hash_scheme = xstrdup("bcrypt"); |
| 297 | } else if (strncmp(authctxt->pw->pw_passwd, "$2a$", 4) == 0 && |
| 298 | strlen(authctxt->pw->pw_passwd) > 29) { |
| 299 | /* |
| 300 | * current-variant bcrypt: |
| 301 | * "$2a$", 2 digit rounds, "$", 22 bytes salt |
| 302 | */ |
| 303 | salt_len = 4 + 2 + 1 + 22 + 1; |
| 304 | *salt = xmalloc(salt_len); |
| 305 | strlcpy(*salt, authctxt->pw->pw_passwd, salt_len); |
| 306 | *hash_scheme = xstrdup("bcrypt"); |
| 307 | } else if (strncmp(authctxt->pw->pw_passwd, "$1$", 3) == 0 && |
| 308 | strlen(authctxt->pw->pw_passwd) > 5) { |
| 309 | /* |
| 310 | * md5crypt: |
| 311 | * "$1$", salt until "$" |
| 312 | */ |
| 313 | cp = strchr(authctxt->pw->pw_passwd + 3, '$'); |
| 314 | if (cp != NULL) { |
| 315 | salt_len = (cp - authctxt->pw->pw_passwd) + 1; |
| 316 | *salt = xmalloc(salt_len); |
| 317 | strlcpy(*salt, authctxt->pw->pw_passwd, |
| 318 | salt_len); |
| 319 | *hash_scheme = xstrdup("md5crypt"); |
| 320 | } |
| 321 | } else if (strncmp(authctxt->pw->pw_passwd, "_", 1) == 0 && |
| 322 | strlen(authctxt->pw->pw_passwd) > 9) { |
| 323 | /* |
| 324 | * BSDI extended crypt: |
| 325 | * "_", 4 digits count, 4 chars salt |
| 326 | */ |
| 327 | salt_len = 1 + 4 + 4 + 1; |
| 328 | *salt = xmalloc(salt_len); |
| 329 | strlcpy(*salt, authctxt->pw->pw_passwd, salt_len); |
| 330 | *hash_scheme = xstrdup("crypt-extended"); |
| 331 | } else if (strlen(authctxt->pw->pw_passwd) == 13 && |
| 332 | valid_crypt_salt(authctxt->pw->pw_passwd[0]) && |
| 333 | valid_crypt_salt(authctxt->pw->pw_passwd[1])) { |
| 334 | /* |
| 335 | * traditional crypt: |
| 336 | * 2 chars salt |
| 337 | */ |
| 338 | salt_len = 2 + 1; |
| 339 | *salt = xmalloc(salt_len); |
| 340 | strlcpy(*salt, authctxt->pw->pw_passwd, salt_len); |
| 341 | *hash_scheme = xstrdup("crypt"); |
| 342 | } |
| 343 | if (*salt == NULL) { |
| 344 | debug("%s: unrecognised crypt scheme for user %s", |
| 345 | __func__, authctxt->pw->pw_name); |
| 346 | } |
| 347 | } |
| 348 | if (*salt == NULL) |
| 349 | fake_salt_and_scheme(authctxt, salt, hash_scheme); |
| 350 | |
| 351 | if (hash_buffer(authctxt->pw->pw_passwd, |
| 352 | strlen(authctxt->pw->pw_passwd), EVP_sha256(), |
| 353 | &secret, &secret_len) != 0) |
| 354 | fatal("%s: hash_buffer", __func__); |
| 355 | if ((*s = BN_bin2bn(secret, secret_len, NULL)) == NULL) |
| 356 | fatal("%s: BN_bin2bn (secret)", __func__); |
| 357 | #ifdef JPAKE_DEBUG |
| 358 | debug3("%s: salt = %s (len %u)", __func__, |
| 359 | *salt, (u_int)strlen(*salt)); |
| 360 | debug3("%s: scheme = %s", __func__, *hash_scheme); |
| 361 | JPAKE_DEBUG_BN((*s, "%s: s = ", __func__)); |
| 362 | #endif |
| 363 | bzero(secret, secret_len); |
| 364 | xfree(secret); |
| 365 | } |
| 366 | |
| 367 | /* |
Damien Miller | cee8523 | 2009-03-06 00:58:22 +1100 | [diff] [blame] | 368 | * Begin authentication attempt. |
Damien Miller | 01ed227 | 2008-11-05 16:20:46 +1100 | [diff] [blame] | 369 | * Note, sets authctxt->postponed while in subprotocol |
| 370 | */ |
| 371 | static int |
| 372 | auth2_jpake_start(Authctxt *authctxt) |
| 373 | { |
| 374 | struct jpake_ctx *pctx = authctxt->jpake_ctx; |
| 375 | u_char *x3_proof, *x4_proof; |
| 376 | u_int x3_proof_len, x4_proof_len; |
| 377 | char *salt, *hash_scheme; |
| 378 | |
| 379 | debug("%s: start", __func__); |
| 380 | |
| 381 | PRIVSEP(jpake_step1(pctx->grp, |
| 382 | &pctx->server_id, &pctx->server_id_len, |
| 383 | &pctx->x3, &pctx->x4, &pctx->g_x3, &pctx->g_x4, |
| 384 | &x3_proof, &x3_proof_len, |
| 385 | &x4_proof, &x4_proof_len)); |
| 386 | |
| 387 | PRIVSEP(auth2_jpake_get_pwdata(authctxt, &pctx->s, |
| 388 | &hash_scheme, &salt)); |
| 389 | |
| 390 | if (!use_privsep) |
| 391 | JPAKE_DEBUG_CTX((pctx, "step 1 sending in %s", __func__)); |
| 392 | |
| 393 | packet_start(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP1); |
| 394 | packet_put_cstring(hash_scheme); |
| 395 | packet_put_cstring(salt); |
| 396 | packet_put_string(pctx->server_id, pctx->server_id_len); |
| 397 | packet_put_bignum2(pctx->g_x3); |
| 398 | packet_put_bignum2(pctx->g_x4); |
| 399 | packet_put_string(x3_proof, x3_proof_len); |
| 400 | packet_put_string(x4_proof, x4_proof_len); |
| 401 | packet_send(); |
| 402 | packet_write_wait(); |
| 403 | |
| 404 | bzero(hash_scheme, strlen(hash_scheme)); |
| 405 | bzero(salt, strlen(salt)); |
| 406 | xfree(hash_scheme); |
| 407 | xfree(salt); |
| 408 | bzero(x3_proof, x3_proof_len); |
| 409 | bzero(x4_proof, x4_proof_len); |
| 410 | xfree(x3_proof); |
| 411 | xfree(x4_proof); |
| 412 | |
| 413 | /* Expect step 1 packet from peer */ |
| 414 | dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP1, |
| 415 | input_userauth_jpake_client_step1); |
| 416 | |
| 417 | authctxt->postponed = 1; |
| 418 | return 0; |
| 419 | } |
| 420 | |
| 421 | /* ARGSUSED */ |
| 422 | static void |
| 423 | input_userauth_jpake_client_step1(int type, u_int32_t seq, void *ctxt) |
| 424 | { |
| 425 | Authctxt *authctxt = ctxt; |
| 426 | struct jpake_ctx *pctx = authctxt->jpake_ctx; |
| 427 | u_char *x1_proof, *x2_proof, *x4_s_proof; |
| 428 | u_int x1_proof_len, x2_proof_len, x4_s_proof_len; |
| 429 | |
| 430 | /* Disable this message */ |
| 431 | dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP1, NULL); |
| 432 | |
| 433 | /* Fetch step 1 values */ |
| 434 | if ((pctx->g_x1 = BN_new()) == NULL || |
| 435 | (pctx->g_x2 = BN_new()) == NULL) |
| 436 | fatal("%s: BN_new", __func__); |
| 437 | pctx->client_id = packet_get_string(&pctx->client_id_len); |
| 438 | packet_get_bignum2(pctx->g_x1); |
| 439 | packet_get_bignum2(pctx->g_x2); |
| 440 | x1_proof = packet_get_string(&x1_proof_len); |
| 441 | x2_proof = packet_get_string(&x2_proof_len); |
| 442 | packet_check_eom(); |
| 443 | |
| 444 | if (!use_privsep) |
| 445 | JPAKE_DEBUG_CTX((pctx, "step 1 received in %s", __func__)); |
| 446 | |
| 447 | PRIVSEP(jpake_step2(pctx->grp, pctx->s, pctx->g_x3, |
| 448 | pctx->g_x1, pctx->g_x2, pctx->x4, |
| 449 | pctx->client_id, pctx->client_id_len, |
| 450 | pctx->server_id, pctx->server_id_len, |
| 451 | x1_proof, x1_proof_len, |
| 452 | x2_proof, x2_proof_len, |
| 453 | &pctx->b, |
| 454 | &x4_s_proof, &x4_s_proof_len)); |
| 455 | |
| 456 | bzero(x1_proof, x1_proof_len); |
| 457 | bzero(x2_proof, x2_proof_len); |
| 458 | xfree(x1_proof); |
| 459 | xfree(x2_proof); |
| 460 | |
| 461 | if (!use_privsep) |
| 462 | JPAKE_DEBUG_CTX((pctx, "step 2 sending in %s", __func__)); |
| 463 | |
| 464 | /* Send values for step 2 */ |
| 465 | packet_start(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP2); |
| 466 | packet_put_bignum2(pctx->b); |
| 467 | packet_put_string(x4_s_proof, x4_s_proof_len); |
| 468 | packet_send(); |
| 469 | packet_write_wait(); |
| 470 | |
| 471 | bzero(x4_s_proof, x4_s_proof_len); |
| 472 | xfree(x4_s_proof); |
| 473 | |
| 474 | /* Expect step 2 packet from peer */ |
| 475 | dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP2, |
| 476 | input_userauth_jpake_client_step2); |
| 477 | } |
| 478 | |
| 479 | /* ARGSUSED */ |
| 480 | static void |
| 481 | input_userauth_jpake_client_step2(int type, u_int32_t seq, void *ctxt) |
| 482 | { |
| 483 | Authctxt *authctxt = ctxt; |
| 484 | struct jpake_ctx *pctx = authctxt->jpake_ctx; |
| 485 | u_char *x2_s_proof; |
| 486 | u_int x2_s_proof_len; |
| 487 | |
| 488 | /* Disable this message */ |
| 489 | dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP2, NULL); |
| 490 | |
| 491 | if ((pctx->a = BN_new()) == NULL) |
| 492 | fatal("%s: BN_new", __func__); |
| 493 | |
| 494 | /* Fetch step 2 values */ |
| 495 | packet_get_bignum2(pctx->a); |
| 496 | x2_s_proof = packet_get_string(&x2_s_proof_len); |
| 497 | packet_check_eom(); |
| 498 | |
| 499 | if (!use_privsep) |
| 500 | JPAKE_DEBUG_CTX((pctx, "step 2 received in %s", __func__)); |
| 501 | |
| 502 | /* Derive shared key and calculate confirmation hash */ |
| 503 | PRIVSEP(jpake_key_confirm(pctx->grp, pctx->s, pctx->a, |
| 504 | pctx->x4, pctx->g_x3, pctx->g_x4, pctx->g_x1, pctx->g_x2, |
| 505 | pctx->server_id, pctx->server_id_len, |
| 506 | pctx->client_id, pctx->client_id_len, |
| 507 | session_id2, session_id2_len, |
| 508 | x2_s_proof, x2_s_proof_len, |
| 509 | &pctx->k, |
| 510 | &pctx->h_k_sid_sessid, &pctx->h_k_sid_sessid_len)); |
| 511 | |
| 512 | bzero(x2_s_proof, x2_s_proof_len); |
| 513 | xfree(x2_s_proof); |
| 514 | |
| 515 | if (!use_privsep) |
| 516 | JPAKE_DEBUG_CTX((pctx, "confirm sending in %s", __func__)); |
| 517 | |
| 518 | /* Send key confirmation proof */ |
| 519 | packet_start(SSH2_MSG_USERAUTH_JPAKE_SERVER_CONFIRM); |
| 520 | packet_put_string(pctx->h_k_sid_sessid, pctx->h_k_sid_sessid_len); |
| 521 | packet_send(); |
| 522 | packet_write_wait(); |
| 523 | |
| 524 | /* Expect confirmation from peer */ |
| 525 | dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_CONFIRM, |
| 526 | input_userauth_jpake_client_confirm); |
| 527 | } |
| 528 | |
| 529 | /* ARGSUSED */ |
| 530 | static void |
| 531 | input_userauth_jpake_client_confirm(int type, u_int32_t seq, void *ctxt) |
| 532 | { |
| 533 | Authctxt *authctxt = ctxt; |
| 534 | struct jpake_ctx *pctx = authctxt->jpake_ctx; |
| 535 | int authenticated = 0; |
| 536 | |
| 537 | /* Disable this message */ |
| 538 | dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_CONFIRM, NULL); |
| 539 | |
| 540 | pctx->h_k_cid_sessid = packet_get_string(&pctx->h_k_cid_sessid_len); |
| 541 | packet_check_eom(); |
| 542 | |
| 543 | if (!use_privsep) |
| 544 | JPAKE_DEBUG_CTX((pctx, "confirm received in %s", __func__)); |
| 545 | |
| 546 | /* Verify expected confirmation hash */ |
| 547 | if (PRIVSEP(jpake_check_confirm(pctx->k, |
| 548 | pctx->client_id, pctx->client_id_len, |
| 549 | session_id2, session_id2_len, |
| 550 | pctx->h_k_cid_sessid, pctx->h_k_cid_sessid_len)) == 1) |
| 551 | authenticated = authctxt->valid ? 1 : 0; |
| 552 | else |
| 553 | debug("%s: confirmation mismatch", __func__); |
| 554 | |
| 555 | /* done */ |
| 556 | authctxt->postponed = 0; |
| 557 | jpake_free(authctxt->jpake_ctx); |
| 558 | authctxt->jpake_ctx = NULL; |
| 559 | userauth_finish(authctxt, authenticated, method_jpake.name); |
| 560 | } |
| 561 | |
| 562 | #endif /* JPAKE */ |
| 563 | |