summaryrefslogtreecommitdiff
path: root/auth2-jpake.c
diff options
context:
space:
mode:
authorColin Watson <cjwatson@debian.org>2010-01-01 17:15:23 +0000
committerColin Watson <cjwatson@debian.org>2010-01-01 17:15:23 +0000
commit99b402ea4c8457b0a3cafff37f5b3410a8dc6476 (patch)
tree1d24ce54c9981ea8cbb4c5a9309964a0e4c4b320 /auth2-jpake.c
parent87552344215a38d3a2b0d4d63dc151e05978bbe1 (diff)
parent54af7a4ae8d455791a631bdfaade4b64436ae16a (diff)
import openssh-5.2p1-gsskex-all-20090726.patch
Diffstat (limited to 'auth2-jpake.c')
-rw-r--r--auth2-jpake.c557
1 files changed, 557 insertions, 0 deletions
diff --git a/auth2-jpake.c b/auth2-jpake.c
new file mode 100644
index 000000000..efe7ff2a3
--- /dev/null
+++ b/auth2-jpake.c
@@ -0,0 +1,557 @@
1/* $OpenBSD: auth2-jpake.c,v 1.2 2008/11/07 23:34:48 dtucker Exp $ */
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
28#ifdef JPAKE
29
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"
45#include "buffer.h"
46#include "auth.h"
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
58#include "jpake.h"
59
60/*
61 * XXX options->permit_empty_passwd (at the moment, they will be refused
62 * anyway because they will mismatch on fake salt.
63 */
64
65/* Dispatch handlers */
66static void input_userauth_jpake_client_step1(int, u_int32_t, void *);
67static void input_userauth_jpake_client_step2(int, u_int32_t, void *);
68static void input_userauth_jpake_client_confirm(int, u_int32_t, void *);
69
70static int auth2_jpake_start(Authctxt *);
71
72/* import */
73extern ServerOptions options;
74extern u_char *session_id2;
75extern u_int session_id2_len;
76
77/*
78 * Attempt J-PAKE authentication.
79 */
80static int
81userauth_jpake(Authctxt *authctxt)
82{
83 int authenticated = 0;
84
85 packet_check_eom();
86
87 debug("jpake-01@openssh.com requested");
88
89 if (authctxt->user != NULL) {
90 if (authctxt->jpake_ctx == NULL)
91 authctxt->jpake_ctx = jpake_new();
92 if (options.zero_knowledge_password_authentication)
93 authenticated = auth2_jpake_start(authctxt);
94 }
95
96 return authenticated;
97}
98
99Authmethod method_jpake = {
100 "jpake-01@openssh.com",
101 userauth_jpake,
102 &options.zero_knowledge_password_authentication
103};
104
105/* Clear context and callbacks */
106void
107auth2_jpake_stop(Authctxt *authctxt)
108{
109 /* unregister callbacks */
110 dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP1, NULL);
111 dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP2, NULL);
112 dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_CONFIRM, NULL);
113 if (authctxt->jpake_ctx != NULL) {
114 jpake_free(authctxt->jpake_ctx);
115 authctxt->jpake_ctx = NULL;
116 }
117}
118
119/* Returns 1 if 'c' is a valid crypt(3) salt character, 0 otherwise */
120static int
121valid_crypt_salt(int c)
122{
123 if (c >= 'A' && c <= 'Z')
124 return 1;
125 if (c >= 'a' && c <= 'z')
126 return 1;
127 if (c >= '.' && c <= '9')
128 return 1;
129 return 0;
130}
131
132/*
133 * Derive fake salt as H(username || first_private_host_key)
134 * This provides relatively stable fake salts for non-existent
135 * users and avoids the jpake method becoming an account validity
136 * oracle.
137 */
138static void
139derive_rawsalt(const char *username, u_char *rawsalt, u_int len)
140{
141 u_char *digest;
142 u_int digest_len;
143 Buffer b;
144 Key *k;
145
146 buffer_init(&b);
147 buffer_put_cstring(&b, username);
148 if ((k = get_hostkey_by_index(0)) == NULL ||
149 (k->flags & KEY_FLAG_EXT))
150 fatal("%s: no hostkeys", __func__);
151 switch (k->type) {
152 case KEY_RSA1:
153 case KEY_RSA:
154 if (k->rsa->p == NULL || k->rsa->q == NULL)
155 fatal("%s: RSA key missing p and/or q", __func__);
156 buffer_put_bignum2(&b, k->rsa->p);
157 buffer_put_bignum2(&b, k->rsa->q);
158 break;
159 case KEY_DSA:
160 if (k->dsa->priv_key == NULL)
161 fatal("%s: DSA key missing priv_key", __func__);
162 buffer_put_bignum2(&b, k->dsa->priv_key);
163 break;
164 default:
165 fatal("%s: unknown key type %d", __func__, k->type);
166 }
167 if (hash_buffer(buffer_ptr(&b), buffer_len(&b), EVP_sha256(),
168 &digest, &digest_len) != 0)
169 fatal("%s: hash_buffer", __func__);
170 buffer_free(&b);
171 if (len > digest_len)
172 fatal("%s: not enough bytes for rawsalt (want %u have %u)",
173 __func__, len, digest_len);
174 memcpy(rawsalt, digest, len);
175 bzero(digest, digest_len);
176 xfree(digest);
177}
178
179/* ASCII an integer [0, 64) for inclusion in a password/salt */
180static char
181pw_encode64(u_int i64)
182{
183 const u_char e64[] =
184 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
185 return e64[i64 % 64];
186}
187
188/* Generate ASCII salt bytes for user */
189static char *
190makesalt(u_int want, const char *user)
191{
192 u_char rawsalt[32];
193 static char ret[33];
194 u_int i;
195
196 if (want > sizeof(ret) - 1)
197 fatal("%s: want %u", __func__, want);
198
199 derive_rawsalt(user, rawsalt, sizeof(rawsalt));
200 bzero(ret, sizeof(ret));
201 for (i = 0; i < want; i++)
202 ret[i] = pw_encode64(rawsalt[i]);
203 bzero(rawsalt, sizeof(rawsalt));
204
205 return ret;
206}
207
208/*
209 * Select the system's default password hashing scheme and generate
210 * a stable fake salt under it for use by a non-existent account.
211 * Prevents jpake method being used to infer the validity of accounts.
212 */
213static void
214fake_salt_and_scheme(Authctxt *authctxt, char **salt, char **scheme)
215{
216 char *rounds_s, *style;
217 long long rounds;
218 login_cap_t *lc;
219
220
221 if ((lc = login_getclass(authctxt->pw->pw_class)) == NULL &&
222 (lc = login_getclass(NULL)) == NULL)
223 fatal("%s: login_getclass failed", __func__);
224 style = login_getcapstr(lc, "localcipher", NULL, NULL);
225 if (style == NULL)
226 style = xstrdup("blowfish,6");
227 login_close(lc);
228
229 if ((rounds_s = strchr(style, ',')) != NULL)
230 *rounds_s++ = '\0';
231 rounds = strtonum(rounds_s, 1, 1<<31, NULL);
232
233 if (strcmp(style, "md5") == 0) {
234 xasprintf(salt, "$1$%s$", makesalt(8, authctxt->user));
235 *scheme = xstrdup("md5");
236 } else if (strcmp(style, "old") == 0) {
237 *salt = xstrdup(makesalt(2, authctxt->user));
238 *scheme = xstrdup("crypt");
239 } else if (strcmp(style, "newsalt") == 0) {
240 rounds = MAX(rounds, 7250);
241 rounds = MIN(rounds, (1<<24) - 1);
242 xasprintf(salt, "_%c%c%c%c%s",
243 pw_encode64(rounds), pw_encode64(rounds >> 6),
244 pw_encode64(rounds >> 12), pw_encode64(rounds >> 18),
245 makesalt(4, authctxt->user));
246 *scheme = xstrdup("crypt-extended");
247 } else {
248 /* Default to blowfish */
249 rounds = MAX(rounds, 3);
250 rounds = MIN(rounds, 31);
251 xasprintf(salt, "$2a$%02lld$%s", rounds,
252 makesalt(22, authctxt->user));
253 *scheme = xstrdup("bcrypt");
254 }
255 xfree(style);
256 debug3("%s: fake %s salt for user %s: %s",
257 __func__, *scheme, authctxt->user, *salt);
258}
259
260/*
261 * Fetch password hashing scheme, password salt and derive shared secret
262 * for user. If user does not exist, a fake but stable and user-unique
263 * salt will be returned.
264 */
265void
266auth2_jpake_get_pwdata(Authctxt *authctxt, BIGNUM **s,
267 char **hash_scheme, char **salt)
268{
269 char *cp;
270 u_char *secret;
271 u_int secret_len, salt_len;
272
273#ifdef JPAKE_DEBUG
274 debug3("%s: valid %d pw %.5s...", __func__,
275 authctxt->valid, authctxt->pw->pw_passwd);
276#endif
277
278 *salt = NULL;
279 *hash_scheme = NULL;
280 if (authctxt->valid) {
281 if (strncmp(authctxt->pw->pw_passwd, "$2$", 3) == 0 &&
282 strlen(authctxt->pw->pw_passwd) > 28) {
283 /*
284 * old-variant bcrypt:
285 * "$2$", 2 digit rounds, "$", 22 bytes salt
286 */
287 salt_len = 3 + 2 + 1 + 22 + 1;
288 *salt = xmalloc(salt_len);
289 strlcpy(*salt, authctxt->pw->pw_passwd, salt_len);
290 *hash_scheme = xstrdup("bcrypt");
291 } else if (strncmp(authctxt->pw->pw_passwd, "$2a$", 4) == 0 &&
292 strlen(authctxt->pw->pw_passwd) > 29) {
293 /*
294 * current-variant bcrypt:
295 * "$2a$", 2 digit rounds, "$", 22 bytes salt
296 */
297 salt_len = 4 + 2 + 1 + 22 + 1;
298 *salt = xmalloc(salt_len);
299 strlcpy(*salt, authctxt->pw->pw_passwd, salt_len);
300 *hash_scheme = xstrdup("bcrypt");
301 } else if (strncmp(authctxt->pw->pw_passwd, "$1$", 3) == 0 &&
302 strlen(authctxt->pw->pw_passwd) > 5) {
303 /*
304 * md5crypt:
305 * "$1$", salt until "$"
306 */
307 cp = strchr(authctxt->pw->pw_passwd + 3, '$');
308 if (cp != NULL) {
309 salt_len = (cp - authctxt->pw->pw_passwd) + 1;
310 *salt = xmalloc(salt_len);
311 strlcpy(*salt, authctxt->pw->pw_passwd,
312 salt_len);
313 *hash_scheme = xstrdup("md5crypt");
314 }
315 } else if (strncmp(authctxt->pw->pw_passwd, "_", 1) == 0 &&
316 strlen(authctxt->pw->pw_passwd) > 9) {
317 /*
318 * BSDI extended crypt:
319 * "_", 4 digits count, 4 chars salt
320 */
321 salt_len = 1 + 4 + 4 + 1;
322 *salt = xmalloc(salt_len);
323 strlcpy(*salt, authctxt->pw->pw_passwd, salt_len);
324 *hash_scheme = xstrdup("crypt-extended");
325 } else if (strlen(authctxt->pw->pw_passwd) == 13 &&
326 valid_crypt_salt(authctxt->pw->pw_passwd[0]) &&
327 valid_crypt_salt(authctxt->pw->pw_passwd[1])) {
328 /*
329 * traditional crypt:
330 * 2 chars salt
331 */
332 salt_len = 2 + 1;
333 *salt = xmalloc(salt_len);
334 strlcpy(*salt, authctxt->pw->pw_passwd, salt_len);
335 *hash_scheme = xstrdup("crypt");
336 }
337 if (*salt == NULL) {
338 debug("%s: unrecognised crypt scheme for user %s",
339 __func__, authctxt->pw->pw_name);
340 }
341 }
342 if (*salt == NULL)
343 fake_salt_and_scheme(authctxt, salt, hash_scheme);
344
345 if (hash_buffer(authctxt->pw->pw_passwd,
346 strlen(authctxt->pw->pw_passwd), EVP_sha256(),
347 &secret, &secret_len) != 0)
348 fatal("%s: hash_buffer", __func__);
349 if ((*s = BN_bin2bn(secret, secret_len, NULL)) == NULL)
350 fatal("%s: BN_bin2bn (secret)", __func__);
351#ifdef JPAKE_DEBUG
352 debug3("%s: salt = %s (len %u)", __func__,
353 *salt, (u_int)strlen(*salt));
354 debug3("%s: scheme = %s", __func__, *hash_scheme);
355 JPAKE_DEBUG_BN((*s, "%s: s = ", __func__));
356#endif
357 bzero(secret, secret_len);
358 xfree(secret);
359}
360
361/*
362 * Being authentication attempt.
363 * Note, sets authctxt->postponed while in subprotocol
364 */
365static int
366auth2_jpake_start(Authctxt *authctxt)
367{
368 struct jpake_ctx *pctx = authctxt->jpake_ctx;
369 u_char *x3_proof, *x4_proof;
370 u_int x3_proof_len, x4_proof_len;
371 char *salt, *hash_scheme;
372
373 debug("%s: start", __func__);
374
375 PRIVSEP(jpake_step1(pctx->grp,
376 &pctx->server_id, &pctx->server_id_len,
377 &pctx->x3, &pctx->x4, &pctx->g_x3, &pctx->g_x4,
378 &x3_proof, &x3_proof_len,
379 &x4_proof, &x4_proof_len));
380
381 PRIVSEP(auth2_jpake_get_pwdata(authctxt, &pctx->s,
382 &hash_scheme, &salt));
383
384 if (!use_privsep)
385 JPAKE_DEBUG_CTX((pctx, "step 1 sending in %s", __func__));
386
387 packet_start(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP1);
388 packet_put_cstring(hash_scheme);
389 packet_put_cstring(salt);
390 packet_put_string(pctx->server_id, pctx->server_id_len);
391 packet_put_bignum2(pctx->g_x3);
392 packet_put_bignum2(pctx->g_x4);
393 packet_put_string(x3_proof, x3_proof_len);
394 packet_put_string(x4_proof, x4_proof_len);
395 packet_send();
396 packet_write_wait();
397
398 bzero(hash_scheme, strlen(hash_scheme));
399 bzero(salt, strlen(salt));
400 xfree(hash_scheme);
401 xfree(salt);
402 bzero(x3_proof, x3_proof_len);
403 bzero(x4_proof, x4_proof_len);
404 xfree(x3_proof);
405 xfree(x4_proof);
406
407 /* Expect step 1 packet from peer */
408 dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP1,
409 input_userauth_jpake_client_step1);
410
411 authctxt->postponed = 1;
412 return 0;
413}
414
415/* ARGSUSED */
416static void
417input_userauth_jpake_client_step1(int type, u_int32_t seq, void *ctxt)
418{
419 Authctxt *authctxt = ctxt;
420 struct jpake_ctx *pctx = authctxt->jpake_ctx;
421 u_char *x1_proof, *x2_proof, *x4_s_proof;
422 u_int x1_proof_len, x2_proof_len, x4_s_proof_len;
423
424 /* Disable this message */
425 dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP1, NULL);
426
427 /* Fetch step 1 values */
428 if ((pctx->g_x1 = BN_new()) == NULL ||
429 (pctx->g_x2 = BN_new()) == NULL)
430 fatal("%s: BN_new", __func__);
431 pctx->client_id = packet_get_string(&pctx->client_id_len);
432 packet_get_bignum2(pctx->g_x1);
433 packet_get_bignum2(pctx->g_x2);
434 x1_proof = packet_get_string(&x1_proof_len);
435 x2_proof = packet_get_string(&x2_proof_len);
436 packet_check_eom();
437
438 if (!use_privsep)
439 JPAKE_DEBUG_CTX((pctx, "step 1 received in %s", __func__));
440
441 PRIVSEP(jpake_step2(pctx->grp, pctx->s, pctx->g_x3,
442 pctx->g_x1, pctx->g_x2, pctx->x4,
443 pctx->client_id, pctx->client_id_len,
444 pctx->server_id, pctx->server_id_len,
445 x1_proof, x1_proof_len,
446 x2_proof, x2_proof_len,
447 &pctx->b,
448 &x4_s_proof, &x4_s_proof_len));
449
450 bzero(x1_proof, x1_proof_len);
451 bzero(x2_proof, x2_proof_len);
452 xfree(x1_proof);
453 xfree(x2_proof);
454
455 if (!use_privsep)
456 JPAKE_DEBUG_CTX((pctx, "step 2 sending in %s", __func__));
457
458 /* Send values for step 2 */
459 packet_start(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP2);
460 packet_put_bignum2(pctx->b);
461 packet_put_string(x4_s_proof, x4_s_proof_len);
462 packet_send();
463 packet_write_wait();
464
465 bzero(x4_s_proof, x4_s_proof_len);
466 xfree(x4_s_proof);
467
468 /* Expect step 2 packet from peer */
469 dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP2,
470 input_userauth_jpake_client_step2);
471}
472
473/* ARGSUSED */
474static void
475input_userauth_jpake_client_step2(int type, u_int32_t seq, void *ctxt)
476{
477 Authctxt *authctxt = ctxt;
478 struct jpake_ctx *pctx = authctxt->jpake_ctx;
479 u_char *x2_s_proof;
480 u_int x2_s_proof_len;
481
482 /* Disable this message */
483 dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP2, NULL);
484
485 if ((pctx->a = BN_new()) == NULL)
486 fatal("%s: BN_new", __func__);
487
488 /* Fetch step 2 values */
489 packet_get_bignum2(pctx->a);
490 x2_s_proof = packet_get_string(&x2_s_proof_len);
491 packet_check_eom();
492
493 if (!use_privsep)
494 JPAKE_DEBUG_CTX((pctx, "step 2 received in %s", __func__));
495
496 /* Derive shared key and calculate confirmation hash */
497 PRIVSEP(jpake_key_confirm(pctx->grp, pctx->s, pctx->a,
498 pctx->x4, pctx->g_x3, pctx->g_x4, pctx->g_x1, pctx->g_x2,
499 pctx->server_id, pctx->server_id_len,
500 pctx->client_id, pctx->client_id_len,
501 session_id2, session_id2_len,
502 x2_s_proof, x2_s_proof_len,
503 &pctx->k,
504 &pctx->h_k_sid_sessid, &pctx->h_k_sid_sessid_len));
505
506 bzero(x2_s_proof, x2_s_proof_len);
507 xfree(x2_s_proof);
508
509 if (!use_privsep)
510 JPAKE_DEBUG_CTX((pctx, "confirm sending in %s", __func__));
511
512 /* Send key confirmation proof */
513 packet_start(SSH2_MSG_USERAUTH_JPAKE_SERVER_CONFIRM);
514 packet_put_string(pctx->h_k_sid_sessid, pctx->h_k_sid_sessid_len);
515 packet_send();
516 packet_write_wait();
517
518 /* Expect confirmation from peer */
519 dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_CONFIRM,
520 input_userauth_jpake_client_confirm);
521}
522
523/* ARGSUSED */
524static void
525input_userauth_jpake_client_confirm(int type, u_int32_t seq, void *ctxt)
526{
527 Authctxt *authctxt = ctxt;
528 struct jpake_ctx *pctx = authctxt->jpake_ctx;
529 int authenticated = 0;
530
531 /* Disable this message */
532 dispatch_set(SSH2_MSG_USERAUTH_JPAKE_CLIENT_CONFIRM, NULL);
533
534 pctx->h_k_cid_sessid = packet_get_string(&pctx->h_k_cid_sessid_len);
535 packet_check_eom();
536
537 if (!use_privsep)
538 JPAKE_DEBUG_CTX((pctx, "confirm received in %s", __func__));
539
540 /* Verify expected confirmation hash */
541 if (PRIVSEP(jpake_check_confirm(pctx->k,
542 pctx->client_id, pctx->client_id_len,
543 session_id2, session_id2_len,
544 pctx->h_k_cid_sessid, pctx->h_k_cid_sessid_len)) == 1)
545 authenticated = authctxt->valid ? 1 : 0;
546 else
547 debug("%s: confirmation mismatch", __func__);
548
549 /* done */
550 authctxt->postponed = 0;
551 jpake_free(authctxt->jpake_ctx);
552 authctxt->jpake_ctx = NULL;
553 userauth_finish(authctxt, authenticated, method_jpake.name);
554}
555
556#endif /* JPAKE */
557