From a6a0788cbbe8dfce2819ee43b09c80725742e21c Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Fri, 7 Dec 2018 03:39:40 +0000 Subject: upstream: only consider the ext-info-c extension during the initial KEX. It shouldn't be sent in subsequent ones, but if it is present we should ignore it. This prevents sshd from sending a SSH_MSG_EXT_INFO for REKEX for buggy these clients. Reported by Jakub Jelen via bz2929; ok dtucker@ OpenBSD-Commit-ID: 91564118547f7807030ec537480303e2371902f9 --- kex.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'kex.c') diff --git a/kex.c b/kex.c index 25f9f66f6..3823a9544 100644 --- a/kex.c +++ b/kex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.c,v 1.141 2018/07/09 13:37:10 sf Exp $ */ +/* $OpenBSD: kex.c,v 1.142 2018/12/07 03:39:40 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * @@ -487,6 +487,7 @@ kex_input_newkeys(int type, u_int32_t seq, struct ssh *ssh) if ((r = ssh_set_newkeys(ssh, MODE_IN)) != 0) return r; kex->done = 1; + kex->flags &= ~KEX_INITIAL; sshbuf_reset(kex->peer); /* sshbuf_reset(kex->my); */ kex->flags &= ~KEX_INIT_SENT; @@ -594,6 +595,7 @@ kex_new(struct ssh *ssh, char *proposal[PROPOSAL_MAX], struct kex **kexp) if ((r = kex_prop2buf(kex->my, proposal)) != 0) goto out; kex->done = 0; + kex->flags = KEX_INITIAL; kex_reset_dispatch(ssh); ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, &kex_input_kexinit); r = 0; @@ -839,7 +841,7 @@ kex_choose_conf(struct ssh *ssh) } /* Check whether client supports ext_info_c */ - if (kex->server) { + if (kex->server && (kex->flags & KEX_INITIAL)) { char *ext; ext = match_list("ext-info-c", peer[PROPOSAL_KEX_ALGS], NULL); -- cgit v1.2.3 From 0a843d9a0e805f14653a555f5c7a8ba99d62c12d Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Thu, 27 Dec 2018 03:25:24 +0000 Subject: upstream: move client/server SSH-* banners to buffers under ssh->kex and factor out the banner exchange. This eliminates some common code from the client and server. Also be more strict about handling \r characters - these should only be accepted immediately before \n (pointed out by Jann Horn). Inspired by a patch from Markus Schmidt. (lots of) feedback and ok markus@ OpenBSD-Commit-ID: 1cc7885487a6754f63641d7d3279b0941890275b --- .depend | 6 +- Makefile.in | 2 +- atomicio.h | 4 +- kex.c | 294 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ kex.h | 20 ++-- kexc25519.c | 10 +- kexc25519c.c | 6 +- kexc25519s.c | 6 +- kexdh.c | 10 +- kexdhc.c | 6 +- kexdhs.c | 6 +- kexecdh.c | 10 +- kexecdhc.c | 6 +- kexecdhs.c | 6 +- kexgex.c | 10 +- kexgexc.c | 6 +- kexgexs.c | 6 +- misc.c | 77 ++++++++++++++- misc.h | 5 +- packet.c | 42 ++++----- ssh.c | 4 +- ssh.h | 6 +- ssh_api.c | 125 ++++++++++++++----------- sshconnect.c | 187 ++----------------------------------- sshconnect.h | 15 ++- sshconnect2.c | 49 +++++----- sshd.c | 118 +---------------------- 27 files changed, 548 insertions(+), 494 deletions(-) (limited to 'kex.c') diff --git a/.depend b/.depend index f85557c9c..193130f5d 100644 --- a/.depend +++ b/.depend @@ -60,8 +60,8 @@ gss-serv.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-comp hash.o: crypto_api.h includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/rmd160.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/getopt.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h digest.h log.h ssherr.h hmac.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/rmd160.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/getopt.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h sshbuf.h digest.h hmac.h hostfile.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/rmd160.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/getopt.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h match.h sshkey.h hostfile.h log.h misc.h ssherr.h digest.h hmac.h -kex.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/rmd160.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/getopt.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h ssh2.h packet.h openbsd-compat/sys-queue.h dispatch.h opacket.h compat.h cipher.h cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h sshkey.h kex.h mac.h log.h match.h misc.h monitor.h ssherr.h sshbuf.h -kex.o: digest.h +kex.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/rmd160.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/getopt.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h ssh.h ssh2.h atomicio.h version.h packet.h openbsd-compat/sys-queue.h dispatch.h opacket.h compat.h cipher.h cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h sshkey.h kex.h mac.h log.h match.h misc.h +kex.o: monitor.h ssherr.h sshbuf.h digest.h kexc25519.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/rmd160.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/getopt.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h sshbuf.h ssh2.h sshkey.h cipher.h cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h kex.h mac.h log.h digest.h ssherr.h kexc25519c.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/rmd160.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/getopt.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h sshkey.h cipher.h cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h kex.h mac.h log.h packet.h openbsd-compat/sys-queue.h dispatch.h opacket.h ssh2.h sshbuf.h digest.h ssherr.h kexc25519s.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/rmd160.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/getopt.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h sshkey.h cipher.h cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h digest.h kex.h mac.h log.h packet.h openbsd-compat/sys-queue.h dispatch.h opacket.h ssh2.h sshbuf.h ssherr.h @@ -149,7 +149,7 @@ sshbuf-getput-crypto.o: includes.h config.h defines.h platform.h openbsd-compat/ sshbuf-misc.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/rmd160.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/getopt.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h ssherr.h sshbuf.h sshbuf.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/rmd160.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/getopt.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h ssherr.h sshbuf.h misc.h sshconnect.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/rmd160.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/getopt.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h hostfile.h ssh.h sshbuf.h packet.h openbsd-compat/sys-queue.h dispatch.h opacket.h compat.h sshkey.h sshconnect.h log.h misc.h readconf.h atomicio.h dns.h monitor_fdpass.h ssh2.h version.h authfile.h -sshconnect.o: ssherr.h authfd.h +sshconnect.o: ssherr.h authfd.h kex.h mac.h sshconnect2.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/rmd160.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/getopt.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h openbsd-compat/sys-queue.h xmalloc.h ssh.h ssh2.h sshbuf.h packet.h dispatch.h opacket.h compat.h cipher.h cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h sshkey.h kex.h mac.h myproposal.h sshconnect2.o: sshconnect.h authfile.h dh.h authfd.h log.h misc.h readconf.h match.h canohost.h msg.h pathnames.h uidswap.h hostfile.h ssherr.h utf8.h sshd.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/rmd160.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/getopt.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h ./openbsd-compat/sys-tree.h openbsd-compat/sys-queue.h xmalloc.h ssh.h ssh2.h sshpty.h packet.h dispatch.h opacket.h log.h sshbuf.h misc.h match.h servconf.h uidswap.h compat.h cipher.h cipher-chachapoly.h chacha.h diff --git a/Makefile.in b/Makefile.in index 126b2c742..6ffccb482 100644 --- a/Makefile.in +++ b/Makefile.in @@ -186,7 +186,7 @@ ssh-agent$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-agent.o ssh-pkcs11-client.o ssh-keygen$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keygen.o $(LD) -o $@ ssh-keygen.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) -ssh-keysign$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keysign.o readconf.o uidswap.o +ssh-keysign$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keysign.o readconf.o uidswap.o compat.o $(LD) -o $@ ssh-keysign.o readconf.o uidswap.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) ssh-pkcs11-helper$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-pkcs11-helper.o ssh-pkcs11.o diff --git a/atomicio.h b/atomicio.h index 0d728ac86..8b3cc6e21 100644 --- a/atomicio.h +++ b/atomicio.h @@ -1,4 +1,4 @@ -/* $OpenBSD: atomicio.h,v 1.11 2010/09/22 22:58:51 djm Exp $ */ +/* $OpenBSD: atomicio.h,v 1.12 2018/12/27 03:25:25 djm Exp $ */ /* * Copyright (c) 2006 Damien Miller. All rights reserved. @@ -29,6 +29,8 @@ #ifndef _ATOMICIO_H #define _ATOMICIO_H +struct iovec; + /* * Ensure all of data on socket comes through. f==read || f==vwrite */ diff --git a/kex.c b/kex.c index 3823a9544..30e1c261d 100644 --- a/kex.c +++ b/kex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.c,v 1.142 2018/12/07 03:39:40 djm Exp $ */ +/* $OpenBSD: kex.c,v 1.143 2018/12/27 03:25:25 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * @@ -25,19 +25,25 @@ #include "includes.h" - +#include +#include #include #include #include #include #include +#include +#include #ifdef WITH_OPENSSL #include #include #endif +#include "ssh.h" #include "ssh2.h" +#include "atomicio.h" +#include "version.h" #include "packet.h" #include "compat.h" #include "cipher.h" @@ -578,32 +584,20 @@ kex_input_kexinit(int type, u_int32_t seq, struct ssh *ssh) return SSH_ERR_INTERNAL_ERROR; } -int -kex_new(struct ssh *ssh, char *proposal[PROPOSAL_MAX], struct kex **kexp) +struct kex * +kex_new(void) { struct kex *kex; - int r; - *kexp = NULL; - if ((kex = calloc(1, sizeof(*kex))) == NULL) - return SSH_ERR_ALLOC_FAIL; - if ((kex->peer = sshbuf_new()) == NULL || - (kex->my = sshbuf_new()) == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto out; - } - if ((r = kex_prop2buf(kex->my, proposal)) != 0) - goto out; - kex->done = 0; - kex->flags = KEX_INITIAL; - kex_reset_dispatch(ssh); - ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, &kex_input_kexinit); - r = 0; - *kexp = kex; - out: - if (r != 0) + if ((kex = calloc(1, sizeof(*kex))) == NULL || + (kex->peer = sshbuf_new()) == NULL || + (kex->my = sshbuf_new()) == NULL || + (kex->client_version = sshbuf_new()) == NULL || + (kex->server_version = sshbuf_new()) == NULL) { kex_free(kex); - return r; + return NULL; + } + return kex; } void @@ -642,6 +636,9 @@ kex_free(struct kex *kex) { u_int mode; + if (kex == NULL) + return; + #ifdef WITH_OPENSSL DH_free(kex->dh); #ifdef OPENSSL_HAS_ECC @@ -654,21 +651,34 @@ kex_free(struct kex *kex) } sshbuf_free(kex->peer); sshbuf_free(kex->my); + sshbuf_free(kex->client_version); + sshbuf_free(kex->server_version); free(kex->session_id); - free(kex->client_version_string); - free(kex->server_version_string); free(kex->failed_choice); free(kex->hostkey_alg); free(kex->name); free(kex); } +int +kex_ready(struct ssh *ssh, char *proposal[PROPOSAL_MAX]) +{ + int r; + + if ((r = kex_prop2buf(ssh->kex->my, proposal)) != 0) + return r; + ssh->kex->flags = KEX_INITIAL; + kex_reset_dispatch(ssh); + ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, &kex_input_kexinit); + return 0; +} + int kex_setup(struct ssh *ssh, char *proposal[PROPOSAL_MAX]) { int r; - if ((r = kex_new(ssh, proposal, &ssh->kex)) != 0) + if ((r = kex_ready(ssh, proposal)) != 0) return r; if ((r = kex_send_kexinit(ssh)) != 0) { /* we start */ kex_free(ssh->kex); @@ -1043,3 +1053,233 @@ dump_digest(char *msg, u_char *digest, int len) sshbuf_dump_data(digest, len, stderr); } #endif + +/* + * Send a plaintext error message to the peer, suffixed by \r\n. + * Only used during banner exchange, and there only for the server. + */ +static void +send_error(struct ssh *ssh, char *msg) +{ + char *crnl = "\r\n"; + + if (!ssh->kex->server) + return; + + if (atomicio(vwrite, ssh_packet_get_connection_out(ssh), + msg, strlen(msg)) != strlen(msg) || + atomicio(vwrite, ssh_packet_get_connection_out(ssh), + crnl, strlen(crnl)) != strlen(crnl)) + error("%s: write: %.100s", __func__, strerror(errno)); +} + +/* + * Sends our identification string and waits for the peer's. Will block for + * up to timeout_ms (or indefinitely if timeout_ms <= 0). + * Returns on 0 success or a ssherr.h code on failure. + */ +int +kex_exchange_identification(struct ssh *ssh, int timeout_ms, + const char *version_addendum) +{ + int remote_major, remote_minor, mismatch; + size_t len, i, n; + int r, expect_nl; + u_char c; + struct sshbuf *our_version = ssh->kex->server ? + ssh->kex->server_version : ssh->kex->client_version; + struct sshbuf *peer_version = ssh->kex->server ? + ssh->kex->client_version : ssh->kex->server_version; + char *our_version_string = NULL, *peer_version_string = NULL; + char *cp, *remote_version = NULL; + + /* Prepare and send our banner */ + sshbuf_reset(our_version); + if (version_addendum != NULL && *version_addendum == '\0') + version_addendum = NULL; + if ((r = sshbuf_putf(our_version, "SSH-%d.%d-%.100s%s%s\r\n", + PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_VERSION, + version_addendum == NULL ? "" : " ", + version_addendum == NULL ? "" : version_addendum)) != 0) { + error("%s: sshbuf_putf: %s", __func__, ssh_err(r)); + goto out; + } + + if (atomicio(vwrite, ssh_packet_get_connection_out(ssh), + sshbuf_mutable_ptr(our_version), + sshbuf_len(our_version)) != sshbuf_len(our_version)) { + error("%s: write: %.100s", __func__, strerror(errno)); + r = SSH_ERR_SYSTEM_ERROR; + goto out; + } + if ((r = sshbuf_consume_end(our_version, 2)) != 0) { /* trim \r\n */ + error("%s: sshbuf_consume_end: %s", __func__, ssh_err(r)); + goto out; + } + our_version_string = sshbuf_dup_string(our_version); + if (our_version_string == NULL) { + error("%s: sshbuf_dup_string failed", __func__); + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + debug("Local version string %.100s", our_version_string); + + /* Read other side's version identification. */ + for (n = 0; ; n++) { + if (n >= SSH_MAX_PRE_BANNER_LINES) { + send_error(ssh, "No SSH identification string " + "received."); + error("%s: No SSH version received in first %u lines " + "from server", __func__, SSH_MAX_PRE_BANNER_LINES); + r = SSH_ERR_INVALID_FORMAT; + goto out; + } + sshbuf_reset(peer_version); + expect_nl = 0; + for (i = 0; ; i++) { + if (timeout_ms > 0) { + r = waitrfd(ssh_packet_get_connection_in(ssh), + &timeout_ms); + if (r == -1 && errno == ETIMEDOUT) { + send_error(ssh, "Timed out waiting " + "for SSH identification string."); + error("Connection timed out during " + "banner exchange"); + r = SSH_ERR_CONN_TIMEOUT; + goto out; + } else if (r == -1) { + error("%s: %s", + __func__, strerror(errno)); + r = SSH_ERR_SYSTEM_ERROR; + goto out; + } + } + + len = atomicio(read, ssh_packet_get_connection_in(ssh), + &c, 1); + if (len != 1 && errno == EPIPE) { + error("%s: Connection closed by remote host", + __func__); + r = SSH_ERR_CONN_CLOSED; + goto out; + } else if (len != 1) { + error("%s: read: %.100s", + __func__, strerror(errno)); + r = SSH_ERR_SYSTEM_ERROR; + goto out; + } + if (c == '\r') { + expect_nl = 1; + continue; + } + if (c == '\n') + break; + if (c == '\0' || expect_nl) { + error("%s: banner line contains invalid " + "characters", __func__); + goto invalid; + } + if ((r = sshbuf_put_u8(peer_version, c)) != 0) { + error("%s: sshbuf_put: %s", + __func__, ssh_err(r)); + goto out; + } + if (sshbuf_len(peer_version) > SSH_MAX_BANNER_LEN) { + error("%s: banner line too long", __func__); + goto invalid; + } + } + /* Is this an actual protocol banner? */ + if (sshbuf_len(peer_version) > 4 && + memcmp(sshbuf_ptr(peer_version), "SSH-", 4) == 0) + break; + /* If not, then just log the line and continue */ + if ((cp = sshbuf_dup_string(peer_version)) == NULL) { + error("%s: sshbuf_dup_string failed", __func__); + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + /* Do not accept lines before the SSH ident from a client */ + if (ssh->kex->server) { + error("%s: client sent invalid protocol identifier " + "\"%.256s\"", __func__, cp); + free(cp); + goto invalid; + } + debug("%s: banner line %zu: %s", __func__, n, cp); + free(cp); + } + peer_version_string = sshbuf_dup_string(peer_version); + if (peer_version_string == NULL) + error("%s: sshbuf_dup_string failed", __func__); + /* XXX must be same size for sscanf */ + if ((remote_version = calloc(1, sshbuf_len(peer_version))) == NULL) { + error("%s: calloc failed", __func__); + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + + /* + * Check that the versions match. In future this might accept + * several versions and set appropriate flags to handle them. + */ + if (sscanf(peer_version_string, "SSH-%d.%d-%[^\n]\n", + &remote_major, &remote_minor, remote_version) != 3) { + error("Bad remote protocol version identification: '%.100s'", + peer_version_string); + invalid: + send_error(ssh, "Invalid SSH identification string."); + r = SSH_ERR_INVALID_FORMAT; + goto out; + } + debug("Remote protocol version %d.%d, remote software version %.100s", + remote_major, remote_minor, remote_version); + ssh->compat = compat_datafellows(remote_version); + + mismatch = 0; + switch (remote_major) { + case 2: + break; + case 1: + if (remote_minor != 99) + mismatch = 1; + break; + default: + mismatch = 1; + break; + } + if (mismatch) { + error("Protocol major versions differ: %d vs. %d", + PROTOCOL_MAJOR_2, remote_major); + send_error(ssh, "Protocol major versions differ."); + r = SSH_ERR_NO_PROTOCOL_VERSION; + goto out; + } + + if (ssh->kex->server && (ssh->compat & SSH_BUG_PROBE) != 0) { + logit("probed from %s port %d with %s. Don't panic.", + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), + peer_version_string); + r = SSH_ERR_CONN_CLOSED; /* XXX */ + goto out; + } + if (ssh->kex->server && (ssh->compat & SSH_BUG_SCANNER) != 0) { + logit("scanned from %s port %d with %s. Don't panic.", + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), + peer_version_string); + r = SSH_ERR_CONN_CLOSED; /* XXX */ + goto out; + } + if ((ssh->compat & SSH_BUG_RSASIGMD5) != 0) { + logit("Remote version \"%.100s\" uses unsafe RSA signature " + "scheme; disabling use of RSA keys", remote_version); + } + /* success */ + r = 0; + out: + free(our_version_string); + free(peer_version_string); + free(remote_version); + return r; +} + diff --git a/kex.h b/kex.h index 0f67f58db..9ba860954 100644 --- a/kex.h +++ b/kex.h @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.h,v 1.92 2018/12/07 03:39:40 djm Exp $ */ +/* $OpenBSD: kex.h,v 1.93 2018/12/27 03:25:25 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -145,12 +145,12 @@ struct kex { int ext_info_c; struct sshbuf *my; struct sshbuf *peer; + struct sshbuf *client_version; + struct sshbuf *server_version; sig_atomic_t done; u_int flags; int hash_alg; int ec_nid; - char *client_version_string; - char *server_version_string; char *failed_choice; int (*verify_host_key)(struct sshkey *, struct ssh *); struct sshkey *(*load_host_public_key)(int, int, struct ssh *); @@ -173,7 +173,10 @@ char *kex_alg_list(char); char *kex_names_cat(const char *, const char *); int kex_assemble_names(char **, const char *, const char *); -int kex_new(struct ssh *, char *[PROPOSAL_MAX], struct kex **); +int kex_exchange_identification(struct ssh *, int, const char *); + +struct kex *kex_new(void); +int kex_ready(struct ssh *, char *[PROPOSAL_MAX]); int kex_setup(struct ssh *, char *[PROPOSAL_MAX]); void kex_free_newkeys(struct newkeys *); void kex_free(struct kex *); @@ -199,22 +202,23 @@ int kexecdh_server(struct ssh *); int kexc25519_client(struct ssh *); int kexc25519_server(struct ssh *); -int kex_dh_hash(int, const char *, const char *, +int kex_dh_hash(int, const struct sshbuf *, const struct sshbuf *, const u_char *, size_t, const u_char *, size_t, const u_char *, size_t, const BIGNUM *, const BIGNUM *, const BIGNUM *, u_char *, size_t *); -int kexgex_hash(int, const char *, const char *, +int kexgex_hash(int, const struct sshbuf *, const struct sshbuf *, const u_char *, size_t, const u_char *, size_t, const u_char *, size_t, int, int, int, const BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, u_char *, size_t *); -int kex_ecdh_hash(int, const EC_GROUP *, const char *, const char *, +int kex_ecdh_hash(int, const EC_GROUP *, + const struct sshbuf *, const struct sshbuf *, const u_char *, size_t, const u_char *, size_t, const u_char *, size_t, const EC_POINT *, const EC_POINT *, const BIGNUM *, u_char *, size_t *); -int kex_c25519_hash(int, const char *, const char *, +int kex_c25519_hash(int, const struct sshbuf *, const struct sshbuf *, const u_char *, size_t, const u_char *, size_t, const u_char *, size_t, const u_char *, const u_char *, const u_char *, size_t, u_char *, size_t *); diff --git a/kexc25519.c b/kexc25519.c index 0897b8c51..712dd523d 100644 --- a/kexc25519.c +++ b/kexc25519.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexc25519.c,v 1.10 2016/05/02 08:49:03 djm Exp $ */ +/* $OpenBSD: kexc25519.c,v 1.11 2018/12/27 03:25:25 djm Exp $ */ /* * Copyright (c) 2001, 2013 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -84,8 +84,8 @@ kexc25519_shared_key(const u_char key[CURVE25519_SIZE], int kex_c25519_hash( int hash_alg, - const char *client_version_string, - const char *server_version_string, + const struct sshbuf *client_version, + const struct sshbuf *server_version, const u_char *ckexinit, size_t ckexinitlen, const u_char *skexinit, size_t skexinitlen, const u_char *serverhostkeyblob, size_t sbloblen, @@ -101,8 +101,8 @@ kex_c25519_hash( return SSH_ERR_INVALID_ARGUMENT; if ((b = sshbuf_new()) == NULL) return SSH_ERR_ALLOC_FAIL; - if ((r = sshbuf_put_cstring(b, client_version_string)) < 0 || - (r = sshbuf_put_cstring(b, server_version_string)) < 0 || + if ((r = sshbuf_put_stringb(b, client_version)) < 0 || + (r = sshbuf_put_stringb(b, server_version)) < 0 || /* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */ (r = sshbuf_put_u32(b, ckexinitlen+1)) < 0 || (r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) < 0 || diff --git a/kexc25519c.c b/kexc25519c.c index a8d92149c..75e7d8c57 100644 --- a/kexc25519c.c +++ b/kexc25519c.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexc25519c.c,v 1.9 2017/12/18 02:25:15 djm Exp $ */ +/* $OpenBSD: kexc25519c.c,v 1.10 2018/12/27 03:25:25 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -129,8 +129,8 @@ input_kex_c25519_reply(int type, u_int32_t seq, struct ssh *ssh) hashlen = sizeof(hash); if ((r = kex_c25519_hash( kex->hash_alg, - kex->client_version_string, - kex->server_version_string, + kex->client_version, + kex->server_version, sshbuf_ptr(kex->my), sshbuf_len(kex->my), sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), server_host_key_blob, sbloblen, diff --git a/kexc25519s.c b/kexc25519s.c index 0800a7a4b..81f816e56 100644 --- a/kexc25519s.c +++ b/kexc25519s.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexc25519s.c,v 1.11 2017/05/31 04:19:28 djm Exp $ */ +/* $OpenBSD: kexc25519s.c,v 1.12 2018/12/27 03:25:25 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -110,8 +110,8 @@ input_kex_c25519_init(int type, u_int32_t seq, struct ssh *ssh) hashlen = sizeof(hash); if ((r = kex_c25519_hash( kex->hash_alg, - kex->client_version_string, - kex->server_version_string, + kex->client_version, + kex->server_version, sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), sshbuf_ptr(kex->my), sshbuf_len(kex->my), server_host_key_blob, sbloblen, diff --git a/kexdh.c b/kexdh.c index e6925b186..34c55ef9f 100644 --- a/kexdh.c +++ b/kexdh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexdh.c,v 1.26 2016/05/02 10:26:04 djm Exp $ */ +/* $OpenBSD: kexdh.c,v 1.27 2018/12/27 03:25:25 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * @@ -46,8 +46,8 @@ int kex_dh_hash( int hash_alg, - const char *client_version_string, - const char *server_version_string, + const struct sshbuf *client_version, + const struct sshbuf *server_version, const u_char *ckexinit, size_t ckexinitlen, const u_char *skexinit, size_t skexinitlen, const u_char *serverhostkeyblob, size_t sbloblen, @@ -63,8 +63,8 @@ kex_dh_hash( return SSH_ERR_INVALID_ARGUMENT; if ((b = sshbuf_new()) == NULL) return SSH_ERR_ALLOC_FAIL; - if ((r = sshbuf_put_cstring(b, client_version_string)) != 0 || - (r = sshbuf_put_cstring(b, server_version_string)) != 0 || + if ((r = sshbuf_put_stringb(b, client_version)) < 0 || + (r = sshbuf_put_stringb(b, server_version)) < 0 || /* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */ (r = sshbuf_put_u32(b, ckexinitlen+1)) != 0 || (r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) != 0 || diff --git a/kexdhc.c b/kexdhc.c index 8b56377ad..b367832d5 100644 --- a/kexdhc.c +++ b/kexdhc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexdhc.c,v 1.22 2018/02/07 02:06:51 jsing Exp $ */ +/* $OpenBSD: kexdhc.c,v 1.24 2018/12/27 03:25:25 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * @@ -178,8 +178,8 @@ input_kex_dh(int type, u_int32_t seq, struct ssh *ssh) hashlen = sizeof(hash); if ((r = kex_dh_hash( kex->hash_alg, - kex->client_version_string, - kex->server_version_string, + kex->client_version, + kex->server_version, sshbuf_ptr(kex->my), sshbuf_len(kex->my), sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), server_host_key_blob, sbloblen, diff --git a/kexdhs.c b/kexdhs.c index 337aab5be..adf70babd 100644 --- a/kexdhs.c +++ b/kexdhs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexdhs.c,v 1.27 2018/04/10 00:10:49 djm Exp $ */ +/* $OpenBSD: kexdhs.c,v 1.29 2018/12/27 03:25:25 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * @@ -166,8 +166,8 @@ input_kex_dh_init(int type, u_int32_t seq, struct ssh *ssh) hashlen = sizeof(hash); if ((r = kex_dh_hash( kex->hash_alg, - kex->client_version_string, - kex->server_version_string, + kex->client_version, + kex->server_version, sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), sshbuf_ptr(kex->my), sshbuf_len(kex->my), server_host_key_blob, sbloblen, diff --git a/kexecdh.c b/kexecdh.c index 2a4fec6b1..4380427ea 100644 --- a/kexecdh.c +++ b/kexecdh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexecdh.c,v 1.6 2015/01/19 20:16:15 markus Exp $ */ +/* $OpenBSD: kexecdh.c,v 1.7 2018/12/27 03:25:25 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -50,8 +50,8 @@ int kex_ecdh_hash( int hash_alg, const EC_GROUP *ec_group, - const char *client_version_string, - const char *server_version_string, + const struct sshbuf *client_version, + const struct sshbuf *server_version, const u_char *ckexinit, size_t ckexinitlen, const u_char *skexinit, size_t skexinitlen, const u_char *serverhostkeyblob, size_t sbloblen, @@ -67,8 +67,8 @@ kex_ecdh_hash( return SSH_ERR_INVALID_ARGUMENT; if ((b = sshbuf_new()) == NULL) return SSH_ERR_ALLOC_FAIL; - if ((r = sshbuf_put_cstring(b, client_version_string)) != 0 || - (r = sshbuf_put_cstring(b, server_version_string)) != 0 || + if ((r = sshbuf_put_stringb(b, client_version)) < 0 || + (r = sshbuf_put_stringb(b, server_version)) < 0 || /* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */ (r = sshbuf_put_u32(b, ckexinitlen+1)) != 0 || (r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) != 0 || diff --git a/kexecdhc.c b/kexecdhc.c index ac146a362..af556dc58 100644 --- a/kexecdhc.c +++ b/kexecdhc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexecdhc.c,v 1.13 2018/02/07 02:06:51 jsing Exp $ */ +/* $OpenBSD: kexecdhc.c,v 1.14 2018/12/27 03:25:25 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -175,8 +175,8 @@ input_kex_ecdh_reply(int type, u_int32_t seq, struct ssh *ssh) if ((r = kex_ecdh_hash( kex->hash_alg, group, - kex->client_version_string, - kex->server_version_string, + kex->client_version, + kex->server_version, sshbuf_ptr(kex->my), sshbuf_len(kex->my), sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), server_host_key_blob, sbloblen, diff --git a/kexecdhs.c b/kexecdhs.c index af4f30309..c690feffe 100644 --- a/kexecdhs.c +++ b/kexecdhs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexecdhs.c,v 1.17 2018/02/07 02:06:51 jsing Exp $ */ +/* $OpenBSD: kexecdhs.c,v 1.18 2018/12/27 03:25:25 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -145,8 +145,8 @@ input_kex_ecdh_init(int type, u_int32_t seq, struct ssh *ssh) if ((r = kex_ecdh_hash( kex->hash_alg, group, - kex->client_version_string, - kex->server_version_string, + kex->client_version, + kex->server_version, sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), sshbuf_ptr(kex->my), sshbuf_len(kex->my), server_host_key_blob, sbloblen, diff --git a/kexgex.c b/kexgex.c index 3ca4bd370..a5d591b0a 100644 --- a/kexgex.c +++ b/kexgex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexgex.c,v 1.29 2015/01/19 20:16:15 markus Exp $ */ +/* $OpenBSD: kexgex.c,v 1.30 2018/12/27 03:25:25 djm Exp $ */ /* * Copyright (c) 2000 Niels Provos. All rights reserved. * Copyright (c) 2001 Markus Friedl. All rights reserved. @@ -46,8 +46,8 @@ int kexgex_hash( int hash_alg, - const char *client_version_string, - const char *server_version_string, + const struct sshbuf *client_version, + const struct sshbuf *server_version, const u_char *ckexinit, size_t ckexinitlen, const u_char *skexinit, size_t skexinitlen, const u_char *serverhostkeyblob, size_t sbloblen, @@ -66,8 +66,8 @@ kexgex_hash( return SSH_ERR_INVALID_ARGUMENT; if ((b = sshbuf_new()) == NULL) return SSH_ERR_ALLOC_FAIL; - if ((r = sshbuf_put_cstring(b, client_version_string)) != 0 || - (r = sshbuf_put_cstring(b, server_version_string)) != 0 || + if ((r = sshbuf_put_stringb(b, client_version)) < 0 || + (r = sshbuf_put_stringb(b, server_version)) < 0 || /* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */ (r = sshbuf_put_u32(b, ckexinitlen+1)) != 0 || (r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) != 0 || diff --git a/kexgexc.c b/kexgexc.c index 0d07f73c7..f2be35ab2 100644 --- a/kexgexc.c +++ b/kexgexc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexgexc.c,v 1.27 2018/02/07 02:06:51 jsing Exp $ */ +/* $OpenBSD: kexgexc.c,v 1.29 2018/12/27 03:25:25 djm Exp $ */ /* * Copyright (c) 2000 Niels Provos. All rights reserved. * Copyright (c) 2001 Markus Friedl. All rights reserved. @@ -222,8 +222,8 @@ input_kex_dh_gex_reply(int type, u_int32_t seq, struct ssh *ssh) hashlen = sizeof(hash); if ((r = kexgex_hash( kex->hash_alg, - kex->client_version_string, - kex->server_version_string, + kex->client_version, + kex->server_version, sshbuf_ptr(kex->my), sshbuf_len(kex->my), sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), server_host_key_blob, sbloblen, diff --git a/kexgexs.c b/kexgexs.c index dc9c0bc60..cd0e758c4 100644 --- a/kexgexs.c +++ b/kexgexs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexgexs.c,v 1.35 2018/10/04 00:04:41 djm Exp $ */ +/* $OpenBSD: kexgexs.c,v 1.36 2018/12/27 03:25:25 djm Exp $ */ /* * Copyright (c) 2000 Niels Provos. All rights reserved. * Copyright (c) 2001 Markus Friedl. All rights reserved. @@ -198,8 +198,8 @@ input_kex_dh_gex_init(int type, u_int32_t seq, struct ssh *ssh) hashlen = sizeof(hash); if ((r = kexgex_hash( kex->hash_alg, - kex->client_version_string, - kex->server_version_string, + kex->client_version, + kex->server_version, sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), sshbuf_ptr(kex->my), sshbuf_len(kex->my), server_host_key_blob, sbloblen, diff --git a/misc.c b/misc.c index 275e68141..bfd786ef8 100644 --- a/misc.c +++ b/misc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.c,v 1.135 2018/12/07 04:36:09 dtucker Exp $ */ +/* $OpenBSD: misc.c,v 1.136 2018/12/27 03:25:25 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2005,2006 Damien Miller. All rights reserved. @@ -38,6 +38,7 @@ #ifdef HAVE_LIBGEN_H # include #endif +#include #include #include #include @@ -234,6 +235,80 @@ set_rdomain(int fd, const char *name) #endif } +/* + * Wait up to *timeoutp milliseconds for fd to be readable. Updates + * *timeoutp with time remaining. + * Returns 0 if fd ready or -1 on timeout or error (see errno). + */ +int +waitrfd(int fd, int *timeoutp) +{ + struct pollfd pfd; + struct timeval t_start; + int oerrno, r; + + monotime_tv(&t_start); + pfd.fd = fd; + pfd.events = POLLIN; + for (; *timeoutp >= 0;) { + r = poll(&pfd, 1, *timeoutp); + oerrno = errno; + ms_subtract_diff(&t_start, timeoutp); + errno = oerrno; + if (r > 0) + return 0; + else if (r == -1 && errno != EAGAIN) + return -1; + else if (r == 0) + break; + } + /* timeout */ + errno = ETIMEDOUT; + return -1; +} + +/* + * Attempt a non-blocking connect(2) to the specified address, waiting up to + * *timeoutp milliseconds for the connection to complete. If the timeout is + * <=0, then wait indefinitely. + * + * Returns 0 on success or -1 on failure. + */ +int +timeout_connect(int sockfd, const struct sockaddr *serv_addr, + socklen_t addrlen, int *timeoutp) +{ + int optval = 0; + socklen_t optlen = sizeof(optval); + + /* No timeout: just do a blocking connect() */ + if (timeoutp == NULL || *timeoutp <= 0) + return connect(sockfd, serv_addr, addrlen); + + set_nonblock(sockfd); + if (connect(sockfd, serv_addr, addrlen) == 0) { + /* Succeeded already? */ + unset_nonblock(sockfd); + return 0; + } else if (errno != EINPROGRESS) + return -1; + + if (waitrfd(sockfd, timeoutp) == -1) + return -1; + + /* Completed or failed */ + if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1) { + debug("getsockopt: %s", strerror(errno)); + return -1; + } + if (optval != 0) { + errno = optval; + return -1; + } + unset_nonblock(sockfd); + return 0; +} + /* Characters considered whitespace in strsep calls. */ #define WHITESPACE " \t\r\n" #define QUOTE "\"" diff --git a/misc.h b/misc.h index 2dd61dc32..47177d838 100644 --- a/misc.h +++ b/misc.h @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.h,v 1.77 2018/12/07 04:36:09 dtucker Exp $ */ +/* $OpenBSD: misc.h,v 1.78 2018/12/27 03:25:25 djm Exp $ */ /* * Author: Tatu Ylonen @@ -17,6 +17,7 @@ #include #include +#include /* Data structure for representing a forwarding request. */ struct Forward { @@ -51,6 +52,8 @@ void set_nodelay(int); int set_reuseaddr(int); char *get_rdomain(int); int set_rdomain(int, const char *); +int waitrfd(int, int *); +int timeout_connect(int, const struct sockaddr *, socklen_t, int *); int a2port(const char *); int a2tun(const char *, int *); char *put_host_port(const char *, u_short); diff --git a/packet.c b/packet.c index dcf35e6e6..e7e6d27a7 100644 --- a/packet.c +++ b/packet.c @@ -1,4 +1,4 @@ -/* $OpenBSD: packet.c,v 1.277 2018/07/16 03:09:13 djm Exp $ */ +/* $OpenBSD: packet.c,v 1.278 2018/12/27 03:25:25 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -58,6 +58,7 @@ #include #include #include +#include #include #include @@ -228,6 +229,7 @@ ssh_alloc_session_state(void) if ((ssh = calloc(1, sizeof(*ssh))) == NULL || (state = calloc(1, sizeof(*state))) == NULL || + (ssh->kex = kex_new()) == NULL || (state->input = sshbuf_new()) == NULL || (state->output = sshbuf_new()) == NULL || (state->outgoing_packet = sshbuf_new()) == NULL || @@ -250,6 +252,10 @@ ssh_alloc_session_state(void) ssh->state = state; return ssh; fail: + if (ssh) { + kex_free(ssh->kex); + free(ssh); + } if (state) { sshbuf_free(state->input); sshbuf_free(state->output); @@ -257,7 +263,6 @@ ssh_alloc_session_state(void) sshbuf_free(state->outgoing_packet); free(state); } - free(ssh); return NULL; } @@ -272,8 +277,7 @@ ssh_packet_set_input_hook(struct ssh *ssh, ssh_packet_hook_fn *hook, void *ctx) int ssh_packet_is_rekeying(struct ssh *ssh) { - return ssh->state->rekeying || - (ssh->kex != NULL && ssh->kex->done == 0); + return ssh->state->rekeying || ssh->kex->done == 0; } /* @@ -932,7 +936,7 @@ ssh_packet_need_rekeying(struct ssh *ssh, u_int outbound_packet_len) return 0; /* Haven't keyed yet or KEX in progress. */ - if (ssh->kex == NULL || ssh_packet_is_rekeying(ssh)) + if (ssh_packet_is_rekeying(ssh)) return 0; /* Peer can't rekey */ @@ -2123,6 +2127,7 @@ void ssh_packet_set_server(struct ssh *ssh) { ssh->state->server_side = 1; + ssh->kex->server = 1; /* XXX unify? */ } void @@ -2175,9 +2180,9 @@ kex_to_blob(struct sshbuf *m, struct kex *kex) (r = sshbuf_put_u32(m, kex->kex_type)) != 0 || (r = sshbuf_put_stringb(m, kex->my)) != 0 || (r = sshbuf_put_stringb(m, kex->peer)) != 0 || - (r = sshbuf_put_u32(m, kex->flags)) != 0 || - (r = sshbuf_put_cstring(m, kex->client_version_string)) != 0 || - (r = sshbuf_put_cstring(m, kex->server_version_string)) != 0) + (r = sshbuf_put_stringb(m, kex->client_version)) != 0 || + (r = sshbuf_put_stringb(m, kex->server_version)) != 0 || + (r = sshbuf_put_u32(m, kex->flags)) != 0) return r; return 0; } @@ -2327,12 +2332,8 @@ kex_from_blob(struct sshbuf *m, struct kex **kexp) struct kex *kex; int r; - if ((kex = calloc(1, sizeof(struct kex))) == NULL || - (kex->my = sshbuf_new()) == NULL || - (kex->peer = sshbuf_new()) == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto out; - } + if ((kex = kex_new()) == NULL) + return SSH_ERR_ALLOC_FAIL; if ((r = sshbuf_get_string(m, &kex->session_id, &kex->session_id_len)) != 0 || (r = sshbuf_get_u32(m, &kex->we_need)) != 0 || (r = sshbuf_get_cstring(m, &kex->hostkey_alg, NULL)) != 0 || @@ -2341,23 +2342,20 @@ kex_from_blob(struct sshbuf *m, struct kex **kexp) (r = sshbuf_get_u32(m, &kex->kex_type)) != 0 || (r = sshbuf_get_stringb(m, kex->my)) != 0 || (r = sshbuf_get_stringb(m, kex->peer)) != 0 || - (r = sshbuf_get_u32(m, &kex->flags)) != 0 || - (r = sshbuf_get_cstring(m, &kex->client_version_string, NULL)) != 0 || - (r = sshbuf_get_cstring(m, &kex->server_version_string, NULL)) != 0) + (r = sshbuf_get_stringb(m, kex->client_version)) != 0 || + (r = sshbuf_get_stringb(m, kex->server_version)) != 0 || + (r = sshbuf_get_u32(m, &kex->flags)) != 0) goto out; kex->server = 1; kex->done = 1; r = 0; out: if (r != 0 || kexp == NULL) { - if (kex != NULL) { - sshbuf_free(kex->my); - sshbuf_free(kex->peer); - free(kex); - } + kex_free(kex); if (kexp != NULL) *kexp = NULL; } else { + kex_free(*kexp); *kexp = kex; } return r; diff --git a/ssh.c b/ssh.c index c6cb7847d..16536a97a 100644 --- a/ssh.c +++ b/ssh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh.c,v 1.496 2018/11/23 05:08:07 djm Exp $ */ +/* $OpenBSD: ssh.c,v 1.497 2018/12/27 03:25:25 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1490,7 +1490,7 @@ main(int ac, char **av) signal(SIGCHLD, main_sigchld_handler); /* Log into the remote system. Never returns if the login fails. */ - ssh_login(&sensitive_data, host, (struct sockaddr *)&hostaddr, + ssh_login(ssh, &sensitive_data, host, (struct sockaddr *)&hostaddr, options.port, pw, timeout_ms); if (packet_connection_is_on_socket()) { diff --git a/ssh.h b/ssh.h index 5abfd7a68..dda6f617e 100644 --- a/ssh.h +++ b/ssh.h @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh.h,v 1.88 2018/06/06 18:29:18 markus Exp $ */ +/* $OpenBSD: ssh.h,v 1.89 2018/12/27 03:25:25 djm Exp $ */ /* * Author: Tatu Ylonen @@ -93,3 +93,7 @@ /* Listen backlog for sshd, ssh-agent and forwarding sockets */ #define SSH_LISTEN_BACKLOG 128 + +/* Limits for banner exchange */ +#define SSH_MAX_BANNER_LEN 8192 +#define SSH_MAX_PRE_BANNER_LINES 1024 diff --git a/ssh_api.c b/ssh_api.c index 53bbc9b49..ab209c4ca 100644 --- a/ssh_api.c +++ b/ssh_api.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh_api.c,v 1.8 2017/04/30 23:13:25 djm Exp $ */ +/* $OpenBSD: ssh_api.c,v 1.9 2018/12/27 03:25:25 djm Exp $ */ /* * Copyright (c) 2012 Markus Friedl. All rights reserved. * @@ -34,8 +34,8 @@ #include int _ssh_exchange_banner(struct ssh *); -int _ssh_send_banner(struct ssh *, char **); -int _ssh_read_banner(struct ssh *, char **); +int _ssh_send_banner(struct ssh *, struct sshbuf *); +int _ssh_read_banner(struct ssh *, struct sshbuf *); int _ssh_order_hostkeyalgs(struct ssh *); int _ssh_verify_host_key(struct sshkey *, struct ssh *); struct sshkey *_ssh_host_public_key(int, int, struct ssh *); @@ -92,7 +92,7 @@ ssh_init(struct ssh **sshp, int is_server, struct kex_params *kex_params) /* Initialize key exchange */ proposal = kex_params ? kex_params->proposal : myproposal; - if ((r = kex_new(ssh, proposal, &ssh->kex)) != 0) { + if ((r = kex_ready(ssh, proposal)) != 0) { ssh_free(ssh); return r; } @@ -236,8 +236,8 @@ ssh_packet_next(struct ssh *ssh, u_char *typep) * enough data. */ *typep = SSH_MSG_NONE; - if (ssh->kex->client_version_string == NULL || - ssh->kex->server_version_string == NULL) + if (sshbuf_len(ssh->kex->client_version) == 0 || + sshbuf_len(ssh->kex->server_version) == 0) return _ssh_exchange_banner(ssh); /* * If we enough data and a dispatch function then @@ -312,39 +312,46 @@ ssh_input_space(struct ssh *ssh, size_t len) /* Read other side's version identification. */ int -_ssh_read_banner(struct ssh *ssh, char **bannerp) +_ssh_read_banner(struct ssh *ssh, struct sshbuf *banner) { - struct sshbuf *input; - const char *s; - char buf[256], remote_version[256]; /* must be same size! */ + struct sshbuf *input = ssh_packet_get_input(ssh); const char *mismatch = "Protocol mismatch.\r\n"; - int r, remote_major, remote_minor; - size_t i, n, j, len; + const u_char *s = sshbuf_ptr(input); + u_char c; + char *cp, *remote_version; + int r, remote_major, remote_minor, expect_nl; + size_t n, j; - *bannerp = NULL; - input = ssh_packet_get_input(ssh); - len = sshbuf_len(input); - s = (const char *)sshbuf_ptr(input); for (j = n = 0;;) { - for (i = 0; i < sizeof(buf) - 1; i++) { - if (j >= len) - return (0); - buf[i] = s[j++]; - if (buf[i] == '\r') { - buf[i] = '\n'; - buf[i + 1] = 0; - continue; /**XXX wait for \n */ + sshbuf_reset(banner); + expect_nl = 0; + for (;;) { + if (j >= sshbuf_len(input)) + return 0; /* insufficient data in input buf */ + c = s[j++]; + if (c == '\r') { + expect_nl = 1; + continue; } - if (buf[i] == '\n') { - buf[i + 1] = 0; + if (c == '\n') break; - } + if (expect_nl) + goto bad; + if ((r = sshbuf_put_u8(banner, c)) != 0) + return r; + if (sshbuf_len(banner) > SSH_MAX_BANNER_LEN) + goto bad; } - buf[sizeof(buf) - 1] = 0; - if (strncmp(buf, "SSH-", 4) == 0) + if (sshbuf_len(banner) >= 4 && + memcmp(sshbuf_ptr(banner), "SSH-", 4) == 0) break; - debug("ssh_exchange_identification: %s", buf); - if (ssh->kex->server || ++n > 65536) { + if ((cp = sshbuf_dup_string(banner)) == NULL) + return SSH_ERR_ALLOC_FAIL; + debug("%s: %s", __func__, cp); + free(cp); + /* Accept lines before banner only on client */ + if (ssh->kex->server || ++n > SSH_MAX_PRE_BANNER_LINES) { + bad: if ((r = sshbuf_put(ssh_packet_get_output(ssh), mismatch, strlen(mismatch))) != 0) return r; @@ -354,11 +361,17 @@ _ssh_read_banner(struct ssh *ssh, char **bannerp) if ((r = sshbuf_consume(input, j)) != 0) return r; + if ((cp = sshbuf_dup_string(banner)) == NULL) + return SSH_ERR_ALLOC_FAIL; + /* XXX remote version must be the same size as banner for sscanf */ + if ((remote_version = calloc(1, sshbuf_len(banner))) == NULL) + return SSH_ERR_ALLOC_FAIL; + /* * Check that the versions match. In future this might accept * several versions and set appropriate flags to handle them. */ - if (sscanf(buf, "SSH-%d.%d-%[^\n]\n", + if (sscanf(cp, "SSH-%d.%d-%[^\n]\n", &remote_major, &remote_minor, remote_version) != 3) return SSH_ERR_INVALID_FORMAT; debug("Remote protocol version %d.%d, remote software version %.100s", @@ -371,27 +384,29 @@ _ssh_read_banner(struct ssh *ssh, char **bannerp) } if (remote_major != 2) return SSH_ERR_PROTOCOL_MISMATCH; - chop(buf); - debug("Remote version string %.100s", buf); - if ((*bannerp = strdup(buf)) == NULL) - return SSH_ERR_ALLOC_FAIL; + debug("Remote version string %.100s", cp); + free(cp); return 0; } /* Send our own protocol version identification. */ int -_ssh_send_banner(struct ssh *ssh, char **bannerp) +_ssh_send_banner(struct ssh *ssh, struct sshbuf *banner) { - char buf[256]; + char *cp; int r; - snprintf(buf, sizeof buf, "SSH-2.0-%.100s\r\n", SSH_VERSION); - if ((r = sshbuf_put(ssh_packet_get_output(ssh), buf, strlen(buf))) != 0) + if ((r = sshbuf_putf(banner, "SSH-2.0-%.100s\r\n", SSH_VERSION)) != 0) + return r; + if ((r = sshbuf_putb(ssh_packet_get_output(ssh), banner)) != 0) + return r; + /* Remove trailing \r\n */ + if ((r = sshbuf_consume_end(banner, 2)) != 0) return r; - chop(buf); - debug("Local version string %.100s", buf); - if ((*bannerp = strdup(buf)) == NULL) + if ((cp = sshbuf_dup_string(banner)) == NULL) return SSH_ERR_ALLOC_FAIL; + debug("Local version string %.100s", cp); + free(cp); return 0; } @@ -408,25 +423,25 @@ _ssh_exchange_banner(struct ssh *ssh) r = 0; if (kex->server) { - if (kex->server_version_string == NULL) - r = _ssh_send_banner(ssh, &kex->server_version_string); + if (sshbuf_len(ssh->kex->server_version) == 0) + r = _ssh_send_banner(ssh, ssh->kex->server_version); if (r == 0 && - kex->server_version_string != NULL && - kex->client_version_string == NULL) - r = _ssh_read_banner(ssh, &kex->client_version_string); + sshbuf_len(ssh->kex->server_version) != 0 && + sshbuf_len(ssh->kex->client_version) == 0) + r = _ssh_read_banner(ssh, ssh->kex->client_version); } else { - if (kex->server_version_string == NULL) - r = _ssh_read_banner(ssh, &kex->server_version_string); + if (sshbuf_len(ssh->kex->server_version) == 0) + r = _ssh_read_banner(ssh, ssh->kex->server_version); if (r == 0 && - kex->server_version_string != NULL && - kex->client_version_string == NULL) - r = _ssh_send_banner(ssh, &kex->client_version_string); + sshbuf_len(ssh->kex->server_version) != 0 && + sshbuf_len(ssh->kex->client_version) == 0) + r = _ssh_send_banner(ssh, ssh->kex->client_version); } if (r != 0) return r; /* start initial kex as soon as we have exchanged the banners */ - if (kex->server_version_string != NULL && - kex->client_version_string != NULL) { + if (sshbuf_len(ssh->kex->server_version) != 0 && + sshbuf_len(ssh->kex->client_version) != 0) { if ((r = _ssh_order_hostkeyalgs(ssh)) != 0 || (r = kex_send_kexinit(ssh)) != 0) return r; diff --git a/sshconnect.c b/sshconnect.c index 4862da5ed..884e33628 100644 --- a/sshconnect.c +++ b/sshconnect.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect.c,v 1.308 2018/11/18 22:43:29 dtucker Exp $ */ +/* $OpenBSD: sshconnect.c,v 1.309 2018/12/27 03:25:25 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -68,9 +68,8 @@ #include "authfile.h" #include "ssherr.h" #include "authfd.h" +#include "kex.h" -char *client_version_string = NULL; -char *server_version_string = NULL; struct sshkey *previous_host_key = NULL; static int matching_host_key_dns = 0; @@ -444,73 +443,6 @@ fail: return sock; } -/* - * Wait up to *timeoutp milliseconds for fd to be readable. Updates - * *timeoutp with time remaining. - * Returns 0 if fd ready or -1 on timeout or error (see errno). - */ -static int -waitrfd(int fd, int *timeoutp) -{ - struct pollfd pfd; - struct timeval t_start; - int oerrno, r; - - monotime_tv(&t_start); - pfd.fd = fd; - pfd.events = POLLIN; - for (; *timeoutp >= 0;) { - r = poll(&pfd, 1, *timeoutp); - oerrno = errno; - ms_subtract_diff(&t_start, timeoutp); - errno = oerrno; - if (r > 0) - return 0; - else if (r == -1 && errno != EAGAIN) - return -1; - else if (r == 0) - break; - } - /* timeout */ - errno = ETIMEDOUT; - return -1; -} - -static int -timeout_connect(int sockfd, const struct sockaddr *serv_addr, - socklen_t addrlen, int *timeoutp) -{ - int optval = 0; - socklen_t optlen = sizeof(optval); - - /* No timeout: just do a blocking connect() */ - if (*timeoutp <= 0) - return connect(sockfd, serv_addr, addrlen); - - set_nonblock(sockfd); - if (connect(sockfd, serv_addr, addrlen) == 0) { - /* Succeeded already? */ - unset_nonblock(sockfd); - return 0; - } else if (errno != EINPROGRESS) - return -1; - - if (waitrfd(sockfd, timeoutp) == -1) - return -1; - - /* Completed or failed */ - if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1) { - debug("getsockopt: %s", strerror(errno)); - return -1; - } - if (optval != 0) { - errno = optval; - return -1; - } - unset_nonblock(sockfd); - return 0; -} - /* * Opens a TCP/IP connection to the remote server on the given host. * The address of the remote host will be returned in hostaddr. @@ -629,110 +561,6 @@ ssh_connect(struct ssh *ssh, const char *host, struct addrinfo *addrs, return ssh_proxy_connect(ssh, host, port, options.proxy_command); } -static void -send_client_banner(int connection_out, int minor1) -{ - /* Send our own protocol version identification. */ - xasprintf(&client_version_string, "SSH-%d.%d-%.100s\r\n", - PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_VERSION); - if (atomicio(vwrite, connection_out, client_version_string, - strlen(client_version_string)) != strlen(client_version_string)) - fatal("write: %.100s", strerror(errno)); - chop(client_version_string); - debug("Local version string %.100s", client_version_string); -} - -/* - * Waits for the server identification string, and sends our own - * identification string. - */ -void -ssh_exchange_identification(int timeout_ms) -{ - char buf[256], remote_version[256]; /* must be same size! */ - int remote_major, remote_minor, mismatch; - int connection_in = packet_get_connection_in(); - int connection_out = packet_get_connection_out(); - u_int i, n; - size_t len; - int rc; - - send_client_banner(connection_out, 0); - - /* Read other side's version identification. */ - for (n = 0;;) { - for (i = 0; i < sizeof(buf) - 1; i++) { - if (timeout_ms > 0) { - rc = waitrfd(connection_in, &timeout_ms); - if (rc == -1 && errno == ETIMEDOUT) { - fatal("Connection timed out during " - "banner exchange"); - } else if (rc == -1) { - fatal("%s: %s", - __func__, strerror(errno)); - } - } - - len = atomicio(read, connection_in, &buf[i], 1); - if (len != 1 && errno == EPIPE) - fatal("ssh_exchange_identification: " - "Connection closed by remote host"); - else if (len != 1) - fatal("ssh_exchange_identification: " - "read: %.100s", strerror(errno)); - if (buf[i] == '\r') { - buf[i] = '\n'; - buf[i + 1] = 0; - continue; /**XXX wait for \n */ - } - if (buf[i] == '\n') { - buf[i + 1] = 0; - break; - } - if (++n > 65536) - fatal("ssh_exchange_identification: " - "No banner received"); - } - buf[sizeof(buf) - 1] = 0; - if (strncmp(buf, "SSH-", 4) == 0) - break; - debug("ssh_exchange_identification: %s", buf); - } - server_version_string = xstrdup(buf); - - /* - * Check that the versions match. In future this might accept - * several versions and set appropriate flags to handle them. - */ - if (sscanf(server_version_string, "SSH-%d.%d-%[^\n]\n", - &remote_major, &remote_minor, remote_version) != 3) - fatal("Bad remote protocol version identification: '%.100s'", buf); - debug("Remote protocol version %d.%d, remote software version %.100s", - remote_major, remote_minor, remote_version); - - active_state->compat = compat_datafellows(remote_version); - mismatch = 0; - - switch (remote_major) { - case 2: - break; - case 1: - if (remote_minor != 99) - mismatch = 1; - break; - default: - mismatch = 1; - break; - } - if (mismatch) - fatal("Protocol major versions differ: %d vs. %d", - PROTOCOL_MAJOR_2, remote_major); - if ((datafellows & SSH_BUG_RSASIGMD5) != 0) - logit("Server version \"%.100s\" uses unsafe RSA signature " - "scheme; disabling use of RSA keys", remote_version); - chop(server_version_string); -} - /* defaults to 'no' */ static int confirm(const char *prompt) @@ -1426,7 +1254,7 @@ out: * This function does not require super-user privileges. */ void -ssh_login(Sensitive *sensitive, const char *orighost, +ssh_login(struct ssh *ssh, Sensitive *sensitive, const char *orighost, struct sockaddr *hostaddr, u_short port, struct passwd *pw, int timeout_ms) { char *host; @@ -1440,16 +1268,17 @@ ssh_login(Sensitive *sensitive, const char *orighost, lowercase(host); /* Exchange protocol version identification strings with the server. */ - ssh_exchange_identification(timeout_ms); + if (kex_exchange_identification(ssh, timeout_ms, NULL) != 0) + cleanup_exit(255); /* error already logged */ /* Put the connection into non-blocking mode. */ - packet_set_nonblocking(); + ssh_packet_set_nonblocking(ssh); /* key exchange */ /* authenticate user */ debug("Authenticating to %s:%d as '%s'", host, port, server_user); - ssh_kex2(host, hostaddr, port); - ssh_userauth2(local_user, server_user, host, sensitive); + ssh_kex2(ssh, host, hostaddr, port); + ssh_userauth2(ssh, local_user, server_user, host, sensitive); free(local_user); } diff --git a/sshconnect.h b/sshconnect.h index 890d85733..44a5071c7 100644 --- a/sshconnect.h +++ b/sshconnect.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect.h,v 1.35 2018/07/19 10:28:47 dtucker Exp $ */ +/* $OpenBSD: sshconnect.h,v 1.36 2018/12/27 03:25:25 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. @@ -37,21 +37,18 @@ int ssh_connect(struct ssh *, const char *, struct addrinfo *, struct sockaddr_storage *, u_short, int, int, int *, int); void ssh_kill_proxy_command(void); -void ssh_login(Sensitive *, const char *, struct sockaddr *, u_short, - struct passwd *, int); - -void ssh_exchange_identification(int); +void ssh_login(struct ssh *, Sensitive *, const char *, + struct sockaddr *, u_short, struct passwd *, int); int verify_host_key(char *, struct sockaddr *, struct sshkey *); void get_hostfile_hostname_ipaddr(char *, struct sockaddr *, u_short, char **, char **); -void ssh_kex(char *, struct sockaddr *); -void ssh_kex2(char *, struct sockaddr *, u_short); +void ssh_kex2(struct ssh *ssh, char *, struct sockaddr *, u_short); -void ssh_userauth1(const char *, const char *, char *, Sensitive *); -void ssh_userauth2(const char *, const char *, char *, Sensitive *); +void ssh_userauth2(struct ssh *ssh, const char *, const char *, + char *, Sensitive *); void ssh_put_password(char *); int ssh_local_cmd(const char *); diff --git a/sshconnect2.c b/sshconnect2.c index adb4e4cbd..19caebabc 100644 --- a/sshconnect2.c +++ b/sshconnect2.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect2.c,v 1.290 2018/11/28 06:00:38 djm Exp $ */ +/* $OpenBSD: sshconnect2.c,v 1.291 2018/12/27 03:25:25 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2008 Damien Miller. All rights reserved. @@ -155,11 +155,10 @@ order_hostkeyalgs(char *host, struct sockaddr *hostaddr, u_short port) } void -ssh_kex2(char *host, struct sockaddr *hostaddr, u_short port) +ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port) { char *myproposal[PROPOSAL_MAX] = { KEX_CLIENT }; char *s, *all_key; - struct kex *kex; int r; xxx_host = host; @@ -199,36 +198,33 @@ ssh_kex2(char *host, struct sockaddr *hostaddr, u_short port) options.rekey_interval); /* start key exchange */ - if ((r = kex_setup(active_state, myproposal)) != 0) + if ((r = kex_setup(ssh, myproposal)) != 0) fatal("kex_setup: %s", ssh_err(r)); - kex = active_state->kex; #ifdef WITH_OPENSSL - kex->kex[KEX_DH_GRP1_SHA1] = kexdh_client; - kex->kex[KEX_DH_GRP14_SHA1] = kexdh_client; - kex->kex[KEX_DH_GRP14_SHA256] = kexdh_client; - kex->kex[KEX_DH_GRP16_SHA512] = kexdh_client; - kex->kex[KEX_DH_GRP18_SHA512] = kexdh_client; - kex->kex[KEX_DH_GEX_SHA1] = kexgex_client; - kex->kex[KEX_DH_GEX_SHA256] = kexgex_client; + ssh->kex->kex[KEX_DH_GRP1_SHA1] = kexdh_client; + ssh->kex->kex[KEX_DH_GRP14_SHA1] = kexdh_client; + ssh->kex->kex[KEX_DH_GRP14_SHA256] = kexdh_client; + ssh->kex->kex[KEX_DH_GRP16_SHA512] = kexdh_client; + ssh->kex->kex[KEX_DH_GRP18_SHA512] = kexdh_client; + ssh->kex->kex[KEX_DH_GEX_SHA1] = kexgex_client; + ssh->kex->kex[KEX_DH_GEX_SHA256] = kexgex_client; # ifdef OPENSSL_HAS_ECC - kex->kex[KEX_ECDH_SHA2] = kexecdh_client; + ssh->kex->kex[KEX_ECDH_SHA2] = kexecdh_client; # endif #endif - kex->kex[KEX_C25519_SHA256] = kexc25519_client; - kex->client_version_string=client_version_string; - kex->server_version_string=server_version_string; - kex->verify_host_key=&verify_host_key_callback; + ssh->kex->kex[KEX_C25519_SHA256] = kexc25519_client; + ssh->kex->verify_host_key=&verify_host_key_callback; - ssh_dispatch_run_fatal(active_state, DISPATCH_BLOCK, &kex->done); + ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &ssh->kex->done); /* remove ext-info from the KEX proposals for rekeying */ myproposal[PROPOSAL_KEX_ALGS] = compat_kex_proposal(options.kex_algorithms); - if ((r = kex_prop2buf(kex->my, myproposal)) != 0) + if ((r = kex_prop2buf(ssh->kex->my, myproposal)) != 0) fatal("kex_prop2buf: %s", ssh_err(r)); - session_id2 = kex->session_id; - session_id2_len = kex->session_id_len; + session_id2 = ssh->kex->session_id; + session_id2_len = ssh->kex->session_id_len; #ifdef DEBUG_KEXDH /* send 1st encrypted/maced/compressed message */ @@ -365,10 +361,9 @@ Authmethod authmethods[] = { }; void -ssh_userauth2(const char *local_user, const char *server_user, char *host, - Sensitive *sensitive) +ssh_userauth2(struct ssh *ssh, const char *local_user, + const char *server_user, char *host, Sensitive *sensitive) { - struct ssh *ssh = active_state; Authctxt authctxt; int r; @@ -392,8 +387,10 @@ ssh_userauth2(const char *local_user, const char *server_user, char *host, authctxt.info_req_seen = 0; authctxt.agent_fd = -1; pubkey_prepare(&authctxt); - if (authctxt.method == NULL) - fatal("ssh_userauth2: internal error: cannot send userauth none request"); + if (authctxt.method == NULL) { + fatal("%s: internal error: cannot send userauth none request", + __func__); + } if ((r = sshpkt_start(ssh, SSH2_MSG_SERVICE_REQUEST)) != 0 || (r = sshpkt_put_cstring(ssh, "ssh-userauth")) != 0 || diff --git a/sshd.c b/sshd.c index fb9d9b60f..3461383a0 100644 --- a/sshd.c +++ b/sshd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshd.c,v 1.519 2018/11/19 04:12:32 djm Exp $ */ +/* $OpenBSD: sshd.c,v 1.520 2018/12/27 03:25:25 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -180,13 +180,6 @@ char **rexec_argv; int listen_socks[MAX_LISTEN_SOCKS]; int num_listen_socks = 0; -/* - * the client's version string, passed by sshd2 in compat mode. if != NULL, - * sshd will skip the version-number exchange - */ -char *client_version_string = NULL; -char *server_version_string = NULL; - /* Daemon's agent connection */ int auth_sock = -1; int have_agent = 0; @@ -363,108 +356,6 @@ grace_alarm_handler(int sig) ssh_remote_ipaddr(active_state), ssh_remote_port(active_state)); } -static void -sshd_exchange_identification(struct ssh *ssh, int sock_in, int sock_out) -{ - u_int i; - int remote_major, remote_minor; - char *s; - char buf[256]; /* Must not be larger than remote_version. */ - char remote_version[256]; /* Must be at least as big as buf. */ - - xasprintf(&server_version_string, "SSH-%d.%d-%.100s%s%s\r\n", - PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_VERSION, - *options.version_addendum == '\0' ? "" : " ", - options.version_addendum); - - /* Send our protocol version identification. */ - if (atomicio(vwrite, sock_out, server_version_string, - strlen(server_version_string)) - != strlen(server_version_string)) { - logit("Could not write ident string to %s port %d", - ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); - cleanup_exit(255); - } - - /* Read other sides version identification. */ - memset(buf, 0, sizeof(buf)); - for (i = 0; i < sizeof(buf) - 1; i++) { - if (atomicio(read, sock_in, &buf[i], 1) != 1) { - logit("Did not receive identification string " - "from %s port %d", - ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); - cleanup_exit(255); - } - if (buf[i] == '\r') { - buf[i] = 0; - /* Kludge for F-Secure Macintosh < 1.0.2 */ - if (i == 12 && - strncmp(buf, "SSH-1.5-W1.0", 12) == 0) - break; - continue; - } - if (buf[i] == '\n') { - buf[i] = 0; - break; - } - } - buf[sizeof(buf) - 1] = 0; - client_version_string = xstrdup(buf); - - /* - * Check that the versions match. In future this might accept - * several versions and set appropriate flags to handle them. - */ - if (sscanf(client_version_string, "SSH-%d.%d-%[^\n]\n", - &remote_major, &remote_minor, remote_version) != 3) { - s = "Protocol mismatch.\n"; - (void) atomicio(vwrite, sock_out, s, strlen(s)); - logit("Bad protocol version identification '%.100s' " - "from %s port %d", client_version_string, - ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); - close(sock_in); - close(sock_out); - cleanup_exit(255); - } - debug("Client protocol version %d.%d; client software version %.100s", - remote_major, remote_minor, remote_version); - - ssh->compat = compat_datafellows(remote_version); - - if ((ssh->compat & SSH_BUG_PROBE) != 0) { - logit("probed from %s port %d with %s. Don't panic.", - ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), - client_version_string); - cleanup_exit(255); - } - if ((ssh->compat & SSH_BUG_SCANNER) != 0) { - logit("scanned from %s port %d with %s. Don't panic.", - ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), - client_version_string); - cleanup_exit(255); - } - if ((ssh->compat & SSH_BUG_RSASIGMD5) != 0) { - logit("Client version \"%.100s\" uses unsafe RSA signature " - "scheme; disabling use of RSA keys", remote_version); - } - - chop(server_version_string); - debug("Local version string %.200s", server_version_string); - - if (remote_major != 2 && - !(remote_major == 1 && remote_minor == 99)) { - s = "Protocol major versions differ.\n"; - (void) atomicio(vwrite, sock_out, s, strlen(s)); - close(sock_in); - close(sock_out); - logit("Protocol major versions differ for %s port %d: " - "%.200s vs. %.200s", - ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), - server_version_string, client_version_string); - cleanup_exit(255); - } -} - /* Destroy the host and server keys. They will no longer be needed. */ void destroy_sensitive_data(void) @@ -2115,7 +2006,9 @@ main(int ac, char **av) if (!debug_flag) alarm(options.login_grace_time); - sshd_exchange_identification(ssh, sock_in, sock_out); + if (kex_exchange_identification(ssh, -1, options.version_addendum) != 0) + cleanup_exit(255); /* error already logged */ + packet_set_nonblocking(); /* allocate authentication context */ @@ -2303,9 +2196,6 @@ do_ssh2_kex(void) # endif #endif kex->kex[KEX_C25519_SHA256] = kexc25519_server; - kex->server = 1; - kex->client_version_string=client_version_string; - kex->server_version_string=server_version_string; kex->load_host_public_key=&get_hostkey_public_by_type; kex->load_host_private_key=&get_hostkey_private_by_type; kex->host_key_index=&get_hostkey_index; -- cgit v1.2.3 From 5ae3f6d314465026d028af82609c1d49ad197655 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 21 Jan 2019 09:55:52 +0000 Subject: upstream: save the derived session id in kex_derive_keys() rather than making each kex method implementation do it. from markus@ ok djm@ OpenBSD-Commit-ID: d61ade9c8d1e13f665f8663c552abff8c8a30673 --- kex.c | 10 +++++++++- kexc25519c.c | 13 +------------ kexc25519s.c | 13 +------------ kexdhc.c | 13 +------------ kexdhs.c | 13 +------------ kexecdhc.c | 13 +------------ kexecdhs.c | 13 +------------ kexgexc.c | 13 +------------ kexgexs.c | 13 +------------ 9 files changed, 17 insertions(+), 97 deletions(-) (limited to 'kex.c') diff --git a/kex.c b/kex.c index 30e1c261d..0d5618ecc 100644 --- a/kex.c +++ b/kex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.c,v 1.143 2018/12/27 03:25:25 djm Exp $ */ +/* $OpenBSD: kex.c,v 1.144 2019/01/21 09:55:52 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * @@ -1009,6 +1009,14 @@ kex_derive_keys(struct ssh *ssh, u_char *hash, u_int hashlen, u_int i, j, mode, ctos; int r; + /* save initial hash as session id */ + if (kex->session_id == NULL) { + kex->session_id_len = hashlen; + kex->session_id = malloc(kex->session_id_len); + if (kex->session_id == NULL) + return SSH_ERR_ALLOC_FAIL; + memcpy(kex->session_id, hash, kex->session_id_len); + } for (i = 0; i < NKEYS; i++) { if ((r = derive_key(ssh, 'A'+i, kex->we_need, hash, hashlen, shared_secret, &keys[i])) != 0) { diff --git a/kexc25519c.c b/kexc25519c.c index 75e7d8c57..59b4e4cc0 100644 --- a/kexc25519c.c +++ b/kexc25519c.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexc25519c.c,v 1.10 2018/12/27 03:25:25 djm Exp $ */ +/* $OpenBSD: kexc25519c.c,v 1.11 2019/01/21 09:55:52 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -144,17 +144,6 @@ input_kex_c25519_reply(int type, u_int32_t seq, struct ssh *ssh) kex->hostkey_alg, ssh->compat)) != 0) goto out; - /* save session id */ - if (kex->session_id == NULL) { - kex->session_id_len = hashlen; - kex->session_id = malloc(kex->session_id_len); - if (kex->session_id == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto out; - } - memcpy(kex->session_id, hash, kex->session_id_len); - } - if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) r = kex_send_newkeys(ssh); out: diff --git a/kexc25519s.c b/kexc25519s.c index 9ff74d912..65df18c4b 100644 --- a/kexc25519s.c +++ b/kexc25519s.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexc25519s.c,v 1.13 2019/01/19 21:43:56 djm Exp $ */ +/* $OpenBSD: kexc25519s.c,v 1.14 2019/01/21 09:55:52 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -121,17 +121,6 @@ input_kex_c25519_init(int type, u_int32_t seq, struct ssh *ssh) hash, &hashlen)) < 0) goto out; - /* save session id := H */ - if (kex->session_id == NULL) { - kex->session_id_len = hashlen; - kex->session_id = malloc(kex->session_id_len); - if (kex->session_id == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto out; - } - memcpy(kex->session_id, hash, kex->session_id_len); - } - /* sign H */ if ((r = kex->sign(ssh, server_host_private, server_host_public, &signature, &slen, hash, hashlen, kex->hostkey_alg)) < 0) diff --git a/kexdhc.c b/kexdhc.c index 236075eec..a37452abd 100644 --- a/kexdhc.c +++ b/kexdhc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexdhc.c,v 1.25 2019/01/21 09:54:11 djm Exp $ */ +/* $OpenBSD: kexdhc.c,v 1.26 2019/01/21 09:55:52 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * @@ -188,17 +188,6 @@ input_kex_dh(int type, u_int32_t seq, struct ssh *ssh) kex->hostkey_alg, ssh->compat)) != 0) goto out; - /* save session id */ - if (kex->session_id == NULL) { - kex->session_id_len = hashlen; - kex->session_id = malloc(kex->session_id_len); - if (kex->session_id == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto out; - } - memcpy(kex->session_id, hash, kex->session_id_len); - } - if ((r = kex_derive_keys_bn(ssh, hash, hashlen, shared_secret)) == 0) r = kex_send_newkeys(ssh); out: diff --git a/kexdhs.c b/kexdhs.c index 4e4872580..b7b64a82a 100644 --- a/kexdhs.c +++ b/kexdhs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexdhs.c,v 1.31 2019/01/21 09:54:11 djm Exp $ */ +/* $OpenBSD: kexdhs.c,v 1.32 2019/01/21 09:55:52 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * @@ -173,17 +173,6 @@ input_kex_dh_init(int type, u_int32_t seq, struct ssh *ssh) hash, &hashlen)) != 0) goto out; - /* save session id := H */ - if (kex->session_id == NULL) { - kex->session_id_len = hashlen; - kex->session_id = malloc(kex->session_id_len); - if (kex->session_id == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto out; - } - memcpy(kex->session_id, hash, kex->session_id_len); - } - /* sign H */ if ((r = kex->sign(ssh, server_host_private, server_host_public, &signature, &slen, hash, hashlen, kex->hostkey_alg)) < 0) diff --git a/kexecdhc.c b/kexecdhc.c index af556dc58..2cff34347 100644 --- a/kexecdhc.c +++ b/kexecdhc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexecdhc.c,v 1.14 2018/12/27 03:25:25 djm Exp $ */ +/* $OpenBSD: kexecdhc.c,v 1.15 2019/01/21 09:55:52 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -190,17 +190,6 @@ input_kex_ecdh_reply(int type, u_int32_t seq, struct ssh *ssh) hashlen, kex->hostkey_alg, ssh->compat)) != 0) goto out; - /* save session id */ - if (kex->session_id == NULL) { - kex->session_id_len = hashlen; - kex->session_id = malloc(kex->session_id_len); - if (kex->session_id == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto out; - } - memcpy(kex->session_id, hash, kex->session_id_len); - } - if ((r = kex_derive_keys_bn(ssh, hash, hashlen, shared_secret)) == 0) r = kex_send_newkeys(ssh); out: diff --git a/kexecdhs.c b/kexecdhs.c index 45ac3f794..4ba2072df 100644 --- a/kexecdhs.c +++ b/kexecdhs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexecdhs.c,v 1.19 2019/01/19 21:43:56 djm Exp $ */ +/* $OpenBSD: kexecdhs.c,v 1.20 2019/01/21 09:55:52 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -156,17 +156,6 @@ input_kex_ecdh_init(int type, u_int32_t seq, struct ssh *ssh) hash, &hashlen)) != 0) goto out; - /* save session id := H */ - if (kex->session_id == NULL) { - kex->session_id_len = hashlen; - kex->session_id = malloc(kex->session_id_len); - if (kex->session_id == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto out; - } - memcpy(kex->session_id, hash, kex->session_id_len); - } - /* sign H */ if ((r = kex->sign(ssh, server_host_private, server_host_public, &signature, &slen, hash, hashlen, kex->hostkey_alg)) < 0) diff --git a/kexgexc.c b/kexgexc.c index dec01fd4f..0425309d4 100644 --- a/kexgexc.c +++ b/kexgexc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexgexc.c,v 1.30 2019/01/21 09:54:11 djm Exp $ */ +/* $OpenBSD: kexgexc.c,v 1.31 2019/01/21 09:55:52 djm Exp $ */ /* * Copyright (c) 2000 Niels Provos. All rights reserved. * Copyright (c) 2001 Markus Friedl. All rights reserved. @@ -229,17 +229,6 @@ input_kex_dh_gex_reply(int type, u_int32_t seq, struct ssh *ssh) hashlen, kex->hostkey_alg, ssh->compat)) != 0) goto out; - /* save session id */ - if (kex->session_id == NULL) { - kex->session_id_len = hashlen; - kex->session_id = malloc(kex->session_id_len); - if (kex->session_id == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto out; - } - memcpy(kex->session_id, hash, kex->session_id_len); - } - if ((r = kex_derive_keys_bn(ssh, hash, hashlen, shared_secret)) == 0) r = kex_send_newkeys(ssh); out: diff --git a/kexgexs.c b/kexgexs.c index 2a8997302..4ffbb1918 100644 --- a/kexgexs.c +++ b/kexgexs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexgexs.c,v 1.38 2019/01/21 09:54:11 djm Exp $ */ +/* $OpenBSD: kexgexs.c,v 1.39 2019/01/21 09:55:52 djm Exp $ */ /* * Copyright (c) 2000 Niels Provos. All rights reserved. * Copyright (c) 2001 Markus Friedl. All rights reserved. @@ -207,17 +207,6 @@ input_kex_dh_gex_init(int type, u_int32_t seq, struct ssh *ssh) hash, &hashlen)) != 0) goto out; - /* save session id := H */ - if (kex->session_id == NULL) { - kex->session_id_len = hashlen; - kex->session_id = malloc(kex->session_id_len); - if (kex->session_id == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto out; - } - memcpy(kex->session_id, hash, kex->session_id_len); - } - /* sign H */ if ((r = kex->sign(ssh, server_host_private, server_host_public, &signature, &slen, hash, hashlen, kex->hostkey_alg)) < 0) -- cgit v1.2.3 From bb39bafb6dc520cc097780f4611a52da7f19c3e2 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 21 Jan 2019 10:05:09 +0000 Subject: upstream: factor out kex_load_hostkey() - this is duplicated in both the client and server implementations for most KEX methods. from markus@ ok djm@ OpenBSD-Commit-ID: 8232fa7c21fbfbcaf838313b0c166dc6c8762f3c --- kex.c | 20 +++++++++++++++++++- kex.h | 3 ++- kexc25519s.c | 17 +++-------------- kexdhs.c | 16 +++------------- kexecdhs.c | 16 +++------------- kexgexs.c | 16 +++------------- 6 files changed, 33 insertions(+), 55 deletions(-) (limited to 'kex.c') diff --git a/kex.c b/kex.c index 0d5618ecc..a0d13a880 100644 --- a/kex.c +++ b/kex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.c,v 1.144 2019/01/21 09:55:52 djm Exp $ */ +/* $OpenBSD: kex.c,v 1.145 2019/01/21 10:05:09 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * @@ -1052,6 +1052,24 @@ kex_derive_keys_bn(struct ssh *ssh, u_char *hash, u_int hashlen, } #endif +int +kex_load_hostkey(struct ssh *ssh, struct sshkey **pubp, struct sshkey **prvp) +{ + struct kex *kex = ssh->kex; + + *pubp = NULL; + *prvp = NULL; + if (kex->load_host_public_key == NULL || + kex->load_host_private_key == NULL) + return SSH_ERR_INVALID_ARGUMENT; + *pubp = kex->load_host_public_key(kex->hostkey_type, + kex->hostkey_nid, ssh); + *prvp = kex->load_host_private_key(kex->hostkey_type, + kex->hostkey_nid, ssh); + if (*pubp == NULL) + return SSH_ERR_NO_HOSTKEY_LOADED; + return 0; +} #if defined(DEBUG_KEX) || defined(DEBUG_KEXDH) || defined(DEBUG_KEXECDH) void diff --git a/kex.h b/kex.h index a11bd5ae6..fa65b8657 100644 --- a/kex.h +++ b/kex.h @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.h,v 1.96 2019/01/21 10:03:37 djm Exp $ */ +/* $OpenBSD: kex.h,v 1.97 2019/01/21 10:05:09 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -184,6 +184,7 @@ void kex_free(struct kex *); int kex_buf2prop(struct sshbuf *, int *, char ***); int kex_prop2buf(struct sshbuf *, char *proposal[PROPOSAL_MAX]); void kex_prop_free(char **); +int kex_load_hostkey(struct ssh *, struct sshkey **, struct sshkey **); int kex_send_kexinit(struct ssh *); int kex_input_kexinit(int, u_int32_t, struct ssh *); diff --git a/kexc25519s.c b/kexc25519s.c index 65df18c4b..d7cc70fee 100644 --- a/kexc25519s.c +++ b/kexc25519s.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexc25519s.c,v 1.14 2019/01/21 09:55:52 djm Exp $ */ +/* $OpenBSD: kexc25519s.c,v 1.15 2019/01/21 10:05:09 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -70,20 +70,9 @@ input_kex_c25519_init(int type, u_int32_t seq, struct ssh *ssh) #ifdef DEBUG_KEXECDH dump_digest("server private key:", server_key, sizeof(server_key)); #endif - if (kex->load_host_public_key == NULL || - kex->load_host_private_key == NULL) { - r = SSH_ERR_INVALID_ARGUMENT; + if ((r = kex_load_hostkey(ssh, &server_host_private, + &server_host_public)) != 0) goto out; - } - server_host_public = kex->load_host_public_key(kex->hostkey_type, - kex->hostkey_nid, ssh); - server_host_private = kex->load_host_private_key(kex->hostkey_type, - kex->hostkey_nid, ssh); - if (server_host_public == NULL) { - r = SSH_ERR_NO_HOSTKEY_LOADED; - goto out; - } - if ((r = sshpkt_get_string(ssh, &client_pubkey, &pklen)) != 0 || (r = sshpkt_get_end(ssh)) != 0) goto out; diff --git a/kexdhs.c b/kexdhs.c index 0f028aaeb..e33901bbf 100644 --- a/kexdhs.c +++ b/kexdhs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexdhs.c,v 1.34 2019/01/21 10:03:37 djm Exp $ */ +/* $OpenBSD: kexdhs.c,v 1.35 2019/01/21 10:05:09 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * @@ -81,19 +81,9 @@ input_kex_dh_init(int type, u_int32_t seq, struct ssh *ssh) size_t hashlen; int r; - if (kex->load_host_public_key == NULL || - kex->load_host_private_key == NULL) { - r = SSH_ERR_INVALID_ARGUMENT; + if ((r = kex_load_hostkey(ssh, &server_host_private, + &server_host_public)) != 0) goto out; - } - server_host_public = kex->load_host_public_key(kex->hostkey_type, - kex->hostkey_nid, ssh); - server_host_private = kex->load_host_private_key(kex->hostkey_type, - kex->hostkey_nid, ssh); - if (server_host_public == NULL) { - r = SSH_ERR_NO_HOSTKEY_LOADED; - goto out; - } /* key, cert */ if ((r = sshpkt_get_bignum2(ssh, &dh_client_pub)) != 0 || diff --git a/kexecdhs.c b/kexecdhs.c index 4ba2072df..b9254eed7 100644 --- a/kexecdhs.c +++ b/kexecdhs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexecdhs.c,v 1.20 2019/01/21 09:55:52 djm Exp $ */ +/* $OpenBSD: kexecdhs.c,v 1.21 2019/01/21 10:05:09 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -89,19 +89,9 @@ input_kex_ecdh_init(int type, u_int32_t seq, struct ssh *ssh) sshkey_dump_ec_key(server_key); #endif - if (kex->load_host_public_key == NULL || - kex->load_host_private_key == NULL) { - r = SSH_ERR_INVALID_ARGUMENT; + if ((r = kex_load_hostkey(ssh, &server_host_private, + &server_host_public)) != 0) goto out; - } - server_host_public = kex->load_host_public_key(kex->hostkey_type, - kex->hostkey_nid, ssh); - server_host_private = kex->load_host_private_key(kex->hostkey_type, - kex->hostkey_nid, ssh); - if (server_host_public == NULL) { - r = SSH_ERR_NO_HOSTKEY_LOADED; - goto out; - } if ((client_public = EC_POINT_new(group)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; diff --git a/kexgexs.c b/kexgexs.c index f8eb36545..a617d4453 100644 --- a/kexgexs.c +++ b/kexgexs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexgexs.c,v 1.40 2019/01/21 10:03:37 djm Exp $ */ +/* $OpenBSD: kexgexs.c,v 1.41 2019/01/21 10:05:09 djm Exp $ */ /* * Copyright (c) 2000 Niels Provos. All rights reserved. * Copyright (c) 2001 Markus Friedl. All rights reserved. @@ -136,19 +136,9 @@ input_kex_dh_gex_init(int type, u_int32_t seq, struct ssh *ssh) size_t hashlen; int r; - if (kex->load_host_public_key == NULL || - kex->load_host_private_key == NULL) { - r = SSH_ERR_INVALID_ARGUMENT; + if ((r = kex_load_hostkey(ssh, &server_host_private, + &server_host_public)) != 0) goto out; - } - server_host_public = kex->load_host_public_key(kex->hostkey_type, - kex->hostkey_nid, ssh); - server_host_private = kex->load_host_private_key(kex->hostkey_type, - kex->hostkey_nid, ssh); - if (server_host_public == NULL) { - r = SSH_ERR_NO_HOSTKEY_LOADED; - goto out; - } /* key, cert */ if ((r = sshpkt_get_bignum2(ssh, &dh_client_pub)) != 0 || -- cgit v1.2.3 From b1b2ff4ed559051d1035419f8f236275fa66d5d6 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 21 Jan 2019 10:07:22 +0000 Subject: upstream: factor out kex_verify_hostkey() - again, duplicated almost exactly across client and server for several KEX methods. from markus@ ok djm@ OpenBSD-Commit-ID: 4e4a16d949dadde002a0aacf6d280a684e20829c --- kex.c | 18 +++++++++++++++++- kex.h | 3 ++- kexc25519c.c | 17 ++--------------- kexdhc.c | 16 ++-------------- kexecdhc.c | 16 ++-------------- kexgexc.c | 16 ++-------------- 6 files changed, 27 insertions(+), 59 deletions(-) (limited to 'kex.c') diff --git a/kex.c b/kex.c index a0d13a880..d8c71bb3e 100644 --- a/kex.c +++ b/kex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.c,v 1.145 2019/01/21 10:05:09 djm Exp $ */ +/* $OpenBSD: kex.c,v 1.146 2019/01/21 10:07:22 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * @@ -1071,6 +1071,22 @@ kex_load_hostkey(struct ssh *ssh, struct sshkey **pubp, struct sshkey **prvp) return 0; } +int +kex_verify_host_key(struct ssh *ssh, struct sshkey *server_host_key) +{ + struct kex *kex = ssh->kex; + + if (kex->verify_host_key == NULL) + return SSH_ERR_INVALID_ARGUMENT; + if (server_host_key->type != kex->hostkey_type || + (kex->hostkey_type == KEY_ECDSA && + server_host_key->ecdsa_nid != kex->hostkey_nid)) + return SSH_ERR_KEY_TYPE_MISMATCH; + if (kex->verify_host_key(server_host_key, ssh) == -1) + return SSH_ERR_SIGNATURE_INVALID; + return 0; +} + #if defined(DEBUG_KEX) || defined(DEBUG_KEXDH) || defined(DEBUG_KEXECDH) void dump_digest(char *msg, u_char *digest, int len) diff --git a/kex.h b/kex.h index fa65b8657..e404d0365 100644 --- a/kex.h +++ b/kex.h @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.h,v 1.97 2019/01/21 10:05:09 djm Exp $ */ +/* $OpenBSD: kex.h,v 1.98 2019/01/21 10:07:22 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -185,6 +185,7 @@ int kex_buf2prop(struct sshbuf *, int *, char ***); int kex_prop2buf(struct sshbuf *, char *proposal[PROPOSAL_MAX]); void kex_prop_free(char **); int kex_load_hostkey(struct ssh *, struct sshkey **, struct sshkey **); +int kex_verify_host_key(struct ssh *, struct sshkey *); int kex_send_kexinit(struct ssh *); int kex_input_kexinit(int, u_int32_t, struct ssh *); diff --git a/kexc25519c.c b/kexc25519c.c index 59b4e4cc0..1c7f79000 100644 --- a/kexc25519c.c +++ b/kexc25519c.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexc25519c.c,v 1.11 2019/01/21 09:55:52 djm Exp $ */ +/* $OpenBSD: kexc25519c.c,v 1.12 2019/01/21 10:07:22 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -80,27 +80,14 @@ input_kex_c25519_reply(int type, u_int32_t seq, struct ssh *ssh) size_t slen, pklen, sbloblen, hashlen; int r; - if (kex->verify_host_key == NULL) { - r = SSH_ERR_INVALID_ARGUMENT; - goto out; - } - /* hostkey */ if ((r = sshpkt_get_string(ssh, &server_host_key_blob, &sbloblen)) != 0 || (r = sshkey_from_blob(server_host_key_blob, sbloblen, &server_host_key)) != 0) goto out; - if (server_host_key->type != kex->hostkey_type || - (kex->hostkey_type == KEY_ECDSA && - server_host_key->ecdsa_nid != kex->hostkey_nid)) { - r = SSH_ERR_KEY_TYPE_MISMATCH; + if ((r = kex_verify_host_key(ssh, server_host_key)) != 0) goto out; - } - if (kex->verify_host_key(server_host_key, ssh) == -1) { - r = SSH_ERR_SIGNATURE_INVALID; - goto out; - } /* Q_S, server public key */ /* signed H */ diff --git a/kexdhc.c b/kexdhc.c index 2e26f22ea..a2af8cb08 100644 --- a/kexdhc.c +++ b/kexdhc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexdhc.c,v 1.28 2019/01/21 10:03:37 djm Exp $ */ +/* $OpenBSD: kexdhc.c,v 1.29 2019/01/21 10:07:22 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * @@ -95,26 +95,14 @@ input_kex_dh(int type, u_int32_t seq, struct ssh *ssh) size_t slen, sbloblen, hashlen; int r; - if (kex->verify_host_key == NULL) { - r = SSH_ERR_INVALID_ARGUMENT; - goto out; - } /* key, cert */ if ((r = sshpkt_get_string(ssh, &server_host_key_blob, &sbloblen)) != 0 || (r = sshkey_from_blob(server_host_key_blob, sbloblen, &server_host_key)) != 0) goto out; - if (server_host_key->type != kex->hostkey_type || - (kex->hostkey_type == KEY_ECDSA && - server_host_key->ecdsa_nid != kex->hostkey_nid)) { - r = SSH_ERR_KEY_TYPE_MISMATCH; + if ((r = kex_verify_host_key(ssh, server_host_key)) != 0) goto out; - } - if (kex->verify_host_key(server_host_key, ssh) == -1) { - r = SSH_ERR_SIGNATURE_INVALID; - goto out; - } /* DH parameter f, server public DH key, signed H */ if ((r = sshpkt_get_bignum2(ssh, &dh_server_pub)) != 0 || (r = sshpkt_get_string(ssh, &signature, &slen)) != 0 || diff --git a/kexecdhc.c b/kexecdhc.c index 2cff34347..bfb9f4707 100644 --- a/kexecdhc.c +++ b/kexecdhc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexecdhc.c,v 1.15 2019/01/21 09:55:52 djm Exp $ */ +/* $OpenBSD: kexecdhc.c,v 1.16 2019/01/21 10:07:22 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -109,10 +109,6 @@ input_kex_ecdh_reply(int type, u_int32_t seq, struct ssh *ssh) size_t klen = 0, hashlen; int r; - if (kex->verify_host_key == NULL) { - r = SSH_ERR_INVALID_ARGUMENT; - goto out; - } group = kex->ec_group; client_key = kex->ec_client_key; @@ -122,16 +118,8 @@ input_kex_ecdh_reply(int type, u_int32_t seq, struct ssh *ssh) (r = sshkey_from_blob(server_host_key_blob, sbloblen, &server_host_key)) != 0) goto out; - if (server_host_key->type != kex->hostkey_type || - (kex->hostkey_type == KEY_ECDSA && - server_host_key->ecdsa_nid != kex->hostkey_nid)) { - r = SSH_ERR_KEY_TYPE_MISMATCH; + if ((r = kex_verify_host_key(ssh, server_host_key)) != 0) goto out; - } - if (kex->verify_host_key(server_host_key, ssh) == -1) { - r = SSH_ERR_SIGNATURE_INVALID; - goto out; - } /* Q_S, server public key */ /* signed H */ diff --git a/kexgexc.c b/kexgexc.c index 600d91acc..ac42127af 100644 --- a/kexgexc.c +++ b/kexgexc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexgexc.c,v 1.32 2019/01/21 10:03:37 djm Exp $ */ +/* $OpenBSD: kexgexc.c,v 1.33 2019/01/21 10:07:22 djm Exp $ */ /* * Copyright (c) 2000 Niels Provos. All rights reserved. * Copyright (c) 2001 Markus Friedl. All rights reserved. @@ -153,26 +153,14 @@ input_kex_dh_gex_reply(int type, u_int32_t seq, struct ssh *ssh) int r; debug("got SSH2_MSG_KEX_DH_GEX_REPLY"); - if (kex->verify_host_key == NULL) { - r = SSH_ERR_INVALID_ARGUMENT; - goto out; - } /* key, cert */ if ((r = sshpkt_get_string(ssh, &server_host_key_blob, &sbloblen)) != 0 || (r = sshkey_from_blob(server_host_key_blob, sbloblen, &server_host_key)) != 0) goto out; - if (server_host_key->type != kex->hostkey_type || - (kex->hostkey_type == KEY_ECDSA && - server_host_key->ecdsa_nid != kex->hostkey_nid)) { - r = SSH_ERR_KEY_TYPE_MISMATCH; + if ((r = kex_verify_host_key(ssh, server_host_key)) != 0) goto out; - } - if (kex->verify_host_key(server_host_key, ssh) == -1) { - r = SSH_ERR_SIGNATURE_INVALID; - goto out; - } /* DH parameter f, server public DH key, signed H */ if ((r = sshpkt_get_bignum2(ssh, &dh_server_pub)) != 0 || (r = sshpkt_get_string(ssh, &signature, &slen)) != 0 || -- cgit v1.2.3 From dfd591618cdf2c96727ac0eb65f89cf54af0d97e Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 21 Jan 2019 10:20:12 +0000 Subject: upstream: Add support for a PQC KEX/KEM: sntrup4591761x25519-sha512@tinyssh.org using the Streamlined NTRU Prime 4591^761 implementation from SUPERCOP coupled with X25519 as a stop-loss. Not enabled by default. introduce KEM API; a simplified framework for DH-ish KEX methods. from markus@ feedback & ok djm@ OpenBSD-Commit-ID: d687f76cffd3561dd73eb302d17a1c3bf321d1a7 --- Makefile.in | 2 + crypto_api.h | 18 +- kex.c | 7 +- kex.h | 25 +- kexc25519.c | 47 +- kexc25519c.c | 10 +- kexc25519s.c | 8 +- kexkemc.c | 128 ++++++ kexkems.c | 116 +++++ kexsntrup4591761x25519.c | 213 +++++++++ monitor.c | 3 +- sntrup4591761.c | 1068 ++++++++++++++++++++++++++++++++++++++++++++++ sntrup4591761.sh | 47 ++ ssh-keyscan.c | 3 +- ssh_api.c | 4 +- sshconnect2.c | 3 +- sshd.c | 3 +- 17 files changed, 1665 insertions(+), 40 deletions(-) create mode 100644 kexkemc.c create mode 100644 kexkems.c create mode 100644 kexsntrup4591761x25519.c create mode 100644 sntrup4591761.c create mode 100644 sntrup4591761.sh (limited to 'kex.c') diff --git a/Makefile.in b/Makefile.in index 7b5de6039..2b22e9f47 100644 --- a/Makefile.in +++ b/Makefile.in @@ -100,8 +100,10 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ kex.o kexdh.o kexgex.o kexecdh.o kexc25519.o \ kexdhc.o kexgexc.o kexecdhc.o kexc25519c.o \ kexdhs.o kexgexs.o kexecdhs.o kexc25519s.o \ + sntrup4591761.o kexsntrup4591761x25519.o kexkemc.o kexkems.o \ platform-pledge.o platform-tracing.o platform-misc.o + SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \ sshconnect.o sshconnect2.o mux.o diff --git a/crypto_api.h b/crypto_api.h index 7f45bbd69..eb05251ff 100644 --- a/crypto_api.h +++ b/crypto_api.h @@ -1,4 +1,4 @@ -/* $OpenBSD: crypto_api.h,v 1.4 2017/12/14 21:07:39 naddy Exp $ */ +/* $OpenBSD: crypto_api.h,v 1.5 2019/01/21 10:20:12 djm Exp $ */ /* * Assembled from generated headers and source files by Markus Friedl. @@ -15,10 +15,15 @@ #endif #include +typedef int8_t crypto_int8; +typedef uint8_t crypto_uint8; +typedef int16_t crypto_int16; +typedef uint16_t crypto_uint16; typedef int32_t crypto_int32; typedef uint32_t crypto_uint32; #define randombytes(buf, buf_len) arc4random_buf((buf), (buf_len)) +#define small_random32() arc4random() #define crypto_hash_sha512_BYTES 64U @@ -37,4 +42,15 @@ int crypto_sign_ed25519_open(unsigned char *, unsigned long long *, const unsigned char *, unsigned long long, const unsigned char *); int crypto_sign_ed25519_keypair(unsigned char *, unsigned char *); +#define crypto_kem_sntrup4591761_PUBLICKEYBYTES 1218 +#define crypto_kem_sntrup4591761_SECRETKEYBYTES 1600 +#define crypto_kem_sntrup4591761_CIPHERTEXTBYTES 1047 +#define crypto_kem_sntrup4591761_BYTES 32 + +int crypto_kem_sntrup4591761_enc(unsigned char *cstr, unsigned char *k, + const unsigned char *pk); +int crypto_kem_sntrup4591761_dec(unsigned char *k, + const unsigned char *cstr, const unsigned char *sk); +int crypto_kem_sntrup4591761_keypair(unsigned char *pk, unsigned char *sk); + #endif /* crypto_api_h */ diff --git a/kex.c b/kex.c index d8c71bb3e..0dba2cefa 100644 --- a/kex.c +++ b/kex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.c,v 1.146 2019/01/21 10:07:22 djm Exp $ */ +/* $OpenBSD: kex.c,v 1.147 2019/01/21 10:20:12 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * @@ -108,6 +108,8 @@ static const struct kexalg kexalgs[] = { #if defined(HAVE_EVP_SHA256) || !defined(WITH_OPENSSL) { KEX_CURVE25519_SHA256, KEX_C25519_SHA256, 0, SSH_DIGEST_SHA256 }, { KEX_CURVE25519_SHA256_OLD, KEX_C25519_SHA256, 0, SSH_DIGEST_SHA256 }, + { KEX_SNTRUP4591761X25519_SHA512, KEX_KEM_SNTRUP4591761X25519_SHA512, 0, + SSH_DIGEST_SHA512 }, #endif /* HAVE_EVP_SHA256 || !WITH_OPENSSL */ { NULL, -1, -1, -1}, }; @@ -653,6 +655,7 @@ kex_free(struct kex *kex) sshbuf_free(kex->my); sshbuf_free(kex->client_version); sshbuf_free(kex->server_version); + sshbuf_free(kex->kem_client_pub); free(kex->session_id); free(kex->failed_choice); free(kex->hostkey_alg); @@ -1089,7 +1092,7 @@ kex_verify_host_key(struct ssh *ssh, struct sshkey *server_host_key) #if defined(DEBUG_KEX) || defined(DEBUG_KEXDH) || defined(DEBUG_KEXECDH) void -dump_digest(char *msg, u_char *digest, int len) +dump_digest(const char *msg, const u_char *digest, int len) { fprintf(stderr, "%s\n", msg); sshbuf_dump_data(digest, len, stderr); diff --git a/kex.h b/kex.h index e404d0365..258a64712 100644 --- a/kex.h +++ b/kex.h @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.h,v 1.98 2019/01/21 10:07:22 djm Exp $ */ +/* $OpenBSD: kex.h,v 1.99 2019/01/21 10:20:12 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -27,6 +27,7 @@ #define KEX_H #include "mac.h" +#include "crypto_api.h" #ifdef WITH_LEAKMALLOC #include "leakmalloc.h" @@ -62,6 +63,7 @@ #define KEX_ECDH_SHA2_NISTP521 "ecdh-sha2-nistp521" #define KEX_CURVE25519_SHA256 "curve25519-sha256" #define KEX_CURVE25519_SHA256_OLD "curve25519-sha256@libssh.org" +#define KEX_SNTRUP4591761X25519_SHA512 "sntrup4591761x25519-sha512@tinyssh.org" #define COMP_NONE 0 /* pre-auth compression (COMP_ZLIB) is only supported in the client */ @@ -100,6 +102,7 @@ enum kex_exchange { KEX_DH_GEX_SHA256, KEX_ECDH_SHA2, KEX_C25519_SHA256, + KEX_KEM_SNTRUP4591761X25519_SHA512, KEX_MAX }; @@ -164,8 +167,10 @@ struct kex { u_int min, max, nbits; /* GEX */ EC_KEY *ec_client_key; /* ECDH */ const EC_GROUP *ec_group; /* ECDH */ - u_char c25519_client_key[CURVE25519_SIZE]; /* 25519 */ + u_char c25519_client_key[CURVE25519_SIZE]; /* 25519 + KEM */ u_char c25519_client_pubkey[CURVE25519_SIZE]; /* 25519 */ + u_char sntrup4591761_client_key[crypto_kem_sntrup4591761_SECRETKEYBYTES]; /* KEM */ + struct sshbuf *kem_client_pub; /* KEM */ }; int kex_names_valid(const char *); @@ -203,6 +208,14 @@ int kexecdh_client(struct ssh *); int kexecdh_server(struct ssh *); int kexc25519_client(struct ssh *); int kexc25519_server(struct ssh *); +int kex_kem_client(struct ssh *); +int kex_kem_server(struct ssh *); + +int kex_kem_sntrup4591761x25519_keypair(struct kex *); +int kex_kem_sntrup4591761x25519_enc(struct kex *, const u_char *, size_t, + struct sshbuf **, struct sshbuf **); +int kex_kem_sntrup4591761x25519_dec(struct kex *, const u_char *, size_t, + struct sshbuf **); int kex_dh_keygen(struct kex *); int kex_dh_compute_key(struct kex *, BIGNUM *, struct sshbuf *); @@ -224,7 +237,7 @@ int kex_ecdh_hash(int, const EC_GROUP *, int kex_c25519_hash(int, const struct sshbuf *, const struct sshbuf *, const u_char *, size_t, const u_char *, size_t, - const u_char *, size_t, const u_char *, const u_char *, + const u_char *, size_t, const u_char *, size_t, const u_char *, size_t, const u_char *, size_t, u_char *, size_t *); void kexc25519_keygen(u_char key[CURVE25519_SIZE], u_char pub[CURVE25519_SIZE]) @@ -234,9 +247,13 @@ int kexc25519_shared_key(const u_char key[CURVE25519_SIZE], const u_char pub[CURVE25519_SIZE], struct sshbuf *out) __attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE))) __attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE))); +int kexc25519_shared_key_ext(const u_char key[CURVE25519_SIZE], + const u_char pub[CURVE25519_SIZE], struct sshbuf *out, int) + __attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE))) + __attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE))); #if defined(DEBUG_KEX) || defined(DEBUG_KEXDH) || defined(DEBUG_KEXECDH) -void dump_digest(char *, u_char *, int); +void dump_digest(const char *, const u_char *, int); #endif #if !defined(WITH_OPENSSL) || !defined(OPENSSL_HAS_ECC) diff --git a/kexc25519.c b/kexc25519.c index acddcab37..3911baf14 100644 --- a/kexc25519.c +++ b/kexc25519.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexc25519.c,v 1.12 2019/01/21 09:49:37 djm Exp $ */ +/* $OpenBSD: kexc25519.c,v 1.13 2019/01/21 10:20:12 djm Exp $ */ /* * Copyright (c) 2001, 2013 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -60,8 +60,8 @@ kexc25519_keygen(u_char key[CURVE25519_SIZE], u_char pub[CURVE25519_SIZE]) } int -kexc25519_shared_key(const u_char key[CURVE25519_SIZE], - const u_char pub[CURVE25519_SIZE], struct sshbuf *out) +kexc25519_shared_key_ext(const u_char key[CURVE25519_SIZE], + const u_char pub[CURVE25519_SIZE], struct sshbuf *out, int raw) { u_char shared_key[CURVE25519_SIZE]; u_char zero[CURVE25519_SIZE]; @@ -77,12 +77,21 @@ kexc25519_shared_key(const u_char key[CURVE25519_SIZE], #ifdef DEBUG_KEXECDH dump_digest("shared secret", shared_key, CURVE25519_SIZE); #endif - sshbuf_reset(out); - r = sshbuf_put_bignum2_bytes(out, shared_key, CURVE25519_SIZE); + if (raw) + r = sshbuf_put(out, shared_key, CURVE25519_SIZE); + else + r = sshbuf_put_bignum2_bytes(out, shared_key, CURVE25519_SIZE); explicit_bzero(shared_key, CURVE25519_SIZE); return r; } +int +kexc25519_shared_key(const u_char key[CURVE25519_SIZE], + const u_char pub[CURVE25519_SIZE], struct sshbuf *out) +{ + return kexc25519_shared_key_ext(key, pub, out, 0); +} + int kex_c25519_hash( int hash_alg, @@ -91,8 +100,8 @@ kex_c25519_hash( const u_char *ckexinit, size_t ckexinitlen, const u_char *skexinit, size_t skexinitlen, const u_char *serverhostkeyblob, size_t sbloblen, - const u_char client_dh_pub[CURVE25519_SIZE], - const u_char server_dh_pub[CURVE25519_SIZE], + const u_char *client_pub, size_t client_pub_len, + const u_char *server_pub, size_t server_pub_len, const u_char *shared_secret, size_t secretlen, u_char *hash, size_t *hashlen) { @@ -103,19 +112,19 @@ kex_c25519_hash( return SSH_ERR_INVALID_ARGUMENT; if ((b = sshbuf_new()) == NULL) return SSH_ERR_ALLOC_FAIL; - if ((r = sshbuf_put_stringb(b, client_version)) < 0 || - (r = sshbuf_put_stringb(b, server_version)) < 0 || + if ((r = sshbuf_put_stringb(b, client_version)) != 0 || + (r = sshbuf_put_stringb(b, server_version)) != 0 || /* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */ - (r = sshbuf_put_u32(b, ckexinitlen+1)) < 0 || - (r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) < 0 || - (r = sshbuf_put(b, ckexinit, ckexinitlen)) < 0 || - (r = sshbuf_put_u32(b, skexinitlen+1)) < 0 || - (r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) < 0 || - (r = sshbuf_put(b, skexinit, skexinitlen)) < 0 || - (r = sshbuf_put_string(b, serverhostkeyblob, sbloblen)) < 0 || - (r = sshbuf_put_string(b, client_dh_pub, CURVE25519_SIZE)) < 0 || - (r = sshbuf_put_string(b, server_dh_pub, CURVE25519_SIZE)) < 0 || - (r = sshbuf_put(b, shared_secret, secretlen)) < 0) { + (r = sshbuf_put_u32(b, ckexinitlen+1)) != 0 || + (r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) != 0 || + (r = sshbuf_put(b, ckexinit, ckexinitlen)) != 0 || + (r = sshbuf_put_u32(b, skexinitlen+1)) != 0 || + (r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) != 0 || + (r = sshbuf_put(b, skexinit, skexinitlen)) != 0 || + (r = sshbuf_put_string(b, serverhostkeyblob, sbloblen)) != 0 || + (r = sshbuf_put_string(b, client_pub, client_pub_len)) != 0 || + (r = sshbuf_put_string(b, server_pub, server_pub_len)) != 0 || + (r = sshbuf_put(b, shared_secret, secretlen)) != 0) { sshbuf_free(b); return r; } diff --git a/kexc25519c.c b/kexc25519c.c index 1c7f79000..cc6e54cc7 100644 --- a/kexc25519c.c +++ b/kexc25519c.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexc25519c.c,v 1.12 2019/01/21 10:07:22 djm Exp $ */ +/* $OpenBSD: kexc25519c.c,v 1.13 2019/01/21 10:20:12 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -109,7 +109,7 @@ input_kex_c25519_reply(int type, u_int32_t seq, struct ssh *ssh) goto out; } if ((r = kexc25519_shared_key(kex->c25519_client_key, server_pubkey, - shared_secret)) < 0) + shared_secret)) != 0) goto out; /* calc and verify H */ @@ -121,10 +121,10 @@ input_kex_c25519_reply(int type, u_int32_t seq, struct ssh *ssh) sshbuf_ptr(kex->my), sshbuf_len(kex->my), sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), server_host_key_blob, sbloblen, - kex->c25519_client_pubkey, - server_pubkey, + kex->c25519_client_pubkey, sizeof(kex->c25519_client_pubkey), + server_pubkey, pklen, sshbuf_ptr(shared_secret), sshbuf_len(shared_secret), - hash, &hashlen)) < 0) + hash, &hashlen)) != 0) goto out; if ((r = sshkey_verify(server_host_key, signature, slen, hash, hashlen, diff --git a/kexc25519s.c b/kexc25519s.c index d7cc70fee..ace4d5c79 100644 --- a/kexc25519s.c +++ b/kexc25519s.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexc25519s.c,v 1.15 2019/01/21 10:05:09 djm Exp $ */ +/* $OpenBSD: kexc25519s.c,v 1.16 2019/01/21 10:20:12 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -104,10 +104,10 @@ input_kex_c25519_init(int type, u_int32_t seq, struct ssh *ssh) sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), sshbuf_ptr(kex->my), sshbuf_len(kex->my), server_host_key_blob, sbloblen, - client_pubkey, - server_pubkey, + client_pubkey, pklen, + server_pubkey, sizeof(server_pubkey), sshbuf_ptr(shared_secret), sshbuf_len(shared_secret), - hash, &hashlen)) < 0) + hash, &hashlen)) != 0) goto out; /* sign H */ diff --git a/kexkemc.c b/kexkemc.c new file mode 100644 index 000000000..47f15c30c --- /dev/null +++ b/kexkemc.c @@ -0,0 +1,128 @@ +/* $OpenBSD: kexkemc.c,v 1.1 2019/01/21 10:20:12 djm Exp $ */ +/* + * Copyright (c) 2019 Markus Friedl. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include +#include + +#include "sshkey.h" +#include "kex.h" +#include "log.h" +#include "packet.h" +#include "ssh2.h" +#include "sshbuf.h" +#include "digest.h" +#include "ssherr.h" + +static int +input_kex_kem_reply(int type, u_int32_t seq, struct ssh *ssh); + +int +kex_kem_client(struct ssh *ssh) +{ + struct kex *kex = ssh->kex; + int r; + + if ((r = kex_kem_sntrup4591761x25519_keypair(kex)) != 0) + return r; + if ((r = sshpkt_start(ssh, SSH2_MSG_KEX_ECDH_INIT)) != 0 || + (r = sshpkt_put_stringb(ssh, kex->kem_client_pub)) != 0 || + (r = sshpkt_send(ssh)) != 0) + return r; + debug("expecting SSH2_MSG_KEX_ECDH_REPLY"); + ssh_dispatch_set(ssh, SSH2_MSG_KEX_ECDH_REPLY, &input_kex_kem_reply); + return 0; +} + +static int +input_kex_kem_reply(int type, u_int32_t seq, struct ssh *ssh) +{ + struct kex *kex = ssh->kex; + struct sshkey *server_host_key = NULL; + struct sshbuf *shared_secret = NULL; + u_char *server_pubkey = NULL; + u_char *server_host_key_blob = NULL, *signature = NULL; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t slen, pklen, sbloblen, hashlen; + int r; + + /* hostkey */ + if ((r = sshpkt_get_string(ssh, &server_host_key_blob, + &sbloblen)) != 0 || + (r = sshkey_from_blob(server_host_key_blob, sbloblen, + &server_host_key)) != 0) + goto out; + if ((r = kex_verify_host_key(ssh, server_host_key)) != 0) + goto out; + + /* Q_S, server public key */ + /* signed H */ + if ((r = sshpkt_get_string(ssh, &server_pubkey, &pklen)) != 0 || + (r = sshpkt_get_string(ssh, &signature, &slen)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + goto out; + + /* compute shared secret */ + if ((r = kex_kem_sntrup4591761x25519_dec(kex, server_pubkey, pklen, + &shared_secret)) != 0) + goto out; + + /* calc and verify H */ + hashlen = sizeof(hash); + if ((r = kex_c25519_hash( + kex->hash_alg, + kex->client_version, + kex->server_version, + sshbuf_ptr(kex->my), sshbuf_len(kex->my), + sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), + server_host_key_blob, sbloblen, + sshbuf_ptr(kex->kem_client_pub), sshbuf_len(kex->kem_client_pub), + server_pubkey, pklen, + sshbuf_ptr(shared_secret), sshbuf_len(shared_secret), + hash, &hashlen)) != 0) + goto out; + + if ((r = sshkey_verify(server_host_key, signature, slen, hash, hashlen, + kex->hostkey_alg, ssh->compat)) != 0) + goto out; + + if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) + r = kex_send_newkeys(ssh); +out: + explicit_bzero(hash, sizeof(hash)); + explicit_bzero(kex->c25519_client_key, sizeof(kex->c25519_client_key)); + explicit_bzero(kex->sntrup4591761_client_key, + sizeof(kex->sntrup4591761_client_key)); + free(server_host_key_blob); + free(server_pubkey); + free(signature); + sshkey_free(server_host_key); + sshbuf_free(shared_secret); + sshbuf_free(kex->kem_client_pub); + kex->kem_client_pub = NULL; + return r; +} diff --git a/kexkems.c b/kexkems.c new file mode 100644 index 000000000..43cf82018 --- /dev/null +++ b/kexkems.c @@ -0,0 +1,116 @@ +/* $OpenBSD: kexkems.c,v 1.1 2019/01/21 10:20:12 djm Exp $ */ +/* + * Copyright (c) 2019 Markus Friedl. All rights reserved. + * + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "sshkey.h" +#include "digest.h" +#include "kex.h" +#include "log.h" +#include "packet.h" +#include "ssh2.h" +#include "sshbuf.h" +#include "ssherr.h" + +static int input_kex_kem_init(int, u_int32_t, struct ssh *); + +int +kex_kem_server(struct ssh *ssh) +{ + debug("expecting SSH2_MSG_KEX_ECDH_INIT"); + ssh_dispatch_set(ssh, SSH2_MSG_KEX_ECDH_INIT, &input_kex_kem_init); + return 0; +} + +static int +input_kex_kem_init(int type, u_int32_t seq, struct ssh *ssh) +{ + struct kex *kex = ssh->kex; + struct sshkey *server_host_private, *server_host_public; + struct sshbuf *shared_secret = NULL; + struct sshbuf *server_pubkey = NULL; + u_char *server_host_key_blob = NULL, *signature = NULL; + u_char *client_pubkey = NULL; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t slen, pklen, sbloblen, hashlen; + int r; + + if ((r = kex_load_hostkey(ssh, &server_host_private, + &server_host_public)) != 0) + goto out; + + if ((r = sshpkt_get_string(ssh, &client_pubkey, &pklen)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + goto out; + + /* compute shared secret */ + if ((r = kex_kem_sntrup4591761x25519_enc(kex, client_pubkey, pklen, + &server_pubkey, &shared_secret)) != 0) + goto out; + + /* calc H */ + if ((r = sshkey_to_blob(server_host_public, &server_host_key_blob, + &sbloblen)) != 0) + goto out; + hashlen = sizeof(hash); + if ((r = kex_c25519_hash( + kex->hash_alg, + kex->client_version, + kex->server_version, + sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), + sshbuf_ptr(kex->my), sshbuf_len(kex->my), + server_host_key_blob, sbloblen, + client_pubkey, pklen, + sshbuf_ptr(server_pubkey), sshbuf_len(server_pubkey), + sshbuf_ptr(shared_secret), sshbuf_len(shared_secret), + hash, &hashlen)) != 0) + goto out; + + /* sign H */ + if ((r = kex->sign(ssh, server_host_private, server_host_public, + &signature, &slen, hash, hashlen, kex->hostkey_alg)) != 0) + goto out; + + /* send server hostkey, ECDH pubkey 'Q_S' and signed H */ + if ((r = sshpkt_start(ssh, SSH2_MSG_KEX_ECDH_REPLY)) != 0 || + (r = sshpkt_put_string(ssh, server_host_key_blob, sbloblen)) != 0 || + (r = sshpkt_put_stringb(ssh, server_pubkey)) != 0 || + (r = sshpkt_put_string(ssh, signature, slen)) != 0 || + (r = sshpkt_send(ssh)) != 0) + goto out; + + if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) + r = kex_send_newkeys(ssh); +out: + explicit_bzero(hash, sizeof(hash)); + free(server_host_key_blob); + free(signature); + free(client_pubkey); + sshbuf_free(shared_secret); + sshbuf_free(server_pubkey); + return r; +} diff --git a/kexsntrup4591761x25519.c b/kexsntrup4591761x25519.c new file mode 100644 index 000000000..ffe05f420 --- /dev/null +++ b/kexsntrup4591761x25519.c @@ -0,0 +1,213 @@ +/* $OpenBSD: kexsntrup4591761x25519.c,v 1.1 2019/01/21 10:20:12 djm Exp $ */ +/* + * Copyright (c) 2019 Markus Friedl. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include +#include + +#include "sshkey.h" +#include "kex.h" +#include "sshbuf.h" +#include "digest.h" +#include "ssherr.h" + +int +kex_kem_sntrup4591761x25519_keypair(struct kex *kex) +{ + struct sshbuf *buf = NULL; + u_char *cp = NULL; + size_t need; + int r; + + if ((buf = sshbuf_new()) == NULL) + return SSH_ERR_ALLOC_FAIL; + need = crypto_kem_sntrup4591761_PUBLICKEYBYTES + CURVE25519_SIZE; + if ((r = sshbuf_reserve(buf, need, &cp)) != 0) + goto out; + crypto_kem_sntrup4591761_keypair(cp, kex->sntrup4591761_client_key); +#ifdef DEBUG_KEXECDH + dump_digest("client public key sntrup4591761:", cp, + crypto_kem_sntrup4591761_PUBLICKEYBYTES); +#endif + cp += crypto_kem_sntrup4591761_PUBLICKEYBYTES; + kexc25519_keygen(kex->c25519_client_key, cp); +#ifdef DEBUG_KEXECDH + dump_digest("client public key c25519:", cp, CURVE25519_SIZE); +#endif + kex->kem_client_pub = buf; + buf = NULL; + out: + sshbuf_free(buf); + return r; +} + +int +kex_kem_sntrup4591761x25519_enc(struct kex *kex, const u_char *pkblob, + size_t pklen, struct sshbuf **server_blobp, struct sshbuf **shared_secretp) +{ + struct sshbuf *server_blob = NULL; + struct sshbuf *buf = NULL; + u_char *kem_key, *ciphertext, *server_pub; + u_char server_key[CURVE25519_SIZE]; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t need; + int r; + + *server_blobp = NULL; + *shared_secretp = NULL; + + /* pkblob contains both KEM and ECDH client pubkeys */ + need = crypto_kem_sntrup4591761_PUBLICKEYBYTES + CURVE25519_SIZE; + if (pklen != need) { + r = SSH_ERR_SIGNATURE_INVALID; + goto out; + } +#ifdef DEBUG_KEXECDH + dump_digest("client public key sntrup4591761:", pkblob, + crypto_kem_sntrup4591761_PUBLICKEYBYTES); + dump_digest("client public key 25519:", + pkblob + crypto_kem_sntrup4591761_PUBLICKEYBYTES, CURVE25519_SIZE); +#endif + /* allocate buffer for concatenation of KEM key and ECDH shared key */ + /* the buffer will be hashed and the result is the shared secret */ + if ((buf = sshbuf_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if ((r = sshbuf_reserve(buf, crypto_kem_sntrup4591761_BYTES, + &kem_key)) != 0) + goto out; + /* allocate space for encrypted KEM key and ECDH pub key */ + if ((server_blob = sshbuf_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + need = crypto_kem_sntrup4591761_CIPHERTEXTBYTES + CURVE25519_SIZE; + if ((r = sshbuf_reserve(server_blob, need, &ciphertext)) != 0) + goto out; + /* generate and encrypt KEM key with client key */ + crypto_kem_sntrup4591761_enc(ciphertext, kem_key, pkblob); + /* generate ECDH key pair, store server pubkey after ciphertext */ + server_pub = ciphertext + crypto_kem_sntrup4591761_CIPHERTEXTBYTES; + kexc25519_keygen(server_key, server_pub); + /* append ECDH shared key */ + if ((r = kexc25519_shared_key_ext(server_key, + pkblob + crypto_kem_sntrup4591761_PUBLICKEYBYTES, buf, 1)) < 0) + goto out; + if ((r = ssh_digest_buffer(kex->hash_alg, buf, hash, sizeof(hash))) != 0) + goto out; +#ifdef DEBUG_KEXECDH + dump_digest("server public key 25519:", server_pub, CURVE25519_SIZE); + dump_digest("server cipher text:", ciphertext, + crypto_kem_sntrup4591761_CIPHERTEXTBYTES); + dump_digest("server kem key:", kem_key, sizeof(kem_key)); + dump_digest("concatenation of KEM key and ECDH shared key:", + sshbuf_ptr(buf), sshbuf_len(buf)); +#endif + /* string-encoded hash is resulting shared secret */ + sshbuf_reset(buf); + if ((r = sshbuf_put_string(buf, hash, + ssh_digest_bytes(kex->hash_alg))) != 0) + goto out; +#ifdef DEBUG_KEXECDH + dump_digest("encoded shared secret:", sshbuf_ptr(buf), sshbuf_len(buf)); +#endif + *server_blobp = server_blob; + *shared_secretp = buf; + server_blob = NULL; + buf = NULL; + out: + explicit_bzero(hash, sizeof(hash)); + explicit_bzero(server_key, sizeof(server_key)); + sshbuf_free(server_blob); + sshbuf_free(buf); + return r; +} + +int +kex_kem_sntrup4591761x25519_dec(struct kex *kex, const u_char *pkblob, + size_t pklen, struct sshbuf **shared_secretp) +{ + struct sshbuf *buf = NULL; + u_char *kem_key = NULL; + const u_char *ciphertext, *server_pub; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t need; + int r, decoded; + + *shared_secretp = NULL; + + need = crypto_kem_sntrup4591761_CIPHERTEXTBYTES + CURVE25519_SIZE; + if (pklen != need) { + r = SSH_ERR_SIGNATURE_INVALID; + goto out; + } + ciphertext = pkblob; + server_pub = pkblob + crypto_kem_sntrup4591761_CIPHERTEXTBYTES; +#ifdef DEBUG_KEXECDH + dump_digest("server cipher text:", ciphertext, + crypto_kem_sntrup4591761_CIPHERTEXTBYTES); + dump_digest("server public key c25519:", server_pub, CURVE25519_SIZE); +#endif + /* hash concatenation of KEM key and ECDH shared key */ + if ((buf = sshbuf_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if ((r = sshbuf_reserve(buf, crypto_kem_sntrup4591761_BYTES, + &kem_key)) != 0) + goto out; + decoded = crypto_kem_sntrup4591761_dec(kem_key, ciphertext, + kex->sntrup4591761_client_key); + if ((r = kexc25519_shared_key_ext(kex->c25519_client_key, server_pub, + buf, 1)) < 0) + goto out; + if ((r = ssh_digest_buffer(kex->hash_alg, buf, hash, sizeof(hash))) != 0) + goto out; +#ifdef DEBUG_KEXECDH + dump_digest("client kem key:", kem_key, sizeof(kem_key)); + dump_digest("concatenation of KEM key and ECDH shared key:", + sshbuf_ptr(buf), sshbuf_len(buf)); +#endif + sshbuf_reset(buf); + if ((r = sshbuf_put_string(buf, hash, + ssh_digest_bytes(kex->hash_alg))) != 0) + goto out; +#ifdef DEBUG_KEXECDH + dump_digest("encoded shared secret:", sshbuf_ptr(buf), sshbuf_len(buf)); +#endif + if (decoded != 0) { + r = SSH_ERR_SIGNATURE_INVALID; + goto out; + } + *shared_secretp = buf; + buf = NULL; + out: + explicit_bzero(hash, sizeof(hash)); + sshbuf_free(buf); + return r; +} diff --git a/monitor.c b/monitor.c index a9546dad2..b10fdebf2 100644 --- a/monitor.c +++ b/monitor.c @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor.c,v 1.192 2019/01/19 21:43:56 djm Exp $ */ +/* $OpenBSD: monitor.c,v 1.193 2019/01/21 10:20:12 djm Exp $ */ /* * Copyright 2002 Niels Provos * Copyright 2002 Markus Friedl @@ -1689,6 +1689,7 @@ monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor) # endif #endif /* WITH_OPENSSL */ kex->kex[KEX_C25519_SHA256] = kexc25519_server; + kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_kem_server; kex->load_host_public_key=&get_hostkey_public_by_type; kex->load_host_private_key=&get_hostkey_private_by_type; kex->host_key_index=&get_hostkey_index; diff --git a/sntrup4591761.c b/sntrup4591761.c new file mode 100644 index 000000000..d3ff549ae --- /dev/null +++ b/sntrup4591761.c @@ -0,0 +1,1068 @@ +#include +#include "crypto_api.h" + +/* from supercop-20181216/crypto_sort/int32/portable3/int32_minmax.inc */ +#define int32_MINMAX(a,b) \ +do { \ + int32 ab = b ^ a; \ + int32 c = b - a; \ + c ^= ab & (c ^ b); \ + c >>= 31; \ + c &= ab; \ + a ^= c; \ + b ^= c; \ +} while(0) + +/* from supercop-20181216/crypto_sort/int32/portable3/sort.c */ +#define int32 crypto_int32 + + +static void crypto_sort_int32(void *array,long long n) +{ + long long top,p,q,r,i; + int32 *x = array; + + if (n < 2) return; + top = 1; + while (top < n - top) top += top; + + for (p = top;p > 0;p >>= 1) { + for (i = 0;i < n - p;++i) + if (!(i & p)) + int32_MINMAX(x[i],x[i+p]); + i = 0; + for (q = top;q > p;q >>= 1) { + for (;i < n - q;++i) { + if (!(i & p)) { + int32 a = x[i + p]; + for (r = q;r > p;r >>= 1) + int32_MINMAX(a,x[i+r]); + x[i + p] = a; + } + } + } + } +} + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/small.h */ +#ifndef small_h +#define small_h + + +typedef crypto_int8 small; + +static void small_encode(unsigned char *,const small *); + +static void small_decode(small *,const unsigned char *); + + +static void small_random(small *); + +static void small_random_weightw(small *); + +#endif + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/mod3.h */ +#ifndef mod3_h +#define mod3_h + + +/* -1 if x is nonzero, 0 otherwise */ +static inline int mod3_nonzero_mask(small x) +{ + return -x*x; +} + +/* input between -100000 and 100000 */ +/* output between -1 and 1 */ +static inline small mod3_freeze(crypto_int32 a) +{ + a -= 3 * ((10923 * a) >> 15); + a -= 3 * ((89478485 * a + 134217728) >> 28); + return a; +} + +static inline small mod3_minusproduct(small a,small b,small c) +{ + crypto_int32 A = a; + crypto_int32 B = b; + crypto_int32 C = c; + return mod3_freeze(A - B * C); +} + +static inline small mod3_plusproduct(small a,small b,small c) +{ + crypto_int32 A = a; + crypto_int32 B = b; + crypto_int32 C = c; + return mod3_freeze(A + B * C); +} + +static inline small mod3_product(small a,small b) +{ + return a * b; +} + +static inline small mod3_sum(small a,small b) +{ + crypto_int32 A = a; + crypto_int32 B = b; + return mod3_freeze(A + B); +} + +static inline small mod3_reciprocal(small a1) +{ + return a1; +} + +static inline small mod3_quotient(small num,small den) +{ + return mod3_product(num,mod3_reciprocal(den)); +} + +#endif + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/modq.h */ +#ifndef modq_h +#define modq_h + + +typedef crypto_int16 modq; + +/* -1 if x is nonzero, 0 otherwise */ +static inline int modq_nonzero_mask(modq x) +{ + crypto_int32 r = (crypto_uint16) x; + r = -r; + r >>= 30; + return r; +} + +/* input between -9000000 and 9000000 */ +/* output between -2295 and 2295 */ +static inline modq modq_freeze(crypto_int32 a) +{ + a -= 4591 * ((228 * a) >> 20); + a -= 4591 * ((58470 * a + 134217728) >> 28); + return a; +} + +static inline modq modq_minusproduct(modq a,modq b,modq c) +{ + crypto_int32 A = a; + crypto_int32 B = b; + crypto_int32 C = c; + return modq_freeze(A - B * C); +} + +static inline modq modq_plusproduct(modq a,modq b,modq c) +{ + crypto_int32 A = a; + crypto_int32 B = b; + crypto_int32 C = c; + return modq_freeze(A + B * C); +} + +static inline modq modq_product(modq a,modq b) +{ + crypto_int32 A = a; + crypto_int32 B = b; + return modq_freeze(A * B); +} + +static inline modq modq_square(modq a) +{ + crypto_int32 A = a; + return modq_freeze(A * A); +} + +static inline modq modq_sum(modq a,modq b) +{ + crypto_int32 A = a; + crypto_int32 B = b; + return modq_freeze(A + B); +} + +static inline modq modq_reciprocal(modq a1) +{ + modq a2 = modq_square(a1); + modq a3 = modq_product(a2,a1); + modq a4 = modq_square(a2); + modq a8 = modq_square(a4); + modq a16 = modq_square(a8); + modq a32 = modq_square(a16); + modq a35 = modq_product(a32,a3); + modq a70 = modq_square(a35); + modq a140 = modq_square(a70); + modq a143 = modq_product(a140,a3); + modq a286 = modq_square(a143); + modq a572 = modq_square(a286); + modq a1144 = modq_square(a572); + modq a1147 = modq_product(a1144,a3); + modq a2294 = modq_square(a1147); + modq a4588 = modq_square(a2294); + modq a4589 = modq_product(a4588,a1); + return a4589; +} + +static inline modq modq_quotient(modq num,modq den) +{ + return modq_product(num,modq_reciprocal(den)); +} + +#endif + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/params.h */ +#ifndef params_h +#define params_h + +#define q 4591 +/* XXX: also built into modq in various ways */ + +#define qshift 2295 +#define p 761 +#define w 286 + +#define rq_encode_len 1218 +#define small_encode_len 191 + +#endif + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/r3.h */ +#ifndef r3_h +#define r3_h + + +static void r3_mult(small *,const small *,const small *); + +extern int r3_recip(small *,const small *); + +#endif + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/rq.h */ +#ifndef rq_h +#define rq_h + + +static void rq_encode(unsigned char *,const modq *); + +static void rq_decode(modq *,const unsigned char *); + +static void rq_encoderounded(unsigned char *,const modq *); + +static void rq_decoderounded(modq *,const unsigned char *); + +static void rq_round3(modq *,const modq *); + +static void rq_mult(modq *,const modq *,const small *); + +int rq_recip3(modq *,const small *); + +#endif + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/swap.h */ +#ifndef swap_h +#define swap_h + +static void swap(void *,void *,int,int); + +#endif + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/dec.c */ +/* See https://ntruprime.cr.yp.to/software.html for detailed documentation. */ + +#ifdef KAT +#endif + + +int crypto_kem_sntrup4591761_dec( + unsigned char *k, + const unsigned char *cstr, + const unsigned char *sk +) +{ + small f[p]; + modq h[p]; + small grecip[p]; + modq c[p]; + modq t[p]; + small t3[p]; + small r[p]; + modq hr[p]; + unsigned char rstr[small_encode_len]; + unsigned char hash[64]; + int i; + int result = 0; + int weight; + + small_decode(f,sk); + small_decode(grecip,sk + small_encode_len); + rq_decode(h,sk + 2 * small_encode_len); + + rq_decoderounded(c,cstr + 32); + + rq_mult(t,c,f); + for (i = 0;i < p;++i) t3[i] = mod3_freeze(modq_freeze(3*t[i])); + + r3_mult(r,t3,grecip); + +#ifdef KAT + { + int j; + printf("decrypt r:"); + for (j = 0;j < p;++j) + if (r[j] == 1) printf(" +%d",j); + else if (r[j] == -1) printf(" -%d",j); + printf("\n"); + } +#endif + + weight = 0; + for (i = 0;i < p;++i) weight += (1 & r[i]); + weight -= w; + result |= modq_nonzero_mask(weight); /* XXX: puts limit on p */ + + rq_mult(hr,h,r); + rq_round3(hr,hr); + for (i = 0;i < p;++i) result |= modq_nonzero_mask(hr[i] - c[i]); + + small_encode(rstr,r); + crypto_hash_sha512(hash,rstr,sizeof rstr); + result |= crypto_verify_32(hash,cstr); + + for (i = 0;i < 32;++i) k[i] = (hash[32 + i] & ~result); + return result; +} + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/enc.c */ +/* See https://ntruprime.cr.yp.to/software.html for detailed documentation. */ + +#ifdef KAT +#endif + + +int crypto_kem_sntrup4591761_enc( + unsigned char *cstr, + unsigned char *k, + const unsigned char *pk +) +{ + small r[p]; + modq h[p]; + modq c[p]; + unsigned char rstr[small_encode_len]; + unsigned char hash[64]; + + small_random_weightw(r); + +#ifdef KAT + { + int i; + printf("encrypt r:"); + for (i = 0;i < p;++i) + if (r[i] == 1) printf(" +%d",i); + else if (r[i] == -1) printf(" -%d",i); + printf("\n"); + } +#endif + + small_encode(rstr,r); + crypto_hash_sha512(hash,rstr,sizeof rstr); + + rq_decode(h,pk); + rq_mult(c,h,r); + rq_round3(c,c); + + memcpy(k,hash + 32,32); + memcpy(cstr,hash,32); + rq_encoderounded(cstr + 32,c); + + return 0; +} + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/keypair.c */ +/* See https://ntruprime.cr.yp.to/software.html for detailed documentation. */ + + +#if crypto_kem_sntrup4591761_PUBLICKEYBYTES != rq_encode_len +#error "crypto_kem_sntrup4591761_PUBLICKEYBYTES must match rq_encode_len" +#endif +#if crypto_kem_sntrup4591761_SECRETKEYBYTES != rq_encode_len + 2 * small_encode_len +#error "crypto_kem_sntrup4591761_SECRETKEYBYTES must match rq_encode_len + 2 * small_encode_len" +#endif + +int crypto_kem_sntrup4591761_keypair(unsigned char *pk,unsigned char *sk) +{ + small g[p]; + small grecip[p]; + small f[p]; + modq f3recip[p]; + modq h[p]; + + do + small_random(g); + while (r3_recip(grecip,g) != 0); + + small_random_weightw(f); + rq_recip3(f3recip,f); + + rq_mult(h,f3recip,g); + + rq_encode(pk,h); + small_encode(sk,f); + small_encode(sk + small_encode_len,grecip); + memcpy(sk + 2 * small_encode_len,pk,rq_encode_len); + + return 0; +} + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/r3_mult.c */ +/* See https://ntruprime.cr.yp.to/software.html for detailed documentation. */ + + +static void r3_mult(small *h,const small *f,const small *g) +{ + small fg[p + p - 1]; + small result; + int i, j; + + for (i = 0;i < p;++i) { + result = 0; + for (j = 0;j <= i;++j) + result = mod3_plusproduct(result,f[j],g[i - j]); + fg[i] = result; + } + for (i = p;i < p + p - 1;++i) { + result = 0; + for (j = i - p + 1;j < p;++j) + result = mod3_plusproduct(result,f[j],g[i - j]); + fg[i] = result; + } + + for (i = p + p - 2;i >= p;--i) { + fg[i - p] = mod3_sum(fg[i - p],fg[i]); + fg[i - p + 1] = mod3_sum(fg[i - p + 1],fg[i]); + } + + for (i = 0;i < p;++i) + h[i] = fg[i]; +} + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/r3_recip.c */ +/* See https://ntruprime.cr.yp.to/software.html for detailed documentation. */ + + +/* caller must ensure that x-y does not overflow */ +static int smaller_mask_r3_recip(int x,int y) +{ + return (x - y) >> 31; +} + +static void vectormod3_product(small *z,int len,const small *x,const small c) +{ + int i; + for (i = 0;i < len;++i) z[i] = mod3_product(x[i],c); +} + +static void vectormod3_minusproduct(small *z,int len,const small *x,const small *y,const small c) +{ + int i; + for (i = 0;i < len;++i) z[i] = mod3_minusproduct(x[i],y[i],c); +} + +static void vectormod3_shift(small *z,int len) +{ + int i; + for (i = len - 1;i > 0;--i) z[i] = z[i - 1]; + z[0] = 0; +} + +/* +r = s^(-1) mod m, returning 0, if s is invertible mod m +or returning -1 if s is not invertible mod m +r,s are polys of degree

= loops) break; + + c = mod3_quotient(g[p],f[p]); + + vectormod3_minusproduct(g,p + 1,g,f,c); + vectormod3_shift(g,p + 1); + +#ifdef SIMPLER + vectormod3_minusproduct(v,loops + 1,v,u,c); + vectormod3_shift(v,loops + 1); +#else + if (loop < p) { + vectormod3_minusproduct(v,loop + 1,v,u,c); + vectormod3_shift(v,loop + 2); + } else { + vectormod3_minusproduct(v + loop - p,p + 1,v + loop - p,u + loop - p,c); + vectormod3_shift(v + loop - p,p + 2); + } +#endif + + e -= 1; + + ++loop; + + swapmask = smaller_mask_r3_recip(e,d) & mod3_nonzero_mask(g[p]); + swap(&e,&d,sizeof e,swapmask); + swap(f,g,(p + 1) * sizeof(small),swapmask); + +#ifdef SIMPLER + swap(u,v,(loops + 1) * sizeof(small),swapmask); +#else + if (loop < p) { + swap(u,v,(loop + 1) * sizeof(small),swapmask); + } else { + swap(u + loop - p,v + loop - p,(p + 1) * sizeof(small),swapmask); + } +#endif + } + + c = mod3_reciprocal(f[p]); + vectormod3_product(r,p,u + p,c); + return smaller_mask_r3_recip(0,d); +} + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/randomsmall.c */ +/* See https://ntruprime.cr.yp.to/software.html for detailed documentation. */ + + +static void small_random(small *g) +{ + int i; + + for (i = 0;i < p;++i) { + crypto_uint32 r = small_random32(); + g[i] = (small) (((1073741823 & r) * 3) >> 30) - 1; + } +} + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/randomweightw.c */ +/* See https://ntruprime.cr.yp.to/software.html for detailed documentation. */ + + +static void small_random_weightw(small *f) +{ + crypto_int32 r[p]; + int i; + + for (i = 0;i < p;++i) r[i] = small_random32(); + for (i = 0;i < w;++i) r[i] &= -2; + for (i = w;i < p;++i) r[i] = (r[i] & -3) | 1; + crypto_sort_int32(r,p); + for (i = 0;i < p;++i) f[i] = ((small) (r[i] & 3)) - 1; +} + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/rq.c */ +/* See https://ntruprime.cr.yp.to/software.html for detailed documentation. */ + + +static void rq_encode(unsigned char *c,const modq *f) +{ + crypto_int32 f0, f1, f2, f3, f4; + int i; + + for (i = 0;i < p/5;++i) { + f0 = *f++ + qshift; + f1 = *f++ + qshift; + f2 = *f++ + qshift; + f3 = *f++ + qshift; + f4 = *f++ + qshift; + /* now want f0 + 6144*f1 + ... as a 64-bit integer */ + f1 *= 3; + f2 *= 9; + f3 *= 27; + f4 *= 81; + /* now want f0 + f1<<11 + f2<<22 + f3<<33 + f4<<44 */ + f0 += f1 << 11; + *c++ = f0; f0 >>= 8; + *c++ = f0; f0 >>= 8; + f0 += f2 << 6; + *c++ = f0; f0 >>= 8; + *c++ = f0; f0 >>= 8; + f0 += f3 << 1; + *c++ = f0; f0 >>= 8; + f0 += f4 << 4; + *c++ = f0; f0 >>= 8; + *c++ = f0; f0 >>= 8; + *c++ = f0; + } + /* XXX: using p mod 5 = 1 */ + f0 = *f++ + qshift; + *c++ = f0; f0 >>= 8; + *c++ = f0; +} + +static void rq_decode(modq *f,const unsigned char *c) +{ + crypto_uint32 c0, c1, c2, c3, c4, c5, c6, c7; + crypto_uint32 f0, f1, f2, f3, f4; + int i; + + for (i = 0;i < p/5;++i) { + c0 = *c++; + c1 = *c++; + c2 = *c++; + c3 = *c++; + c4 = *c++; + c5 = *c++; + c6 = *c++; + c7 = *c++; + + /* f0 + f1*6144 + f2*6144^2 + f3*6144^3 + f4*6144^4 */ + /* = c0 + c1*256 + ... + c6*256^6 + c7*256^7 */ + /* with each f between 0 and 4590 */ + + c6 += c7 << 8; + /* c6 <= 23241 = floor(4591*6144^4/2^48) */ + /* f4 = (16/81)c6 + (1/1296)(c5+[0,1]) - [0,0.75] */ + /* claim: 2^19 f4 < x < 2^19(f4+1) */ + /* where x = 103564 c6 + 405(c5+1) */ + /* proof: x - 2^19 f4 = (76/81)c6 + (37/81)c5 + 405 - (32768/81)[0,1] + 2^19[0,0.75] */ + /* at least 405 - 32768/81 > 0 */ + /* at most (76/81)23241 + (37/81)255 + 405 + 2^19 0.75 < 2^19 */ + f4 = (103564*c6 + 405*(c5+1)) >> 19; + + c5 += c6 << 8; + c5 -= (f4 * 81) << 4; + c4 += c5 << 8; + + /* f0 + f1*6144 + f2*6144^2 + f3*6144^3 */ + /* = c0 + c1*256 + c2*256^2 + c3*256^3 + c4*256^4 */ + /* c4 <= 247914 = floor(4591*6144^3/2^32) */ + /* f3 = (1/54)(c4+[0,1]) - [0,0.75] */ + /* claim: 2^19 f3 < x < 2^19(f3+1) */ + /* where x = 9709(c4+2) */ + /* proof: x - 2^19 f3 = 19418 - (1/27)c4 - (262144/27)[0,1] + 2^19[0,0.75] */ + /* at least 19418 - 247914/27 - 262144/27 > 0 */ + /* at most 19418 + 2^19 0.75 < 2^19 */ + f3 = (9709*(c4+2)) >> 19; + + c4 -= (f3 * 27) << 1; + c3 += c4 << 8; + /* f0 + f1*6144 + f2*6144^2 */ + /* = c0 + c1*256 + c2*256^2 + c3*256^3 */ + /* c3 <= 10329 = floor(4591*6144^2/2^24) */ + /* f2 = (4/9)c3 + (1/576)c2 + (1/147456)c1 + (1/37748736)c0 - [0,0.75] */ + /* claim: 2^19 f2 < x < 2^19(f2+1) */ + /* where x = 233017 c3 + 910(c2+2) */ + /* proof: x - 2^19 f2 = 1820 + (1/9)c3 - (2/9)c2 - (32/9)c1 - (1/72)c0 + 2^19[0,0.75] */ + /* at least 1820 - (2/9)255 - (32/9)255 - (1/72)255 > 0 */ + /* at most 1820 + (1/9)10329 + 2^19 0.75 < 2^19 */ + f2 = (233017*c3 + 910*(c2+2)) >> 19; + + c2 += c3 << 8; + c2 -= (f2 * 9) << 6; + c1 += c2 << 8; + /* f0 + f1*6144 */ + /* = c0 + c1*256 */ + /* c1 <= 110184 = floor(4591*6144/2^8) */ + /* f1 = (1/24)c1 + (1/6144)c0 - (1/6144)f0 */ + /* claim: 2^19 f1 < x < 2^19(f1+1) */ + /* where x = 21845(c1+2) + 85 c0 */ + /* proof: x - 2^19 f1 = 43690 - (1/3)c1 - (1/3)c0 + 2^19 [0,0.75] */ + /* at least 43690 - (1/3)110184 - (1/3)255 > 0 */ + /* at most 43690 + 2^19 0.75 < 2^19 */ + f1 = (21845*(c1+2) + 85*c0) >> 19; + + c1 -= (f1 * 3) << 3; + c0 += c1 << 8; + f0 = c0; + + *f++ = modq_freeze(f0 + q - qshift); + *f++ = modq_freeze(f1 + q - qshift); + *f++ = modq_freeze(f2 + q - qshift); + *f++ = modq_freeze(f3 + q - qshift); + *f++ = modq_freeze(f4 + q - qshift); + } + + c0 = *c++; + c1 = *c++; + c0 += c1 << 8; + *f++ = modq_freeze(c0 + q - qshift); +} + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/rq_mult.c */ +/* See https://ntruprime.cr.yp.to/software.html for detailed documentation. */ + + +static void rq_mult(modq *h,const modq *f,const small *g) +{ + modq fg[p + p - 1]; + modq result; + int i, j; + + for (i = 0;i < p;++i) { + result = 0; + for (j = 0;j <= i;++j) + result = modq_plusproduct(result,f[j],g[i - j]); + fg[i] = result; + } + for (i = p;i < p + p - 1;++i) { + result = 0; + for (j = i - p + 1;j < p;++j) + result = modq_plusproduct(result,f[j],g[i - j]); + fg[i] = result; + } + + for (i = p + p - 2;i >= p;--i) { + fg[i - p] = modq_sum(fg[i - p],fg[i]); + fg[i - p + 1] = modq_sum(fg[i - p + 1],fg[i]); + } + + for (i = 0;i < p;++i) + h[i] = fg[i]; +} + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/rq_recip3.c */ +/* See https://ntruprime.cr.yp.to/software.html for detailed documentation. */ + + +/* caller must ensure that x-y does not overflow */ +static int smaller_mask_rq_recip3(int x,int y) +{ + return (x - y) >> 31; +} + +static void vectormodq_product(modq *z,int len,const modq *x,const modq c) +{ + int i; + for (i = 0;i < len;++i) z[i] = modq_product(x[i],c); +} + +static void vectormodq_minusproduct(modq *z,int len,const modq *x,const modq *y,const modq c) +{ + int i; + for (i = 0;i < len;++i) z[i] = modq_minusproduct(x[i],y[i],c); +} + +static void vectormodq_shift(modq *z,int len) +{ + int i; + for (i = len - 1;i > 0;--i) z[i] = z[i - 1]; + z[0] = 0; +} + +/* +r = (3s)^(-1) mod m, returning 0, if s is invertible mod m +or returning -1 if s is not invertible mod m +r,s are polys of degree

= loops) break; + + c = modq_quotient(g[p],f[p]); + + vectormodq_minusproduct(g,p + 1,g,f,c); + vectormodq_shift(g,p + 1); + +#ifdef SIMPLER + vectormodq_minusproduct(v,loops + 1,v,u,c); + vectormodq_shift(v,loops + 1); +#else + if (loop < p) { + vectormodq_minusproduct(v,loop + 1,v,u,c); + vectormodq_shift(v,loop + 2); + } else { + vectormodq_minusproduct(v + loop - p,p + 1,v + loop - p,u + loop - p,c); + vectormodq_shift(v + loop - p,p + 2); + } +#endif + + e -= 1; + + ++loop; + + swapmask = smaller_mask_rq_recip3(e,d) & modq_nonzero_mask(g[p]); + swap(&e,&d,sizeof e,swapmask); + swap(f,g,(p + 1) * sizeof(modq),swapmask); + +#ifdef SIMPLER + swap(u,v,(loops + 1) * sizeof(modq),swapmask); +#else + if (loop < p) { + swap(u,v,(loop + 1) * sizeof(modq),swapmask); + } else { + swap(u + loop - p,v + loop - p,(p + 1) * sizeof(modq),swapmask); + } +#endif + } + + c = modq_reciprocal(f[p]); + vectormodq_product(r,p,u + p,c); + return smaller_mask_rq_recip3(0,d); +} + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/rq_round3.c */ +/* See https://ntruprime.cr.yp.to/software.html for detailed documentation. */ + + +static void rq_round3(modq *h,const modq *f) +{ + int i; + + for (i = 0;i < p;++i) + h[i] = ((21846 * (f[i] + 2295) + 32768) >> 16) * 3 - 2295; +} + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/rq_rounded.c */ +/* See https://ntruprime.cr.yp.to/software.html for detailed documentation. */ + + +static void rq_encoderounded(unsigned char *c,const modq *f) +{ + crypto_int32 f0, f1, f2; + int i; + + for (i = 0;i < p/3;++i) { + f0 = *f++ + qshift; + f1 = *f++ + qshift; + f2 = *f++ + qshift; + f0 = (21846 * f0) >> 16; + f1 = (21846 * f1) >> 16; + f2 = (21846 * f2) >> 16; + /* now want f0 + f1*1536 + f2*1536^2 as a 32-bit integer */ + f2 *= 3; + f1 += f2 << 9; + f1 *= 3; + f0 += f1 << 9; + *c++ = f0; f0 >>= 8; + *c++ = f0; f0 >>= 8; + *c++ = f0; f0 >>= 8; + *c++ = f0; + } + /* XXX: using p mod 3 = 2 */ + f0 = *f++ + qshift; + f1 = *f++ + qshift; + f0 = (21846 * f0) >> 16; + f1 = (21846 * f1) >> 16; + f1 *= 3; + f0 += f1 << 9; + *c++ = f0; f0 >>= 8; + *c++ = f0; f0 >>= 8; + *c++ = f0; +} + +static void rq_decoderounded(modq *f,const unsigned char *c) +{ + crypto_uint32 c0, c1, c2, c3; + crypto_uint32 f0, f1, f2; + int i; + + for (i = 0;i < p/3;++i) { + c0 = *c++; + c1 = *c++; + c2 = *c++; + c3 = *c++; + + /* f0 + f1*1536 + f2*1536^2 */ + /* = c0 + c1*256 + c2*256^2 + c3*256^3 */ + /* with each f between 0 and 1530 */ + + /* f2 = (64/9)c3 + (1/36)c2 + (1/9216)c1 + (1/2359296)c0 - [0,0.99675] */ + /* claim: 2^21 f2 < x < 2^21(f2+1) */ + /* where x = 14913081*c3 + 58254*c2 + 228*(c1+2) */ + /* proof: x - 2^21 f2 = 456 - (8/9)c0 + (4/9)c1 - (2/9)c2 + (1/9)c3 + 2^21 [0,0.99675] */ + /* at least 456 - (8/9)255 - (2/9)255 > 0 */ + /* at most 456 + (4/9)255 + (1/9)255 + 2^21 0.99675 < 2^21 */ + f2 = (14913081*c3 + 58254*c2 + 228*(c1+2)) >> 21; + + c2 += c3 << 8; + c2 -= (f2 * 9) << 2; + /* f0 + f1*1536 */ + /* = c0 + c1*256 + c2*256^2 */ + /* c2 <= 35 = floor((1530+1530*1536)/256^2) */ + /* f1 = (128/3)c2 + (1/6)c1 + (1/1536)c0 - (1/1536)f0 */ + /* claim: 2^21 f1 < x < 2^21(f1+1) */ + /* where x = 89478485*c2 + 349525*c1 + 1365*(c0+1) */ + /* proof: x - 2^21 f1 = 1365 - (1/3)c2 - (1/3)c1 - (1/3)c0 + (4096/3)f0 */ + /* at least 1365 - (1/3)35 - (1/3)255 - (1/3)255 > 0 */ + /* at most 1365 + (4096/3)1530 < 2^21 */ + f1 = (89478485*c2 + 349525*c1 + 1365*(c0+1)) >> 21; + + c1 += c2 << 8; + c1 -= (f1 * 3) << 1; + + c0 += c1 << 8; + f0 = c0; + + *f++ = modq_freeze(f0 * 3 + q - qshift); + *f++ = modq_freeze(f1 * 3 + q - qshift); + *f++ = modq_freeze(f2 * 3 + q - qshift); + } + + c0 = *c++; + c1 = *c++; + c2 = *c++; + + f1 = (89478485*c2 + 349525*c1 + 1365*(c0+1)) >> 21; + + c1 += c2 << 8; + c1 -= (f1 * 3) << 1; + + c0 += c1 << 8; + f0 = c0; + + *f++ = modq_freeze(f0 * 3 + q - qshift); + *f++ = modq_freeze(f1 * 3 + q - qshift); +} + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/small.c */ +/* See https://ntruprime.cr.yp.to/software.html for detailed documentation. */ + + +/* XXX: these functions rely on p mod 4 = 1 */ + +/* all coefficients in -1, 0, 1 */ +static void small_encode(unsigned char *c,const small *f) +{ + small c0; + int i; + + for (i = 0;i < p/4;++i) { + c0 = *f++ + 1; + c0 += (*f++ + 1) << 2; + c0 += (*f++ + 1) << 4; + c0 += (*f++ + 1) << 6; + *c++ = c0; + } + c0 = *f++ + 1; + *c++ = c0; +} + +static void small_decode(small *f,const unsigned char *c) +{ + unsigned char c0; + int i; + + for (i = 0;i < p/4;++i) { + c0 = *c++; + *f++ = ((small) (c0 & 3)) - 1; c0 >>= 2; + *f++ = ((small) (c0 & 3)) - 1; c0 >>= 2; + *f++ = ((small) (c0 & 3)) - 1; c0 >>= 2; + *f++ = ((small) (c0 & 3)) - 1; + } + c0 = *c++; + *f++ = ((small) (c0 & 3)) - 1; +} + +/* from supercop-20181216/crypto_kem/sntrup4591761/ref/swap.c */ +/* See https://ntruprime.cr.yp.to/software.html for detailed documentation. */ + + +static void swap(void *x,void *y,int bytes,int mask) +{ + int i; + char xi, yi, c, t; + + c = mask; + + for (i = 0;i < bytes;++i) { + xi = i[(char *) x]; + yi = i[(char *) y]; + t = c & (xi ^ yi); + xi ^= t; + yi ^= t; + i[(char *) x] = xi; + i[(char *) y] = yi; + } +} + diff --git a/sntrup4591761.sh b/sntrup4591761.sh new file mode 100644 index 000000000..5540ca4d9 --- /dev/null +++ b/sntrup4591761.sh @@ -0,0 +1,47 @@ +#!/bin/sh +FILES=" + supercop-20181216/crypto_sort/int32/portable3/int32_minmax.inc + supercop-20181216/crypto_sort/int32/portable3/sort.c + supercop-20181216/crypto_kem/sntrup4591761/ref/small.h + supercop-20181216/crypto_kem/sntrup4591761/ref/mod3.h + supercop-20181216/crypto_kem/sntrup4591761/ref/modq.h + supercop-20181216/crypto_kem/sntrup4591761/ref/params.h + supercop-20181216/crypto_kem/sntrup4591761/ref/r3.h + supercop-20181216/crypto_kem/sntrup4591761/ref/rq.h + supercop-20181216/crypto_kem/sntrup4591761/ref/swap.h + supercop-20181216/crypto_kem/sntrup4591761/ref/dec.c + supercop-20181216/crypto_kem/sntrup4591761/ref/enc.c + supercop-20181216/crypto_kem/sntrup4591761/ref/keypair.c + supercop-20181216/crypto_kem/sntrup4591761/ref/r3_mult.c + supercop-20181216/crypto_kem/sntrup4591761/ref/r3_recip.c + supercop-20181216/crypto_kem/sntrup4591761/ref/randomsmall.c + supercop-20181216/crypto_kem/sntrup4591761/ref/randomweightw.c + supercop-20181216/crypto_kem/sntrup4591761/ref/rq.c + supercop-20181216/crypto_kem/sntrup4591761/ref/rq_mult.c + supercop-20181216/crypto_kem/sntrup4591761/ref/rq_recip3.c + supercop-20181216/crypto_kem/sntrup4591761/ref/rq_round3.c + supercop-20181216/crypto_kem/sntrup4591761/ref/rq_rounded.c + supercop-20181216/crypto_kem/sntrup4591761/ref/small.c + supercop-20181216/crypto_kem/sntrup4591761/ref/swap.c +" +### + +set -e +DIR=/data/git/mfriedl +cd $DIR +echo '#include ' +echo '#include "crypto_api.h"' +echo +for i in $FILES; do + echo "/* from $i */" + b=$(basename $i .c) + grep \ + -v '#include' $i | \ + grep -v "extern crypto_int32 small_random32" | + sed -e "s/crypto_kem_/crypto_kem_sntrup4591761_/g" \ + -e "s/smaller_mask/smaller_mask_${b}/g" \ + -e "s/void crypto_sort/void crypto_sort_int32/" \ + -e "s/^extern void /static void /" \ + -e "s/^void /static void /" + echo +done diff --git a/ssh-keyscan.c b/ssh-keyscan.c index 88449f672..83a768700 100644 --- a/ssh-keyscan.c +++ b/ssh-keyscan.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-keyscan.c,v 1.120 2018/06/06 18:29:18 markus Exp $ */ +/* $OpenBSD: ssh-keyscan.c,v 1.121 2019/01/21 10:20:12 djm Exp $ */ /* * Copyright 1995, 1996 by David Mazieres . * @@ -272,6 +272,7 @@ keygrab_ssh2(con *c) # endif #endif c->c_ssh->kex->kex[KEX_C25519_SHA256] = kexc25519_client; + c->c_ssh->kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_kem_client; ssh_set_verify_host_key_callback(c->c_ssh, key_print_wrapper); /* * do the key-exchange until an error occurs or until diff --git a/ssh_api.c b/ssh_api.c index 182c0d7e4..73981aa37 100644 --- a/ssh_api.c +++ b/ssh_api.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh_api.c,v 1.10 2019/01/19 21:43:56 djm Exp $ */ +/* $OpenBSD: ssh_api.c,v 1.11 2019/01/21 10:20:12 djm Exp $ */ /* * Copyright (c) 2012 Markus Friedl. All rights reserved. * @@ -111,6 +111,7 @@ ssh_init(struct ssh **sshp, int is_server, struct kex_params *kex_params) # endif #endif /* WITH_OPENSSL */ ssh->kex->kex[KEX_C25519_SHA256] = kexc25519_server; + ssh->kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_kem_server; ssh->kex->load_host_public_key=&_ssh_host_public_key; ssh->kex->load_host_private_key=&_ssh_host_private_key; ssh->kex->sign=&_ssh_host_key_sign; @@ -128,6 +129,7 @@ ssh_init(struct ssh **sshp, int is_server, struct kex_params *kex_params) # endif #endif /* WITH_OPENSSL */ ssh->kex->kex[KEX_C25519_SHA256] = kexc25519_client; + ssh->kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_kem_client; ssh->kex->verify_host_key =&_ssh_verify_host_key; } *sshp = ssh; diff --git a/sshconnect2.c b/sshconnect2.c index 65d8be667..05657fd73 100644 --- a/sshconnect2.c +++ b/sshconnect2.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect2.c,v 1.296 2019/01/21 01:05:00 djm Exp $ */ +/* $OpenBSD: sshconnect2.c,v 1.297 2019/01/21 10:20:12 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2008 Damien Miller. All rights reserved. @@ -213,6 +213,7 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port) # endif #endif ssh->kex->kex[KEX_C25519_SHA256] = kexc25519_client; + ssh->kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_kem_client; ssh->kex->verify_host_key=&verify_host_key_callback; ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &ssh->kex->done); diff --git a/sshd.c b/sshd.c index f6927672e..330b8052d 100644 --- a/sshd.c +++ b/sshd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshd.c,v 1.527 2019/01/19 21:43:56 djm Exp $ */ +/* $OpenBSD: sshd.c,v 1.528 2019/01/21 10:20:12 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -2219,6 +2219,7 @@ do_ssh2_kex(struct ssh *ssh) # endif #endif kex->kex[KEX_C25519_SHA256] = kexc25519_server; + kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_kem_server; kex->load_host_public_key=&get_hostkey_public_by_type; kex->load_host_private_key=&get_hostkey_private_by_type; kex->host_key_index=&get_hostkey_index; -- cgit v1.2.3 From 4b83e2a2cc0c12e671a77eaba1c1245894f4e884 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 21 Jan 2019 10:33:49 +0000 Subject: upstream: remove kex_derive_keys_bn wrapper; no unused since the DH-like KEX methods have moved to KEM from markus@ ok djm@ OpenBSD-Commit-ID: bde9809103832f349545e4f5bb733d316db9a060 --- kex.c | 19 +------------------ kex.h | 3 +-- 2 files changed, 2 insertions(+), 20 deletions(-) (limited to 'kex.c') diff --git a/kex.c b/kex.c index 0dba2cefa..4fb087863 100644 --- a/kex.c +++ b/kex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.c,v 1.147 2019/01/21 10:20:12 djm Exp $ */ +/* $OpenBSD: kex.c,v 1.148 2019/01/21 10:33:49 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * @@ -1038,23 +1038,6 @@ kex_derive_keys(struct ssh *ssh, u_char *hash, u_int hashlen, return 0; } -#ifdef WITH_OPENSSL -int -kex_derive_keys_bn(struct ssh *ssh, u_char *hash, u_int hashlen, - const BIGNUM *secret) -{ - struct sshbuf *shared_secret; - int r; - - if ((shared_secret = sshbuf_new()) == NULL) - return SSH_ERR_ALLOC_FAIL; - if ((r = sshbuf_put_bignum2(shared_secret, secret)) == 0) - r = kex_derive_keys(ssh, hash, hashlen, shared_secret); - sshbuf_free(shared_secret); - return r; -} -#endif - int kex_load_hostkey(struct ssh *ssh, struct sshkey **pubp, struct sshkey **prvp) { diff --git a/kex.h b/kex.h index 9b4c23670..e3be30403 100644 --- a/kex.h +++ b/kex.h @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.h,v 1.102 2019/01/21 10:29:56 djm Exp $ */ +/* $OpenBSD: kex.h,v 1.103 2019/01/21 10:33:49 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -196,7 +196,6 @@ int kex_send_kexinit(struct ssh *); int kex_input_kexinit(int, u_int32_t, struct ssh *); int kex_input_ext_info(int, u_int32_t, struct ssh *); int kex_derive_keys(struct ssh *, u_char *, u_int, const struct sshbuf *); -int kex_derive_keys_bn(struct ssh *, u_char *, u_int, const BIGNUM *); int kex_send_newkeys(struct ssh *); int kex_start_rekex(struct ssh *); -- cgit v1.2.3 From aaca72d6f1279b842066e07bff797019efeb2c23 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 21 Jan 2019 10:40:11 +0000 Subject: upstream: rename kex->kem_client_pub -> kex->client_pub now that KEM has been renamed to kexgen from markus@ ok djm@ OpenBSD-Commit-ID: fac6da5dc63530ad0da537db022a9a4cfbe8bed8 --- Makefile.in | 2 +- kex.c | 4 ++-- kex.h | 19 ++++------------- kexc25519.c | 55 ++---------------------------------------------- kexdh.c | 4 ++-- kexecdh.c | 4 ++-- kexsntrup4591761x25519.c | 4 ++-- monitor.c | 18 ++++++++-------- ssh-keyscan.c | 18 ++++++++-------- ssh_api.c | 34 +++++++++++++++--------------- sshconnect2.c | 18 ++++++++-------- sshd.c | 18 ++++++++-------- 12 files changed, 68 insertions(+), 130 deletions(-) (limited to 'kex.c') diff --git a/Makefile.in b/Makefile.in index fd539184a..6f001bb36 100644 --- a/Makefile.in +++ b/Makefile.in @@ -99,7 +99,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ sc25519.o ge25519.o fe25519.o ed25519.o verify.o hash.o \ kex.o kexdh.o kexgex.o kexecdh.o kexc25519.o \ kexgexc.o kexgexs.o \ - sntrup4591761.o kexsntrup4591761x25519.o kexkemc.o kexkems.o \ + sntrup4591761.o kexsntrup4591761x25519.o kexgen.o \ platform-pledge.o platform-tracing.o platform-misc.o diff --git a/kex.c b/kex.c index 4fb087863..cec9b2985 100644 --- a/kex.c +++ b/kex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.c,v 1.148 2019/01/21 10:33:49 djm Exp $ */ +/* $OpenBSD: kex.c,v 1.149 2019/01/21 10:40:11 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * @@ -655,7 +655,7 @@ kex_free(struct kex *kex) sshbuf_free(kex->my); sshbuf_free(kex->client_version); sshbuf_free(kex->server_version); - sshbuf_free(kex->kem_client_pub); + sshbuf_free(kex->client_pub); free(kex->session_id); free(kex->failed_choice); free(kex->hostkey_alg); diff --git a/kex.h b/kex.h index 6798e33f9..44e6d1972 100644 --- a/kex.h +++ b/kex.h @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.h,v 1.104 2019/01/21 10:35:09 djm Exp $ */ +/* $OpenBSD: kex.h,v 1.106 2019/01/21 10:40:11 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -170,7 +170,7 @@ struct kex { u_char c25519_client_key[CURVE25519_SIZE]; /* 25519 + KEM */ u_char c25519_client_pubkey[CURVE25519_SIZE]; /* 25519 */ u_char sntrup4591761_client_key[crypto_kem_sntrup4591761_SECRETKEYBYTES]; /* KEM */ - struct sshbuf *kem_client_pub; /* KEM */ + struct sshbuf *client_pub; }; int kex_names_valid(const char *); @@ -199,16 +199,10 @@ int kex_derive_keys(struct ssh *, u_char *, u_int, const struct sshbuf *); int kex_send_newkeys(struct ssh *); int kex_start_rekex(struct ssh *); -int kexdh_client(struct ssh *); -int kexdh_server(struct ssh *); int kexgex_client(struct ssh *); int kexgex_server(struct ssh *); -int kexecdh_client(struct ssh *); -int kexecdh_server(struct ssh *); -int kexc25519_client(struct ssh *); -int kexc25519_server(struct ssh *); -int kex_kem_client(struct ssh *); -int kex_kem_server(struct ssh *); +int kex_gen_client(struct ssh *); +int kex_gen_server(struct ssh *); int kex_dh_keypair(struct kex *); int kex_dh_enc(struct kex *, const struct sshbuf *, struct sshbuf **, @@ -241,11 +235,6 @@ int kexgex_hash(int, const struct sshbuf *, const struct sshbuf *, const BIGNUM *, const u_char *, size_t, u_char *, size_t *); -int kex_c25519_hash(int, const struct sshbuf *, const struct sshbuf *, - const u_char *, size_t, const u_char *, size_t, - const u_char *, size_t, const struct sshbuf *, const struct sshbuf *, - const struct sshbuf *, u_char *, size_t *); - void kexc25519_keygen(u_char key[CURVE25519_SIZE], u_char pub[CURVE25519_SIZE]) __attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE))) __attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE))); diff --git a/kexc25519.c b/kexc25519.c index ec5bb574f..f13d766d7 100644 --- a/kexc25519.c +++ b/kexc25519.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexc25519.c,v 1.15 2019/01/21 10:35:09 djm Exp $ */ +/* $OpenBSD: kexc25519.c,v 1.17 2019/01/21 10:40:11 djm Exp $ */ /* * Copyright (c) 2019 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -88,57 +88,6 @@ kexc25519_shared_key(const u_char key[CURVE25519_SIZE], return kexc25519_shared_key_ext(key, pub, out, 0); } -int -kex_c25519_hash( - int hash_alg, - const struct sshbuf *client_version, - const struct sshbuf *server_version, - const u_char *ckexinit, size_t ckexinitlen, - const u_char *skexinit, size_t skexinitlen, - const u_char *serverhostkeyblob, size_t sbloblen, - const struct sshbuf *client_pub, - const struct sshbuf *server_pub, - const struct sshbuf *shared_secret, - u_char *hash, size_t *hashlen) -{ - struct sshbuf *b; - int r; - - if (*hashlen < ssh_digest_bytes(hash_alg)) - return SSH_ERR_INVALID_ARGUMENT; - if ((b = sshbuf_new()) == NULL) - return SSH_ERR_ALLOC_FAIL; - if ((r = sshbuf_put_stringb(b, client_version)) != 0 || - (r = sshbuf_put_stringb(b, server_version)) != 0 || - /* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */ - (r = sshbuf_put_u32(b, ckexinitlen+1)) != 0 || - (r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) != 0 || - (r = sshbuf_put(b, ckexinit, ckexinitlen)) != 0 || - (r = sshbuf_put_u32(b, skexinitlen+1)) != 0 || - (r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) != 0 || - (r = sshbuf_put(b, skexinit, skexinitlen)) != 0 || - (r = sshbuf_put_string(b, serverhostkeyblob, sbloblen)) != 0 || - (r = sshbuf_put_stringb(b, client_pub)) != 0 || - (r = sshbuf_put_stringb(b, server_pub)) != 0 || - (r = sshbuf_putb(b, shared_secret)) != 0) { - sshbuf_free(b); - return r; - } -#ifdef DEBUG_KEX - sshbuf_dump(b, stderr); -#endif - if (ssh_digest_buffer(hash_alg, b, hash, *hashlen) != 0) { - sshbuf_free(b); - return SSH_ERR_LIBCRYPTO_ERROR; - } - sshbuf_free(b); - *hashlen = ssh_digest_bytes(hash_alg); -#ifdef DEBUG_KEX - dump_digest("hash", hash, *hashlen); -#endif - return 0; -} - int kex_c25519_keypair(struct kex *kex) { @@ -154,7 +103,7 @@ kex_c25519_keypair(struct kex *kex) #ifdef DEBUG_KEXECDH dump_digest("client public key c25519:", cp, CURVE25519_SIZE); #endif - kex->kem_client_pub = buf; + kex->client_pub = buf; buf = NULL; out: sshbuf_free(buf); diff --git a/kexdh.c b/kexdh.c index 943774624..6812add20 100644 --- a/kexdh.c +++ b/kexdh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexdh.c,v 1.31 2019/01/21 10:35:09 djm Exp $ */ +/* $OpenBSD: kexdh.c,v 1.32 2019/01/21 10:40:11 djm Exp $ */ /* * Copyright (c) 2019 Markus Friedl. All rights reserved. * @@ -128,7 +128,7 @@ kex_dh_keypair(struct kex *kex) BN_print_fp(stderr, pub_key); fprintf(stderr, "\n"); #endif - kex->kem_client_pub = buf; + kex->client_pub = buf; buf = NULL; out: sshbuf_free(buf); diff --git a/kexecdh.c b/kexecdh.c index ae9018773..0aeab2e9b 100644 --- a/kexecdh.c +++ b/kexecdh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexecdh.c,v 1.9 2019/01/21 10:35:09 djm Exp $ */ +/* $OpenBSD: kexecdh.c,v 1.10 2019/01/21 10:40:11 djm Exp $ */ /* * Copyright (c) 2010 Damien Miller. All rights reserved. * Copyright (c) 2019 Markus Friedl. All rights reserved. @@ -80,7 +80,7 @@ kex_ecdh_keypair(struct kex *kex) kex->ec_client_key = client_key; kex->ec_group = group; client_key = NULL; /* owned by the kex */ - kex->kem_client_pub = buf; + kex->client_pub = buf; buf = NULL; out: EC_KEY_free(client_key); diff --git a/kexsntrup4591761x25519.c b/kexsntrup4591761x25519.c index d845f3d44..b0605b96a 100644 --- a/kexsntrup4591761x25519.c +++ b/kexsntrup4591761x25519.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexsntrup4591761x25519.c,v 1.2 2019/01/21 10:35:09 djm Exp $ */ +/* $OpenBSD: kexsntrup4591761x25519.c,v 1.3 2019/01/21 10:40:11 djm Exp $ */ /* * Copyright (c) 2019 Markus Friedl. All rights reserved. * @@ -58,7 +58,7 @@ kex_kem_sntrup4591761x25519_keypair(struct kex *kex) #ifdef DEBUG_KEXECDH dump_digest("client public key c25519:", cp, CURVE25519_SIZE); #endif - kex->kem_client_pub = buf; + kex->client_pub = buf; buf = NULL; out: sshbuf_free(buf); diff --git a/monitor.c b/monitor.c index d3357b73c..60e529444 100644 --- a/monitor.c +++ b/monitor.c @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor.c,v 1.196 2019/01/21 10:29:56 djm Exp $ */ +/* $OpenBSD: monitor.c,v 1.197 2019/01/21 10:38:54 djm Exp $ */ /* * Copyright 2002 Niels Provos * Copyright 2002 Markus Friedl @@ -1677,19 +1677,19 @@ monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor) if ((kex = ssh->kex) != NULL) { /* XXX set callbacks */ #ifdef WITH_OPENSSL - kex->kex[KEX_DH_GRP1_SHA1] = kex_kem_server; - kex->kex[KEX_DH_GRP14_SHA1] = kex_kem_server; - kex->kex[KEX_DH_GRP14_SHA256] = kex_kem_server; - kex->kex[KEX_DH_GRP16_SHA512] = kex_kem_server; - kex->kex[KEX_DH_GRP18_SHA512] = kex_kem_server; + kex->kex[KEX_DH_GRP1_SHA1] = kex_gen_server; + kex->kex[KEX_DH_GRP14_SHA1] = kex_gen_server; + kex->kex[KEX_DH_GRP14_SHA256] = kex_gen_server; + kex->kex[KEX_DH_GRP16_SHA512] = kex_gen_server; + kex->kex[KEX_DH_GRP18_SHA512] = kex_gen_server; kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; kex->kex[KEX_DH_GEX_SHA256] = kexgex_server; # ifdef OPENSSL_HAS_ECC - kex->kex[KEX_ECDH_SHA2] = kex_kem_server; + kex->kex[KEX_ECDH_SHA2] = kex_gen_server; # endif #endif /* WITH_OPENSSL */ - kex->kex[KEX_C25519_SHA256] = kex_kem_server; - kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_kem_server; + kex->kex[KEX_C25519_SHA256] = kex_gen_server; + kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_gen_server; kex->load_host_public_key=&get_hostkey_public_by_type; kex->load_host_private_key=&get_hostkey_private_by_type; kex->host_key_index=&get_hostkey_index; diff --git a/ssh-keyscan.c b/ssh-keyscan.c index 9541ecf4a..144daa6df 100644 --- a/ssh-keyscan.c +++ b/ssh-keyscan.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-keyscan.c,v 1.124 2019/01/21 10:29:56 djm Exp $ */ +/* $OpenBSD: ssh-keyscan.c,v 1.125 2019/01/21 10:38:54 djm Exp $ */ /* * Copyright 1995, 1996 by David Mazieres . * @@ -260,19 +260,19 @@ keygrab_ssh2(con *c) exit(1); } #ifdef WITH_OPENSSL - c->c_ssh->kex->kex[KEX_DH_GRP1_SHA1] = kex_kem_client; - c->c_ssh->kex->kex[KEX_DH_GRP14_SHA1] = kex_kem_client; - c->c_ssh->kex->kex[KEX_DH_GRP14_SHA256] = kex_kem_client; - c->c_ssh->kex->kex[KEX_DH_GRP16_SHA512] = kex_kem_client; - c->c_ssh->kex->kex[KEX_DH_GRP18_SHA512] = kex_kem_client; + c->c_ssh->kex->kex[KEX_DH_GRP1_SHA1] = kex_gen_client; + c->c_ssh->kex->kex[KEX_DH_GRP14_SHA1] = kex_gen_client; + c->c_ssh->kex->kex[KEX_DH_GRP14_SHA256] = kex_gen_client; + c->c_ssh->kex->kex[KEX_DH_GRP16_SHA512] = kex_gen_client; + c->c_ssh->kex->kex[KEX_DH_GRP18_SHA512] = kex_gen_client; c->c_ssh->kex->kex[KEX_DH_GEX_SHA1] = kexgex_client; c->c_ssh->kex->kex[KEX_DH_GEX_SHA256] = kexgex_client; # ifdef OPENSSL_HAS_ECC - c->c_ssh->kex->kex[KEX_ECDH_SHA2] = kex_kem_client; + c->c_ssh->kex->kex[KEX_ECDH_SHA2] = kex_gen_client; # endif #endif - c->c_ssh->kex->kex[KEX_C25519_SHA256] = kex_kem_client; - c->c_ssh->kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_kem_client; + c->c_ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client; + c->c_ssh->kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_gen_client; ssh_set_verify_host_key_callback(c->c_ssh, key_print_wrapper); /* * do the key-exchange until an error occurs or until diff --git a/ssh_api.c b/ssh_api.c index b21769d23..57509973b 100644 --- a/ssh_api.c +++ b/ssh_api.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh_api.c,v 1.14 2019/01/21 10:29:56 djm Exp $ */ +/* $OpenBSD: ssh_api.c,v 1.15 2019/01/21 10:38:54 djm Exp $ */ /* * Copyright (c) 2012 Markus Friedl. All rights reserved. * @@ -99,37 +99,37 @@ ssh_init(struct ssh **sshp, int is_server, struct kex_params *kex_params) ssh->kex->server = is_server; if (is_server) { #ifdef WITH_OPENSSL - ssh->kex->kex[KEX_DH_GRP1_SHA1] = kex_kem_server; - ssh->kex->kex[KEX_DH_GRP14_SHA1] = kex_kem_server; - ssh->kex->kex[KEX_DH_GRP14_SHA256] = kex_kem_server; - ssh->kex->kex[KEX_DH_GRP16_SHA512] = kex_kem_server; - ssh->kex->kex[KEX_DH_GRP18_SHA512] = kex_kem_server; + ssh->kex->kex[KEX_DH_GRP1_SHA1] = kex_gen_server; + ssh->kex->kex[KEX_DH_GRP14_SHA1] = kex_gen_server; + ssh->kex->kex[KEX_DH_GRP14_SHA256] = kex_gen_server; + ssh->kex->kex[KEX_DH_GRP16_SHA512] = kex_gen_server; + ssh->kex->kex[KEX_DH_GRP18_SHA512] = kex_gen_server; ssh->kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; ssh->kex->kex[KEX_DH_GEX_SHA256] = kexgex_server; # ifdef OPENSSL_HAS_ECC - ssh->kex->kex[KEX_ECDH_SHA2] = kex_kem_server; + ssh->kex->kex[KEX_ECDH_SHA2] = kex_gen_server; # endif #endif /* WITH_OPENSSL */ - ssh->kex->kex[KEX_C25519_SHA256] = kex_kem_server; - ssh->kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_kem_server; + ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_server; + ssh->kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_gen_server; ssh->kex->load_host_public_key=&_ssh_host_public_key; ssh->kex->load_host_private_key=&_ssh_host_private_key; ssh->kex->sign=&_ssh_host_key_sign; } else { #ifdef WITH_OPENSSL - ssh->kex->kex[KEX_DH_GRP1_SHA1] = kex_kem_client; - ssh->kex->kex[KEX_DH_GRP14_SHA1] = kex_kem_client; - ssh->kex->kex[KEX_DH_GRP14_SHA256] = kex_kem_client; - ssh->kex->kex[KEX_DH_GRP16_SHA512] = kex_kem_client; - ssh->kex->kex[KEX_DH_GRP18_SHA512] = kex_kem_client; + ssh->kex->kex[KEX_DH_GRP1_SHA1] = kex_gen_client; + ssh->kex->kex[KEX_DH_GRP14_SHA1] = kex_gen_client; + ssh->kex->kex[KEX_DH_GRP14_SHA256] = kex_gen_client; + ssh->kex->kex[KEX_DH_GRP16_SHA512] = kex_gen_client; + ssh->kex->kex[KEX_DH_GRP18_SHA512] = kex_gen_client; ssh->kex->kex[KEX_DH_GEX_SHA1] = kexgex_client; ssh->kex->kex[KEX_DH_GEX_SHA256] = kexgex_client; # ifdef OPENSSL_HAS_ECC - ssh->kex->kex[KEX_ECDH_SHA2] = kex_kem_client; + ssh->kex->kex[KEX_ECDH_SHA2] = kex_gen_client; # endif #endif /* WITH_OPENSSL */ - ssh->kex->kex[KEX_C25519_SHA256] = kex_kem_client; - ssh->kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_kem_client; + ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client; + ssh->kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_gen_client; ssh->kex->verify_host_key =&_ssh_verify_host_key; } *sshp = ssh; diff --git a/sshconnect2.c b/sshconnect2.c index aa5160185..2aa7b9933 100644 --- a/sshconnect2.c +++ b/sshconnect2.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect2.c,v 1.300 2019/01/21 10:29:56 djm Exp $ */ +/* $OpenBSD: sshconnect2.c,v 1.301 2019/01/21 10:38:54 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2008 Damien Miller. All rights reserved. @@ -201,19 +201,19 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port) if ((r = kex_setup(ssh, myproposal)) != 0) fatal("kex_setup: %s", ssh_err(r)); #ifdef WITH_OPENSSL - ssh->kex->kex[KEX_DH_GRP1_SHA1] = kex_kem_client; - ssh->kex->kex[KEX_DH_GRP14_SHA1] = kex_kem_client; - ssh->kex->kex[KEX_DH_GRP14_SHA256] = kex_kem_client; - ssh->kex->kex[KEX_DH_GRP16_SHA512] = kex_kem_client; - ssh->kex->kex[KEX_DH_GRP18_SHA512] = kex_kem_client; + ssh->kex->kex[KEX_DH_GRP1_SHA1] = kex_gen_client; + ssh->kex->kex[KEX_DH_GRP14_SHA1] = kex_gen_client; + ssh->kex->kex[KEX_DH_GRP14_SHA256] = kex_gen_client; + ssh->kex->kex[KEX_DH_GRP16_SHA512] = kex_gen_client; + ssh->kex->kex[KEX_DH_GRP18_SHA512] = kex_gen_client; ssh->kex->kex[KEX_DH_GEX_SHA1] = kexgex_client; ssh->kex->kex[KEX_DH_GEX_SHA256] = kexgex_client; # ifdef OPENSSL_HAS_ECC - ssh->kex->kex[KEX_ECDH_SHA2] = kex_kem_client; + ssh->kex->kex[KEX_ECDH_SHA2] = kex_gen_client; # endif #endif - ssh->kex->kex[KEX_C25519_SHA256] = kex_kem_client; - ssh->kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_kem_client; + ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client; + ssh->kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_gen_client; ssh->kex->verify_host_key=&verify_host_key_callback; ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &ssh->kex->done); diff --git a/sshd.c b/sshd.c index ddbedd6c6..058260d6f 100644 --- a/sshd.c +++ b/sshd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshd.c,v 1.531 2019/01/21 10:29:56 djm Exp $ */ +/* $OpenBSD: sshd.c,v 1.532 2019/01/21 10:38:54 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -2207,19 +2207,19 @@ do_ssh2_kex(struct ssh *ssh) fatal("kex_setup: %s", ssh_err(r)); kex = ssh->kex; #ifdef WITH_OPENSSL - kex->kex[KEX_DH_GRP1_SHA1] = kex_kem_server; - kex->kex[KEX_DH_GRP14_SHA1] = kex_kem_server; - kex->kex[KEX_DH_GRP14_SHA256] = kex_kem_server; - kex->kex[KEX_DH_GRP16_SHA512] = kex_kem_server; - kex->kex[KEX_DH_GRP18_SHA512] = kex_kem_server; + kex->kex[KEX_DH_GRP1_SHA1] = kex_gen_server; + kex->kex[KEX_DH_GRP14_SHA1] = kex_gen_server; + kex->kex[KEX_DH_GRP14_SHA256] = kex_gen_server; + kex->kex[KEX_DH_GRP16_SHA512] = kex_gen_server; + kex->kex[KEX_DH_GRP18_SHA512] = kex_gen_server; kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; kex->kex[KEX_DH_GEX_SHA256] = kexgex_server; # ifdef OPENSSL_HAS_ECC - kex->kex[KEX_ECDH_SHA2] = kex_kem_server; + kex->kex[KEX_ECDH_SHA2] = kex_gen_server; # endif #endif - kex->kex[KEX_C25519_SHA256] = kex_kem_server; - kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_kem_server; + kex->kex[KEX_C25519_SHA256] = kex_gen_server; + kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_gen_server; kex->load_host_public_key=&get_hostkey_public_by_type; kex->load_host_private_key=&get_hostkey_private_by_type; kex->host_key_index=&get_hostkey_index; -- cgit v1.2.3 From 70edd73edc4df54e5eee50cd27c25427b34612f8 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 21 Jan 2019 12:08:13 +0000 Subject: upstream: fix reversed arguments to kex_load_hostkey(); manifested as errors in cert-hostkey.sh regress failures. OpenBSD-Commit-ID: 12dab63850b844f84d5a67e86d9e21a42fba93ba --- kex.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'kex.c') diff --git a/kex.c b/kex.c index cec9b2985..34808b5c3 100644 --- a/kex.c +++ b/kex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.c,v 1.149 2019/01/21 10:40:11 djm Exp $ */ +/* $OpenBSD: kex.c,v 1.150 2019/01/21 12:08:13 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * @@ -1039,7 +1039,7 @@ kex_derive_keys(struct ssh *ssh, u_char *hash, u_int hashlen, } int -kex_load_hostkey(struct ssh *ssh, struct sshkey **pubp, struct sshkey **prvp) +kex_load_hostkey(struct ssh *ssh, struct sshkey **prvp, struct sshkey **pubp) { struct kex *kex = ssh->kex; -- cgit v1.2.3 From 7ce79be85036c4b36937f1b1ba85f6094068412c Mon Sep 17 00:00:00 2001 From: Simon Wilkinson Date: Sun, 9 Feb 2014 16:09:48 +0000 Subject: GSSAPI key exchange support This patch has been rejected upstream: "None of the OpenSSH developers are in favour of adding this, and this situation has not changed for several years. This is not a slight on Simon's patch, which is of fine quality, but just that a) we don't trust GSSAPI implementations that much and b) we don't like adding new KEX since they are pre-auth attack surface. This one is particularly scary, since it requires hooks out to typically root-owned system resources." However, quite a lot of people rely on this in Debian, and it's better to have it merged into the main openssh package rather than having separate -krb5 packages (as we used to have). It seems to have a generally good security history. Origin: other, https://github.com/openssh-gsskex/openssh-gsskex/commits/debian/master Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1242 Last-Updated: 2019-06-05 Patch-Name: gssapi.patch --- Makefile.in | 3 +- auth-krb5.c | 17 +- auth.c | 96 +-------- auth2-gss.c | 56 +++++- auth2.c | 2 + canohost.c | 93 +++++++++ canohost.h | 3 + clientloop.c | 15 +- configure.ac | 24 +++ gss-genr.c | 300 +++++++++++++++++++++++++++- gss-serv-krb5.c | 85 +++++++- gss-serv.c | 186 +++++++++++++++-- hmac.c | 1 + kex.c | 66 +++++- kex.h | 29 +++ kexdh.c | 10 + kexgen.c | 2 +- kexgssc.c | 606 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ kexgsss.c | 474 ++++++++++++++++++++++++++++++++++++++++++++ mac.c | 1 + monitor.c | 139 ++++++++++++- monitor.h | 2 + monitor_wrap.c | 57 +++++- monitor_wrap.h | 4 +- readconf.c | 70 +++++++ readconf.h | 6 + servconf.c | 47 +++++ servconf.h | 3 + session.c | 10 +- ssh-gss.h | 50 ++++- ssh.1 | 8 + ssh.c | 4 +- ssh_config | 2 + ssh_config.5 | 57 ++++++ sshconnect2.c | 140 ++++++++++++- sshd.c | 120 ++++++++++- sshd_config | 2 + sshd_config.5 | 30 +++ sshkey.c | 3 +- sshkey.h | 1 + 40 files changed, 2664 insertions(+), 160 deletions(-) create mode 100644 kexgssc.c create mode 100644 kexgsss.c (limited to 'kex.c') diff --git a/Makefile.in b/Makefile.in index 6f001bb36..c31821acc 100644 --- a/Makefile.in +++ b/Makefile.in @@ -100,6 +100,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ kex.o kexdh.o kexgex.o kexecdh.o kexc25519.o \ kexgexc.o kexgexs.o \ sntrup4591761.o kexsntrup4591761x25519.o kexgen.o \ + kexgssc.o \ platform-pledge.o platform-tracing.o platform-misc.o @@ -114,7 +115,7 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o \ auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \ auth2-none.o auth2-passwd.o auth2-pubkey.o \ monitor.o monitor_wrap.o auth-krb5.o \ - auth2-gss.o gss-serv.o gss-serv-krb5.o \ + auth2-gss.o gss-serv.o gss-serv-krb5.o kexgsss.o \ loginrec.o auth-pam.o auth-shadow.o auth-sia.o md5crypt.o \ sftp-server.o sftp-common.o \ sandbox-null.o sandbox-rlimit.o sandbox-systrace.o sandbox-darwin.o \ diff --git a/auth-krb5.c b/auth-krb5.c index 3096f1c8e..204752e1b 100644 --- a/auth-krb5.c +++ b/auth-krb5.c @@ -182,8 +182,13 @@ auth_krb5_password(Authctxt *authctxt, const char *password) len = strlen(authctxt->krb5_ticket_file) + 6; authctxt->krb5_ccname = xmalloc(len); +#ifdef USE_CCAPI + snprintf(authctxt->krb5_ccname, len, "API:%s", + authctxt->krb5_ticket_file); +#else snprintf(authctxt->krb5_ccname, len, "FILE:%s", authctxt->krb5_ticket_file); +#endif #ifdef USE_PAM if (options.use_pam) @@ -240,15 +245,22 @@ krb5_cleanup_proc(Authctxt *authctxt) #ifndef HEIMDAL krb5_error_code ssh_krb5_cc_gen(krb5_context ctx, krb5_ccache *ccache) { - int tmpfd, ret, oerrno; + int ret, oerrno; char ccname[40]; mode_t old_umask; +#ifdef USE_CCAPI + char cctemplate[] = "API:krb5cc_%d"; +#else + char cctemplate[] = "FILE:/tmp/krb5cc_%d_XXXXXXXXXX"; + int tmpfd; +#endif ret = snprintf(ccname, sizeof(ccname), - "FILE:/tmp/krb5cc_%d_XXXXXXXXXX", geteuid()); + cctemplate, geteuid()); if (ret < 0 || (size_t)ret >= sizeof(ccname)) return ENOMEM; +#ifndef USE_CCAPI old_umask = umask(0177); tmpfd = mkstemp(ccname + strlen("FILE:")); oerrno = errno; @@ -265,6 +277,7 @@ ssh_krb5_cc_gen(krb5_context ctx, krb5_ccache *ccache) { return oerrno; } close(tmpfd); +#endif return (krb5_cc_resolve(ctx, ccname, ccache)); } diff --git a/auth.c b/auth.c index 8696f258e..f7a23afba 100644 --- a/auth.c +++ b/auth.c @@ -399,7 +399,8 @@ auth_root_allowed(struct ssh *ssh, const char *method) case PERMIT_NO_PASSWD: if (strcmp(method, "publickey") == 0 || strcmp(method, "hostbased") == 0 || - strcmp(method, "gssapi-with-mic") == 0) + strcmp(method, "gssapi-with-mic") == 0 || + strcmp(method, "gssapi-keyex") == 0) return 1; break; case PERMIT_FORCED_ONLY: @@ -723,99 +724,6 @@ fakepw(void) return (&fake); } -/* - * Returns the remote DNS hostname as a string. The returned string must not - * be freed. NB. this will usually trigger a DNS query the first time it is - * called. - * This function does additional checks on the hostname to mitigate some - * attacks on legacy rhosts-style authentication. - * XXX is RhostsRSAAuthentication vulnerable to these? - * XXX Can we remove these checks? (or if not, remove RhostsRSAAuthentication?) - */ - -static char * -remote_hostname(struct ssh *ssh) -{ - struct sockaddr_storage from; - socklen_t fromlen; - struct addrinfo hints, *ai, *aitop; - char name[NI_MAXHOST], ntop2[NI_MAXHOST]; - const char *ntop = ssh_remote_ipaddr(ssh); - - /* Get IP address of client. */ - fromlen = sizeof(from); - memset(&from, 0, sizeof(from)); - if (getpeername(ssh_packet_get_connection_in(ssh), - (struct sockaddr *)&from, &fromlen) < 0) { - debug("getpeername failed: %.100s", strerror(errno)); - return strdup(ntop); - } - - ipv64_normalise_mapped(&from, &fromlen); - if (from.ss_family == AF_INET6) - fromlen = sizeof(struct sockaddr_in6); - - debug3("Trying to reverse map address %.100s.", ntop); - /* Map the IP address to a host name. */ - if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name), - NULL, 0, NI_NAMEREQD) != 0) { - /* Host name not found. Use ip address. */ - return strdup(ntop); - } - - /* - * if reverse lookup result looks like a numeric hostname, - * someone is trying to trick us by PTR record like following: - * 1.1.1.10.in-addr.arpa. IN PTR 2.3.4.5 - */ - memset(&hints, 0, sizeof(hints)); - hints.ai_socktype = SOCK_DGRAM; /*dummy*/ - hints.ai_flags = AI_NUMERICHOST; - if (getaddrinfo(name, NULL, &hints, &ai) == 0) { - logit("Nasty PTR record \"%s\" is set up for %s, ignoring", - name, ntop); - freeaddrinfo(ai); - return strdup(ntop); - } - - /* Names are stored in lowercase. */ - lowercase(name); - - /* - * Map it back to an IP address and check that the given - * address actually is an address of this host. This is - * necessary because anyone with access to a name server can - * define arbitrary names for an IP address. Mapping from - * name to IP address can be trusted better (but can still be - * fooled if the intruder has access to the name server of - * the domain). - */ - memset(&hints, 0, sizeof(hints)); - hints.ai_family = from.ss_family; - hints.ai_socktype = SOCK_STREAM; - if (getaddrinfo(name, NULL, &hints, &aitop) != 0) { - logit("reverse mapping checking getaddrinfo for %.700s " - "[%s] failed.", name, ntop); - return strdup(ntop); - } - /* Look for the address from the list of addresses. */ - for (ai = aitop; ai; ai = ai->ai_next) { - if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop2, - sizeof(ntop2), NULL, 0, NI_NUMERICHOST) == 0 && - (strcmp(ntop, ntop2) == 0)) - break; - } - freeaddrinfo(aitop); - /* If we reached the end of the list, the address was not there. */ - if (ai == NULL) { - /* Address not found for the host name. */ - logit("Address %.100s maps to %.600s, but this does not " - "map back to the address.", ntop, name); - return strdup(ntop); - } - return strdup(name); -} - /* * Return the canonical name of the host in the other side of the current * connection. The host name is cached, so it is efficient to call this diff --git a/auth2-gss.c b/auth2-gss.c index 9351e0428..d6446c0cf 100644 --- a/auth2-gss.c +++ b/auth2-gss.c @@ -1,7 +1,7 @@ /* $OpenBSD: auth2-gss.c,v 1.29 2018/07/31 03:10:27 djm Exp $ */ /* - * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. + * Copyright (c) 2001-2007 Simon Wilkinson. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -54,6 +54,48 @@ static int input_gssapi_mic(int type, u_int32_t plen, struct ssh *ssh); static int input_gssapi_exchange_complete(int type, u_int32_t plen, struct ssh *ssh); static int input_gssapi_errtok(int, u_int32_t, struct ssh *); +/* + * The 'gssapi_keyex' userauth mechanism. + */ +static int +userauth_gsskeyex(struct ssh *ssh) +{ + Authctxt *authctxt = ssh->authctxt; + int r, authenticated = 0; + struct sshbuf *b = NULL; + gss_buffer_desc mic, gssbuf; + u_char *p; + size_t len; + + if ((r = sshpkt_get_string(ssh, &p, &len)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + fatal("%s: %s", __func__, ssh_err(r)); + + if ((b = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new failed", __func__); + + mic.value = p; + mic.length = len; + + ssh_gssapi_buildmic(b, authctxt->user, authctxt->service, + "gssapi-keyex"); + + if ((gssbuf.value = sshbuf_mutable_ptr(b)) == NULL) + fatal("%s: sshbuf_mutable_ptr failed", __func__); + gssbuf.length = sshbuf_len(b); + + /* gss_kex_context is NULL with privsep, so we can't check it here */ + if (!GSS_ERROR(PRIVSEP(ssh_gssapi_checkmic(gss_kex_context, + &gssbuf, &mic)))) + authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user, + authctxt->pw, 1)); + + sshbuf_free(b); + free(mic.value); + + return (authenticated); +} + /* * We only support those mechanisms that we know about (ie ones that we know * how to check local user kuserok and the like) @@ -260,7 +302,8 @@ input_gssapi_exchange_complete(int type, u_int32_t plen, struct ssh *ssh) if ((r = sshpkt_get_end(ssh)) != 0) fatal("%s: %s", __func__, ssh_err(r)); - authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user)); + authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user, + authctxt->pw, 1)); if ((!use_privsep || mm_is_monitor()) && (displayname = ssh_gssapi_displayname()) != NULL) @@ -306,7 +349,8 @@ input_gssapi_mic(int type, u_int32_t plen, struct ssh *ssh) gssbuf.length = sshbuf_len(b); if (!GSS_ERROR(PRIVSEP(ssh_gssapi_checkmic(gssctxt, &gssbuf, &mic)))) - authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user)); + authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user, + authctxt->pw, 0)); else logit("GSSAPI MIC check failed"); @@ -326,6 +370,12 @@ input_gssapi_mic(int type, u_int32_t plen, struct ssh *ssh) return 0; } +Authmethod method_gsskeyex = { + "gssapi-keyex", + userauth_gsskeyex, + &options.gss_authentication +}; + Authmethod method_gssapi = { "gssapi-with-mic", userauth_gssapi, diff --git a/auth2.c b/auth2.c index 16ae1a363..7417eafa4 100644 --- a/auth2.c +++ b/auth2.c @@ -75,6 +75,7 @@ extern Authmethod method_passwd; extern Authmethod method_kbdint; extern Authmethod method_hostbased; #ifdef GSSAPI +extern Authmethod method_gsskeyex; extern Authmethod method_gssapi; #endif @@ -82,6 +83,7 @@ Authmethod *authmethods[] = { &method_none, &method_pubkey, #ifdef GSSAPI + &method_gsskeyex, &method_gssapi, #endif &method_passwd, diff --git a/canohost.c b/canohost.c index f71a08568..404731d24 100644 --- a/canohost.c +++ b/canohost.c @@ -35,6 +35,99 @@ #include "canohost.h" #include "misc.h" +/* + * Returns the remote DNS hostname as a string. The returned string must not + * be freed. NB. this will usually trigger a DNS query the first time it is + * called. + * This function does additional checks on the hostname to mitigate some + * attacks on legacy rhosts-style authentication. + * XXX is RhostsRSAAuthentication vulnerable to these? + * XXX Can we remove these checks? (or if not, remove RhostsRSAAuthentication?) + */ + +char * +remote_hostname(struct ssh *ssh) +{ + struct sockaddr_storage from; + socklen_t fromlen; + struct addrinfo hints, *ai, *aitop; + char name[NI_MAXHOST], ntop2[NI_MAXHOST]; + const char *ntop = ssh_remote_ipaddr(ssh); + + /* Get IP address of client. */ + fromlen = sizeof(from); + memset(&from, 0, sizeof(from)); + if (getpeername(ssh_packet_get_connection_in(ssh), + (struct sockaddr *)&from, &fromlen) < 0) { + debug("getpeername failed: %.100s", strerror(errno)); + return strdup(ntop); + } + + ipv64_normalise_mapped(&from, &fromlen); + if (from.ss_family == AF_INET6) + fromlen = sizeof(struct sockaddr_in6); + + debug3("Trying to reverse map address %.100s.", ntop); + /* Map the IP address to a host name. */ + if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name), + NULL, 0, NI_NAMEREQD) != 0) { + /* Host name not found. Use ip address. */ + return strdup(ntop); + } + + /* + * if reverse lookup result looks like a numeric hostname, + * someone is trying to trick us by PTR record like following: + * 1.1.1.10.in-addr.arpa. IN PTR 2.3.4.5 + */ + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_DGRAM; /*dummy*/ + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(name, NULL, &hints, &ai) == 0) { + logit("Nasty PTR record \"%s\" is set up for %s, ignoring", + name, ntop); + freeaddrinfo(ai); + return strdup(ntop); + } + + /* Names are stored in lowercase. */ + lowercase(name); + + /* + * Map it back to an IP address and check that the given + * address actually is an address of this host. This is + * necessary because anyone with access to a name server can + * define arbitrary names for an IP address. Mapping from + * name to IP address can be trusted better (but can still be + * fooled if the intruder has access to the name server of + * the domain). + */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = from.ss_family; + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo(name, NULL, &hints, &aitop) != 0) { + logit("reverse mapping checking getaddrinfo for %.700s " + "[%s] failed.", name, ntop); + return strdup(ntop); + } + /* Look for the address from the list of addresses. */ + for (ai = aitop; ai; ai = ai->ai_next) { + if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop2, + sizeof(ntop2), NULL, 0, NI_NUMERICHOST) == 0 && + (strcmp(ntop, ntop2) == 0)) + break; + } + freeaddrinfo(aitop); + /* If we reached the end of the list, the address was not there. */ + if (ai == NULL) { + /* Address not found for the host name. */ + logit("Address %.100s maps to %.600s, but this does not " + "map back to the address.", ntop, name); + return strdup(ntop); + } + return strdup(name); +} + void ipv64_normalise_mapped(struct sockaddr_storage *addr, socklen_t *len) { diff --git a/canohost.h b/canohost.h index 26d62855a..0cadc9f18 100644 --- a/canohost.h +++ b/canohost.h @@ -15,6 +15,9 @@ #ifndef _CANOHOST_H #define _CANOHOST_H +struct ssh; + +char *remote_hostname(struct ssh *); char *get_peer_ipaddr(int); int get_peer_port(int); char *get_local_ipaddr(int); diff --git a/clientloop.c b/clientloop.c index 086c0dfe8..9b90c64f3 100644 --- a/clientloop.c +++ b/clientloop.c @@ -112,6 +112,10 @@ #include "ssherr.h" #include "hostfile.h" +#ifdef GSSAPI +#include "ssh-gss.h" +#endif + /* import options */ extern Options options; @@ -1374,9 +1378,18 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg, break; /* Do channel operations unless rekeying in progress. */ - if (!ssh_packet_is_rekeying(ssh)) + if (!ssh_packet_is_rekeying(ssh)) { channel_after_select(ssh, readset, writeset); +#ifdef GSSAPI + if (options.gss_renewal_rekey && + ssh_gssapi_credentials_updated(NULL)) { + debug("credentials updated - forcing rekey"); + need_rekeying = 1; + } +#endif + } + /* Buffer input from the connection. */ client_process_net_input(ssh, readset); diff --git a/configure.ac b/configure.ac index 30be6c182..2869f7042 100644 --- a/configure.ac +++ b/configure.ac @@ -665,6 +665,30 @@ main() { if (NSVersionOfRunTimeLibrary("System") >= (60 << 16)) [Use tunnel device compatibility to OpenBSD]) AC_DEFINE([SSH_TUN_PREPEND_AF], [1], [Prepend the address family to IP tunnel traffic]) + AC_MSG_CHECKING([if we have the Security Authorization Session API]) + AC_TRY_COMPILE([#include ], + [SessionCreate(0, 0);], + [ac_cv_use_security_session_api="yes" + AC_DEFINE([USE_SECURITY_SESSION_API], [1], + [platform has the Security Authorization Session API]) + LIBS="$LIBS -framework Security" + AC_MSG_RESULT([yes])], + [ac_cv_use_security_session_api="no" + AC_MSG_RESULT([no])]) + AC_MSG_CHECKING([if we have an in-memory credentials cache]) + AC_TRY_COMPILE( + [#include ], + [cc_context_t c; + (void) cc_initialize (&c, 0, NULL, NULL);], + [AC_DEFINE([USE_CCAPI], [1], + [platform uses an in-memory credentials cache]) + LIBS="$LIBS -framework Security" + AC_MSG_RESULT([yes]) + if test "x$ac_cv_use_security_session_api" = "xno"; then + AC_MSG_ERROR([*** Need a security framework to use the credentials cache API ***]) + fi], + [AC_MSG_RESULT([no])] + ) m4_pattern_allow([AU_IPv]) AC_CHECK_DECL([AU_IPv4], [], AC_DEFINE([AU_IPv4], [0], [System only supports IPv4 audit records]) diff --git a/gss-genr.c b/gss-genr.c index d56257b4a..763a63ffa 100644 --- a/gss-genr.c +++ b/gss-genr.c @@ -1,7 +1,7 @@ /* $OpenBSD: gss-genr.c,v 1.26 2018/07/10 09:13:30 djm Exp $ */ /* - * Copyright (c) 2001-2007 Simon Wilkinson. All rights reserved. + * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -41,12 +41,36 @@ #include "sshbuf.h" #include "log.h" #include "ssh2.h" +#include "cipher.h" +#include "sshkey.h" +#include "kex.h" +#include "digest.h" +#include "packet.h" #include "ssh-gss.h" extern u_char *session_id2; extern u_int session_id2_len; +typedef struct { + char *encoded; + gss_OID oid; +} ssh_gss_kex_mapping; + +/* + * XXX - It would be nice to find a more elegant way of handling the + * XXX passing of the key exchange context to the userauth routines + */ + +Gssctxt *gss_kex_context = NULL; + +static ssh_gss_kex_mapping *gss_enc2oid = NULL; + +int +ssh_gssapi_oid_table_ok(void) { + return (gss_enc2oid != NULL); +} + /* sshbuf_get for gss_buffer_desc */ int ssh_gssapi_get_buffer_desc(struct sshbuf *b, gss_buffer_desc *g) @@ -62,6 +86,162 @@ ssh_gssapi_get_buffer_desc(struct sshbuf *b, gss_buffer_desc *g) return 0; } +/* sshpkt_get of gss_buffer_desc */ +int +ssh_gssapi_sshpkt_get_buffer_desc(struct ssh *ssh, gss_buffer_desc *g) +{ + int r; + u_char *p; + size_t len; + + if ((r = sshpkt_get_string(ssh, &p, &len)) != 0) + return r; + g->value = p; + g->length = len; + return 0; +} + +/* + * Return a list of the gss-group1-sha1 mechanisms supported by this program + * + * We test mechanisms to ensure that we can use them, to avoid starting + * a key exchange with a bad mechanism + */ + +char * +ssh_gssapi_client_mechanisms(const char *host, const char *client, + const char *kex) { + gss_OID_set gss_supported = NULL; + OM_uint32 min_status; + + if (GSS_ERROR(gss_indicate_mechs(&min_status, &gss_supported))) + return NULL; + + return ssh_gssapi_kex_mechs(gss_supported, ssh_gssapi_check_mechanism, + host, client, kex); +} + +char * +ssh_gssapi_kex_mechs(gss_OID_set gss_supported, ssh_gssapi_check_fn *check, + const char *host, const char *client, const char *kex) { + struct sshbuf *buf = NULL; + size_t i; + int r = SSH_ERR_ALLOC_FAIL; + int oidpos, enclen; + char *mechs, *encoded; + u_char digest[SSH_DIGEST_MAX_LENGTH]; + char deroid[2]; + struct ssh_digest_ctx *md = NULL; + char *s, *cp, *p; + + if (gss_enc2oid != NULL) { + for (i = 0; gss_enc2oid[i].encoded != NULL; i++) + free(gss_enc2oid[i].encoded); + free(gss_enc2oid); + } + + gss_enc2oid = xmalloc(sizeof(ssh_gss_kex_mapping) * + (gss_supported->count + 1)); + + if ((buf = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new failed", __func__); + + oidpos = 0; + s = cp = xstrdup(kex); + for (i = 0; i < gss_supported->count; i++) { + if (gss_supported->elements[i].length < 128 && + (*check)(NULL, &(gss_supported->elements[i]), host, client)) { + + deroid[0] = SSH_GSS_OIDTYPE; + deroid[1] = gss_supported->elements[i].length; + + if ((md = ssh_digest_start(SSH_DIGEST_MD5)) == NULL || + (r = ssh_digest_update(md, deroid, 2)) != 0 || + (r = ssh_digest_update(md, + gss_supported->elements[i].elements, + gss_supported->elements[i].length)) != 0 || + (r = ssh_digest_final(md, digest, sizeof(digest))) != 0) + fatal("%s: digest failed: %s", __func__, + ssh_err(r)); + ssh_digest_free(md); + md = NULL; + + encoded = xmalloc(ssh_digest_bytes(SSH_DIGEST_MD5) + * 2); + enclen = __b64_ntop(digest, + ssh_digest_bytes(SSH_DIGEST_MD5), encoded, + ssh_digest_bytes(SSH_DIGEST_MD5) * 2); + + cp = strncpy(s, kex, strlen(kex)); + for ((p = strsep(&cp, ",")); p && *p != '\0'; + (p = strsep(&cp, ","))) { + if (sshbuf_len(buf) != 0 && + (r = sshbuf_put_u8(buf, ',')) != 0) + fatal("%s: sshbuf_put_u8 error: %s", + __func__, ssh_err(r)); + if ((r = sshbuf_put(buf, p, strlen(p))) != 0 || + (r = sshbuf_put(buf, encoded, enclen)) != 0) + fatal("%s: sshbuf_put error: %s", + __func__, ssh_err(r)); + } + + gss_enc2oid[oidpos].oid = &(gss_supported->elements[i]); + gss_enc2oid[oidpos].encoded = encoded; + oidpos++; + } + } + free(s); + gss_enc2oid[oidpos].oid = NULL; + gss_enc2oid[oidpos].encoded = NULL; + + if ((mechs = sshbuf_dup_string(buf)) == NULL) + fatal("%s: sshbuf_dup_string failed", __func__); + + sshbuf_free(buf); + + if (strlen(mechs) == 0) { + free(mechs); + mechs = NULL; + } + + return (mechs); +} + +gss_OID +ssh_gssapi_id_kex(Gssctxt *ctx, char *name, int kex_type) { + int i = 0; + +#define SKIP_KEX_NAME(type) \ + case type: \ + if (strlen(name) < sizeof(type##_ID)) \ + return GSS_C_NO_OID; \ + name += sizeof(type##_ID) - 1; \ + break; + + switch (kex_type) { + SKIP_KEX_NAME(KEX_GSS_GRP1_SHA1) + SKIP_KEX_NAME(KEX_GSS_GRP14_SHA1) + SKIP_KEX_NAME(KEX_GSS_GRP14_SHA256) + SKIP_KEX_NAME(KEX_GSS_GRP16_SHA512) + SKIP_KEX_NAME(KEX_GSS_GEX_SHA1) + SKIP_KEX_NAME(KEX_GSS_NISTP256_SHA256) + SKIP_KEX_NAME(KEX_GSS_C25519_SHA256) + default: + return GSS_C_NO_OID; + } + +#undef SKIP_KEX_NAME + + while (gss_enc2oid[i].encoded != NULL && + strcmp(name, gss_enc2oid[i].encoded) != 0) + i++; + + if (gss_enc2oid[i].oid != NULL && ctx != NULL) + ssh_gssapi_set_oid(ctx, gss_enc2oid[i].oid); + + return gss_enc2oid[i].oid; +} + /* Check that the OID in a data stream matches that in the context */ int ssh_gssapi_check_oid(Gssctxt *ctx, void *data, size_t len) @@ -218,7 +398,7 @@ ssh_gssapi_init_ctx(Gssctxt *ctx, int deleg_creds, gss_buffer_desc *recv_tok, } ctx->major = gss_init_sec_context(&ctx->minor, - GSS_C_NO_CREDENTIAL, &ctx->context, ctx->name, ctx->oid, + ctx->client_creds, &ctx->context, ctx->name, ctx->oid, GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG | deleg_flag, 0, NULL, recv_tok, NULL, send_tok, flags, NULL); @@ -247,9 +427,43 @@ ssh_gssapi_import_name(Gssctxt *ctx, const char *host) return (ctx->major); } +OM_uint32 +ssh_gssapi_client_identity(Gssctxt *ctx, const char *name) +{ + gss_buffer_desc gssbuf; + gss_name_t gssname; + OM_uint32 status; + gss_OID_set oidset; + + gssbuf.value = (void *) name; + gssbuf.length = strlen(gssbuf.value); + + gss_create_empty_oid_set(&status, &oidset); + gss_add_oid_set_member(&status, ctx->oid, &oidset); + + ctx->major = gss_import_name(&ctx->minor, &gssbuf, + GSS_C_NT_USER_NAME, &gssname); + + if (!ctx->major) + ctx->major = gss_acquire_cred(&ctx->minor, + gssname, 0, oidset, GSS_C_INITIATE, + &ctx->client_creds, NULL, NULL); + + gss_release_name(&status, &gssname); + gss_release_oid_set(&status, &oidset); + + if (ctx->major) + ssh_gssapi_error(ctx); + + return(ctx->major); +} + OM_uint32 ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash) { + if (ctx == NULL) + return -1; + if ((ctx->major = gss_get_mic(&ctx->minor, ctx->context, GSS_C_QOP_DEFAULT, buffer, hash))) ssh_gssapi_error(ctx); @@ -257,6 +471,19 @@ ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash) return (ctx->major); } +/* Priviledged when used by server */ +OM_uint32 +ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) +{ + if (ctx == NULL) + return -1; + + ctx->major = gss_verify_mic(&ctx->minor, ctx->context, + gssbuf, gssmic, NULL); + + return (ctx->major); +} + void ssh_gssapi_buildmic(struct sshbuf *b, const char *user, const char *service, const char *context) @@ -273,11 +500,16 @@ ssh_gssapi_buildmic(struct sshbuf *b, const char *user, const char *service, } int -ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) +ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host, + const char *client) { gss_buffer_desc token = GSS_C_EMPTY_BUFFER; OM_uint32 major, minor; gss_OID_desc spnego_oid = {6, (void *)"\x2B\x06\x01\x05\x05\x02"}; + Gssctxt *intctx = NULL; + + if (ctx == NULL) + ctx = &intctx; /* RFC 4462 says we MUST NOT do SPNEGO */ if (oid->length == spnego_oid.length && @@ -287,6 +519,10 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) ssh_gssapi_build_ctx(ctx); ssh_gssapi_set_oid(*ctx, oid); major = ssh_gssapi_import_name(*ctx, host); + + if (!GSS_ERROR(major) && client) + major = ssh_gssapi_client_identity(*ctx, client); + if (!GSS_ERROR(major)) { major = ssh_gssapi_init_ctx(*ctx, 0, GSS_C_NO_BUFFER, &token, NULL); @@ -296,10 +532,66 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) GSS_C_NO_BUFFER); } - if (GSS_ERROR(major)) + if (GSS_ERROR(major) || intctx != NULL) ssh_gssapi_delete_ctx(ctx); return (!GSS_ERROR(major)); } +int +ssh_gssapi_credentials_updated(Gssctxt *ctxt) { + static gss_name_t saved_name = GSS_C_NO_NAME; + static OM_uint32 saved_lifetime = 0; + static gss_OID saved_mech = GSS_C_NO_OID; + static gss_name_t name; + static OM_uint32 last_call = 0; + OM_uint32 lifetime, now, major, minor; + int equal; + + now = time(NULL); + + if (ctxt) { + debug("Rekey has happened - updating saved versions"); + + if (saved_name != GSS_C_NO_NAME) + gss_release_name(&minor, &saved_name); + + major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL, + &saved_name, &saved_lifetime, NULL, NULL); + + if (!GSS_ERROR(major)) { + saved_mech = ctxt->oid; + saved_lifetime+= now; + } else { + /* Handle the error */ + } + return 0; + } + + if (now - last_call < 10) + return 0; + + last_call = now; + + if (saved_mech == GSS_C_NO_OID) + return 0; + + major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL, + &name, &lifetime, NULL, NULL); + if (major == GSS_S_CREDENTIALS_EXPIRED) + return 0; + else if (GSS_ERROR(major)) + return 0; + + major = gss_compare_name(&minor, saved_name, name, &equal); + gss_release_name(&minor, &name); + if (GSS_ERROR(major)) + return 0; + + if (equal && (saved_lifetime < lifetime + now - 10)) + return 1; + + return 0; +} + #endif /* GSSAPI */ diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c index a151bc1e4..ef9beb67c 100644 --- a/gss-serv-krb5.c +++ b/gss-serv-krb5.c @@ -1,7 +1,7 @@ /* $OpenBSD: gss-serv-krb5.c,v 1.9 2018/07/09 21:37:55 markus Exp $ */ /* - * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. + * Copyright (c) 2001-2007 Simon Wilkinson. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -120,8 +120,8 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client) krb5_error_code problem; krb5_principal princ; OM_uint32 maj_status, min_status; - int len; const char *errmsg; + const char *new_ccname; if (client->creds == NULL) { debug("No credentials stored"); @@ -180,11 +180,16 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client) return; } - client->store.filename = xstrdup(krb5_cc_get_name(krb_context, ccache)); + new_ccname = krb5_cc_get_name(krb_context, ccache); + client->store.envvar = "KRB5CCNAME"; - len = strlen(client->store.filename) + 6; - client->store.envval = xmalloc(len); - snprintf(client->store.envval, len, "FILE:%s", client->store.filename); +#ifdef USE_CCAPI + xasprintf(&client->store.envval, "API:%s", new_ccname); + client->store.filename = NULL; +#else + xasprintf(&client->store.envval, "FILE:%s", new_ccname); + client->store.filename = xstrdup(new_ccname); +#endif #ifdef USE_PAM if (options.use_pam) @@ -196,6 +201,71 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client) return; } +int +ssh_gssapi_krb5_updatecreds(ssh_gssapi_ccache *store, + ssh_gssapi_client *client) +{ + krb5_ccache ccache = NULL; + krb5_principal principal = NULL; + char *name = NULL; + krb5_error_code problem; + OM_uint32 maj_status, min_status; + + if ((problem = krb5_cc_resolve(krb_context, store->envval, &ccache))) { + logit("krb5_cc_resolve(): %.100s", + krb5_get_err_text(krb_context, problem)); + return 0; + } + + /* Find out who the principal in this cache is */ + if ((problem = krb5_cc_get_principal(krb_context, ccache, + &principal))) { + logit("krb5_cc_get_principal(): %.100s", + krb5_get_err_text(krb_context, problem)); + krb5_cc_close(krb_context, ccache); + return 0; + } + + if ((problem = krb5_unparse_name(krb_context, principal, &name))) { + logit("krb5_unparse_name(): %.100s", + krb5_get_err_text(krb_context, problem)); + krb5_free_principal(krb_context, principal); + krb5_cc_close(krb_context, ccache); + return 0; + } + + + if (strcmp(name,client->exportedname.value)!=0) { + debug("Name in local credentials cache differs. Not storing"); + krb5_free_principal(krb_context, principal); + krb5_cc_close(krb_context, ccache); + krb5_free_unparsed_name(krb_context, name); + return 0; + } + krb5_free_unparsed_name(krb_context, name); + + /* Name matches, so lets get on with it! */ + + if ((problem = krb5_cc_initialize(krb_context, ccache, principal))) { + logit("krb5_cc_initialize(): %.100s", + krb5_get_err_text(krb_context, problem)); + krb5_free_principal(krb_context, principal); + krb5_cc_close(krb_context, ccache); + return 0; + } + + krb5_free_principal(krb_context, principal); + + if ((maj_status = gss_krb5_copy_ccache(&min_status, client->creds, + ccache))) { + logit("gss_krb5_copy_ccache() failed. Sorry!"); + krb5_cc_close(krb_context, ccache); + return 0; + } + + return 1; +} + ssh_gssapi_mech gssapi_kerberos_mech = { "toWM5Slw5Ew8Mqkay+al2g==", "Kerberos", @@ -203,7 +273,8 @@ ssh_gssapi_mech gssapi_kerberos_mech = { NULL, &ssh_gssapi_krb5_userok, NULL, - &ssh_gssapi_krb5_storecreds + &ssh_gssapi_krb5_storecreds, + &ssh_gssapi_krb5_updatecreds }; #endif /* KRB5 */ diff --git a/gss-serv.c b/gss-serv.c index ab3a15f0f..1d47870e7 100644 --- a/gss-serv.c +++ b/gss-serv.c @@ -1,7 +1,7 @@ /* $OpenBSD: gss-serv.c,v 1.31 2018/07/09 21:37:55 markus Exp $ */ /* - * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. + * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -44,17 +44,19 @@ #include "session.h" #include "misc.h" #include "servconf.h" +#include "uidswap.h" #include "ssh-gss.h" +#include "monitor_wrap.h" extern ServerOptions options; static ssh_gssapi_client gssapi_client = - { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, - GSS_C_NO_CREDENTIAL, NULL, {NULL, NULL, NULL, NULL}}; + { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL, + GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0}; ssh_gssapi_mech gssapi_null_mech = - { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL}; + { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL}; #ifdef KRB5 extern ssh_gssapi_mech gssapi_kerberos_mech; @@ -140,6 +142,29 @@ ssh_gssapi_server_ctx(Gssctxt **ctx, gss_OID oid) return (ssh_gssapi_acquire_cred(*ctx)); } +/* Unprivileged */ +char * +ssh_gssapi_server_mechanisms(void) { + if (supported_oids == NULL) + ssh_gssapi_prepare_supported_oids(); + return (ssh_gssapi_kex_mechs(supported_oids, + &ssh_gssapi_server_check_mech, NULL, NULL, + options.gss_kex_algorithms)); +} + +/* Unprivileged */ +int +ssh_gssapi_server_check_mech(Gssctxt **dum, gss_OID oid, const char *data, + const char *dummy) { + Gssctxt *ctx = NULL; + int res; + + res = !GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctx, oid))); + ssh_gssapi_delete_ctx(&ctx); + + return (res); +} + /* Unprivileged */ void ssh_gssapi_supported_oids(gss_OID_set *oidset) @@ -150,7 +175,9 @@ ssh_gssapi_supported_oids(gss_OID_set *oidset) gss_OID_set supported; gss_create_empty_oid_set(&min_status, oidset); - gss_indicate_mechs(&min_status, &supported); + + if (GSS_ERROR(gss_indicate_mechs(&min_status, &supported))) + return; while (supported_mechs[i]->name != NULL) { if (GSS_ERROR(gss_test_oid_set_member(&min_status, @@ -276,8 +303,48 @@ OM_uint32 ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client) { int i = 0; + int equal = 0; + gss_name_t new_name = GSS_C_NO_NAME; + gss_buffer_desc ename = GSS_C_EMPTY_BUFFER; + + if (options.gss_store_rekey && client->used && ctx->client_creds) { + if (client->mech->oid.length != ctx->oid->length || + (memcmp(client->mech->oid.elements, + ctx->oid->elements, ctx->oid->length) !=0)) { + debug("Rekeyed credentials have different mechanism"); + return GSS_S_COMPLETE; + } + + if ((ctx->major = gss_inquire_cred_by_mech(&ctx->minor, + ctx->client_creds, ctx->oid, &new_name, + NULL, NULL, NULL))) { + ssh_gssapi_error(ctx); + return (ctx->major); + } + + ctx->major = gss_compare_name(&ctx->minor, client->name, + new_name, &equal); + + if (GSS_ERROR(ctx->major)) { + ssh_gssapi_error(ctx); + return (ctx->major); + } + + if (!equal) { + debug("Rekeyed credentials have different name"); + return GSS_S_COMPLETE; + } - gss_buffer_desc ename; + debug("Marking rekeyed credentials for export"); + + gss_release_name(&ctx->minor, &client->name); + gss_release_cred(&ctx->minor, &client->creds); + client->name = new_name; + client->creds = ctx->client_creds; + ctx->client_creds = GSS_C_NO_CREDENTIAL; + client->updated = 1; + return GSS_S_COMPLETE; + } client->mech = NULL; @@ -292,6 +359,13 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client) if (client->mech == NULL) return GSS_S_FAILURE; + if (ctx->client_creds && + (ctx->major = gss_inquire_cred_by_mech(&ctx->minor, + ctx->client_creds, ctx->oid, &client->name, NULL, NULL, NULL))) { + ssh_gssapi_error(ctx); + return (ctx->major); + } + if ((ctx->major = gss_display_name(&ctx->minor, ctx->client, &client->displayname, NULL))) { ssh_gssapi_error(ctx); @@ -309,6 +383,8 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client) return (ctx->major); } + gss_release_buffer(&ctx->minor, &ename); + /* We can't copy this structure, so we just move the pointer to it */ client->creds = ctx->client_creds; ctx->client_creds = GSS_C_NO_CREDENTIAL; @@ -356,19 +432,23 @@ ssh_gssapi_do_child(char ***envp, u_int *envsizep) /* Privileged */ int -ssh_gssapi_userok(char *user) +ssh_gssapi_userok(char *user, struct passwd *pw, int kex) { OM_uint32 lmin; + (void) kex; /* used in privilege separation */ + if (gssapi_client.exportedname.length == 0 || gssapi_client.exportedname.value == NULL) { debug("No suitable client data"); return 0; } if (gssapi_client.mech && gssapi_client.mech->userok) - if ((*gssapi_client.mech->userok)(&gssapi_client, user)) + if ((*gssapi_client.mech->userok)(&gssapi_client, user)) { + gssapi_client.used = 1; + gssapi_client.store.owner = pw; return 1; - else { + } else { /* Destroy delegated credentials if userok fails */ gss_release_buffer(&lmin, &gssapi_client.displayname); gss_release_buffer(&lmin, &gssapi_client.exportedname); @@ -382,14 +462,90 @@ ssh_gssapi_userok(char *user) return (0); } -/* Privileged */ -OM_uint32 -ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) +/* These bits are only used for rekeying. The unpriviledged child is running + * as the user, the monitor is root. + * + * In the child, we want to : + * *) Ask the monitor to store our credentials into the store we specify + * *) If it succeeds, maybe do a PAM update + */ + +/* Stuff for PAM */ + +#ifdef USE_PAM +static int ssh_gssapi_simple_conv(int n, const struct pam_message **msg, + struct pam_response **resp, void *data) { - ctx->major = gss_verify_mic(&ctx->minor, ctx->context, - gssbuf, gssmic, NULL); + return (PAM_CONV_ERR); +} +#endif - return (ctx->major); +void +ssh_gssapi_rekey_creds(void) { + int ok; +#ifdef USE_PAM + int ret; + pam_handle_t *pamh = NULL; + struct pam_conv pamconv = {ssh_gssapi_simple_conv, NULL}; + char *envstr; +#endif + + if (gssapi_client.store.filename == NULL && + gssapi_client.store.envval == NULL && + gssapi_client.store.envvar == NULL) + return; + + ok = PRIVSEP(ssh_gssapi_update_creds(&gssapi_client.store)); + + if (!ok) + return; + + debug("Rekeyed credentials stored successfully"); + + /* Actually managing to play with the ssh pam stack from here will + * be next to impossible. In any case, we may want different options + * for rekeying. So, use our own :) + */ +#ifdef USE_PAM + if (!use_privsep) { + debug("Not even going to try and do PAM with privsep disabled"); + return; + } + + ret = pam_start("sshd-rekey", gssapi_client.store.owner->pw_name, + &pamconv, &pamh); + if (ret) + return; + + xasprintf(&envstr, "%s=%s", gssapi_client.store.envvar, + gssapi_client.store.envval); + + ret = pam_putenv(pamh, envstr); + if (!ret) + pam_setcred(pamh, PAM_REINITIALIZE_CRED); + pam_end(pamh, PAM_SUCCESS); +#endif +} + +int +ssh_gssapi_update_creds(ssh_gssapi_ccache *store) { + int ok = 0; + + /* Check we've got credentials to store */ + if (!gssapi_client.updated) + return 0; + + gssapi_client.updated = 0; + + temporarily_use_uid(gssapi_client.store.owner); + if (gssapi_client.mech && gssapi_client.mech->updatecreds) + ok = (*gssapi_client.mech->updatecreds)(store, &gssapi_client); + else + debug("No update function for this mechanism"); + + restore_uid(); + + return ok; } /* Privileged */ diff --git a/hmac.c b/hmac.c index 1c879640c..a29f32c5c 100644 --- a/hmac.c +++ b/hmac.c @@ -19,6 +19,7 @@ #include #include +#include #include "sshbuf.h" #include "digest.h" diff --git a/kex.c b/kex.c index 34808b5c3..a2a4794e8 100644 --- a/kex.c +++ b/kex.c @@ -55,11 +55,16 @@ #include "misc.h" #include "dispatch.h" #include "monitor.h" +#include "xmalloc.h" #include "ssherr.h" #include "sshbuf.h" #include "digest.h" +#ifdef GSSAPI +#include "ssh-gss.h" +#endif + /* prototype */ static int kex_choose_conf(struct ssh *); static int kex_input_newkeys(int, u_int32_t, struct ssh *); @@ -113,15 +118,28 @@ static const struct kexalg kexalgs[] = { #endif /* HAVE_EVP_SHA256 || !WITH_OPENSSL */ { NULL, -1, -1, -1}, }; +static const struct kexalg gss_kexalgs[] = { +#ifdef GSSAPI + { KEX_GSS_GEX_SHA1_ID, KEX_GSS_GEX_SHA1, 0, SSH_DIGEST_SHA1 }, + { KEX_GSS_GRP1_SHA1_ID, KEX_GSS_GRP1_SHA1, 0, SSH_DIGEST_SHA1 }, + { KEX_GSS_GRP14_SHA1_ID, KEX_GSS_GRP14_SHA1, 0, SSH_DIGEST_SHA1 }, + { KEX_GSS_GRP14_SHA256_ID, KEX_GSS_GRP14_SHA256, 0, SSH_DIGEST_SHA256 }, + { KEX_GSS_GRP16_SHA512_ID, KEX_GSS_GRP16_SHA512, 0, SSH_DIGEST_SHA512 }, + { KEX_GSS_NISTP256_SHA256_ID, KEX_GSS_NISTP256_SHA256, + NID_X9_62_prime256v1, SSH_DIGEST_SHA256 }, + { KEX_GSS_C25519_SHA256_ID, KEX_GSS_C25519_SHA256, 0, SSH_DIGEST_SHA256 }, +#endif + { NULL, -1, -1, -1 }, +}; -char * -kex_alg_list(char sep) +static char * +kex_alg_list_internal(char sep, const struct kexalg *algs) { char *ret = NULL, *tmp; size_t nlen, rlen = 0; const struct kexalg *k; - for (k = kexalgs; k->name != NULL; k++) { + for (k = algs; k->name != NULL; k++) { if (ret != NULL) ret[rlen++] = sep; nlen = strlen(k->name); @@ -136,6 +154,18 @@ kex_alg_list(char sep) return ret; } +char * +kex_alg_list(char sep) +{ + return kex_alg_list_internal(sep, kexalgs); +} + +char * +kex_gss_alg_list(char sep) +{ + return kex_alg_list_internal(sep, gss_kexalgs); +} + static const struct kexalg * kex_alg_by_name(const char *name) { @@ -145,6 +175,10 @@ kex_alg_by_name(const char *name) if (strcmp(k->name, name) == 0) return k; } + for (k = gss_kexalgs; k->name != NULL; k++) { + if (strncmp(k->name, name, strlen(k->name)) == 0) + return k; + } return NULL; } @@ -301,6 +335,29 @@ kex_assemble_names(char **listp, const char *def, const char *all) return r; } +/* Validate GSS KEX method name list */ +int +kex_gss_names_valid(const char *names) +{ + char *s, *cp, *p; + + if (names == NULL || *names == '\0') + return 0; + s = cp = xstrdup(names); + for ((p = strsep(&cp, ",")); p && *p != '\0'; + (p = strsep(&cp, ","))) { + if (strncmp(p, "gss-", 4) != 0 + || kex_alg_by_name(p) == NULL) { + error("Unsupported KEX algorithm \"%.100s\"", p); + free(s); + return 0; + } + } + debug3("gss kex names ok: [%s]", names); + free(s); + return 1; +} + /* put algorithm proposal into buffer */ int kex_prop2buf(struct sshbuf *b, char *proposal[PROPOSAL_MAX]) @@ -657,6 +714,9 @@ kex_free(struct kex *kex) sshbuf_free(kex->server_version); sshbuf_free(kex->client_pub); free(kex->session_id); +#ifdef GSSAPI + free(kex->gss_host); +#endif /* GSSAPI */ free(kex->failed_choice); free(kex->hostkey_alg); free(kex->name); diff --git a/kex.h b/kex.h index 6d446d1cc..2d5f1d4ed 100644 --- a/kex.h +++ b/kex.h @@ -103,6 +103,15 @@ enum kex_exchange { KEX_ECDH_SHA2, KEX_C25519_SHA256, KEX_KEM_SNTRUP4591761X25519_SHA512, +#ifdef GSSAPI + KEX_GSS_GRP1_SHA1, + KEX_GSS_GRP14_SHA1, + KEX_GSS_GRP14_SHA256, + KEX_GSS_GRP16_SHA512, + KEX_GSS_GEX_SHA1, + KEX_GSS_NISTP256_SHA256, + KEX_GSS_C25519_SHA256, +#endif KEX_MAX }; @@ -154,6 +163,12 @@ struct kex { u_int flags; int hash_alg; int ec_nid; +#ifdef GSSAPI + int gss_deleg_creds; + int gss_trust_dns; + char *gss_host; + char *gss_client; +#endif char *failed_choice; int (*verify_host_key)(struct sshkey *, struct ssh *); struct sshkey *(*load_host_public_key)(int, int, struct ssh *); @@ -175,8 +190,10 @@ struct kex { int kex_names_valid(const char *); char *kex_alg_list(char); +char *kex_gss_alg_list(char); char *kex_names_cat(const char *, const char *); int kex_assemble_names(char **, const char *, const char *); +int kex_gss_names_valid(const char *); int kex_exchange_identification(struct ssh *, int, const char *); @@ -203,6 +220,12 @@ int kexgex_client(struct ssh *); int kexgex_server(struct ssh *); int kex_gen_client(struct ssh *); int kex_gen_server(struct ssh *); +#if defined(GSSAPI) && defined(WITH_OPENSSL) +int kexgssgex_client(struct ssh *); +int kexgssgex_server(struct ssh *); +int kexgss_client(struct ssh *); +int kexgss_server(struct ssh *); +#endif int kex_dh_keypair(struct kex *); int kex_dh_enc(struct kex *, const struct sshbuf *, struct sshbuf **, @@ -235,6 +258,12 @@ int kexgex_hash(int, const struct sshbuf *, const struct sshbuf *, const BIGNUM *, const u_char *, size_t, u_char *, size_t *); +int kex_gen_hash(int hash_alg, const struct sshbuf *client_version, + const struct sshbuf *server_version, const struct sshbuf *client_kexinit, + const struct sshbuf *server_kexinit, const struct sshbuf *server_host_key_blob, + const struct sshbuf *client_pub, const struct sshbuf *server_pub, + const struct sshbuf *shared_secret, u_char *hash, size_t *hashlen); + void kexc25519_keygen(u_char key[CURVE25519_SIZE], u_char pub[CURVE25519_SIZE]) __attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE))) __attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE))); diff --git a/kexdh.c b/kexdh.c index 67133e339..edaa46762 100644 --- a/kexdh.c +++ b/kexdh.c @@ -48,13 +48,23 @@ kex_dh_keygen(struct kex *kex) { switch (kex->kex_type) { case KEX_DH_GRP1_SHA1: +#ifdef GSSAPI + case KEX_GSS_GRP1_SHA1: +#endif kex->dh = dh_new_group1(); break; case KEX_DH_GRP14_SHA1: case KEX_DH_GRP14_SHA256: +#ifdef GSSAPI + case KEX_GSS_GRP14_SHA1: + case KEX_GSS_GRP14_SHA256: +#endif kex->dh = dh_new_group14(); break; case KEX_DH_GRP16_SHA512: +#ifdef GSSAPI + case KEX_GSS_GRP16_SHA512: +#endif kex->dh = dh_new_group16(); break; case KEX_DH_GRP18_SHA512: diff --git a/kexgen.c b/kexgen.c index 2abbb9ef6..569dc83f3 100644 --- a/kexgen.c +++ b/kexgen.c @@ -43,7 +43,7 @@ static int input_kex_gen_init(int, u_int32_t, struct ssh *); static int input_kex_gen_reply(int type, u_int32_t seq, struct ssh *ssh); -static int +int kex_gen_hash( int hash_alg, const struct sshbuf *client_version, diff --git a/kexgssc.c b/kexgssc.c new file mode 100644 index 000000000..f6e1405eb --- /dev/null +++ b/kexgssc.c @@ -0,0 +1,606 @@ +/* + * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "includes.h" + +#if defined(GSSAPI) && defined(WITH_OPENSSL) + +#include "includes.h" + +#include +#include + +#include + +#include "xmalloc.h" +#include "sshbuf.h" +#include "ssh2.h" +#include "sshkey.h" +#include "cipher.h" +#include "kex.h" +#include "log.h" +#include "packet.h" +#include "dh.h" +#include "digest.h" +#include "ssherr.h" + +#include "ssh-gss.h" + +int +kexgss_client(struct ssh *ssh) +{ + struct kex *kex = ssh->kex; + gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER, + recv_tok = GSS_C_EMPTY_BUFFER, + gssbuf, msg_tok = GSS_C_EMPTY_BUFFER, *token_ptr; + Gssctxt *ctxt; + OM_uint32 maj_status, min_status, ret_flags; + struct sshbuf *server_blob = NULL; + struct sshbuf *shared_secret = NULL; + struct sshbuf *server_host_key_blob = NULL; + struct sshbuf *empty = NULL; + u_char *msg; + int type = 0; + int first = 1; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t hashlen; + u_char c; + int r; + + /* Initialise our GSSAPI world */ + ssh_gssapi_build_ctx(&ctxt); + if (ssh_gssapi_id_kex(ctxt, kex->name, kex->kex_type) + == GSS_C_NO_OID) + fatal("Couldn't identify host exchange"); + + if (ssh_gssapi_import_name(ctxt, kex->gss_host)) + fatal("Couldn't import hostname"); + + if (kex->gss_client && + ssh_gssapi_client_identity(ctxt, kex->gss_client)) + fatal("Couldn't acquire client credentials"); + + /* Step 1 */ + switch (kex->kex_type) { + case KEX_GSS_GRP1_SHA1: + case KEX_GSS_GRP14_SHA1: + case KEX_GSS_GRP14_SHA256: + case KEX_GSS_GRP16_SHA512: + r = kex_dh_keypair(kex); + break; + case KEX_GSS_NISTP256_SHA256: + r = kex_ecdh_keypair(kex); + break; + case KEX_GSS_C25519_SHA256: + r = kex_c25519_keypair(kex); + break; + default: + fatal("%s: Unexpected KEX type %d", __func__, kex->kex_type); + } + if (r != 0) + return r; + + token_ptr = GSS_C_NO_BUFFER; + + do { + debug("Calling gss_init_sec_context"); + + maj_status = ssh_gssapi_init_ctx(ctxt, + kex->gss_deleg_creds, token_ptr, &send_tok, + &ret_flags); + + if (GSS_ERROR(maj_status)) { + /* XXX Useles code: Missing send? */ + if (send_tok.length != 0) { + if ((r = sshpkt_start(ssh, + SSH2_MSG_KEXGSS_CONTINUE)) != 0 || + (r = sshpkt_put_string(ssh, send_tok.value, + send_tok.length)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + } + fatal("gss_init_context failed"); + } + + /* If we've got an old receive buffer get rid of it */ + if (token_ptr != GSS_C_NO_BUFFER) + gss_release_buffer(&min_status, &recv_tok); + + if (maj_status == GSS_S_COMPLETE) { + /* If mutual state flag is not true, kex fails */ + if (!(ret_flags & GSS_C_MUTUAL_FLAG)) + fatal("Mutual authentication failed"); + + /* If integ avail flag is not true kex fails */ + if (!(ret_flags & GSS_C_INTEG_FLAG)) + fatal("Integrity check failed"); + } + + /* + * If we have data to send, then the last message that we + * received cannot have been a 'complete'. + */ + if (send_tok.length != 0) { + if (first) { + if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_INIT)) != 0 || + (r = sshpkt_put_string(ssh, send_tok.value, + send_tok.length)) != 0 || + (r = sshpkt_put_stringb(ssh, kex->client_pub)) != 0) + fatal("failed to construct packet: %s", ssh_err(r)); + first = 0; + } else { + if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || + (r = sshpkt_put_string(ssh, send_tok.value, + send_tok.length)) != 0) + fatal("failed to construct packet: %s", ssh_err(r)); + } + if ((r = sshpkt_send(ssh)) != 0) + fatal("failed to send packet: %s", ssh_err(r)); + gss_release_buffer(&min_status, &send_tok); + + /* If we've sent them data, they should reply */ + do { + type = ssh_packet_read(ssh); + if (type == SSH2_MSG_KEXGSS_HOSTKEY) { + debug("Received KEXGSS_HOSTKEY"); + if (server_host_key_blob) + fatal("Server host key received more than once"); + if ((r = sshpkt_getb_froms(ssh, &server_host_key_blob)) != 0) + fatal("Failed to read server host key: %s", ssh_err(r)); + } + } while (type == SSH2_MSG_KEXGSS_HOSTKEY); + + switch (type) { + case SSH2_MSG_KEXGSS_CONTINUE: + debug("Received GSSAPI_CONTINUE"); + if (maj_status == GSS_S_COMPLETE) + fatal("GSSAPI Continue received from server when complete"); + if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, + &recv_tok)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + fatal("Failed to read token: %s", ssh_err(r)); + break; + case SSH2_MSG_KEXGSS_COMPLETE: + debug("Received GSSAPI_COMPLETE"); + if (msg_tok.value != NULL) + fatal("Received GSSAPI_COMPLETE twice?"); + if ((r = sshpkt_getb_froms(ssh, &server_blob)) != 0 || + (r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, + &msg_tok)) != 0) + fatal("Failed to read message: %s", ssh_err(r)); + + /* Is there a token included? */ + if ((r = sshpkt_get_u8(ssh, &c)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + if (c) { + if ((r = ssh_gssapi_sshpkt_get_buffer_desc( + ssh, &recv_tok)) != 0) + fatal("Failed to read token: %s", ssh_err(r)); + /* If we're already complete - protocol error */ + if (maj_status == GSS_S_COMPLETE) + sshpkt_disconnect(ssh, "Protocol error: received token when complete"); + } else { + /* No token included */ + if (maj_status != GSS_S_COMPLETE) + sshpkt_disconnect(ssh, "Protocol error: did not receive final token"); + } + if ((r = sshpkt_get_end(ssh)) != 0) { + fatal("Expecting end of packet."); + } + break; + case SSH2_MSG_KEXGSS_ERROR: + debug("Received Error"); + if ((r = sshpkt_get_u32(ssh, &maj_status)) != 0 || + (r = sshpkt_get_u32(ssh, &min_status)) != 0 || + (r = sshpkt_get_string(ssh, &msg, NULL)) != 0 || + (r = sshpkt_get_string(ssh, NULL, NULL)) != 0 || /* lang tag */ + (r = sshpkt_get_end(ssh)) != 0) + fatal("sshpkt_get failed: %s", ssh_err(r)); + fatal("GSSAPI Error: \n%.400s", msg); + default: + sshpkt_disconnect(ssh, "Protocol error: didn't expect packet type %d", + type); + } + token_ptr = &recv_tok; + } else { + /* No data, and not complete */ + if (maj_status != GSS_S_COMPLETE) + fatal("Not complete, and no token output"); + } + } while (maj_status & GSS_S_CONTINUE_NEEDED); + + /* + * We _must_ have received a COMPLETE message in reply from the + * server, which will have set server_blob and msg_tok + */ + + if (type != SSH2_MSG_KEXGSS_COMPLETE) + fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it"); + + /* compute shared secret */ + switch (kex->kex_type) { + case KEX_GSS_GRP1_SHA1: + case KEX_GSS_GRP14_SHA1: + case KEX_GSS_GRP14_SHA256: + case KEX_GSS_GRP16_SHA512: + r = kex_dh_dec(kex, server_blob, &shared_secret); + break; + case KEX_GSS_C25519_SHA256: + if (sshbuf_ptr(server_blob)[sshbuf_len(server_blob)] & 0x80) + fatal("The received key has MSB of last octet set!"); + r = kex_c25519_dec(kex, server_blob, &shared_secret); + break; + case KEX_GSS_NISTP256_SHA256: + if (sshbuf_len(server_blob) != 65) + fatal("The received NIST-P256 key did not match" + "expected length (expected 65, got %zu)", sshbuf_len(server_blob)); + + if (sshbuf_ptr(server_blob)[0] != POINT_CONVERSION_UNCOMPRESSED) + fatal("The received NIST-P256 key does not have first octet 0x04"); + + r = kex_ecdh_dec(kex, server_blob, &shared_secret); + break; + default: + r = SSH_ERR_INVALID_ARGUMENT; + break; + } + if (r != 0) + goto out; + + if ((empty = sshbuf_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + + hashlen = sizeof(hash); + if ((r = kex_gen_hash( + kex->hash_alg, + kex->client_version, + kex->server_version, + kex->my, + kex->peer, + (server_host_key_blob ? server_host_key_blob : empty), + kex->client_pub, + server_blob, + shared_secret, + hash, &hashlen)) != 0) + fatal("%s: Unexpected KEX type %d", __func__, kex->kex_type); + + gssbuf.value = hash; + gssbuf.length = hashlen; + + /* Verify that the hash matches the MIC we just got. */ + if (GSS_ERROR(ssh_gssapi_checkmic(ctxt, &gssbuf, &msg_tok))) + sshpkt_disconnect(ssh, "Hash's MIC didn't verify"); + + gss_release_buffer(&min_status, &msg_tok); + + if (kex->gss_deleg_creds) + ssh_gssapi_credentials_updated(ctxt); + + if (gss_kex_context == NULL) + gss_kex_context = ctxt; + else + ssh_gssapi_delete_ctx(&ctxt); + + if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) + r = kex_send_newkeys(ssh); + +out: + explicit_bzero(hash, sizeof(hash)); + explicit_bzero(kex->c25519_client_key, sizeof(kex->c25519_client_key)); + sshbuf_free(empty); + sshbuf_free(server_host_key_blob); + sshbuf_free(server_blob); + sshbuf_free(shared_secret); + sshbuf_free(kex->client_pub); + kex->client_pub = NULL; + return r; +} + +int +kexgssgex_client(struct ssh *ssh) +{ + struct kex *kex = ssh->kex; + gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER, + recv_tok = GSS_C_EMPTY_BUFFER, gssbuf, + msg_tok = GSS_C_EMPTY_BUFFER, *token_ptr; + Gssctxt *ctxt; + OM_uint32 maj_status, min_status, ret_flags; + struct sshbuf *shared_secret = NULL; + BIGNUM *p = NULL; + BIGNUM *g = NULL; + struct sshbuf *buf = NULL; + struct sshbuf *server_host_key_blob = NULL; + struct sshbuf *server_blob = NULL; + BIGNUM *dh_server_pub = NULL; + u_char *msg; + int type = 0; + int first = 1; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t hashlen; + const BIGNUM *pub_key, *dh_p, *dh_g; + int nbits = 0, min = DH_GRP_MIN, max = DH_GRP_MAX; + struct sshbuf *empty = NULL; + u_char c; + int r; + + /* Initialise our GSSAPI world */ + ssh_gssapi_build_ctx(&ctxt); + if (ssh_gssapi_id_kex(ctxt, kex->name, kex->kex_type) + == GSS_C_NO_OID) + fatal("Couldn't identify host exchange"); + + if (ssh_gssapi_import_name(ctxt, kex->gss_host)) + fatal("Couldn't import hostname"); + + if (kex->gss_client && + ssh_gssapi_client_identity(ctxt, kex->gss_client)) + fatal("Couldn't acquire client credentials"); + + debug("Doing group exchange"); + nbits = dh_estimate(kex->dh_need * 8); + + kex->min = DH_GRP_MIN; + kex->max = DH_GRP_MAX; + kex->nbits = nbits; + if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUPREQ)) != 0 || + (r = sshpkt_put_u32(ssh, min)) != 0 || + (r = sshpkt_put_u32(ssh, nbits)) != 0 || + (r = sshpkt_put_u32(ssh, max)) != 0 || + (r = sshpkt_send(ssh)) != 0) + fatal("Failed to construct a packet: %s", ssh_err(r)); + + if ((r = ssh_packet_read_expect(ssh, SSH2_MSG_KEXGSS_GROUP)) != 0) + fatal("Error: %s", ssh_err(r)); + + if ((r = sshpkt_get_bignum2(ssh, &p)) != 0 || + (r = sshpkt_get_bignum2(ssh, &g)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + fatal("shpkt_get_bignum2 failed: %s", ssh_err(r)); + + if (BN_num_bits(p) < min || BN_num_bits(p) > max) + fatal("GSSGRP_GEX group out of range: %d !< %d !< %d", + min, BN_num_bits(p), max); + + if ((kex->dh = dh_new_group(g, p)) == NULL) + fatal("dn_new_group() failed"); + p = g = NULL; /* belong to kex->dh now */ + + if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0) + goto out; + DH_get0_key(kex->dh, &pub_key, NULL); + + token_ptr = GSS_C_NO_BUFFER; + + do { + /* Step 2 - call GSS_Init_sec_context() */ + debug("Calling gss_init_sec_context"); + + maj_status = ssh_gssapi_init_ctx(ctxt, + kex->gss_deleg_creds, token_ptr, &send_tok, + &ret_flags); + + if (GSS_ERROR(maj_status)) { + /* XXX Useles code: Missing send? */ + if (send_tok.length != 0) { + if ((r = sshpkt_start(ssh, + SSH2_MSG_KEXGSS_CONTINUE)) != 0 || + (r = sshpkt_put_string(ssh, send_tok.value, + send_tok.length)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + } + fatal("gss_init_context failed"); + } + + /* If we've got an old receive buffer get rid of it */ + if (token_ptr != GSS_C_NO_BUFFER) + gss_release_buffer(&min_status, &recv_tok); + + if (maj_status == GSS_S_COMPLETE) { + /* If mutual state flag is not true, kex fails */ + if (!(ret_flags & GSS_C_MUTUAL_FLAG)) + fatal("Mutual authentication failed"); + + /* If integ avail flag is not true kex fails */ + if (!(ret_flags & GSS_C_INTEG_FLAG)) + fatal("Integrity check failed"); + } + + /* + * If we have data to send, then the last message that we + * received cannot have been a 'complete'. + */ + if (send_tok.length != 0) { + if (first) { + if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_INIT)) != 0 || + (r = sshpkt_put_string(ssh, send_tok.value, + send_tok.length)) != 0 || + (r = sshpkt_put_bignum2(ssh, pub_key)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + first = 0; + } else { + if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || + (r = sshpkt_put_string(ssh,send_tok.value, + send_tok.length)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + } + if ((r = sshpkt_send(ssh)) != 0) + fatal("sshpkt_send failed: %s", ssh_err(r)); + gss_release_buffer(&min_status, &send_tok); + + /* If we've sent them data, they should reply */ + do { + type = ssh_packet_read(ssh); + if (type == SSH2_MSG_KEXGSS_HOSTKEY) { + debug("Received KEXGSS_HOSTKEY"); + if (server_host_key_blob) + fatal("Server host key received more than once"); + if ((r = sshpkt_getb_froms(ssh, &server_host_key_blob)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + } + } while (type == SSH2_MSG_KEXGSS_HOSTKEY); + + switch (type) { + case SSH2_MSG_KEXGSS_CONTINUE: + debug("Received GSSAPI_CONTINUE"); + if (maj_status == GSS_S_COMPLETE) + fatal("GSSAPI Continue received from server when complete"); + if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, + &recv_tok)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + break; + case SSH2_MSG_KEXGSS_COMPLETE: + debug("Received GSSAPI_COMPLETE"); + if (msg_tok.value != NULL) + fatal("Received GSSAPI_COMPLETE twice?"); + if ((r = sshpkt_getb_froms(ssh, &server_blob)) != 0 || + (r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, + &msg_tok)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + + /* Is there a token included? */ + if ((r = sshpkt_get_u8(ssh, &c)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + if (c) { + if ((r = ssh_gssapi_sshpkt_get_buffer_desc( + ssh, &recv_tok)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + /* If we're already complete - protocol error */ + if (maj_status == GSS_S_COMPLETE) + sshpkt_disconnect(ssh, "Protocol error: received token when complete"); + } else { + /* No token included */ + if (maj_status != GSS_S_COMPLETE) + sshpkt_disconnect(ssh, "Protocol error: did not receive final token"); + } + break; + case SSH2_MSG_KEXGSS_ERROR: + debug("Received Error"); + if ((r = sshpkt_get_u32(ssh, &maj_status)) != 0 || + (r = sshpkt_get_u32(ssh, &min_status)) != 0 || + (r = sshpkt_get_string(ssh, &msg, NULL)) != 0 || + (r = sshpkt_get_string(ssh, NULL, NULL)) != 0 || /* lang tag */ + (r = sshpkt_get_end(ssh)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + fatal("GSSAPI Error: \n%.400s", msg); + default: + sshpkt_disconnect(ssh, "Protocol error: didn't expect packet type %d", + type); + } + token_ptr = &recv_tok; + } else { + /* No data, and not complete */ + if (maj_status != GSS_S_COMPLETE) + fatal("Not complete, and no token output"); + } + } while (maj_status & GSS_S_CONTINUE_NEEDED); + + /* + * We _must_ have received a COMPLETE message in reply from the + * server, which will have set dh_server_pub and msg_tok + */ + + if (type != SSH2_MSG_KEXGSS_COMPLETE) + fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it"); + + /* 7. C verifies that the key Q_S is valid */ + /* 8. C computes shared secret */ + if ((buf = sshbuf_new()) == NULL || + (r = sshbuf_put_stringb(buf, server_blob)) != 0 || + (r = sshbuf_get_bignum2(buf, &dh_server_pub)) != 0) + goto out; + sshbuf_free(buf); + buf = NULL; + + if ((shared_secret = sshbuf_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + + if ((r = kex_dh_compute_key(kex, dh_server_pub, shared_secret)) != 0) + goto out; + if ((empty = sshbuf_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + + DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g); + hashlen = sizeof(hash); + if ((r = kexgex_hash( + kex->hash_alg, + kex->client_version, + kex->server_version, + kex->my, + kex->peer, + (server_host_key_blob ? server_host_key_blob : empty), + kex->min, kex->nbits, kex->max, + dh_p, dh_g, + pub_key, + dh_server_pub, + sshbuf_ptr(shared_secret), sshbuf_len(shared_secret), + hash, &hashlen)) != 0) + fatal("Failed to calculate hash: %s", ssh_err(r)); + + gssbuf.value = hash; + gssbuf.length = hashlen; + + /* Verify that the hash matches the MIC we just got. */ + if (GSS_ERROR(ssh_gssapi_checkmic(ctxt, &gssbuf, &msg_tok))) + sshpkt_disconnect(ssh, "Hash's MIC didn't verify"); + + gss_release_buffer(&min_status, &msg_tok); + + /* save session id */ + if (kex->session_id == NULL) { + kex->session_id_len = hashlen; + kex->session_id = xmalloc(kex->session_id_len); + memcpy(kex->session_id, hash, kex->session_id_len); + } + + if (kex->gss_deleg_creds) + ssh_gssapi_credentials_updated(ctxt); + + if (gss_kex_context == NULL) + gss_kex_context = ctxt; + else + ssh_gssapi_delete_ctx(&ctxt); + + /* Finally derive the keys and send them */ + if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) + r = kex_send_newkeys(ssh); +out: + sshbuf_free(buf); + sshbuf_free(server_blob); + sshbuf_free(empty); + explicit_bzero(hash, sizeof(hash)); + DH_free(kex->dh); + kex->dh = NULL; + BN_clear_free(dh_server_pub); + sshbuf_free(shared_secret); + sshbuf_free(server_host_key_blob); + return r; +} +#endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */ diff --git a/kexgsss.c b/kexgsss.c new file mode 100644 index 000000000..60bc02deb --- /dev/null +++ b/kexgsss.c @@ -0,0 +1,474 @@ +/* + * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "includes.h" + +#if defined(GSSAPI) && defined(WITH_OPENSSL) + +#include + +#include +#include + +#include "xmalloc.h" +#include "sshbuf.h" +#include "ssh2.h" +#include "sshkey.h" +#include "cipher.h" +#include "kex.h" +#include "log.h" +#include "packet.h" +#include "dh.h" +#include "ssh-gss.h" +#include "monitor_wrap.h" +#include "misc.h" /* servconf.h needs misc.h for struct ForwardOptions */ +#include "servconf.h" +#include "ssh-gss.h" +#include "digest.h" +#include "ssherr.h" + +extern ServerOptions options; + +int +kexgss_server(struct ssh *ssh) +{ + struct kex *kex = ssh->kex; + OM_uint32 maj_status, min_status; + + /* + * Some GSSAPI implementations use the input value of ret_flags (an + * output variable) as a means of triggering mechanism specific + * features. Initializing it to zero avoids inadvertently + * activating this non-standard behaviour. + */ + + OM_uint32 ret_flags = 0; + gss_buffer_desc gssbuf, recv_tok, msg_tok; + gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; + Gssctxt *ctxt = NULL; + struct sshbuf *shared_secret = NULL; + struct sshbuf *client_pubkey = NULL; + struct sshbuf *server_pubkey = NULL; + struct sshbuf *empty = sshbuf_new(); + int type = 0; + gss_OID oid; + char *mechs; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t hashlen; + int r; + + /* Initialise GSSAPI */ + + /* If we're rekeying, privsep means that some of the private structures + * in the GSSAPI code are no longer available. This kludges them back + * into life + */ + if (!ssh_gssapi_oid_table_ok()) { + mechs = ssh_gssapi_server_mechanisms(); + free(mechs); + } + + debug2("%s: Identifying %s", __func__, kex->name); + oid = ssh_gssapi_id_kex(NULL, kex->name, kex->kex_type); + if (oid == GSS_C_NO_OID) + fatal("Unknown gssapi mechanism"); + + debug2("%s: Acquiring credentials", __func__); + + if (GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctxt, oid)))) + fatal("Unable to acquire credentials for the server"); + + do { + debug("Wait SSH2_MSG_KEXGSS_INIT"); + type = ssh_packet_read(ssh); + switch(type) { + case SSH2_MSG_KEXGSS_INIT: + if (client_pubkey != NULL) + fatal("Received KEXGSS_INIT after initialising"); + if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, + &recv_tok)) != 0 || + (r = sshpkt_getb_froms(ssh, &client_pubkey)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + + switch (kex->kex_type) { + case KEX_GSS_GRP1_SHA1: + case KEX_GSS_GRP14_SHA1: + case KEX_GSS_GRP14_SHA256: + case KEX_GSS_GRP16_SHA512: + r = kex_dh_enc(kex, client_pubkey, &server_pubkey, + &shared_secret); + break; + case KEX_GSS_NISTP256_SHA256: + r = kex_ecdh_enc(kex, client_pubkey, &server_pubkey, + &shared_secret); + break; + case KEX_GSS_C25519_SHA256: + r = kex_c25519_enc(kex, client_pubkey, &server_pubkey, + &shared_secret); + break; + default: + fatal("%s: Unexpected KEX type %d", __func__, kex->kex_type); + } + if (r != 0) + goto out; + + /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */ + break; + case SSH2_MSG_KEXGSS_CONTINUE: + if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, + &recv_tok)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + break; + default: + sshpkt_disconnect(ssh, + "Protocol error: didn't expect packet type %d", + type); + } + + maj_status = PRIVSEP(ssh_gssapi_accept_ctx(ctxt, &recv_tok, + &send_tok, &ret_flags)); + + gss_release_buffer(&min_status, &recv_tok); + + if (maj_status != GSS_S_COMPLETE && send_tok.length == 0) + fatal("Zero length token output when incomplete"); + + if (client_pubkey == NULL) + fatal("No client public key"); + + if (maj_status & GSS_S_CONTINUE_NEEDED) { + debug("Sending GSSAPI_CONTINUE"); + if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || + (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 || + (r = sshpkt_send(ssh)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + gss_release_buffer(&min_status, &send_tok); + } + } while (maj_status & GSS_S_CONTINUE_NEEDED); + + if (GSS_ERROR(maj_status)) { + if (send_tok.length > 0) { + if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || + (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 || + (r = sshpkt_send(ssh)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + } + fatal("accept_ctx died"); + } + + if (!(ret_flags & GSS_C_MUTUAL_FLAG)) + fatal("Mutual Authentication flag wasn't set"); + + if (!(ret_flags & GSS_C_INTEG_FLAG)) + fatal("Integrity flag wasn't set"); + + hashlen = sizeof(hash); + if ((r = kex_gen_hash( + kex->hash_alg, + kex->client_version, + kex->server_version, + kex->peer, + kex->my, + empty, + client_pubkey, + server_pubkey, + shared_secret, + hash, &hashlen)) != 0) + goto out; + + gssbuf.value = hash; + gssbuf.length = hashlen; + + if (GSS_ERROR(PRIVSEP(ssh_gssapi_sign(ctxt, &gssbuf, &msg_tok)))) + fatal("Couldn't get MIC"); + + if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_COMPLETE)) != 0 || + (r = sshpkt_put_stringb(ssh, server_pubkey)) != 0 || + (r = sshpkt_put_string(ssh, msg_tok.value, msg_tok.length)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + + if (send_tok.length != 0) { + if ((r = sshpkt_put_u8(ssh, 1)) != 0 || /* true */ + (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + } else { + if ((r = sshpkt_put_u8(ssh, 0)) != 0) /* false */ + fatal("sshpkt failed: %s", ssh_err(r)); + } + if ((r = sshpkt_send(ssh)) != 0) + fatal("sshpkt_send failed: %s", ssh_err(r)); + + gss_release_buffer(&min_status, &send_tok); + gss_release_buffer(&min_status, &msg_tok); + + if (gss_kex_context == NULL) + gss_kex_context = ctxt; + else + ssh_gssapi_delete_ctx(&ctxt); + + if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) + r = kex_send_newkeys(ssh); + + /* If this was a rekey, then save out any delegated credentials we + * just exchanged. */ + if (options.gss_store_rekey) + ssh_gssapi_rekey_creds(); +out: + sshbuf_free(empty); + explicit_bzero(hash, sizeof(hash)); + sshbuf_free(shared_secret); + sshbuf_free(client_pubkey); + sshbuf_free(server_pubkey); + return r; +} + +int +kexgssgex_server(struct ssh *ssh) +{ + struct kex *kex = ssh->kex; + OM_uint32 maj_status, min_status; + + /* + * Some GSSAPI implementations use the input value of ret_flags (an + * output variable) as a means of triggering mechanism specific + * features. Initializing it to zero avoids inadvertently + * activating this non-standard behaviour. + */ + + OM_uint32 ret_flags = 0; + gss_buffer_desc gssbuf, recv_tok, msg_tok; + gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; + Gssctxt *ctxt = NULL; + struct sshbuf *shared_secret = NULL; + int type = 0; + gss_OID oid; + char *mechs; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t hashlen; + BIGNUM *dh_client_pub = NULL; + const BIGNUM *pub_key, *dh_p, *dh_g; + int min = -1, max = -1, nbits = -1; + int cmin = -1, cmax = -1; /* client proposal */ + struct sshbuf *empty = sshbuf_new(); + int r; + + /* Initialise GSSAPI */ + + /* If we're rekeying, privsep means that some of the private structures + * in the GSSAPI code are no longer available. This kludges them back + * into life + */ + if (!ssh_gssapi_oid_table_ok()) + if ((mechs = ssh_gssapi_server_mechanisms())) + free(mechs); + + debug2("%s: Identifying %s", __func__, kex->name); + oid = ssh_gssapi_id_kex(NULL, kex->name, kex->kex_type); + if (oid == GSS_C_NO_OID) + fatal("Unknown gssapi mechanism"); + + debug2("%s: Acquiring credentials", __func__); + + if (GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctxt, oid)))) + fatal("Unable to acquire credentials for the server"); + + /* 5. S generates an ephemeral key pair (do the allocations early) */ + debug("Doing group exchange"); + ssh_packet_read_expect(ssh, SSH2_MSG_KEXGSS_GROUPREQ); + /* store client proposal to provide valid signature */ + if ((r = sshpkt_get_u32(ssh, &cmin)) != 0 || + (r = sshpkt_get_u32(ssh, &nbits)) != 0 || + (r = sshpkt_get_u32(ssh, &cmax)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + kex->nbits = nbits; + kex->min = cmin; + kex->max = cmax; + min = MAX(DH_GRP_MIN, cmin); + max = MIN(DH_GRP_MAX, cmax); + nbits = MAXIMUM(DH_GRP_MIN, nbits); + nbits = MINIMUM(DH_GRP_MAX, nbits); + if (max < min || nbits < min || max < nbits) + fatal("GSS_GEX, bad parameters: %d !< %d !< %d", + min, nbits, max); + kex->dh = PRIVSEP(choose_dh(min, nbits, max)); + if (kex->dh == NULL) { + sshpkt_disconnect(ssh, "Protocol error: no matching group found"); + fatal("Protocol error: no matching group found"); + } + + DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g); + if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUP)) != 0 || + (r = sshpkt_put_bignum2(ssh, dh_p)) != 0 || + (r = sshpkt_put_bignum2(ssh, dh_g)) != 0 || + (r = sshpkt_send(ssh)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + + if ((r = ssh_packet_write_wait(ssh)) != 0) + fatal("ssh_packet_write_wait: %s", ssh_err(r)); + + /* Compute our exchange value in parallel with the client */ + if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0) + goto out; + + do { + debug("Wait SSH2_MSG_GSSAPI_INIT"); + type = ssh_packet_read(ssh); + switch(type) { + case SSH2_MSG_KEXGSS_INIT: + if (dh_client_pub != NULL) + fatal("Received KEXGSS_INIT after initialising"); + if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, + &recv_tok)) != 0 || + (r = sshpkt_get_bignum2(ssh, &dh_client_pub)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + + /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */ + break; + case SSH2_MSG_KEXGSS_CONTINUE: + if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, + &recv_tok)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + break; + default: + sshpkt_disconnect(ssh, + "Protocol error: didn't expect packet type %d", + type); + } + + maj_status = PRIVSEP(ssh_gssapi_accept_ctx(ctxt, &recv_tok, + &send_tok, &ret_flags)); + + gss_release_buffer(&min_status, &recv_tok); + + if (maj_status != GSS_S_COMPLETE && send_tok.length == 0) + fatal("Zero length token output when incomplete"); + + if (dh_client_pub == NULL) + fatal("No client public key"); + + if (maj_status & GSS_S_CONTINUE_NEEDED) { + debug("Sending GSSAPI_CONTINUE"); + if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || + (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 || + (r = sshpkt_send(ssh)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + gss_release_buffer(&min_status, &send_tok); + } + } while (maj_status & GSS_S_CONTINUE_NEEDED); + + if (GSS_ERROR(maj_status)) { + if (send_tok.length > 0) { + if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 || + (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 || + (r = sshpkt_send(ssh)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + } + fatal("accept_ctx died"); + } + + if (!(ret_flags & GSS_C_MUTUAL_FLAG)) + fatal("Mutual Authentication flag wasn't set"); + + if (!(ret_flags & GSS_C_INTEG_FLAG)) + fatal("Integrity flag wasn't set"); + + /* calculate shared secret */ + if ((shared_secret = sshbuf_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if ((r = kex_dh_compute_key(kex, dh_client_pub, shared_secret)) != 0) + goto out; + + DH_get0_key(kex->dh, &pub_key, NULL); + DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g); + hashlen = sizeof(hash); + if ((r = kexgex_hash( + kex->hash_alg, + kex->client_version, + kex->server_version, + kex->peer, + kex->my, + empty, + cmin, nbits, cmax, + dh_p, dh_g, + dh_client_pub, + pub_key, + sshbuf_ptr(shared_secret), sshbuf_len(shared_secret), + hash, &hashlen)) != 0) + fatal("kexgex_hash failed: %s", ssh_err(r)); + + gssbuf.value = hash; + gssbuf.length = hashlen; + + if (GSS_ERROR(PRIVSEP(ssh_gssapi_sign(ctxt, &gssbuf, &msg_tok)))) + fatal("Couldn't get MIC"); + + if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_COMPLETE)) != 0 || + (r = sshpkt_put_bignum2(ssh, pub_key)) != 0 || + (r = sshpkt_put_string(ssh, msg_tok.value, msg_tok.length)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + + if (send_tok.length != 0) { + if ((r = sshpkt_put_u8(ssh, 1)) != 0 || /* true */ + (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + } else { + if ((r = sshpkt_put_u8(ssh, 0)) != 0) /* false */ + fatal("sshpkt failed: %s", ssh_err(r)); + } + if ((r = sshpkt_send(ssh)) != 0) + fatal("sshpkt failed: %s", ssh_err(r)); + + gss_release_buffer(&min_status, &send_tok); + gss_release_buffer(&min_status, &msg_tok); + + if (gss_kex_context == NULL) + gss_kex_context = ctxt; + else + ssh_gssapi_delete_ctx(&ctxt); + + /* Finally derive the keys and send them */ + if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) + r = kex_send_newkeys(ssh); + + /* If this was a rekey, then save out any delegated credentials we + * just exchanged. */ + if (options.gss_store_rekey) + ssh_gssapi_rekey_creds(); +out: + sshbuf_free(empty); + explicit_bzero(hash, sizeof(hash)); + DH_free(kex->dh); + kex->dh = NULL; + BN_clear_free(dh_client_pub); + sshbuf_free(shared_secret); + return r; +} +#endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */ diff --git a/mac.c b/mac.c index 51dc11d76..3d11eba62 100644 --- a/mac.c +++ b/mac.c @@ -29,6 +29,7 @@ #include #include +#include #include "digest.h" #include "hmac.h" diff --git a/monitor.c b/monitor.c index 60e529444..0766d6ef5 100644 --- a/monitor.c +++ b/monitor.c @@ -147,6 +147,8 @@ int mm_answer_gss_setup_ctx(struct ssh *, int, struct sshbuf *); int mm_answer_gss_accept_ctx(struct ssh *, int, struct sshbuf *); int mm_answer_gss_userok(struct ssh *, int, struct sshbuf *); int mm_answer_gss_checkmic(struct ssh *, int, struct sshbuf *); +int mm_answer_gss_sign(struct ssh *, int, struct sshbuf *); +int mm_answer_gss_updatecreds(struct ssh *, int, struct sshbuf *); #endif #ifdef SSH_AUDIT_EVENTS @@ -219,11 +221,18 @@ struct mon_table mon_dispatch_proto20[] = { {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx}, {MONITOR_REQ_GSSUSEROK, MON_ONCE|MON_AUTHDECIDE, mm_answer_gss_userok}, {MONITOR_REQ_GSSCHECKMIC, MON_ONCE, mm_answer_gss_checkmic}, + {MONITOR_REQ_GSSSIGN, MON_ONCE, mm_answer_gss_sign}, #endif {0, 0, NULL} }; struct mon_table mon_dispatch_postauth20[] = { +#ifdef GSSAPI + {MONITOR_REQ_GSSSETUP, 0, mm_answer_gss_setup_ctx}, + {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx}, + {MONITOR_REQ_GSSSIGN, 0, mm_answer_gss_sign}, + {MONITOR_REQ_GSSUPCREDS, 0, mm_answer_gss_updatecreds}, +#endif #ifdef WITH_OPENSSL {MONITOR_REQ_MODULI, 0, mm_answer_moduli}, #endif @@ -292,6 +301,10 @@ monitor_child_preauth(struct ssh *ssh, struct monitor *pmonitor) /* Permit requests for moduli and signatures */ monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); +#ifdef GSSAPI + /* and for the GSSAPI key exchange */ + monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1); +#endif /* The first few requests do not require asynchronous access */ while (!authenticated) { @@ -405,6 +418,10 @@ monitor_child_postauth(struct ssh *ssh, struct monitor *pmonitor) monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); monitor_permit(mon_dispatch, MONITOR_REQ_TERM, 1); +#ifdef GSSAPI + /* and for the GSSAPI key exchange */ + monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1); +#endif if (auth_opts->permit_pty_flag) { monitor_permit(mon_dispatch, MONITOR_REQ_PTY, 1); @@ -1687,6 +1704,17 @@ monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor) # ifdef OPENSSL_HAS_ECC kex->kex[KEX_ECDH_SHA2] = kex_gen_server; # endif +# ifdef GSSAPI + if (options.gss_keyex) { + kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server; + kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_server; + kex->kex[KEX_GSS_GRP14_SHA256] = kexgss_server; + kex->kex[KEX_GSS_GRP16_SHA512] = kexgss_server; + kex->kex[KEX_GSS_GEX_SHA1] = kexgssgex_server; + kex->kex[KEX_GSS_NISTP256_SHA256] = kexgss_server; + kex->kex[KEX_GSS_C25519_SHA256] = kexgss_server; + } +# endif #endif /* WITH_OPENSSL */ kex->kex[KEX_C25519_SHA256] = kex_gen_server; kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_gen_server; @@ -1780,8 +1808,8 @@ mm_answer_gss_setup_ctx(struct ssh *ssh, int sock, struct sshbuf *m) u_char *p; int r; - if (!options.gss_authentication) - fatal("%s: GSSAPI authentication not enabled", __func__); + if (!options.gss_authentication && !options.gss_keyex) + fatal("%s: GSSAPI not enabled", __func__); if ((r = sshbuf_get_string(m, &p, &len)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); @@ -1813,8 +1841,8 @@ mm_answer_gss_accept_ctx(struct ssh *ssh, int sock, struct sshbuf *m) OM_uint32 flags = 0; /* GSI needs this */ int r; - if (!options.gss_authentication) - fatal("%s: GSSAPI authentication not enabled", __func__); + if (!options.gss_authentication && !options.gss_keyex) + fatal("%s: GSSAPI not enabled", __func__); if ((r = ssh_gssapi_get_buffer_desc(m, &in)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); @@ -1834,6 +1862,7 @@ mm_answer_gss_accept_ctx(struct ssh *ssh, int sock, struct sshbuf *m) monitor_permit(mon_dispatch, MONITOR_REQ_GSSSTEP, 0); monitor_permit(mon_dispatch, MONITOR_REQ_GSSUSEROK, 1); monitor_permit(mon_dispatch, MONITOR_REQ_GSSCHECKMIC, 1); + monitor_permit(mon_dispatch, MONITOR_REQ_GSSSIGN, 1); } return (0); } @@ -1845,8 +1874,8 @@ mm_answer_gss_checkmic(struct ssh *ssh, int sock, struct sshbuf *m) OM_uint32 ret; int r; - if (!options.gss_authentication) - fatal("%s: GSSAPI authentication not enabled", __func__); + if (!options.gss_authentication && !options.gss_keyex) + fatal("%s: GSSAPI not enabled", __func__); if ((r = ssh_gssapi_get_buffer_desc(m, &gssbuf)) != 0 || (r = ssh_gssapi_get_buffer_desc(m, &mic)) != 0) @@ -1872,13 +1901,17 @@ mm_answer_gss_checkmic(struct ssh *ssh, int sock, struct sshbuf *m) int mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m) { - int r, authenticated; + int r, authenticated, kex; const char *displayname; - if (!options.gss_authentication) - fatal("%s: GSSAPI authentication not enabled", __func__); + if (!options.gss_authentication && !options.gss_keyex) + fatal("%s: GSSAPI not enabled", __func__); - authenticated = authctxt->valid && ssh_gssapi_userok(authctxt->user); + if ((r = sshbuf_get_u32(m, &kex)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + + authenticated = authctxt->valid && + ssh_gssapi_userok(authctxt->user, authctxt->pw, kex); sshbuf_reset(m); if ((r = sshbuf_put_u32(m, authenticated)) != 0) @@ -1887,7 +1920,11 @@ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m) debug3("%s: sending result %d", __func__, authenticated); mm_request_send(sock, MONITOR_ANS_GSSUSEROK, m); - auth_method = "gssapi-with-mic"; + if (kex) { + auth_method = "gssapi-keyex"; + } else { + auth_method = "gssapi-with-mic"; + } if ((displayname = ssh_gssapi_displayname()) != NULL) auth2_record_info(authctxt, "%s", displayname); @@ -1895,5 +1932,85 @@ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m) /* Monitor loop will terminate if authenticated */ return (authenticated); } + +int +mm_answer_gss_sign(struct ssh *ssh, int socket, struct sshbuf *m) +{ + gss_buffer_desc data; + gss_buffer_desc hash = GSS_C_EMPTY_BUFFER; + OM_uint32 major, minor; + size_t len; + u_char *p = NULL; + int r; + + if (!options.gss_authentication && !options.gss_keyex) + fatal("%s: GSSAPI not enabled", __func__); + + if ((r = sshbuf_get_string(m, &p, &len)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + data.value = p; + data.length = len; + /* Lengths of SHA-1, SHA-256 and SHA-512 hashes that are used */ + if (data.length != 20 && data.length != 32 && data.length != 64) + fatal("%s: data length incorrect: %d", __func__, + (int) data.length); + + /* Save the session ID on the first time around */ + if (session_id2_len == 0) { + session_id2_len = data.length; + session_id2 = xmalloc(session_id2_len); + memcpy(session_id2, data.value, session_id2_len); + } + major = ssh_gssapi_sign(gsscontext, &data, &hash); + + free(data.value); + + sshbuf_reset(m); + + if ((r = sshbuf_put_u32(m, major)) != 0 || + (r = sshbuf_put_string(m, hash.value, hash.length)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + + mm_request_send(socket, MONITOR_ANS_GSSSIGN, m); + + gss_release_buffer(&minor, &hash); + + /* Turn on getpwnam permissions */ + monitor_permit(mon_dispatch, MONITOR_REQ_PWNAM, 1); + + /* And credential updating, for when rekeying */ + monitor_permit(mon_dispatch, MONITOR_REQ_GSSUPCREDS, 1); + + return (0); +} + +int +mm_answer_gss_updatecreds(struct ssh *ssh, int socket, struct sshbuf *m) { + ssh_gssapi_ccache store; + int r, ok; + + if (!options.gss_authentication && !options.gss_keyex) + fatal("%s: GSSAPI not enabled", __func__); + + if ((r = sshbuf_get_string(m, (u_char **)&store.filename, NULL)) != 0 || + (r = sshbuf_get_string(m, (u_char **)&store.envvar, NULL)) != 0 || + (r = sshbuf_get_string(m, (u_char **)&store.envval, NULL)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + + ok = ssh_gssapi_update_creds(&store); + + free(store.filename); + free(store.envvar); + free(store.envval); + + sshbuf_reset(m); + if ((r = sshbuf_put_u32(m, ok)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + + mm_request_send(socket, MONITOR_ANS_GSSUPCREDS, m); + + return(0); +} + #endif /* GSSAPI */ diff --git a/monitor.h b/monitor.h index 683e5e071..2b1a2d590 100644 --- a/monitor.h +++ b/monitor.h @@ -63,6 +63,8 @@ enum monitor_reqtype { MONITOR_REQ_PAM_FREE_CTX = 110, MONITOR_ANS_PAM_FREE_CTX = 111, MONITOR_REQ_AUDIT_EVENT = 112, MONITOR_REQ_AUDIT_COMMAND = 113, + MONITOR_REQ_GSSSIGN = 150, MONITOR_ANS_GSSSIGN = 151, + MONITOR_REQ_GSSUPCREDS = 152, MONITOR_ANS_GSSUPCREDS = 153, }; struct ssh; diff --git a/monitor_wrap.c b/monitor_wrap.c index 186e8f022..8e4c1c1f8 100644 --- a/monitor_wrap.c +++ b/monitor_wrap.c @@ -978,13 +978,15 @@ mm_ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) } int -mm_ssh_gssapi_userok(char *user) +mm_ssh_gssapi_userok(char *user, struct passwd *pw, int kex) { struct sshbuf *m; int r, authenticated = 0; if ((m = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); + if ((r = sshbuf_put_u32(m, kex)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUSEROK, m); mm_request_receive_expect(pmonitor->m_recvfd, @@ -997,4 +999,57 @@ mm_ssh_gssapi_userok(char *user) debug3("%s: user %sauthenticated",__func__, authenticated ? "" : "not "); return (authenticated); } + +OM_uint32 +mm_ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_desc *data, gss_buffer_desc *hash) +{ + struct sshbuf *m; + OM_uint32 major; + int r; + + if ((m = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new failed", __func__); + if ((r = sshbuf_put_string(m, data->value, data->length)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSSIGN, m); + mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSSIGN, m); + + if ((r = sshbuf_get_u32(m, &major)) != 0 || + (r = ssh_gssapi_get_buffer_desc(m, hash)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + + sshbuf_free(m); + + return (major); +} + +int +mm_ssh_gssapi_update_creds(ssh_gssapi_ccache *store) +{ + struct sshbuf *m; + int r, ok; + + if ((m = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new failed", __func__); + + if ((r = sshbuf_put_cstring(m, + store->filename ? store->filename : "")) != 0 || + (r = sshbuf_put_cstring(m, + store->envvar ? store->envvar : "")) != 0 || + (r = sshbuf_put_cstring(m, + store->envval ? store->envval : "")) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUPCREDS, m); + mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSUPCREDS, m); + + if ((r = sshbuf_get_u32(m, &ok)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + + sshbuf_free(m); + + return (ok); +} + #endif /* GSSAPI */ diff --git a/monitor_wrap.h b/monitor_wrap.h index fdebb3aa4..69164a8c0 100644 --- a/monitor_wrap.h +++ b/monitor_wrap.h @@ -61,8 +61,10 @@ int mm_sshkey_verify(const struct sshkey *, const u_char *, size_t, OM_uint32 mm_ssh_gssapi_server_ctx(Gssctxt **, gss_OID); OM_uint32 mm_ssh_gssapi_accept_ctx(Gssctxt *, gss_buffer_desc *, gss_buffer_desc *, OM_uint32 *); -int mm_ssh_gssapi_userok(char *user); +int mm_ssh_gssapi_userok(char *user, struct passwd *, int kex); OM_uint32 mm_ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t); +OM_uint32 mm_ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t); +int mm_ssh_gssapi_update_creds(ssh_gssapi_ccache *); #endif #ifdef USE_PAM diff --git a/readconf.c b/readconf.c index ec497e79f..4d699e5f1 100644 --- a/readconf.c +++ b/readconf.c @@ -67,6 +67,7 @@ #include "uidswap.h" #include "myproposal.h" #include "digest.h" +#include "ssh-gss.h" /* Format of the configuration file: @@ -162,6 +163,8 @@ typedef enum { oClearAllForwardings, oNoHostAuthenticationForLocalhost, oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout, oAddressFamily, oGssAuthentication, oGssDelegateCreds, + oGssTrustDns, oGssKeyEx, oGssClientIdentity, oGssRenewalRekey, + oGssServerIdentity, oGssKexAlgorithms, oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly, oSendEnv, oSetEnv, oControlPath, oControlMaster, oControlPersist, oHashKnownHosts, @@ -202,10 +205,22 @@ static struct { /* Sometimes-unsupported options */ #if defined(GSSAPI) { "gssapiauthentication", oGssAuthentication }, + { "gssapikeyexchange", oGssKeyEx }, { "gssapidelegatecredentials", oGssDelegateCreds }, + { "gssapitrustdns", oGssTrustDns }, + { "gssapiclientidentity", oGssClientIdentity }, + { "gssapiserveridentity", oGssServerIdentity }, + { "gssapirenewalforcesrekey", oGssRenewalRekey }, + { "gssapikexalgorithms", oGssKexAlgorithms }, # else { "gssapiauthentication", oUnsupported }, + { "gssapikeyexchange", oUnsupported }, { "gssapidelegatecredentials", oUnsupported }, + { "gssapitrustdns", oUnsupported }, + { "gssapiclientidentity", oUnsupported }, + { "gssapiserveridentity", oUnsupported }, + { "gssapirenewalforcesrekey", oUnsupported }, + { "gssapikexalgorithms", oUnsupported }, #endif #ifdef ENABLE_PKCS11 { "pkcs11provider", oPKCS11Provider }, @@ -983,10 +998,42 @@ parse_time: intptr = &options->gss_authentication; goto parse_flag; + case oGssKeyEx: + intptr = &options->gss_keyex; + goto parse_flag; + case oGssDelegateCreds: intptr = &options->gss_deleg_creds; goto parse_flag; + case oGssTrustDns: + intptr = &options->gss_trust_dns; + goto parse_flag; + + case oGssClientIdentity: + charptr = &options->gss_client_identity; + goto parse_string; + + case oGssServerIdentity: + charptr = &options->gss_server_identity; + goto parse_string; + + case oGssRenewalRekey: + intptr = &options->gss_renewal_rekey; + goto parse_flag; + + case oGssKexAlgorithms: + arg = strdelim(&s); + if (!arg || *arg == '\0') + fatal("%.200s line %d: Missing argument.", + filename, linenum); + if (!kex_gss_names_valid(arg)) + fatal("%.200s line %d: Bad GSSAPI KexAlgorithms '%s'.", + filename, linenum, arg ? arg : ""); + if (*activep && options->gss_kex_algorithms == NULL) + options->gss_kex_algorithms = xstrdup(arg); + break; + case oBatchMode: intptr = &options->batch_mode; goto parse_flag; @@ -1854,7 +1901,13 @@ initialize_options(Options * options) options->pubkey_authentication = -1; options->challenge_response_authentication = -1; options->gss_authentication = -1; + options->gss_keyex = -1; options->gss_deleg_creds = -1; + options->gss_trust_dns = -1; + options->gss_renewal_rekey = -1; + options->gss_client_identity = NULL; + options->gss_server_identity = NULL; + options->gss_kex_algorithms = NULL; options->password_authentication = -1; options->kbd_interactive_authentication = -1; options->kbd_interactive_devices = NULL; @@ -2000,8 +2053,18 @@ fill_default_options(Options * options) options->challenge_response_authentication = 1; if (options->gss_authentication == -1) options->gss_authentication = 0; + if (options->gss_keyex == -1) + options->gss_keyex = 0; if (options->gss_deleg_creds == -1) options->gss_deleg_creds = 0; + if (options->gss_trust_dns == -1) + options->gss_trust_dns = 0; + if (options->gss_renewal_rekey == -1) + options->gss_renewal_rekey = 0; +#ifdef GSSAPI + if (options->gss_kex_algorithms == NULL) + options->gss_kex_algorithms = strdup(GSS_KEX_DEFAULT_KEX); +#endif if (options->password_authentication == -1) options->password_authentication = 1; if (options->kbd_interactive_authentication == -1) @@ -2616,7 +2679,14 @@ dump_client_config(Options *o, const char *host) dump_cfg_fmtint(oGatewayPorts, o->fwd_opts.gateway_ports); #ifdef GSSAPI dump_cfg_fmtint(oGssAuthentication, o->gss_authentication); + dump_cfg_fmtint(oGssKeyEx, o->gss_keyex); dump_cfg_fmtint(oGssDelegateCreds, o->gss_deleg_creds); + dump_cfg_fmtint(oGssTrustDns, o->gss_trust_dns); + dump_cfg_fmtint(oGssRenewalRekey, o->gss_renewal_rekey); + dump_cfg_string(oGssClientIdentity, o->gss_client_identity); + dump_cfg_string(oGssServerIdentity, o->gss_server_identity); + dump_cfg_string(oGssKexAlgorithms, o->gss_kex_algorithms ? + o->gss_kex_algorithms : GSS_KEX_DEFAULT_KEX); #endif /* GSSAPI */ dump_cfg_fmtint(oHashKnownHosts, o->hash_known_hosts); dump_cfg_fmtint(oHostbasedAuthentication, o->hostbased_authentication); diff --git a/readconf.h b/readconf.h index 8e36bf32a..0bff6d80a 100644 --- a/readconf.h +++ b/readconf.h @@ -40,7 +40,13 @@ typedef struct { int challenge_response_authentication; /* Try S/Key or TIS, authentication. */ int gss_authentication; /* Try GSS authentication */ + int gss_keyex; /* Try GSS key exchange */ int gss_deleg_creds; /* Delegate GSS credentials */ + int gss_trust_dns; /* Trust DNS for GSS canonicalization */ + int gss_renewal_rekey; /* Credential renewal forces rekey */ + char *gss_client_identity; /* Principal to initiate GSSAPI with */ + char *gss_server_identity; /* GSSAPI target principal */ + char *gss_kex_algorithms; /* GSSAPI kex methods to be offered by client. */ int password_authentication; /* Try password * authentication. */ int kbd_interactive_authentication; /* Try keyboard-interactive auth. */ diff --git a/servconf.c b/servconf.c index ffac5d2c7..ffdad31e7 100644 --- a/servconf.c +++ b/servconf.c @@ -64,6 +64,7 @@ #include "auth.h" #include "myproposal.h" #include "digest.h" +#include "ssh-gss.h" static void add_listen_addr(ServerOptions *, const char *, const char *, int); @@ -124,8 +125,11 @@ initialize_server_options(ServerOptions *options) options->kerberos_ticket_cleanup = -1; options->kerberos_get_afs_token = -1; options->gss_authentication=-1; + options->gss_keyex = -1; options->gss_cleanup_creds = -1; options->gss_strict_acceptor = -1; + options->gss_store_rekey = -1; + options->gss_kex_algorithms = NULL; options->password_authentication = -1; options->kbd_interactive_authentication = -1; options->challenge_response_authentication = -1; @@ -351,10 +355,18 @@ fill_default_server_options(ServerOptions *options) options->kerberos_get_afs_token = 0; if (options->gss_authentication == -1) options->gss_authentication = 0; + if (options->gss_keyex == -1) + options->gss_keyex = 0; if (options->gss_cleanup_creds == -1) options->gss_cleanup_creds = 1; if (options->gss_strict_acceptor == -1) options->gss_strict_acceptor = 1; + if (options->gss_store_rekey == -1) + options->gss_store_rekey = 0; +#ifdef GSSAPI + if (options->gss_kex_algorithms == NULL) + options->gss_kex_algorithms = strdup(GSS_KEX_DEFAULT_KEX); +#endif if (options->password_authentication == -1) options->password_authentication = 1; if (options->kbd_interactive_authentication == -1) @@ -498,6 +510,7 @@ typedef enum { sHostKeyAlgorithms, sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile, sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor, + sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey, sAcceptEnv, sSetEnv, sPermitTunnel, sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory, sUsePrivilegeSeparation, sAllowAgentForwarding, @@ -572,12 +585,22 @@ static struct { #ifdef GSSAPI { "gssapiauthentication", sGssAuthentication, SSHCFG_ALL }, { "gssapicleanupcredentials", sGssCleanupCreds, SSHCFG_GLOBAL }, + { "gssapicleanupcreds", sGssCleanupCreds, SSHCFG_GLOBAL }, { "gssapistrictacceptorcheck", sGssStrictAcceptor, SSHCFG_GLOBAL }, + { "gssapikeyexchange", sGssKeyEx, SSHCFG_GLOBAL }, + { "gssapistorecredentialsonrekey", sGssStoreRekey, SSHCFG_GLOBAL }, + { "gssapikexalgorithms", sGssKexAlgorithms, SSHCFG_GLOBAL }, #else { "gssapiauthentication", sUnsupported, SSHCFG_ALL }, { "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL }, + { "gssapicleanupcreds", sUnsupported, SSHCFG_GLOBAL }, { "gssapistrictacceptorcheck", sUnsupported, SSHCFG_GLOBAL }, + { "gssapikeyexchange", sUnsupported, SSHCFG_GLOBAL }, + { "gssapistorecredentialsonrekey", sUnsupported, SSHCFG_GLOBAL }, + { "gssapikexalgorithms", sUnsupported, SSHCFG_GLOBAL }, #endif + { "gssusesessionccache", sUnsupported, SSHCFG_GLOBAL }, + { "gssapiusesessioncredcache", sUnsupported, SSHCFG_GLOBAL }, { "passwordauthentication", sPasswordAuthentication, SSHCFG_ALL }, { "kbdinteractiveauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, { "challengeresponseauthentication", sChallengeResponseAuthentication, SSHCFG_GLOBAL }, @@ -1485,6 +1508,10 @@ process_server_config_line(ServerOptions *options, char *line, intptr = &options->gss_authentication; goto parse_flag; + case sGssKeyEx: + intptr = &options->gss_keyex; + goto parse_flag; + case sGssCleanupCreds: intptr = &options->gss_cleanup_creds; goto parse_flag; @@ -1493,6 +1520,22 @@ process_server_config_line(ServerOptions *options, char *line, intptr = &options->gss_strict_acceptor; goto parse_flag; + case sGssStoreRekey: + intptr = &options->gss_store_rekey; + goto parse_flag; + + case sGssKexAlgorithms: + arg = strdelim(&cp); + if (!arg || *arg == '\0') + fatal("%.200s line %d: Missing argument.", + filename, linenum); + if (!kex_gss_names_valid(arg)) + fatal("%.200s line %d: Bad GSSAPI KexAlgorithms '%s'.", + filename, linenum, arg ? arg : ""); + if (*activep && options->gss_kex_algorithms == NULL) + options->gss_kex_algorithms = xstrdup(arg); + break; + case sPasswordAuthentication: intptr = &options->password_authentication; goto parse_flag; @@ -2579,6 +2622,10 @@ dump_config(ServerOptions *o) #ifdef GSSAPI dump_cfg_fmtint(sGssAuthentication, o->gss_authentication); dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds); + dump_cfg_fmtint(sGssKeyEx, o->gss_keyex); + dump_cfg_fmtint(sGssStrictAcceptor, o->gss_strict_acceptor); + dump_cfg_fmtint(sGssStoreRekey, o->gss_store_rekey); + dump_cfg_string(sGssKexAlgorithms, o->gss_kex_algorithms); #endif dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication); dump_cfg_fmtint(sKbdInteractiveAuthentication, diff --git a/servconf.h b/servconf.h index 54e0a8d8d..a476d5220 100644 --- a/servconf.h +++ b/servconf.h @@ -126,8 +126,11 @@ typedef struct { int kerberos_get_afs_token; /* If true, try to get AFS token if * authenticated with Kerberos. */ int gss_authentication; /* If true, permit GSSAPI authentication */ + int gss_keyex; /* If true, permit GSSAPI key exchange */ int gss_cleanup_creds; /* If true, destroy cred cache on logout */ int gss_strict_acceptor; /* If true, restrict the GSSAPI acceptor name */ + int gss_store_rekey; + char *gss_kex_algorithms; /* GSSAPI kex methods to be offered by client. */ int password_authentication; /* If true, permit password * authentication. */ int kbd_interactive_authentication; /* If true, permit */ diff --git a/session.c b/session.c index ac06b08e9..ac3d9d19d 100644 --- a/session.c +++ b/session.c @@ -2674,13 +2674,19 @@ do_cleanup(struct ssh *ssh, Authctxt *authctxt) #ifdef KRB5 if (options.kerberos_ticket_cleanup && - authctxt->krb5_ctx) + authctxt->krb5_ctx) { + temporarily_use_uid(authctxt->pw); krb5_cleanup_proc(authctxt); + restore_uid(); + } #endif #ifdef GSSAPI - if (options.gss_cleanup_creds) + if (options.gss_cleanup_creds) { + temporarily_use_uid(authctxt->pw); ssh_gssapi_cleanup_creds(); + restore_uid(); + } #endif /* remove agent socket */ diff --git a/ssh-gss.h b/ssh-gss.h index 36180d07a..70dd36658 100644 --- a/ssh-gss.h +++ b/ssh-gss.h @@ -1,6 +1,6 @@ /* $OpenBSD: ssh-gss.h,v 1.14 2018/07/10 09:13:30 djm Exp $ */ /* - * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. + * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -61,10 +61,30 @@ #define SSH_GSS_OIDTYPE 0x06 +#define SSH2_MSG_KEXGSS_INIT 30 +#define SSH2_MSG_KEXGSS_CONTINUE 31 +#define SSH2_MSG_KEXGSS_COMPLETE 32 +#define SSH2_MSG_KEXGSS_HOSTKEY 33 +#define SSH2_MSG_KEXGSS_ERROR 34 +#define SSH2_MSG_KEXGSS_GROUPREQ 40 +#define SSH2_MSG_KEXGSS_GROUP 41 +#define KEX_GSS_GRP1_SHA1_ID "gss-group1-sha1-" +#define KEX_GSS_GRP14_SHA1_ID "gss-group14-sha1-" +#define KEX_GSS_GRP14_SHA256_ID "gss-group14-sha256-" +#define KEX_GSS_GRP16_SHA512_ID "gss-group16-sha512-" +#define KEX_GSS_GEX_SHA1_ID "gss-gex-sha1-" +#define KEX_GSS_NISTP256_SHA256_ID "gss-nistp256-sha256-" +#define KEX_GSS_C25519_SHA256_ID "gss-curve25519-sha256-" + +#define GSS_KEX_DEFAULT_KEX \ + KEX_GSS_GEX_SHA1_ID "," \ + KEX_GSS_GRP14_SHA1_ID + typedef struct { char *filename; char *envvar; char *envval; + struct passwd *owner; void *data; } ssh_gssapi_ccache; @@ -72,8 +92,11 @@ typedef struct { gss_buffer_desc displayname; gss_buffer_desc exportedname; gss_cred_id_t creds; + gss_name_t name; struct ssh_gssapi_mech_struct *mech; ssh_gssapi_ccache store; + int used; + int updated; } ssh_gssapi_client; typedef struct ssh_gssapi_mech_struct { @@ -84,6 +107,7 @@ typedef struct ssh_gssapi_mech_struct { int (*userok) (ssh_gssapi_client *, char *); int (*localname) (ssh_gssapi_client *, char **); void (*storecreds) (ssh_gssapi_client *); + int (*updatecreds) (ssh_gssapi_ccache *, ssh_gssapi_client *); } ssh_gssapi_mech; typedef struct { @@ -94,10 +118,11 @@ typedef struct { gss_OID oid; /* client */ gss_cred_id_t creds; /* server */ gss_name_t client; /* server */ - gss_cred_id_t client_creds; /* server */ + gss_cred_id_t client_creds; /* both */ } Gssctxt; extern ssh_gssapi_mech *supported_mechs[]; +extern Gssctxt *gss_kex_context; int ssh_gssapi_check_oid(Gssctxt *, void *, size_t); void ssh_gssapi_set_oid_data(Gssctxt *, void *, size_t); @@ -109,6 +134,7 @@ OM_uint32 ssh_gssapi_test_oid_supported(OM_uint32 *, gss_OID, int *); struct sshbuf; int ssh_gssapi_get_buffer_desc(struct sshbuf *, gss_buffer_desc *); +int ssh_gssapi_sshpkt_get_buffer_desc(struct ssh *, gss_buffer_desc *); OM_uint32 ssh_gssapi_import_name(Gssctxt *, const char *); OM_uint32 ssh_gssapi_init_ctx(Gssctxt *, int, @@ -123,17 +149,33 @@ void ssh_gssapi_delete_ctx(Gssctxt **); OM_uint32 ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t); void ssh_gssapi_buildmic(struct sshbuf *, const char *, const char *, const char *); -int ssh_gssapi_check_mechanism(Gssctxt **, gss_OID, const char *); +int ssh_gssapi_check_mechanism(Gssctxt **, gss_OID, const char *, const char *); +OM_uint32 ssh_gssapi_client_identity(Gssctxt *, const char *); +int ssh_gssapi_credentials_updated(Gssctxt *); /* In the server */ +typedef int ssh_gssapi_check_fn(Gssctxt **, gss_OID, const char *, + const char *); +char *ssh_gssapi_client_mechanisms(const char *, const char *, const char *); +char *ssh_gssapi_kex_mechs(gss_OID_set, ssh_gssapi_check_fn *, const char *, + const char *, const char *); +gss_OID ssh_gssapi_id_kex(Gssctxt *, char *, int); +int ssh_gssapi_server_check_mech(Gssctxt **,gss_OID, const char *, + const char *); OM_uint32 ssh_gssapi_server_ctx(Gssctxt **, gss_OID); -int ssh_gssapi_userok(char *name); +int ssh_gssapi_userok(char *name, struct passwd *, int kex); OM_uint32 ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t); void ssh_gssapi_do_child(char ***, u_int *); void ssh_gssapi_cleanup_creds(void); void ssh_gssapi_storecreds(void); const char *ssh_gssapi_displayname(void); +char *ssh_gssapi_server_mechanisms(void); +int ssh_gssapi_oid_table_ok(void); + +int ssh_gssapi_update_creds(ssh_gssapi_ccache *store); +void ssh_gssapi_rekey_creds(void); + #endif /* GSSAPI */ #endif /* _SSH_GSS_H */ diff --git a/ssh.1 b/ssh.1 index 9480eba8d..a1c7d2305 100644 --- a/ssh.1 +++ b/ssh.1 @@ -497,7 +497,13 @@ For full details of the options listed below, and their possible values, see .It GatewayPorts .It GlobalKnownHostsFile .It GSSAPIAuthentication +.It GSSAPIKeyExchange +.It GSSAPIClientIdentity .It GSSAPIDelegateCredentials +.It GSSAPIKexAlgorithms +.It GSSAPIRenewalForcesRekey +.It GSSAPIServerIdentity +.It GSSAPITrustDns .It HashKnownHosts .It Host .It HostbasedAuthentication @@ -573,6 +579,8 @@ flag), (supported message integrity codes), .Ar kex (key exchange algorithms), +.Ar kex-gss +(GSSAPI key exchange algorithms), .Ar key (key types), .Ar key-cert diff --git a/ssh.c b/ssh.c index 91e7c3511..42be7d88f 100644 --- a/ssh.c +++ b/ssh.c @@ -736,6 +736,8 @@ main(int ac, char **av) cp = mac_alg_list('\n'); else if (strcmp(optarg, "kex") == 0) cp = kex_alg_list('\n'); + else if (strcmp(optarg, "kex-gss") == 0) + cp = kex_gss_alg_list('\n'); else if (strcmp(optarg, "key") == 0) cp = sshkey_alg_list(0, 0, 0, '\n'); else if (strcmp(optarg, "key-cert") == 0) @@ -748,7 +750,7 @@ main(int ac, char **av) cp = xstrdup("2"); else if (strcmp(optarg, "help") == 0) { cp = xstrdup( - "cipher\ncipher-auth\nkex\nkey\n" + "cipher\ncipher-auth\nkex\nkex-gss\nkey\n" "key-cert\nkey-plain\nmac\n" "protocol-version\nsig"); } diff --git a/ssh_config b/ssh_config index 5e8ef548b..1ff999b68 100644 --- a/ssh_config +++ b/ssh_config @@ -24,6 +24,8 @@ # HostbasedAuthentication no # GSSAPIAuthentication no # GSSAPIDelegateCredentials no +# GSSAPIKeyExchange no +# GSSAPITrustDNS no # BatchMode no # CheckHostIP yes # AddressFamily any diff --git a/ssh_config.5 b/ssh_config.5 index 412629637..c3c8b274a 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -754,10 +754,67 @@ The default is Specifies whether user authentication based on GSSAPI is allowed. The default is .Cm no . +.It Cm GSSAPIClientIdentity +If set, specifies the GSSAPI client identity that ssh should use when +connecting to the server. The default is unset, which means that the default +identity will be used. .It Cm GSSAPIDelegateCredentials Forward (delegate) credentials to the server. The default is .Cm no . +.It Cm GSSAPIKeyExchange +Specifies whether key exchange based on GSSAPI may be used. When using +GSSAPI key exchange the server need not have a host key. +The default is +.Dq no . +.It Cm GSSAPIRenewalForcesRekey +If set to +.Dq yes +then renewal of the client's GSSAPI credentials will force the rekeying of the +ssh connection. With a compatible server, this will delegate the renewed +credentials to a session on the server. +.Pp +Checks are made to ensure that credentials are only propagated when the new +credentials match the old ones on the originating client and where the +receiving server still has the old set in its cache. +.Pp +The default is +.Dq no . +.Pp +For this to work +.Cm GSSAPIKeyExchange +needs to be enabled in the server and also used by the client. +.It Cm GSSAPIServerIdentity +If set, specifies the GSSAPI server identity that ssh should expect when +connecting to the server. The default is unset, which means that the +expected GSSAPI server identity will be determined from the target +hostname. +.It Cm GSSAPITrustDns +Set to +.Dq yes +to indicate that the DNS is trusted to securely canonicalize +the name of the host being connected to. If +.Dq no , +the hostname entered on the +command line will be passed untouched to the GSSAPI library. +The default is +.Dq no . +.It Cm GSSAPIKexAlgorithms +The list of key exchange algorithms that are offered for GSSAPI +key exchange. Possible values are +.Bd -literal -offset 3n +gss-gex-sha1-, +gss-group1-sha1-, +gss-group14-sha1-, +gss-group14-sha256-, +gss-group16-sha512-, +gss-nistp256-sha256-, +gss-curve25519-sha256- +.Ed +.Pp +The default is +.Dq gss-gex-sha1-,gss-group14-sha1- . +This option only applies to protocol version 2 connections using GSSAPI. .It Cm HashKnownHosts Indicates that .Xr ssh 1 diff --git a/sshconnect2.c b/sshconnect2.c index dffee90b1..4020371ae 100644 --- a/sshconnect2.c +++ b/sshconnect2.c @@ -78,8 +78,6 @@ #endif /* import */ -extern char *client_version_string; -extern char *server_version_string; extern Options options; /* @@ -161,6 +159,11 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port) char *s, *all_key; int r; +#if defined(GSSAPI) && defined(WITH_OPENSSL) + char *orig = NULL, *gss = NULL; + char *gss_host = NULL; +#endif + xxx_host = host; xxx_hostaddr = hostaddr; @@ -193,6 +196,35 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port) order_hostkeyalgs(host, hostaddr, port)); } +#if defined(GSSAPI) && defined(WITH_OPENSSL) + if (options.gss_keyex) { + /* Add the GSSAPI mechanisms currently supported on this + * client to the key exchange algorithm proposal */ + orig = myproposal[PROPOSAL_KEX_ALGS]; + + if (options.gss_server_identity) + gss_host = xstrdup(options.gss_server_identity); + else if (options.gss_trust_dns) + gss_host = remote_hostname(ssh); + else + gss_host = xstrdup(host); + + gss = ssh_gssapi_client_mechanisms(gss_host, + options.gss_client_identity, options.gss_kex_algorithms); + if (gss) { + debug("Offering GSSAPI proposal: %s", gss); + xasprintf(&myproposal[PROPOSAL_KEX_ALGS], + "%s,%s", gss, orig); + + /* If we've got GSSAPI algorithms, then we also support the + * 'null' hostkey, as a last resort */ + orig = myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]; + xasprintf(&myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS], + "%s,null", orig); + } + } +#endif + if (options.rekey_limit || options.rekey_interval) ssh_packet_set_rekey_limits(ssh, options.rekey_limit, options.rekey_interval); @@ -211,16 +243,46 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port) # ifdef OPENSSL_HAS_ECC ssh->kex->kex[KEX_ECDH_SHA2] = kex_gen_client; # endif +# ifdef GSSAPI + if (options.gss_keyex) { + ssh->kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_client; + ssh->kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_client; + ssh->kex->kex[KEX_GSS_GRP14_SHA256] = kexgss_client; + ssh->kex->kex[KEX_GSS_GRP16_SHA512] = kexgss_client; + ssh->kex->kex[KEX_GSS_GEX_SHA1] = kexgssgex_client; + ssh->kex->kex[KEX_GSS_NISTP256_SHA256] = kexgss_client; + ssh->kex->kex[KEX_GSS_C25519_SHA256] = kexgss_client; + } +# endif #endif ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client; ssh->kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_gen_client; ssh->kex->verify_host_key=&verify_host_key_callback; +#if defined(GSSAPI) && defined(WITH_OPENSSL) + if (options.gss_keyex) { + ssh->kex->gss_deleg_creds = options.gss_deleg_creds; + ssh->kex->gss_trust_dns = options.gss_trust_dns; + ssh->kex->gss_client = options.gss_client_identity; + ssh->kex->gss_host = gss_host; + } +#endif + ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &ssh->kex->done); /* remove ext-info from the KEX proposals for rekeying */ myproposal[PROPOSAL_KEX_ALGS] = compat_kex_proposal(options.kex_algorithms); +#if defined(GSSAPI) && defined(WITH_OPENSSL) + /* repair myproposal after it was crumpled by the */ + /* ext-info removal above */ + if (gss) { + orig = myproposal[PROPOSAL_KEX_ALGS]; + xasprintf(&myproposal[PROPOSAL_KEX_ALGS], + "%s,%s", gss, orig); + free(gss); + } +#endif if ((r = kex_prop2buf(ssh->kex->my, myproposal)) != 0) fatal("kex_prop2buf: %s", ssh_err(r)); @@ -317,6 +379,7 @@ static int input_gssapi_response(int type, u_int32_t, struct ssh *); static int input_gssapi_token(int type, u_int32_t, struct ssh *); static int input_gssapi_error(int, u_int32_t, struct ssh *); static int input_gssapi_errtok(int, u_int32_t, struct ssh *); +static int userauth_gsskeyex(struct ssh *); #endif void userauth(struct ssh *, char *); @@ -333,6 +396,11 @@ static char *authmethods_get(void); Authmethod authmethods[] = { #ifdef GSSAPI + {"gssapi-keyex", + userauth_gsskeyex, + NULL, + &options.gss_keyex, + NULL}, {"gssapi-with-mic", userauth_gssapi, userauth_gssapi_cleanup, @@ -698,12 +766,25 @@ userauth_gssapi(struct ssh *ssh) OM_uint32 min; int r, ok = 0; gss_OID mech = NULL; + char *gss_host; + + if (options.gss_server_identity) + gss_host = xstrdup(options.gss_server_identity); + else if (options.gss_trust_dns) + gss_host = remote_hostname(ssh); + else + gss_host = xstrdup(authctxt->host); /* Try one GSSAPI method at a time, rather than sending them all at * once. */ if (authctxt->gss_supported_mechs == NULL) - gss_indicate_mechs(&min, &authctxt->gss_supported_mechs); + if (GSS_ERROR(gss_indicate_mechs(&min, + &authctxt->gss_supported_mechs))) { + authctxt->gss_supported_mechs = NULL; + free(gss_host); + return 0; + } /* Check to see whether the mechanism is usable before we offer it */ while (authctxt->mech_tried < authctxt->gss_supported_mechs->count && @@ -712,13 +793,15 @@ userauth_gssapi(struct ssh *ssh) elements[authctxt->mech_tried]; /* My DER encoding requires length<128 */ if (mech->length < 128 && ssh_gssapi_check_mechanism(&gssctxt, - mech, authctxt->host)) { + mech, gss_host, options.gss_client_identity)) { ok = 1; /* Mechanism works */ } else { authctxt->mech_tried++; } } + free(gss_host); + if (!ok || mech == NULL) return 0; @@ -958,6 +1041,55 @@ input_gssapi_error(int type, u_int32_t plen, struct ssh *ssh) free(lang); return r; } + +int +userauth_gsskeyex(struct ssh *ssh) +{ + struct sshbuf *b = NULL; + Authctxt *authctxt = ssh->authctxt; + gss_buffer_desc gssbuf; + gss_buffer_desc mic = GSS_C_EMPTY_BUFFER; + OM_uint32 ms; + int r; + + static int attempt = 0; + if (attempt++ >= 1) + return (0); + + if (gss_kex_context == NULL) { + debug("No valid Key exchange context"); + return (0); + } + + if ((b = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new failed", __func__); + + ssh_gssapi_buildmic(b, authctxt->server_user, authctxt->service, + "gssapi-keyex"); + + if ((gssbuf.value = sshbuf_mutable_ptr(b)) == NULL) + fatal("%s: sshbuf_mutable_ptr failed", __func__); + gssbuf.length = sshbuf_len(b); + + if (GSS_ERROR(ssh_gssapi_sign(gss_kex_context, &gssbuf, &mic))) { + sshbuf_free(b); + return (0); + } + + if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_REQUEST)) != 0 || + (r = sshpkt_put_cstring(ssh, authctxt->server_user)) != 0 || + (r = sshpkt_put_cstring(ssh, authctxt->service)) != 0 || + (r = sshpkt_put_cstring(ssh, authctxt->method->name)) != 0 || + (r = sshpkt_put_string(ssh, mic.value, mic.length)) != 0 || + (r = sshpkt_send(ssh)) != 0) + fatal("%s: %s", __func__, ssh_err(r)); + + sshbuf_free(b); + gss_release_buffer(&ms, &mic); + + return (1); +} + #endif /* GSSAPI */ static int diff --git a/sshd.c b/sshd.c index cbd3bce91..98680721b 100644 --- a/sshd.c +++ b/sshd.c @@ -123,6 +123,10 @@ #include "version.h" #include "ssherr.h" +#ifdef USE_SECURITY_SESSION_API +#include +#endif + /* Re-exec fds */ #define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1) #define REEXEC_STARTUP_PIPE_FD (STDERR_FILENO + 2) @@ -796,8 +800,8 @@ notify_hostkeys(struct ssh *ssh) } debug3("%s: sent %u hostkeys", __func__, nkeys); if (nkeys == 0) - fatal("%s: no hostkeys", __func__); - if ((r = sshpkt_send(ssh)) != 0) + debug3("%s: no hostkeys", __func__); + else if ((r = sshpkt_send(ssh)) != 0) sshpkt_fatal(ssh, r, "%s: send", __func__); sshbuf_free(buf); } @@ -1769,7 +1773,8 @@ main(int ac, char **av) free(fp); } accumulate_host_timing_secret(cfg, NULL); - if (!sensitive_data.have_ssh2_key) { + /* The GSSAPI key exchange can run without a host key */ + if (!sensitive_data.have_ssh2_key && !options.gss_keyex) { logit("sshd: no hostkeys available -- exiting."); exit(1); } @@ -2064,6 +2069,60 @@ main(int ac, char **av) rdomain == NULL ? "" : "\""); free(laddr); +#ifdef USE_SECURITY_SESSION_API + /* + * Create a new security session for use by the new user login if + * the current session is the root session or we are not launched + * by inetd (eg: debugging mode or server mode). We do not + * necessarily need to create a session if we are launched from + * inetd because Panther xinetd will create a session for us. + * + * The only case where this logic will fail is if there is an + * inetd running in a non-root session which is not creating + * new sessions for us. Then all the users will end up in the + * same session (bad). + * + * When the client exits, the session will be destroyed for us + * automatically. + * + * We must create the session before any credentials are stored + * (including AFS pags, which happens a few lines below). + */ + { + OSStatus err = 0; + SecuritySessionId sid = 0; + SessionAttributeBits sattrs = 0; + + err = SessionGetInfo(callerSecuritySession, &sid, &sattrs); + if (err) + error("SessionGetInfo() failed with error %.8X", + (unsigned) err); + else + debug("Current Session ID is %.8X / Session Attributes are %.8X", + (unsigned) sid, (unsigned) sattrs); + + if (inetd_flag && !(sattrs & sessionIsRoot)) + debug("Running in inetd mode in a non-root session... " + "assuming inetd created the session for us."); + else { + debug("Creating new security session..."); + err = SessionCreate(0, sessionHasTTY | sessionIsRemote); + if (err) + error("SessionCreate() failed with error %.8X", + (unsigned) err); + + err = SessionGetInfo(callerSecuritySession, &sid, + &sattrs); + if (err) + error("SessionGetInfo() failed with error %.8X", + (unsigned) err); + else + debug("New Session ID is %.8X / Session Attributes are %.8X", + (unsigned) sid, (unsigned) sattrs); + } + } +#endif + /* * We don't want to listen forever unless the other side * successfully authenticates itself. So we set up an alarm which is @@ -2260,6 +2319,48 @@ do_ssh2_kex(struct ssh *ssh) myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = compat_pkalg_proposal( list_hostkey_types()); +#if defined(GSSAPI) && defined(WITH_OPENSSL) + { + char *orig; + char *gss = NULL; + char *newstr = NULL; + orig = myproposal[PROPOSAL_KEX_ALGS]; + + /* + * If we don't have a host key, then there's no point advertising + * the other key exchange algorithms + */ + + if (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]) == 0) + orig = NULL; + + if (options.gss_keyex) + gss = ssh_gssapi_server_mechanisms(); + else + gss = NULL; + + if (gss && orig) + xasprintf(&newstr, "%s,%s", gss, orig); + else if (gss) + newstr = gss; + else if (orig) + newstr = orig; + + /* + * If we've got GSSAPI mechanisms, then we've got the 'null' host + * key alg, but we can't tell people about it unless its the only + * host key algorithm we support + */ + if (gss && (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS])) == 0) + myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = "null"; + + if (newstr) + myproposal[PROPOSAL_KEX_ALGS] = newstr; + else + fatal("No supported key exchange algorithms"); + } +#endif + /* start key exchange */ if ((r = kex_setup(ssh, myproposal)) != 0) fatal("kex_setup: %s", ssh_err(r)); @@ -2275,7 +2376,18 @@ do_ssh2_kex(struct ssh *ssh) # ifdef OPENSSL_HAS_ECC kex->kex[KEX_ECDH_SHA2] = kex_gen_server; # endif -#endif +# ifdef GSSAPI + if (options.gss_keyex) { + kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server; + kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_server; + kex->kex[KEX_GSS_GRP14_SHA256] = kexgss_server; + kex->kex[KEX_GSS_GRP16_SHA512] = kexgss_server; + kex->kex[KEX_GSS_GEX_SHA1] = kexgssgex_server; + kex->kex[KEX_GSS_NISTP256_SHA256] = kexgss_server; + kex->kex[KEX_GSS_C25519_SHA256] = kexgss_server; + } +# endif +#endif /* WITH_OPENSSL */ kex->kex[KEX_C25519_SHA256] = kex_gen_server; kex->kex[KEX_KEM_SNTRUP4591761X25519_SHA512] = kex_gen_server; kex->load_host_public_key=&get_hostkey_public_by_type; diff --git a/sshd_config b/sshd_config index 19b7c91a1..2c48105f8 100644 --- a/sshd_config +++ b/sshd_config @@ -69,6 +69,8 @@ AuthorizedKeysFile .ssh/authorized_keys # GSSAPI options #GSSAPIAuthentication no #GSSAPICleanupCredentials yes +#GSSAPIStrictAcceptorCheck yes +#GSSAPIKeyExchange no # Set this to 'yes' to enable PAM authentication, account processing, # and session processing. If this is enabled, PAM authentication will diff --git a/sshd_config.5 b/sshd_config.5 index b224f2929..2baa6622b 100644 --- a/sshd_config.5 +++ b/sshd_config.5 @@ -653,6 +653,11 @@ Specifies whether to automatically destroy the user's credentials cache on logout. The default is .Cm yes . +.It Cm GSSAPIKeyExchange +Specifies whether key exchange based on GSSAPI is allowed. GSSAPI key exchange +doesn't rely on ssh keys to verify host identity. +The default is +.Cm no . .It Cm GSSAPIStrictAcceptorCheck Determines whether to be strict about the identity of the GSSAPI acceptor a client authenticates against. @@ -667,6 +672,31 @@ machine's default store. This facility is provided to assist with operation on multi homed machines. The default is .Cm yes . +.It Cm GSSAPIStoreCredentialsOnRekey +Controls whether the user's GSSAPI credentials should be updated following a +successful connection rekeying. This option can be used to accepted renewed +or updated credentials from a compatible client. The default is +.Dq no . +.Pp +For this to work +.Cm GSSAPIKeyExchange +needs to be enabled in the server and also used by the client. +.It Cm GSSAPIKexAlgorithms +The list of key exchange algorithms that are accepted by GSSAPI +key exchange. Possible values are +.Bd -literal -offset 3n +gss-gex-sha1-, +gss-group1-sha1-, +gss-group14-sha1-, +gss-group14-sha256-, +gss-group16-sha512-, +gss-nistp256-sha256-, +gss-curve25519-sha256- +.Ed +.Pp +The default is +.Dq gss-gex-sha1-,gss-group14-sha1- . +This option only applies to protocol version 2 connections using GSSAPI. .It Cm HostbasedAcceptedKeyTypes Specifies the key types that will be accepted for hostbased authentication as a list of comma-separated patterns. diff --git a/sshkey.c b/sshkey.c index ad1957762..789cd61ef 100644 --- a/sshkey.c +++ b/sshkey.c @@ -135,6 +135,7 @@ static const struct keytype keytypes[] = { # endif /* OPENSSL_HAS_NISTP521 */ # endif /* OPENSSL_HAS_ECC */ #endif /* WITH_OPENSSL */ + { "null", "null", NULL, KEY_NULL, 0, 0, 0 }, { NULL, NULL, NULL, -1, -1, 0, 0 } }; @@ -223,7 +224,7 @@ sshkey_alg_list(int certs_only, int plain_only, int include_sigonly, char sep) const struct keytype *kt; for (kt = keytypes; kt->type != -1; kt++) { - if (kt->name == NULL) + if (kt->name == NULL || kt->type == KEY_NULL) continue; if (!include_sigonly && kt->sigonly) continue; diff --git a/sshkey.h b/sshkey.h index a91e60436..c11106c93 100644 --- a/sshkey.h +++ b/sshkey.h @@ -65,6 +65,7 @@ enum sshkey_types { KEY_ED25519_CERT, KEY_XMSS, KEY_XMSS_CERT, + KEY_NULL, KEY_UNSPEC }; -- cgit v1.2.3 From 85e700a732e9a308eeee67f5a284e19fd6befbb8 Mon Sep 17 00:00:00 2001 From: Matthew Vernon Date: Sun, 9 Feb 2014 16:10:05 +0000 Subject: Include the Debian version in our identification This makes it easier to audit networks for versions patched against security vulnerabilities. It has little detrimental effect, as attackers will generally just try attacks rather than bothering to scan for vulnerable-looking version strings. (However, see debian-banner.patch.) Forwarded: not-needed Last-Update: 2019-06-05 Patch-Name: package-versioning.patch --- kex.c | 2 +- version.h | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'kex.c') diff --git a/kex.c b/kex.c index a2a4794e8..be354206d 100644 --- a/kex.c +++ b/kex.c @@ -1186,7 +1186,7 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms, if (version_addendum != NULL && *version_addendum == '\0') version_addendum = NULL; if ((r = sshbuf_putf(our_version, "SSH-%d.%d-%.100s%s%s\r\n", - PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_VERSION, + PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_RELEASE, version_addendum == NULL ? "" : " ", version_addendum == NULL ? "" : version_addendum)) != 0) { error("%s: sshbuf_putf: %s", __func__, ssh_err(r)); diff --git a/version.h b/version.h index 806ead9a6..599c859e6 100644 --- a/version.h +++ b/version.h @@ -3,4 +3,9 @@ #define SSH_VERSION "OpenSSH_8.0" #define SSH_PORTABLE "p1" -#define SSH_RELEASE SSH_VERSION SSH_PORTABLE +#define SSH_RELEASE_MINIMUM SSH_VERSION SSH_PORTABLE +#ifdef SSH_EXTRAVERSION +#define SSH_RELEASE SSH_RELEASE_MINIMUM " " SSH_EXTRAVERSION +#else +#define SSH_RELEASE SSH_RELEASE_MINIMUM +#endif -- cgit v1.2.3 From 085c44daefaee16df97e1b2a0967b2140cc86de0 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Sun, 9 Feb 2014 16:10:06 +0000 Subject: Add DebianBanner server configuration option Setting this to "no" causes sshd to omit the Debian revision from its initial protocol handshake, for those scared by package-versioning.patch. Bug-Debian: http://bugs.debian.org/562048 Forwarded: not-needed Last-Update: 2019-06-05 Patch-Name: debian-banner.patch --- kex.c | 5 +++-- kex.h | 2 +- servconf.c | 9 +++++++++ servconf.h | 2 ++ sshconnect.c | 2 +- sshd.c | 3 ++- sshd_config.5 | 5 +++++ 7 files changed, 23 insertions(+), 5 deletions(-) (limited to 'kex.c') diff --git a/kex.c b/kex.c index be354206d..bbb7a2340 100644 --- a/kex.c +++ b/kex.c @@ -1168,7 +1168,7 @@ send_error(struct ssh *ssh, char *msg) */ int kex_exchange_identification(struct ssh *ssh, int timeout_ms, - const char *version_addendum) + int debian_banner, const char *version_addendum) { int remote_major, remote_minor, mismatch; size_t len, i, n; @@ -1186,7 +1186,8 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms, if (version_addendum != NULL && *version_addendum == '\0') version_addendum = NULL; if ((r = sshbuf_putf(our_version, "SSH-%d.%d-%.100s%s%s\r\n", - PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_RELEASE, + PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, + debian_banner ? SSH_RELEASE : SSH_RELEASE_MINIMUM, version_addendum == NULL ? "" : " ", version_addendum == NULL ? "" : version_addendum)) != 0) { error("%s: sshbuf_putf: %s", __func__, ssh_err(r)); diff --git a/kex.h b/kex.h index 2d5f1d4ed..39f67bbc1 100644 --- a/kex.h +++ b/kex.h @@ -195,7 +195,7 @@ char *kex_names_cat(const char *, const char *); int kex_assemble_names(char **, const char *, const char *); int kex_gss_names_valid(const char *); -int kex_exchange_identification(struct ssh *, int, const char *); +int kex_exchange_identification(struct ssh *, int, int, const char *); struct kex *kex_new(void); int kex_ready(struct ssh *, char *[PROPOSAL_MAX]); diff --git a/servconf.c b/servconf.c index c01e0690e..8d2bced52 100644 --- a/servconf.c +++ b/servconf.c @@ -184,6 +184,7 @@ initialize_server_options(ServerOptions *options) options->fingerprint_hash = -1; options->disable_forwarding = -1; options->expose_userauth_info = -1; + options->debian_banner = -1; } /* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */ @@ -437,6 +438,8 @@ fill_default_server_options(ServerOptions *options) options->disable_forwarding = 0; if (options->expose_userauth_info == -1) options->expose_userauth_info = 0; + if (options->debian_banner == -1) + options->debian_banner = 1; assemble_algorithms(options); @@ -523,6 +526,7 @@ typedef enum { sStreamLocalBindMask, sStreamLocalBindUnlink, sAllowStreamLocalForwarding, sFingerprintHash, sDisableForwarding, sExposeAuthInfo, sRDomain, + sDebianBanner, sDeprecated, sIgnore, sUnsupported } ServerOpCodes; @@ -682,6 +686,7 @@ static struct { { "exposeauthinfo", sExposeAuthInfo, SSHCFG_ALL }, { "rdomain", sRDomain, SSHCFG_ALL }, { "casignaturealgorithms", sCASignatureAlgorithms, SSHCFG_ALL }, + { "debianbanner", sDebianBanner, SSHCFG_GLOBAL }, { NULL, sBadOption, 0 } }; @@ -2211,6 +2216,10 @@ process_server_config_line(ServerOptions *options, char *line, *charptr = xstrdup(arg); break; + case sDebianBanner: + intptr = &options->debian_banner; + goto parse_flag; + case sDeprecated: case sIgnore: case sUnsupported: diff --git a/servconf.h b/servconf.h index a476d5220..986093ffa 100644 --- a/servconf.h +++ b/servconf.h @@ -214,6 +214,8 @@ typedef struct { int fingerprint_hash; int expose_userauth_info; u_int64_t timing_secret; + + int debian_banner; } ServerOptions; /* Information about the incoming connection as used by Match */ diff --git a/sshconnect.c b/sshconnect.c index 0b6f6af4b..1183ffe0e 100644 --- a/sshconnect.c +++ b/sshconnect.c @@ -1287,7 +1287,7 @@ ssh_login(struct ssh *ssh, Sensitive *sensitive, const char *orighost, lowercase(host); /* Exchange protocol version identification strings with the server. */ - if (kex_exchange_identification(ssh, timeout_ms, NULL) != 0) + if (kex_exchange_identification(ssh, timeout_ms, 1, NULL) != 0) cleanup_exit(255); /* error already logged */ /* Put the connection into non-blocking mode. */ diff --git a/sshd.c b/sshd.c index e3e96426e..1e7ece588 100644 --- a/sshd.c +++ b/sshd.c @@ -2160,7 +2160,8 @@ main(int ac, char **av) if (!debug_flag) alarm(options.login_grace_time); - if (kex_exchange_identification(ssh, -1, options.version_addendum) != 0) + if (kex_exchange_identification(ssh, -1, options.debian_banner, + options.version_addendum) != 0) cleanup_exit(255); /* error already logged */ ssh_packet_set_nonblocking(ssh); diff --git a/sshd_config.5 b/sshd_config.5 index 2ef671d1b..addea54a0 100644 --- a/sshd_config.5 +++ b/sshd_config.5 @@ -543,6 +543,11 @@ or .Cm no . The default is .Cm yes . +.It Cm DebianBanner +Specifies whether the distribution-specified extra version suffix is +included during initial protocol handshake. +The default is +.Cm yes . .It Cm DenyGroups This keyword can be followed by a list of group name patterns, separated by spaces. -- cgit v1.2.3