From a699d952e5e20be83fada0ff10ba8e02ad4dcd81 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Mon, 3 Nov 2008 19:27:34 +1100 Subject: - stevesk@cvs.openbsd.org 2008/11/01 17:40:33 [clientloop.c readconf.c readconf.h ssh.c] merge dynamic forward parsing into parse_forward(); 'i think this is OK' djm@ --- readconf.c | 82 ++++++++++++++++++++++++++++++++++---------------------------- 1 file changed, 45 insertions(+), 37 deletions(-) (limited to 'readconf.c') diff --git a/readconf.c b/readconf.c index 73f6eb361..27be8df68 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.167 2008/06/26 11:46:31 grunk Exp $ */ +/* $OpenBSD: readconf.c,v 1.168 2008/11/01 17:40:33 stevesk Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -706,56 +706,39 @@ parse_int: case oLocalForward: case oRemoteForward: + case oDynamicForward: arg = strdelim(&s); if (arg == NULL || *arg == '\0') fatal("%.200s line %d: Missing port argument.", filename, linenum); - arg2 = strdelim(&s); - if (arg2 == NULL || *arg2 == '\0') - fatal("%.200s line %d: Missing target argument.", - filename, linenum); - /* construct a string for parse_forward */ - snprintf(fwdarg, sizeof(fwdarg), "%s:%s", arg, arg2); + if (opcode == oLocalForward || + opcode == oRemoteForward) { + arg2 = strdelim(&s); + if (arg2 == NULL || *arg2 == '\0') + fatal("%.200s line %d: Missing target argument.", + filename, linenum); - if (parse_forward(&fwd, fwdarg) == 0) + /* construct a string for parse_forward */ + snprintf(fwdarg, sizeof(fwdarg), "%s:%s", arg, arg2); + } else if (opcode == oDynamicForward) { + strlcpy(fwdarg, arg, sizeof(fwdarg)); + } + + if (parse_forward(&fwd, fwdarg, + opcode == oDynamicForward ? 1 : 0) == 0) fatal("%.200s line %d: Bad forwarding specification.", filename, linenum); if (*activep) { - if (opcode == oLocalForward) + if (opcode == oLocalForward || + opcode == oDynamicForward) add_local_forward(options, &fwd); else if (opcode == oRemoteForward) add_remote_forward(options, &fwd); } break; - case oDynamicForward: - arg = strdelim(&s); - if (!arg || *arg == '\0') - fatal("%.200s line %d: Missing port argument.", - filename, linenum); - memset(&fwd, '\0', sizeof(fwd)); - fwd.connect_host = "socks"; - fwd.listen_host = hpdelim(&arg); - if (fwd.listen_host == NULL || - strlen(fwd.listen_host) >= NI_MAXHOST) - fatal("%.200s line %d: Bad forwarding specification.", - filename, linenum); - if (arg) { - fwd.listen_port = a2port(arg); - fwd.listen_host = cleanhostname(fwd.listen_host); - } else { - fwd.listen_port = a2port(fwd.listen_host); - fwd.listen_host = NULL; - } - if (fwd.listen_port == 0) - fatal("%.200s line %d: Badly formatted port number.", - filename, linenum); - if (*activep) - add_local_forward(options, &fwd); - break; - case oClearAllForwardings: intptr = &options->clear_forwardings; goto parse_flag; @@ -1219,11 +1202,14 @@ fill_default_options(Options * options) /* * parse_forward * parses a string containing a port forwarding specification of the form: + * dynamicfwd == 0 * [listenhost:]listenport:connecthost:connectport + * dynamicfwd == 1 + * [listenhost:]listenport * returns number of arguments parsed or zero on error */ int -parse_forward(Forward *fwd, const char *fwdspec) +parse_forward(Forward *fwd, const char *fwdspec, int dynamicfwd) { int i; char *p, *cp, *fwdarg[4]; @@ -1245,6 +1231,18 @@ parse_forward(Forward *fwd, const char *fwdspec) i = 0; /* failure */ switch (i) { + case 1: + fwd->listen_host = NULL; + fwd->listen_port = a2port(fwdarg[0]); + fwd->connect_host = xstrdup("socks"); + break; + + case 2: + fwd->listen_host = xstrdup(cleanhostname(fwdarg[0])); + fwd->listen_port = a2port(fwdarg[1]); + fwd->connect_host = xstrdup("socks"); + break; + case 3: fwd->listen_host = NULL; fwd->listen_port = a2port(fwdarg[0]); @@ -1264,7 +1262,17 @@ parse_forward(Forward *fwd, const char *fwdspec) xfree(p); - if (fwd->listen_port == 0 || fwd->connect_port == 0) + if (dynamicfwd) { + if (!(i == 1 || i == 2)) + goto fail_free; + } else { + if (!(i == 3 || i == 4)) + goto fail_free; + if (fwd->connect_port == 0) + goto fail_free; + } + + if (fwd->listen_port == 0) goto fail_free; if (fwd->connect_host != NULL && -- cgit v1.2.3 From a279d252ab3be71bdcc0ccc3c691c95ca9a04c24 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Mon, 3 Nov 2008 19:28:07 +1100 Subject: - stevesk@cvs.openbsd.org 2008/11/03 01:07:02 [readconf.c] remove valueless comment --- ChangeLog | 5 ++++- readconf.c | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'readconf.c') diff --git a/ChangeLog b/ChangeLog index 3054499ad..a64eff3fc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -100,6 +100,9 @@ [ttymodes.c] protocol 2 tty modes support is now 7.5 years old so remove these debug3()s; ok deraadt@ + - stevesk@cvs.openbsd.org 2008/11/03 01:07:02 + [readconf.c] + remove valueless comment 20080906 - (dtucker) [config.guess config.sub] Update to latest versions from @@ -4834,4 +4837,4 @@ OpenServer 6 and add osr5bigcrypt support so when someone migrates passwords between UnixWare and OpenServer they will still work. OK dtucker@ -$Id: ChangeLog,v 1.5123 2008/11/03 08:27:52 djm Exp $ +$Id: ChangeLog,v 1.5124 2008/11/03 08:28:07 djm Exp $ diff --git a/readconf.c b/readconf.c index 27be8df68..e61486b09 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.168 2008/11/01 17:40:33 stevesk Exp $ */ +/* $OpenBSD: readconf.c,v 1.169 2008/11/03 01:07:02 stevesk Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -942,7 +942,6 @@ read_config_file(const char *filename, const char *host, Options *options, int active, linenum; int bad_options = 0; - /* Open the file. */ if ((f = fopen(filename, "r")) == NULL) return 0; -- cgit v1.2.3 From f4b39538f45a6d8487fff3cf6b9e80e1293da06d Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Mon, 3 Nov 2008 19:28:21 +1100 Subject: - stevesk@cvs.openbsd.org 2008/11/03 02:44:41 [readconf.c] fix comment --- ChangeLog | 5 ++++- readconf.c | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'readconf.c') diff --git a/ChangeLog b/ChangeLog index a64eff3fc..f581613d3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -103,6 +103,9 @@ - stevesk@cvs.openbsd.org 2008/11/03 01:07:02 [readconf.c] remove valueless comment + - stevesk@cvs.openbsd.org 2008/11/03 02:44:41 + [readconf.c] + fix comment 20080906 - (dtucker) [config.guess config.sub] Update to latest versions from @@ -4837,4 +4840,4 @@ OpenServer 6 and add osr5bigcrypt support so when someone migrates passwords between UnixWare and OpenServer they will still work. OK dtucker@ -$Id: ChangeLog,v 1.5124 2008/11/03 08:28:07 djm Exp $ +$Id: ChangeLog,v 1.5125 2008/11/03 08:28:21 djm Exp $ diff --git a/readconf.c b/readconf.c index e61486b09..7f7bbfee2 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.169 2008/11/03 01:07:02 stevesk Exp $ */ +/* $OpenBSD: readconf.c,v 1.170 2008/11/03 02:44:41 stevesk Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1225,7 +1225,7 @@ parse_forward(Forward *fwd, const char *fwdspec, int dynamicfwd) if ((fwdarg[i] = hpdelim(&cp)) == NULL) break; - /* Check for trailing garbage in 4-arg case*/ + /* Check for trailing garbage */ if (cp != NULL) i = 0; /* failure */ -- cgit v1.2.3 From 01ed2272a1545336173bf3aef66fbccc3494c8d8 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Wed, 5 Nov 2008 16:20:46 +1100 Subject: - djm@cvs.openbsd.org 2008/11/04 08:22:13 [auth.h auth2.c monitor.c monitor.h monitor_wrap.c monitor_wrap.h] [readconf.c readconf.h servconf.c servconf.h ssh2.h ssh_config.5] [sshconnect2.c sshd_config.5 jpake.c jpake.h schnorr.c auth2-jpake.c] [Makefile.in] Add support for an experimental zero-knowledge password authentication method using the J-PAKE protocol described in F. Hao, P. Ryan, "Password Authenticated Key Exchange by Juggling", 16th Workshop on Security Protocols, Cambridge, April 2008. This method allows password-based authentication without exposing the password to the server. Instead, the client and server exchange cryptographic proofs to demonstrate of knowledge of the password while revealing nothing useful to an attacker or compromised endpoint. This is experimental, work-in-progress code and is presently compiled-time disabled (turn on -DJPAKE in Makefile.inc). "just commit it. It isn't too intrusive." deraadt@ --- ChangeLog | 21 +- Makefile.in | 6 +- auth.h | 6 +- auth2-jpake.c | 557 ++++++++++++++++++++++++++++++++++++++++++++++++++++ auth2.c | 12 +- jpake.c | 604 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ jpake.h | 134 +++++++++++++ monitor.c | 227 +++++++++++++++++++++- monitor.h | 9 +- monitor_wrap.c | 167 +++++++++++++++- monitor_wrap.h | 22 ++- readconf.c | 18 +- readconf.h | 3 +- schnorr.c | 407 ++++++++++++++++++++++++++++++++++++++ servconf.c | 20 +- servconf.h | 4 +- ssh2.h | 9 +- ssh_config.5 | 15 +- sshconnect2.c | 303 ++++++++++++++++++++++++++++- sshd_config.5 | 18 +- 20 files changed, 2537 insertions(+), 25 deletions(-) create mode 100644 auth2-jpake.c create mode 100644 jpake.c create mode 100644 jpake.h create mode 100644 schnorr.c (limited to 'readconf.c') diff --git a/ChangeLog b/ChangeLog index cf80ff1e6..012749b0d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,6 +7,25 @@ [auth.c] need unistd.h for close() prototype (ID sync only) + - djm@cvs.openbsd.org 2008/11/04 08:22:13 + [auth.h auth2.c monitor.c monitor.h monitor_wrap.c monitor_wrap.h] + [readconf.c readconf.h servconf.c servconf.h ssh2.h ssh_config.5] + [sshconnect2.c sshd_config.5 jpake.c jpake.h schnorr.c auth2-jpake.c] + [Makefile.in] + Add support for an experimental zero-knowledge password authentication + method using the J-PAKE protocol described in F. Hao, P. Ryan, + "Password Authenticated Key Exchange by Juggling", 16th Workshop on + Security Protocols, Cambridge, April 2008. + + This method allows password-based authentication without exposing + the password to the server. Instead, the client and server exchange + cryptographic proofs to demonstrate of knowledge of the password while + revealing nothing useful to an attacker or compromised endpoint. + + This is experimental, work-in-progress code and is presently + compiled-time disabled (turn on -DJPAKE in Makefile.inc). + + "just commit it. It isn't too intrusive." deraadt@ 20081103 - OpenBSD CVS Sync @@ -4857,4 +4876,4 @@ OpenServer 6 and add osr5bigcrypt support so when someone migrates passwords between UnixWare and OpenServer they will still work. OK dtucker@ -$Id: ChangeLog,v 1.5129 2008/11/05 05:12:54 djm Exp $ +$Id: ChangeLog,v 1.5130 2008/11/05 05:20:46 djm Exp $ diff --git a/Makefile.in b/Makefile.in index c1b7ab5a6..312b8d2b1 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,4 +1,4 @@ -# $Id: Makefile.in,v 1.297 2008/07/08 14:21:12 djm Exp $ +# $Id: Makefile.in,v 1.298 2008/11/05 05:20:46 djm Exp $ # uncomment if you run a non bourne compatable shell. Ie. csh #SHELL = @SH@ @@ -71,7 +71,7 @@ LIBSSH_OBJS=acss.o authfd.o authfile.o bufaux.o bufbn.o buffer.o \ atomicio.o key.o dispatch.o kex.o mac.o uidswap.o uuencode.o misc.o \ monitor_fdpass.o rijndael.o ssh-dss.o ssh-rsa.o dh.o kexdh.o \ kexgex.o kexdhc.o kexgexc.o scard.o msg.o progressmeter.o dns.o \ - entropy.o scard-opensc.o gss-genr.o umac.o + entropy.o scard-opensc.o gss-genr.o umac.o jpake.o schnorr.o SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \ sshconnect.o sshconnect1.o sshconnect2.o mux.o @@ -81,7 +81,7 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o auth-rsa.o auth-rh-rsa.o \ auth.o auth1.o auth2.o auth-options.o session.o \ auth-chall.o auth2-chall.o groupaccess.o \ auth-skey.o auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \ - auth2-none.o auth2-passwd.o auth2-pubkey.o \ + auth2-none.o auth2-passwd.o auth2-pubkey.o auth2-jpake.o \ monitor_mm.o monitor.o monitor_wrap.o kexdhs.o kexgexs.o \ auth-krb5.o \ auth2-gss.o gss-serv.o gss-serv-krb5.o \ diff --git a/auth.h b/auth.h index 6a70f0eb6..3a70f4421 100644 --- a/auth.h +++ b/auth.h @@ -1,4 +1,4 @@ -/* $OpenBSD: auth.h,v 1.61 2008/07/02 12:03:51 dtucker Exp $ */ +/* $OpenBSD: auth.h,v 1.62 2008/11/04 08:22:12 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. @@ -59,6 +59,7 @@ struct Authctxt { struct passwd *pw; /* set if 'valid' */ char *style; void *kbdintctxt; + void *jpake_ctx; #ifdef BSD_AUTH auth_session_t *as; #endif @@ -156,6 +157,9 @@ int bsdauth_respond(void *, u_int, char **); int skey_query(void *, char **, char **, u_int *, char ***, u_int **); int skey_respond(void *, u_int, char **); +void auth2_jpake_get_pwdata(Authctxt *, BIGNUM **, char **, char **); +void auth2_jpake_stop(Authctxt *); + int allowed_user(struct passwd *); struct passwd * getpwnamallow(const char *user); diff --git a/auth2-jpake.c b/auth2-jpake.c new file mode 100644 index 000000000..0029ec26b --- /dev/null +++ b/auth2-jpake.c @@ -0,0 +1,557 @@ +/* $OpenBSD: auth2-jpake.c,v 1.1 2008/11/04 08:22:12 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 + */ + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "xmalloc.h" +#include "ssh2.h" +#include "key.h" +#include "hostfile.h" +#include "buffer.h" +#include "auth.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 "jpake.h" + +#ifdef JPAKE + +/* + * 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); +} + +/* + * Being 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)); + 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 */ + diff --git a/auth2.c b/auth2.c index a835abfc6..ecf857052 100644 --- a/auth2.c +++ b/auth2.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth2.c,v 1.119 2008/07/04 23:30:16 djm Exp $ */ +/* $OpenBSD: auth2.c,v 1.120 2008/11/04 08:22:12 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * @@ -71,12 +71,18 @@ extern Authmethod method_hostbased; #ifdef GSSAPI extern Authmethod method_gssapi; #endif +#ifdef JPAKE +extern Authmethod method_jpake; +#endif Authmethod *authmethods[] = { &method_none, &method_pubkey, #ifdef GSSAPI &method_gssapi, +#endif +#ifdef JPAKE + &method_jpake, #endif &method_passwd, &method_kbdint, @@ -257,8 +263,12 @@ input_userauth_request(int type, u_int32_t seq, void *ctxt) } /* reset state */ auth2_challenge_stop(authctxt); +#ifdef JPAKE + auth2_jpake_stop(authctxt); +#endif #ifdef GSSAPI + /* XXX move to auth2_gssapi_stop() */ dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, NULL); #endif diff --git a/jpake.c b/jpake.c new file mode 100644 index 000000000..565f2e255 --- /dev/null +++ b/jpake.c @@ -0,0 +1,604 @@ +/* $OpenBSD: jpake.c,v 1.1 2008/11/04 08:22:12 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. + */ + +/* + * Shared components 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 + */ + +#include "includes.h" + +#include + +#include +#include +#include + +#include +#include + +#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 "jpake.h" + +#ifdef JPAKE + +/* RFC3526 group 5, 1536 bits */ +#define JPAKE_GROUP_G "2" +#define JPAKE_GROUP_P \ + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74" \ + "020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437" \ + "4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \ + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05" \ + "98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB" \ + "9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF" + +struct jpake_group * +jpake_default_group(void) +{ + struct jpake_group *ret; + + ret = xmalloc(sizeof(*ret)); + ret->p = ret->q = ret->g = NULL; + if (BN_hex2bn(&ret->p, JPAKE_GROUP_P) == 0 || + BN_hex2bn(&ret->g, JPAKE_GROUP_G) == 0) + fatal("%s: BN_hex2bn", __func__); + /* Subgroup order is p/2 (p is a safe prime) */ + if ((ret->q = BN_new()) == NULL) + fatal("%s: BN_new", __func__); + if (BN_rshift1(ret->q, ret->p) != 1) + fatal("%s: BN_rshift1", __func__); + + return ret; +} + +/* + * Generate uniformly distributed random number in range (1, high). + * Return number on success, NULL on failure. + */ +BIGNUM * +bn_rand_range_gt_one(const BIGNUM *high) +{ + BIGNUM *r, *tmp; + int success = -1; + + if ((tmp = BN_new()) == NULL) { + error("%s: BN_new", __func__); + return NULL; + } + if ((r = BN_new()) == NULL) { + error("%s: BN_new failed", __func__); + goto out; + } + if (BN_set_word(tmp, 2) != 1) { + error("%s: BN_set_word(tmp, 2)", __func__); + goto out; + } + if (BN_sub(tmp, high, tmp) == -1) { + error("%s: BN_sub failed (tmp = high - 2)", __func__); + goto out; + } + if (BN_rand_range(r, tmp) == -1) { + error("%s: BN_rand_range failed", __func__); + goto out; + } + if (BN_set_word(tmp, 2) != 1) { + error("%s: BN_set_word(tmp, 2)", __func__); + goto out; + } + if (BN_add(r, r, tmp) == -1) { + error("%s: BN_add failed (r = r + 2)", __func__); + goto out; + } + success = 0; + out: + BN_clear_free(tmp); + if (success == 0) + return r; + BN_clear_free(r); + return NULL; +} + +/* + * Hash contents of buffer 'b' with hash 'md'. Returns 0 on success, + * with digest via 'digestp' (caller to free) and length via 'lenp'. + * Returns -1 on failure. + */ +int +hash_buffer(const u_char *buf, u_int len, const EVP_MD *md, + u_char **digestp, u_int *lenp) +{ + u_char digest[EVP_MAX_MD_SIZE]; + u_int digest_len; + EVP_MD_CTX evp_md_ctx; + int success = -1; + + EVP_MD_CTX_init(&evp_md_ctx); + + if (EVP_DigestInit_ex(&evp_md_ctx, md, NULL) != 1) { + error("%s: EVP_DigestInit_ex", __func__); + goto out; + } + if (EVP_DigestUpdate(&evp_md_ctx, buf, len) != 1) { + error("%s: EVP_DigestUpdate", __func__); + goto out; + } + if (EVP_DigestFinal_ex(&evp_md_ctx, digest, &digest_len) != 1) { + error("%s: EVP_DigestFinal_ex", __func__); + goto out; + } + *digestp = xmalloc(digest_len); + *lenp = digest_len; + memcpy(*digestp, digest, *lenp); + success = 0; + out: + EVP_MD_CTX_cleanup(&evp_md_ctx); + bzero(digest, sizeof(digest)); + digest_len = 0; + return success; +} + +/* print formatted string followed by bignum */ +void +jpake_debug3_bn(const BIGNUM *n, const char *fmt, ...) +{ + char *out, *h; + va_list args; + + out = NULL; + va_start(args, fmt); + vasprintf(&out, fmt, args); + va_end(args); + if (out == NULL) + fatal("%s: vasprintf failed", __func__); + + if (n == NULL) + debug3("%s(null)", out); + else { + h = BN_bn2hex(n); + debug3("%s0x%s", out, h); + free(h); + } + free(out); +} + +/* print formatted string followed by buffer contents in hex */ +void +jpake_debug3_buf(const u_char *buf, u_int len, const char *fmt, ...) +{ + char *out, h[65]; + u_int i, j; + va_list args; + + out = NULL; + va_start(args, fmt); + vasprintf(&out, fmt, args); + va_end(args); + if (out == NULL) + fatal("%s: vasprintf failed", __func__); + + debug3("%s length %u%s", out, len, buf == NULL ? " (null)" : ""); + free(out); + if (buf == NULL) + return; + + *h = '\0'; + for (i = j = 0; i < len; i++) { + snprintf(h + j, sizeof(h) - j, "%02x", buf[i]); + j += 2; + if (j >= sizeof(h) - 1 || i == len - 1) { + debug3(" %s", h); + *h = '\0'; + j = 0; + } + } +} + +struct jpake_ctx * +jpake_new(void) +{ + struct jpake_ctx *ret; + + ret = xcalloc(1, sizeof(*ret)); + + ret->grp = jpake_default_group(); + + ret->s = ret->k = NULL; + ret->x1 = ret->x2 = ret->x3 = ret->x4 = NULL; + ret->g_x1 = ret->g_x2 = ret->g_x3 = ret->g_x4 = NULL; + ret->a = ret->b = NULL; + + ret->client_id = ret->server_id = NULL; + ret->h_k_cid_sessid = ret->h_k_sid_sessid = NULL; + + debug3("%s: alloc %p", __func__, ret); + + return ret; +} + + +void +jpake_free(struct jpake_ctx *pctx) +{ + debug3("%s: free %p", __func__, pctx); + +#define JPAKE_BN_CLEAR_FREE(v) \ + do { \ + if ((v) != NULL) { \ + BN_clear_free(v); \ + (v) = NULL; \ + } \ + } while (0) +#define JPAKE_BUF_CLEAR_FREE(v, l) \ + do { \ + if ((v) != NULL) { \ + bzero((v), (l)); \ + xfree(v); \ + (v) = NULL; \ + (l) = 0; \ + } \ + } while (0) + + JPAKE_BN_CLEAR_FREE(pctx->s); + JPAKE_BN_CLEAR_FREE(pctx->k); + JPAKE_BN_CLEAR_FREE(pctx->x1); + JPAKE_BN_CLEAR_FREE(pctx->x2); + JPAKE_BN_CLEAR_FREE(pctx->x3); + JPAKE_BN_CLEAR_FREE(pctx->x4); + JPAKE_BN_CLEAR_FREE(pctx->g_x1); + JPAKE_BN_CLEAR_FREE(pctx->g_x2); + JPAKE_BN_CLEAR_FREE(pctx->g_x3); + JPAKE_BN_CLEAR_FREE(pctx->g_x4); + JPAKE_BN_CLEAR_FREE(pctx->a); + JPAKE_BN_CLEAR_FREE(pctx->b); + + JPAKE_BUF_CLEAR_FREE(pctx->client_id, pctx->client_id_len); + JPAKE_BUF_CLEAR_FREE(pctx->server_id, pctx->server_id_len); + JPAKE_BUF_CLEAR_FREE(pctx->h_k_cid_sessid, pctx->h_k_cid_sessid_len); + JPAKE_BUF_CLEAR_FREE(pctx->h_k_sid_sessid, pctx->h_k_sid_sessid_len); + +#undef JPAKE_BN_CLEAR_FREE +#undef JPAKE_BUF_CLEAR_FREE + + bzero(pctx, sizeof(pctx)); + xfree(pctx); +} + +/* dump entire jpake_ctx. NB. includes private values! */ +void +jpake_dump(struct jpake_ctx *pctx, const char *fmt, ...) +{ + char *out; + va_list args; + + out = NULL; + va_start(args, fmt); + vasprintf(&out, fmt, args); + va_end(args); + if (out == NULL) + fatal("%s: vasprintf failed", __func__); + + debug3("%s: %s (ctx at %p)", __func__, out, pctx); + if (pctx == NULL) { + free(out); + return; + } + +#define JPAKE_DUMP_BN(a) do { \ + if ((a) != NULL) \ + JPAKE_DEBUG_BN(((a), "%s = ", #a)); \ + } while (0) +#define JPAKE_DUMP_BUF(a, b) do { \ + if ((a) != NULL) \ + JPAKE_DEBUG_BUF((a, b, "%s", #a)); \ + } while (0) + + JPAKE_DUMP_BN(pctx->s); + JPAKE_DUMP_BN(pctx->k); + JPAKE_DUMP_BN(pctx->x1); + JPAKE_DUMP_BN(pctx->x2); + JPAKE_DUMP_BN(pctx->x3); + JPAKE_DUMP_BN(pctx->x4); + JPAKE_DUMP_BN(pctx->g_x1); + JPAKE_DUMP_BN(pctx->g_x2); + JPAKE_DUMP_BN(pctx->g_x3); + JPAKE_DUMP_BN(pctx->g_x4); + JPAKE_DUMP_BN(pctx->a); + JPAKE_DUMP_BN(pctx->b); + + JPAKE_DUMP_BUF(pctx->client_id, pctx->client_id_len); + JPAKE_DUMP_BUF(pctx->server_id, pctx->server_id_len); + JPAKE_DUMP_BUF(pctx->h_k_cid_sessid, pctx->h_k_cid_sessid_len); + JPAKE_DUMP_BUF(pctx->h_k_sid_sessid, pctx->h_k_sid_sessid_len); + + debug3("%s: %s done", __func__, out); + free(out); +} + +/* Shared parts of step 1 exchange calculation */ +void +jpake_step1(struct jpake_group *grp, + u_char **id, u_int *id_len, + BIGNUM **priv1, BIGNUM **priv2, BIGNUM **g_priv1, BIGNUM **g_priv2, + u_char **priv1_proof, u_int *priv1_proof_len, + u_char **priv2_proof, u_int *priv2_proof_len) +{ + BN_CTX *bn_ctx; + + if ((bn_ctx = BN_CTX_new()) == NULL) + fatal("%s: BN_CTX_new", __func__); + + /* Random nonce to prevent replay */ + *id = xmalloc(KZP_ID_LEN); + *id_len = KZP_ID_LEN; + arc4random_buf(*id, *id_len); + + /* + * x1/x3 is a random element of Zq + * x2/x4 is a random element of Z*q + * We also exclude [1] from x1/x3 candidates and [0, 1] from + * x2/x4 candiates to avoid possible degeneracy (i.e. g^0, g^1). + */ + if ((*priv1 = bn_rand_range_gt_one(grp->q)) == NULL || + (*priv2 = bn_rand_range_gt_one(grp->q)) == NULL) + fatal("%s: bn_rand_range_gt_one", __func__); + + /* + * client: g_x1 = g^x1 mod p / server: g_x3 = g^x3 mod p + * client: g_x2 = g^x2 mod p / server: g_x4 = g^x4 mod p + */ + if ((*g_priv1 = BN_new()) == NULL || + (*g_priv2 = BN_new()) == NULL) + fatal("%s: BN_new", __func__); + if (BN_mod_exp(*g_priv1, grp->g, *priv1, grp->p, bn_ctx) == -1) + fatal("%s: BN_mod_exp", __func__); + if (BN_mod_exp(*g_priv2, grp->g, *priv2, grp->p, bn_ctx) == -1) + fatal("%s: BN_mod_exp", __func__); + + /* Generate proofs for holding x1/x3 and x2/x4 */ + if (schnorr_sign(grp->p, grp->q, grp->g, + *priv1, *g_priv1, *id, *id_len, + priv1_proof, priv1_proof_len) != 0) + fatal("%s: schnorr_sign", __func__); + if (schnorr_sign(grp->p, grp->q, grp->g, + *priv2, *g_priv2, *id, *id_len, + priv2_proof, priv2_proof_len) != 0) + fatal("%s: schnorr_sign", __func__); + + BN_CTX_free(bn_ctx); +} + +/* Shared parts of step 2 exchange calculation */ +void +jpake_step2(struct jpake_group *grp, BIGNUM *s, + BIGNUM *mypub1, BIGNUM *theirpub1, BIGNUM *theirpub2, BIGNUM *mypriv2, + const u_char *theirid, u_int theirid_len, + const u_char *myid, u_int myid_len, + const u_char *theirpub1_proof, u_int theirpub1_proof_len, + const u_char *theirpub2_proof, u_int theirpub2_proof_len, + BIGNUM **newpub, + u_char **newpub_exponent_proof, u_int *newpub_exponent_proof_len) +{ + BN_CTX *bn_ctx; + BIGNUM *tmp, *exponent; + + /* Validate peer's step 1 values */ + if (BN_cmp(theirpub1, BN_value_one()) <= 0) + fatal("%s: theirpub1 <= 1", __func__); + if (BN_cmp(theirpub2, BN_value_one()) <= 0) + fatal("%s: theirpub2 <= 1", __func__); + + if (schnorr_verify(grp->p, grp->q, grp->g, theirpub1, + theirid, theirid_len, theirpub1_proof, theirpub1_proof_len) != 1) + fatal("%s: schnorr_verify theirpub1 failed", __func__); + if (schnorr_verify(grp->p, grp->q, grp->g, theirpub2, + theirid, theirid_len, theirpub2_proof, theirpub2_proof_len) != 1) + fatal("%s: schnorr_verify theirpub2 failed", __func__); + + if ((bn_ctx = BN_CTX_new()) == NULL) + fatal("%s: BN_CTX_new", __func__); + + if ((*newpub = BN_new()) == NULL || + (tmp = BN_new()) == NULL || + (exponent = BN_new()) == NULL) + fatal("%s: BN_new", __func__); + + /* + * client: exponent = x2 * s mod p + * server: exponent = x4 * s mod p + */ + if (BN_mod_mul(exponent, mypriv2, s, grp->q, bn_ctx) != 1) + fatal("%s: BN_mod_mul (exponent = mypriv2 * s mod p)", + __func__); + + /* + * client: tmp = g^(x1 + x3 + x4) mod p + * server: tmp = g^(x1 + x2 + x3) mod p + */ + if (BN_mod_mul(tmp, mypub1, theirpub1, grp->p, bn_ctx) != 1) + fatal("%s: BN_mod_mul (tmp = mypub1 * theirpub1 mod p)", + __func__); + if (BN_mod_mul(tmp, tmp, theirpub2, grp->p, bn_ctx) != 1) + fatal("%s: BN_mod_mul (tmp = tmp * theirpub2 mod p)", __func__); + + /* + * client: a = tmp^exponent = g^((x1+x3+x4) * x2 * s) mod p + * server: b = tmp^exponent = g^((x1+x2+x3) * x4 * s) mod p + */ + if (BN_mod_exp(*newpub, tmp, exponent, grp->p, bn_ctx) != 1) + fatal("%s: BN_mod_mul (newpub = tmp^exponent mod p)", __func__); + + JPAKE_DEBUG_BN((tmp, "%s: tmp = ", __func__)); + JPAKE_DEBUG_BN((exponent, "%s: exponent = ", __func__)); + + /* Note the generator here is 'tmp', not g */ + if (schnorr_sign(grp->p, grp->q, tmp, exponent, *newpub, + myid, myid_len, + newpub_exponent_proof, newpub_exponent_proof_len) != 0) + fatal("%s: schnorr_sign newpub", __func__); + + BN_clear_free(tmp); /* XXX stash for later use? */ + BN_clear_free(exponent); /* XXX stash for later use? (yes, in conf) */ + + BN_CTX_free(bn_ctx); +} + +/* Confirmation hash calculation */ +void +jpake_confirm_hash(const BIGNUM *k, + const u_char *endpoint_id, u_int endpoint_id_len, + const u_char *sess_id, u_int sess_id_len, + u_char **confirm_hash, u_int *confirm_hash_len) +{ + Buffer b; + + /* + * Calculate confirmation proof: + * client: H(k || client_id || session_id) + * server: H(k || server_id || session_id) + */ + buffer_init(&b); + buffer_put_bignum2(&b, k); + buffer_put_string(&b, endpoint_id, endpoint_id_len); + buffer_put_string(&b, sess_id, sess_id_len); + if (hash_buffer(buffer_ptr(&b), buffer_len(&b), EVP_sha256(), + confirm_hash, confirm_hash_len) != 0) + fatal("%s: hash_buffer", __func__); + buffer_free(&b); +} + +/* Shared parts of key derivation and confirmation calculation */ +void +jpake_key_confirm(struct jpake_group *grp, BIGNUM *s, BIGNUM *step2_val, + BIGNUM *mypriv2, BIGNUM *mypub1, BIGNUM *mypub2, + BIGNUM *theirpub1, BIGNUM *theirpub2, + const u_char *my_id, u_int my_id_len, + const u_char *their_id, u_int their_id_len, + const u_char *sess_id, u_int sess_id_len, + const u_char *theirpriv2_s_proof, u_int theirpriv2_s_proof_len, + BIGNUM **k, + u_char **confirm_hash, u_int *confirm_hash_len) +{ + BN_CTX *bn_ctx; + BIGNUM *tmp; + + if ((bn_ctx = BN_CTX_new()) == NULL) + fatal("%s: BN_CTX_new", __func__); + if ((tmp = BN_new()) == NULL || + (*k = BN_new()) == NULL) + fatal("%s: BN_new", __func__); + + /* Validate step 2 values */ + if (BN_cmp(step2_val, BN_value_one()) <= 0) + fatal("%s: step2_val <= 1", __func__); + + /* + * theirpriv2_s_proof is calculated with a different generator: + * tmp = g^(mypriv1+mypriv2+theirpub1) = g^mypub1*g^mypub2*g^theirpub1 + * Calculate it here so we can check the signature. + */ + if (BN_mod_mul(tmp, mypub1, mypub2, grp->p, bn_ctx) != 1) + fatal("%s: BN_mod_mul (tmp = mypub1 * mypub2 mod p)", __func__); + if (BN_mod_mul(tmp, tmp, theirpub1, grp->p, bn_ctx) != 1) + fatal("%s: BN_mod_mul (tmp = tmp * theirpub1 mod p)", __func__); + + JPAKE_DEBUG_BN((tmp, "%s: tmp = ", __func__)); + + if (schnorr_verify(grp->p, grp->q, tmp, step2_val, + their_id, their_id_len, + theirpriv2_s_proof, theirpriv2_s_proof_len) != 1) + fatal("%s: schnorr_verify theirpriv2_s_proof failed", __func__); + + /* + * Derive shared key: + * client: k = (b / g^(x2*x4*s))^x2 = g^((x1+x3)*x2*x4*s) + * server: k = (a / g^(x2*x4*s))^x4 = g^((x1+x3)*x2*x4*s) + * + * Computed as: + * client: k = (g_x4^(q - (x2 * s)) * b)^x2 mod p + * server: k = (g_x2^(q - (x4 * s)) * b)^x4 mod p + */ + if (BN_mul(tmp, mypriv2, s, bn_ctx) != 1) + fatal("%s: BN_mul (tmp = mypriv2 * s)", __func__); + if (BN_mod_sub(tmp, grp->q, tmp, grp->q, bn_ctx) != 1) + fatal("%s: BN_mod_sub (tmp = q - tmp mod q)", __func__); + if (BN_mod_exp(tmp, theirpub2, tmp, grp->p, bn_ctx) != 1) + fatal("%s: BN_mod_exp (tmp = theirpub2^tmp) mod p", __func__); + if (BN_mod_mul(tmp, tmp, step2_val, grp->p, bn_ctx) != 1) + fatal("%s: BN_mod_mul (tmp = tmp * step2_val) mod p", __func__); + if (BN_mod_exp(*k, tmp, mypriv2, grp->p, bn_ctx) != 1) + fatal("%s: BN_mod_exp (k = tmp^mypriv2) mod p", __func__); + + BN_CTX_free(bn_ctx); + BN_clear_free(tmp); + + jpake_confirm_hash(*k, my_id, my_id_len, sess_id, sess_id_len, + confirm_hash, confirm_hash_len); +} + +/* + * Calculate and check confirmation hash from peer. Returns 1 on success + * 0 on failure/mismatch. + */ +int +jpake_check_confirm(const BIGNUM *k, + const u_char *peer_id, u_int peer_id_len, + const u_char *sess_id, u_int sess_id_len, + const u_char *peer_confirm_hash, u_int peer_confirm_hash_len) +{ + u_char *expected_confirm_hash; + u_int expected_confirm_hash_len; + int success = 0; + + /* Calculate and verify expected confirmation hash */ + jpake_confirm_hash(k, peer_id, peer_id_len, sess_id, sess_id_len, + &expected_confirm_hash, &expected_confirm_hash_len); + + JPAKE_DEBUG_BUF((expected_confirm_hash, expected_confirm_hash_len, + "%s: expected confirm hash", __func__)); + JPAKE_DEBUG_BUF((peer_confirm_hash, peer_confirm_hash_len, + "%s: received confirm hash", __func__)); + + if (peer_confirm_hash_len != expected_confirm_hash_len) + error("%s: confirmation length mismatch (my %u them %u)", + __func__, expected_confirm_hash_len, peer_confirm_hash_len); + else if (memcmp(peer_confirm_hash, expected_confirm_hash, + expected_confirm_hash_len) == 0) + success = 1; + bzero(expected_confirm_hash, expected_confirm_hash_len); + xfree(expected_confirm_hash); + debug3("%s: success = %d", __func__, success); + return success; +} + +/* XXX main() function with tests */ + +#endif /* JPAKE */ + diff --git a/jpake.h b/jpake.h new file mode 100644 index 000000000..a3d800cd3 --- /dev/null +++ b/jpake.h @@ -0,0 +1,134 @@ +/* $OpenBSD: jpake.h,v 1.1 2008/11/04 08:22: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. + */ + +#ifndef JPAKE_H +#define JPAKE_H + +#include + +#include + +/* Set JPAKE_DEBUG in CFLAGS for privacy-violating debugging */ +#ifndef JPAKE_DEBUG +# define JPAKE_DEBUG_BN(a) +# define JPAKE_DEBUG_BUF(a) +# define JPAKE_DEBUG_CTX(a) +#else +# define JPAKE_DEBUG_BN(a) jpake_debug3_bn a +# define JPAKE_DEBUG_BUF(a) jpake_debug3_buf a +# define JPAKE_DEBUG_CTX(a) jpake_dump a +#endif /* SCHNORR_DEBUG */ + +struct jpake_group { + BIGNUM *p, *q, *g; +}; + +#define KZP_ID_LEN 16 /* Length of client and server IDs */ + +struct jpake_ctx { + /* Parameters */ + struct jpake_group *grp; + + /* Private values shared by client and server */ + BIGNUM *s; /* Secret (salted, crypted password) */ + BIGNUM *k; /* Derived key */ + + /* Client private values (NULL for server) */ + BIGNUM *x1; /* random in Zq */ + BIGNUM *x2; /* random in Z*q */ + + /* Server private values (NULL for server) */ + BIGNUM *x3; /* random in Zq */ + BIGNUM *x4; /* random in Z*q */ + + /* Step 1: C->S */ + u_char *client_id; /* Anti-replay nonce */ + u_int client_id_len; + BIGNUM *g_x1; /* g^x1 */ + BIGNUM *g_x2; /* g^x2 */ + + /* Step 1: S->C */ + u_char *server_id; /* Anti-replay nonce */ + u_int server_id_len; + BIGNUM *g_x3; /* g^x3 */ + BIGNUM *g_x4; /* g^x4 */ + + /* Step 2: C->S */ + BIGNUM *a; /* g^((x1+x3+x4)*x2*s) */ + + /* Step 2: S->C */ + BIGNUM *b; /* g^((x1+x2+x3)*x4*s) */ + + /* Confirmation: C->S */ + u_char *h_k_cid_sessid; /* H(k || client_id || session_id) */ + u_int h_k_cid_sessid_len; + + /* Confirmation: S->C */ + u_char *h_k_sid_sessid; /* H(k || server_id || session_id) */ + u_int h_k_sid_sessid_len; +}; + +/* jpake.c */ +struct jpake_group *jpake_default_group(void); +BIGNUM *bn_rand_range_gt_one(const BIGNUM *high); +int hash_buffer(const u_char *, u_int, const EVP_MD *, u_char **, u_int *); +void jpake_debug3_bn(const BIGNUM *, const char *, ...) + __attribute__((__nonnull__ (2))) + __attribute__((format(printf, 2, 3))); +void jpake_debug3_buf(const u_char *, u_int, const char *, ...) + __attribute__((__nonnull__ (3))) + __attribute__((format(printf, 3, 4))); +void jpake_dump(struct jpake_ctx *, const char *, ...) + __attribute__((__nonnull__ (2))) + __attribute__((format(printf, 2, 3))); +struct jpake_ctx *jpake_new(void); +void jpake_free(struct jpake_ctx *); + +void jpake_step1(struct jpake_group *, u_char **, u_int *, + BIGNUM **, BIGNUM **, BIGNUM **, BIGNUM **, + u_char **, u_int *, u_char **, u_int *); + +void jpake_step2(struct jpake_group *, BIGNUM *, + BIGNUM *, BIGNUM *, BIGNUM *, BIGNUM *, + const u_char *, u_int, const u_char *, u_int, + const u_char *, u_int, const u_char *, u_int, + BIGNUM **, u_char **, u_int *); + +void jpake_confirm_hash(const BIGNUM *, + const u_char *, u_int, + const u_char *, u_int, + u_char **, u_int *); + +void jpake_key_confirm(struct jpake_group *, BIGNUM *, BIGNUM *, + BIGNUM *, BIGNUM *, BIGNUM *, BIGNUM *, BIGNUM *, + const u_char *, u_int, const u_char *, u_int, + const u_char *, u_int, const u_char *, u_int, + BIGNUM **, u_char **, u_int *); + +int jpake_check_confirm(const BIGNUM *, const u_char *, u_int, + const u_char *, u_int, const u_char *, u_int); + +/* schnorr.c */ +int schnorr_sign(const BIGNUM *, const BIGNUM *, const BIGNUM *, + const BIGNUM *, const BIGNUM *, const u_char *, u_int , + u_char **, u_int *); +int schnorr_verify(const BIGNUM *, const BIGNUM *, const BIGNUM *, + const BIGNUM *, const u_char *, u_int, + const u_char *, u_int); + +#endif /* JPAKE_H */ + diff --git a/monitor.c b/monitor.c index 73cf6bc9b..39deedc8f 100644 --- a/monitor.c +++ b/monitor.c @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor.c,v 1.99 2008/07/10 18:08:11 markus Exp $ */ +/* $OpenBSD: monitor.c,v 1.100 2008/11/04 08:22:13 djm Exp $ */ /* * Copyright 2002 Niels Provos * Copyright 2002 Markus Friedl @@ -87,6 +87,7 @@ #include "misc.h" #include "compat.h" #include "ssh2.h" +#include "jpake.h" #ifdef GSSAPI static Gssctxt *gsscontext = NULL; @@ -149,6 +150,11 @@ int mm_answer_rsa_challenge(int, Buffer *); int mm_answer_rsa_response(int, Buffer *); int mm_answer_sesskey(int, Buffer *); int mm_answer_sessid(int, Buffer *); +int mm_answer_jpake_get_pwdata(int, Buffer *); +int mm_answer_jpake_step1(int, Buffer *); +int mm_answer_jpake_step2(int, Buffer *); +int mm_answer_jpake_key_confirm(int, Buffer *); +int mm_answer_jpake_check_confirm(int, Buffer *); #ifdef USE_PAM int mm_answer_pam_start(int, Buffer *); @@ -233,6 +239,13 @@ struct mon_table mon_dispatch_proto20[] = { {MONITOR_REQ_GSSSTEP, MON_ISAUTH, mm_answer_gss_accept_ctx}, {MONITOR_REQ_GSSUSEROK, MON_AUTH, mm_answer_gss_userok}, {MONITOR_REQ_GSSCHECKMIC, MON_ISAUTH, mm_answer_gss_checkmic}, +#endif +#ifdef JPAKE + {MONITOR_REQ_JPAKE_GET_PWDATA, MON_ONCE, mm_answer_jpake_get_pwdata}, + {MONITOR_REQ_JPAKE_STEP1, MON_ISAUTH, mm_answer_jpake_step1}, + {MONITOR_REQ_JPAKE_STEP2, MON_ONCE, mm_answer_jpake_step2}, + {MONITOR_REQ_JPAKE_KEY_CONFIRM, MON_ONCE, mm_answer_jpake_key_confirm}, + {MONITOR_REQ_JPAKE_CHECK_CONFIRM, MON_AUTH, mm_answer_jpake_check_confirm}, #endif {0, 0, NULL} }; @@ -379,6 +392,15 @@ monitor_child_preauth(Authctxt *_authctxt, struct monitor *pmonitor) if (!authenticated) authctxt->failures++; } +#ifdef JPAKE + /* Cleanup JPAKE context after authentication */ + if (ent->flags & MON_AUTHDECIDE) { + if (authctxt->jpake_ctx != NULL) { + jpake_free(authctxt->jpake_ctx); + authctxt->jpake_ctx = NULL; + } + } +#endif } if (!authctxt->valid) @@ -1969,3 +1991,206 @@ mm_answer_gss_userok(int sock, Buffer *m) return (authenticated); } #endif /* GSSAPI */ + +#ifdef JPAKE +int +mm_answer_jpake_step1(int sock, Buffer *m) +{ + struct jpake_ctx *pctx; + u_char *x3_proof, *x4_proof; + u_int x3_proof_len, x4_proof_len; + + if (!options.zero_knowledge_password_authentication) + fatal("zero_knowledge_password_authentication disabled"); + + if (authctxt->jpake_ctx != NULL) + fatal("%s: authctxt->jpake_ctx already set (%p)", + __func__, authctxt->jpake_ctx); + authctxt->jpake_ctx = pctx = jpake_new(); + + 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); + + JPAKE_DEBUG_CTX((pctx, "step1 done in %s", __func__)); + + buffer_clear(m); + + buffer_put_string(m, pctx->server_id, pctx->server_id_len); + buffer_put_bignum2(m, pctx->g_x3); + buffer_put_bignum2(m, pctx->g_x4); + buffer_put_string(m, x3_proof, x3_proof_len); + buffer_put_string(m, x4_proof, x4_proof_len); + + debug3("%s: sending step1", __func__); + mm_request_send(sock, MONITOR_ANS_JPAKE_STEP1, m); + + bzero(x3_proof, x3_proof_len); + bzero(x4_proof, x4_proof_len); + xfree(x3_proof); + xfree(x4_proof); + + monitor_permit(mon_dispatch, MONITOR_REQ_JPAKE_GET_PWDATA, 1); + monitor_permit(mon_dispatch, MONITOR_REQ_JPAKE_STEP1, 0); + + return 0; +} + +int +mm_answer_jpake_get_pwdata(int sock, Buffer *m) +{ + struct jpake_ctx *pctx = authctxt->jpake_ctx; + char *hash_scheme, *salt; + + if (pctx == NULL) + fatal("%s: pctx == NULL", __func__); + + auth2_jpake_get_pwdata(authctxt, &pctx->s, &hash_scheme, &salt); + + buffer_clear(m); + /* pctx->s is sensitive, not returned to slave */ + buffer_put_cstring(m, hash_scheme); + buffer_put_cstring(m, salt); + + debug3("%s: sending pwdata", __func__); + mm_request_send(sock, MONITOR_ANS_JPAKE_GET_PWDATA, m); + + bzero(hash_scheme, strlen(hash_scheme)); + bzero(salt, strlen(salt)); + xfree(hash_scheme); + xfree(salt); + + monitor_permit(mon_dispatch, MONITOR_REQ_JPAKE_STEP2, 1); + + return 0; +} + +int +mm_answer_jpake_step2(int sock, Buffer *m) +{ + 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; + + if (pctx == NULL) + fatal("%s: pctx == NULL", __func__); + + if ((pctx->g_x1 = BN_new()) == NULL || + (pctx->g_x2 = BN_new()) == NULL) + fatal("%s: BN_new", __func__); + buffer_get_bignum2(m, pctx->g_x1); + buffer_get_bignum2(m, pctx->g_x2); + pctx->client_id = buffer_get_string(m, &pctx->client_id_len); + x1_proof = buffer_get_string(m, &x1_proof_len); + x2_proof = buffer_get_string(m, &x2_proof_len); + + 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); + + JPAKE_DEBUG_CTX((pctx, "step2 done in %s", __func__)); + + bzero(x1_proof, x1_proof_len); + bzero(x2_proof, x2_proof_len); + xfree(x1_proof); + xfree(x2_proof); + + buffer_clear(m); + + buffer_put_bignum2(m, pctx->b); + buffer_put_string(m, x4_s_proof, x4_s_proof_len); + + debug3("%s: sending step2", __func__); + mm_request_send(sock, MONITOR_ANS_JPAKE_STEP2, m); + + bzero(x4_s_proof, x4_s_proof_len); + xfree(x4_s_proof); + + monitor_permit(mon_dispatch, MONITOR_REQ_JPAKE_KEY_CONFIRM, 1); + + return 0; +} + +int +mm_answer_jpake_key_confirm(int sock, Buffer *m) +{ + struct jpake_ctx *pctx = authctxt->jpake_ctx; + u_char *x2_s_proof; + u_int x2_s_proof_len; + + if (pctx == NULL) + fatal("%s: pctx == NULL", __func__); + + if ((pctx->a = BN_new()) == NULL) + fatal("%s: BN_new", __func__); + buffer_get_bignum2(m, pctx->a); + x2_s_proof = buffer_get_string(m, &x2_s_proof_len); + + 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); + + JPAKE_DEBUG_CTX((pctx, "key_confirm done in %s", __func__)); + + bzero(x2_s_proof, x2_s_proof_len); + buffer_clear(m); + + /* pctx->k is sensitive, not sent */ + buffer_put_string(m, pctx->h_k_sid_sessid, pctx->h_k_sid_sessid_len); + + debug3("%s: sending confirmation hash", __func__); + mm_request_send(sock, MONITOR_ANS_JPAKE_KEY_CONFIRM, m); + + monitor_permit(mon_dispatch, MONITOR_REQ_JPAKE_CHECK_CONFIRM, 1); + + return 0; +} + +int +mm_answer_jpake_check_confirm(int sock, Buffer *m) +{ + int authenticated = 0; + u_char *peer_confirm_hash; + u_int peer_confirm_hash_len; + struct jpake_ctx *pctx = authctxt->jpake_ctx; + + if (pctx == NULL) + fatal("%s: pctx == NULL", __func__); + + peer_confirm_hash = buffer_get_string(m, &peer_confirm_hash_len); + + authenticated = jpake_check_confirm(pctx->k, + pctx->client_id, pctx->client_id_len, + session_id2, session_id2_len, + peer_confirm_hash, peer_confirm_hash_len) && authctxt->valid; + + JPAKE_DEBUG_CTX((pctx, "check_confirm done in %s", __func__)); + + bzero(peer_confirm_hash, peer_confirm_hash_len); + xfree(peer_confirm_hash); + + buffer_clear(m); + buffer_put_int(m, authenticated); + + debug3("%s: sending result %d", __func__, authenticated); + mm_request_send(sock, MONITOR_ANS_JPAKE_CHECK_CONFIRM, m); + + monitor_permit(mon_dispatch, MONITOR_REQ_JPAKE_STEP1, 1); + + auth_method = "jpake-01@openssh.com"; + return authenticated; +} + +#endif /* JPAKE */ diff --git a/monitor.h b/monitor.h index 464009ad8..a8a2c0c19 100644 --- a/monitor.h +++ b/monitor.h @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor.h,v 1.14 2006/03/25 22:22:43 djm Exp $ */ +/* $OpenBSD: monitor.h,v 1.15 2008/11/04 08:22:13 djm Exp $ */ /* * Copyright 2002 Niels Provos @@ -60,7 +60,12 @@ enum monitor_reqtype { MONITOR_REQ_PAM_RESPOND, MONITOR_ANS_PAM_RESPOND, MONITOR_REQ_PAM_FREE_CTX, MONITOR_ANS_PAM_FREE_CTX, MONITOR_REQ_AUDIT_EVENT, MONITOR_REQ_AUDIT_COMMAND, - MONITOR_REQ_TERM + MONITOR_REQ_TERM, + MONITOR_REQ_JPAKE_STEP1, MONITOR_ANS_JPAKE_STEP1, + MONITOR_REQ_JPAKE_GET_PWDATA, MONITOR_ANS_JPAKE_GET_PWDATA, + MONITOR_REQ_JPAKE_STEP2, MONITOR_ANS_JPAKE_STEP2, + MONITOR_REQ_JPAKE_KEY_CONFIRM, MONITOR_ANS_JPAKE_KEY_CONFIRM, + MONITOR_REQ_JPAKE_CHECK_CONFIRM, MONITOR_ANS_JPAKE_CHECK_CONFIRM, }; struct mm_master; diff --git a/monitor_wrap.c b/monitor_wrap.c index 40463d078..0986fc518 100644 --- a/monitor_wrap.c +++ b/monitor_wrap.c @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor_wrap.c,v 1.63 2008/07/10 18:08:11 markus Exp $ */ +/* $OpenBSD: monitor_wrap.c,v 1.64 2008/11/04 08:22:13 djm Exp $ */ /* * Copyright 2002 Niels Provos * Copyright 2002 Markus Friedl @@ -40,6 +40,7 @@ #include #include +#include #include "openbsd-compat/sys-queue.h" #include "xmalloc.h" @@ -70,7 +71,7 @@ #include "atomicio.h" #include "monitor_fdpass.h" #include "misc.h" -#include "servconf.h" +#include "jpake.h" #include "channels.h" #include "session.h" @@ -1256,3 +1257,165 @@ mm_ssh_gssapi_userok(char *user) return (authenticated); } #endif /* GSSAPI */ + +#ifdef JPAKE +void +mm_auth2_jpake_get_pwdata(Authctxt *authctxt, BIGNUM **s, + char **hash_scheme, char **salt) +{ + Buffer m; + + debug3("%s entering", __func__); + + buffer_init(&m); + mm_request_send(pmonitor->m_recvfd, + MONITOR_REQ_JPAKE_GET_PWDATA, &m); + + debug3("%s: waiting for MONITOR_ANS_JPAKE_GET_PWDATA", __func__); + mm_request_receive_expect(pmonitor->m_recvfd, + MONITOR_ANS_JPAKE_GET_PWDATA, &m); + + *hash_scheme = buffer_get_string(&m, NULL); + *salt = buffer_get_string(&m, NULL); + + buffer_free(&m); +} + +void +mm_jpake_step1(struct jpake_group *grp, + u_char **id, u_int *id_len, + BIGNUM **priv1, BIGNUM **priv2, BIGNUM **g_priv1, BIGNUM **g_priv2, + u_char **priv1_proof, u_int *priv1_proof_len, + u_char **priv2_proof, u_int *priv2_proof_len) +{ + Buffer m; + + debug3("%s entering", __func__); + + buffer_init(&m); + mm_request_send(pmonitor->m_recvfd, + MONITOR_REQ_JPAKE_STEP1, &m); + + debug3("%s: waiting for MONITOR_ANS_JPAKE_STEP1", __func__); + mm_request_receive_expect(pmonitor->m_recvfd, + MONITOR_ANS_JPAKE_STEP1, &m); + + if ((*priv1 = BN_new()) == NULL || + (*priv2 = BN_new()) == NULL || + (*g_priv1 = BN_new()) == NULL || + (*g_priv2 = BN_new()) == NULL) + fatal("%s: BN_new", __func__); + + *id = buffer_get_string(&m, id_len); + /* priv1 and priv2 are, well, private */ + buffer_get_bignum2(&m, *g_priv1); + buffer_get_bignum2(&m, *g_priv2); + *priv1_proof = buffer_get_string(&m, priv1_proof_len); + *priv2_proof = buffer_get_string(&m, priv2_proof_len); + + buffer_free(&m); +} + +void +mm_jpake_step2(struct jpake_group *grp, BIGNUM *s, + BIGNUM *mypub1, BIGNUM *theirpub1, BIGNUM *theirpub2, BIGNUM *mypriv2, + const u_char *theirid, u_int theirid_len, + const u_char *myid, u_int myid_len, + const u_char *theirpub1_proof, u_int theirpub1_proof_len, + const u_char *theirpub2_proof, u_int theirpub2_proof_len, + BIGNUM **newpub, + u_char **newpub_exponent_proof, u_int *newpub_exponent_proof_len) +{ + Buffer m; + + debug3("%s entering", __func__); + + buffer_init(&m); + /* monitor already has all bignums except theirpub1, theirpub2 */ + buffer_put_bignum2(&m, theirpub1); + buffer_put_bignum2(&m, theirpub2); + /* monitor already knows our id */ + buffer_put_string(&m, theirid, theirid_len); + buffer_put_string(&m, theirpub1_proof, theirpub1_proof_len); + buffer_put_string(&m, theirpub2_proof, theirpub2_proof_len); + + mm_request_send(pmonitor->m_recvfd, + MONITOR_REQ_JPAKE_STEP2, &m); + + debug3("%s: waiting for MONITOR_ANS_JPAKE_STEP2", __func__); + mm_request_receive_expect(pmonitor->m_recvfd, + MONITOR_ANS_JPAKE_STEP2, &m); + + if ((*newpub = BN_new()) == NULL) + fatal("%s: BN_new", __func__); + + buffer_get_bignum2(&m, *newpub); + *newpub_exponent_proof = buffer_get_string(&m, + newpub_exponent_proof_len); + + buffer_free(&m); +} + +void +mm_jpake_key_confirm(struct jpake_group *grp, BIGNUM *s, BIGNUM *step2_val, + BIGNUM *mypriv2, BIGNUM *mypub1, BIGNUM *mypub2, + BIGNUM *theirpub1, BIGNUM *theirpub2, + const u_char *my_id, u_int my_id_len, + const u_char *their_id, u_int their_id_len, + const u_char *sess_id, u_int sess_id_len, + const u_char *theirpriv2_s_proof, u_int theirpriv2_s_proof_len, + BIGNUM **k, + u_char **confirm_hash, u_int *confirm_hash_len) +{ + Buffer m; + + debug3("%s entering", __func__); + + buffer_init(&m); + /* monitor already has all bignums except step2_val */ + buffer_put_bignum2(&m, step2_val); + /* monitor already knows all the ids */ + buffer_put_string(&m, theirpriv2_s_proof, theirpriv2_s_proof_len); + + mm_request_send(pmonitor->m_recvfd, + MONITOR_REQ_JPAKE_KEY_CONFIRM, &m); + + debug3("%s: waiting for MONITOR_ANS_JPAKE_KEY_CONFIRM", __func__); + mm_request_receive_expect(pmonitor->m_recvfd, + MONITOR_ANS_JPAKE_KEY_CONFIRM, &m); + + /* 'k' is sensitive and stays in the monitor */ + *confirm_hash = buffer_get_string(&m, confirm_hash_len); + + buffer_free(&m); +} + +int +mm_jpake_check_confirm(const BIGNUM *k, + const u_char *peer_id, u_int peer_id_len, + const u_char *sess_id, u_int sess_id_len, + const u_char *peer_confirm_hash, u_int peer_confirm_hash_len) +{ + Buffer m; + int success = 0; + + debug3("%s entering", __func__); + + buffer_init(&m); + /* k is dummy in slave, ignored */ + /* monitor knows all the ids */ + buffer_put_string(&m, peer_confirm_hash, peer_confirm_hash_len); + mm_request_send(pmonitor->m_recvfd, + MONITOR_REQ_JPAKE_CHECK_CONFIRM, &m); + + debug3("%s: waiting for MONITOR_ANS_JPAKE_CHECK_CONFIRM", __func__); + mm_request_receive_expect(pmonitor->m_recvfd, + MONITOR_ANS_JPAKE_CHECK_CONFIRM, &m); + + success = buffer_get_int(&m); + buffer_free(&m); + + debug3("%s: success = %d", __func__, success); + return success; +} +#endif /* JPAKE */ diff --git a/monitor_wrap.h b/monitor_wrap.h index 329189c2a..55c4b99f3 100644 --- a/monitor_wrap.h +++ b/monitor_wrap.h @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor_wrap.h,v 1.20 2006/08/03 03:34:42 deraadt Exp $ */ +/* $OpenBSD: monitor_wrap.h,v 1.21 2008/11/04 08:22:13 djm Exp $ */ /* * Copyright 2002 Niels Provos @@ -101,6 +101,26 @@ int mm_bsdauth_respond(void *, u_int, char **); int mm_skey_query(void *, char **, char **, u_int *, char ***, u_int **); int mm_skey_respond(void *, u_int, char **); +/* jpake */ +struct jpake_group; +void mm_auth2_jpake_get_pwdata(struct Authctxt *, BIGNUM **, char **, char **); +void mm_jpake_step1(struct jpake_group *, u_char **, u_int *, + BIGNUM **, BIGNUM **, BIGNUM **, BIGNUM **, + u_char **, u_int *, u_char **, u_int *); +void mm_jpake_step2(struct jpake_group *, BIGNUM *, + BIGNUM *, BIGNUM *, BIGNUM *, BIGNUM *, + const u_char *, u_int, const u_char *, u_int, + const u_char *, u_int, const u_char *, u_int, + BIGNUM **, u_char **, u_int *); +void mm_jpake_key_confirm(struct jpake_group *, BIGNUM *, BIGNUM *, + BIGNUM *, BIGNUM *, BIGNUM *, BIGNUM *, BIGNUM *, + const u_char *, u_int, const u_char *, u_int, + const u_char *, u_int, const u_char *, u_int, + BIGNUM **, u_char **, u_int *); +int mm_jpake_check_confirm(const BIGNUM *, + const u_char *, u_int, const u_char *, u_int, const u_char *, u_int); + + /* zlib allocation hooks */ void *mm_zalloc(struct mm_master *, u_int, u_int); diff --git a/readconf.c b/readconf.c index 7f7bbfee2..ba70d9da0 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.170 2008/11/03 02:44:41 stevesk Exp $ */ +/* $OpenBSD: readconf.c,v 1.171 2008/11/04 08:22:13 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -130,7 +130,7 @@ typedef enum { oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly, oSendEnv, oControlPath, oControlMaster, oHashKnownHosts, oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand, - oVisualHostKey, + oVisualHostKey, oZeroKnowledgePasswordAuthentication, oDeprecated, oUnsupported } OpCodes; @@ -228,6 +228,13 @@ static struct { { "localcommand", oLocalCommand }, { "permitlocalcommand", oPermitLocalCommand }, { "visualhostkey", oVisualHostKey }, +#ifdef JPAKE + { "zeroknowledgepasswordauthentication", + oZeroKnowledgePasswordAuthentication }, +#else + { "zeroknowledgepasswordauthentication", oUnsupported }, +#endif + { NULL, oBadOption } }; @@ -412,6 +419,10 @@ parse_flag: intptr = &options->password_authentication; goto parse_flag; + case oZeroKnowledgePasswordAuthentication: + intptr = &options->zero_knowledge_password_authentication; + goto parse_flag; + case oKbdInteractiveAuthentication: intptr = &options->kbd_interactive_authentication; goto parse_flag; @@ -1054,6 +1065,7 @@ initialize_options(Options * options) options->local_command = NULL; options->permit_local_command = -1; options->visual_host_key = -1; + options->zero_knowledge_password_authentication = -1; } /* @@ -1190,6 +1202,8 @@ fill_default_options(Options * options) options->permit_local_command = 0; if (options->visual_host_key == -1) options->visual_host_key = 0; + if (options->zero_knowledge_password_authentication == -1) + options->zero_knowledge_password_authentication = 0; /* options->local_command should not be set by default */ /* options->proxy_command should not be set by default */ /* options->user will be set in the main program if appropriate */ diff --git a/readconf.h b/readconf.h index c1387a896..c9e5f6a41 100644 --- a/readconf.h +++ b/readconf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.h,v 1.75 2008/11/01 17:40:33 stevesk Exp $ */ +/* $OpenBSD: readconf.h,v 1.76 2008/11/04 08:22:13 djm Exp $ */ /* * Author: Tatu Ylonen @@ -49,6 +49,7 @@ typedef struct { * authentication. */ int kbd_interactive_authentication; /* Try keyboard-interactive auth. */ char *kbd_interactive_devices; /* Keyboard-interactive auth devices. */ + int zero_knowledge_password_authentication; /* Try jpake */ int batch_mode; /* Batch mode: do not ask for passwords. */ int check_host_ip; /* Also keep track of keys for IP address */ int strict_host_key_checking; /* Strict host key checking. */ diff --git a/schnorr.c b/schnorr.c new file mode 100644 index 000000000..e3abe5702 --- /dev/null +++ b/schnorr.c @@ -0,0 +1,407 @@ +/* $OpenBSD: schnorr.c,v 1.1 2008/11/04 08:22: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. + */ + +/* + * Implementation of Schnorr signatures / zero-knowledge proofs, based on + * description 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 + */ + +#include "includes.h" + +#include + +#include +#include +#include + +#include +#include + +#include "xmalloc.h" +#include "buffer.h" +#include "log.h" + +#include "jpake.h" + +/* #define SCHNORR_DEBUG */ /* Privacy-violating debugging */ +/* #define SCHNORR_MAIN */ /* Include main() selftest */ + +/* XXX */ +/* Parametise signature hash? (sha256, sha1, etc.) */ +/* Signature format - include type name, hash type, group params? */ + +#ifndef SCHNORR_DEBUG +# define SCHNORR_DEBUG_BN(a) +# define SCHNORR_DEBUG_BUF(a) +#else +# define SCHNORR_DEBUG_BN(a) jpake_debug3_bn a +# define SCHNORR_DEBUG_BUF(a) jpake_debug3_buf a +#endif /* SCHNORR_DEBUG */ + +/* + * Calculate hash component of Schnorr signature H(g || g^v || g^x || id) + * using SHA1. Returns signature as bignum or NULL on error. + */ +static BIGNUM * +schnorr_hash(const BIGNUM *p, const BIGNUM *q, const BIGNUM *g, + const BIGNUM *g_v, const BIGNUM *g_x, + const u_char *id, u_int idlen) +{ + u_char *digest; + u_int digest_len; + BIGNUM *h; + EVP_MD_CTX evp_md_ctx; + Buffer b; + int success = -1; + + if ((h = BN_new()) == NULL) { + error("%s: BN_new", __func__); + return NULL; + } + + buffer_init(&b); + EVP_MD_CTX_init(&evp_md_ctx); + + /* h = H(g || g^v || g^x || id) */ + buffer_put_bignum2(&b, g); + buffer_put_bignum2(&b, g_v); + buffer_put_bignum2(&b, g_x); + buffer_put_string(&b, id, idlen); + + SCHNORR_DEBUG_BUF((buffer_ptr(&b), buffer_len(&b), + "%s: hashblob", __func__)); + if (hash_buffer(buffer_ptr(&b), buffer_len(&b), EVP_sha256(), + &digest, &digest_len) != 0) { + error("%s: hash_buffer", __func__); + goto out; + } + if (BN_bin2bn(digest, (int)digest_len, h) == NULL) { + error("%s: BN_bin2bn", __func__); + goto out; + } + success = 0; + SCHNORR_DEBUG_BN((h, "%s: h = ", __func__)); + out: + buffer_free(&b); + EVP_MD_CTX_cleanup(&evp_md_ctx); + bzero(digest, digest_len); + xfree(digest); + digest_len = 0; + if (success == 0) + return h; + BN_clear_free(h); + return NULL; +} + +/* + * Generate Schnorr signature to prove knowledge of private value 'x' used + * in public exponent g^x, under group defined by 'grp_p', 'grp_q' and 'grp_g' + * 'idlen' bytes from 'id' will be included in the signature hash as an anti- + * replay salt. + * On success, 0 is returned and *siglen bytes of signature are returned in + * *sig (caller to free). Returns -1 on failure. + */ +int +schnorr_sign(const BIGNUM *grp_p, const BIGNUM *grp_q, const BIGNUM *grp_g, + const BIGNUM *x, const BIGNUM *g_x, const u_char *id, u_int idlen, + u_char **sig, u_int *siglen) +{ + int success = -1; + Buffer b; + BIGNUM *h, *tmp, *v, *g_v, *r; + BN_CTX *bn_ctx; + + SCHNORR_DEBUG_BN((x, "%s: x = ", __func__)); + SCHNORR_DEBUG_BN((g_x, "%s: g_x = ", __func__)); + + /* Avoid degenerate cases: g^0 yields a spoofable signature */ + if (BN_cmp(g_x, BN_value_one()) <= 0) { + error("%s: g_x < 1", __func__); + return -1; + } + + h = g_v = r = tmp = v = NULL; + if ((bn_ctx = BN_CTX_new()) == NULL) { + error("%s: BN_CTX_new", __func__); + goto out; + } + if ((g_v = BN_new()) == NULL || + (r = BN_new()) == NULL || + (tmp = BN_new()) == NULL) { + error("%s: BN_new", __func__); + goto out; + } + + /* + * v must be a random element of Zq, so 1 <= v < q + * we also exclude v = 1, since g^1 looks dangerous + */ + if ((v = bn_rand_range_gt_one(grp_p)) == NULL) { + error("%s: bn_rand_range2", __func__); + goto out; + } + SCHNORR_DEBUG_BN((v, "%s: v = ", __func__)); + + /* g_v = g^v mod p */ + if (BN_mod_exp(g_v, grp_g, v, grp_p, bn_ctx) == -1) { + error("%s: BN_mod_exp (g^v mod p)", __func__); + goto out; + } + SCHNORR_DEBUG_BN((g_v, "%s: g_v = ", __func__)); + + /* h = H(g || g^v || g^x || id) */ + if ((h = schnorr_hash(grp_p, grp_q, grp_g, g_v, g_x, + id, idlen)) == NULL) { + error("%s: schnorr_hash failed", __func__); + goto out; + } + + /* r = v - xh mod q */ + if (BN_mod_mul(tmp, x, h, grp_q, bn_ctx) == -1) { + error("%s: BN_mod_mul (tmp = xv mod q)", __func__); + goto out; + } + if (BN_mod_sub(r, v, tmp, grp_q, bn_ctx) == -1) { + error("%s: BN_mod_mul (r = v - tmp)", __func__); + goto out; + } + SCHNORR_DEBUG_BN((r, "%s: r = ", __func__)); + + /* Signature is (g_v, r) */ + buffer_init(&b); + /* XXX sigtype-hash as string? */ + buffer_put_bignum2(&b, g_v); + buffer_put_bignum2(&b, r); + *siglen = buffer_len(&b); + *sig = xmalloc(*siglen); + memcpy(*sig, buffer_ptr(&b), *siglen); + SCHNORR_DEBUG_BUF((buffer_ptr(&b), buffer_len(&b), + "%s: sigblob", __func__)); + buffer_free(&b); + success = 0; + out: + BN_CTX_free(bn_ctx); + if (h != NULL) + BN_clear_free(h); + if (v != NULL) + BN_clear_free(v); + BN_clear_free(r); + BN_clear_free(g_v); + BN_clear_free(tmp); + + return success; +} + +/* + * Verify Schnorr signature 'sig' of length 'siglen' against public exponent + * g_x (g^x) under group defined by 'grp_p', 'grp_q' and 'grp_g'. + * Signature hash will be salted with 'idlen' bytes from 'id'. + * Returns -1 on failure, 0 on incorrect signature or 1 on matching signature. + */ +int +schnorr_verify(const BIGNUM *grp_p, const BIGNUM *grp_q, const BIGNUM *grp_g, + const BIGNUM *g_x, const u_char *id, u_int idlen, + const u_char *sig, u_int siglen) +{ + int success = -1; + Buffer b; + BIGNUM *g_v, *h, *r, *g_xh, *g_r, *expected; + BN_CTX *bn_ctx; + u_int rlen; + + SCHNORR_DEBUG_BN((g_x, "%s: g_x = ", __func__)); + + /* Avoid degenerate cases: g^0 yields a spoofable signature */ + if (BN_cmp(g_x, BN_value_one()) <= 0) { + error("%s: g_x < 1", __func__); + return -1; + } + + g_v = h = r = g_xh = g_r = expected = NULL; + if ((bn_ctx = BN_CTX_new()) == NULL) { + error("%s: BN_CTX_new", __func__); + goto out; + } + if ((g_v = BN_new()) == NULL || + (r = BN_new()) == NULL || + (g_xh = BN_new()) == NULL || + (g_r = BN_new()) == NULL || + (expected = BN_new()) == NULL) { + error("%s: BN_new", __func__); + goto out; + } + + /* Extract g^v and r from signature blob */ + buffer_init(&b); + buffer_append(&b, sig, siglen); + SCHNORR_DEBUG_BUF((buffer_ptr(&b), buffer_len(&b), + "%s: sigblob", __func__)); + buffer_get_bignum2(&b, g_v); + buffer_get_bignum2(&b, r); + rlen = buffer_len(&b); + buffer_free(&b); + if (rlen != 0) { + error("%s: remaining bytes in signature %d", __func__, rlen); + goto out; + } + buffer_free(&b); + SCHNORR_DEBUG_BN((g_v, "%s: g_v = ", __func__)); + SCHNORR_DEBUG_BN((r, "%s: r = ", __func__)); + + /* h = H(g || g^v || g^x || id) */ + if ((h = schnorr_hash(grp_p, grp_q, grp_g, g_v, g_x, + id, idlen)) == NULL) { + error("%s: schnorr_hash failed", __func__); + goto out; + } + + /* g_xh = (g^x)^h */ + if (BN_mod_exp(g_xh, g_x, h, grp_p, bn_ctx) == -1) { + error("%s: BN_mod_exp (g_x^h mod p)", __func__); + goto out; + } + SCHNORR_DEBUG_BN((g_xh, "%s: g_xh = ", __func__)); + + /* g_r = g^r */ + if (BN_mod_exp(g_r, grp_g, r, grp_p, bn_ctx) == -1) { + error("%s: BN_mod_exp (g_x^h mod p)", __func__); + goto out; + } + SCHNORR_DEBUG_BN((g_r, "%s: g_r = ", __func__)); + + /* expected = g^r * g_xh */ + if (BN_mod_mul(expected, g_r, g_xh, grp_p, bn_ctx) == -1) { + error("%s: BN_mod_mul (expected = g_r mod p)", __func__); + goto out; + } + SCHNORR_DEBUG_BN((expected, "%s: expected = ", __func__)); + + /* Check g_v == expected */ + success = BN_cmp(expected, g_v) == 0; + out: + BN_CTX_free(bn_ctx); + if (h != NULL) + BN_clear_free(h); + BN_clear_free(g_v); + BN_clear_free(r); + BN_clear_free(g_xh); + BN_clear_free(g_r); + BN_clear_free(expected); + return success; +} + +#ifdef SCHNORR_MAIN +static void +schnorr_selftest_one(const BIGNUM *grp_p, const BIGNUM *grp_q, + const BIGNUM *grp_g, const BIGNUM *x) +{ + BIGNUM *g_x; + u_char *sig; + u_int siglen; + BN_CTX *bn_ctx; + + if ((bn_ctx = BN_CTX_new()) == NULL) + fatal("%s: BN_CTX_new", __func__); + if ((g_x = BN_new()) == NULL) + fatal("%s: BN_new", __func__); + + if (BN_mod_exp(g_x, grp_g, x, grp_p, bn_ctx) == -1) + fatal("%s: g_x", __func__); + if (schnorr_sign(grp_p, grp_q, grp_g, x, g_x, "junk", 4, &sig, &siglen)) + fatal("%s: schnorr_sign", __func__); + if (schnorr_verify(grp_p, grp_q, grp_g, g_x, "junk", 4, + sig, siglen) != 1) + fatal("%s: verify fail", __func__); + if (schnorr_verify(grp_p, grp_q, grp_g, g_x, "JUNK", 4, + sig, siglen) != 0) + fatal("%s: verify should have failed (bad ID)", __func__); + sig[4] ^= 1; + if (schnorr_verify(grp_p, grp_q, grp_g, g_x, "junk", 4, + sig, siglen) != 0) + fatal("%s: verify should have failed (bit error)", __func__); + xfree(sig); + BN_free(g_x); + BN_CTX_free(bn_ctx); +} + +static void +schnorr_selftest(void) +{ + BIGNUM *x; + struct jpake_group *grp; + u_int i; + char *hh; + + grp = jpake_default_group(); + if ((x = BN_new()) == NULL) + fatal("%s: BN_new", __func__); + SCHNORR_DEBUG_BN((grp->p, "%s: grp->p = ", __func__)); + SCHNORR_DEBUG_BN((grp->q, "%s: grp->q = ", __func__)); + SCHNORR_DEBUG_BN((grp->g, "%s: grp->g = ", __func__)); + + /* [1, 20) */ + for (i = 1; i < 20; i++) { + printf("x = %u\n", i); + fflush(stdout); + if (BN_set_word(x, i) != 1) + fatal("%s: set x word", __func__); + schnorr_selftest_one(grp->p, grp->q, grp->g, x); + } + + /* 100 x random [0, p) */ + for (i = 0; i < 100; i++) { + if (BN_rand_range(x, grp->p) != 1) + fatal("%s: BN_rand_range", __func__); + hh = BN_bn2hex(x); + printf("x = (random) 0x%s\n", hh); + free(hh); + fflush(stdout); + schnorr_selftest_one(grp->p, grp->q, grp->g, x); + } + + /* [q-20, q) */ + if (BN_set_word(x, 20) != 1) + fatal("%s: BN_set_word (x = 20)", __func__); + if (BN_sub(x, grp->q, x) != 1) + fatal("%s: BN_sub (q - x)", __func__); + for (i = 0; i < 19; i++) { + hh = BN_bn2hex(x); + printf("x = (q - %d) 0x%s\n", 20 - i, hh); + free(hh); + fflush(stdout); + schnorr_selftest_one(grp->p, grp->q, grp->g, x); + if (BN_add(x, x, BN_value_one()) != 1) + fatal("%s: BN_add (x + 1)", __func__); + } + BN_free(x); +} + +int +main(int argc, char **argv) +{ + log_init(argv[0], SYSLOG_LEVEL_DEBUG3, SYSLOG_FACILITY_USER, 1); + + schnorr_selftest(); + return 0; +} +#endif + diff --git a/servconf.c b/servconf.c index f2d414334..c7d2d0b94 100644 --- a/servconf.c +++ b/servconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: servconf.c,v 1.189 2008/11/03 08:59:41 djm Exp $ */ +/* $OpenBSD: servconf.c,v 1.190 2008/11/04 08:22:13 djm Exp $ */ /* * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved @@ -127,6 +127,7 @@ initialize_server_options(ServerOptions *options) options->num_permitted_opens = -1; options->adm_forced_command = NULL; options->chroot_directory = NULL; + options->zero_knowledge_password_authentication = -1; } void @@ -258,6 +259,8 @@ fill_default_server_options(ServerOptions *options) options->authorized_keys_file = _PATH_SSH_USER_PERMITTED_KEYS; if (options->permit_tun == -1) options->permit_tun = SSH_TUNMODE_NO; + if (options->zero_knowledge_password_authentication == -1) + options->zero_knowledge_password_authentication = 0; /* Turn privilege separation on by default */ if (use_privsep == -1) @@ -302,6 +305,7 @@ typedef enum { sGssAuthentication, sGssCleanupCreds, sAcceptEnv, sPermitTunnel, sMatch, sPermitOpen, sForceCommand, sChrootDirectory, sUsePrivilegeSeparation, sAllowAgentForwarding, + sZeroKnowledgePasswordAuthentication, sDeprecated, sUnsupported } ServerOpCodes; @@ -368,6 +372,11 @@ static struct { { "kbdinteractiveauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, { "challengeresponseauthentication", sChallengeResponseAuthentication, SSHCFG_GLOBAL }, { "skeyauthentication", sChallengeResponseAuthentication, SSHCFG_GLOBAL }, /* alias */ +#ifdef JPAKE + { "zeroknowledgepasswordauthentication", sZeroKnowledgePasswordAuthentication, SSHCFG_ALL }, +#else + { "zeroknowledgepasswordauthentication", sUnsupported, SSHCFG_ALL }, +#endif { "checkmail", sDeprecated, SSHCFG_GLOBAL }, { "listenaddress", sListenAddress, SSHCFG_GLOBAL }, { "addressfamily", sAddressFamily, SSHCFG_GLOBAL }, @@ -890,6 +899,10 @@ process_server_config_line(ServerOptions *options, char *line, intptr = &options->password_authentication; goto parse_flag; + case sZeroKnowledgePasswordAuthentication: + intptr = &options->zero_knowledge_password_authentication; + goto parse_flag; + case sKbdInteractiveAuthentication: intptr = &options->kbd_interactive_authentication; goto parse_flag; @@ -1377,6 +1390,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth) M_CP_INTOPT(kerberos_authentication); M_CP_INTOPT(hostbased_authentication); M_CP_INTOPT(kbd_interactive_authentication); + M_CP_INTOPT(zero_knowledge_password_authentication); M_CP_INTOPT(permit_root_login); M_CP_INTOPT(permit_empty_passwd); @@ -1578,6 +1592,10 @@ dump_config(ServerOptions *o) #ifdef GSSAPI dump_cfg_fmtint(sGssAuthentication, o->gss_authentication); dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds); +#endif +#ifdef JPAKE + dump_cfg_fmtint(sZeroKnowledgePasswordAuthentication, + o->zero_knowledge_password_authentication); #endif dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication); dump_cfg_fmtint(sKbdInteractiveAuthentication, diff --git a/servconf.h b/servconf.h index 40ac64f13..1d4c3a01a 100644 --- a/servconf.h +++ b/servconf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: servconf.h,v 1.85 2008/06/10 04:50:25 dtucker Exp $ */ +/* $OpenBSD: servconf.h,v 1.86 2008/11/04 08:22:13 djm Exp $ */ /* * Author: Tatu Ylonen @@ -96,6 +96,8 @@ typedef struct { * authentication. */ int kbd_interactive_authentication; /* If true, permit */ int challenge_response_authentication; + int zero_knowledge_password_authentication; + /* If true, permit jpake auth */ int permit_empty_passwd; /* If false, do not permit empty * passwords. */ int permit_user_env; /* If true, read ~/.ssh/environment */ diff --git a/ssh2.h b/ssh2.h index cf56bc4ee..1c33dc268 100644 --- a/ssh2.h +++ b/ssh2.h @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh2.h,v 1.10 2006/03/25 22:22:43 djm Exp $ */ +/* $OpenBSD: ssh2.h,v 1.11 2008/11/04 08:22:13 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. @@ -111,6 +111,12 @@ #define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 #define SSH2_MSG_USERAUTH_INFO_REQUEST 60 #define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 +#define SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP1 60 +#define SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP1 61 +#define SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP2 62 +#define SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP2 63 +#define SSH2_MSG_USERAUTH_JPAKE_CLIENT_CONFIRM 64 +#define SSH2_MSG_USERAUTH_JPAKE_SERVER_CONFIRM 65 /* connection protocol: generic */ @@ -159,3 +165,4 @@ #define SSH2_OPEN_RESOURCE_SHORTAGE 4 #define SSH2_EXTENDED_DATA_STDERR 1 + diff --git a/ssh_config.5 b/ssh_config.5 index 254940ef8..abc3b0b16 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -34,8 +34,8 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: ssh_config.5,v 1.114 2008/10/17 18:36:24 stevesk Exp $ -.Dd $Mdocdate: October 17 2008 $ +.\" $OpenBSD: ssh_config.5,v 1.115 2008/11/04 08:22:13 djm Exp $ +.Dd $Mdocdate: November 4 2008 $ .Dt SSH_CONFIG 5 .Os .Sh NAME @@ -1079,6 +1079,17 @@ Specifies the full pathname of the program. The default is .Pa /usr/X11R6/bin/xauth . +.It Cm ZeroKnowledgePasswordAuthentication +Specifies whether to use zero knowledge password authentication. +This authentication method avoids exposure of password to untrusted +hosts. +The argument to this keyword must be +.Dq yes +or +.Dq no . +The default is currently +.Dq no +as this method is considered experimental. .El .Sh PATTERNS A diff --git a/sshconnect2.c b/sshconnect2.c index 7d0c5e825..a762eec3b 100644 --- a/sshconnect2.c +++ b/sshconnect2.c @@ -1,6 +1,7 @@ -/* $OpenBSD: sshconnect2.c,v 1.169 2008/11/01 04:50:08 djm Exp $ */ +/* $OpenBSD: sshconnect2.c,v 1.170 2008/11/04 08:22:13 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. + * Copyright (c) 2008 Damien Miller. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -67,6 +68,7 @@ #include "msg.h" #include "pathnames.h" #include "uidswap.h" +#include "jpake.h" #ifdef GSSAPI #include "ssh-gss.h" @@ -201,6 +203,7 @@ struct Authctxt { struct Authmethod { char *name; /* string to compare against server's list */ int (*userauth)(Authctxt *authctxt); + void (*cleanup)(Authctxt *authctxt); int *enabled; /* flag in option struct that enables method */ int *batch_flag; /* flag in option struct that disables method */ }; @@ -212,12 +215,18 @@ void input_userauth_error(int, u_int32_t, void *); void input_userauth_info_req(int, u_int32_t, void *); void input_userauth_pk_ok(int, u_int32_t, void *); void input_userauth_passwd_changereq(int, u_int32_t, void *); +void input_userauth_jpake_server_step1(int, u_int32_t, void *); +void input_userauth_jpake_server_step2(int, u_int32_t, void *); +void input_userauth_jpake_server_confirm(int, u_int32_t, void *); int userauth_none(Authctxt *); int userauth_pubkey(Authctxt *); int userauth_passwd(Authctxt *); int userauth_kbdint(Authctxt *); int userauth_hostbased(Authctxt *); +int userauth_jpake(Authctxt *); + +void userauth_jpake_cleanup(Authctxt *); #ifdef GSSAPI int userauth_gssapi(Authctxt *authctxt); @@ -243,30 +252,43 @@ Authmethod authmethods[] = { #ifdef GSSAPI {"gssapi-with-mic", userauth_gssapi, + NULL, &options.gss_authentication, NULL}, #endif {"hostbased", userauth_hostbased, + NULL, &options.hostbased_authentication, NULL}, {"publickey", userauth_pubkey, + NULL, &options.pubkey_authentication, NULL}, +#ifdef JPAKE + {"jpake-01@openssh.com", + userauth_jpake, + userauth_jpake_cleanup, + &options.zero_knowledge_password_authentication, + &options.batch_mode}, +#endif {"keyboard-interactive", userauth_kbdint, + NULL, &options.kbd_interactive_authentication, &options.batch_mode}, {"password", userauth_passwd, + NULL, &options.password_authentication, &options.batch_mode}, {"none", userauth_none, NULL, + NULL, NULL}, - {NULL, NULL, NULL, NULL} + {NULL, NULL, NULL, NULL, NULL} }; void @@ -334,6 +356,9 @@ ssh_userauth2(const char *local_user, const char *server_user, char *host, void userauth(Authctxt *authctxt, char *authlist) { + if (authctxt->method != NULL && authctxt->method->cleanup != NULL) + authctxt->method->cleanup(authctxt); + if (authctxt->methoddata) { xfree(authctxt->methoddata); authctxt->methoddata = NULL; @@ -851,6 +876,209 @@ input_userauth_passwd_changereq(int type, u_int32_t seqnr, void *ctxt) &input_userauth_passwd_changereq); } +#ifdef JPAKE +static char * +pw_encrypt(const char *password, const char *crypt_scheme, const char *salt) +{ + /* OpenBSD crypt(3) handles all of these */ + if (strcmp(crypt_scheme, "crypt") == 0 || + strcmp(crypt_scheme, "bcrypt") == 0 || + strcmp(crypt_scheme, "md5crypt") == 0 || + strcmp(crypt_scheme, "crypt-extended") == 0) + return xstrdup(crypt(password, salt)); + error("%s: unsupported password encryption scheme \"%.100s\"", + __func__, crypt_scheme); + return NULL; +} + +static BIGNUM * +jpake_password_to_secret(Authctxt *authctxt, const char *crypt_scheme, + const char *salt) +{ + char prompt[256], *password, *crypted; + u_char *secret; + u_int secret_len; + BIGNUM *ret; + + snprintf(prompt, sizeof(prompt), "%.30s@%.128s's password (JPAKE): ", + authctxt->server_user, authctxt->host); + password = read_passphrase(prompt, 0); + + if ((crypted = pw_encrypt(password, crypt_scheme, salt)) == NULL) { + logit("Disabling %s authentication", authctxt->method->name); + authctxt->method->enabled = NULL; + /* Continue with an empty password to fail gracefully */ + crypted = xstrdup(""); + } + +#ifdef JPAKE_DEBUG + debug3("%s: salt = %s", __func__, salt); + debug3("%s: scheme = %s", __func__, crypt_scheme); + debug3("%s: crypted = %s", __func__, crypted); +#endif + + if (hash_buffer(crypted, strlen(crypted), EVP_sha256(), + &secret, &secret_len) != 0) + fatal("%s: hash_buffer", __func__); + + bzero(password, strlen(password)); + bzero(crypted, strlen(crypted)); + xfree(password); + xfree(crypted); + + if ((ret = BN_bin2bn(secret, secret_len, NULL)) == NULL) + fatal("%s: BN_bin2bn (secret)", __func__); + bzero(secret, secret_len); + xfree(secret); + + return ret; +} + +/* ARGSUSED */ +void +input_userauth_jpake_server_step1(int type, u_int32_t seq, void *ctxt) +{ + Authctxt *authctxt = ctxt; + struct jpake_ctx *pctx = authctxt->methoddata; + u_char *x3_proof, *x4_proof, *x2_s_proof; + u_int x3_proof_len, x4_proof_len, x2_s_proof_len; + char *crypt_scheme, *salt; + + /* Disable this message */ + dispatch_set(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP1, NULL); + + if ((pctx->g_x3 = BN_new()) == NULL || + (pctx->g_x4 = BN_new()) == NULL) + fatal("%s: BN_new", __func__); + + /* Fetch step 1 values */ + crypt_scheme = packet_get_string(NULL); + salt = packet_get_string(NULL); + pctx->server_id = packet_get_string(&pctx->server_id_len); + packet_get_bignum2(pctx->g_x3); + packet_get_bignum2(pctx->g_x4); + x3_proof = packet_get_string(&x3_proof_len); + x4_proof = packet_get_string(&x4_proof_len); + packet_check_eom(); + + JPAKE_DEBUG_CTX((pctx, "step 1 received in %s", __func__)); + + /* Obtain password and derive secret */ + pctx->s = jpake_password_to_secret(authctxt, crypt_scheme, salt); + bzero(crypt_scheme, strlen(crypt_scheme)); + bzero(salt, strlen(salt)); + xfree(crypt_scheme); + xfree(salt); + JPAKE_DEBUG_BN((pctx->s, "%s: s = ", __func__)); + + /* Calculate step 2 values */ + jpake_step2(pctx->grp, pctx->s, pctx->g_x1, + pctx->g_x3, pctx->g_x4, pctx->x2, + pctx->server_id, pctx->server_id_len, + pctx->client_id, pctx->client_id_len, + x3_proof, x3_proof_len, + x4_proof, x4_proof_len, + &pctx->a, + &x2_s_proof, &x2_s_proof_len); + + bzero(x3_proof, x3_proof_len); + bzero(x4_proof, x4_proof_len); + xfree(x3_proof); + xfree(x4_proof); + + JPAKE_DEBUG_CTX((pctx, "step 2 sending in %s", __func__)); + + /* Send values for step 2 */ + packet_start(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP2); + packet_put_bignum2(pctx->a); + packet_put_string(x2_s_proof, x2_s_proof_len); + packet_send(); + + bzero(x2_s_proof, x2_s_proof_len); + xfree(x2_s_proof); + + /* Expect step 2 packet from peer */ + dispatch_set(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP2, + input_userauth_jpake_server_step2); +} + +/* ARGSUSED */ +void +input_userauth_jpake_server_step2(int type, u_int32_t seq, void *ctxt) +{ + Authctxt *authctxt = ctxt; + struct jpake_ctx *pctx = authctxt->methoddata; + u_char *x4_s_proof; + u_int x4_s_proof_len; + + /* Disable this message */ + dispatch_set(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP2, NULL); + + if ((pctx->b = BN_new()) == NULL) + fatal("%s: BN_new", __func__); + + /* Fetch step 2 values */ + packet_get_bignum2(pctx->b); + x4_s_proof = packet_get_string(&x4_s_proof_len); + packet_check_eom(); + + JPAKE_DEBUG_CTX((pctx, "step 2 received in %s", __func__)); + + /* Derive shared key and calculate confirmation hash */ + jpake_key_confirm(pctx->grp, pctx->s, pctx->b, + pctx->x2, pctx->g_x1, pctx->g_x2, pctx->g_x3, pctx->g_x4, + pctx->client_id, pctx->client_id_len, + pctx->server_id, pctx->server_id_len, + session_id2, session_id2_len, + x4_s_proof, x4_s_proof_len, + &pctx->k, + &pctx->h_k_cid_sessid, &pctx->h_k_cid_sessid_len); + + bzero(x4_s_proof, x4_s_proof_len); + xfree(x4_s_proof); + + JPAKE_DEBUG_CTX((pctx, "confirm sending in %s", __func__)); + + /* Send key confirmation proof */ + packet_start(SSH2_MSG_USERAUTH_JPAKE_CLIENT_CONFIRM); + packet_put_string(pctx->h_k_cid_sessid, pctx->h_k_cid_sessid_len); + packet_send(); + + /* Expect confirmation from peer */ + dispatch_set(SSH2_MSG_USERAUTH_JPAKE_SERVER_CONFIRM, + input_userauth_jpake_server_confirm); +} + +/* ARGSUSED */ +void +input_userauth_jpake_server_confirm(int type, u_int32_t seq, void *ctxt) +{ + Authctxt *authctxt = ctxt; + struct jpake_ctx *pctx = authctxt->methoddata; + + /* Disable this message */ + dispatch_set(SSH2_MSG_USERAUTH_JPAKE_SERVER_CONFIRM, NULL); + + pctx->h_k_sid_sessid = packet_get_string(&pctx->h_k_sid_sessid_len); + packet_check_eom(); + + JPAKE_DEBUG_CTX((pctx, "confirm received in %s", __func__)); + + /* Verify expected confirmation hash */ + if (jpake_check_confirm(pctx->k, + pctx->server_id, pctx->server_id_len, + session_id2, session_id2_len, + pctx->h_k_sid_sessid, pctx->h_k_sid_sessid_len) == 1) + debug("%s: %s success", __func__, authctxt->method->name); + else { + debug("%s: confirmation mismatch", __func__); + /* XXX stash this so if auth succeeds then we can warn/kill */ + } + + userauth_jpake_cleanup(authctxt); +} +#endif /* JPAKE */ + static int identity_sign(Identity *id, u_char **sigp, u_int *lenp, u_char *data, u_int datalen) @@ -1425,6 +1653,76 @@ userauth_hostbased(Authctxt *authctxt) return 1; } +#ifdef JPAKE +int +userauth_jpake(Authctxt *authctxt) +{ + struct jpake_ctx *pctx; + u_char *x1_proof, *x2_proof; + u_int x1_proof_len, x2_proof_len; + static int attempt = 0; /* XXX share with userauth_password's? */ + + if (attempt++ >= options.number_of_password_prompts) + return 0; + if (attempt != 1) + error("Permission denied, please try again."); + + if (authctxt->methoddata != NULL) + fatal("%s: authctxt->methoddata already set (%p)", + __func__, authctxt->methoddata); + + authctxt->methoddata = pctx = jpake_new(); + + /* + * Send request immediately, to get the protocol going while + * we do the initial computations. + */ + packet_start(SSH2_MSG_USERAUTH_REQUEST); + packet_put_cstring(authctxt->server_user); + packet_put_cstring(authctxt->service); + packet_put_cstring(authctxt->method->name); + packet_send(); + packet_write_wait(); + + jpake_step1(pctx->grp, + &pctx->client_id, &pctx->client_id_len, + &pctx->x1, &pctx->x2, &pctx->g_x1, &pctx->g_x2, + &x1_proof, &x1_proof_len, + &x2_proof, &x2_proof_len); + + JPAKE_DEBUG_CTX((pctx, "step 1 sending in %s", __func__)); + + packet_start(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP1); + packet_put_string(pctx->client_id, pctx->client_id_len); + packet_put_bignum2(pctx->g_x1); + packet_put_bignum2(pctx->g_x2); + packet_put_string(x1_proof, x1_proof_len); + packet_put_string(x2_proof, x2_proof_len); + packet_send(); + + bzero(x1_proof, x1_proof_len); + bzero(x2_proof, x2_proof_len); + xfree(x1_proof); + xfree(x2_proof); + + /* Expect step 1 packet from peer */ + dispatch_set(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP1, + input_userauth_jpake_server_step1); + + return 1; +} + +void +userauth_jpake_cleanup(Authctxt *authctxt) +{ + debug3("%s: clean up", __func__); + if (authctxt->methoddata != NULL) { + jpake_free(authctxt->methoddata); + authctxt->methoddata = NULL; + } +} +#endif /* JPAKE */ + /* find auth method */ /* @@ -1526,3 +1824,4 @@ authmethods_get(void) buffer_free(&b); return list; } + diff --git a/sshd_config.5 b/sshd_config.5 index 06fe5fd38..a4a4be6e5 100644 --- a/sshd_config.5 +++ b/sshd_config.5 @@ -34,8 +34,8 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: sshd_config.5,v 1.97 2008/10/09 03:50:54 djm Exp $ -.Dd $Mdocdate: October 9 2008 $ +.\" $OpenBSD: sshd_config.5,v 1.98 2008/11/04 08:22:13 djm Exp $ +.Dd $Mdocdate: November 4 2008 $ .Dt SSHD_CONFIG 5 .Os .Sh NAME @@ -612,8 +612,9 @@ Available keywords are .Cm RSAAuthentication , .Cm X11DisplayOffset , .Cm X11Forwarding , +.Cm X11UseLocalHost , and -.Cm X11UseLocalHost . +.Cm ZeroKnowledgePasswordAuthentication . .It Cm MaxAuthTries Specifies the maximum number of authentication attempts permitted per connection. @@ -1004,6 +1005,17 @@ Specifies the full pathname of the program. The default is .Pa /usr/X11R6/bin/xauth . +.It Cm ZeroKnowledgePasswordAuthentication +Specifies whether to use zero knowledge password authentication. +This authentication method avoids exposure of password to untrusted +hosts. +The argument to this keyword must be +.Dq yes +or +.Dq no . +The default is currently +.Dq no +as this method is considered experimental. .El .Sh TIME FORMATS .Xr sshd 8 -- cgit v1.2.3 From 1a0442fce894dc09c50d882d9d19f8ed95977a3f Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Wed, 5 Nov 2008 16:30:06 +1100 Subject: - stevesk@cvs.openbsd.org 2008/11/04 19:18:00 [readconf.c] because parse_forward() is now used to parse all forward types (DLR), and it malloc's space for host variables, we don't need to malloc here. fixes small memory leaks. previously dynamic forwards were not parsed in parse_forward() and space was not malloc'd in that case. ok djm@ --- ChangeLog | 12 +++++++++++- readconf.c | 12 +++++------- 2 files changed, 16 insertions(+), 8 deletions(-) (limited to 'readconf.c') diff --git a/ChangeLog b/ChangeLog index 012749b0d..d49180e6e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -26,6 +26,16 @@ compiled-time disabled (turn on -DJPAKE in Makefile.inc). "just commit it. It isn't too intrusive." deraadt@ + - stevesk@cvs.openbsd.org 2008/11/04 19:18:00 + [readconf.c] + because parse_forward() is now used to parse all forward types (DLR), + and it malloc's space for host variables, we don't need to malloc + here. fixes small memory leaks. + + previously dynamic forwards were not parsed in parse_forward() and + space was not malloc'd in that case. + + ok djm@ 20081103 - OpenBSD CVS Sync @@ -4876,4 +4886,4 @@ OpenServer 6 and add osr5bigcrypt support so when someone migrates passwords between UnixWare and OpenServer they will still work. OK dtucker@ -$Id: ChangeLog,v 1.5130 2008/11/05 05:20:46 djm Exp $ +$Id: ChangeLog,v 1.5131 2008/11/05 05:30:06 djm Exp $ diff --git a/readconf.c b/readconf.c index ba70d9da0..d1ffd84a8 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.171 2008/11/04 08:22:13 djm Exp $ */ +/* $OpenBSD: readconf.c,v 1.172 2008/11/04 19:18:00 stevesk Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -256,10 +256,9 @@ add_local_forward(Options *options, const Forward *newfwd) fatal("Too many local forwards (max %d).", SSH_MAX_FORWARDS_PER_DIRECTION); fwd = &options->local_forwards[options->num_local_forwards++]; - fwd->listen_host = (newfwd->listen_host == NULL) ? - NULL : xstrdup(newfwd->listen_host); + fwd->listen_host = newfwd->listen_host; fwd->listen_port = newfwd->listen_port; - fwd->connect_host = xstrdup(newfwd->connect_host); + fwd->connect_host = newfwd->connect_host; fwd->connect_port = newfwd->connect_port; } @@ -277,10 +276,9 @@ add_remote_forward(Options *options, const Forward *newfwd) SSH_MAX_FORWARDS_PER_DIRECTION); fwd = &options->remote_forwards[options->num_remote_forwards++]; - fwd->listen_host = (newfwd->listen_host == NULL) ? - NULL : xstrdup(newfwd->listen_host); + fwd->listen_host = newfwd->listen_host; fwd->listen_port = newfwd->listen_port; - fwd->connect_host = xstrdup(newfwd->connect_host); + fwd->connect_host = newfwd->connect_host; fwd->connect_port = newfwd->connect_port; } -- cgit v1.2.3 From 0d772d9d11ceedd9432383019a75859813149673 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Tue, 9 Dec 2008 14:12:05 +1100 Subject: - djm@cvs.openbsd.org 2008/12/09 02:58:16 [readconf.c] don't leave junk (free'd) pointers around in Forward *fwd argument on failure; avoids double-free in ~C -L handler when given an invalid forwarding specification; bz#1539 report from adejong AT debian.org via Colin Watson; ok markus@ dtucker@ --- ChangeLog | 8 +++++++- readconf.c | 10 +++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) (limited to 'readconf.c') diff --git a/ChangeLog b/ChangeLog index 2ea6a05da..ab50a006a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,12 @@ Deal correctly with failures in remote stat() operation in sftp, correcting fail-on-error behaviour in batchmode. bz#1541 report and fix from anedvedicky AT gmail.com; ok markus@ + - djm@cvs.openbsd.org 2008/12/09 02:58:16 + [readconf.c] + don't leave junk (free'd) pointers around in Forward *fwd argument on + failure; avoids double-free in ~C -L handler when given an invalid + forwarding specification; bz#1539 report from adejong AT debian.org + via Colin Watson; ok markus@ dtucker@ 20081208 - (djm) [configure.ac] bz#1538: better test for ProPolice/SSP: actually @@ -4979,5 +4985,5 @@ OpenServer 6 and add osr5bigcrypt support so when someone migrates passwords between UnixWare and OpenServer they will still work. OK dtucker@ -$Id: ChangeLog,v 1.5153 2008/12/09 03:11:49 djm Exp $ +$Id: ChangeLog,v 1.5154 2008/12/09 03:12:05 djm Exp $ diff --git a/readconf.c b/readconf.c index d1ffd84a8..ab89e0c05 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.172 2008/11/04 19:18:00 stevesk Exp $ */ +/* $OpenBSD: readconf.c,v 1.173 2008/12/09 02:58:16 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1293,9 +1293,13 @@ parse_forward(Forward *fwd, const char *fwdspec, int dynamicfwd) return (i); fail_free: - if (fwd->connect_host != NULL) + if (fwd->connect_host != NULL) { xfree(fwd->connect_host); - if (fwd->listen_host != NULL) + fwd->connect_host = NULL; + } + if (fwd->listen_host != NULL) { xfree(fwd->listen_host); + fwd->listen_host = NULL; + } return (0); } -- cgit v1.2.3 From 5bc6aae3dbeda2e6b23361238b16fbadbfcd2ce5 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Wed, 28 Jan 2009 16:27:31 +1100 Subject: - stevesk@cvs.openbsd.org 2009/01/15 17:38:43 [readconf.c] 1) use obsolete instead of alias for consistency 2) oUserKnownHostsFile not obsolete but oGlobalKnownHostsFile2 is so move the comment. 3) reorder so like options are together ok djm@ --- ChangeLog | 9 ++++++++- readconf.c | 8 ++++---- 2 files changed, 12 insertions(+), 5 deletions(-) (limited to 'readconf.c') diff --git a/ChangeLog b/ChangeLog index 332dacce3..50cf16d5f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -46,6 +46,13 @@ [channels.c] support SOCKS4A protocol, from dwmw2 AT infradead.org via bz#1482; "looks ok" markus@ + - stevesk@cvs.openbsd.org 2009/01/15 17:38:43 + [readconf.c] + 1) use obsolete instead of alias for consistency + 2) oUserKnownHostsFile not obsolete but oGlobalKnownHostsFile2 is + so move the comment. + 3) reorder so like options are together + ok djm@ 20090107 - (djm) [uidswap.c] bz#1412: Support >16 supplemental groups in OS X. @@ -5055,5 +5062,5 @@ OpenServer 6 and add osr5bigcrypt support so when someone migrates passwords between UnixWare and OpenServer they will still work. OK dtucker@ -$Id: ChangeLog,v 1.5171 2009/01/28 05:24:41 djm Exp $ +$Id: ChangeLog,v 1.5172 2009/01/28 05:27:31 djm Exp $ diff --git a/readconf.c b/readconf.c index ab89e0c05..f63a00c47 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.173 2008/12/09 02:58:16 djm Exp $ */ +/* $OpenBSD: readconf.c,v 1.174 2009/01/15 17:38:43 stevesk Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -172,7 +172,7 @@ static struct { { "fallbacktorsh", oDeprecated }, { "usersh", oDeprecated }, { "identityfile", oIdentityFile }, - { "identityfile2", oIdentityFile }, /* alias */ + { "identityfile2", oIdentityFile }, /* obsolete */ { "identitiesonly", oIdentitiesOnly }, { "hostname", oHostName }, { "hostkeyalias", oHostKeyAlias }, @@ -188,8 +188,8 @@ static struct { { "host", oHost }, { "escapechar", oEscapeChar }, { "globalknownhostsfile", oGlobalKnownHostsFile }, - { "userknownhostsfile", oUserKnownHostsFile }, /* obsolete */ - { "globalknownhostsfile2", oGlobalKnownHostsFile2 }, + { "globalknownhostsfile2", oGlobalKnownHostsFile2 }, /* obsolete */ + { "userknownhostsfile", oUserKnownHostsFile }, { "userknownhostsfile2", oUserKnownHostsFile2 }, /* obsolete */ { "connectionattempts", oConnectionAttempts }, { "batchmode", oBatchMode }, -- cgit v1.2.3 From 3dc71ad8653bab5591fc75bb1d3e6aa8fb9360df Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Wed, 28 Jan 2009 16:31:22 +1100 Subject: - djm@cvs.openbsd.org 2009/01/22 10:02:34 [clientloop.c misc.c readconf.c readconf.h servconf.c servconf.h] [serverloop.c ssh-keyscan.c ssh.c sshd.c] make a2port() return -1 when it encounters an invalid port number rather than 0, which it will now treat as valid (needed for future work) adjust current consumers of a2port() to check its return value is <= 0, which in turn required some things to be converted from u_short => int make use of int vs. u_short consistent in some other places too feedback & ok markus@ --- ChangeLog | 11 ++++++++++- clientloop.c | 10 +++++----- misc.c | 22 +++++++++------------- readconf.c | 6 +++--- readconf.h | 6 +++--- servconf.c | 20 ++++++++++---------- servconf.h | 8 ++++---- serverloop.c | 4 ++-- ssh-keyscan.c | 4 ++-- ssh.c | 4 ++-- sshd.c | 4 ++-- 11 files changed, 52 insertions(+), 47 deletions(-) (limited to 'readconf.c') diff --git a/ChangeLog b/ChangeLog index 6b109e5be..2b371d11e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -61,6 +61,15 @@ [channels.c] oops! I committed the wrong version of the Channel->path diff, it was missing some tweaks suggested by stevesk@ + - djm@cvs.openbsd.org 2009/01/22 10:02:34 + [clientloop.c misc.c readconf.c readconf.h servconf.c servconf.h] + [serverloop.c ssh-keyscan.c ssh.c sshd.c] + make a2port() return -1 when it encounters an invalid port number + rather than 0, which it will now treat as valid (needed for future work) + adjust current consumers of a2port() to check its return value is <= 0, + which in turn required some things to be converted from u_short => int + make use of int vs. u_short consistent in some other places too + feedback & ok markus@ 20090107 - (djm) [uidswap.c] bz#1412: Support >16 supplemental groups in OS X. @@ -5070,5 +5079,5 @@ OpenServer 6 and add osr5bigcrypt support so when someone migrates passwords between UnixWare and OpenServer they will still work. OK dtucker@ -$Id: ChangeLog,v 1.5174 2009/01/28 05:30:33 djm Exp $ +$Id: ChangeLog,v 1.5175 2009/01/28 05:31:22 djm Exp $ diff --git a/clientloop.c b/clientloop.c index fdeedc351..1b5badb71 100644 --- a/clientloop.c +++ b/clientloop.c @@ -1,4 +1,4 @@ -/* $OpenBSD: clientloop.c,v 1.207 2008/12/09 22:37:33 stevesk Exp $ */ +/* $OpenBSD: clientloop.c,v 1.208 2009/01/22 10:02:34 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -765,7 +765,7 @@ process_cmdline(void) char *s, *cmd, *cancel_host; int delete = 0; int local = 0, remote = 0, dynamic = 0; - u_short cancel_port; + int cancel_port; Forward fwd; bzero(&fwd, sizeof(fwd)); @@ -843,7 +843,7 @@ process_cmdline(void) cancel_port = a2port(cancel_host); cancel_host = NULL; } - if (cancel_port == 0) { + if (cancel_port <= 0) { logit("Bad forwarding close port"); goto out; } @@ -1638,7 +1638,7 @@ client_request_forwarded_tcpip(const char *request_type, int rchan) { Channel *c = NULL; char *listen_address, *originator_address; - int listen_port, originator_port; + u_short listen_port, originator_port; /* Get rest of the packet */ listen_address = packet_get_string(NULL); @@ -1664,7 +1664,7 @@ client_request_x11(const char *request_type, int rchan) { Channel *c = NULL; char *originator; - int originator_port; + u_short originator_port; int sock; if (!options.forward_x11) { diff --git a/misc.c b/misc.c index 8b303f16f..755eda105 100644 --- a/misc.c +++ b/misc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.c,v 1.69 2008/06/13 01:38:23 dtucker Exp $ */ +/* $OpenBSD: misc.c,v 1.70 2009/01/22 10:02:34 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2005,2006 Damien Miller. All rights reserved. @@ -221,23 +221,19 @@ pwcopy(struct passwd *pw) /* * Convert ASCII string to TCP/IP port number. - * Port must be >0 and <=65535. - * Return 0 if invalid. + * Port must be >=0 and <=65535. + * Return -1 if invalid. */ int a2port(const char *s) { - long port; - char *endp; - - errno = 0; - port = strtol(s, &endp, 0); - if (s == endp || *endp != '\0' || - (errno == ERANGE && (port == LONG_MIN || port == LONG_MAX)) || - port <= 0 || port > 65535) - return 0; + long long port; + const char *errstr; - return port; + port = strtonum(s, 0, 65535, &errstr); + if (errstr != NULL) + return -1; + return (int)port; } int diff --git a/readconf.c b/readconf.c index f63a00c47..0a8be1400 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.174 2009/01/15 17:38:43 stevesk Exp $ */ +/* $OpenBSD: readconf.c,v 1.175 2009/01/22 10:02:34 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1279,11 +1279,11 @@ parse_forward(Forward *fwd, const char *fwdspec, int dynamicfwd) } else { if (!(i == 3 || i == 4)) goto fail_free; - if (fwd->connect_port == 0) + if (fwd->connect_port <= 0) goto fail_free; } - if (fwd->listen_port == 0) + if (fwd->listen_port <= 0) goto fail_free; if (fwd->connect_host != NULL && diff --git a/readconf.h b/readconf.h index c9e5f6a41..d94d65890 100644 --- a/readconf.h +++ b/readconf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.h,v 1.76 2008/11/04 08:22:13 djm Exp $ */ +/* $OpenBSD: readconf.h,v 1.77 2009/01/22 10:02:34 djm Exp $ */ /* * Author: Tatu Ylonen @@ -20,9 +20,9 @@ typedef struct { char *listen_host; /* Host (address) to listen on. */ - u_short listen_port; /* Port to forward. */ + int listen_port; /* Port to forward. */ char *connect_host; /* Host to connect. */ - u_short connect_port; /* Port to connect on connect_host. */ + int connect_port; /* Port to connect on connect_host. */ } Forward; /* Data structure for representing option data. */ diff --git a/servconf.c b/servconf.c index 7d8851860..e7fc2a781 100644 --- a/servconf.c +++ b/servconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: servconf.c,v 1.193 2008/12/09 03:20:42 stevesk Exp $ */ +/* $OpenBSD: servconf.c,v 1.194 2009/01/22 10:02:34 djm Exp $ */ /* * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved @@ -42,8 +42,8 @@ #include "channels.h" #include "groupaccess.h" -static void add_listen_addr(ServerOptions *, char *, u_short); -static void add_one_listen_addr(ServerOptions *, char *, u_short); +static void add_listen_addr(ServerOptions *, char *, int); +static void add_one_listen_addr(ServerOptions *, char *, int); /* Use of privilege separation or not */ extern int use_privsep; @@ -460,7 +460,7 @@ parse_token(const char *cp, const char *filename, } static void -add_listen_addr(ServerOptions *options, char *addr, u_short port) +add_listen_addr(ServerOptions *options, char *addr, int port) { u_int i; @@ -476,7 +476,7 @@ add_listen_addr(ServerOptions *options, char *addr, u_short port) } static void -add_one_listen_addr(ServerOptions *options, char *addr, u_short port) +add_one_listen_addr(ServerOptions *options, char *addr, int port) { struct addrinfo hints, *ai, *aitop; char strport[NI_MAXSERV]; @@ -486,7 +486,7 @@ add_one_listen_addr(ServerOptions *options, char *addr, u_short port) hints.ai_family = options->address_family; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = (addr == NULL) ? AI_PASSIVE : 0; - snprintf(strport, sizeof strport, "%u", port); + snprintf(strport, sizeof strport, "%d", port); if ((gaierr = getaddrinfo(addr, strport, &hints, &aitop)) != 0) fatal("bad addr or host: %s (%s)", addr ? addr : "", @@ -642,7 +642,7 @@ process_server_config_line(ServerOptions *options, char *line, SyslogFacility *log_facility_ptr; LogLevel *log_level_ptr; ServerOpCodes opcode; - u_short port; + int port; u_int i, flags = 0; size_t len; @@ -699,7 +699,7 @@ process_server_config_line(ServerOptions *options, char *line, fatal("%s line %d: missing port number.", filename, linenum); options->ports[options->num_ports++] = a2port(arg); - if (options->ports[options->num_ports-1] == 0) + if (options->ports[options->num_ports-1] <= 0) fatal("%s line %d: Badly formatted port number.", filename, linenum); break; @@ -752,7 +752,7 @@ process_server_config_line(ServerOptions *options, char *line, p = cleanhostname(p); if (arg == NULL) port = 0; - else if ((port = a2port(arg)) == 0) + else if ((port = a2port(arg)) <= 0) fatal("%s line %d: bad port number", filename, linenum); add_listen_addr(options, p, port); @@ -1265,7 +1265,7 @@ process_server_config_line(ServerOptions *options, char *line, fatal("%s line %d: missing host in PermitOpen", filename, linenum); p = cleanhostname(p); - if (arg == NULL || (port = a2port(arg)) == 0) + if (arg == NULL || (port = a2port(arg)) <= 0) fatal("%s line %d: bad port number in " "PermitOpen", filename, linenum); if (*activep && n == -1) diff --git a/servconf.h b/servconf.h index 1d4c3a01a..b3ac7da4b 100644 --- a/servconf.h +++ b/servconf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: servconf.h,v 1.86 2008/11/04 08:22:13 djm Exp $ */ +/* $OpenBSD: servconf.h,v 1.87 2009/01/22 10:02:34 djm Exp $ */ /* * Author: Tatu Ylonen @@ -41,9 +41,9 @@ #define INTERNAL_SFTP_NAME "internal-sftp" typedef struct { - u_int num_ports; - u_int ports_from_cmdline; - u_short ports[MAX_PORTS]; /* Port number to listen on. */ + u_int num_ports; + u_int ports_from_cmdline; + int ports[MAX_PORTS]; /* Port number to listen on. */ char *listen_addr; /* Address on which the server listens. */ struct addrinfo *listen_addrs; /* Addresses on which the server listens. */ int address_family; /* Address family used by the server. */ diff --git a/serverloop.c b/serverloop.c index 6a3ae1665..931779e30 100644 --- a/serverloop.c +++ b/serverloop.c @@ -1,4 +1,4 @@ -/* $OpenBSD: serverloop.c,v 1.154 2008/12/02 19:08:59 markus Exp $ */ +/* $OpenBSD: serverloop.c,v 1.155 2009/01/22 10:02:34 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -942,7 +942,7 @@ server_request_direct_tcpip(void) { Channel *c; char *target, *originator; - int target_port, originator_port; + u_short target_port, originator_port; target = packet_get_string(NULL); target_port = packet_get_int(); diff --git a/ssh-keyscan.c b/ssh-keyscan.c index c6ec3507e..9a91be499 100644 --- a/ssh-keyscan.c +++ b/ssh-keyscan.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-keyscan.c,v 1.77 2008/11/01 11:14:36 sobrado Exp $ */ +/* $OpenBSD: ssh-keyscan.c,v 1.78 2009/01/22 10:02:34 djm Exp $ */ /* * Copyright 1995, 1996 by David Mazieres . * @@ -748,7 +748,7 @@ main(int argc, char **argv) break; case 'p': ssh_port = a2port(optarg); - if (ssh_port == 0) { + if (ssh_port <= 0) { fprintf(stderr, "Bad port '%s'\n", optarg); exit(1); } diff --git a/ssh.c b/ssh.c index 5bb67c5b1..26f070f3e 100644 --- a/ssh.c +++ b/ssh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh.c,v 1.322 2008/11/01 17:40:33 stevesk Exp $ */ +/* $OpenBSD: ssh.c,v 1.323 2009/01/22 10:02:34 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -443,7 +443,7 @@ main(int ac, char **av) break; case 'p': options.port = a2port(optarg); - if (options.port == 0) { + if (options.port <= 0) { fprintf(stderr, "Bad port '%s'\n", optarg); exit(255); } diff --git a/sshd.c b/sshd.c index fa314b8a7..3b5cd3cfd 100644 --- a/sshd.c +++ b/sshd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshd.c,v 1.365 2008/10/30 19:31:16 stevesk Exp $ */ +/* $OpenBSD: sshd.c,v 1.366 2009/01/22 10:02:34 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1333,7 +1333,7 @@ main(int ac, char **av) exit(1); } options.ports[options.num_ports++] = a2port(optarg); - if (options.ports[options.num_ports-1] == 0) { + if (options.ports[options.num_ports-1] <= 0) { fprintf(stderr, "Bad port number.\n"); exit(1); } -- cgit v1.2.3 From 4bf648f7766ba764d7a78b1dbb26df4f0d42a8c9 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Sat, 14 Feb 2009 16:28:21 +1100 Subject: - djm@cvs.openbsd.org 2009/02/12 03:00:56 [canohost.c canohost.h channels.c channels.h clientloop.c readconf.c] [readconf.h serverloop.c ssh.c] support remote port forwarding with a zero listen port (-R0:...) to dyamically allocate a listen port at runtime (this is actually specified in rfc4254); bz#1003 ok markus@ --- ChangeLog | 8 +++++++- canohost.c | 4 ++-- canohost.h | 4 +++- channels.c | 52 ++++++++++++++++++++++++++++++++++++++++++++-------- channels.h | 4 ++-- clientloop.c | 4 ++-- readconf.c | 13 +++++++++---- readconf.h | 4 ++-- serverloop.c | 9 ++++++--- ssh.c | 15 +++++++++++---- 10 files changed, 88 insertions(+), 29 deletions(-) (limited to 'readconf.c') diff --git a/ChangeLog b/ChangeLog index cdcd1aced..d8f8f2610 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,6 +4,12 @@ [sftp.c] Initialize a few variables to prevent spurious "may be used uninitialized" warnings from newer gcc's. ok djm@ + - djm@cvs.openbsd.org 2009/02/12 03:00:56 + [canohost.c canohost.h channels.c channels.h clientloop.c readconf.c] + [readconf.h serverloop.c ssh.c] + support remote port forwarding with a zero listen port (-R0:...) to + dyamically allocate a listen port at runtime (this is actually + specified in rfc4254); bz#1003 ok markus@ 20090212 - (djm) [sshpty.c] bz#1419: OSX uses cloning ptys that automagically @@ -5130,5 +5136,5 @@ OpenServer 6 and add osr5bigcrypt support so when someone migrates passwords between UnixWare and OpenServer they will still work. OK dtucker@ -$Id: ChangeLog,v 1.5186 2009/02/14 05:26:19 djm Exp $ +$Id: ChangeLog,v 1.5187 2009/02/14 05:28:21 djm Exp $ diff --git a/canohost.c b/canohost.c index 42011fd0a..7138f48d0 100644 --- a/canohost.c +++ b/canohost.c @@ -1,4 +1,4 @@ -/* $OpenBSD: canohost.c,v 1.63 2008/06/12 00:03:49 dtucker Exp $ */ +/* $OpenBSD: canohost.c,v 1.64 2009/02/12 03:00:56 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -342,7 +342,7 @@ get_remote_name_or_ip(u_int utmp_len, int use_dns) /* Returns the local/remote port for the socket. */ -static int +int get_sock_port(int sock, int local) { struct sockaddr_storage from; diff --git a/canohost.h b/canohost.h index e33e8941b..d9b41ffe5 100644 --- a/canohost.h +++ b/canohost.h @@ -1,4 +1,4 @@ -/* $OpenBSD: canohost.h,v 1.9 2006/03/25 22:22:42 djm Exp $ */ +/* $OpenBSD: canohost.h,v 1.10 2009/02/12 03:00:56 djm Exp $ */ /* * Author: Tatu Ylonen @@ -23,5 +23,7 @@ char *get_local_name(int); int get_remote_port(void); int get_local_port(void); +int get_sock_port(int, int); + void ipv64_normalise_mapped(struct sockaddr_storage *, socklen_t *); diff --git a/channels.c b/channels.c index 0b1c34c83..dea60ba24 100644 --- a/channels.c +++ b/channels.c @@ -1,4 +1,4 @@ -/* $OpenBSD: channels.c,v 1.294 2009/01/22 09:49:57 djm Exp $ */ +/* $OpenBSD: channels.c,v 1.295 2009/02/12 03:00:56 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -2460,7 +2460,8 @@ channel_set_af(int af) } static int -channel_setup_fwd_listener(int type, const char *listen_addr, u_short listen_port, +channel_setup_fwd_listener(int type, const char *listen_addr, + u_short listen_port, int *allocated_listen_port, const char *host_to_connect, u_short port_to_connect, int gateway_ports) { Channel *c; @@ -2468,6 +2469,7 @@ channel_setup_fwd_listener(int type, const char *listen_addr, u_short listen_por struct addrinfo hints, *ai, *aitop; const char *host, *addr; char ntop[NI_MAXHOST], strport[NI_MAXSERV]; + in_port_t *lport_p; host = (type == SSH_CHANNEL_RPORT_LISTENER) ? listen_addr : host_to_connect; @@ -2536,10 +2538,29 @@ channel_setup_fwd_listener(int type, const char *listen_addr, u_short listen_por } return 0; } - + if (allocated_listen_port != NULL) + *allocated_listen_port = 0; for (ai = aitop; ai; ai = ai->ai_next) { - if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) + switch (ai->ai_family) { + case AF_INET: + lport_p = &((struct sockaddr_in *)ai->ai_addr)-> + sin_port; + break; + case AF_INET6: + lport_p = &((struct sockaddr_in6 *)ai->ai_addr)-> + sin6_port; + break; + default: continue; + } + /* + * If allocating a port for -R forwards, then use the + * same port for all address families. + */ + if (type == SSH_CHANNEL_RPORT_LISTENER && listen_port == 0 && + allocated_listen_port != NULL && *allocated_listen_port > 0) + *lport_p = htons(*allocated_listen_port); + if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) { error("channel_setup_fwd_listener: getnameinfo failed"); @@ -2555,7 +2576,8 @@ channel_setup_fwd_listener(int type, const char *listen_addr, u_short listen_por channel_set_reuseaddr(sock); - debug("Local forwarding listening on %s port %s.", ntop, strport); + debug("Local forwarding listening on %s port %s.", + ntop, strport); /* Bind the socket to the address. */ if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) { @@ -2574,6 +2596,19 @@ channel_setup_fwd_listener(int type, const char *listen_addr, u_short listen_por close(sock); continue; } + + /* + * listen_port == 0 requests a dynamically allocated port - + * record what we got. + */ + if (type == SSH_CHANNEL_RPORT_LISTENER && listen_port == 0 && + allocated_listen_port != NULL && + *allocated_listen_port == 0) { + *allocated_listen_port = get_sock_port(sock, 1); + debug("Allocated listen port %d", + *allocated_listen_port); + } + /* Allocate a channel number for the socket. */ c = channel_new("port listener", type, sock, sock, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, @@ -2616,17 +2651,18 @@ channel_setup_local_fwd_listener(const char *listen_host, u_short listen_port, const char *host_to_connect, u_short port_to_connect, int gateway_ports) { return channel_setup_fwd_listener(SSH_CHANNEL_PORT_LISTENER, - listen_host, listen_port, host_to_connect, port_to_connect, + listen_host, listen_port, NULL, host_to_connect, port_to_connect, gateway_ports); } /* protocol v2 remote port fwd, used by sshd */ int channel_setup_remote_fwd_listener(const char *listen_address, - u_short listen_port, int gateway_ports) + u_short listen_port, int *allocated_listen_port, int gateway_ports) { return channel_setup_fwd_listener(SSH_CHANNEL_RPORT_LISTENER, - listen_address, listen_port, NULL, 0, gateway_ports); + listen_address, listen_port, allocated_listen_port, + NULL, 0, gateway_ports); } /* diff --git a/channels.h b/channels.h index 19fee769c..1488ed7e5 100644 --- a/channels.h +++ b/channels.h @@ -1,4 +1,4 @@ -/* $OpenBSD: channels.h,v 1.97 2009/01/22 09:46:01 djm Exp $ */ +/* $OpenBSD: channels.h,v 1.98 2009/02/12 03:00:56 djm Exp $ */ /* * Author: Tatu Ylonen @@ -245,7 +245,7 @@ int channel_request_remote_forwarding(const char *, u_short, int channel_setup_local_fwd_listener(const char *, u_short, const char *, u_short, int); void channel_request_rforward_cancel(const char *host, u_short port); -int channel_setup_remote_fwd_listener(const char *, u_short, int); +int channel_setup_remote_fwd_listener(const char *, u_short, int *, int); int channel_cancel_rport_listener(const char *, u_short); /* x11 forwarding */ diff --git a/clientloop.c b/clientloop.c index 1b5badb71..a2d2d1d07 100644 --- a/clientloop.c +++ b/clientloop.c @@ -1,4 +1,4 @@ -/* $OpenBSD: clientloop.c,v 1.208 2009/01/22 10:02:34 djm Exp $ */ +/* $OpenBSD: clientloop.c,v 1.209 2009/02/12 03:00:56 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -849,7 +849,7 @@ process_cmdline(void) } channel_request_rforward_cancel(cancel_host, cancel_port); } else { - if (!parse_forward(&fwd, s, dynamic ? 1 : 0)) { + if (!parse_forward(&fwd, s, dynamic, remote)) { logit("Bad forwarding specification."); goto out; } diff --git a/readconf.c b/readconf.c index 0a8be1400..53fc6c7ba 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.175 2009/01/22 10:02:34 djm Exp $ */ +/* $OpenBSD: readconf.c,v 1.176 2009/02/12 03:00:56 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -735,7 +735,8 @@ parse_int: } if (parse_forward(&fwd, fwdarg, - opcode == oDynamicForward ? 1 : 0) == 0) + opcode == oDynamicForward ? 1 : 0, + opcode == oRemoteForward ? 1 : 0) == 0) fatal("%.200s line %d: Bad forwarding specification.", filename, linenum); @@ -1220,7 +1221,7 @@ fill_default_options(Options * options) * returns number of arguments parsed or zero on error */ int -parse_forward(Forward *fwd, const char *fwdspec, int dynamicfwd) +parse_forward(Forward *fwd, const char *fwdspec, int dynamicfwd, int remotefwd) { int i; char *p, *cp, *fwdarg[4]; @@ -1283,12 +1284,16 @@ parse_forward(Forward *fwd, const char *fwdspec, int dynamicfwd) goto fail_free; } - if (fwd->listen_port <= 0) + if (fwd->listen_port < 0 || (!remotefwd && fwd->listen_port == 0)) goto fail_free; if (fwd->connect_host != NULL && strlen(fwd->connect_host) >= NI_MAXHOST) goto fail_free; + if (fwd->listen_host != NULL && + strlen(fwd->listen_host) >= NI_MAXHOST) + goto fail_free; + return (i); diff --git a/readconf.h b/readconf.h index d94d65890..8fb3a8528 100644 --- a/readconf.h +++ b/readconf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.h,v 1.77 2009/01/22 10:02:34 djm Exp $ */ +/* $OpenBSD: readconf.h,v 1.78 2009/02/12 03:00:56 djm Exp $ */ /* * Author: Tatu Ylonen @@ -134,7 +134,7 @@ typedef struct { void initialize_options(Options *); void fill_default_options(Options *); int read_config_file(const char *, const char *, Options *, int); -int parse_forward(Forward *, const char *, int); +int parse_forward(Forward *, const char *, int, int); int process_config_line(Options *, const char *, char *, const char *, int, int *); diff --git a/serverloop.c b/serverloop.c index 931779e30..6244ad71c 100644 --- a/serverloop.c +++ b/serverloop.c @@ -1,4 +1,4 @@ -/* $OpenBSD: serverloop.c,v 1.155 2009/01/22 10:02:34 djm Exp $ */ +/* $OpenBSD: serverloop.c,v 1.156 2009/02/12 03:00:56 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1095,7 +1095,7 @@ server_input_global_request(int type, u_int32_t seq, void *ctxt) { char *rtype; int want_reply; - int success = 0; + int success = 0, allocated_listen_port = 0; rtype = packet_get_string(NULL); want_reply = packet_get_char(); @@ -1119,7 +1119,8 @@ server_input_global_request(int type, u_int32_t seq, void *ctxt) if (!options.allow_tcp_forwarding || no_port_forwarding_flag #ifndef NO_IPPORT_RESERVED_CONCEPT - || (listen_port < IPPORT_RESERVED && pw->pw_uid != 0) + || (listen_port != 0 && + listen_port < IPPORT_RESERVED && pw->pw_uid != 0) #endif ) { success = 0; @@ -1149,6 +1150,8 @@ server_input_global_request(int type, u_int32_t seq, void *ctxt) if (want_reply) { packet_start(success ? SSH2_MSG_REQUEST_SUCCESS : SSH2_MSG_REQUEST_FAILURE); + if (success && allocated_listen_port > 0) + packet_put_int(allocated_listen_port); packet_send(); packet_write_wait(); } diff --git a/ssh.c b/ssh.c index 26f070f3e..9d43bb74f 100644 --- a/ssh.c +++ b/ssh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh.c,v 1.323 2009/01/22 10:02:34 djm Exp $ */ +/* $OpenBSD: ssh.c,v 1.324 2009/02/12 03:00:56 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -453,7 +453,7 @@ main(int ac, char **av) break; case 'L': - if (parse_forward(&fwd, optarg, 0)) + if (parse_forward(&fwd, optarg, 0, 0)) add_local_forward(&options, &fwd); else { fprintf(stderr, @@ -464,7 +464,7 @@ main(int ac, char **av) break; case 'R': - if (parse_forward(&fwd, optarg, 0)) { + if (parse_forward(&fwd, optarg, 0, 1)) { add_remote_forward(&options, &fwd); } else { fprintf(stderr, @@ -475,7 +475,7 @@ main(int ac, char **av) break; case 'D': - if (parse_forward(&fwd, optarg, 1)) { + if (parse_forward(&fwd, optarg, 1, 0)) { add_local_forward(&options, &fwd); } else { fprintf(stderr, @@ -837,9 +837,16 @@ ssh_confirm_remote_forward(int type, u_int32_t seq, void *ctxt) { Forward *rfwd = (Forward *)ctxt; + /* XXX verbose() on failure? */ debug("remote forward %s for: listen %d, connect %s:%d", type == SSH2_MSG_REQUEST_SUCCESS ? "success" : "failure", rfwd->listen_port, rfwd->connect_host, rfwd->connect_port); + if (type == SSH2_MSG_REQUEST_SUCCESS && rfwd->listen_port == 0) { + logit("Allocated port %u for remote forward to %s:%d", + packet_get_int(), + rfwd->connect_host, rfwd->connect_port); + } + if (type == SSH2_MSG_REQUEST_FAILURE) { if (options.exit_on_forward_failure) fatal("Error: remote port forwarding failed for " -- cgit v1.2.3