]> andersk Git - openssh.git/blame - auth2-jpake.c
- djm@cvs.openbsd.org 2010/01/30 02:54:53
[openssh.git] / auth2-jpake.c
CommitLineData
5b01421b 1/* $OpenBSD: auth2-jpake.c,v 1.3 2009/03/05 07:18:19 djm Exp $ */
5adf6b9a 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
3d7f6c3d 28#ifdef JPAKE
29
5adf6b9a 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"
5adf6b9a 45#include "auth.h"
9b9302ea 46#include "buffer.h"
5adf6b9a 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
5b01421b 58#include "schnorr.h"
5adf6b9a 59#include "jpake.h"
60
5adf6b9a 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 */
67static void input_userauth_jpake_client_step1(int, u_int32_t, void *);
68static void input_userauth_jpake_client_step2(int, u_int32_t, void *);
69static void input_userauth_jpake_client_confirm(int, u_int32_t, void *);
70
71static int auth2_jpake_start(Authctxt *);
72
73/* import */
74extern ServerOptions options;
75extern u_char *session_id2;
76extern u_int session_id2_len;
77
78/*
79 * Attempt J-PAKE authentication.
80 */
81static int
82userauth_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
100Authmethod method_jpake = {
101 "jpake-01@openssh.com",
102 userauth_jpake,
103 &options.zero_knowledge_password_authentication
104};
105
106/* Clear context and callbacks */
107void
108auth2_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 */
121static int
122valid_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 */
139static void
140derive_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;
165 default:
166 fatal("%s: unknown key type %d", __func__, k->type);
167 }
168 if (hash_buffer(buffer_ptr(&b), buffer_len(&b), EVP_sha256(),
169 &digest, &digest_len) != 0)
170 fatal("%s: hash_buffer", __func__);
171 buffer_free(&b);
172 if (len > digest_len)
173 fatal("%s: not enough bytes for rawsalt (want %u have %u)",
174 __func__, len, digest_len);
175 memcpy(rawsalt, digest, len);
176 bzero(digest, digest_len);
177 xfree(digest);
178}
179
180/* ASCII an integer [0, 64) for inclusion in a password/salt */
181static char
182pw_encode64(u_int i64)
183{
184 const u_char e64[] =
185 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
186 return e64[i64 % 64];
187}
188
189/* Generate ASCII salt bytes for user */
190static char *
191makesalt(u_int want, const char *user)
192{
193 u_char rawsalt[32];
194 static char ret[33];
195 u_int i;
196
197 if (want > sizeof(ret) - 1)
198 fatal("%s: want %u", __func__, want);
199
200 derive_rawsalt(user, rawsalt, sizeof(rawsalt));
201 bzero(ret, sizeof(ret));
202 for (i = 0; i < want; i++)
203 ret[i] = pw_encode64(rawsalt[i]);
204 bzero(rawsalt, sizeof(rawsalt));
205
206 return ret;
207}
208
209/*
210 * Select the system's default password hashing scheme and generate
211 * a stable fake salt under it for use by a non-existent account.
212 * Prevents jpake method being used to infer the validity of accounts.
213 */
214static void
215fake_salt_and_scheme(Authctxt *authctxt, char **salt, char **scheme)
216{
217 char *rounds_s, *style;
218 long long rounds;
219 login_cap_t *lc;
220
221
222 if ((lc = login_getclass(authctxt->pw->pw_class)) == NULL &&
223 (lc = login_getclass(NULL)) == NULL)
224 fatal("%s: login_getclass failed", __func__);
225 style = login_getcapstr(lc, "localcipher", NULL, NULL);
226 if (style == NULL)
227 style = xstrdup("blowfish,6");
228 login_close(lc);
229
230 if ((rounds_s = strchr(style, ',')) != NULL)
231 *rounds_s++ = '\0';
232 rounds = strtonum(rounds_s, 1, 1<<31, NULL);
233
234 if (strcmp(style, "md5") == 0) {
235 xasprintf(salt, "$1$%s$", makesalt(8, authctxt->user));
236 *scheme = xstrdup("md5");
237 } else if (strcmp(style, "old") == 0) {
238 *salt = xstrdup(makesalt(2, authctxt->user));
239 *scheme = xstrdup("crypt");
240 } else if (strcmp(style, "newsalt") == 0) {
241 rounds = MAX(rounds, 7250);
242 rounds = MIN(rounds, (1<<24) - 1);
243 xasprintf(salt, "_%c%c%c%c%s",
244 pw_encode64(rounds), pw_encode64(rounds >> 6),
245 pw_encode64(rounds >> 12), pw_encode64(rounds >> 18),
246 makesalt(4, authctxt->user));
247 *scheme = xstrdup("crypt-extended");
248 } else {
249 /* Default to blowfish */
250 rounds = MAX(rounds, 3);
251 rounds = MIN(rounds, 31);
252 xasprintf(salt, "$2a$%02lld$%s", rounds,
253 makesalt(22, authctxt->user));
254 *scheme = xstrdup("bcrypt");
255 }
256 xfree(style);
257 debug3("%s: fake %s salt for user %s: %s",
258 __func__, *scheme, authctxt->user, *salt);
259}
260
261/*
262 * Fetch password hashing scheme, password salt and derive shared secret
263 * for user. If user does not exist, a fake but stable and user-unique
264 * salt will be returned.
265 */
266void
267auth2_jpake_get_pwdata(Authctxt *authctxt, BIGNUM **s,
268 char **hash_scheme, char **salt)
269{
270 char *cp;
271 u_char *secret;
272 u_int secret_len, salt_len;
273
274#ifdef JPAKE_DEBUG
275 debug3("%s: valid %d pw %.5s...", __func__,
276 authctxt->valid, authctxt->pw->pw_passwd);
277#endif
278
279 *salt = NULL;
280 *hash_scheme = NULL;
281 if (authctxt->valid) {
282 if (strncmp(authctxt->pw->pw_passwd, "$2$", 3) == 0 &&
283 strlen(authctxt->pw->pw_passwd) > 28) {
284 /*
285 * old-variant bcrypt:
286 * "$2$", 2 digit rounds, "$", 22 bytes salt
287 */
288 salt_len = 3 + 2 + 1 + 22 + 1;
289 *salt = xmalloc(salt_len);
290 strlcpy(*salt, authctxt->pw->pw_passwd, salt_len);
291 *hash_scheme = xstrdup("bcrypt");
292 } else if (strncmp(authctxt->pw->pw_passwd, "$2a$", 4) == 0 &&
293 strlen(authctxt->pw->pw_passwd) > 29) {
294 /*
295 * current-variant bcrypt:
296 * "$2a$", 2 digit rounds, "$", 22 bytes salt
297 */
298 salt_len = 4 + 2 + 1 + 22 + 1;
299 *salt = xmalloc(salt_len);
300 strlcpy(*salt, authctxt->pw->pw_passwd, salt_len);
301 *hash_scheme = xstrdup("bcrypt");
302 } else if (strncmp(authctxt->pw->pw_passwd, "$1$", 3) == 0 &&
303 strlen(authctxt->pw->pw_passwd) > 5) {
304 /*
305 * md5crypt:
306 * "$1$", salt until "$"
307 */
308 cp = strchr(authctxt->pw->pw_passwd + 3, '$');
309 if (cp != NULL) {
310 salt_len = (cp - authctxt->pw->pw_passwd) + 1;
311 *salt = xmalloc(salt_len);
312 strlcpy(*salt, authctxt->pw->pw_passwd,
313 salt_len);
314 *hash_scheme = xstrdup("md5crypt");
315 }
316 } else if (strncmp(authctxt->pw->pw_passwd, "_", 1) == 0 &&
317 strlen(authctxt->pw->pw_passwd) > 9) {
318 /*
319 * BSDI extended crypt:
320 * "_", 4 digits count, 4 chars salt
321 */
322 salt_len = 1 + 4 + 4 + 1;
323 *salt = xmalloc(salt_len);
324 strlcpy(*salt, authctxt->pw->pw_passwd, salt_len);
325 *hash_scheme = xstrdup("crypt-extended");
326 } else if (strlen(authctxt->pw->pw_passwd) == 13 &&
327 valid_crypt_salt(authctxt->pw->pw_passwd[0]) &&
328 valid_crypt_salt(authctxt->pw->pw_passwd[1])) {
329 /*
330 * traditional crypt:
331 * 2 chars salt
332 */
333 salt_len = 2 + 1;
334 *salt = xmalloc(salt_len);
335 strlcpy(*salt, authctxt->pw->pw_passwd, salt_len);
336 *hash_scheme = xstrdup("crypt");
337 }
338 if (*salt == NULL) {
339 debug("%s: unrecognised crypt scheme for user %s",
340 __func__, authctxt->pw->pw_name);
341 }
342 }
343 if (*salt == NULL)
344 fake_salt_and_scheme(authctxt, salt, hash_scheme);
345
346 if (hash_buffer(authctxt->pw->pw_passwd,
347 strlen(authctxt->pw->pw_passwd), EVP_sha256(),
348 &secret, &secret_len) != 0)
349 fatal("%s: hash_buffer", __func__);
350 if ((*s = BN_bin2bn(secret, secret_len, NULL)) == NULL)
351 fatal("%s: BN_bin2bn (secret)", __func__);
352#ifdef JPAKE_DEBUG
353 debug3("%s: salt = %s (len %u)", __func__,
354 *salt, (u_int)strlen(*salt));
355 debug3("%s: scheme = %s", __func__, *hash_scheme);
356 JPAKE_DEBUG_BN((*s, "%s: s = ", __func__));
357#endif
358 bzero(secret, secret_len);
359 xfree(secret);
360}
361
362/*
5b01421b 363 * Begin authentication attempt.
5adf6b9a 364 * Note, sets authctxt->postponed while in subprotocol
365 */
366static int
367auth2_jpake_start(Authctxt *authctxt)
368{
369 struct jpake_ctx *pctx = authctxt->jpake_ctx;
370 u_char *x3_proof, *x4_proof;
371 u_int x3_proof_len, x4_proof_len;
372 char *salt, *hash_scheme;
373
374 debug("%s: start", __func__);
375
376 PRIVSEP(jpake_step1(pctx->grp,
377 &pctx->server_id, &pctx->server_id_len,
378 &pctx->x3, &pctx->x4, &pctx->g_x3, &pctx->g_x4,
379 &x3_proof, &x3_proof_len,
380 &x4_proof, &x4_proof_len));
381
382 PRIVSEP(auth2_jpake_get_pwdata(authctxt, &pctx->s,
383 &hash_scheme, &salt));
384
385 if (!use_privsep)
386 JPAKE_DEBUG_CTX((pctx, "step 1 sending in %s", __func__));
387
388 packet_start(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP1);
389 packet_put_cstring(hash_scheme);
390 packet_put_cstring(salt);
391 packet_put_string(pctx->server_id, pctx->server_id_len);
392 packet_put_bignum2(pctx->g_x3);
393 packet_put_bignum2(pctx->g_x4);
394 packet_put_string(x3_proof, x3_proof_len);
395 packet_put_string(x4_proof, x4_proof_len);
396 packet_send();
397 packet_write_wait();
398
399 bzero(hash_scheme, strlen(hash_scheme));
400 bzero(salt, strlen(salt));
401 xfree(hash_scheme);
402 xfree(salt);
403 bzero(x3_proof, x3_proof_len);
404 bzero(x4_proof, x4_proof_len);
405 xfree(x3_proof);
406 xfree(x4_proof);
407
408 /* Expect step 1 packet from peer */
409 dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP1,
410 input_userauth_jpake_client_step1);
411
412 authctxt->postponed = 1;
413 return 0;
414}
415
416/* ARGSUSED */
417static void
418input_userauth_jpake_client_step1(int type, u_int32_t seq, void *ctxt)
419{
420 Authctxt *authctxt = ctxt;
421 struct jpake_ctx *pctx = authctxt->jpake_ctx;
422 u_char *x1_proof, *x2_proof, *x4_s_proof;
423 u_int x1_proof_len, x2_proof_len, x4_s_proof_len;
424
425 /* Disable this message */
426 dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP1, NULL);
427
428 /* Fetch step 1 values */
429 if ((pctx->g_x1 = BN_new()) == NULL ||
430 (pctx->g_x2 = BN_new()) == NULL)
431 fatal("%s: BN_new", __func__);
432 pctx->client_id = packet_get_string(&pctx->client_id_len);
433 packet_get_bignum2(pctx->g_x1);
434 packet_get_bignum2(pctx->g_x2);
435 x1_proof = packet_get_string(&x1_proof_len);
436 x2_proof = packet_get_string(&x2_proof_len);
437 packet_check_eom();
438
439 if (!use_privsep)
440 JPAKE_DEBUG_CTX((pctx, "step 1 received in %s", __func__));
441
442 PRIVSEP(jpake_step2(pctx->grp, pctx->s, pctx->g_x3,
443 pctx->g_x1, pctx->g_x2, pctx->x4,
444 pctx->client_id, pctx->client_id_len,
445 pctx->server_id, pctx->server_id_len,
446 x1_proof, x1_proof_len,
447 x2_proof, x2_proof_len,
448 &pctx->b,
449 &x4_s_proof, &x4_s_proof_len));
450
451 bzero(x1_proof, x1_proof_len);
452 bzero(x2_proof, x2_proof_len);
453 xfree(x1_proof);
454 xfree(x2_proof);
455
456 if (!use_privsep)
457 JPAKE_DEBUG_CTX((pctx, "step 2 sending in %s", __func__));
458
459 /* Send values for step 2 */
460 packet_start(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP2);
461 packet_put_bignum2(pctx->b);
462 packet_put_string(x4_s_proof, x4_s_proof_len);
463 packet_send();
464 packet_write_wait();
465
466 bzero(x4_s_proof, x4_s_proof_len);
467 xfree(x4_s_proof);
468
469 /* Expect step 2 packet from peer */
470 dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP2,
471 input_userauth_jpake_client_step2);
472}
473
474/* ARGSUSED */
475static void
476input_userauth_jpake_client_step2(int type, u_int32_t seq, void *ctxt)
477{
478 Authctxt *authctxt = ctxt;
479 struct jpake_ctx *pctx = authctxt->jpake_ctx;
480 u_char *x2_s_proof;
481 u_int x2_s_proof_len;
482
483 /* Disable this message */
484 dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP2, NULL);
485
486 if ((pctx->a = BN_new()) == NULL)
487 fatal("%s: BN_new", __func__);
488
489 /* Fetch step 2 values */
490 packet_get_bignum2(pctx->a);
491 x2_s_proof = packet_get_string(&x2_s_proof_len);
492 packet_check_eom();
493
494 if (!use_privsep)
495 JPAKE_DEBUG_CTX((pctx, "step 2 received in %s", __func__));
496
497 /* Derive shared key and calculate confirmation hash */
498 PRIVSEP(jpake_key_confirm(pctx->grp, pctx->s, pctx->a,
499 pctx->x4, pctx->g_x3, pctx->g_x4, pctx->g_x1, pctx->g_x2,
500 pctx->server_id, pctx->server_id_len,
501 pctx->client_id, pctx->client_id_len,
502 session_id2, session_id2_len,
503 x2_s_proof, x2_s_proof_len,
504 &pctx->k,
505 &pctx->h_k_sid_sessid, &pctx->h_k_sid_sessid_len));
506
507 bzero(x2_s_proof, x2_s_proof_len);
508 xfree(x2_s_proof);
509
510 if (!use_privsep)
511 JPAKE_DEBUG_CTX((pctx, "confirm sending in %s", __func__));
512
513 /* Send key confirmation proof */
514 packet_start(SSH2_MSG_USERAUTH_JPAKE_SERVER_CONFIRM);
515 packet_put_string(pctx->h_k_sid_sessid, pctx->h_k_sid_sessid_len);
516 packet_send();
517 packet_write_wait();
518
519 /* Expect confirmation from peer */
520 dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_CONFIRM,
521 input_userauth_jpake_client_confirm);
522}
523
524/* ARGSUSED */
525static void
526input_userauth_jpake_client_confirm(int type, u_int32_t seq, void *ctxt)
527{
528 Authctxt *authctxt = ctxt;
529 struct jpake_ctx *pctx = authctxt->jpake_ctx;
530 int authenticated = 0;
531
532 /* Disable this message */
533 dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_CONFIRM, NULL);
534
535 pctx->h_k_cid_sessid = packet_get_string(&pctx->h_k_cid_sessid_len);
536 packet_check_eom();
537
538 if (!use_privsep)
539 JPAKE_DEBUG_CTX((pctx, "confirm received in %s", __func__));
540
541 /* Verify expected confirmation hash */
542 if (PRIVSEP(jpake_check_confirm(pctx->k,
543 pctx->client_id, pctx->client_id_len,
544 session_id2, session_id2_len,
545 pctx->h_k_cid_sessid, pctx->h_k_cid_sessid_len)) == 1)
546 authenticated = authctxt->valid ? 1 : 0;
547 else
548 debug("%s: confirmation mismatch", __func__);
549
550 /* done */
551 authctxt->postponed = 0;
552 jpake_free(authctxt->jpake_ctx);
553 authctxt->jpake_ctx = NULL;
554 userauth_finish(authctxt, authenticated, method_jpake.name);
555}
556
557#endif /* JPAKE */
558
This page took 0.352732 seconds and 5 git commands to generate.