diff options
author | Rene Mayrhofer <rene@mayrhofer.eu.org> | 2007-04-12 20:30:08 +0000 |
---|---|---|
committer | Rene Mayrhofer <rene@mayrhofer.eu.org> | 2007-04-12 20:30:08 +0000 |
commit | b0d8ed94fe9e74afb49fdf5f11e4add29879c65c (patch) | |
tree | b20167235628771046e940a82a906a6d0991ee4a /src/pluto | |
parent | ea939d07c84d2a8e51215458063fc05e9c399290 (diff) | |
download | vyos-strongswan-b0d8ed94fe9e74afb49fdf5f11e4add29879c65c.tar.gz vyos-strongswan-b0d8ed94fe9e74afb49fdf5f11e4add29879c65c.zip |
[svn-upgrade] Integrating new upstream version, strongswan (4.1.1)
Diffstat (limited to 'src/pluto')
134 files changed, 70586 insertions, 0 deletions
diff --git a/src/pluto/Makefile.am b/src/pluto/Makefile.am new file mode 100644 index 000000000..b1b848c76 --- /dev/null +++ b/src/pluto/Makefile.am @@ -0,0 +1,140 @@ +# Makefile.am was ported from the old Makefile the most +# painless way. Only the most important options are included, +# further work may be necessary here... + +ipsec_PROGRAMS = pluto _pluto_adns + +pluto_SOURCES = \ +ac.c ac.h \ +alg_info.c alg_info.h \ +asn1.c asn1.h \ +ca.c ca.h \ +certs.c certs.h \ +connections.c connections.h \ +constants.c constants.h \ +cookie.c cookie.h \ +crl.c crl.h \ +crypto.c crypto.h \ +db_ops.c db_ops.h \ +defs.c defs.h \ +demux.c demux.h \ +dnskey.c dnskey.h \ +dsa.c dsa.h \ +elgamal.c elgamal.h \ +fetch.c fetch.h \ +foodgroups.c foodgroups.h \ +gcryptfix.c gcryptfix.h \ +id.c id.h \ +ike_alg.c ike_alg.h \ +ipsec_doi.c ipsec_doi.h \ +kameipsec.h \ +kernel.c kernel.h \ +kernel_alg.c kernel_alg.h \ +kernel_netlink.c kernel_netlink.h \ +kernel_noklips.c kernel_noklips.h \ +kernel_pfkey.c kernel_pfkey.h \ +keys.c keys.h \ +lex.c lex.h \ +log.c log.h \ +md2.c md2.h \ +md5.c md5.h \ +modecfg.c modecfg.h \ +mp_defs.c mp_defs.h \ +nat_traversal.c nat_traversal.h \ +ocsp.c ocsp.h \ +oid.c oid.h \ +packet.c packet.h \ +pem.c pem.h \ +pgp.c pgp.h \ +pkcs1.c pkcs1.h \ +pkcs7.c pkcs7.h \ +plutomain.c \ +primegen.c smallprime.c \ +rcv_whack.c rcv_whack.h \ +rnd.c rnd.h \ +server.c server.h \ +sha1.c sha1.h \ +smartcard.c smartcard.h \ +spdb.c spdb.h \ +state.c state.h \ +timer.c timer.h \ +vendor.c vendor.h \ +virtual.c virtual.h \ +xauth.c xauth.h \ +x509.c x509.h \ +alg/ike_alg_aes.c alg/ike_alg_blowfish.c alg/ike_alg_twofish.c \ +alg/ike_alg_serpent.c alg/ike_alg_sha2.c alg/ike_alginit.c \ +linux26/netlink.h linux26/rtnetlink.h linux26/xfrm.h \ +rsaref/pkcs11t.h rsaref/pkcs11.h rsaref/unix.h rsaref/pkcs11f.h + +_pluto_adns_SOURCES = adns.c adns.h + +INCLUDES = \ +-I$(top_srcdir)/src/libfreeswan \ +-I$(top_srcdir)/src/libcrypto \ +-I$(top_srcdir)/src/whack + +AM_CFLAGS = \ +-DIPSEC_DIR=\"${ipsecdir}\" \ +-DIPSEC_CONFDIR=\"${confdir}\" \ +-DIPSEC_PIDDIR=\"${piddir}\" \ +-DSHARED_SECRETS_FILE=\"${confdir}/ipsec.secrets\" \ +-DKERNEL26_SUPPORT -DKERNEL26_HAS_KAME_DUPLICATES \ +-DPLUTO -DKLIPS -DDEBUG -DTHREADS + +pluto_LDADD = \ +$(top_srcdir)/src/libfreeswan/libfreeswan.a \ +$(top_srcdir)/src/libcrypto/libcrypto.a \ +-lgmp -lresolv -lpthread -ldl + +_pluto_adns_LDADD = \ +$(top_srcdir)/src/libfreeswan/libfreeswan.a \ +-lresolv -ldl + +dist_man_MANS = pluto.8 ipsec.secrets.5 +EXTRA_DIST = oid.pl oid.txt +BUILT_SOURCES = oid.c oid.h +MAINTAINERCLEANFILES = oid.c oid.h + +oid.c: oid.txt oid.pl + $(PERL) oid.pl + +oid.h: oid.txt oid.pl + $(PERL) oid.pl + +# This compile option activates the sending of a strongSwan VID +if USE_VENDORID + AM_CFLAGS += -DVENDORID +endif + +# This compile option activates the support of the Cisco VPN client +if USE_CISCO_QUIRKS + AM_CFLAGS += -DCISCO_QUIRKS +endif + +# This compile option activates NAT traversal with IPSec transport mode +if USE_NAT_TRANSPORT + AM_CFLAGS += -DI_KNOW_TRANSPORT_MODE_HAS_SECURITY_CONCERN_BUT_I_WANT_IT +endif + +# This compile option activates dynamic URL fetching using libcurl +if USE_LIBCURL + pluto_LDADD += -lcurl +endif + +# This compile option activates dynamic LDAP CRL fetching +if USE_LIBLDAP + pluto_LDADD += -lldap -llber +endif + +install-exec-local : + mkdir -p -m 755 $(confdir)/ipsec.d + mkdir -p -m 755 $(confdir)/ipsec.d/cacerts + mkdir -p -m 755 $(confdir)/ipsec.d/ocspcerts + mkdir -p -m 755 $(confdir)/ipsec.d/certs + mkdir -p -m 755 $(confdir)/ipsec.d/acerts + mkdir -p -m 755 $(confdir)/ipsec.d/aacerts + mkdir -p -m 755 $(confdir)/ipsec.d/crls + mkdir -p -m 755 $(confdir)/ipsec.d/reqs + mkdir -p -m 700 $(confdir)/ipsec.d/private + diff --git a/src/pluto/Makefile.in b/src/pluto/Makefile.in new file mode 100644 index 000000000..1f996a065 --- /dev/null +++ b/src/pluto/Makefile.in @@ -0,0 +1,878 @@ +# Makefile.in generated by automake 1.9.6 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005 Free Software Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# Makefile.am was ported from the old Makefile the most +# painless way. Only the most important options are included, +# further work may be necessary here... + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +top_builddir = ../.. +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +INSTALL = @INSTALL@ +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +ipsec_PROGRAMS = pluto$(EXEEXT) _pluto_adns$(EXEEXT) + +# This compile option activates the sending of a strongSwan VID +@USE_VENDORID_TRUE@am__append_1 = -DVENDORID + +# This compile option activates the support of the Cisco VPN client +@USE_CISCO_QUIRKS_TRUE@am__append_2 = -DCISCO_QUIRKS + +# This compile option activates NAT traversal with IPSec transport mode +@USE_NAT_TRANSPORT_TRUE@am__append_3 = -DI_KNOW_TRANSPORT_MODE_HAS_SECURITY_CONCERN_BUT_I_WANT_IT + +# This compile option activates dynamic URL fetching using libcurl +@USE_LIBCURL_TRUE@am__append_4 = -lcurl + +# This compile option activates dynamic LDAP CRL fetching +@USE_LIBLDAP_TRUE@am__append_5 = -lldap -llber +subdir = src/pluto +DIST_COMMON = $(dist_man_MANS) $(srcdir)/Makefile.am \ + $(srcdir)/Makefile.in TODO +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +am__installdirs = "$(DESTDIR)$(ipsecdir)" "$(DESTDIR)$(man5dir)" \ + "$(DESTDIR)$(man8dir)" +ipsecPROGRAMS_INSTALL = $(INSTALL_PROGRAM) +PROGRAMS = $(ipsec_PROGRAMS) +am__pluto_adns_OBJECTS = adns.$(OBJEXT) +_pluto_adns_OBJECTS = $(am__pluto_adns_OBJECTS) +_pluto_adns_DEPENDENCIES = \ + $(top_srcdir)/src/libfreeswan/libfreeswan.a +am_pluto_OBJECTS = ac.$(OBJEXT) alg_info.$(OBJEXT) asn1.$(OBJEXT) \ + ca.$(OBJEXT) certs.$(OBJEXT) connections.$(OBJEXT) \ + constants.$(OBJEXT) cookie.$(OBJEXT) crl.$(OBJEXT) \ + crypto.$(OBJEXT) db_ops.$(OBJEXT) defs.$(OBJEXT) \ + demux.$(OBJEXT) dnskey.$(OBJEXT) dsa.$(OBJEXT) \ + elgamal.$(OBJEXT) fetch.$(OBJEXT) foodgroups.$(OBJEXT) \ + gcryptfix.$(OBJEXT) id.$(OBJEXT) ike_alg.$(OBJEXT) \ + ipsec_doi.$(OBJEXT) kernel.$(OBJEXT) kernel_alg.$(OBJEXT) \ + kernel_netlink.$(OBJEXT) kernel_noklips.$(OBJEXT) \ + kernel_pfkey.$(OBJEXT) keys.$(OBJEXT) lex.$(OBJEXT) \ + log.$(OBJEXT) md2.$(OBJEXT) md5.$(OBJEXT) modecfg.$(OBJEXT) \ + mp_defs.$(OBJEXT) nat_traversal.$(OBJEXT) ocsp.$(OBJEXT) \ + oid.$(OBJEXT) packet.$(OBJEXT) pem.$(OBJEXT) pgp.$(OBJEXT) \ + pkcs1.$(OBJEXT) pkcs7.$(OBJEXT) plutomain.$(OBJEXT) \ + primegen.$(OBJEXT) smallprime.$(OBJEXT) rcv_whack.$(OBJEXT) \ + rnd.$(OBJEXT) server.$(OBJEXT) sha1.$(OBJEXT) \ + smartcard.$(OBJEXT) spdb.$(OBJEXT) state.$(OBJEXT) \ + timer.$(OBJEXT) vendor.$(OBJEXT) virtual.$(OBJEXT) \ + xauth.$(OBJEXT) x509.$(OBJEXT) ike_alg_aes.$(OBJEXT) \ + ike_alg_blowfish.$(OBJEXT) ike_alg_twofish.$(OBJEXT) \ + ike_alg_serpent.$(OBJEXT) ike_alg_sha2.$(OBJEXT) \ + ike_alginit.$(OBJEXT) +pluto_OBJECTS = $(am_pluto_OBJECTS) +am__DEPENDENCIES_1 = +pluto_DEPENDENCIES = $(top_srcdir)/src/libfreeswan/libfreeswan.a \ + $(top_srcdir)/src/libcrypto/libcrypto.a $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +DEFAULT_INCLUDES = -I. -I$(srcdir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(LIBTOOL) --tag=CC --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +SOURCES = $(_pluto_adns_SOURCES) $(pluto_SOURCES) +DIST_SOURCES = $(_pluto_adns_SOURCES) $(pluto_SOURCES) +man5dir = $(mandir)/man5 +man8dir = $(mandir)/man8 +NROFF = nroff +MANS = $(dist_man_MANS) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMDEP_FALSE = @AMDEP_FALSE@ +AMDEP_TRUE = @AMDEP_TRUE@ +AMTAR = @AMTAR@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BUILD_EAP_SIM_FALSE = @BUILD_EAP_SIM_FALSE@ +BUILD_EAP_SIM_TRUE = @BUILD_EAP_SIM_TRUE@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO = @ECHO@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +F77 = @F77@ +FFLAGS = @FFLAGS@ +GPERF = @GPERF@ +GREP = @GREP@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LEX = @LEX@ +LEXLIB = @LEXLIB@ +LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +USE_CISCO_QUIRKS_FALSE = @USE_CISCO_QUIRKS_FALSE@ +USE_CISCO_QUIRKS_TRUE = @USE_CISCO_QUIRKS_TRUE@ +USE_LEAK_DETECTIVE_FALSE = @USE_LEAK_DETECTIVE_FALSE@ +USE_LEAK_DETECTIVE_TRUE = @USE_LEAK_DETECTIVE_TRUE@ +USE_LIBCURL_FALSE = @USE_LIBCURL_FALSE@ +USE_LIBCURL_TRUE = @USE_LIBCURL_TRUE@ +USE_LIBLDAP_FALSE = @USE_LIBLDAP_FALSE@ +USE_LIBLDAP_TRUE = @USE_LIBLDAP_TRUE@ +USE_NAT_TRANSPORT_FALSE = @USE_NAT_TRANSPORT_FALSE@ +USE_NAT_TRANSPORT_TRUE = @USE_NAT_TRANSPORT_TRUE@ +USE_SMARTCARD_FALSE = @USE_SMARTCARD_FALSE@ +USE_SMARTCARD_TRUE = @USE_SMARTCARD_TRUE@ +USE_VENDORID_FALSE = @USE_VENDORID_FALSE@ +USE_VENDORID_TRUE = @USE_VENDORID_TRUE@ +VERSION = @VERSION@ +YACC = @YACC@ +YFLAGS = @YFLAGS@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_F77 = @ac_ct_F77@ +am__fastdepCC_FALSE = @am__fastdepCC_FALSE@ +am__fastdepCC_TRUE = @am__fastdepCC_TRUE@ +am__fastdepCXX_FALSE = @am__fastdepCXX_FALSE@ +am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +confdir = @confdir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +eapdir = @eapdir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +ipsecdir = @ipsecdir@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +piddir = @piddir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +pluto_SOURCES = \ +ac.c ac.h \ +alg_info.c alg_info.h \ +asn1.c asn1.h \ +ca.c ca.h \ +certs.c certs.h \ +connections.c connections.h \ +constants.c constants.h \ +cookie.c cookie.h \ +crl.c crl.h \ +crypto.c crypto.h \ +db_ops.c db_ops.h \ +defs.c defs.h \ +demux.c demux.h \ +dnskey.c dnskey.h \ +dsa.c dsa.h \ +elgamal.c elgamal.h \ +fetch.c fetch.h \ +foodgroups.c foodgroups.h \ +gcryptfix.c gcryptfix.h \ +id.c id.h \ +ike_alg.c ike_alg.h \ +ipsec_doi.c ipsec_doi.h \ +kameipsec.h \ +kernel.c kernel.h \ +kernel_alg.c kernel_alg.h \ +kernel_netlink.c kernel_netlink.h \ +kernel_noklips.c kernel_noklips.h \ +kernel_pfkey.c kernel_pfkey.h \ +keys.c keys.h \ +lex.c lex.h \ +log.c log.h \ +md2.c md2.h \ +md5.c md5.h \ +modecfg.c modecfg.h \ +mp_defs.c mp_defs.h \ +nat_traversal.c nat_traversal.h \ +ocsp.c ocsp.h \ +oid.c oid.h \ +packet.c packet.h \ +pem.c pem.h \ +pgp.c pgp.h \ +pkcs1.c pkcs1.h \ +pkcs7.c pkcs7.h \ +plutomain.c \ +primegen.c smallprime.c \ +rcv_whack.c rcv_whack.h \ +rnd.c rnd.h \ +server.c server.h \ +sha1.c sha1.h \ +smartcard.c smartcard.h \ +spdb.c spdb.h \ +state.c state.h \ +timer.c timer.h \ +vendor.c vendor.h \ +virtual.c virtual.h \ +xauth.c xauth.h \ +x509.c x509.h \ +alg/ike_alg_aes.c alg/ike_alg_blowfish.c alg/ike_alg_twofish.c \ +alg/ike_alg_serpent.c alg/ike_alg_sha2.c alg/ike_alginit.c \ +linux26/netlink.h linux26/rtnetlink.h linux26/xfrm.h \ +rsaref/pkcs11t.h rsaref/pkcs11.h rsaref/unix.h rsaref/pkcs11f.h + +_pluto_adns_SOURCES = adns.c adns.h +INCLUDES = \ +-I$(top_srcdir)/src/libfreeswan \ +-I$(top_srcdir)/src/libcrypto \ +-I$(top_srcdir)/src/whack + +AM_CFLAGS = -DIPSEC_DIR=\"${ipsecdir}\" -DIPSEC_CONFDIR=\"${confdir}\" \ + -DIPSEC_PIDDIR=\"${piddir}\" \ + -DSHARED_SECRETS_FILE=\"${confdir}/ipsec.secrets\" \ + -DKERNEL26_SUPPORT -DKERNEL26_HAS_KAME_DUPLICATES -DPLUTO \ + -DKLIPS -DDEBUG -DTHREADS $(am__append_1) $(am__append_2) \ + $(am__append_3) +pluto_LDADD = $(top_srcdir)/src/libfreeswan/libfreeswan.a \ + $(top_srcdir)/src/libcrypto/libcrypto.a -lgmp -lresolv \ + -lpthread -ldl $(am__append_4) $(am__append_5) +_pluto_adns_LDADD = \ +$(top_srcdir)/src/libfreeswan/libfreeswan.a \ +-lresolv -ldl + +dist_man_MANS = pluto.8 ipsec.secrets.5 +EXTRA_DIST = oid.pl oid.txt +BUILT_SOURCES = oid.c oid.h +MAINTAINERCLEANFILES = oid.c oid.h +all: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/pluto/Makefile'; \ + cd $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/pluto/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +install-ipsecPROGRAMS: $(ipsec_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(ipsecdir)" || $(mkdir_p) "$(DESTDIR)$(ipsecdir)" + @list='$(ipsec_PROGRAMS)'; for p in $$list; do \ + p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + if test -f $$p \ + || test -f $$p1 \ + ; then \ + f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(ipsecPROGRAMS_INSTALL) '$$p' '$(DESTDIR)$(ipsecdir)/$$f'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(ipsecPROGRAMS_INSTALL) "$$p" "$(DESTDIR)$(ipsecdir)/$$f" || exit 1; \ + else :; fi; \ + done + +uninstall-ipsecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(ipsec_PROGRAMS)'; for p in $$list; do \ + f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " rm -f '$(DESTDIR)$(ipsecdir)/$$f'"; \ + rm -f "$(DESTDIR)$(ipsecdir)/$$f"; \ + done + +clean-ipsecPROGRAMS: + @list='$(ipsec_PROGRAMS)'; for p in $$list; do \ + f=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f $$p $$f"; \ + rm -f $$p $$f ; \ + done +_pluto_adns$(EXEEXT): $(_pluto_adns_OBJECTS) $(_pluto_adns_DEPENDENCIES) + @rm -f _pluto_adns$(EXEEXT) + $(LINK) $(_pluto_adns_LDFLAGS) $(_pluto_adns_OBJECTS) $(_pluto_adns_LDADD) $(LIBS) +pluto$(EXEEXT): $(pluto_OBJECTS) $(pluto_DEPENDENCIES) + @rm -f pluto$(EXEEXT) + $(LINK) $(pluto_LDFLAGS) $(pluto_OBJECTS) $(pluto_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ac.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/adns.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/alg_info.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/asn1.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ca.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/certs.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/connections.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/constants.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cookie.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crl.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crypto.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db_ops.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/defs.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/demux.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dnskey.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsa.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/elgamal.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fetch.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/foodgroups.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gcryptfix.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/id.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ike_alg.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ike_alg_aes.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ike_alg_blowfish.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ike_alg_serpent.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ike_alg_sha2.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ike_alg_twofish.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ike_alginit.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipsec_doi.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/kernel.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/kernel_alg.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/kernel_netlink.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/kernel_noklips.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/kernel_pfkey.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keys.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lex.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/log.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md2.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/modecfg.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mp_defs.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nat_traversal.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ocsp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/oid.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/packet.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pem.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pgp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pkcs1.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pkcs7.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/plutomain.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/primegen.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rcv_whack.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rnd.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sha1.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smallprime.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smartcard.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/spdb.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/state.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/timer.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/vendor.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/x509.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xauth.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ `$(CYGPATH_W) '$<'`; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ if $(LTCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Plo"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $< + +ike_alg_aes.o: alg/ike_alg_aes.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ike_alg_aes.o -MD -MP -MF "$(DEPDIR)/ike_alg_aes.Tpo" -c -o ike_alg_aes.o `test -f 'alg/ike_alg_aes.c' || echo '$(srcdir)/'`alg/ike_alg_aes.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ike_alg_aes.Tpo" "$(DEPDIR)/ike_alg_aes.Po"; else rm -f "$(DEPDIR)/ike_alg_aes.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='alg/ike_alg_aes.c' object='ike_alg_aes.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ike_alg_aes.o `test -f 'alg/ike_alg_aes.c' || echo '$(srcdir)/'`alg/ike_alg_aes.c + +ike_alg_aes.obj: alg/ike_alg_aes.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ike_alg_aes.obj -MD -MP -MF "$(DEPDIR)/ike_alg_aes.Tpo" -c -o ike_alg_aes.obj `if test -f 'alg/ike_alg_aes.c'; then $(CYGPATH_W) 'alg/ike_alg_aes.c'; else $(CYGPATH_W) '$(srcdir)/alg/ike_alg_aes.c'; fi`; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ike_alg_aes.Tpo" "$(DEPDIR)/ike_alg_aes.Po"; else rm -f "$(DEPDIR)/ike_alg_aes.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='alg/ike_alg_aes.c' object='ike_alg_aes.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ike_alg_aes.obj `if test -f 'alg/ike_alg_aes.c'; then $(CYGPATH_W) 'alg/ike_alg_aes.c'; else $(CYGPATH_W) '$(srcdir)/alg/ike_alg_aes.c'; fi` + +ike_alg_blowfish.o: alg/ike_alg_blowfish.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ike_alg_blowfish.o -MD -MP -MF "$(DEPDIR)/ike_alg_blowfish.Tpo" -c -o ike_alg_blowfish.o `test -f 'alg/ike_alg_blowfish.c' || echo '$(srcdir)/'`alg/ike_alg_blowfish.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ike_alg_blowfish.Tpo" "$(DEPDIR)/ike_alg_blowfish.Po"; else rm -f "$(DEPDIR)/ike_alg_blowfish.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='alg/ike_alg_blowfish.c' object='ike_alg_blowfish.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ike_alg_blowfish.o `test -f 'alg/ike_alg_blowfish.c' || echo '$(srcdir)/'`alg/ike_alg_blowfish.c + +ike_alg_blowfish.obj: alg/ike_alg_blowfish.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ike_alg_blowfish.obj -MD -MP -MF "$(DEPDIR)/ike_alg_blowfish.Tpo" -c -o ike_alg_blowfish.obj `if test -f 'alg/ike_alg_blowfish.c'; then $(CYGPATH_W) 'alg/ike_alg_blowfish.c'; else $(CYGPATH_W) '$(srcdir)/alg/ike_alg_blowfish.c'; fi`; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ike_alg_blowfish.Tpo" "$(DEPDIR)/ike_alg_blowfish.Po"; else rm -f "$(DEPDIR)/ike_alg_blowfish.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='alg/ike_alg_blowfish.c' object='ike_alg_blowfish.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ike_alg_blowfish.obj `if test -f 'alg/ike_alg_blowfish.c'; then $(CYGPATH_W) 'alg/ike_alg_blowfish.c'; else $(CYGPATH_W) '$(srcdir)/alg/ike_alg_blowfish.c'; fi` + +ike_alg_twofish.o: alg/ike_alg_twofish.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ike_alg_twofish.o -MD -MP -MF "$(DEPDIR)/ike_alg_twofish.Tpo" -c -o ike_alg_twofish.o `test -f 'alg/ike_alg_twofish.c' || echo '$(srcdir)/'`alg/ike_alg_twofish.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ike_alg_twofish.Tpo" "$(DEPDIR)/ike_alg_twofish.Po"; else rm -f "$(DEPDIR)/ike_alg_twofish.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='alg/ike_alg_twofish.c' object='ike_alg_twofish.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ike_alg_twofish.o `test -f 'alg/ike_alg_twofish.c' || echo '$(srcdir)/'`alg/ike_alg_twofish.c + +ike_alg_twofish.obj: alg/ike_alg_twofish.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ike_alg_twofish.obj -MD -MP -MF "$(DEPDIR)/ike_alg_twofish.Tpo" -c -o ike_alg_twofish.obj `if test -f 'alg/ike_alg_twofish.c'; then $(CYGPATH_W) 'alg/ike_alg_twofish.c'; else $(CYGPATH_W) '$(srcdir)/alg/ike_alg_twofish.c'; fi`; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ike_alg_twofish.Tpo" "$(DEPDIR)/ike_alg_twofish.Po"; else rm -f "$(DEPDIR)/ike_alg_twofish.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='alg/ike_alg_twofish.c' object='ike_alg_twofish.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ike_alg_twofish.obj `if test -f 'alg/ike_alg_twofish.c'; then $(CYGPATH_W) 'alg/ike_alg_twofish.c'; else $(CYGPATH_W) '$(srcdir)/alg/ike_alg_twofish.c'; fi` + +ike_alg_serpent.o: alg/ike_alg_serpent.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ike_alg_serpent.o -MD -MP -MF "$(DEPDIR)/ike_alg_serpent.Tpo" -c -o ike_alg_serpent.o `test -f 'alg/ike_alg_serpent.c' || echo '$(srcdir)/'`alg/ike_alg_serpent.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ike_alg_serpent.Tpo" "$(DEPDIR)/ike_alg_serpent.Po"; else rm -f "$(DEPDIR)/ike_alg_serpent.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='alg/ike_alg_serpent.c' object='ike_alg_serpent.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ike_alg_serpent.o `test -f 'alg/ike_alg_serpent.c' || echo '$(srcdir)/'`alg/ike_alg_serpent.c + +ike_alg_serpent.obj: alg/ike_alg_serpent.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ike_alg_serpent.obj -MD -MP -MF "$(DEPDIR)/ike_alg_serpent.Tpo" -c -o ike_alg_serpent.obj `if test -f 'alg/ike_alg_serpent.c'; then $(CYGPATH_W) 'alg/ike_alg_serpent.c'; else $(CYGPATH_W) '$(srcdir)/alg/ike_alg_serpent.c'; fi`; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ike_alg_serpent.Tpo" "$(DEPDIR)/ike_alg_serpent.Po"; else rm -f "$(DEPDIR)/ike_alg_serpent.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='alg/ike_alg_serpent.c' object='ike_alg_serpent.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ike_alg_serpent.obj `if test -f 'alg/ike_alg_serpent.c'; then $(CYGPATH_W) 'alg/ike_alg_serpent.c'; else $(CYGPATH_W) '$(srcdir)/alg/ike_alg_serpent.c'; fi` + +ike_alg_sha2.o: alg/ike_alg_sha2.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ike_alg_sha2.o -MD -MP -MF "$(DEPDIR)/ike_alg_sha2.Tpo" -c -o ike_alg_sha2.o `test -f 'alg/ike_alg_sha2.c' || echo '$(srcdir)/'`alg/ike_alg_sha2.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ike_alg_sha2.Tpo" "$(DEPDIR)/ike_alg_sha2.Po"; else rm -f "$(DEPDIR)/ike_alg_sha2.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='alg/ike_alg_sha2.c' object='ike_alg_sha2.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ike_alg_sha2.o `test -f 'alg/ike_alg_sha2.c' || echo '$(srcdir)/'`alg/ike_alg_sha2.c + +ike_alg_sha2.obj: alg/ike_alg_sha2.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ike_alg_sha2.obj -MD -MP -MF "$(DEPDIR)/ike_alg_sha2.Tpo" -c -o ike_alg_sha2.obj `if test -f 'alg/ike_alg_sha2.c'; then $(CYGPATH_W) 'alg/ike_alg_sha2.c'; else $(CYGPATH_W) '$(srcdir)/alg/ike_alg_sha2.c'; fi`; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ike_alg_sha2.Tpo" "$(DEPDIR)/ike_alg_sha2.Po"; else rm -f "$(DEPDIR)/ike_alg_sha2.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='alg/ike_alg_sha2.c' object='ike_alg_sha2.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ike_alg_sha2.obj `if test -f 'alg/ike_alg_sha2.c'; then $(CYGPATH_W) 'alg/ike_alg_sha2.c'; else $(CYGPATH_W) '$(srcdir)/alg/ike_alg_sha2.c'; fi` + +ike_alginit.o: alg/ike_alginit.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ike_alginit.o -MD -MP -MF "$(DEPDIR)/ike_alginit.Tpo" -c -o ike_alginit.o `test -f 'alg/ike_alginit.c' || echo '$(srcdir)/'`alg/ike_alginit.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ike_alginit.Tpo" "$(DEPDIR)/ike_alginit.Po"; else rm -f "$(DEPDIR)/ike_alginit.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='alg/ike_alginit.c' object='ike_alginit.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ike_alginit.o `test -f 'alg/ike_alginit.c' || echo '$(srcdir)/'`alg/ike_alginit.c + +ike_alginit.obj: alg/ike_alginit.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ike_alginit.obj -MD -MP -MF "$(DEPDIR)/ike_alginit.Tpo" -c -o ike_alginit.obj `if test -f 'alg/ike_alginit.c'; then $(CYGPATH_W) 'alg/ike_alginit.c'; else $(CYGPATH_W) '$(srcdir)/alg/ike_alginit.c'; fi`; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ike_alginit.Tpo" "$(DEPDIR)/ike_alginit.Po"; else rm -f "$(DEPDIR)/ike_alginit.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='alg/ike_alginit.c' object='ike_alginit.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ike_alginit.obj `if test -f 'alg/ike_alginit.c'; then $(CYGPATH_W) 'alg/ike_alginit.c'; else $(CYGPATH_W) '$(srcdir)/alg/ike_alginit.c'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +distclean-libtool: + -rm -f libtool +uninstall-info-am: +install-man5: $(man5_MANS) $(man_MANS) + @$(NORMAL_INSTALL) + test -z "$(man5dir)" || $(mkdir_p) "$(DESTDIR)$(man5dir)" + @list='$(man5_MANS) $(dist_man5_MANS) $(nodist_man5_MANS)'; \ + l2='$(man_MANS) $(dist_man_MANS) $(nodist_man_MANS)'; \ + for i in $$l2; do \ + case "$$i" in \ + *.5*) list="$$list $$i" ;; \ + esac; \ + done; \ + for i in $$list; do \ + if test -f $(srcdir)/$$i; then file=$(srcdir)/$$i; \ + else file=$$i; fi; \ + ext=`echo $$i | sed -e 's/^.*\\.//'`; \ + case "$$ext" in \ + 5*) ;; \ + *) ext='5' ;; \ + esac; \ + inst=`echo $$i | sed -e 's/\\.[0-9a-z]*$$//'`; \ + inst=`echo $$inst | sed -e 's/^.*\///'`; \ + inst=`echo $$inst | sed '$(transform)'`.$$ext; \ + echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man5dir)/$$inst'"; \ + $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man5dir)/$$inst"; \ + done +uninstall-man5: + @$(NORMAL_UNINSTALL) + @list='$(man5_MANS) $(dist_man5_MANS) $(nodist_man5_MANS)'; \ + l2='$(man_MANS) $(dist_man_MANS) $(nodist_man_MANS)'; \ + for i in $$l2; do \ + case "$$i" in \ + *.5*) list="$$list $$i" ;; \ + esac; \ + done; \ + for i in $$list; do \ + ext=`echo $$i | sed -e 's/^.*\\.//'`; \ + case "$$ext" in \ + 5*) ;; \ + *) ext='5' ;; \ + esac; \ + inst=`echo $$i | sed -e 's/\\.[0-9a-z]*$$//'`; \ + inst=`echo $$inst | sed -e 's/^.*\///'`; \ + inst=`echo $$inst | sed '$(transform)'`.$$ext; \ + echo " rm -f '$(DESTDIR)$(man5dir)/$$inst'"; \ + rm -f "$(DESTDIR)$(man5dir)/$$inst"; \ + done +install-man8: $(man8_MANS) $(man_MANS) + @$(NORMAL_INSTALL) + test -z "$(man8dir)" || $(mkdir_p) "$(DESTDIR)$(man8dir)" + @list='$(man8_MANS) $(dist_man8_MANS) $(nodist_man8_MANS)'; \ + l2='$(man_MANS) $(dist_man_MANS) $(nodist_man_MANS)'; \ + for i in $$l2; do \ + case "$$i" in \ + *.8*) list="$$list $$i" ;; \ + esac; \ + done; \ + for i in $$list; do \ + if test -f $(srcdir)/$$i; then file=$(srcdir)/$$i; \ + else file=$$i; fi; \ + ext=`echo $$i | sed -e 's/^.*\\.//'`; \ + case "$$ext" in \ + 8*) ;; \ + *) ext='8' ;; \ + esac; \ + inst=`echo $$i | sed -e 's/\\.[0-9a-z]*$$//'`; \ + inst=`echo $$inst | sed -e 's/^.*\///'`; \ + inst=`echo $$inst | sed '$(transform)'`.$$ext; \ + echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man8dir)/$$inst'"; \ + $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man8dir)/$$inst"; \ + done +uninstall-man8: + @$(NORMAL_UNINSTALL) + @list='$(man8_MANS) $(dist_man8_MANS) $(nodist_man8_MANS)'; \ + l2='$(man_MANS) $(dist_man_MANS) $(nodist_man_MANS)'; \ + for i in $$l2; do \ + case "$$i" in \ + *.8*) list="$$list $$i" ;; \ + esac; \ + done; \ + for i in $$list; do \ + ext=`echo $$i | sed -e 's/^.*\\.//'`; \ + case "$$ext" in \ + 8*) ;; \ + *) ext='8' ;; \ + esac; \ + inst=`echo $$i | sed -e 's/\\.[0-9a-z]*$$//'`; \ + inst=`echo $$inst | sed -e 's/^.*\///'`; \ + inst=`echo $$inst | sed '$(transform)'`.$$ext; \ + echo " rm -f '$(DESTDIR)$(man8dir)/$$inst'"; \ + rm -f "$(DESTDIR)$(man8dir)/$$inst"; \ + done + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + test -z "$(CTAGS_ARGS)$$tags$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$tags $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && cd $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) $$here + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \ + list='$(DISTFILES)'; for file in $$list; do \ + case $$file in \ + $(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \ + $(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \ + esac; \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test "$$dir" != "$$file" && test "$$dir" != "."; then \ + dir="/$$dir"; \ + $(mkdir_p) "$(distdir)$$dir"; \ + else \ + dir=''; \ + fi; \ + if test -d $$d/$$file; then \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ + fi; \ + cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ + else \ + test -f $(distdir)/$$file \ + || cp -p $$d/$$file $(distdir)/$$file \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-am +all-am: Makefile $(PROGRAMS) $(MANS) +installdirs: + for dir in "$(DESTDIR)$(ipsecdir)" "$(DESTDIR)$(man5dir)" "$(DESTDIR)$(man8dir)"; do \ + test -z "$$dir" || $(mkdir_p) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) + -test -z "$(MAINTAINERCLEANFILES)" || rm -f $(MAINTAINERCLEANFILES) +clean: clean-am + +clean-am: clean-generic clean-ipsecPROGRAMS clean-libtool \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-libtool distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +info: info-am + +info-am: + +install-data-am: install-ipsecPROGRAMS install-man + +install-exec-am: install-exec-local + +install-info: install-info-am + +install-man: install-man5 install-man8 + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-info-am uninstall-ipsecPROGRAMS uninstall-man + +uninstall-man: uninstall-man5 uninstall-man8 + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-ipsecPROGRAMS clean-libtool ctags distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-exec \ + install-exec-am install-exec-local install-info \ + install-info-am install-ipsecPROGRAMS install-man install-man5 \ + install-man8 install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags uninstall \ + uninstall-am uninstall-info-am uninstall-ipsecPROGRAMS \ + uninstall-man uninstall-man5 uninstall-man8 + + +oid.c: oid.txt oid.pl + $(PERL) oid.pl + +oid.h: oid.txt oid.pl + $(PERL) oid.pl + +install-exec-local : + mkdir -p -m 755 $(confdir)/ipsec.d + mkdir -p -m 755 $(confdir)/ipsec.d/cacerts + mkdir -p -m 755 $(confdir)/ipsec.d/ocspcerts + mkdir -p -m 755 $(confdir)/ipsec.d/certs + mkdir -p -m 755 $(confdir)/ipsec.d/acerts + mkdir -p -m 755 $(confdir)/ipsec.d/aacerts + mkdir -p -m 755 $(confdir)/ipsec.d/crls + mkdir -p -m 755 $(confdir)/ipsec.d/reqs + mkdir -p -m 700 $(confdir)/ipsec.d/private +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/pluto/TODO b/src/pluto/TODO new file mode 100644 index 000000000..7db4a9ebc --- /dev/null +++ b/src/pluto/TODO @@ -0,0 +1,129 @@ +Pluto TODO list +=============== +RCSID $Id: TODO,v 1.1 2004/03/15 20:35:28 as Exp $ + +- should all log entries that are for errors say ERROR? + +- Add a "plug-in" facility so that others can add features without + changing the mainline code. This is how X509/LDAP/biometric stuff + might be added. + +- (internal change only) routines for outputting payloads should plug + "np" into the previous payload so that a payload generating routine + need not know what the next payload will be. This may be more bother + than it is worth. + +- notifications, in and out + + delete + + first contact + + last contact? (not part of drafts, but would be nice) + +- Make DNS usage for asynchronous (non-blocking) + + looking up KEY and TXT records during negotiation + + perhaps not for whack command arguments and ipsec.secrets since the + library code uses gethostbyname + +- check that ipsec auto and whack to agree on what is worth reporting + +- Should Pluto (rather than ipsec manual) install %passthrough conns? + That way Pluto would know of them. + +- For responding to Road Warriors, how can we decide if the RW has + gone away? The rekeying event is perhaps too imprecise. Even if + rekeying event is good enough, how do we know if the route should be + torn down? Perhaps limiting a Phase 1 ID to one IP address would + help (limiting a client subnet to one peer already helps). Perhaps + (in some rate-limited way) we can take an ICMP host unreachable + as a hint to do some authenticated and reliable probe. + +- it is annoying that Pluto and auto have different models for public keys. + + auto specifies one per connection + + Pluto allows one to be specified per id + Two connections with the same id are going to use the same key: + the one of the last conn to be added! + + I think auto ought to be fixed. It is hard for Pluto to warn when + there is a conflict since the deletion of a connection doesn't + prompt auto to tell pluto to delete the public key. + +- different connections with the same host IP addresses are randomly + interchangeable until the ID payload is received. At least for the + Responder case (and eventually for the opportunistic Initiator). + Worse, all Road Warriors must be considered to have the + indistinguishable IP addresses. This affects ISAKMP SA negotiation. + Currently, there is little flexibility in this negotiation, so the + problem is limited to the specification of acceptable authentication + method(s). Correct, but more work than seems worthwhile, would be + to select the conn based on what is proposed. + + Warning about such confusion at connection definition time isn't great + because there is no confusion when explicitly initiated (a particular + conn is specified). Warning for a Road Warrior conn is possible + since it cannot be initiated (and has been implemented). + +- characterize and ameliorate DOS attacks. Lots of rate limiting. + +- look at John Denker's wish list: http://www.quintillion.com/moat/wish.list + +- use of random numbers needs to be audited. + +- unknown (not just unimplemented) transforms cause a negotiation to + fail. Only the transform should be rejected. + +- we need better policy control. Our present flags need to be + modulated (forbid, allow, offer, require) + +- HS will specify how --copyright and --version should behave + +- HS will initiate project-wide terminology replacing ISAKMP SA, IPSEC + SA, Protection Suite, Phase 1, Main Mode, Phase 2, Quick Mode, ... + Simplicity and clarity will be a goal. + +- interface discovery ought to match what is specified in ipsec.conf. + This probably means grokking /proc/net/ipsec_tncfg. Documented in + ipsec_tncfg(5). This won't do for Hugh's debugging setup. + + +Protocol Issues +=============== + +Notification and delete payloads seem to be "escape hatches" for the +protocols. As such, anything implemented using them seems to be +kludged without being well designed or well situated or well +constrained in the protocols. Often the precise meaning (if any) or +usage is under specified. An implementation is allowed to ignore +them, so they cannot really matter (but they too often do). Their +specification ought to be scrutinized by a protocol guru. + +Any extra payload in last main mode message is not protected (not +authenticated by hash). + +Should notification payloads be interpreted before or after the normal +payloads (i.e. understood in the context of, executed in the context of). + +What is the precise result of an INITIAL_CONNECTION? What is a +"system" (eg. does Phase 1 Identity count)? What is "earlier" or +"before" (simultaneous negotiation is possible, with time being only a +partial order)? Could it be used for FINAL_CONTACT (needed too)? + +Blasting out a pile of UDP messages, especially to a particular +destination, is likely to provoke message loss. The exchanges are +just that, so they individually are self-throttling. But what about +multiple exchanges simultaneously? What about notifications (example: +when shutting down, a flurry of delete notifications are likely). +Should the RFCs be designed to protect against this problem? + +draft-jenkins-ipsec-rekeying-03.txt rekeying is way too complicated. +Our solution looks sound and simple (we have the Responder install the +incoming IPSEC SA before sending its first reply). In "2.2.1.4 +Responder Pre-Set-up Security Hole", the draft claims that setting up +the IPSEC SA early leaves the Responder open to replay attacks. I +think that this is wrong: the Message Id, since it must not be reused, +serves to prove that this isn't a replay. + +The details for notification messages suggested by +draft-ietf-ipsec-notifymsg-02.txt are over-complicated, just to make +them machine-comprehensible. I think this is over-engineering, +justified only if another level of negotiation is contemplated (ugh!). +Plain text is probably sufficient for informing humans (I admit that +there is a problem with I18N). diff --git a/src/pluto/ac.c b/src/pluto/ac.c new file mode 100644 index 000000000..bcf5f80d1 --- /dev/null +++ b/src/pluto/ac.c @@ -0,0 +1,1018 @@ +/* Support of X.509 attribute certificates + * Copyright (C) 2002 Ueli Galizzi, Ariane Seiler + * Copyright (C) 2003 Martin Berner, Lukas Suter + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: ac.c,v 1.12 2005/12/06 22:49:32 as Exp $ + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <dirent.h> +#include <time.h> +#include <sys/types.h> + +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "asn1.h" +#include "oid.h" +#include "ac.h" +#include "x509.h" +#include "crl.h" +#include "ca.h" +#include "certs.h" +#include "log.h" +#include "whack.h" +#include "fetch.h" + +/* chained list of X.509 attribute certificates */ + +static x509acert_t *x509acerts = NULL; + +/* chained list of ietfAttributes */ + +static ietfAttrList_t *ietfAttributes = NULL; + +/* ASN.1 definition of ietfAttrSyntax */ + +static const asn1Object_t ietfAttrSyntaxObjects[] = +{ + { 0, "ietfAttrSyntax", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */ + { 1, "policyAuthority", ASN1_CONTEXT_C_0, ASN1_OPT | + ASN1_BODY }, /* 1 */ + { 1, "end opt", ASN1_EOC, ASN1_END }, /* 2 */ + { 1, "values", ASN1_SEQUENCE, ASN1_LOOP }, /* 3 */ + { 2, "octets", ASN1_OCTET_STRING, ASN1_OPT | + ASN1_BODY }, /* 4 */ + { 2, "end choice", ASN1_EOC, ASN1_END }, /* 5 */ + { 2, "oid", ASN1_OID, ASN1_OPT | + ASN1_BODY }, /* 6 */ + { 2, "end choice", ASN1_EOC, ASN1_END }, /* 7 */ + { 2, "string", ASN1_UTF8STRING, ASN1_OPT | + ASN1_BODY }, /* 8 */ + { 2, "end choice", ASN1_EOC, ASN1_END }, /* 9 */ + { 1, "end loop", ASN1_EOC, ASN1_END } /* 10 */ +}; + +#define IETF_ATTR_OCTETS 4 +#define IETF_ATTR_OID 6 +#define IETF_ATTR_STRING 8 +#define IETF_ATTR_ROOF 11 + +/* ASN.1 definition of roleSyntax */ + +static const asn1Object_t roleSyntaxObjects[] = +{ + { 0, "roleSyntax", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */ + { 1, "roleAuthority", ASN1_CONTEXT_C_0, ASN1_OPT | + ASN1_OBJ }, /* 1 */ + { 1, "end opt", ASN1_EOC, ASN1_END }, /* 2 */ + { 1, "roleName", ASN1_CONTEXT_C_1, ASN1_OBJ } /* 3 */ +}; + +#define ROLE_ROOF 4 + +/* ASN.1 definition of an X509 attribute certificate */ + +static const asn1Object_t acObjects[] = +{ + { 0, "AttributeCertificate", ASN1_SEQUENCE, ASN1_OBJ }, /* 0 */ + { 1, "AttributeCertificateInfo", ASN1_SEQUENCE, ASN1_OBJ }, /* 1 */ + { 2, "version", ASN1_INTEGER, ASN1_DEF | + ASN1_BODY }, /* 2 */ + { 2, "holder", ASN1_SEQUENCE, ASN1_NONE }, /* 3 */ + { 3, "baseCertificateID", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 4 */ + { 4, "issuer", ASN1_SEQUENCE, ASN1_OBJ }, /* 5 */ + { 4, "serial", ASN1_INTEGER, ASN1_BODY }, /* 6 */ + { 4, "issuerUID", ASN1_BIT_STRING, ASN1_OPT | + ASN1_BODY }, /* 7 */ + { 4, "end opt", ASN1_EOC, ASN1_END }, /* 8 */ + { 3, "end opt", ASN1_EOC, ASN1_END }, /* 9 */ + { 3, "entityName", ASN1_CONTEXT_C_1, ASN1_OPT | + ASN1_OBJ }, /* 10 */ + { 3, "end opt", ASN1_EOC, ASN1_END }, /* 11 */ + { 3, "objectDigestInfo", ASN1_CONTEXT_C_2, ASN1_OPT }, /* 12 */ + { 4, "digestedObjectType", ASN1_ENUMERATED, ASN1_BODY }, /* 13*/ + { 4, "otherObjectTypeID", ASN1_OID, ASN1_OPT | + ASN1_BODY }, /* 14 */ + { 4, "end opt", ASN1_EOC, ASN1_END }, /* 15*/ + { 4, "digestAlgorithm", ASN1_EOC, ASN1_RAW }, /* 16 */ + { 3, "end opt", ASN1_EOC, ASN1_END }, /* 17 */ + { 2, "v2Form", ASN1_CONTEXT_C_0, ASN1_NONE }, /* 18 */ + { 3, "issuerName", ASN1_SEQUENCE, ASN1_OPT | + ASN1_OBJ }, /* 19 */ + { 3, "end opt", ASN1_EOC, ASN1_END }, /* 20 */ + { 3, "baseCertificateID", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 21 */ + { 4, "issuerSerial", ASN1_SEQUENCE, ASN1_NONE }, /* 22 */ + { 5, "issuer", ASN1_SEQUENCE, ASN1_OBJ }, /* 23 */ + { 5, "serial", ASN1_INTEGER, ASN1_BODY }, /* 24 */ + { 5, "issuerUID", ASN1_BIT_STRING, ASN1_OPT | + ASN1_BODY }, /* 25 */ + { 5, "end opt", ASN1_EOC, ASN1_END }, /* 26 */ + { 3, "end opt", ASN1_EOC, ASN1_END }, /* 27 */ + { 3, "objectDigestInfo", ASN1_CONTEXT_C_1, ASN1_OPT }, /* 28 */ + { 4, "digestInfo", ASN1_SEQUENCE, ASN1_OBJ }, /* 29 */ + { 5, "digestedObjectType", ASN1_ENUMERATED, ASN1_BODY }, /* 30 */ + { 5, "otherObjectTypeID", ASN1_OID, ASN1_OPT | + ASN1_BODY }, /* 31 */ + { 5, "end opt", ASN1_EOC, ASN1_END }, /* 32 */ + { 5, "digestAlgorithm", ASN1_EOC, ASN1_RAW }, /* 33 */ + { 3, "end opt", ASN1_EOC, ASN1_END }, /* 34 */ + { 2, "signature", ASN1_EOC, ASN1_RAW }, /* 35 */ + { 2, "serialNumber", ASN1_INTEGER, ASN1_BODY }, /* 36 */ + { 2, "attrCertValidityPeriod", ASN1_SEQUENCE, ASN1_NONE }, /* 37 */ + { 3, "notBeforeTime", ASN1_GENERALIZEDTIME, ASN1_BODY }, /* 38 */ + { 3, "notAfterTime", ASN1_GENERALIZEDTIME, ASN1_BODY }, /* 39 */ + { 2, "attributes", ASN1_SEQUENCE, ASN1_LOOP }, /* 40 */ + { 3, "attribute", ASN1_SEQUENCE, ASN1_NONE }, /* 41 */ + { 4, "type", ASN1_OID, ASN1_BODY }, /* 42 */ + { 4, "values", ASN1_SET, ASN1_LOOP }, /* 43 */ + { 5, "value", ASN1_EOC, ASN1_RAW }, /* 44 */ + { 4, "end loop", ASN1_EOC, ASN1_END }, /* 45 */ + { 2, "end loop", ASN1_EOC, ASN1_END }, /* 46 */ + { 2, "extensions", ASN1_SEQUENCE, ASN1_LOOP }, /* 47 */ + { 3, "extension", ASN1_SEQUENCE, ASN1_NONE }, /* 48 */ + { 4, "extnID", ASN1_OID, ASN1_BODY }, /* 49 */ + { 4, "critical", ASN1_BOOLEAN, ASN1_DEF | + ASN1_BODY }, /* 50 */ + { 4, "extnValue", ASN1_OCTET_STRING, ASN1_BODY }, /* 51 */ + { 2, "end loop", ASN1_EOC, ASN1_END }, /* 52 */ + { 1, "signatureAlgorithm", ASN1_EOC, ASN1_RAW }, /* 53 */ + { 1, "signatureValue", ASN1_BIT_STRING, ASN1_BODY } /* 54 */ +}; + +#define AC_OBJ_CERTIFICATE 0 +#define AC_OBJ_CERTIFICATE_INFO 1 +#define AC_OBJ_VERSION 2 +#define AC_OBJ_HOLDER_ISSUER 5 +#define AC_OBJ_HOLDER_SERIAL 6 +#define AC_OBJ_ENTITY_NAME 10 +#define AC_OBJ_ISSUER_NAME 19 +#define AC_OBJ_ISSUER 23 +#define AC_OBJ_SIG_ALG 35 +#define AC_OBJ_SERIAL_NUMBER 36 +#define AC_OBJ_NOT_BEFORE 38 +#define AC_OBJ_NOT_AFTER 39 +#define AC_OBJ_ATTRIBUTE_TYPE 42 +#define AC_OBJ_ATTRIBUTE_VALUE 44 +#define AC_OBJ_EXTN_ID 49 +#define AC_OBJ_CRITICAL 50 +#define AC_OBJ_EXTN_VALUE 51 +#define AC_OBJ_ALGORITHM 53 +#define AC_OBJ_SIGNATURE 54 +#define AC_OBJ_ROOF 55 + +const x509acert_t empty_ac = { + NULL , /* *next */ + 0 , /* installed */ + { NULL, 0 }, /* certificate */ + { NULL, 0 }, /* certificateInfo */ + 1 , /* version */ + /* holder */ + /* baseCertificateID */ + { NULL, 0 }, /* holderIssuer */ + { NULL, 0 }, /* holderSerial */ + /* entityName */ + { NULL, 0 }, /* generalNames */ + /* v2Form */ + { NULL, 0 }, /* issuerName */ + /* signature */ + OID_UNKNOWN, /* sigAlg */ + { NULL, 0 }, /* serialNumber */ + /* attrCertValidityPeriod */ + 0 , /* notBefore */ + 0 , /* notAfter */ + /* attributes */ + NULL , /* charging */ + NULL , /* groups */ + /* extensions */ + { NULL, 0 }, /* authKeyID */ + { NULL, 0 }, /* authKeySerialNumber */ + FALSE , /* noRevAvail */ + /* signatureAlgorithm */ + OID_UNKNOWN, /* algorithm */ + { NULL, 0 }, /* signature */ +}; + + +/* compare two ietfAttributes, returns zero if a equals b + * negative/positive if a is earlier/later in the alphabet than b + */ +static int +cmp_ietfAttr(ietfAttr_t *a,ietfAttr_t *b) +{ + int cmp_len, len, cmp_value; + + /* cannot compare OID with STRING or OCTETS attributes */ + if (a->kind == IETF_ATTRIBUTE_OID && b->kind != IETF_ATTRIBUTE_OID) + return 1; + + cmp_len = a->value.len - b->value.len; + len = (cmp_len < 0)? a->value.len : b->value.len; + cmp_value = memcmp(a->value.ptr, b->value.ptr, len); + + return (cmp_value == 0)? cmp_len : cmp_value; +} + +/* + * add an ietfAttribute to the chained list + */ +static ietfAttr_t* +add_ietfAttr(ietfAttr_t *attr) +{ + ietfAttrList_t **listp = &ietfAttributes; + ietfAttrList_t *list = *listp; + int cmp = -1; + + while (list != NULL) + { + cmp = cmp_ietfAttr(attr, list->attr); + if (cmp <= 0) + break; + listp = &list->next; + list = *listp; + } + + if (cmp == 0) + { + /* attribute already exists, increase count */ + pfree(attr); + list->attr->count++; + return list->attr; + } + else + { + ietfAttrList_t *el = alloc_thing(ietfAttrList_t, "ietfAttrList"); + + /* new attribute, unshare value */ + attr->value.ptr = clone_bytes(attr->value.ptr, attr->value.len + , "attr value"); + attr->count = 1; + time(&attr->installed); + + el->attr = attr; + el->next = list; + *listp = el; + + return attr; + } +} + +/* + * decodes a comma separated list of group attributes + */ +void +decode_groups(char *groups, ietfAttrList_t **listp) +{ + if (groups == NULL) + return; + + while (strlen(groups) > 0) + { + char *end; + char *next = strchr(groups, ','); + + if (next == NULL) + end = next = groups + strlen(groups); + else + end = next++; + + /* eat preceeding whitespace */ + while (groups < end && *groups == ' ') + groups++; + + /* eat trailing whitespace */ + while (end > groups && *(end-1) == ' ') + end--; + + if (groups < end) + { + ietfAttr_t *attr = alloc_thing(ietfAttr_t, "ietfAttr"); + ietfAttrList_t *el = alloc_thing(ietfAttrList_t, "ietfAttrList"); + + attr->kind = IETF_ATTRIBUTE_STRING; + attr->value.ptr = groups; + attr->value.len = end - groups; + attr->count = 0; + + el->attr = add_ietfAttr(attr); + el->next = *listp; + *listp = el; + } + + groups = next; + } +} + +static bool +same_attribute(const ietfAttr_t *a, const ietfAttr_t *b) +{ + return (a->kind == b->kind && a->value.len == b->value.len + && memcmp(a->value.ptr, b->value.ptr, b->value.len) == 0); +} + +bool +group_membership(const ietfAttrList_t *peer_list + , const char *conn + , const ietfAttrList_t *conn_list) +{ + if (conn_list == NULL) + return TRUE; + + while (peer_list != NULL) + { + const ietfAttr_t *peer_attr = peer_list->attr; + const ietfAttrList_t *list = conn_list; + + while (list != NULL) + { + ietfAttr_t *conn_attr = list->attr; + + if (same_attribute(conn_attr, peer_attr)) + { + DBG(DBG_CONTROL, + DBG_log("%s: peer matches group '%.*s'" + , conn + , (int)peer_attr->value.len, peer_attr->value.ptr) + ) + return TRUE; + } + list = list->next; + } + peer_list = peer_list->next; + } + DBG(DBG_CONTROL, + DBG_log("%s: peer doesn't match any group", conn) + ) + return FALSE; +} + + +void +unshare_ietfAttrList(ietfAttrList_t **listp) +{ + ietfAttrList_t *list = *listp; + + while (list != NULL) + { + ietfAttrList_t *el = alloc_thing(ietfAttrList_t, "ietfAttrList"); + + el->attr = list->attr; + el->attr->count++; + el->next = NULL; + *listp = el; + listp = &el->next; + list = list->next; + } +} + +/* + * parses ietfAttrSyntax + */ +static ietfAttrList_t* +parse_ietfAttrSyntax(chunk_t blob, int level0) +{ + asn1_ctx_t ctx; + chunk_t object; + u_int level; + int objectID = 0; + + ietfAttrList_t *list = NULL; + + asn1_init(&ctx, blob, level0, FALSE, DBG_RAW); + + while (objectID < IETF_ATTR_ROOF) + { + if (!extract_object(ietfAttrSyntaxObjects, &objectID, &object, &level, &ctx)) + return NULL; + + switch (objectID) + { + case IETF_ATTR_OCTETS: + case IETF_ATTR_OID: + case IETF_ATTR_STRING: + { + ietfAttr_t *attr = alloc_thing(ietfAttr_t, "ietfAttr"); + ietfAttrList_t *el = alloc_thing(ietfAttrList_t, "ietfAttrList"); + + attr->kind = (objectID - IETF_ATTR_OCTETS) / 2; + attr->value = object; + attr->count = 0; + + el->attr = add_ietfAttr(attr); + el->next = list; + list = el; + } + break; + default: + break; + } + objectID++; + } + return list; +} +/* + * parses roleSyntax + */ +static void +parse_roleSyntax(chunk_t blob, int level0) +{ + asn1_ctx_t ctx; + chunk_t object; + u_int level; + int objectID = 0; + + asn1_init(&ctx, blob, level0, FALSE, DBG_RAW); + + while (objectID < ROLE_ROOF) + { + if (!extract_object(roleSyntaxObjects, &objectID, &object, &level, &ctx)) + return; + + switch (objectID) { + default: + break; + } + objectID++; + } +} + +/* + * Parses an X.509 attribute certificate + */ +bool +parse_ac(chunk_t blob, x509acert_t *ac) +{ + asn1_ctx_t ctx; + bool critical; + chunk_t object; + u_int level; + u_int type = OID_UNKNOWN; + u_int extn_oid = OID_UNKNOWN; + int objectID = 0; + + asn1_init(&ctx, blob, 0, FALSE, DBG_RAW); + + while (objectID < AC_OBJ_ROOF) { + + if (!extract_object(acObjects, &objectID, &object, &level, &ctx)) + return FALSE; + + /* those objects which will parsed further need the next higher level */ + level++; + + switch (objectID) + { + case AC_OBJ_CERTIFICATE: + ac->certificate = object; + break; + case AC_OBJ_CERTIFICATE_INFO: + ac->certificateInfo = object; + break; + case AC_OBJ_VERSION: + ac->version = (object.len) ? (1 + (u_int)*object.ptr) : 1; + DBG(DBG_PARSING, + DBG_log(" v%d", ac->version); + ) + if (ac->version != 2) + { + plog("v%d attribute certificates are not supported" + , ac->version); + return FALSE; + } + break; + case AC_OBJ_HOLDER_ISSUER: + ac->holderIssuer = get_directoryName(object, level, FALSE); + break; + case AC_OBJ_HOLDER_SERIAL: + ac->holderSerial = object; + break; + case AC_OBJ_ENTITY_NAME: + ac->entityName = get_directoryName(object, level, TRUE); + break; + case AC_OBJ_ISSUER_NAME: + ac->issuerName = get_directoryName(object, level, FALSE); + break; + case AC_OBJ_SIG_ALG: + ac->sigAlg = parse_algorithmIdentifier(object, level, NULL); + break; + case AC_OBJ_SERIAL_NUMBER: + ac->serialNumber = object; + break; + case AC_OBJ_NOT_BEFORE: + ac->notBefore = asn1totime(&object, ASN1_GENERALIZEDTIME); + break; + case AC_OBJ_NOT_AFTER: + ac->notAfter = asn1totime(&object, ASN1_GENERALIZEDTIME); + break; + case AC_OBJ_ATTRIBUTE_TYPE: + type = known_oid(object); + break; + case AC_OBJ_ATTRIBUTE_VALUE: + { + switch (type) { + case OID_AUTHENTICATION_INFO: + DBG(DBG_PARSING, + DBG_log(" need to parse authenticationInfo") + ) + break; + case OID_ACCESS_IDENTITY: + DBG(DBG_PARSING, + DBG_log(" need to parse accessIdentity") + ) + break; + case OID_CHARGING_IDENTITY: + ac->charging = parse_ietfAttrSyntax(object, level); + break; + case OID_GROUP: + ac->groups = parse_ietfAttrSyntax(object, level); + break; + case OID_ROLE: + parse_roleSyntax(object, level); + break; + default: + break; + } + } + break; + case AC_OBJ_EXTN_ID: + extn_oid = known_oid(object); + break; + case AC_OBJ_CRITICAL: + critical = object.len && *object.ptr; + DBG(DBG_PARSING, + DBG_log(" %s",(critical)?"TRUE":"FALSE"); + ) + break; + case AC_OBJ_EXTN_VALUE: + { + switch (extn_oid) { + case OID_CRL_DISTRIBUTION_POINTS: + DBG(DBG_PARSING, + DBG_log(" need to parse crlDistributionPoints") + ) + break; + case OID_AUTHORITY_KEY_ID: + parse_authorityKeyIdentifier(object, level + , &ac->authKeyID, &ac->authKeySerialNumber); + break; + case OID_TARGET_INFORMATION: + DBG(DBG_PARSING, + DBG_log(" need to parse targetInformation") + ) + break; + case OID_NO_REV_AVAIL: + ac->noRevAvail = TRUE; + break; + default: + break; + } + } + break; + case AC_OBJ_ALGORITHM: + ac->algorithm = parse_algorithmIdentifier(object, level, NULL); + break; + case AC_OBJ_SIGNATURE: + ac->signature = object; + break; + + default: + break; + } + objectID++; + } + time(&ac->installed); + return TRUE; +} + +/* + * compare two X.509 attribute certificates by comparing their signatures + */ +static bool +same_x509acert(x509acert_t *a, x509acert_t *b) +{ + return a->signature.len == b->signature.len && + memcmp(a->signature.ptr, b->signature.ptr, b->signature.len) == 0; +} + +/* + * release an ietfAttribute, free it if count reaches zero + */ +static void +release_ietfAttr(ietfAttr_t* attr) +{ + if (--attr->count == 0) + { + ietfAttrList_t **plist = &ietfAttributes; + ietfAttrList_t *list = *plist; + + while (list->attr != attr) + { + plist = &list->next; + list = *plist; + } + *plist = list->next; + + pfree(attr->value.ptr); + pfree(attr); + pfree(list); + } +} + +/* + * free an ietfAttrList + */ +void +free_ietfAttrList(ietfAttrList_t* list) +{ + while (list != NULL) + { + ietfAttrList_t *el = list; + + release_ietfAttr(el->attr); + list = list->next; + pfree(el); + } +} + +/* + * free a X.509 attribute certificate + */ +void +free_acert(x509acert_t *ac) +{ + if (ac != NULL) + { + free_ietfAttrList(ac->charging); + free_ietfAttrList(ac->groups); + pfreeany(ac->certificate.ptr); + pfree(ac); + } +} + +/* + * free first X.509 attribute certificate in the chained list + */ +static void +free_first_acert(void) +{ + x509acert_t *first = x509acerts; + x509acerts = first->next; + free_acert(first); +} + +/* + * Free all attribute certificates in the chained list + */ +void +free_acerts(void) +{ + while (x509acerts != NULL) + free_first_acert(); +} + +/* + * get a X.509 attribute certificate for a given holder + */ +x509acert_t* +get_x509acert(chunk_t issuer, chunk_t serial) +{ + x509acert_t *ac = x509acerts; + x509acert_t *prev_ac = NULL; + + while (ac != NULL) + { + if (same_dn(issuer, ac->holderIssuer) + && same_serial(serial, ac->holderSerial)) + { + if (ac!= x509acerts) + { + /* bring the certificate up front */ + prev_ac->next = ac->next; + ac->next = x509acerts; + x509acerts = ac; + } + return ac; + } + prev_ac = ac; + ac = ac->next; + } + return NULL; +} + +/* + * add a X.509 attribute certificate to the chained list + */ +static void +add_acert(x509acert_t *ac) +{ + x509acert_t *old_ac = get_x509acert(ac->holderIssuer, ac->holderSerial); + + if (old_ac != NULL) + { + if (ac->notBefore >old_ac->notBefore) + { + /* delete the old attribute cert */ + free_first_acert(); + DBG(DBG_CONTROL, + DBG_log("attribute cert is newer - existing cert deleted") + ) + } + else + { + DBG(DBG_CONTROL, + DBG_log("attribute cert is not newer - existing cert kept"); + ) + free_acert(ac); + return; + } + } + plog("attribute cert added"); + + /* insert new attribute cert at the root of the chain */ + ac->next = x509acerts; + x509acerts = ac; +} + +/* verify the validity of an attribute certificate by + * checking the notBefore and notAfter dates + */ +static err_t +check_ac_validity(const x509acert_t *ac) +{ + time_t current_time; + + time(¤t_time); + DBG(DBG_CONTROL | DBG_PARSING, + DBG_log(" not before : %s", timetoa(&ac->notBefore, TRUE)); + DBG_log(" current time: %s", timetoa(¤t_time, TRUE)); + DBG_log(" not after : %s", timetoa(&ac->notAfter, TRUE)); + ) + + if (current_time < ac->notBefore) + return "attribute certificate is not valid yet"; + if (current_time > ac->notAfter) + return "attribute certificate has expired"; + else + return NULL; +} + +/* + * verifies a X.509 attribute certificate + */ +bool +verify_x509acert(x509acert_t *ac, bool strict) +{ + u_char buf[BUF_LEN]; + x509cert_t *aacert; + err_t ugh = NULL; + time_t valid_until = ac->notAfter; + + DBG(DBG_CONTROL, + dntoa(buf, BUF_LEN, ac->entityName); + DBG_log("holder: '%s'",buf); + dntoa(buf, BUF_LEN, ac->issuerName); + DBG_log("issuer: '%s'",buf); + ) + + ugh = check_ac_validity(ac); + + if (ugh != NULL) + { + plog("%s", ugh); + return FALSE; + } + DBG(DBG_CONTROL, + DBG_log("attribute certificate is valid") + ) + + lock_authcert_list("verify_x509acert"); + aacert = get_authcert(ac->issuerName, ac->authKeySerialNumber + , ac->authKeyID, AUTH_AA); + unlock_authcert_list("verify_x509acert"); + + if (aacert == NULL) + { + plog("issuer aacert not found"); + return FALSE; + } + DBG(DBG_CONTROL, + DBG_log("issuer aacert found") + ) + + if (!check_signature(ac->certificateInfo, ac->signature + , ac->algorithm, ac->algorithm, aacert)) + { + plog("attribute certificate signature is invalid"); + return FALSE; + } + DBG(DBG_CONTROL, + DBG_log("attribute certificate signature is valid"); + ) + + return verify_x509cert(aacert, strict, &valid_until); +} + +/* + * Loads X.509 attribute certificates + */ +void +load_acerts(void) +{ + u_char buf[BUF_LEN]; + + /* change directory to specified path */ + u_char *save_dir = getcwd(buf, BUF_LEN); + + if (!chdir(A_CERT_PATH)) + { + struct dirent **filelist; + int n; + + plog("Changing to directory '%s'",A_CERT_PATH); + n = scandir(A_CERT_PATH, &filelist, file_select, alphasort); + + if (n > 0) + { + while (n--) + { + chunk_t blob = empty_chunk; + bool pgp = FALSE; + + if (load_coded_file(filelist[n]->d_name, NULL, "acert", &blob, &pgp)) + { + x509acert_t *ac = alloc_thing(x509acert_t, "x509acert"); + + *ac = empty_ac; + + if (parse_ac(blob, ac) + && verify_x509acert(ac, FALSE)) + add_acert(ac); + else + free_acert(ac); + } + free(filelist[n]); + } + free(filelist); + } + } + /* restore directory path */ + chdir(save_dir); +} + +/* + * lists group attributes separated by commas on a single line + */ +void +format_groups(const ietfAttrList_t *list, char *buf, int len) +{ + bool first_group = TRUE; + + while (list != NULL && len > 0) + { + ietfAttr_t *attr = list->attr; + + if (attr->kind == IETF_ATTRIBUTE_OCTETS + || attr->kind == IETF_ATTRIBUTE_STRING) + { + int written = snprintf(buf, len, "%s%.*s" + , (first_group)? "" : ", " + , (int)attr->value.len, attr->value.ptr); + + first_group = FALSE; + + /* return value of snprintf() up to glibc 2.0.6 */ + if (written < 0) + break; + + buf += written; + len -= written; + } + list = list->next; + } +} + +/* + * list all X.509 attribute certificates in the chained list + */ +void +list_acerts(bool utc) +{ + x509acert_t *ac = x509acerts; + time_t now; + + /* determine the current time */ + time(&now); + + if (ac != NULL) + { + whack_log(RC_COMMENT, " "); + whack_log(RC_COMMENT, "List of X.509 Attribute Certificates:"); + whack_log(RC_COMMENT, " "); + } + + while (ac != NULL) + { + u_char buf[BUF_LEN]; + + whack_log(RC_COMMENT, "%s",timetoa(&ac->installed, utc)); + if (ac->entityName.ptr != NULL) + { + dntoa(buf, BUF_LEN, ac->entityName); + whack_log(RC_COMMENT, " holder: '%s'", buf); + } + if (ac->holderIssuer.ptr != NULL) + { + dntoa(buf, BUF_LEN, ac->holderIssuer); + whack_log(RC_COMMENT, " hissuer: '%s'", buf); + } + if (ac->holderSerial.ptr != NULL) + { + datatot(ac->holderSerial.ptr, ac->holderSerial.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT, " hserial: %s", buf); + } + if (ac->groups != NULL) + { + format_groups(ac->groups, buf, BUF_LEN); + whack_log(RC_COMMENT, " groups: %s", buf); + } + dntoa(buf, BUF_LEN, ac->issuerName); + whack_log(RC_COMMENT, " issuer: '%s'", buf); + datatot(ac->serialNumber.ptr, ac->serialNumber.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT, " serial: %s", buf); + whack_log(RC_COMMENT, " validity: not before %s %s", + timetoa(&ac->notBefore, utc), + (ac->notBefore < now)?"ok":"fatal (not valid yet)"); + whack_log(RC_COMMENT, " not after %s %s", + timetoa(&ac->notAfter, utc), + check_expiry(ac->notAfter, ACERT_WARNING_INTERVAL, TRUE)); + if (ac->authKeyID.ptr != NULL) + { + datatot(ac->authKeyID.ptr, ac->authKeyID.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT, " authkey: %s", buf); + } + if (ac->authKeySerialNumber.ptr != NULL) + { + datatot(ac->authKeySerialNumber.ptr, ac->authKeySerialNumber.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT, " aserial: %s", buf); + } + + ac = ac->next; + } +} + +/* + * list all group attributes in alphabetical order + */ +void +list_groups(bool utc) +{ + ietfAttrList_t *list = ietfAttributes; + + if (list != NULL) + { + whack_log(RC_COMMENT, " "); + whack_log(RC_COMMENT, "List of Group Attributes:"); + whack_log(RC_COMMENT, " "); + } + + while (list != NULL) + { + ietfAttr_t *attr = list->attr; + + whack_log(RC_COMMENT, "%s, count: %d", timetoa(&attr->installed, utc), + attr->count); + + switch (attr->kind) + { + case IETF_ATTRIBUTE_OCTETS: + case IETF_ATTRIBUTE_STRING: + whack_log(RC_COMMENT, " %.*s", (int)attr->value.len, attr->value.ptr); + break; + case IETF_ATTRIBUTE_OID: + whack_log(RC_COMMENT, " OID"); + break; + default: + break; + } + + list = list->next; + } +} diff --git a/src/pluto/ac.h b/src/pluto/ac.h new file mode 100644 index 000000000..3913d745d --- /dev/null +++ b/src/pluto/ac.h @@ -0,0 +1,103 @@ +/* Support of X.509 attribute certificates + * Copyright (C) 2002 Ueli Galizzi, Ariane Seiler + * Copyright (C) 2003 Martin Berner, Lukas Suter + + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: ac.h,v 1.8 2005/02/17 20:56:04 as Exp $ + */ + +#ifndef _AC_H +#define _AC_H + +/* definition of ietfAttribute kinds */ + +typedef enum { + IETF_ATTRIBUTE_OCTETS = 0, + IETF_ATTRIBUTE_OID = 1, + IETF_ATTRIBUTE_STRING = 2 +} ietfAttribute_t; + +/* access structure for an ietfAttribute */ + +typedef struct ietfAttr ietfAttr_t; + +struct ietfAttr { + time_t installed; + int count; + ietfAttribute_t kind; + chunk_t value; +}; + +typedef struct ietfAttrList ietfAttrList_t; + +struct ietfAttrList { + ietfAttrList_t *next; + ietfAttr_t *attr; +}; + + +/* access structure for an X.509 attribute certificate */ + +typedef struct x509acert x509acert_t; + +struct x509acert { + x509acert_t *next; + time_t installed; + chunk_t certificate; + chunk_t certificateInfo; + u_int version; + /* holder */ + /* baseCertificateID */ + chunk_t holderIssuer; + chunk_t holderSerial; + chunk_t entityName; + /* v2Form */ + chunk_t issuerName; + /* signature */ + int sigAlg; + chunk_t serialNumber; + /* attrCertValidityPeriod */ + time_t notBefore; + time_t notAfter; + /* attributes */ + ietfAttrList_t *charging; + ietfAttrList_t *groups; + /* extensions */ + chunk_t authKeyID; + chunk_t authKeySerialNumber; + bool noRevAvail; + /* signatureAlgorithm */ + int algorithm; + chunk_t signature; +}; + +/* used for initialization */ +extern const x509acert_t empty_ac; + +extern void unshare_ietfAttrList(ietfAttrList_t **listp); +extern void free_ietfAttrList(ietfAttrList_t *list); +extern void decode_groups(char *groups, ietfAttrList_t **listp); +extern bool group_membership(const ietfAttrList_t *my_list + , const char *conn, const ietfAttrList_t *conn_list); +extern bool parse_ac(chunk_t blob, x509acert_t *ac); +extern bool verify_x509acert(x509acert_t *ac, bool strict); +extern x509acert_t* get_x509acert(chunk_t issuer, chunk_t serial); +extern void load_acerts(void); +extern void free_acert(x509acert_t *ac); +extern void free_acerts(void); +extern void list_acerts(bool utc); +extern void list_groups(bool utc); +extern void format_groups(const ietfAttrList_t *list, char *buf, int len); + + +#endif /* _AH_H */ diff --git a/src/pluto/adns.c b/src/pluto/adns.c new file mode 100644 index 000000000..c5977d23c --- /dev/null +++ b/src/pluto/adns.c @@ -0,0 +1,615 @@ +/* Pluto Asynchronous DNS Helper Program -- for internal use only! + * Copyright (C) 2002 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: adns.c,v 1.1 2004/03/15 20:35:28 as Exp $ + */ + +#ifndef USE_LWRES /* whole file! */ + +/* This program executes as multiple processes. The Master process + * receives queries (struct adns_query messages) from Pluto and distributes + * them amongst Worker processes. These Worker processes are created + * by the Master whenever a query arrives and no existing Worker is free. + * At most MAX_WORKERS will be created; after that, the Master will queue + * queries until a Worker becomes free. When a Worker has an answer from + * the resolver, it sends the answer as a struct adns_answer message to the + * Master. The Master then forwards the answer to Pluto, noting that + * the Worker is free to accept another query. + * + * The protocol is simple: Pluto sends a sequence of queries and receives + * a sequence of answers. select(2) is used by Pluto and by the Master + * process to decide when to read, but writes are done without checking + * for readiness. Communications is via pipes. Since only one process + * can write to each pipe, messages will not be interleaved. Fixed length + * records are used for simplicity. + * + * Pluto needs a way to indicate to the Master when to shut down + * and the Master needs to indicate this to each worker. EOF on the pipe + * signifies this. + * + * The interfaces between these components are considered private to + * Pluto. This allows us to get away with less checking. This is a + * reason to use pipes instead of TCP/IP. + * + * Although the code uses plain old UNIX processes, it could be modified + * to use threads. That might reduce resource requirements. It would + * preclude running on systems without thread-safe resolvers. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <syslog.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#include <resolv.h> +#include <netdb.h> /* ??? for h_errno */ + +#include <freeswan.h> + +/* GCC magic! */ +#ifdef GCC_LINT +# define UNUSED __attribute__ ((unused)) +#else +# define UNUSED /* ignore */ +#endif + +#include "constants.h" +#include "adns.h" /* needs <resolv.h> */ + +/* shared by all processes */ + +static const char *name; /* program name, for messages */ + +static bool debug = FALSE; + +/* Read a variable-length record from a pipe (and no more!). + * First bytes must be a size_t containing the length. + * HES_CONTINUE if record read + * HES_OK if EOF + * HES_IO_ERROR_IN if errno tells the tale. + * Others are errors. + */ +static enum helper_exit_status +read_pipe(int fd, unsigned char *stuff, size_t minlen, size_t maxlen) +{ + size_t n = 0; + size_t goal = minlen; + + do { + ssize_t m = read(fd, stuff + n, goal - n); + + if (m == -1) + { + if (errno != EINTR) + { + syslog(LOG_ERR, "Input error on pipe: %s", strerror(errno)); + return HES_IO_ERROR_IN; + } + } + else if (m == 0) + { + return HES_OK; /* treat empty message as EOF */ + } + else + { + n += m; + if (n >= sizeof(size_t)) + { + goal = *(size_t *)(void *)stuff; + if (goal < minlen || maxlen < goal) + { + if (debug) + fprintf(stderr, "%lu : [%lu, %lu]\n" + , (unsigned long)goal + , (unsigned long)minlen, (unsigned long)maxlen); + return HES_BAD_LEN; + } + } + } + } while (n < goal); + + return HES_CONTINUE; +} + +/* Write a variable-length record to a pipe. + * First bytes must be a size_t containing the length. + * HES_CONTINUE if record written + * Others are errors. + */ +static enum helper_exit_status +write_pipe(int fd, const unsigned char *stuff) +{ + size_t len = *(const size_t *)(const void *)stuff; + size_t n = 0; + + do { + ssize_t m = write(fd, stuff + n, len - n); + + if (m == -1) + { + /* error, but ignore and retry if EINTR */ + if (errno != EINTR) + { + syslog(LOG_ERR, "Output error from master: %s", strerror(errno)); + return HES_IO_ERROR_OUT; + } + } + else + { + n += m; + } + } while (n != len); + return HES_CONTINUE; +} + +/**************** worker process ****************/ + +/* The interface in RHL6.x and BIND distribution 8.2.2 are different, + * so we build some of our own :-( + */ + +/* Support deprecated interface to allow for older releases of the resolver. + * Fake new interface! + * See resolver(3) bind distribution (should be in RHL6.1, but isn't). + * __RES was 19960801 in RHL6.2, an old resolver. + */ + +#if (__RES) <= 19960801 +# define OLD_RESOLVER 1 +#endif + +#ifdef OLD_RESOLVER + +# define res_ninit(statp) res_init() +# define res_nquery(statp, dname, class, type, answer, anslen) \ + res_query(dname, class, type, answer, anslen) +# define res_nclose(statp) res_close() + +static struct __res_state *statp = &_res; + +#else /* !OLD_RESOLVER */ + +static struct __res_state my_res_state /* = { 0 } */; +static res_state statp = &my_res_state; + +#endif /* !OLD_RESOLVER */ + +static int +worker(int qfd, int afd) +{ + { + int r = res_ninit(statp); + + if (r != 0) + { + syslog(LOG_ERR, "cannot initialize resolver"); + return HES_RES_INIT; + } +#ifndef OLD_RESOLVER + statp->options |= RES_ROTATE; +#endif + statp->options |= RES_DEBUG; + } + + for (;;) + { + struct adns_query q; + struct adns_answer a; + + enum helper_exit_status r = read_pipe(qfd, (unsigned char *)&q + , sizeof(q), sizeof(q)); + + if (r != HES_CONTINUE) + return r; /* some kind of exit */ + + if (q.qmagic != ADNS_Q_MAGIC) + { + syslog(LOG_ERR, "error in input from master: bad magic"); + return HES_BAD_MAGIC; + } + + a.amagic = ADNS_A_MAGIC; + a.serial = q.serial; + + a.result = res_nquery(statp, q.name_buf, C_IN, q.type, a.ans, sizeof(a.ans)); + a.h_errno_val = h_errno; + + a.len = offsetof(struct adns_answer, ans) + (a.result < 0? 0 : a.result); + +#ifdef DEBUG + if (((q.debugging & IMPAIR_DELAY_ADNS_KEY_ANSWER) && q.type == T_KEY) + || ((q.debugging & IMPAIR_DELAY_ADNS_TXT_ANSWER) && q.type == T_TXT)) + sleep(30); /* delay the answer */ +#endif + + /* write answer, possibly a bit at a time */ + r = write_pipe(afd, (const unsigned char *)&a); + + if (r != HES_CONTINUE) + return r; /* some kind of exit */ + } +} + +/**************** master process ****************/ + +bool eof_from_pluto = FALSE; +#define PLUTO_QFD 0 /* queries come on stdin */ +#define PLUTO_AFD 1 /* answers go out on stdout */ + +#ifndef MAX_WORKERS +# define MAX_WORKERS 10 /* number of in-flight queries */ +#endif + +struct worker_info { + int qfd; /* query pipe's file descriptor */ + int afd; /* answer pipe's file descriptor */ + pid_t pid; + bool busy; + void *continuation; /* of outstanding request */ +}; + +static struct worker_info wi[MAX_WORKERS]; +static struct worker_info *wi_roof = wi; + +/* request FIFO */ + +struct query_list { + struct query_list *next; + struct adns_query aq; +}; + +static struct query_list *oldest_query = NULL; +static struct query_list *newest_query; /* undefined when oldest == NULL */ +static struct query_list *free_queries = NULL; + +static bool +spawn_worker(void) +{ + int qfds[2]; + int afds[2]; + pid_t p; + + if (pipe(qfds) != 0 || pipe(afds) != 0) + { + syslog(LOG_ERR, "pipe(2) failed: %s", strerror(errno)); + exit(HES_PIPE); + } + + wi_roof->qfd = qfds[1]; /* write end of query pipe */ + wi_roof->afd = afds[0]; /* read end of answer pipe */ + + p = fork(); + if (p == -1) + { + /* fork failed: ignore if at least one worker exists */ + if (wi_roof == wi) + { + syslog(LOG_ERR, "fork(2) error creating first worker: %s", strerror(errno)); + exit(HES_FORK); + } + close(qfds[0]); + close(qfds[1]); + close(afds[0]); + close(afds[1]); + return FALSE; + } + else if (p == 0) + { + /* child */ + struct worker_info *w; + + close(PLUTO_QFD); + close(PLUTO_AFD); + /* close all master pipes, including ours */ + for (w = wi; w <= wi_roof; w++) + { + close(w->qfd); + close(w->afd); + } + exit(worker(qfds[0], afds[1])); + } + else + { + /* parent */ + struct worker_info *w = wi_roof++; + + w->pid = p; + w->busy = FALSE; + close(qfds[0]); + close(afds[1]); + return TRUE; + } +} + +static void +send_eof(struct worker_info *w) +{ + pid_t p; + int status; + + close(w->qfd); + w->qfd = NULL_FD; + + close(w->afd); + w->afd = NULL_FD; + + /* reap child */ + p = waitpid(w->pid, &status, 0); + /* ignore result -- what could we do with it? */ +} + +static void +forward_query(struct worker_info *w) +{ + struct query_list *q = oldest_query; + + if (q == NULL) + { + if (eof_from_pluto) + send_eof(w); + } + else + { + enum helper_exit_status r + = write_pipe(w->qfd, (const unsigned char *) &q->aq); + + if (r != HES_CONTINUE) + exit(r); + + w->busy = TRUE; + + oldest_query = q->next; + q->next = free_queries; + free_queries = q; + } +} + +static void +query(void) +{ + struct query_list *q = free_queries; + enum helper_exit_status r; + + /* find an unused queue entry */ + if (q == NULL) + { + q = malloc(sizeof(*q)); + if (q == NULL) + { + syslog(LOG_ERR, "malloc(3) failed"); + exit(HES_MALLOC); + } + } + else + { + free_queries = q->next; + } + + r = read_pipe(PLUTO_QFD, (unsigned char *)&q->aq + , sizeof(q->aq), sizeof(q->aq)); + + if (r == HES_OK) + { + /* EOF: we're done, except for unanswered queries */ + struct worker_info *w; + + eof_from_pluto = TRUE; + q->next = free_queries; + free_queries = q; + + /* Send bye-bye to unbusy processes. + * Note that if there are queued queries, there won't be + * any non-busy workers. + */ + for (w = wi; w != wi_roof; w++) + if (!w->busy) + send_eof(w); + } + else if (r != HES_CONTINUE) + { + exit(r); + } + else if (q->aq.qmagic != ADNS_Q_MAGIC) + { + syslog(LOG_ERR, "error in query from Pluto: bad magic"); + exit(HES_BAD_MAGIC); + } + else + { + struct worker_info *w; + + /* got a query */ + + /* add it to FIFO */ + q->next = NULL; + if (oldest_query == NULL) + oldest_query = q; + else + newest_query->next = q; + newest_query = q; + + /* See if any worker available */ + for (w = wi; ; w++) + { + if (w == wi_roof) + { + /* no free worker */ + if (w == wi + MAX_WORKERS) + break; /* no more to be created */ + /* make a new one */ + if (!spawn_worker()) + break; /* cannot create one at this time */ + } + if (!w->busy) + { + /* assign first to free worker */ + forward_query(w); + break; + } + } + } + return; +} + +static void +answer(struct worker_info *w) +{ + struct adns_answer a; + enum helper_exit_status r = read_pipe(w->afd, (unsigned char *)&a + , offsetof(struct adns_answer, ans), sizeof(a)); + + if (r == HES_OK) + { + /* unexpected EOF */ + syslog(LOG_ERR, "unexpected EOF from worker"); + exit(HES_IO_ERROR_IN); + } + else if (r != HES_CONTINUE) + { + exit(r); + } + else if (a.amagic != ADNS_A_MAGIC) + { + syslog(LOG_ERR, "Input from worker error: bad magic"); + exit(HES_BAD_MAGIC); + } + else if (a.continuation != w->continuation) + { + /* answer doesn't match query */ + syslog(LOG_ERR, "Input from worker error: continuation mismatch"); + exit(HES_SYNC); + } + else + { + /* pass the answer on to Pluto */ + enum helper_exit_status r + = write_pipe(PLUTO_AFD, (const unsigned char *) &a); + + if (r != HES_CONTINUE) + exit(r); + w->busy = FALSE; + forward_query(w); + } +} + +/* assumption: input limited; accept blocking on output */ +static int +master(void) +{ + for (;;) + { + fd_set readfds; + int maxfd = PLUTO_QFD; /* approximate lower bound */ + int ndes = 0; + struct worker_info *w; + + FD_ZERO(&readfds); + if (!eof_from_pluto) + { + FD_SET(PLUTO_QFD, &readfds); + ndes++; + } + for (w = wi; w != wi_roof; w++) + { + if (w->busy) + { + FD_SET(w->afd, &readfds); + ndes++; + if (maxfd < w->afd) + maxfd = w->afd; + } + } + + if (ndes == 0) + return HES_OK; /* done! */ + + do { + ndes = select(maxfd + 1, &readfds, NULL, NULL, NULL); + } while (ndes == -1 && errno == EINTR); + if (ndes == -1) + { + syslog(LOG_ERR, "select(2) error: %s", strerror(errno)); + exit(HES_IO_ERROR_SELECT); + } + else if (ndes > 0) + { + if (FD_ISSET(PLUTO_QFD, &readfds)) + { + query(); + ndes--; + } + for (w = wi; ndes > 0 && w != wi_roof; w++) + { + if (w->busy && FD_ISSET(w->afd, &readfds)) + { + answer(w); + ndes--; + } + } + } + } +} + +/* Not to be invoked by strangers -- user hostile. + * Mandatory args: query-fd answer-fd + * Optional arg: -d, signifying "debug". + */ + +static void +adns_usage(const char *fmt, const char *arg) +{ + const char **sp = ipsec_copyright_notice(); + + fprintf(stderr, "INTERNAL TO PLUTO: DO NOT EXECUTE\n"); + + fprintf(stderr, fmt, arg); + fprintf(stderr, "\n%s\n", ipsec_version_string()); + + for (; *sp != NULL; sp++) + fprintf(stderr, "%s\n", *sp); + + syslog(LOG_ERR, fmt, arg); + exit(HES_INVOCATION); +} + +int +main(int argc UNUSED, char **argv) +{ + int i = 1; + + name = argv[0]; + + while (i < argc) + { + if (streq(argv[i], "-d")) + { + i++; + debug = TRUE; + } + else + { + adns_usage("unexpected argument \"%s\"", argv[i]); + /*NOTREACHED*/ + } + } + + return master(); +} + +#endif /* !USE_LWRES */ diff --git a/src/pluto/adns.h b/src/pluto/adns.h new file mode 100644 index 000000000..00fc4ad07 --- /dev/null +++ b/src/pluto/adns.h @@ -0,0 +1,75 @@ +/* Pluto Asynchronous DNS Helper Program's Header + * Copyright (C) 2002 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: adns.h,v 1.1 2004/03/15 20:35:28 as Exp $ + */ + +#ifndef USE_LWRES /* whole file! */ + +/* The interface in RHL6.x and BIND distribution 8.2.2 are different, + * so we build some of our own :-( + */ + +# ifndef NS_MAXDNAME +# define NS_MAXDNAME MAXDNAME /* I hope this is long enough for IPv6 */ +# endif + +# ifndef NS_PACKETSZ +# define NS_PACKETSZ PACKETSZ +# endif + +/* protocol version */ + +#define ADNS_Q_MAGIC (((((('d' << 8) + 'n') << 8) + 's') << 8) + 4) +#define ADNS_A_MAGIC (((((('d' << 8) + 'n') << 8) + 's') << 8) + 128 + 4) + +/* note: both struct adns_query and struct adns_answer must start with + * size_t len; + */ + +struct adns_query { + size_t len; + unsigned int qmagic; + unsigned long serial; + lset_t debugging; /* only used #ifdef DEBUG, but don't want layout to change */ + u_char name_buf[NS_MAXDNAME + 2]; + int type; /* T_KEY or T_TXT */ +}; + +struct adns_answer { + size_t len; + unsigned int amagic; + unsigned long serial; + struct adns_continuation *continuation; + int result; + int h_errno_val; + u_char ans[NS_PACKETSZ * 10]; /* very probably bigger than necessary */ +}; + +enum helper_exit_status { + HES_CONTINUE = -1, /* not an exit */ + HES_OK = 0, /* all's well that ends well (perhaps EOF) */ + HES_INVOCATION, /* improper invocation */ + HES_IO_ERROR_SELECT, /* IO error in select() */ + HES_MALLOC, /* malloc failed */ + HES_IO_ERROR_IN, /* error reading pipe */ + HES_IO_ERROR_OUT, /* error reading pipe */ + HES_PIPE, /* pipe(2) failed */ + HES_SYNC, /* answer from worker doesn't match query */ + HES_FORK, /* fork(2) failed */ + HES_RES_INIT, /* resolver initialization failed */ + HES_BAD_LEN, /* implausible .len field */ + HES_BAD_MAGIC, /* .magic field wrong */ +}; + +#endif /* !USE_LWRES */ diff --git a/src/pluto/alg/ike_alg_aes.c b/src/pluto/alg/ike_alg_aes.c new file mode 100644 index 000000000..44de09b4c --- /dev/null +++ b/src/pluto/alg/ike_alg_aes.c @@ -0,0 +1,68 @@ +#include <stdio.h> +#include <string.h> +#include <stddef.h> +#include <sys/types.h> +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "libaes/aes_cbc.h" +#include "alg_info.h" +#include "ike_alg.h" + +#define AES_CBC_BLOCK_SIZE (128/BITS_PER_BYTE) +#define AES_KEY_MIN_LEN 128 +#define AES_KEY_DEF_LEN 128 +#define AES_KEY_MAX_LEN 256 + +static void +do_aes(u_int8_t *buf, size_t buf_len, u_int8_t *key, size_t key_size, u_int8_t *iv, bool enc) +{ + aes_context aes_ctx; + char iv_bak[AES_CBC_BLOCK_SIZE]; + char *new_iv = NULL; /* logic will avoid copy to NULL */ + + aes_set_key(&aes_ctx, key, key_size, 0); + + /* + * my AES cbc does not touch passed IV (optimization for + * ESP handling), so I must "emulate" des-like IV + * crunching + */ + if (!enc) + memcpy(new_iv=iv_bak, (char*) buf + buf_len - AES_CBC_BLOCK_SIZE + , AES_CBC_BLOCK_SIZE); + + AES_cbc_encrypt(&aes_ctx, buf, buf, buf_len, iv, enc); + + if (enc) + new_iv = (char*) buf + buf_len-AES_CBC_BLOCK_SIZE; + + memcpy(iv, new_iv, AES_CBC_BLOCK_SIZE); +} + +struct encrypt_desc algo_aes = +{ + algo_type: IKE_ALG_ENCRYPT, + algo_id: OAKLEY_AES_CBC, + algo_next: NULL, + enc_ctxsize: sizeof(aes_context), + enc_blocksize: AES_CBC_BLOCK_SIZE, + keyminlen: AES_KEY_MIN_LEN, + keydeflen: AES_KEY_DEF_LEN, + keymaxlen: AES_KEY_MAX_LEN, + do_crypt: do_aes, +}; + +int ike_alg_aes_init(void); + +int +ike_alg_aes_init(void) +{ + int ret = ike_alg_register_enc(&algo_aes); + return ret; +} +/* +IKE_ALG_INIT_NAME: ike_alg_aes_init +*/ diff --git a/src/pluto/alg/ike_alg_blowfish.c b/src/pluto/alg/ike_alg_blowfish.c new file mode 100644 index 000000000..2bbef051b --- /dev/null +++ b/src/pluto/alg/ike_alg_blowfish.c @@ -0,0 +1,52 @@ +#include <stdio.h> +#include <string.h> +#include <stddef.h> +#include <sys/types.h> +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "libblowfish/blowfish.h" +#include "alg_info.h" +#include "ike_alg.h" + +#define BLOWFISH_CBC_BLOCK_SIZE 8 /* block size */ +#define BLOWFISH_KEY_MIN_LEN 128 +#define BLOWFISH_KEY_MAX_LEN 448 + + +static void +do_blowfish(u_int8_t *buf, size_t buf_len, u_int8_t *key, size_t key_size, u_int8_t *iv, bool enc) +{ + BF_KEY bf_ctx; + + BF_set_key(&bf_ctx, key_size , key); + BF_cbc_encrypt(buf, buf, buf_len, &bf_ctx, iv, enc); +} + +struct encrypt_desc algo_blowfish = +{ + algo_type: IKE_ALG_ENCRYPT, + algo_id: OAKLEY_BLOWFISH_CBC, + algo_next: NULL, + enc_ctxsize: sizeof(BF_KEY), + enc_blocksize: BLOWFISH_CBC_BLOCK_SIZE, + keyminlen: BLOWFISH_KEY_MIN_LEN, + keydeflen: BLOWFISH_KEY_MIN_LEN, + keymaxlen: BLOWFISH_KEY_MAX_LEN, + do_crypt: do_blowfish, +}; + +int ike_alg_blowfish_init(void); + +int +ike_alg_blowfish_init(void) +{ + int ret = ike_alg_register_enc(&algo_blowfish); + + return ret; +} +/* +IKE_ALG_INIT_NAME: ike_alg_blowfish_init +*/ diff --git a/src/pluto/alg/ike_alg_serpent.c b/src/pluto/alg/ike_alg_serpent.c new file mode 100644 index 000000000..fb01caa41 --- /dev/null +++ b/src/pluto/alg/ike_alg_serpent.c @@ -0,0 +1,70 @@ +#include <stdio.h> +#include <string.h> +#include <stddef.h> +#include <sys/types.h> +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "libserpent/serpent_cbc.h" +#include "alg_info.h" +#include "ike_alg.h" + +#define SERPENT_CBC_BLOCK_SIZE (128/BITS_PER_BYTE) +#define SERPENT_KEY_MIN_LEN 128 +#define SERPENT_KEY_DEF_LEN 128 +#define SERPENT_KEY_MAX_LEN 256 + +static void +do_serpent(u_int8_t *buf, size_t buf_size, u_int8_t *key, size_t key_size, u_int8_t *iv, bool enc) +{ + serpent_context serpent_ctx; + char iv_bak[SERPENT_CBC_BLOCK_SIZE]; + char *new_iv = NULL; /* logic will avoid copy to NULL */ + + + serpent_set_key(&serpent_ctx, key, key_size); + /* + * my SERPENT cbc does not touch passed IV (optimization for + * ESP handling), so I must "emulate" des-like IV + * crunching + */ + if (!enc) + memcpy(new_iv=iv_bak, + (char*) buf + buf_size-SERPENT_CBC_BLOCK_SIZE, + SERPENT_CBC_BLOCK_SIZE); + + serpent_cbc_encrypt(&serpent_ctx, buf, buf, buf_size, iv, enc); + + if (enc) + new_iv = (char*) buf + buf_size-SERPENT_CBC_BLOCK_SIZE; + + memcpy(iv, new_iv, SERPENT_CBC_BLOCK_SIZE); +} + +struct encrypt_desc encrypt_desc_serpent = +{ + algo_type: IKE_ALG_ENCRYPT, + algo_id: OAKLEY_SERPENT_CBC, + algo_next: NULL, + enc_ctxsize: sizeof(struct serpent_context), + enc_blocksize: SERPENT_CBC_BLOCK_SIZE, + keyminlen: SERPENT_KEY_MIN_LEN, + keydeflen: SERPENT_KEY_DEF_LEN, + keymaxlen: SERPENT_KEY_MAX_LEN, + do_crypt: do_serpent, +}; + +int ike_alg_serpent_init(void); + +int +ike_alg_serpent_init(void) +{ + int ret = ike_alg_register_enc(&encrypt_desc_serpent); + + return ret; +} +/* +IKE_ALG_INIT_NAME: ike_alg_serpent_init +*/ diff --git a/src/pluto/alg/ike_alg_sha2.c b/src/pluto/alg/ike_alg_sha2.c new file mode 100644 index 000000000..6b7c8438c --- /dev/null +++ b/src/pluto/alg/ike_alg_sha2.c @@ -0,0 +1,634 @@ +#include <stdio.h> +#include <string.h> +#include <stddef.h> +#include <sys/types.h> +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "libsha2/sha2.h" +#include "alg_info.h" +#include "ike_alg.h" + +static void +sha256_hash_final(u_char *hash, sha256_context *ctx) +{ + sha256_final(ctx); + memcpy(hash, ctx->sha_out, SHA2_256_DIGEST_SIZE); +} + +static void +sha384_hash_final(u_char *hash, sha512_context *ctx) +{ + sha512_final(ctx); + memcpy(hash, ctx->sha_out, SHA2_384_DIGEST_SIZE); +} + +static void +sha512_hash_final(u_char *hash, sha512_context *ctx) +{ + sha512_final(ctx); + memcpy(hash, ctx->sha_out, SHA2_512_DIGEST_SIZE); +} + +/* SHA-256 hash test vectors + * from "The Secure Hash Algorithm Validation System (SHAVS)" + * July 22, 2004, Lawrence E. Bassham III, NIST + */ + +static const u_char sha256_short2_msg[] = { + 0x19 +}; + +static const u_char sha256_short2_msg_digest[] = { + 0x68, 0xaa, 0x2e, 0x2e, 0xe5, 0xdf, 0xf9, 0x6e, + 0x33, 0x55, 0xe6, 0xc7, 0xee, 0x37, 0x3e, 0x3d, + 0x6a, 0x4e, 0x17, 0xf7, 0x5f, 0x95, 0x18, 0xd8, + 0x43, 0x70, 0x9c, 0x0c, 0x9b, 0xc3, 0xe3, 0xd4 +}; + +static const u_char sha256_short4_msg[] = { + 0xe3, 0xd7, 0x25, 0x70, 0xdc, 0xdd, 0x78, 0x7c, + 0xe3, 0x88, 0x7a, 0xb2, 0xcd, 0x68, 0x46, 0x52 +}; + +static const u_char sha256_short4_msg_digest[] = { + 0x17, 0x5e, 0xe6, 0x9b, 0x02, 0xba, 0x9b, 0x58, + 0xe2, 0xb0, 0xa5, 0xfd, 0x13, 0x81, 0x9c, 0xea, + 0x57, 0x3f, 0x39, 0x40, 0xa9, 0x4f, 0x82, 0x51, + 0x28, 0xcf, 0x42, 0x09, 0xbe, 0xab, 0xb4, 0xe8 +}; + +static const u_char sha256_long2_msg[] = { + 0x83, 0x26, 0x75, 0x4e, 0x22, 0x77, 0x37, 0x2f, + 0x4f, 0xc1, 0x2b, 0x20, 0x52, 0x7a, 0xfe, 0xf0, + 0x4d, 0x8a, 0x05, 0x69, 0x71, 0xb1, 0x1a, 0xd5, + 0x71, 0x23, 0xa7, 0xc1, 0x37, 0x76, 0x00, 0x00, + 0xd7, 0xbe, 0xf6, 0xf3, 0xc1, 0xf7, 0xa9, 0x08, + 0x3a, 0xa3, 0x9d, 0x81, 0x0d, 0xb3, 0x10, 0x77, + 0x7d, 0xab, 0x8b, 0x1e, 0x7f, 0x02, 0xb8, 0x4a, + 0x26, 0xc7, 0x73, 0x32, 0x5f, 0x8b, 0x23, 0x74, + 0xde, 0x7a, 0x4b, 0x5a, 0x58, 0xcb, 0x5c, 0x5c, + 0xf3, 0x5b, 0xce, 0xe6, 0xfb, 0x94, 0x6e, 0x5b, + 0xd6, 0x94, 0xfa, 0x59, 0x3a, 0x8b, 0xeb, 0x3f, + 0x9d, 0x65, 0x92, 0xec, 0xed, 0xaa, 0x66, 0xca, + 0x82, 0xa2, 0x9d, 0x0c, 0x51, 0xbc, 0xf9, 0x33, + 0x62, 0x30, 0xe5, 0xd7, 0x84, 0xe4, 0xc0, 0xa4, + 0x3f, 0x8d, 0x79, 0xa3, 0x0a, 0x16, 0x5c, 0xba, + 0xbe, 0x45, 0x2b, 0x77, 0x4b, 0x9c, 0x71, 0x09, + 0xa9, 0x7d, 0x13, 0x8f, 0x12, 0x92, 0x28, 0x96, + 0x6f, 0x6c, 0x0a, 0xdc, 0x10, 0x6a, 0xad, 0x5a, + 0x9f, 0xdd, 0x30, 0x82, 0x57, 0x69, 0xb2, 0xc6, + 0x71, 0xaf, 0x67, 0x59, 0xdf, 0x28, 0xeb, 0x39, + 0x3d, 0x54, 0xd6 +}; + +static const u_char sha256_long2_msg_digest[] = { + 0x97, 0xdb, 0xca, 0x7d, 0xf4, 0x6d, 0x62, 0xc8, + 0xa4, 0x22, 0xc9, 0x41, 0xdd, 0x7e, 0x83, 0x5b, + 0x8a, 0xd3, 0x36, 0x17, 0x63, 0xf7, 0xe9, 0xb2, + 0xd9, 0x5f, 0x4f, 0x0d, 0xa6, 0xe1, 0xcc, 0xbc +}; + +static const hash_testvector_t sha256_hash_testvectors[] = { + { sizeof(sha256_short2_msg), sha256_short2_msg, sha256_short2_msg_digest }, + { sizeof(sha256_short4_msg), sha256_short4_msg, sha256_short4_msg_digest }, + { sizeof(sha256_long2_msg), sha256_long2_msg, sha256_long2_msg_digest }, + { 0, NULL, NULL } +}; + +/* SHA-384 hash test vectors + * from "The Secure Hash Algorithm Validation System (SHAVS)" + * July 22, 2004, Lawrence E. Bassham III, NIST + */ + +static const u_char sha384_short2_msg[] = { + 0xb9 +}; + +static const u_char sha384_short2_msg_digest[] = { + 0xbc, 0x80, 0x89, 0xa1, 0x90, 0x07, 0xc0, 0xb1, + 0x41, 0x95, 0xf4, 0xec, 0xc7, 0x40, 0x94, 0xfe, + 0xc6, 0x4f, 0x01, 0xf9, 0x09, 0x29, 0x28, 0x2c, + 0x2f, 0xb3, 0x92, 0x88, 0x15, 0x78, 0x20, 0x8a, + 0xd4, 0x66, 0x82, 0x8b, 0x1c, 0x6c, 0x28, 0x3d, + 0x27, 0x22, 0xcf, 0x0a, 0xd1, 0xab, 0x69, 0x38 +}; + +static const u_char sha384_short4_msg[] = { + 0xa4, 0x1c, 0x49, 0x77, 0x79, 0xc0, 0x37, 0x5f, + 0xf1, 0x0a, 0x7f, 0x4e, 0x08, 0x59, 0x17, 0x39 +}; + +static const u_char sha384_short4_msg_digest[] = { + 0xc9, 0xa6, 0x84, 0x43, 0xa0, 0x05, 0x81, 0x22, + 0x56, 0xb8, 0xec, 0x76, 0xb0, 0x05, 0x16, 0xf0, + 0xdb, 0xb7, 0x4f, 0xab, 0x26, 0xd6, 0x65, 0x91, + 0x3f, 0x19, 0x4b, 0x6f, 0xfb, 0x0e, 0x91, 0xea, + 0x99, 0x67, 0x56, 0x6b, 0x58, 0x10, 0x9c, 0xbc, + 0x67, 0x5c, 0xc2, 0x08, 0xe4, 0xc8, 0x23, 0xf7 +}; + +static const u_char sha384_long2_msg[] = { + 0x39, 0x96, 0x69, 0xe2, 0x8f, 0x6b, 0x9c, 0x6d, + 0xbc, 0xbb, 0x69, 0x12, 0xec, 0x10, 0xff, 0xcf, + 0x74, 0x79, 0x03, 0x49, 0xb7, 0xdc, 0x8f, 0xbe, + 0x4a, 0x8e, 0x7b, 0x3b, 0x56, 0x21, 0xdb, 0x0f, + 0x3e, 0x7d, 0xc8, 0x7f, 0x82, 0x32, 0x64, 0xbb, + 0xe4, 0x0d, 0x18, 0x11, 0xc9, 0xea, 0x20, 0x61, + 0xe1, 0xc8, 0x4a, 0xd1, 0x0a, 0x23, 0xfa, 0xc1, + 0x72, 0x7e, 0x72, 0x02, 0xfc, 0x3f, 0x50, 0x42, + 0xe6, 0xbf, 0x58, 0xcb, 0xa8, 0xa2, 0x74, 0x6e, + 0x1f, 0x64, 0xf9, 0xb9, 0xea, 0x35, 0x2c, 0x71, + 0x15, 0x07, 0x05, 0x3c, 0xf4, 0xe5, 0x33, 0x9d, + 0x52, 0x86, 0x5f, 0x25, 0xcc, 0x22, 0xb5, 0xe8, + 0x77, 0x84, 0xa1, 0x2f, 0xc9, 0x61, 0xd6, 0x6c, + 0xb6, 0xe8, 0x95, 0x73, 0x19, 0x9a, 0x2c, 0xe6, + 0x56, 0x5c, 0xbd, 0xf1, 0x3d, 0xca, 0x40, 0x38, + 0x32, 0xcf, 0xcb, 0x0e, 0x8b, 0x72, 0x11, 0xe8, + 0x3a, 0xf3, 0x2a, 0x11, 0xac, 0x17, 0x92, 0x9f, + 0xf1, 0xc0, 0x73, 0xa5, 0x1c, 0xc0, 0x27, 0xaa, + 0xed, 0xef, 0xf8, 0x5a, 0xad, 0x7c, 0x2b, 0x7c, + 0x5a, 0x80, 0x3e, 0x24, 0x04, 0xd9, 0x6d, 0x2a, + 0x77, 0x35, 0x7b, 0xda, 0x1a, 0x6d, 0xae, 0xed, + 0x17, 0x15, 0x1c, 0xb9, 0xbc, 0x51, 0x25, 0xa4, + 0x22, 0xe9, 0x41, 0xde, 0x0c, 0xa0, 0xfc, 0x50, + 0x11, 0xc2, 0x3e, 0xcf, 0xfe, 0xfd, 0xd0, 0x96, + 0x76, 0x71, 0x1c, 0xf3, 0xdb, 0x0a, 0x34, 0x40, + 0x72, 0x0e ,0x16, 0x15, 0xc1, 0xf2, 0x2f, 0xbc, + 0x3c, 0x72, 0x1d, 0xe5, 0x21, 0xe1, 0xb9, 0x9b, + 0xa1, 0xbd, 0x55, 0x77, 0x40, 0x86, 0x42, 0x14, + 0x7e, 0xd0, 0x96 +}; + +static const u_char sha384_long2_msg_digest[] = { + 0x4f, 0x44, 0x0d, 0xb1, 0xe6, 0xed, 0xd2, 0x89, + 0x9f, 0xa3, 0x35, 0xf0, 0x95, 0x15, 0xaa, 0x02, + 0x5e, 0xe1, 0x77, 0xa7, 0x9f, 0x4b, 0x4a, 0xaf, + 0x38, 0xe4, 0x2b, 0x5c, 0x4d, 0xe6, 0x60, 0xf5, + 0xde, 0x8f, 0xb2, 0xa5, 0xb2, 0xfb, 0xd2, 0xa3, + 0xcb, 0xff, 0xd2, 0x0c, 0xff, 0x12, 0x88, 0xc0 +}; + +static const hash_testvector_t sha384_hash_testvectors[] = { + { sizeof(sha384_short2_msg), sha384_short2_msg, sha384_short2_msg_digest }, + { sizeof(sha384_short4_msg), sha384_short4_msg, sha384_short4_msg_digest }, + { sizeof(sha384_long2_msg), sha384_long2_msg, sha384_long2_msg_digest }, + { 0, NULL, NULL } +}; + +/* SHA-512 hash test vectors + * from "The Secure Hash Algorithm Validation System (SHAVS)" + * July 22, 2004, Lawrence E. Bassham III, NIST + */ + +static const u_char sha512_short2_msg[] = { + 0xd0 +}; + +static const u_char sha512_short2_msg_digest[] = { + 0x99, 0x92, 0x20, 0x29, 0x38, 0xe8, 0x82, 0xe7, + 0x3e, 0x20, 0xf6, 0xb6, 0x9e, 0x68, 0xa0, 0xa7, + 0x14, 0x90, 0x90, 0x42, 0x3d, 0x93, 0xc8, 0x1b, + 0xab, 0x3f, 0x21, 0x67, 0x8d, 0x4a, 0xce, 0xee, + 0xe5, 0x0e, 0x4e, 0x8c, 0xaf, 0xad, 0xa4, 0xc8, + 0x5a, 0x54, 0xea, 0x83, 0x06, 0x82, 0x6c, 0x4a, + 0xd6, 0xe7, 0x4c, 0xec, 0xe9, 0x63, 0x1b, 0xfa, + 0x8a, 0x54, 0x9b, 0x4a, 0xb3, 0xfb, 0xba, 0x15 +}; + +static const u_char sha512_short4_msg[] = { + 0x8d, 0x4e, 0x3c, 0x0e, 0x38, 0x89, 0x19, 0x14, + 0x91, 0x81, 0x6e, 0x9d, 0x98, 0xbf, 0xf0, 0xa0 +}; + +static const u_char sha512_short4_msg_digest[] = { + 0xcb, 0x0b, 0x67, 0xa4, 0xb8, 0x71, 0x2c, 0xd7, + 0x3c, 0x9a, 0xab, 0xc0, 0xb1, 0x99, 0xe9, 0x26, + 0x9b, 0x20, 0x84, 0x4a, 0xfb, 0x75, 0xac, 0xbd, + 0xd1, 0xc1, 0x53, 0xc9, 0x82, 0x89, 0x24, 0xc3, + 0xdd, 0xed, 0xaa, 0xfe, 0x66, 0x9c, 0x5f, 0xdd, + 0x0b, 0xc6, 0x6f, 0x63, 0x0f, 0x67, 0x73, 0x98, + 0x82, 0x13, 0xeb, 0x1b, 0x16, 0xf5, 0x17, 0xad, + 0x0d, 0xe4, 0xb2, 0xf0, 0xc9, 0x5c, 0x90, 0xf8 +}; + +static const u_char sha512_long2_msg[] = { + 0xa5, 0x5f, 0x20, 0xc4, 0x11, 0xaa, 0xd1, 0x32, + 0x80, 0x7a, 0x50, 0x2d, 0x65, 0x82, 0x4e, 0x31, + 0xa2, 0x30, 0x54, 0x32, 0xaa, 0x3d, 0x06, 0xd3, + 0xe2, 0x82, 0xa8, 0xd8, 0x4e, 0x0d, 0xe1, 0xde, + 0x69, 0x74, 0xbf, 0x49, 0x54, 0x69, 0xfc, 0x7f, + 0x33, 0x8f, 0x80, 0x54, 0xd5, 0x8c, 0x26, 0xc4, + 0x93, 0x60, 0xc3, 0xe8, 0x7a, 0xf5, 0x65, 0x23, + 0xac, 0xf6, 0xd8, 0x9d, 0x03, 0xe5, 0x6f, 0xf2, + 0xf8, 0x68, 0x00, 0x2b, 0xc3, 0xe4, 0x31, 0xed, + 0xc4, 0x4d, 0xf2, 0xf0, 0x22, 0x3d, 0x4b, 0xb3, + 0xb2, 0x43, 0x58, 0x6e, 0x1a, 0x7d, 0x92, 0x49, + 0x36, 0x69, 0x4f, 0xcb, 0xba, 0xf8, 0x8d, 0x95, + 0x19, 0xe4, 0xeb, 0x50, 0xa6, 0x44, 0xf8, 0xe4, + 0xf9, 0x5e, 0xb0, 0xea, 0x95, 0xbc, 0x44, 0x65, + 0xc8, 0x82, 0x1a, 0xac, 0xd2, 0xfe, 0x15, 0xab, + 0x49, 0x81, 0x16, 0x4b, 0xbb, 0x6d, 0xc3, 0x2f, + 0x96, 0x90, 0x87, 0xa1, 0x45, 0xb0, 0xd9, 0xcc, + 0x9c, 0x67, 0xc2, 0x2b, 0x76, 0x32, 0x99, 0x41, + 0x9c, 0xc4, 0x12, 0x8b, 0xe9, 0xa0, 0x77, 0xb3, + 0xac, 0xe6, 0x34, 0x06, 0x4e, 0x6d, 0x99, 0x28, + 0x35, 0x13, 0xdc, 0x06, 0xe7, 0x51, 0x5d, 0x0d, + 0x73, 0x13, 0x2e, 0x9a, 0x0d, 0xc6, 0xd3, 0xb1, + 0xf8, 0xb2, 0x46, 0xf1, 0xa9, 0x8a, 0x3f, 0xc7, + 0x29, 0x41, 0xb1, 0xe3, 0xbb, 0x20, 0x98, 0xe8, + 0xbf, 0x16, 0xf2, 0x68, 0xd6, 0x4f, 0x0b, 0x0f, + 0x47, 0x07, 0xfe, 0x1e, 0xa1, 0xa1, 0x79, 0x1b, + 0xa2, 0xf3, 0xc0, 0xc7, 0x58, 0xe5, 0xf5, 0x51, + 0x86, 0x3a, 0x96, 0xc9, 0x49, 0xad, 0x47, 0xd7, + 0xfb, 0x40, 0xd2 +}; + +static const u_char sha512_long2_msg_digest[] = { + 0xc6, 0x65, 0xbe, 0xfb, 0x36, 0xda, 0x18, 0x9d, + 0x78, 0x82, 0x2d, 0x10, 0x52, 0x8c, 0xbf, 0x3b, + 0x12, 0xb3, 0xee, 0xf7, 0x26, 0x03, 0x99, 0x09, + 0xc1, 0xa1, 0x6a, 0x27, 0x0d, 0x48, 0x71, 0x93, + 0x77, 0x96, 0x6b, 0x95, 0x7a, 0x87, 0x8e, 0x72, + 0x05, 0x84, 0x77, 0x9a, 0x62, 0x82, 0x5c, 0x18, + 0xda, 0x26, 0x41, 0x5e, 0x49, 0xa7, 0x17, 0x6a, + 0x89, 0x4e, 0x75, 0x10, 0xfd, 0x14, 0x51, 0xf5 +}; + +static const hash_testvector_t sha512_hash_testvectors[] = { + { sizeof(sha512_short2_msg), sha512_short2_msg, sha512_short2_msg_digest }, + { sizeof(sha512_short4_msg), sha512_short4_msg, sha512_short4_msg_digest }, + { sizeof(sha512_long2_msg), sha512_long2_msg, sha512_long2_msg_digest }, + { 0, NULL, NULL } +}; + +/* SHA-256, SHA-384, and SHA-512 hmac test vectors + * from RFC 4231 "Identifiers and Test Vectors for HMAC-SHA-224, + * HMAC-SHA-256, HMAC-SHA-384, and HMAC-SHA-512" + * December 2005, M. Nystrom, RSA Security + */ + +static const u_char sha2_hmac1_key[] = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b +}; + +static const u_char sha2_hmac1_msg[] = { + 0x48, 0x69, 0x20, 0x54, 0x68, 0x65, 0x72, 0x65 +}; + +static const u_char sha2_hmac1_256[] = { + 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, + 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0x0b, 0xf1, 0x2b, + 0x88, 0x1d, 0xc2, 0x00, 0xc9, 0x83, 0x3d, 0xa7, + 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32, 0xcf, 0xf7 +}; + +static const u_char sha2_hmac1_384[] = { + 0xaf, 0xd0, 0x39, 0x44, 0xd8, 0x48, 0x95, 0x62, + 0x6b, 0x08, 0x25, 0xf4, 0xab ,0x46, 0x90, 0x7f, + 0x15, 0xf9, 0xda, 0xdb, 0xe4, 0x10, 0x1e, 0xc6, + 0x82, 0xaa, 0x03, 0x4c, 0x7c, 0xeb, 0xc5, 0x9c, + 0xfa, 0xea, 0x9e, 0xa9, 0x07, 0x6e, 0xde, 0x7f, + 0x4a, 0xf1, 0x52, 0xe8, 0xb2, 0xfa, 0x9c, 0xb6 +}; + +static const u_char sha2_hmac1_512[] = { + 0x87, 0xaa, 0x7c, 0xde, 0xa5, 0xef, 0x61, 0x9d, + 0x4f, 0xf0, 0xb4, 0x24, 0x1a, 0x1d, 0x6c, 0xb0, + 0x23, 0x79, 0xf4, 0xe2, 0xce, 0x4e, 0xc2, 0x78, + 0x7a, 0xd0, 0xb3, 0x05, 0x45, 0xe1, 0x7c, 0xde, + 0xda, 0xa8, 0x33, 0xb7, 0xd6, 0xb8, 0xa7, 0x02, + 0x03, 0x8b, 0x27, 0x4e, 0xae, 0xa3, 0xf4, 0xe4, + 0xbe, 0x9d, 0x91, 0x4e, 0xeb, 0x61, 0xf1, 0x70, + 0x2e, 0x69, 0x6c, 0x20, 0x3a, 0x12, 0x68, 0x54 +}; + +static const u_char sha2_hmac2_key[] = { + 0x4a, 0x65, 0x66, 0x65 +}; + +static const u_char sha2_hmac2_msg[] = { + 0x77, 0x68, 0x61, 0x74, 0x20, 0x64, 0x6f, 0x20, + 0x79, 0x61, 0x20, 0x77, 0x61, 0x6e, 0x74, 0x20, + 0x66, 0x6f, 0x72, 0x20, 0x6e, 0x6f, 0x74, 0x68, + 0x69, 0x6e, 0x67, 0x3f +}; + +static const u_char sha2_hmac2_256[] = { + 0x5b, 0xdc, 0xc1, 0x46, 0xbf, 0x60, 0x75, 0x4e, + 0x6a, 0x04, 0x24, 0x26, 0x08, 0x95, 0x75, 0xc7, + 0x5a, 0x00, 0x3f, 0x08, 0x9d, 0x27, 0x39, 0x83, + 0x9d, 0xec, 0x58, 0xb9, 0x64, 0xec, 0x38, 0x43 +}; + +static const u_char sha2_hmac2_384[] = { + 0xaf, 0x45, 0xd2, 0xe3, 0x76, 0x48, 0x40, 0x31, + 0x61, 0x7f, 0x78, 0xd2, 0xb5, 0x8a, 0x6b, 0x1b, + 0x9c, 0x7e, 0xf4, 0x64, 0xf5, 0xa0, 0x1b, 0x47, + 0xe4, 0x2e, 0xc3, 0x73, 0x63, 0x22, 0x44, 0x5e, + 0x8e, 0x22, 0x40, 0xca, 0x5e, 0x69, 0xe2, 0xc7, + 0x8b, 0x32, 0x39, 0xec, 0xfa, 0xb2, 0x16, 0x49 +}; + +static const u_char sha2_hmac2_512[] = { + 0x16, 0x4b, 0x7a, 0x7b, 0xfc, 0xf8, 0x19, 0xe2, + 0xe3, 0x95, 0xfb, 0xe7, 0x3b, 0x56, 0xe0, 0xa3, + 0x87, 0xbd, 0x64, 0x22, 0x2e, 0x83, 0x1f, 0xd6, + 0x10, 0x27, 0x0c, 0xd7, 0xea, 0x25, 0x05, 0x54, + 0x97, 0x58, 0xbf, 0x75, 0xc0, 0x5a, 0x99, 0x4a, + 0x6d, 0x03, 0x4f, 0x65, 0xf8, 0xf0, 0xe6, 0xfd, + 0xca, 0xea, 0xb1, 0xa3, 0x4d, 0x4a, 0x6b, 0x4b, + 0x63, 0x6e, 0x07, 0x0a, 0x38, 0xbc, 0xe7, 0x37 +}; + +static const u_char sha2_hmac3_key[] = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa +}; + +static const u_char sha2_hmac3_msg[] = { + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd +}; + +static const u_char sha2_hmac3_256[] = { + 0x77, 0x3e, 0xa9, 0x1e, 0x36, 0x80, 0x0e, 0x46, + 0x85, 0x4d, 0xb8, 0xeb, 0xd0, 0x91, 0x81, 0xa7, + 0x29, 0x59, 0x09, 0x8b, 0x3e, 0xf8, 0xc1, 0x22, + 0xd9, 0x63, 0x55, 0x14, 0xce, 0xd5, 0x65, 0xfe +}; + +static const u_char sha2_hmac3_384[] = { + 0x88, 0x06, 0x26, 0x08, 0xd3, 0xe6, 0xad, 0x8a, + 0x0a, 0xa2, 0xac, 0xe0, 0x14, 0xc8, 0xa8, 0x6f, + 0x0a, 0xa6, 0x35, 0xd9, 0x47, 0xac, 0x9f, 0xeb, + 0xe8, 0x3e, 0xf4, 0xe5, 0x59, 0x66, 0x14, 0x4b, + 0x2a, 0x5a, 0xb3, 0x9d, 0xc1, 0x38, 0x14, 0xb9, + 0x4e, 0x3a, 0xb6, 0xe1, 0x01, 0xa3, 0x4f, 0x27 +}; + +static const u_char sha2_hmac3_512[] = { + 0xfa, 0x73, 0xb0, 0x08, 0x9d, 0x56, 0xa2, 0x84, + 0xef, 0xb0, 0xf0, 0x75, 0x6c, 0x89, 0x0b, 0xe9, + 0xb1, 0xb5, 0xdb, 0xdd, 0x8e, 0xe8, 0x1a, 0x36, + 0x55, 0xf8, 0x3e, 0x33, 0xb2, 0x27, 0x9d, 0x39, + 0xbf, 0x3e, 0x84, 0x82, 0x79, 0xa7, 0x22, 0xc8, + 0x06, 0xb4, 0x85, 0xa4, 0x7e, 0x67, 0xc8, 0x07, + 0xb9, 0x46, 0xa3, 0x37, 0xbe, 0xe8, 0x94, 0x26, + 0x74, 0x27, 0x88, 0x59, 0xe1, 0x32, 0x92, 0xfb +}; + +static const u_char sha2_hmac4_key[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19 +}; + +static const u_char sha2_hmac4_msg[] = { + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd +}; + +static const u_char sha2_hmac4_256[] = { + 0x82, 0x55, 0x8a, 0x38, 0x9a, 0x44, 0x3c, 0x0e, + 0xa4, 0xcc, 0x81, 0x98, 0x99, 0xf2, 0x08, 0x3a, + 0x85, 0xf0, 0xfa, 0xa3, 0xe5, 0x78, 0xf8, 0x07, + 0x7a, 0x2e, 0x3f, 0xf4, 0x67, 0x29, 0x66, 0x5b +}; + +static const u_char sha2_hmac4_384[] = { + 0x3e, 0x8a, 0x69, 0xb7, 0x78, 0x3c, 0x25, 0x85, + 0x19, 0x33, 0xab, 0x62, 0x90, 0xaf, 0x6c, 0xa7, + 0x7a, 0x99, 0x81, 0x48, 0x08, 0x50, 0x00, 0x9c, + 0xc5, 0x57, 0x7c, 0x6e, 0x1f, 0x57, 0x3b, 0x4e, + 0x68, 0x01, 0xdd, 0x23, 0xc4, 0xa7, 0xd6, 0x79, + 0xcc, 0xf8, 0xa3, 0x86, 0xc6, 0x74, 0xcf, 0xfb +}; + +static const u_char sha2_hmac4_512[] = { + 0xb0, 0xba, 0x46, 0x56, 0x37, 0x45, 0x8c, 0x69, + 0x90, 0xe5, 0xa8, 0xc5, 0xf6, 0x1d, 0x4a, 0xf7, + 0xe5, 0x76, 0xd9, 0x7f, 0xf9, 0x4b, 0x87, 0x2d, + 0xe7, 0x6f, 0x80, 0x50, 0x36, 0x1e, 0xe3, 0xdb, + 0xa9, 0x1c, 0xa5, 0xc1, 0x1a, 0xa2, 0x5e, 0xb4, + 0xd6, 0x79, 0x27, 0x5c, 0xc5, 0x78, 0x80, 0x63, + 0xa5, 0xf1, 0x97, 0x41, 0x12, 0x0c, 0x4f, 0x2d, + 0xe2, 0xad, 0xeb, 0xeb, 0x10, 0xa2, 0x98, 0xdd +}; + +static const u_char sha2_hmac6_key[] = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa +}; + +static const u_char sha2_hmac6_msg[] = { + 0x54, 0x65, 0x73, 0x74, 0x20, 0x55, 0x73, 0x69, + 0x6e, 0x67, 0x20, 0x4c, 0x61, 0x72, 0x67, 0x65, + 0x72, 0x20, 0x54, 0x68, 0x61, 0x6e, 0x20, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x2d, 0x53, 0x69, 0x7a, + 0x65, 0x20, 0x4b, 0x65, 0x79, 0x20, 0x2d, 0x20, + 0x48, 0x61, 0x73, 0x68, 0x20, 0x4b, 0x65, 0x79, + 0x20, 0x46, 0x69, 0x72, 0x73, 0x74 +}; + +static const u_char sha2_hmac6_256[] = { + 0x60, 0xe4, 0x31, 0x59, 0x1e, 0xe0, 0xb6, 0x7f, + 0x0d, 0x8a, 0x26, 0xaa, 0xcb, 0xf5, 0xb7, 0x7f, + 0x8e, 0x0b, 0xc6, 0x21, 0x37, 0x28, 0xc5, 0x14, + 0x05, 0x46, 0x04, 0x0f, 0x0e, 0xe3, 0x7f, 0x54 +}; + +static const u_char sha2_hmac6_384[] = { + 0x4e, 0xce, 0x08, 0x44, 0x85, 0x81, 0x3e, 0x90, + 0x88, 0xd2, 0xc6, 0x3a, 0x04, 0x1b, 0xc5, 0xb4, + 0x4f, 0x9e, 0xf1, 0x01, 0x2a, 0x2b, 0x58, 0x8f, + 0x3c, 0xd1, 0x1f, 0x05, 0x03, 0x3a, 0xc4, 0xc6, + 0x0c, 0x2e, 0xf6, 0xab, 0x40, 0x30, 0xfe, 0x82, + 0x96, 0x24, 0x8d, 0xf1, 0x63, 0xf4, 0x49, 0x52 +}; + +static const u_char sha2_hmac6_512[] = { + 0x80, 0xb2, 0x42, 0x63, 0xc7, 0xc1, 0xa3, 0xeb, + 0xb7, 0x14, 0x93, 0xc1, 0xdd, 0x7b, 0xe8, 0xb4, + 0x9b, 0x46, 0xd1, 0xf4, 0x1b, 0x4a, 0xee, 0xc1, + 0x12, 0x1b, 0x01, 0x37, 0x83, 0xf8, 0xf3, 0x52, + 0x6b, 0x56, 0xd0, 0x37, 0xe0, 0x5f, 0x25, 0x98, + 0xbd, 0x0f, 0xd2, 0x21, 0x5d, 0x6a, 0x1e, 0x52, + 0x95, 0xe6, 0x4f, 0x73, 0xf6, 0x3f, 0x0a, 0xec, + 0x8b, 0x91, 0x5a, 0x98, 0x5d, 0x78, 0x65, 0x98 +}; + +static const u_char sha2_hmac7_msg[] = { + 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, + 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x75, + 0x73, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x6c, + 0x61, 0x72, 0x67, 0x65, 0x72, 0x20, 0x74, 0x68, + 0x61, 0x6e, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x6b, 0x65, + 0x79, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x61, 0x20, + 0x6c, 0x61, 0x72, 0x67, 0x65, 0x72, 0x20, 0x74, + 0x68, 0x61, 0x6e, 0x20, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x20, 0x54, 0x68, 0x65, + 0x20, 0x6b, 0x65, 0x79, 0x20, 0x6e, 0x65, 0x65, + 0x64, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, + 0x20, 0x68, 0x61, 0x73, 0x68, 0x65, 0x64, 0x20, + 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x20, 0x62, + 0x65, 0x69, 0x6e, 0x67, 0x20, 0x75, 0x73, 0x65, + 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x48, 0x4d, 0x41, 0x43, 0x20, 0x61, 0x6c, + 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x2e +}; + +static const u_char sha2_hmac7_256[] = { + 0x9b, 0x09, 0xff, 0xa7, 0x1b, 0x94, 0x2f, 0xcb, + 0x27, 0x63, 0x5f, 0xbc, 0xd5, 0xb0, 0xe9, 0x44, + 0xbf, 0xdc, 0x63, 0x64, 0x4f, 0x07, 0x13, 0x93, + 0x8a, 0x7f, 0x51, 0x53, 0x5c, 0x3a, 0x35, 0xe2 +}; + +static const u_char sha2_hmac7_384[] = { + 0x66, 0x17, 0x17, 0x8e, 0x94, 0x1f, 0x02, 0x0d, + 0x35, 0x1e, 0x2f, 0x25, 0x4e, 0x8f, 0xd3, 0x2c, + 0x60, 0x24, 0x20, 0xfe, 0xb0, 0xb8, 0xfb, 0x9a, + 0xdc, 0xce, 0xbb, 0x82, 0x46, 0x1e, 0x99, 0xc5, + 0xa6, 0x78, 0xcc, 0x31, 0xe7, 0x99, 0x17, 0x6d, + 0x38, 0x60, 0xe6, 0x11, 0x0c, 0x46, 0x52, 0x3e +}; + +static const u_char sha2_hmac7_512[] = { + 0xe3, 0x7b, 0x6a, 0x77, 0x5d, 0xc8, 0x7d, 0xba, + 0xa4, 0xdf, 0xa9, 0xf9, 0x6e, 0x5e, 0x3f, 0xfd, + 0xde, 0xbd, 0x71, 0xf8, 0x86, 0x72, 0x89, 0x86, + 0x5d, 0xf5, 0xa3, 0x2d, 0x20, 0xcd, 0xc9, 0x44, + 0xb6, 0x02, 0x2c, 0xac, 0x3c, 0x49, 0x82, 0xb1, + 0x0d, 0x5e, 0xeb, 0x55, 0xc3, 0xe4, 0xde, 0x15, + 0x13, 0x46, 0x76, 0xfb, 0x6d, 0xe0, 0x44, 0x60, + 0x65, 0xc9, 0x74, 0x40, 0xfa, 0x8c, 0x6a, 0x58 +}; + +static const hmac_testvector_t sha256_hmac_testvectors[] = { + { sizeof(sha2_hmac1_key), sha2_hmac1_key, sizeof(sha2_hmac1_msg), sha2_hmac1_msg, sha2_hmac1_256 }, + { sizeof(sha2_hmac2_key), sha2_hmac2_key, sizeof(sha2_hmac2_msg), sha2_hmac2_msg, sha2_hmac2_256 }, + { sizeof(sha2_hmac3_key), sha2_hmac3_key, sizeof(sha2_hmac3_msg), sha2_hmac3_msg, sha2_hmac3_256 }, + { sizeof(sha2_hmac4_key), sha2_hmac4_key, sizeof(sha2_hmac4_msg), sha2_hmac4_msg, sha2_hmac4_256 }, + { sizeof(sha2_hmac6_key), sha2_hmac6_key, sizeof(sha2_hmac6_msg), sha2_hmac6_msg, sha2_hmac6_256 }, + { sizeof(sha2_hmac6_key), sha2_hmac6_key, sizeof(sha2_hmac7_msg), sha2_hmac7_msg, sha2_hmac7_256 }, + { 0, NULL, 0, NULL, NULL } +}; + +static const hmac_testvector_t sha384_hmac_testvectors[] = { + { sizeof(sha2_hmac1_key), sha2_hmac1_key, sizeof(sha2_hmac1_msg), sha2_hmac1_msg, sha2_hmac1_384 }, + { sizeof(sha2_hmac2_key), sha2_hmac2_key, sizeof(sha2_hmac2_msg), sha2_hmac2_msg, sha2_hmac2_384 }, + { sizeof(sha2_hmac3_key), sha2_hmac3_key, sizeof(sha2_hmac3_msg), sha2_hmac3_msg, sha2_hmac3_384 }, + { sizeof(sha2_hmac4_key), sha2_hmac4_key, sizeof(sha2_hmac4_msg), sha2_hmac4_msg, sha2_hmac4_384 }, + { sizeof(sha2_hmac6_key), sha2_hmac6_key, sizeof(sha2_hmac6_msg), sha2_hmac6_msg, sha2_hmac6_384 }, + { sizeof(sha2_hmac6_key), sha2_hmac6_key, sizeof(sha2_hmac7_msg), sha2_hmac7_msg, sha2_hmac7_384 }, + { 0, NULL, 0, NULL, NULL } +}; + +static const hmac_testvector_t sha512_hmac_testvectors[] = { + { sizeof(sha2_hmac1_key), sha2_hmac1_key, sizeof(sha2_hmac1_msg), sha2_hmac1_msg, sha2_hmac1_512 }, + { sizeof(sha2_hmac2_key), sha2_hmac2_key, sizeof(sha2_hmac2_msg), sha2_hmac2_msg, sha2_hmac2_512 }, + { sizeof(sha2_hmac3_key), sha2_hmac3_key, sizeof(sha2_hmac3_msg), sha2_hmac3_msg, sha2_hmac3_512 }, + { sizeof(sha2_hmac4_key), sha2_hmac4_key, sizeof(sha2_hmac4_msg), sha2_hmac4_msg, sha2_hmac4_512 }, + { sizeof(sha2_hmac6_key), sha2_hmac6_key, sizeof(sha2_hmac6_msg), sha2_hmac6_msg, sha2_hmac6_512 }, + { sizeof(sha2_hmac6_key), sha2_hmac6_key, sizeof(sha2_hmac7_msg), sha2_hmac7_msg, sha2_hmac7_512 }, + { 0, NULL, 0, NULL, NULL } +}; + +struct hash_desc hash_desc_sha2_256 = { + algo_type: IKE_ALG_HASH, + algo_id: OAKLEY_SHA2_256, + algo_next: NULL, + hash_ctx_size: sizeof(sha256_context), + hash_block_size: SHA2_256_BLOCK_SIZE, + hash_digest_size: SHA2_256_DIGEST_SIZE, + hash_testvectors: sha256_hash_testvectors, + hmac_testvectors: sha256_hmac_testvectors, + hash_init: (void (*)(void *))sha256_init, + hash_update: (void (*)(void *, const u_char *, size_t ))sha256_write, + hash_final:(void (*)(u_char *, void *))sha256_hash_final +}; + +struct hash_desc hash_desc_sha2_384 = { + algo_type: IKE_ALG_HASH, + algo_id: OAKLEY_SHA2_384, + algo_next: NULL, + hash_ctx_size: sizeof(sha512_context), + hash_block_size: SHA2_384_BLOCK_SIZE, + hash_digest_size: SHA2_384_DIGEST_SIZE, + hash_testvectors: sha384_hash_testvectors, + hmac_testvectors: sha384_hmac_testvectors, + hash_init: (void (*)(void *))sha384_init, + hash_update: (void (*)(void *, const u_char *, size_t ))sha512_write, + hash_final:(void (*)(u_char *, void *))sha384_hash_final +}; + +struct hash_desc hash_desc_sha2_512 = { + algo_type: IKE_ALG_HASH, + algo_id: OAKLEY_SHA2_512, + algo_next: NULL, + hash_ctx_size: sizeof(sha512_context), + hash_block_size: SHA2_512_BLOCK_SIZE, + hash_digest_size: SHA2_512_DIGEST_SIZE, + hash_testvectors: sha512_hash_testvectors, + hmac_testvectors: sha512_hmac_testvectors, + hash_init: (void (*)(void *))sha512_init, + hash_update: (void (*)(void *, const u_char *, size_t ))sha512_write, + hash_final:(void (*)(u_char *, void *))sha512_hash_final +}; + +int ike_alg_sha2_init(void); + +int +ike_alg_sha2_init(void) +{ + int ret +; + ret = ike_alg_register_hash(&hash_desc_sha2_256); + if (ret) + goto out; + ret = ike_alg_register_hash(&hash_desc_sha2_384); + if (ret) + goto out; + ret = ike_alg_register_hash(&hash_desc_sha2_512); + +out: + return ret; +} + +/* +IKE_ALG_INIT_NAME: ike_alg_sha2_init +*/ diff --git a/src/pluto/alg/ike_alg_twofish.c b/src/pluto/alg/ike_alg_twofish.c new file mode 100644 index 000000000..1788bc394 --- /dev/null +++ b/src/pluto/alg/ike_alg_twofish.c @@ -0,0 +1,85 @@ +#include <stdio.h> +#include <string.h> +#include <stddef.h> +#include <sys/types.h> +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "libtwofish/twofish_cbc.h" +#include "alg_info.h" +#include "ike_alg.h" + +#define TWOFISH_CBC_BLOCK_SIZE (128/BITS_PER_BYTE) +#define TWOFISH_KEY_MIN_LEN 128 +#define TWOFISH_KEY_DEF_LEN 128 +#define TWOFISH_KEY_MAX_LEN 256 + +static void +do_twofish(u_int8_t *buf, size_t buf_size, u_int8_t *key, size_t key_size, u_int8_t *iv, bool enc) +{ + twofish_context twofish_ctx; + char iv_bak[TWOFISH_CBC_BLOCK_SIZE]; + char *new_iv = NULL; /* logic will avoid copy to NULL */ + + twofish_set_key(&twofish_ctx, key, key_size); + /* + * my TWOFISH cbc does not touch passed IV (optimization for + * ESP handling), so I must "emulate" des-like IV + * crunching + */ + if (!enc) + memcpy(new_iv=iv_bak, + (char*) buf + buf_size-TWOFISH_CBC_BLOCK_SIZE, + TWOFISH_CBC_BLOCK_SIZE); + + twofish_cbc_encrypt(&twofish_ctx, buf, buf, buf_size, iv, enc); + + if (enc) + new_iv = (char*) buf + buf_size-TWOFISH_CBC_BLOCK_SIZE; + + memcpy(iv, new_iv, TWOFISH_CBC_BLOCK_SIZE); +} + +struct encrypt_desc encrypt_desc_twofish = +{ + algo_type: IKE_ALG_ENCRYPT, + algo_id: OAKLEY_TWOFISH_CBC, + algo_next: NULL, + enc_ctxsize: sizeof(twofish_context), + enc_blocksize: TWOFISH_CBC_BLOCK_SIZE, + keydeflen: TWOFISH_KEY_MIN_LEN, + keyminlen: TWOFISH_KEY_DEF_LEN, + keymaxlen: TWOFISH_KEY_MAX_LEN, + do_crypt: do_twofish, +}; + +struct encrypt_desc encrypt_desc_twofish_ssh = +{ + algo_type: IKE_ALG_ENCRYPT, + algo_id: OAKLEY_TWOFISH_CBC_SSH, + algo_next: NULL, + enc_ctxsize: sizeof(twofish_context), + enc_blocksize: TWOFISH_CBC_BLOCK_SIZE, + keydeflen: TWOFISH_KEY_MIN_LEN, + keyminlen: TWOFISH_KEY_DEF_LEN, + keymaxlen: TWOFISH_KEY_MAX_LEN, + do_crypt: do_twofish, +}; + +int ike_alg_twofish_init(void); + +int +ike_alg_twofish_init(void) +{ + int ret = ike_alg_register_enc(&encrypt_desc_twofish); + + if (ike_alg_register_enc(&encrypt_desc_twofish_ssh) < 0) + plog("ike_alg_twofish_init(): Experimental OAKLEY_TWOFISH_CBC_SSH activation failed"); + + return ret; +} +/* +IKE_ALG_INIT_NAME: ike_alg_twofish_init +*/ diff --git a/src/pluto/alg/ike_alginit.c b/src/pluto/alg/ike_alginit.c new file mode 100644 index 000000000..8784bf31b --- /dev/null +++ b/src/pluto/alg/ike_alginit.c @@ -0,0 +1,7 @@ +extern int ike_alg_init(void); int ike_alg_init(void) { +{ extern int ike_alg_aes_init (void); ike_alg_aes_init();} +{ extern int ike_alg_blowfish_init (void); ike_alg_blowfish_init();} +{ extern int ike_alg_serpent_init (void); ike_alg_serpent_init();} +{ extern int ike_alg_sha2_init (void); ike_alg_sha2_init();} +{ extern int ike_alg_twofish_init (void); ike_alg_twofish_init();} +return 0;} diff --git a/src/pluto/alg_info.c b/src/pluto/alg_info.c new file mode 100644 index 000000000..ac5d1672f --- /dev/null +++ b/src/pluto/alg_info.c @@ -0,0 +1,1205 @@ +/* + * Algorithm info parsing and creation functions + * Author: JuanJo Ciarlante <jjo-ipsec@mendoza.gov.ar> + * + * $Id: alg_info.c,v 1.5 2004/09/29 22:42:49 as Exp $ + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ +#include <stddef.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <freeswan.h> +#include <ipsec_policy.h> +#include <pfkeyv2.h> + +#include "alg_info.h" +#include "constants.h" +#ifndef NO_PLUTO +#include "defs.h" +#include "log.h" +#include "whack.h" +#include "sha1.h" +#include "md5.h" +#include "crypto.h" +#include "kernel_alg.h" +#include "ike_alg.h" +#else +/* + * macros/functions for compilation without pluto (eg: spi for manual conns) + */ +#include <assert.h> +#define passert(x) assert(x) +extern int debug; /* eg: spi.c */ +#define DBG(cond, action) { if (debug) { action ; } } +#define DBG_log(x, args...) fprintf(stderr, x "\n" , ##args); +#define RC_LOG_SERIOUS +#define loglog(x, args...) fprintf(stderr, ##args); +#define alloc_thing(thing, name) alloc_bytes(sizeof (thing), name) +void * alloc_bytes(size_t size, const char *name) { + void *p=malloc(size); + if (p == NULL) + fprintf(stderr, "unable to malloc %lu bytes for %s", + (unsigned long) size, name); + memset(p, '\0', size); + return p; +} +#define pfreeany(ptr) free(ptr) +#endif /* NO_PLUTO */ + +/* + * sadb/ESP aa attrib converters + */ +int +alg_info_esp_aa2sadb(int auth) +{ + int sadb_aalg = 0; + + switch(auth) { + case AUTH_ALGORITHM_HMAC_MD5: + case AUTH_ALGORITHM_HMAC_SHA1: + sadb_aalg = auth + 1; + break; + case AUTH_ALGORITHM_HMAC_SHA2_256: + case AUTH_ALGORITHM_HMAC_SHA2_384: + case AUTH_ALGORITHM_HMAC_SHA2_512: + case AUTH_ALGORITHM_HMAC_RIPEMD: + sadb_aalg = auth; + break; + default: + /* loose ... */ + sadb_aalg = auth; + } + return sadb_aalg; +} + +int /* __attribute__ ((unused)) */ +alg_info_esp_sadb2aa(int sadb_aalg) +{ + int auth = 0; + + switch(sadb_aalg) { + case SADB_AALG_MD5_HMAC: + case SADB_AALG_SHA1_HMAC: + auth = sadb_aalg - 1; + break; + /* since they are the same ... :) */ + case AUTH_ALGORITHM_HMAC_SHA2_256: + case AUTH_ALGORITHM_HMAC_SHA2_384: + case AUTH_ALGORITHM_HMAC_SHA2_512: + case AUTH_ALGORITHM_HMAC_RIPEMD: + auth = sadb_aalg; + break; + default: + /* loose ... */ + auth = sadb_aalg; + } + return auth; +} + +/* + * Search enum_name array with in prefixed uppercase + */ +static int +enum_search_prefix (enum_names *ed, const char *prefix, const char *str, int strlen) +{ + char buf[64]; + char *ptr; + int ret; + int len = sizeof(buf) - 1; /* reserve space for final \0 */ + + for (ptr = buf; *prefix; *ptr++ = *prefix++, len--); + while (strlen-- && len-- && *str) *ptr++ = toupper(*str++); + *ptr = 0; + + DBG(DBG_CRYPT, + DBG_log("enum_search_prefix () calling enum_search(%p, \"%s\")" + , ed, buf) + ) + ret = enum_search(ed, buf); + return ret; +} + +/* + * Search enum_name array with in prefixed and postfixed uppercase + */ +static int +enum_search_ppfix (enum_names *ed, const char *prefix, const char *postfix, const char *str, int strlen) +{ + char buf[64]; + char *ptr; + int ret; + int len = sizeof(buf) - 1; /* reserve space for final \0 */ + + for (ptr = buf; *prefix; *ptr++ = *prefix++, len--); + while (strlen-- && len-- && *str) *ptr++ = toupper(*str++); + while (len-- && *postfix) *ptr++ = *postfix++; + *ptr = 0; + + DBG(DBG_CRYPT, + DBG_log("enum_search_ppfixi () calling enum_search(%p, \"%s\")" + , ed, buf) + ) + ret = enum_search(ed, buf); + return ret; +} + +/* + * Search esp_transformid_names for a match, eg: + * "3des" <=> "ESP_3DES" + */ +#define ESP_MAGIC_ID 0x00ffff01 + +static int +ealg_getbyname_esp(const char *const str, int len) +{ + if (!str || !*str) + return -1; + + /* leave special case for eg: "id248" string */ + if (strcmp("id", str) == 0) + return ESP_MAGIC_ID; + + return enum_search_prefix(&esp_transformid_names, "ESP_", str, len); +} + +/* + * Search auth_alg_names for a match, eg: + * "md5" <=> "AUTH_ALGORITHM_HMAC_MD5" + */ +static int +aalg_getbyname_esp(const char *const str, int len) +{ + int ret; + unsigned num; + + if (!str || !*str) + return -1; + + /* interpret 'SHA' as 'SHA1' */ + if (strncasecmp("SHA", str, len) == 0) + return enum_search(&auth_alg_names, "AUTH_ALGORITHM_HMAC_SHA1"); + + ret = enum_search_prefix(&auth_alg_names,"AUTH_ALGORITHM_HMAC_", str ,len); + if (ret >= 0) + return ret; + + ret = enum_search_prefix(&auth_alg_names,"AUTH_ALGORITHM_", str, len); + if (ret >= 0) + return ret; + + sscanf(str, "id%d%n", &ret, &num); + return (ret >= 0 && num != strlen(str))? -1 : ret; +} + +static int +modp_getbyname_esp(const char *const str, int len) +{ + int ret; + + if (!str || !*str) + return -1; + + ret = enum_search_prefix(&oakley_group_names,"OAKLEY_GROUP_", str, len); + if (ret >= 0) + return ret; + + ret = enum_search_ppfix(&oakley_group_names, "OAKLEY_GROUP_", " (extension)", str, len); + return ret; +} + +void +alg_info_free(struct alg_info *alg_info) +{ + pfreeany(alg_info); +} + +/* + * Raw add routine: only checks for no duplicates + */ +static void +__alg_info_esp_add (struct alg_info_esp *alg_info, int ealg_id, unsigned ek_bits, int aalg_id, unsigned ak_bits) +{ + struct esp_info *esp_info=alg_info->esp; + unsigned cnt = alg_info->alg_info_cnt, i; + + /* check for overflows */ + passert(cnt < elemsof(alg_info->esp)); + + /* dont add duplicates */ + for (i = 0; i < cnt; i++) + { + if (esp_info[i].esp_ealg_id == ealg_id + && (!ek_bits || esp_info[i].esp_ealg_keylen == ek_bits) + && esp_info[i].esp_aalg_id == aalg_id + && (!ak_bits || esp_info[i].esp_aalg_keylen == ak_bits)) + return; + } + + esp_info[cnt].esp_ealg_id = ealg_id; + esp_info[cnt].esp_ealg_keylen = ek_bits; + esp_info[cnt].esp_aalg_id = aalg_id; + esp_info[cnt].esp_aalg_keylen = ak_bits; + + /* sadb values */ + esp_info[cnt].encryptalg = ealg_id; + esp_info[cnt].authalg = alg_info_esp_aa2sadb(aalg_id); + alg_info->alg_info_cnt++; + + DBG(DBG_CRYPT, + DBG_log("__alg_info_esp_add() ealg=%d aalg=%d cnt=%d" + , ealg_id, aalg_id, alg_info->alg_info_cnt) + ) +} + +/* + * Add ESP alg info _with_ logic (policy): + */ +static void +alg_info_esp_add (struct alg_info *alg_info, int ealg_id, int ek_bits, int aalg_id, int ak_bits) +{ + /* Policy: default to 3DES */ + if (ealg_id == 0) + ealg_id = ESP_3DES; + + if (ealg_id > 0) + { +#ifndef NO_PLUTO + if (aalg_id > 0) +#else + /* Allow no auth for manual conns (from spi.c) */ + if (aalg_id >= 0) +#endif + __alg_info_esp_add((struct alg_info_esp *)alg_info, + ealg_id, ek_bits, + aalg_id, ak_bits); + else + { + /* Policy: default to MD5 and SHA1 */ + __alg_info_esp_add((struct alg_info_esp *)alg_info, + ealg_id, ek_bits, + AUTH_ALGORITHM_HMAC_MD5, ak_bits); + __alg_info_esp_add((struct alg_info_esp *)alg_info, + ealg_id, ek_bits, + AUTH_ALGORITHM_HMAC_SHA1, ak_bits); + } + } +} + +#ifndef NO_PLUTO +/************************************** + * + * IKE alg + * + *************************************/ +/* + * Search oakley_enc_names for a match, eg: + * "3des_cbc" <=> "OAKLEY_3DES_CBC" + */ +static int +ealg_getbyname_ike(const char *const str, int len) +{ + int ret; + + if (!str || !*str) + return -1; + + ret = enum_search_prefix(&oakley_enc_names,"OAKLEY_", str, len); + if (ret >= 0) + return ret; + + ret = enum_search_ppfix(&oakley_enc_names, "OAKLEY_", "_CBC", str, len); + return ret; +} + +/* + * Search oakley_hash_names for a match, eg: + * "md5" <=> "OAKLEY_MD5" + */ +static int +aalg_getbyname_ike(const char *const str, int len) +{ + int ret; + unsigned num; + + if (!str || !*str) + return -1; + + /* interpret 'SHA1' as 'SHA' */ + if (strncasecmp("SHA1", str, len) == 0) + return enum_search(&oakley_hash_names, "OAKLEY_SHA"); + + ret = enum_search_prefix(&oakley_hash_names,"OAKLEY_", str, len); + if (ret >= 0) + return ret; + + sscanf(str, "id%d%n", &ret, &num); + return (ret >=0 && num != strlen(str))? -1 : ret; +} + +/* + * Search oakley_group_names for a match, eg: + * "modp1024" <=> "OAKLEY_GROUP_MODP1024" + */ +static int +modp_getbyname_ike(const char *const str, int len) +{ + int ret; + + if (!str || !*str) + return -1; + + ret = enum_search_prefix(&oakley_group_names,"OAKLEY_GROUP_", str, len); + if (ret >= 0) + return ret; + + ret = enum_search_ppfix(&oakley_group_names, "OAKLEY_GROUP_", " (extension)", str, len); + return ret; +} + +static void +__alg_info_ike_add (struct alg_info_ike *alg_info, int ealg_id, unsigned ek_bits, int aalg_id, unsigned ak_bits, int modp_id) +{ + struct ike_info *ike_info = alg_info->ike; + unsigned cnt = alg_info->alg_info_cnt; + unsigned i; + + /* check for overflows */ + passert(cnt < elemsof(alg_info->ike)); + + /* dont add duplicates */ + for (i = 0;i < cnt; i++) + { + if (ike_info[i].ike_ealg == ealg_id + && (!ek_bits || ike_info[i].ike_eklen == ek_bits) + && ike_info[i].ike_halg == aalg_id + && (!ak_bits || ike_info[i].ike_hklen == ak_bits) + && ike_info[i].ike_modp==modp_id) + return; + } + + ike_info[cnt].ike_ealg = ealg_id; + ike_info[cnt].ike_eklen = ek_bits; + ike_info[cnt].ike_halg = aalg_id; + ike_info[cnt].ike_hklen = ak_bits; + ike_info[cnt].ike_modp = modp_id; + alg_info->alg_info_cnt++; + + DBG(DBG_CRYPT, + DBG_log("__alg_info_ike_add() ealg=%d aalg=%d modp_id=%d, cnt=%d" + , ealg_id, aalg_id, modp_id + , alg_info->alg_info_cnt) + ) +} + +/* + * Proposals will be built by looping over default_ike_groups array and + * merging alg_info (ike_info) contents + */ + +static int default_ike_groups[] = { + OAKLEY_GROUP_MODP1536, + OAKLEY_GROUP_MODP1024 +}; + +/* + * Add IKE alg info _with_ logic (policy): + */ +static void +alg_info_ike_add (struct alg_info *alg_info, int ealg_id, int ek_bits, int aalg_id, int ak_bits, int modp_id) +{ + int i = 0; + int n_groups = elemsof(default_ike_groups); + + /* if specified modp_id avoid loop over default_ike_groups */ + if (modp_id) + { + n_groups=0; + goto in_loop; + } + + for (; n_groups--; i++) + { + modp_id = default_ike_groups[i]; +in_loop: + /* Policy: default to 3DES */ + if (ealg_id == 0) + ealg_id = OAKLEY_3DES_CBC; + + if (ealg_id > 0) + { + if (aalg_id > 0) + __alg_info_ike_add((struct alg_info_ike *)alg_info, + ealg_id, ek_bits, + aalg_id, ak_bits, + modp_id); + else + { + /* Policy: default to MD5 and SHA */ + __alg_info_ike_add((struct alg_info_ike *)alg_info, + ealg_id, ek_bits, + OAKLEY_MD5, ak_bits, + modp_id); + __alg_info_ike_add((struct alg_info_ike *)alg_info, + ealg_id, ek_bits, + OAKLEY_SHA, ak_bits, + modp_id); + } + } + } +} +#endif /* NO_PLUTO */ + +/* + * Creates a new alg_info by parsing passed string + */ +enum parser_state_esp { + ST_INI, + ST_EA, /* encrypt algo */ + ST_EA_END, + ST_EK, /* enc. key length */ + ST_EK_END, + ST_AA, /* auth algo */ + ST_AA_END, + ST_AK, /* auth. key length */ + ST_AK_END, + ST_MODP, /* modp spec */ + ST_FLAG_STRICT, + ST_END, + ST_EOF, + ST_ERR +}; + +static const char *parser_state_esp_names[] = { + "ST_INI", + "ST_EA", + "ST_EA_END", + "ST_EK", + "ST_EK_END", + "ST_AA", + "ST_AA_END", + "ST_AK", + "ST_AK_END", + "ST_MOPD", + "ST_FLAG_STRICT", + "ST_END", + "ST_EOF", + "ST_ERR" +}; + +static const char* +parser_state_name_esp(enum parser_state_esp state) +{ + return parser_state_esp_names[state]; +} + +/* XXX:jjo to implement different parser for ESP and IKE */ +struct parser_context { + unsigned state, old_state; + unsigned protoid; + char ealg_buf[16]; + char aalg_buf[16]; + char modp_buf[16]; + int (*ealg_getbyname)(const char *const str, int len); + int (*aalg_getbyname)(const char *const str, int len); + int (*modp_getbyname)(const char *const str, int len); + char *ealg_str; + char *aalg_str; + char *modp_str; + int eklen; + int aklen; + int ch; + const char *err; +}; + +static inline void +parser_set_state(struct parser_context *p_ctx, enum parser_state_esp state) +{ + if (state != p_ctx->state) + { + p_ctx->old_state = p_ctx->state; + p_ctx->state = state; + } +} + +static int +parser_machine(struct parser_context *p_ctx) +{ + int ch = p_ctx->ch; + + /* special 'absolute' cases */ + p_ctx->err = "No error."; + + /* chars that end algo strings */ + switch (ch){ + case 0: /* end-of-string */ + case '!': /* flag as strict algo list */ + case ',': /* algo string separator */ + switch (p_ctx->state) { + case ST_EA: + case ST_EK: + case ST_AA: + case ST_AK: + case ST_MODP: + case ST_FLAG_STRICT: + { + enum parser_state_esp next_state = 0; + + switch (ch) { + case 0: + next_state = ST_EOF; + break; + case ',': + next_state = ST_END; + break; + case '!': + next_state = ST_FLAG_STRICT; + break; + } + /* ch? parser_set_state(p_ctx, ST_END) : parser_set_state(p_ctx, ST_EOF) ; */ + parser_set_state(p_ctx, next_state); + goto out; + } + default: + p_ctx->err = "String ended with invalid char"; + goto err; + } + } +re_eval: + switch (p_ctx->state) { + case ST_INI: + if (isspace(ch)) + break; + if (isalnum(ch)) + { + *(p_ctx->ealg_str++) = ch; + parser_set_state(p_ctx, ST_EA); + break; + } + p_ctx->err = "No alphanum. char initially found"; + goto err; + case ST_EA: + if (isalpha(ch) || ch == '_') + { + *(p_ctx->ealg_str++) = ch; + break; + } + if (isdigit(ch)) + { + /* bravely switch to enc keylen */ + *(p_ctx->ealg_str) = 0; + parser_set_state(p_ctx, ST_EK); + goto re_eval; + } + if (ch == '-') + { + *(p_ctx->ealg_str) = 0; + parser_set_state(p_ctx, ST_EA_END); + break; + } + p_ctx->err = "No valid char found after enc alg string"; + goto err; + case ST_EA_END: + if (isdigit(ch)) + { + /* bravely switch to enc keylen */ + parser_set_state(p_ctx, ST_EK); + goto re_eval; + } + if (isalpha(ch)) + { + parser_set_state(p_ctx, ST_AA); + goto re_eval; + } + p_ctx->err = "No alphanum char found after enc alg separator"; + goto err; + case ST_EK: + if (ch == '-') + { + parser_set_state(p_ctx, ST_EK_END); + break; + } + if (isdigit(ch)) + { + p_ctx->eklen = p_ctx->eklen*10 + ch - '0'; + break; + } + p_ctx->err = "Non digit or valid separator found while reading enc keylen"; + goto err; + case ST_EK_END: + if (isalpha(ch)) + { + parser_set_state(p_ctx, ST_AA); + goto re_eval; + } + p_ctx->err = "Non alpha char found after enc keylen end separator"; + goto err; + case ST_AA: + if (ch == '-') + { + *(p_ctx->aalg_str++) = 0; + parser_set_state(p_ctx, ST_AA_END); + break; + } + if (isalnum(ch) || ch == '_') + { + *(p_ctx->aalg_str++) = ch; + break; + } + p_ctx->err = "Non alphanum or valid separator found in auth string"; + goto err; + case ST_AA_END: + if (isdigit(ch)) + { + parser_set_state(p_ctx, ST_AK); + goto re_eval; + } + /* Only allow modpXXXX string if we have a modp_getbyname method */ + if ((p_ctx->modp_getbyname) && isalpha(ch)) + { + parser_set_state(p_ctx, ST_MODP); + goto re_eval; + } + p_ctx->err = "Non initial digit found for auth keylen"; + goto err; + case ST_AK: + if (ch=='-') + { + parser_set_state(p_ctx, ST_AK_END); + break; + } + if (isdigit(ch)) + { + p_ctx->aklen = p_ctx->aklen*10 + ch - '0'; + break; + } + p_ctx->err = "Non digit found for auth keylen"; + goto err; + case ST_AK_END: + /* Only allow modpXXXX string if we have a modp_getbyname method */ + if ((p_ctx->modp_getbyname) && isalpha(ch)) + { + parser_set_state(p_ctx, ST_MODP); + goto re_eval; + } + p_ctx->err = "Non alpha char found after auth keylen"; + goto err; + case ST_MODP: + if (isalnum(ch)) + { + *(p_ctx->modp_str++) = ch; + break; + } + p_ctx->err = "Non alphanum char found after in modp string"; + goto err; + case ST_FLAG_STRICT: + if (ch == 0) + parser_set_state(p_ctx, ST_END); + p_ctx->err = "Flags character(s) must be at end of whole string"; + goto err; + + /* XXX */ + case ST_END: + case ST_EOF: + case ST_ERR: + break; + /* XXX */ + } +out: + return p_ctx->state; +err: + parser_set_state(p_ctx, ST_ERR); + return ST_ERR; +} + +/* + * Must be called for each "new" char, with new + * character in ctx.ch + */ +static void +parser_init(struct parser_context *p_ctx, unsigned protoid) +{ + memset(p_ctx, 0, sizeof (*p_ctx)); + p_ctx->protoid = protoid; /* XXX: jjo */ + p_ctx->protoid = PROTO_IPSEC_ESP; + p_ctx->ealg_str = p_ctx->ealg_buf; + p_ctx->aalg_str = p_ctx->aalg_buf; + p_ctx->modp_str = p_ctx->modp_buf; + p_ctx->state = ST_INI; + + switch (protoid) { +#ifndef NO_PLUTO + case PROTO_ISAKMP: + p_ctx->ealg_getbyname = ealg_getbyname_ike; + p_ctx->aalg_getbyname = aalg_getbyname_ike; + p_ctx->modp_getbyname = modp_getbyname_ike; + break; +#endif + case PROTO_IPSEC_ESP: + p_ctx->ealg_getbyname = ealg_getbyname_esp; + p_ctx->aalg_getbyname = aalg_getbyname_esp; + break; + } +} + +static int +parser_alg_info_add(struct parser_context *p_ctx, struct alg_info *alg_info) +{ + int ealg_id = 0; + int aalg_id = 0; + int modp_id = 0; +#ifndef NO_PLUTO + const struct oakley_group_desc *gd; +#endif + + if (*p_ctx->ealg_buf) + { + ealg_id = p_ctx->ealg_getbyname(p_ctx->ealg_buf, strlen(p_ctx->ealg_buf)); + if (ealg_id == ESP_MAGIC_ID) + { + ealg_id = p_ctx->eklen; + p_ctx->eklen = 0; + } + if (ealg_id < 0) + { + p_ctx->err = "enc_alg not found"; + return -1; + } + DBG(DBG_CRYPT, + DBG_log("parser_alg_info_add() ealg_getbyname(\"%s\")=%d" + , p_ctx->ealg_buf + , ealg_id) + ) + } + if (*p_ctx->aalg_buf) + { + aalg_id = p_ctx->aalg_getbyname(p_ctx->aalg_buf, strlen(p_ctx->aalg_buf)); + if (aalg_id < 0) + { + p_ctx->err = "hash_alg not found"; + return -1; + } + DBG(DBG_CRYPT, + DBG_log("parser_alg_info_add() aalg_getbyname(\"%s\")=%d" + , p_ctx->aalg_buf + , aalg_id) + ) + } + if (p_ctx->modp_getbyname && *p_ctx->modp_buf) + { + modp_id = p_ctx->modp_getbyname(p_ctx->modp_buf, strlen(p_ctx->modp_buf)); + if (modp_id < 0) + { + p_ctx->err = "modp group not found"; + return -1; + } + DBG(DBG_CRYPT, + DBG_log("parser_alg_info_add() modp_getbyname(\"%s\")=%d" + , p_ctx->modp_buf + , modp_id) + ) + } + switch (alg_info->alg_info_protoid) { + case PROTO_IPSEC_ESP: + alg_info_esp_add(alg_info, + ealg_id, p_ctx->eklen, + aalg_id, p_ctx->aklen); + break; +#ifndef NO_PLUTO + case PROTO_ISAKMP: + if (modp_id && !(gd = lookup_group(modp_id))) + { + p_ctx->err = "found modp group id, but not supported"; + return -1; + } + alg_info_ike_add(alg_info, + ealg_id, p_ctx->eklen, + aalg_id, p_ctx->aklen, + modp_id); + break; +#endif + default: + return -1; + } + return 0; +} + +static int +alg_info_parse_str (struct alg_info *alg_info, const char *alg_str, const char **err_p) +{ + struct parser_context ctx; + int ret; + const char *ptr; + static char err_buf[256]; + + *err_buf = 0; + parser_init(&ctx, alg_info->alg_info_protoid); + if (err_p) + *err_p = NULL; + + /* use default if nul esp string */ + if (!*alg_str) + { + switch (alg_info->alg_info_protoid) { +#ifndef NO_PLUTO + case PROTO_ISAKMP: + alg_info_ike_add(alg_info, 0, 0, 0, 0, 0); + return 0; +#endif + case PROTO_IPSEC_ESP: + alg_info_esp_add(alg_info, 0, 0, 0, 0); + return 0; + default: + /* IMPOSSIBLE */ + passert(alg_info->alg_info_protoid); + } + } + + for (ret = 0, ptr = alg_str; ret < ST_EOF;) + { + ctx.ch = *ptr++; + ret = parser_machine(&ctx); + + switch (ret) { + case ST_FLAG_STRICT: + alg_info->alg_info_flags |= ALG_INFO_F_STRICT; + break; + case ST_END: + case ST_EOF: + DBG(DBG_CRYPT, + DBG_log("alg_info_parse_str() ealg_buf=%s aalg_buf=%s" + "eklen=%d aklen=%d", + ctx.ealg_buf, ctx.aalg_buf, + ctx.eklen, ctx.aklen) + ) + if (parser_alg_info_add(&ctx, alg_info) < 0) + { + snprintf(err_buf, sizeof(err_buf), + "%s, enc_alg=\"%s\", auth_alg=\"%s\", modp=\"%s\"", + ctx.err, + ctx.ealg_buf, + ctx.aalg_buf, + ctx.modp_buf); + goto err; + } + /* zero out for next run (ST_END) */ + parser_init(&ctx, alg_info->alg_info_protoid); + break; + case ST_ERR: + snprintf(err_buf, sizeof(err_buf), + "%s, just after \"%.*s\" (old_state=%s)", + ctx.err, + (int)(ptr-alg_str-1), alg_str , + parser_state_name_esp(ctx.old_state)); + goto err; + default: + if (!ctx.ch) + break; + } + } + return 0; +err: + if (err_p) + *err_p=err_buf; + return -1; +} + +struct alg_info_esp * +alg_info_esp_create_from_str (const char *alg_str, const char **err_p) +{ + struct alg_info_esp *alg_info_esp; + char esp_buf[256]; + static char err_buf[256]; + char *pfs_name; + int ret = 0; + /* + * alg_info storage should be sized dynamically + * but this may require 2passes to know + * transform count in advance. + */ + alg_info_esp = alloc_thing (struct alg_info_esp, "alg_info_esp"); + if (!alg_info_esp) + goto out; + + pfs_name=index (alg_str, ';'); + if (pfs_name) + { + memcpy(esp_buf, alg_str, pfs_name-alg_str); + esp_buf[pfs_name-alg_str] = 0; + alg_str = esp_buf; + pfs_name++; + + /* if pfs strings AND first char is not '0' */ + if (*pfs_name && pfs_name[0] != '0') + { + ret = modp_getbyname_esp(pfs_name, strlen(pfs_name)); + if (ret < 0) + { + /* Bomb if pfsgroup not found */ + DBG(DBG_CRYPT, + DBG_log("alg_info_esp_create_from_str(): pfsgroup \"%s\" not found" + , pfs_name) + ) + if (*err_p) + { + snprintf(err_buf, sizeof(err_buf), + "pfsgroup \"%s\" not found", + pfs_name); + + *err_p = err_buf; + } + goto out; + } + alg_info_esp->esp_pfsgroup = ret; + } + } + else + alg_info_esp->esp_pfsgroup = 0; + + alg_info_esp->alg_info_protoid = PROTO_IPSEC_ESP; + ret = alg_info_parse_str((struct alg_info *)alg_info_esp, alg_str, err_p) ; +out: + if (ret < 0) + { + pfreeany(alg_info_esp); + alg_info_esp = NULL; + } + return alg_info_esp; +} + +#ifndef NO_PLUTO +struct alg_info_ike * +alg_info_ike_create_from_str (const char *alg_str, const char **err_p) +{ + struct alg_info_ike *alg_info_ike; + /* + * alg_info storage should be sized dynamically + * but this may require 2passes to know + * transform count in advance. + */ + alg_info_ike = alloc_thing (struct alg_info_ike, "alg_info_ike"); + alg_info_ike->alg_info_protoid = PROTO_ISAKMP; + + if (alg_info_parse_str((struct alg_info *)alg_info_ike, + alg_str, err_p) < 0) + { + pfreeany(alg_info_ike); + return NULL; + } + return alg_info_ike; +} +#endif + +/* + * alg_info struct can be shared by + * several connections instances, + * handle free() with ref_cnts + */ +void +alg_info_addref(struct alg_info *alg_info) +{ + if (alg_info != NULL) + { + alg_info->ref_cnt++; + DBG(DBG_CRYPT, + DBG_log("alg_info_addref() alg_info->ref_cnt=%d" + , alg_info->ref_cnt) + ) + } +} + +void +alg_info_delref(struct alg_info **alg_info_p) +{ + struct alg_info *alg_info = *alg_info_p; + + if (alg_info != NULL) + { + passert(alg_info->ref_cnt != 0); + alg_info->ref_cnt--; + DBG(DBG_CRYPT, + DBG_log("alg_info_delref() alg_info->ref_cnt=%d" + , alg_info->ref_cnt) + ) + if (alg_info->ref_cnt == 0) + { + DBG(DBG_CRYPT, + DBG_log("alg_info_delref() freeing alg_info") + ) + alg_info_free(alg_info); + } + *alg_info_p = NULL; + } +} + +/* snprint already parsed transform list (alg_info) */ +int +alg_info_snprint(char *buf, int buflen, struct alg_info *alg_info) +{ + char *ptr = buf; + int np = 0; + struct esp_info *esp_info; +#ifndef NO_PLUTO + struct ike_info *ike_info; +#endif + int cnt; + + switch (alg_info->alg_info_protoid) { + case PROTO_IPSEC_ESP: + { + struct alg_info_esp *alg_info_esp = (struct alg_info_esp *)alg_info; + + ALG_INFO_ESP_FOREACH(alg_info_esp, esp_info, cnt) + { + np = snprintf(ptr, buflen, "%d_%03d-%d, " + , esp_info->esp_ealg_id + , (int)esp_info->esp_ealg_keylen + , esp_info->esp_aalg_id); + ptr += np; + buflen -= np; + if (buflen < 0) + goto out; + } + if (alg_info_esp->esp_pfsgroup) + { + np = snprintf(ptr, buflen, "; pfsgroup=%d; " + , alg_info_esp->esp_pfsgroup); + ptr += np; + buflen -= np; + if (buflen < 0) + goto out; + } + break; + } +#ifndef NO_PLUTO + case PROTO_ISAKMP: + ALG_INFO_IKE_FOREACH((struct alg_info_ike *)alg_info, ike_info, cnt) + { + np = snprintf(ptr, buflen, "%d_%03d-%d-%d, " + , ike_info->ike_ealg + , (int)ike_info->ike_eklen + , ike_info->ike_halg + , ike_info->ike_modp); + ptr += np; + buflen -= np; + if (buflen < 0) + goto out; + } + break; +#endif + default: + np = snprintf(buf, buflen, "INVALID protoid=%d\n" + , alg_info->alg_info_protoid); + ptr += np; + buflen -= np; + goto out; + } + + np = snprintf(ptr, buflen, "%s" + , alg_info->alg_info_flags & ALG_INFO_F_STRICT? + "strict":""); + ptr += np; + buflen -= np; +out: + if (buflen < 0) + { + loglog(RC_LOG_SERIOUS + , "buffer space exhausted in alg_info_snprint_ike(), buflen=%d" + , buflen); + } + + return ptr - buf; +} + +#ifndef NO_PLUTO +int +alg_info_snprint_esp(char *buf, int buflen, struct alg_info_esp *alg_info) +{ + char *ptr = buf; + + int cnt = alg_info->alg_info_cnt; + struct esp_info *esp_info = alg_info->esp; + + while (cnt--) + { + if (kernel_alg_esp_enc_ok(esp_info->esp_ealg_id, 0, NULL) + && kernel_alg_esp_auth_ok(esp_info->esp_aalg_id, NULL)) + { + u_int eklen = (esp_info->esp_ealg_keylen) + ? esp_info->esp_ealg_keylen + : kernel_alg_esp_enc_keylen(esp_info->esp_ealg_id) + * BITS_PER_BYTE; + + u_int aklen = esp_info->esp_aalg_keylen + ? esp_info->esp_aalg_keylen + : kernel_alg_esp_auth_keylen(esp_info->esp_aalg_id) + * BITS_PER_BYTE; + + int ret = snprintf(ptr, buflen, "%d_%03d-%d_%03d, ", + esp_info->esp_ealg_id, eklen, + esp_info->esp_aalg_id, aklen); + ptr += ret; + buflen -= ret; + if (buflen < 0) + break; + } + esp_info++; + } + return ptr - buf; +} + +int +alg_info_snprint_ike(char *buf, int buflen, struct alg_info_ike *alg_info) +{ + char *ptr = buf; + + int cnt = alg_info->alg_info_cnt; + struct ike_info *ike_info = alg_info->ike; + + while (cnt--) + { + struct encrypt_desc *enc_desc = ike_alg_get_encrypter(ike_info->ike_ealg); + struct hash_desc *hash_desc = ike_alg_get_hasher(ike_info->ike_halg); + + if (enc_desc != NULL && hash_desc != NULL + && lookup_group(ike_info->ike_modp)) + { + + u_int eklen = (ike_info->ike_eklen) + ? ike_info->ike_eklen + : enc_desc->keydeflen; + + u_int aklen = (ike_info->ike_hklen) + ? ike_info->ike_hklen + : hash_desc->hash_digest_size * BITS_PER_BYTE; + + int ret = snprintf(ptr, buflen, "%d_%03d-%d_%03d-%d, ", + ike_info->ike_ealg, eklen, + ike_info->ike_halg, aklen, + ike_info->ike_modp); + ptr += ret; + buflen -= ret; + if (buflen < 0) + break; + } + ike_info++; + } + return ptr - buf; +} +#endif /* NO_PLUTO */ diff --git a/src/pluto/alg_info.h b/src/pluto/alg_info.h new file mode 100644 index 000000000..cd2011dcc --- /dev/null +++ b/src/pluto/alg_info.h @@ -0,0 +1,85 @@ +/* Algorithm info parsing and creation functions + * Author: JuanJo Ciarlante <jjo-ipsec@mendoza.gov.ar> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: alg_info.h,v 1.4 2004/09/29 22:39:44 as Exp $ + */ + +#ifndef ALG_INFO_H +#define ALG_INFO_H + +struct esp_info { + u_int8_t transid; /* ESP transform */ + u_int16_t auth; /* AUTH */ + size_t enckeylen; /* keylength for ESP transform */ + size_t authkeylen; /* keylength for AUTH */ + u_int8_t encryptalg; /* normally encryptalg=transid */ + u_int8_t authalg; /* normally authalg=auth+1 */ +}; + +struct ike_info { + u_int16_t ike_ealg; /* high 16 bit nums for reserved */ + u_int8_t ike_halg; + size_t ike_eklen; + size_t ike_hklen; + u_int16_t ike_modp; +}; + +#define ALG_INFO_COMMON \ + int alg_info_cnt; \ + int ref_cnt; \ + unsigned alg_info_flags; \ + unsigned alg_info_protoid + +struct alg_info { + ALG_INFO_COMMON; +}; + +struct alg_info_esp { + ALG_INFO_COMMON; + struct esp_info esp[64]; + int esp_pfsgroup; +}; + +struct alg_info_ike { + ALG_INFO_COMMON; + struct ike_info ike[64]; +}; +#define esp_ealg_id transid +#define esp_aalg_id auth +#define esp_ealg_keylen enckeylen /* bits */ +#define esp_aalg_keylen authkeylen /* bits */ + +/* alg_info_flags bits */ +#define ALG_INFO_F_STRICT 0x01 + +extern int alg_info_esp_aa2sadb(int auth); +extern int alg_info_esp_sadb2aa(int sadb_aalg); +extern void alg_info_free(struct alg_info *alg_info); +extern void alg_info_addref(struct alg_info *alg_info); +extern void alg_info_delref(struct alg_info **alg_info); +extern struct alg_info_esp* alg_info_esp_create_from_str(const char *alg_str + , const char **err_p); +extern struct alg_info_ike* alg_info_ike_create_from_str(const char *alg_str + , const char **err_p); +extern int alg_info_parse(const char *str); +extern int alg_info_snprint(char *buf, int buflen + , struct alg_info *alg_info); +extern int alg_info_snprint_esp(char *buf, int buflen + , struct alg_info_esp *alg_info); +extern int alg_info_snprint_ike(char *buf, int buflen + , struct alg_info_ike *alg_info); +#define ALG_INFO_ESP_FOREACH(ai, ai_esp, i) \ + for (i=(ai)->alg_info_cnt,ai_esp=(ai)->esp; i--; ai_esp++) +#define ALG_INFO_IKE_FOREACH(ai, ai_ike, i) \ + for (i=(ai)->alg_info_cnt,ai_ike=(ai)->ike; i--; ai_ike++) +#endif /* ALG_INFO_H */ diff --git a/src/pluto/asn1.c b/src/pluto/asn1.c new file mode 100644 index 000000000..0663bc490 --- /dev/null +++ b/src/pluto/asn1.c @@ -0,0 +1,770 @@ +/* Simple ASN.1 parser + * Copyright (C) 2000-2004 Andreas Steffen, Zuercher Hochschule Winterthur + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: asn1.c,v 1.16 2006/01/04 21:00:43 as Exp $ + */ + +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "mp_defs.h" +#include "asn1.h" +#include "oid.h" +#include "log.h" + +/* some common prefabricated ASN.1 constants */ + +static u_char ASN1_INTEGER_0_str[] = { 0x02, 0x00 }; +static u_char ASN1_INTEGER_1_str[] = { 0x02, 0x01, 0x01 }; +static u_char ASN1_INTEGER_2_str[] = { 0x02, 0x01, 0x02 }; + +const chunk_t ASN1_INTEGER_0 = strchunk(ASN1_INTEGER_0_str); +const chunk_t ASN1_INTEGER_1 = strchunk(ASN1_INTEGER_1_str); +const chunk_t ASN1_INTEGER_2 = strchunk(ASN1_INTEGER_2_str); + +/* some popular algorithmIdentifiers */ + +static u_char ASN1_md5_id_str[] = { + 0x30, 0x0C, + 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, + 0x05, 0x00 +}; + +static u_char ASN1_sha1_id_str[] = { + 0x30, 0x09, + 0x06, 0x05, 0x2B, 0x0E,0x03, 0x02, 0x1A, + 0x05, 0x00 +}; + +static u_char ASN1_md5WithRSA_id_str[] = { + 0x30, 0x0D, + 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x04, + 0x05, 0x00 +}; + +static u_char ASN1_sha1WithRSA_id_str[] = { + 0x30, 0x0D, + 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05, + 0x05, 0x00 +}; + +static u_char ASN1_rsaEncryption_id_str[] = { + 0x30, 0x0D, + 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, + 0x05, 0x00 +}; + +const chunk_t ASN1_md5_id = strchunk(ASN1_md5_id_str); +const chunk_t ASN1_sha1_id = strchunk(ASN1_sha1_id_str); +const chunk_t ASN1_rsaEncryption_id = strchunk(ASN1_rsaEncryption_id_str); +const chunk_t ASN1_md5WithRSA_id = strchunk(ASN1_md5WithRSA_id_str); +const chunk_t ASN1_sha1WithRSA_id = strchunk(ASN1_sha1WithRSA_id_str); + +/* ASN.1 definiton of an algorithmIdentifier */ + +static const asn1Object_t algorithmIdentifierObjects[] = { + { 0, "algorithmIdentifier", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */ + { 1, "algorithm", ASN1_OID, ASN1_BODY }, /* 1 */ + { 1, "parameters", ASN1_EOC, ASN1_RAW } /* 2 */ +}; + +#define ALGORITHM_ID_ALG 1 +#define ALGORITHM_ID_PARAMETERS 2 +#define ALGORITHM_ID_ROOF 3 + +/* + * return the ASN.1 encoded algorithm identifier + */ +chunk_t +asn1_algorithmIdentifier(int oid) +{ + switch (oid) + { + case OID_RSA_ENCRYPTION: + return ASN1_rsaEncryption_id; + case OID_MD5_WITH_RSA: + return ASN1_md5WithRSA_id; + case OID_SHA1_WITH_RSA: + return ASN1_sha1WithRSA_id; + case OID_MD5: + return ASN1_md5_id; + case OID_SHA1: + return ASN1_sha1_id; + default: + return empty_chunk; + } +} + +/* If the oid is listed in the oid_names table then the corresponding + * position in the oid_names table is returned otherwise -1 is returned + */ +int +known_oid(chunk_t object) +{ + int oid = 0; + + while (object.len) + { + if (oid_names[oid].octet == *object.ptr) + { + if (--object.len == 0 || oid_names[oid].down == 0) + { + return oid; /* found terminal symbol */ + } + else + { + object.ptr++; oid++; /* advance to next hex octet */ + } + } + else + { + if (oid_names[oid].next) + oid = oid_names[oid].next; + else + return OID_UNKNOWN; + } + } + return -1; +} + +/* + * Decodes the length in bytes of an ASN.1 object + */ +u_int +asn1_length(chunk_t *blob) +{ + u_char n; + size_t len; + + /* advance from tag field on to length field */ + blob->ptr++; + blob->len--; + + /* read first octet of length field */ + n = *blob->ptr++; + blob->len--; + + if ((n & 0x80) == 0) /* single length octet */ + return n; + + /* composite length, determine number of length octets */ + n &= 0x7f; + + if (n > blob->len) + { + DBG(DBG_PARSING, + DBG_log("number of length octets is larger than ASN.1 object") + ) + return ASN1_INVALID_LENGTH; + } + + if (n > sizeof(len)) + { + DBG(DBG_PARSING, + DBG_log("number of length octets is larger than limit of %d octets" + , (int)sizeof(len)) + ) + return ASN1_INVALID_LENGTH; + } + + len = 0; + + while (n-- > 0) + { + len = 256*len + *blob->ptr++; + blob->len--; + } + return len; +} + +/* + * codes ASN.1 lengths up to a size of 16'777'215 bytes + */ +void +code_asn1_length(size_t length, chunk_t *code) +{ + if (length < 128) + { + code->ptr[0] = length; + code->len = 1; + } + else if (length < 256) + { + code->ptr[0] = 0x81; + code->ptr[1] = (u_char) length; + code->len = 2; + } + else if (length < 65536) + { + code->ptr[0] = 0x82; + code->ptr[1] = length >> 8; + code->ptr[2] = length & 0x00ff; + code->len = 3; + } + else + { + code->ptr[0] = 0x83; + code->ptr[1] = length >> 16; + code->ptr[2] = (length >> 8) & 0x00ff; + code->ptr[3] = length & 0x0000ff; + code->len = 4; + } +} + +/* + * build an empty asn.1 object with tag and length fields already filled in + */ +u_char* +build_asn1_object(chunk_t *object, asn1_t type, size_t datalen) +{ + u_char length_buf[4]; + chunk_t length = { length_buf, 0 }; + u_char *pos; + + /* code the asn.1 length field */ + code_asn1_length(datalen, &length); + + /* allocate memory for the asn.1 TLV object */ + object->len = 1 + length.len + datalen; + object->ptr = alloc_bytes(object->len, "asn1 object"); + + /* set position pointer at the start of the object */ + pos = object->ptr; + + /* copy the asn.1 tag field and advance the pointer */ + *pos++ = type; + + /* copy the asn.1 length field and advance the pointer */ + chunkcpy(pos, length); + + return pos; +} + +/* + * build a simple ASN.1 object + */ +chunk_t +asn1_simple_object(asn1_t tag, chunk_t content) +{ + chunk_t object; + + u_char *pos = build_asn1_object(&object, tag, content.len); + chunkcpy(pos, content); + + return object; +} + +/* Build an ASN.1 object from a variable number of individual chunks. + * Depending on the mode, chunks either are moved ('m') or copied ('c'). + */ +chunk_t +asn1_wrap(asn1_t type, const char *mode, ...) +{ + chunk_t construct; + va_list chunks; + u_char *pos; + int i; + int count = strlen(mode); + + /* sum up lengths of individual chunks */ + va_start(chunks, mode); + construct.len = 0; + for (i = 0; i < count; i++) + { + chunk_t ch = va_arg(chunks, chunk_t); + construct.len += ch.len; + } + va_end(chunks); + + /* allocate needed memory for construct */ + pos = build_asn1_object(&construct, type, construct.len); + + /* copy or move the chunks */ + va_start(chunks, mode); + for (i = 0; i < count; i++) + { + chunk_t ch = va_arg(chunks, chunk_t); + + switch (*mode++) + { + case 'm': + mv_chunk(&pos, ch); + break; + case 'c': + default: + chunkcpy(pos, ch); + } + } + va_end(chunks); + + return construct; +} + +/* + * convert a MP integer into a DER coded ASN.1 object + */ +chunk_t +asn1_integer_from_mpz(const mpz_t value) +{ + size_t bits = mpz_sizeinbase(value, 2); /* size in bits */ + size_t size = 1 + bits / BITS_PER_BYTE; /* size in bytes */ + chunk_t n = mpz_to_n(value, size); + + return asn1_wrap(ASN1_INTEGER, "m", n); +} + +/* + * determines if a character string is of type ASN.1 printableString + */ +bool +is_printablestring(chunk_t str) +{ + const char printablestring_charset[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 '()+,-./:=?"; + u_int i; + + for (i = 0; i < str.len; i++) + { + if (strchr(printablestring_charset, str.ptr[i]) == NULL) + return FALSE; + } + return TRUE; +} + +/* + * Converts ASN.1 UTCTIME or GENERALIZEDTIME into calender time + */ +time_t +asn1totime(const chunk_t *utctime, asn1_t type) +{ + struct tm t; + time_t tz_offset; + u_char *eot = NULL; + + if ((eot = memchr(utctime->ptr, 'Z', utctime->len)) != NULL) + { + tz_offset = 0; /* Zulu time with a zero time zone offset */ + } + else if ((eot = memchr(utctime->ptr, '+', utctime->len)) != NULL) + { + int tz_hour, tz_min; + + sscanf(eot+1, "%2d%2d", &tz_hour, &tz_min); + tz_offset = 3600*tz_hour + 60*tz_min; /* positive time zone offset */ + } + else if ((eot = memchr(utctime->ptr, '-', utctime->len)) != NULL) + { + int tz_hour, tz_min; + + sscanf(eot+1, "%2d%2d", &tz_hour, &tz_min); + tz_offset = -3600*tz_hour - 60*tz_min; /* negative time zone offset */ + } + else + { + return 0; /* error in time format */ + } + + { + const char* format = (type == ASN1_UTCTIME)? "%2d%2d%2d%2d%2d": + "%4d%2d%2d%2d%2d"; + + sscanf(utctime->ptr, format, &t.tm_year, &t.tm_mon, &t.tm_mday, + &t.tm_hour, &t.tm_min); + } + + /* is there a seconds field? */ + if ((eot - utctime->ptr) == ((type == ASN1_UTCTIME)?12:14)) + { + sscanf(eot-2, "%2d", &t.tm_sec); + } + else + { + t.tm_sec = 0; + } + + /* representation of year */ + if (t.tm_year >= 1900) + { + t.tm_year -= 1900; + } + else if (t.tm_year >= 100) + { + return 0; + } + else if (t.tm_year < 50) + { + t.tm_year += 100; + } + + /* representation of month 0..11*/ + t.tm_mon--; + + /* set daylight saving time to off */ + t.tm_isdst = 0; + + /* compensate timezone */ + + return mktime(&t) - timezone - tz_offset; +} + +/* + * convert a date into ASN.1 UTCTIME or GENERALIZEDTIME format + */ +chunk_t +timetoasn1(const time_t *time, asn1_t type) +{ + int offset; + const char *format; + char buf[TIMETOA_BUF]; + chunk_t formatted_time; + struct tm *t = gmtime(time); + + if (type == ASN1_GENERALIZEDTIME) + { + format = "%04d%02d%02d%02d%02d%02dZ"; + offset = 1900; + } + else /* ASN1_UTCTIME */ + { + format = "%02d%02d%02d%02d%02d%02dZ"; + offset = (t->tm_year < 100)? 0 : -100; + } + sprintf(buf, format, t->tm_year + offset, t->tm_mon + 1, t->tm_mday + , t->tm_hour, t->tm_min, t->tm_sec); + formatted_time.ptr = buf; + formatted_time.len = strlen(buf); + return asn1_simple_object(type, formatted_time); +} + + +/* + * Initializes the internal context of the ASN.1 parser + */ +void +asn1_init(asn1_ctx_t *ctx, chunk_t blob, u_int level0, + bool implicit, u_int cond) +{ + ctx->blobs[0] = blob; + ctx->level0 = level0; + ctx->implicit = implicit; + ctx->cond = cond; + memset(ctx->loopAddr, '\0', sizeof(ctx->loopAddr)); +} + +/* + * print the value of an ASN.1 simple object + */ +static void +debug_asn1_simple_object(chunk_t object, asn1_t type, u_int cond) +{ + int oid; + + switch (type) + { + case ASN1_OID: + oid = known_oid(object); + if (oid != OID_UNKNOWN) + { + DBG(DBG_PARSING, + DBG_log(" '%s'",oid_names[oid].name); + ) + return; + } + break; + case ASN1_UTF8STRING: + case ASN1_IA5STRING: + case ASN1_PRINTABLESTRING: + case ASN1_T61STRING: + case ASN1_VISIBLESTRING: + DBG(DBG_PARSING, + DBG_log(" '%.*s'", (int)object.len, object.ptr); + ) + return; + case ASN1_UTCTIME: + case ASN1_GENERALIZEDTIME: + DBG(DBG_PARSING, + time_t time = asn1totime(&object, type); + DBG_log(" '%s'", timetoa(&time, TRUE)); + ) + return; + default: + break; + } + DBG(cond, + DBG_dump_chunk("", object); + ) +} + +/* + * Parses and extracts the next ASN.1 object + */ +bool +extract_object(asn1Object_t const *objects, + u_int *objectID, chunk_t *object, u_int *level, asn1_ctx_t *ctx) +{ + asn1Object_t obj = objects[*objectID]; + chunk_t *blob; + chunk_t *blob1; + u_char *start_ptr; + + *object = empty_chunk; + + if (obj.flags & ASN1_END) /* end of loop or option found */ + { + if (ctx->loopAddr[obj.level] && ctx->blobs[obj.level+1].len > 0) + { + *objectID = ctx->loopAddr[obj.level]; /* another iteration */ + obj = objects[*objectID]; + } + else + { + ctx->loopAddr[obj.level] = 0; /* exit loop or option*/ + return TRUE; + } + } + + *level = ctx->level0 + obj.level; + blob = ctx->blobs + obj.level; + blob1 = blob + 1; + start_ptr = blob->ptr; + + /* handle ASN.1 defaults values */ + + if ((obj.flags & ASN1_DEF) + && (blob->len == 0 || *start_ptr != obj.type) ) + { + /* field is missing */ + DBG(DBG_PARSING, + DBG_log("L%d - %s:", *level, obj.name); + ) + if (obj.type & ASN1_CONSTRUCTED) + { + (*objectID)++ ; /* skip context-specific tag */ + } + return TRUE; + } + + /* handle ASN.1 options */ + + if ((obj.flags & ASN1_OPT) + && (blob->len == 0 || *start_ptr != obj.type)) + { + /* advance to end of missing option field */ + do + (*objectID)++; + while (!((objects[*objectID].flags & ASN1_END) + && (objects[*objectID].level == obj.level))); + return TRUE; + } + + /* an ASN.1 object must possess at least a tag and length field */ + + if (blob->len < 2) + { + DBG(DBG_PARSING, + DBG_log("L%d - %s: ASN.1 object smaller than 2 octets", + *level, obj.name); + ) + return FALSE; + } + + blob1->len = asn1_length(blob); + + if (blob1->len == ASN1_INVALID_LENGTH || blob->len < blob1->len) + { + DBG(DBG_PARSING, + DBG_log("L%d - %s: length of ASN.1 object invalid or too large", + *level, obj.name); + ) + return FALSE; + } + + blob1->ptr = blob->ptr; + blob->ptr += blob1->len; + blob->len -= blob1->len; + + /* return raw ASN.1 object without prior type checking */ + + if (obj.flags & ASN1_RAW) + { + DBG(DBG_PARSING, + DBG_log("L%d - %s:", *level, obj.name); + ) + object->ptr = start_ptr; + object->len = (size_t)(blob->ptr - start_ptr); + return TRUE; + } + + if (*start_ptr != obj.type && !(ctx->implicit && *objectID == 0)) + { + DBG(DBG_PARSING, + DBG_log("L%d - %s: ASN1 tag 0x%02x expected, but is 0x%02x", + *level, obj.name, obj.type, *start_ptr); + DBG_dump("", start_ptr, (u_int)(blob->ptr - start_ptr)); + ) + return FALSE; + } + + DBG(DBG_PARSING, + DBG_log("L%d - %s:", ctx->level0+obj.level, obj.name); + ) + + /* In case of "SEQUENCE OF" or "SET OF" start a loop */ + + if (obj.flags & ASN1_LOOP) + { + if (blob1->len > 0) + { + /* at least one item, start the loop */ + ctx->loopAddr[obj.level] = *objectID + 1; + } + else + { + /* no items, advance directly to end of loop */ + do + (*objectID)++; + while (!((objects[*objectID].flags & ASN1_END) + && (objects[*objectID].level == obj.level))); + return TRUE; + } + } + + if (obj.flags & ASN1_OBJ) + { + object->ptr = start_ptr; + object->len = (size_t)(blob->ptr - start_ptr); + DBG(ctx->cond, + DBG_dump_chunk("", *object); + ) + } + else if (obj.flags & ASN1_BODY) + { + *object = *blob1; + debug_asn1_simple_object(*object, obj.type, ctx->cond); + } + return TRUE; +} + +/* + * parse an ASN.1 simple type + */ +bool +parse_asn1_simple_object(chunk_t *object, asn1_t type, u_int level +, const char* name) +{ + size_t len; + + /* an ASN.1 object must possess at least a tag and length field */ + if (object->len < 2) + { + DBG(DBG_PARSING, + DBG_log("L%d - %s: ASN.1 object smaller than 2 octets", + level, name); + ) + return FALSE; + } + + if (*object->ptr != type) + { + DBG(DBG_PARSING, + DBG_log("L%d - %s: ASN1 tag 0x%02x expected, but is 0x%02x", + level, name, type, *object->ptr); + ) + return FALSE; + } + + len = asn1_length(object); + + if (len == ASN1_INVALID_LENGTH || object->len < len) + { + DBG(DBG_PARSING, + DBG_log("L%d - %s: length of ASN.1 object invalid or too large", + level, name); + ) + return FALSE; + } + + DBG(DBG_PARSING, + DBG_log("L%d - %s:", level, name); + ) + debug_asn1_simple_object(*object, type, DBG_RAW); + return TRUE; +} + +/* + * extracts an algorithmIdentifier + */ +int +parse_algorithmIdentifier(chunk_t blob, int level0, chunk_t *parameters) +{ + asn1_ctx_t ctx; + chunk_t object; + u_int level; + int alg = OID_UNKNOWN; + int objectID = 0; + + asn1_init(&ctx, blob, level0, FALSE, DBG_RAW); + + while (objectID < ALGORITHM_ID_ROOF) + { + if (!extract_object(algorithmIdentifierObjects, &objectID, &object, &level, &ctx)) + return OID_UNKNOWN; + + switch (objectID) + { + case ALGORITHM_ID_ALG: + alg = known_oid(object); + break; + case ALGORITHM_ID_PARAMETERS: + if (parameters != NULL) + *parameters = object; + break; + default: + break; + } + objectID++; + } + return alg; + } + +/* + * tests if a blob contains a valid ASN.1 set or sequence + */ +bool +is_asn1(chunk_t blob) +{ + u_int len; + u_char tag = *blob.ptr; + + if (tag != ASN1_SEQUENCE && tag != ASN1_SET) + { + DBG(DBG_PARSING, + DBG_log(" file content is not binary ASN.1"); + ) + return FALSE; + } + len = asn1_length(&blob); + if (len != blob.len) + { + DBG(DBG_PARSING, + DBG_log(" file size does not match ASN.1 coded length"); + ) + return FALSE; + } + return TRUE; +} diff --git a/src/pluto/asn1.h b/src/pluto/asn1.h new file mode 100644 index 000000000..2a3fb3e9e --- /dev/null +++ b/src/pluto/asn1.h @@ -0,0 +1,141 @@ +/* Simple ASN.1 parser + * Copyright (C) 2000-2004 Andreas Steffen, Zuercher Hochschule Winterthur + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: asn1.h,v 1.14 2005/12/06 22:50:10 as Exp $ + */ + +#ifndef _ASN1_H +#define _ASN1_H + +#include <stdarg.h> +#include <gmp.h> + +#include "defs.h" + +/* Defines some primitive ASN1 types */ + +typedef enum { + ASN1_EOC = 0x00, + ASN1_BOOLEAN = 0x01, + ASN1_INTEGER = 0x02, + ASN1_BIT_STRING = 0x03, + ASN1_OCTET_STRING = 0x04, + ASN1_NULL = 0x05, + ASN1_OID = 0x06, + ASN1_ENUMERATED = 0x0A, + ASN1_UTF8STRING = 0x0C, + ASN1_NUMERICSTRING = 0x12, + ASN1_PRINTABLESTRING = 0x13, + ASN1_T61STRING = 0x14, + ASN1_VIDEOTEXSTRING = 0x15, + ASN1_IA5STRING = 0x16, + ASN1_UTCTIME = 0x17, + ASN1_GENERALIZEDTIME = 0x18, + ASN1_GRAPHICSTRING = 0x19, + ASN1_VISIBLESTRING = 0x1A, + ASN1_GENERALSTRING = 0x1B, + ASN1_UNIVERSALSTRING = 0x1C, + ASN1_BMPSTRING = 0x1E, + + ASN1_CONSTRUCTED = 0x20, + + ASN1_SEQUENCE = 0x30, + + ASN1_SET = 0x31, + + ASN1_CONTEXT_S_0 = 0x80, + ASN1_CONTEXT_S_1 = 0x81, + ASN1_CONTEXT_S_2 = 0x82, + ASN1_CONTEXT_S_3 = 0x83, + ASN1_CONTEXT_S_4 = 0x84, + ASN1_CONTEXT_S_5 = 0x85, + ASN1_CONTEXT_S_6 = 0x86, + ASN1_CONTEXT_S_7 = 0x87, + ASN1_CONTEXT_S_8 = 0x88, + + ASN1_CONTEXT_C_0 = 0xA0, + ASN1_CONTEXT_C_1 = 0xA1, + ASN1_CONTEXT_C_2 = 0xA2, + ASN1_CONTEXT_C_3 = 0xA3, + ASN1_CONTEXT_C_4 = 0xA4, + ASN1_CONTEXT_C_5 = 0xA5 +} asn1_t; + +/* Definition of ASN1 flags */ + +#define ASN1_NONE 0x00 +#define ASN1_DEF 0x01 +#define ASN1_OPT 0x02 +#define ASN1_LOOP 0x04 +#define ASN1_END 0x08 +#define ASN1_OBJ 0x10 +#define ASN1_BODY 0x20 +#define ASN1_RAW 0x40 + +#define ASN1_INVALID_LENGTH 0xffffffff + +/* definition of an ASN.1 object */ + +typedef struct { + u_int level; + const u_char *name; + asn1_t type; + u_char flags; +} asn1Object_t; + +#define ASN1_MAX_LEVEL 10 + +typedef struct { + bool implicit; + u_int cond; + u_int level0; + u_int loopAddr[ASN1_MAX_LEVEL+1]; + chunk_t blobs[ASN1_MAX_LEVEL+2]; +} asn1_ctx_t; + +/* some common prefabricated ASN.1 constants */ + +extern const chunk_t ASN1_INTEGER_0; +extern const chunk_t ASN1_INTEGER_1; +extern const chunk_t ASN1_INTEGER_2; + +/* some popular algorithmIdentifiers */ +extern const chunk_t ASN1_md5_id; +extern const chunk_t ASN1_sha1_id; +extern const chunk_t ASN1_rsaEncryption_id; +extern const chunk_t ASN1_md5WithRSA_id; +extern const chunk_t ASN1_sha1WithRSA_id; + +extern chunk_t asn1_algorithmIdentifier(int oid); +extern int known_oid(chunk_t object); +extern u_int asn1_length(chunk_t *blob); +extern void code_asn1_length(size_t length, chunk_t *code); +extern u_char* build_asn1_object(chunk_t *object, asn1_t type, size_t datalen); +extern chunk_t asn1_integer_from_mpz(const mpz_t value); +extern chunk_t asn1_simple_object(asn1_t tag, chunk_t content); +extern chunk_t asn1_wrap(asn1_t type, const char *mode, ...); +extern bool is_printablestring(chunk_t str); +extern time_t asn1totime(const chunk_t *utctime, asn1_t type); +extern chunk_t timetoasn1(const time_t *time, asn1_t type); +extern void asn1_init(asn1_ctx_t *ctx, chunk_t blob + , u_int level0, bool implicit, u_int cond); +extern bool extract_object(asn1Object_t const *objects + , u_int *objectID, chunk_t *object, u_int *level, asn1_ctx_t *ctx); +extern bool parse_asn1_simple_object(chunk_t *object, asn1_t type, u_int level + , const char* name); +extern int parse_algorithmIdentifier(chunk_t blob, int level0 + , chunk_t *parameters); +extern bool is_asn1(chunk_t blob); + +#endif /* _ASN1_H */ + diff --git a/src/pluto/ca.c b/src/pluto/ca.c new file mode 100644 index 000000000..d1be22e2f --- /dev/null +++ b/src/pluto/ca.c @@ -0,0 +1,694 @@ +/* Certification Authority (CA) support for IKE authentication + * Copyright (C) 2002-2004 Andreas Steffen, Zuercher Hochschule Winterthur + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: ca.c,v 1.10 2005/12/25 12:29:55 as Exp $ + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <dirent.h> +#include <time.h> +#include <sys/types.h> + +#include <freeswan.h> +#include <ipsec_policy.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "x509.h" +#include "ca.h" +#include "certs.h" +#include "whack.h" +#include "fetch.h" + +/* chained list of X.509 authority certificates (ca, aa, and ocsp) */ + +static x509cert_t *x509authcerts = NULL; + +const ca_info_t empty_ca_info = { + NULL , /* next */ + NULL , /* name */ + UNDEFINED_TIME, + { NULL, 0 } , /* authName */ + { NULL, 0 } , /* authKeyID */ + { NULL, 0 } , /* authKey SerialNumber */ + NULL , /* ldaphost */ + NULL , /* ldapbase */ + NULL , /* ocspori */ + NULL , /* crluri */ + FALSE /* strictcrlpolicy */ +}; + +/* chained list of X.509 certification authority information records */ + +static ca_info_t *ca_infos = NULL; + +/* + * Checks if CA a is trusted by CA b + */ +bool +trusted_ca(chunk_t a, chunk_t b, int *pathlen) +{ + bool match = FALSE; + + /* no CA b specified -> any CA a is accepted */ + if (b.ptr == NULL) + { + *pathlen = (a.ptr == NULL)? 0 : MAX_CA_PATH_LEN; + return TRUE; + } + + /* no CA a specified -> trust cannot be established */ + if (a.ptr == NULL) + { + *pathlen = MAX_CA_PATH_LEN; + return FALSE; + } + + *pathlen = 0; + + /* CA a equals CA b -> we have a match */ + if (same_dn(a, b)) + return TRUE; + + /* CA a might be a subordinate CA of b */ + lock_authcert_list("trusted_ca"); + + while ((*pathlen)++ < MAX_CA_PATH_LEN) + { + x509cert_t *cacert = get_authcert(a, empty_chunk, empty_chunk, AUTH_CA); + + /* cacert not found or self-signed root cacert-> exit */ + if (cacert == NULL || same_dn(cacert->issuer, a)) + break; + + /* does the issuer of CA a match CA b? */ + match = same_dn(cacert->issuer, b); + + /* we have a match and exit the loop */ + if (match) + break; + + /* go one level up in the CA chain */ + a = cacert->issuer; + } + + unlock_authcert_list("trusted_ca"); + return match; +} + +/* + * does our CA match one of the requested CAs? + */ +bool +match_requested_ca(generalName_t *requested_ca, chunk_t our_ca, int *our_pathlen) +{ + /* if no ca is requested than any ca will match */ + if (requested_ca == NULL) + { + *our_pathlen = 0; + return TRUE; + } + + *our_pathlen = MAX_CA_PATH_LEN + 1; + + while (requested_ca != NULL) + { + int pathlen; + + if (trusted_ca(our_ca, requested_ca->name, &pathlen) + && pathlen < *our_pathlen) + *our_pathlen = pathlen; + requested_ca = requested_ca->next; + } + + return *our_pathlen <= MAX_CA_PATH_LEN; +} + +/* + * free the first authority certificate in the chain + */ +static void +free_first_authcert(void) +{ + x509cert_t *first = x509authcerts; + x509authcerts = first->next; + free_x509cert(first); +} + +/* + * free all CA certificates + */ +void +free_authcerts(void) +{ + lock_authcert_list("free_authcerts"); + + while (x509authcerts != NULL) + free_first_authcert(); + + unlock_authcert_list("free_authcerts"); +} + +/* + * get a X.509 authority certificate with a given subject or keyid + */ +x509cert_t* +get_authcert(chunk_t subject, chunk_t serial, chunk_t keyid, u_char auth_flags) +{ + x509cert_t *cert = x509authcerts; + x509cert_t *prev_cert = NULL; + + while (cert != NULL) + { + if (cert->authority_flags & auth_flags + && ((keyid.ptr != NULL) ? same_keyid(keyid, cert->subjectKeyID) + : (same_dn(subject, cert->subject) + && same_serial(serial, cert->serialNumber)))) + { + if (cert != x509authcerts) + { + /* bring the certificate up front */ + prev_cert->next = cert->next; + cert->next = x509authcerts; + x509authcerts = cert; + } + return cert; + } + prev_cert = cert; + cert = cert->next; + } + return NULL; +} + +/* + * add an authority certificate to the chained list + */ +bool +add_authcert(x509cert_t *cert, u_char auth_flags) +{ + x509cert_t *old_cert; + + /* set authority flags */ + cert->authority_flags |= auth_flags; + + lock_authcert_list("add_authcert"); + + old_cert = get_authcert(cert->subject, cert->serialNumber + , cert->subjectKeyID, auth_flags); + + if (old_cert != NULL) + { + if (same_x509cert(cert, old_cert)) + { + /* cert is already present, just add additional authority flags */ + old_cert->authority_flags |= cert->authority_flags; + DBG(DBG_CONTROL | DBG_PARSING , + DBG_log(" authcert is already present and identical") + ) + unlock_authcert_list("add_authcert"); + + free_x509cert(cert); + return FALSE; + } + else + { + /* cert is already present but will be replaced by new cert */ + free_first_authcert(); + DBG(DBG_CONTROL | DBG_PARSING , + DBG_log(" existing authcert deleted") + ) + } + } + + /* add new authcert to chained list */ + cert->next = x509authcerts; + x509authcerts = cert; + share_x509cert(cert); /* set count to one */ + DBG(DBG_CONTROL | DBG_PARSING, + DBG_log(" authcert inserted") + ) + unlock_authcert_list("add_authcert"); + return TRUE; +} + +/* + * Loads authority certificates + */ +void +load_authcerts(const char *type, const char *path, u_char auth_flags) +{ + struct dirent **filelist; + u_char buf[BUF_LEN]; + u_char *save_dir; + int n; + + /* change directory to specified path */ + save_dir = getcwd(buf, BUF_LEN); + + if (chdir(path)) + { + plog("Could not change to directory '%s'", path); + } + else + { + plog("Changing to directory '%s'", path); + n = scandir(path, &filelist, file_select, alphasort); + + if (n < 0) + plog(" scandir() error"); + else + { + while (n--) + { + cert_t cert; + + if (load_cert(filelist[n]->d_name, type, &cert)) + add_authcert(cert.u.x509, auth_flags); + + free(filelist[n]); + } + free(filelist); + } + } + /* restore directory path */ + chdir(save_dir); +} + +/* + * list all X.509 authcerts with given auth flags in a chained list + */ +void +list_authcerts(const char *caption, u_char auth_flags, bool utc) +{ + lock_authcert_list("list_authcerts"); + list_x509cert_chain(caption, x509authcerts, auth_flags, utc); + unlock_authcert_list("list_authcerts"); +} + +/* + * get a cacert with a given subject or keyid from an alternative list + */ +static const x509cert_t* +get_alt_cacert(chunk_t subject, chunk_t serial, chunk_t keyid + , const x509cert_t *cert) +{ + while (cert != NULL) + { + if ((keyid.ptr != NULL) ? same_keyid(keyid, cert->subjectKeyID) + : (same_dn(subject, cert->subject) + && same_serial(serial, cert->serialNumber))) + { + return cert; + } + cert = cert->next; + } + return NULL; +} + +/* establish trust into a candidate authcert by going up the trust chain. + * validity and revocation status are not checked. + */ +bool +trust_authcert_candidate(const x509cert_t *cert, const x509cert_t *alt_chain) +{ + int pathlen; + + lock_authcert_list("trust_authcert_candidate"); + + for (pathlen = 0; pathlen < MAX_CA_PATH_LEN; pathlen++) + { + const x509cert_t *authcert = NULL; + u_char buf[BUF_LEN]; + + DBG(DBG_CONTROL, + dntoa(buf, BUF_LEN, cert->subject); + DBG_log("subject: '%s'",buf); + dntoa(buf, BUF_LEN, cert->issuer); + DBG_log("issuer: '%s'",buf); + if (cert->authKeyID.ptr != NULL) + { + datatot(cert->authKeyID.ptr, cert->authKeyID.len, ':' + , buf, BUF_LEN); + DBG_log("authkey: %s", buf); + } + ) + + /* search in alternative chain first */ + authcert = get_alt_cacert(cert->issuer, cert->authKeySerialNumber + , cert->authKeyID, alt_chain); + + if (authcert != NULL) + { + DBG(DBG_CONTROL, + DBG_log("issuer cacert found in alternative chain") + ) + } + else + { + /* search in trusted chain */ + authcert = get_authcert(cert->issuer, cert->authKeySerialNumber + , cert->authKeyID, AUTH_CA); + + if (authcert != NULL) + { + DBG(DBG_CONTROL, + DBG_log("issuer cacert found") + ) + } + else + { + plog("issuer cacert not found"); + unlock_authcert_list("trust_authcert_candidate"); + return FALSE; + } + } + + if (!check_signature(cert->tbsCertificate, cert->signature + , cert->algorithm, cert->algorithm, authcert)) + { + plog("certificate signature is invalid"); + unlock_authcert_list("trust_authcert_candidate"); + return FALSE; + } + DBG(DBG_CONTROL, + DBG_log("certificate signature is valid") + ) + + /* check if cert is a self-signed root ca */ + if (pathlen > 0 && same_dn(cert->issuer, cert->subject)) + { + DBG(DBG_CONTROL, + DBG_log("reached self-signed root ca") + ) + unlock_authcert_list("trust_authcert_candidate"); + return TRUE; + } + + /* go up one step in the trust chain */ + cert = authcert; + } + plog("maximum ca path length of %d levels exceeded", MAX_CA_PATH_LEN); + unlock_authcert_list("trust_authcert_candidate"); + return FALSE; +} + +/* + * get a CA info record with a given authName or authKeyID + */ +ca_info_t* +get_ca_info(chunk_t authname, chunk_t serial, chunk_t keyid) +{ + ca_info_t *ca= ca_infos; + + while (ca!= NULL) + { + if ((keyid.ptr != NULL) ? same_keyid(keyid, ca->authKeyID) + : (same_dn(authname, ca->authName) + && same_serial(serial, ca->authKeySerialNumber))) + { + return ca; + } + ca = ca->next; + } + return NULL; +} + + +/* + * free the dynamic memory used by a ca_info record + */ +static void +free_ca_info(ca_info_t* ca_info) +{ + if (ca_info == NULL) + return; + + pfreeany(ca_info->name); + pfreeany(ca_info->ldaphost); + pfreeany(ca_info->ldapbase); + pfreeany(ca_info->ocspuri); + + freeanychunk(ca_info->authName); + freeanychunk(ca_info->authKeyID); + freeanychunk(ca_info->authKeySerialNumber); + + free_generalNames(ca_info->crluri, TRUE); + + pfree(ca_info); +} + +/* + * free all CA certificates + */ +void +free_ca_infos(void) +{ + while (ca_infos != NULL) + { + ca_info_t *ca = ca_infos; + + ca_infos = ca_infos->next; + free_ca_info(ca); + } +} + +/* + * find a CA information record by name and optionally delete it + */ +bool +find_ca_info_by_name(const char *name, bool delete) +{ + ca_info_t **ca_p = &ca_infos; + ca_info_t *ca = *ca_p; + + while (ca != NULL) + { + /* is there already an entry? */ + if (streq(name, ca->name)) + { + if (delete) + { + lock_ca_info_list("find_ca_info_by_name"); + *ca_p = ca->next; + free_ca_info(ca); + plog("deleting ca description \"%s\"", name); + unlock_ca_info_list("find_ca_info_by_name"); + } + return TRUE; + } + ca_p = &ca->next; + ca = *ca_p; + } + return FALSE; +} + + + /* + * adds a CA description to a chained list + */ +void +add_ca_info(const whack_message_t *msg) +{ + smartcard_t *sc = NULL; + cert_t cert; + bool valid_cert = FALSE; + bool cached_cert = FALSE; + + if (find_ca_info_by_name(msg->name, FALSE)) + { + loglog(RC_DUPNAME, "attempt to redefine ca record \"%s\"", msg->name); + return; + } + + if (scx_on_smartcard(msg->cacert)) + { + /* load CA cert from smartcard */ + valid_cert = scx_load_cert(msg->cacert, &sc, &cert, &cached_cert); + } + else + { + /* load CA cert from file */ + valid_cert = load_ca_cert(msg->cacert, &cert); + } + + if (valid_cert) + { + char buf[BUF_LEN]; + x509cert_t *cacert = cert.u.x509; + ca_info_t *ca = NULL; + + /* does the authname already exist? */ + ca = get_ca_info(cacert->subject, cacert->serialNumber + , cacert->subjectKeyID); + + if (ca != NULL) + { + /* ca_info is already present */ + loglog(RC_DUPNAME, " duplicate ca information in record \"%s\" found," + "ignoring \"%s\"", ca->name, msg->name); + free_x509cert(cacert); + return; + } + + plog("added ca description \"%s\"", msg->name); + + /* create and initialize new ca_info record */ + ca = alloc_thing(ca_info_t, "ca info"); + *ca = empty_ca_info; + + /* name */ + ca->name = clone_str(msg->name, "ca name"); + + /* authName */ + clonetochunk(ca->authName, cacert->subject.ptr + , cacert->subject.len, "authName"); + dntoa(buf, BUF_LEN, ca->authName); + DBG(DBG_CONTROL, + DBG_log("authname: '%s'", buf) + ) + + /* authSerialNumber */ + clonetochunk(ca->authKeySerialNumber, cacert->serialNumber.ptr + , cacert->serialNumber.len, "authKeySerialNumber"); + + /* authKeyID */ + if (cacert->subjectKeyID.ptr != NULL) + { + clonetochunk(ca->authKeyID, cacert->subjectKeyID.ptr + , cacert->subjectKeyID.len, "authKeyID"); + datatot(cacert->subjectKeyID.ptr, cacert->subjectKeyID.len, ':' + , buf, BUF_LEN); + DBG(DBG_CONTROL | DBG_PARSING , + DBG_log("authkey: %s", buf) + ) + } + + /* ldaphost */ + ca->ldaphost = clone_str(msg->ldaphost, "ldaphost"); + + /* ldapbase */ + ca->ldapbase = clone_str(msg->ldapbase, "ldapbase"); + + /* ocspuri */ + if (msg->ocspuri != NULL) + { + if (strncasecmp(msg->ocspuri, "http", 4) == 0) + ca->ocspuri = clone_str(msg->ocspuri, "ocspuri"); + else + plog(" ignoring ocspuri with unkown protocol"); + } + + /* crluri2*/ + if (msg->crluri2 != NULL) + { + generalName_t gn = + { NULL, GN_URI, {msg->crluri2, strlen(msg->crluri2)} }; + + add_distribution_points(&gn, &ca->crluri); + } + + /* crluri */ + if (msg->crluri != NULL) + { + generalName_t gn = + { NULL, GN_URI, {msg->crluri, strlen(msg->crluri)} }; + + add_distribution_points(&gn, &ca->crluri); + } + + /* strictrlpolicy */ + ca->strictcrlpolicy = msg->whack_strict; + + /* insert ca_info record into the chained list */ + lock_ca_info_list("add_ca_info"); + + ca->next = ca_infos; + ca_infos = ca; + ca->installed = time(NULL); + + unlock_ca_info_list("add_ca_info"); + + /* add cacert to list of authcerts */ + if (!cached_cert) + { + if (add_authcert(cacert, AUTH_CA) && sc != NULL) + { + if (sc->last_cert.type == CERT_X509_SIGNATURE) + sc->last_cert.u.x509->count--; + sc->last_cert = cert; + share_cert(sc->last_cert); + } + } + if (sc != NULL) + time(&sc->last_load); + } +} + +/* + * list all ca_info records in the chained list + */ +void +list_ca_infos(bool utc) +{ + ca_info_t *ca = ca_infos; + + if (ca != NULL) + { + whack_log(RC_COMMENT, " "); + whack_log(RC_COMMENT, "List of X.509 CA Information Records:"); + whack_log(RC_COMMENT, " "); + } + + while (ca != NULL) + { + u_char buf[BUF_LEN]; + + /* strictpolicy per CA not supported yet + * + whack_log(RC_COMMENT, "%s, \"%s\", strictcrlpolicy: %s" + , timetoa(&ca->installed, utc), ca->name + , ca->strictcrlpolicy? "yes":"no"); + */ + whack_log(RC_COMMENT, "%s, \"%s\"", timetoa(&ca->installed, utc), ca->name); + dntoa(buf, BUF_LEN, ca->authName); + whack_log(RC_COMMENT, " authname: '%s'", buf); + if (ca->ldaphost != NULL) + whack_log(RC_COMMENT, " ldaphost: '%s'", ca->ldaphost); + if (ca->ldapbase != NULL) + whack_log(RC_COMMENT, " ldapbase: '%s'", ca->ldapbase); + if (ca->ocspuri != NULL) + whack_log(RC_COMMENT, " ocspuri: '%s'", ca->ocspuri); + + list_distribution_points(ca->crluri); + + if (ca->authKeyID.ptr != NULL) + { + datatot(ca->authKeyID.ptr, ca->authKeyID.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT, " authkey: %s", buf); + } + if (ca->authKeySerialNumber.ptr != NULL) + { + datatot(ca->authKeySerialNumber.ptr, ca->authKeySerialNumber.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT, " aserial: %s", buf); + } + ca = ca->next; + } +} + + diff --git a/src/pluto/ca.h b/src/pluto/ca.h new file mode 100644 index 000000000..8d4602dc6 --- /dev/null +++ b/src/pluto/ca.h @@ -0,0 +1,70 @@ +/* Certification Authority (CA) support for IKE authentication + * Copyright (C) 2002-2004 Andreas Steffen, Zuercher Hochschule Winterthur + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: ca.h,v 1.5 2005/12/25 12:28:40 as Exp $ + */ + +#ifndef _CA_H +#define _CA_H + +#include "x509.h" +#include "whack.h" + +#define MAX_CA_PATH_LEN 7 + +/* authority flags */ + +#define AUTH_NONE 0x00 /* no authorities */ +#define AUTH_CA 0x01 /* certification authority */ +#define AUTH_AA 0x02 /* authorization authority */ +#define AUTH_OCSP 0x04 /* ocsp signing authority */ + +/* CA info structures */ + +typedef struct ca_info ca_info_t; + +struct ca_info { + ca_info_t *next; + char *name; + time_t installed; + chunk_t authName; + chunk_t authKeyID; + chunk_t authKeySerialNumber; + char *ldaphost; + char *ldapbase; + char *ocspuri; + generalName_t *crluri; + bool strictcrlpolicy; +}; + +extern bool trusted_ca(chunk_t a, chunk_t b, int *pathlen); +extern bool match_requested_ca(generalName_t *requested_ca + , chunk_t our_ca, int *our_pathlen); +extern x509cert_t* get_authcert(chunk_t subject, chunk_t serial, chunk_t keyid + , u_char auth_flags); +extern void load_authcerts(const char *type, const char *path + , u_char auth_flags); +extern bool add_authcert(x509cert_t *cert, u_char auth_flags); +extern void free_authcerts(void); +extern void list_authcerts(const char *caption, u_char auth_flags, bool utc); +extern bool trust_authcert_candidate(const x509cert_t *cert + , const x509cert_t *alt_chain); +extern ca_info_t* get_ca_info(chunk_t name, chunk_t serial, chunk_t keyid); +extern bool find_ca_info_by_name(const char *name, bool delete); +extern void add_ca_info(const whack_message_t *msg); +extern void delete_ca_info(const char *name); +extern void free_ca_infos(void); +extern void list_ca_infos(bool utc); + +#endif /* _CA_H */ + diff --git a/src/pluto/certs.c b/src/pluto/certs.c new file mode 100644 index 000000000..779646a98 --- /dev/null +++ b/src/pluto/certs.c @@ -0,0 +1,287 @@ +/* Certificate support for IKE authentication + * Copyright (C) 2002-2004 Andreas Steffen, Zuercher Hochschule Winterthur + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: certs.c,v 1.8 2005/11/06 22:55:41 as Exp $ + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <freeswan.h> +#include <ipsec_policy.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "asn1.h" +#include "id.h" +#include "x509.h" +#include "pgp.h" +#include "pem.h" +#include "certs.h" +#include "pkcs1.h" + +/* + * used for initializatin of certs + */ +const cert_t empty_cert = {CERT_NONE, {NULL}}; + +/* + * extracts the certificate to be sent to the peer + */ +chunk_t +get_mycert(cert_t cert) +{ + switch (cert.type) + { + case CERT_PGP: + return cert.u.pgp->certificate; + case CERT_X509_SIGNATURE: + return cert.u.x509->certificate; + default: + return empty_chunk; + } +} + +/* load a coded key or certificate file with autodetection + * of binary DER or base64 PEM ASN.1 formats and armored PGP format + */ +bool +load_coded_file(const char *filename, prompt_pass_t *pass, const char *type +, chunk_t *blob, bool *pgp) +{ + err_t ugh = NULL; + + FILE *fd = fopen(filename, "r"); + + if (fd) + { + int bytes; + fseek(fd, 0, SEEK_END ); + blob->len = ftell(fd); + rewind(fd); + blob->ptr = alloc_bytes(blob->len, type); + bytes = fread(blob->ptr, 1, blob->len, fd); + fclose(fd); + plog(" loaded %s file '%s' (%d bytes)", type, filename, bytes); + + *pgp = FALSE; + + /* try DER format */ + if (is_asn1(*blob)) + { + DBG(DBG_PARSING, + DBG_log(" file coded in DER format"); + ) + return TRUE; + } + + /* try PEM format */ + ugh = pemtobin(blob, pass, filename, pgp); + + if (ugh == NULL) + { + if (*pgp) + { + DBG(DBG_PARSING, + DBG_log(" file coded in armored PGP format"); + ) + return TRUE; + } + if (is_asn1(*blob)) + { + DBG(DBG_PARSING, + DBG_log(" file coded in PEM format"); + ) + return TRUE; + } + ugh = "file coded in unknown format, discarded"; + } + + /* a conversion error has occured */ + plog(" %s", ugh); + pfree(blob->ptr); + *blob = empty_chunk; + } + else + { + plog(" could not open %s file '%s'", type, filename); + } + return FALSE; +} + +/* + * Loads a PKCS#1 or PGP private RSA key file + */ +err_t +load_rsa_private_key(const char* filename, prompt_pass_t *pass +, RSA_private_key_t *key) +{ + err_t ugh = NULL; + bool pgp = FALSE; + chunk_t blob = empty_chunk; + + const char *path = concatenate_paths(PRIVATE_KEY_PATH, filename); + + if (load_coded_file(path, pass, "private key", &blob, &pgp)) + { + if (pgp) + { + if (!parse_pgp(blob, NULL, key)) + ugh = "syntax error in PGP private key file"; + } + else + { + if (!pkcs1_parse_private_key(blob, key)) + ugh = "syntax error in PKCS#1 private key file"; + } + pfree(blob.ptr); + } + else + ugh = "error loading RSA private key file"; + + return ugh; +} +/* + * Loads a X.509 or OpenPGP certificate + */ +bool +load_cert(const char *filename, const char *label, cert_t *cert) +{ + bool pgp = FALSE; + chunk_t blob = empty_chunk; + + /* initialize cert struct */ + cert->type = CERT_NONE; + cert->u.x509 = NULL; + + if (load_coded_file(filename, NULL, label, &blob, &pgp)) + { + if (pgp) + { + pgpcert_t *pgpcert = alloc_thing(pgpcert_t, "pgpcert"); + *pgpcert = empty_pgpcert; + if (parse_pgp(blob, pgpcert, NULL)) + { + cert->type = CERT_PGP; + cert->u.pgp = pgpcert; + return TRUE; + } + else + { + plog(" error in OpenPGP certificate"); + free_pgpcert(pgpcert); + return FALSE; + } + } + else + { + x509cert_t *x509cert = alloc_thing(x509cert_t, "x509cert"); + *x509cert = empty_x509cert; + if (parse_x509cert(blob, 0, x509cert)) + { + cert->type = CERT_X509_SIGNATURE; + cert->u.x509 = x509cert; + return TRUE; + } + else + { + plog(" error in X.509 certificate"); + free_x509cert(x509cert); + return FALSE; + } + } + } + return FALSE; +} + +/* + * Loads a host certificate + */ +bool +load_host_cert(const char *filename, cert_t *cert) +{ + const char *path = concatenate_paths(HOST_CERT_PATH, filename); + + return load_cert(path, "host cert", cert); +} + +/* + * Loads a CA certificate + */ +bool +load_ca_cert(const char *filename, cert_t *cert) +{ + const char *path = concatenate_paths(CA_CERT_PATH, filename); + + return load_cert(path, "CA cert", cert); +} + +/* + * establish equality of two certificates + */ +bool +same_cert(const cert_t *a, const cert_t *b) +{ + return a->type == b->type && a->u.x509 == b->u.x509; +} + +/* for each link pointing to the certif icate + " increase the count by one + */ +void +share_cert(cert_t cert) +{ + switch (cert.type) + { + case CERT_PGP: + share_pgpcert(cert.u.pgp); + break; + case CERT_X509_SIGNATURE: + share_x509cert(cert.u.x509); + break; + default: + break; + } +} + +/* release of a certificate decreases the count by one + " the certificate is freed when the counter reaches zero + */ +void +release_cert(cert_t cert) +{ + switch (cert.type) + { + case CERT_PGP: + release_pgpcert(cert.u.pgp); + break; + case CERT_X509_SIGNATURE: + release_x509cert(cert.u.x509); + break; + default: + break; + } +} + +/* + * list all X.509 and OpenPGP end certificates + */ +void +list_certs(bool utc) +{ + list_x509_end_certs(utc); + list_pgp_end_certs(utc); +} + diff --git a/src/pluto/certs.h b/src/pluto/certs.h new file mode 100644 index 000000000..ca5acd35a --- /dev/null +++ b/src/pluto/certs.h @@ -0,0 +1,80 @@ +/* Certificate support for IKE authentication + * Copyright (C) 2002-2004 Andreas Steffen, Zuercher Hochschule Winterthur + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: certs.h,v 1.7 2005/11/06 22:55:41 as Exp $ + */ + +#ifndef _CERTS_H +#define _CERTS_H + +#include "pkcs1.h" +#include "x509.h" +#include "pgp.h" + +/* path definitions for private keys, end certs, + * cacerts, attribute certs and crls + */ +#define PRIVATE_KEY_PATH IPSEC_CONFDIR "/ipsec.d/private" +#define HOST_CERT_PATH IPSEC_CONFDIR "/ipsec.d/certs" +#define CA_CERT_PATH IPSEC_CONFDIR "/ipsec.d/cacerts" +#define A_CERT_PATH IPSEC_CONFDIR "/ipsec.d/acerts" +#define AA_CERT_PATH IPSEC_CONFDIR "/ipsec.d/aacerts" +#define OCSP_CERT_PATH IPSEC_CONFDIR "/ipsec.d/ocspcerts" +#define CRL_PATH IPSEC_CONFDIR "/ipsec.d/crls" +#define REQ_PATH IPSEC_CONFDIR "/ipsec.d/reqs" + +/* advance warning of imminent expiry of + * cacerts, public keys, and crls + */ +#define CA_CERT_WARNING_INTERVAL 30 /* days */ +#define OCSP_CERT_WARNING_INTERVAL 30 /* days */ +#define PUBKEY_WARNING_INTERVAL 7 /* days */ +#define CRL_WARNING_INTERVAL 7 /* days */ +#define ACERT_WARNING_INTERVAL 1 /* day */ + +/* certificate access structure + * currently X.509 and OpenPGP certificates are supported + */ +typedef struct { + u_char type; + union { + x509cert_t *x509; + pgpcert_t *pgp; + } u; +} cert_t; + +/* used for initialization */ +extern const cert_t empty_cert; + +/* do not send certificate requests + * flag set in plutomain.c and used in ipsec_doi.c + */ +extern bool no_cr_send; + +extern err_t load_rsa_private_key(const char* filename, prompt_pass_t *pass + , RSA_private_key_t *key); +extern chunk_t get_mycert(cert_t cert); +extern bool load_coded_file(const char *filename, prompt_pass_t *pass + , const char *type, chunk_t *blob, bool *pgp); +extern bool load_cert(const char *filename, const char *label + , cert_t *cert); +extern bool load_host_cert(const char *filename, cert_t *cert); +extern bool load_ca_cert(const char *filename, cert_t *cert); +extern bool same_cert(const cert_t *a, const cert_t *b); +extern void share_cert(cert_t cert); +extern void release_cert(cert_t cert); +extern void list_certs(bool utc); + +#endif /* _CERTS_H */ + + diff --git a/src/pluto/connections.c b/src/pluto/connections.c new file mode 100644 index 000000000..0d02b979c --- /dev/null +++ b/src/pluto/connections.c @@ -0,0 +1,4406 @@ +/* information about connections between hosts and clients + * Copyright (C) 1998-2002 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: connections.c,v 1.43 2006/04/29 18:16:02 as Exp $ + */ + +#include <string.h> +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <unistd.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <resolv.h> +#include <arpa/nameser.h> /* missing from <resolv.h> on old systems */ +#include <sys/queue.h> + +#include <freeswan.h> +#include <ipsec_policy.h> +#include "kameipsec.h" + +#include "constants.h" +#include "defs.h" +#include "id.h" +#include "x509.h" +#include "ca.h" +#include "crl.h" +#include "pgp.h" +#include "certs.h" +#include "ac.h" +#include "smartcard.h" +#include "fetch.h" +#include "connections.h" +#include "foodgroups.h" +#include "demux.h" +#include "state.h" +#include "timer.h" +#include "ipsec_doi.h" /* needs demux.h and state.h */ +#include "server.h" +#include "kernel.h" +#include "log.h" +#include "keys.h" +#include "adns.h" /* needs <resolv.h> */ +#include "dnskey.h" /* needs keys.h and adns.h */ +#include "whack.h" +#include "alg_info.h" +#include "ike_alg.h" +#include "nat_traversal.h" +#include "virtual.h" + +static void flush_pending_by_connection(struct connection *c); /* forward */ + +static struct connection *connections = NULL; + +/* struct host_pair: a nexus of information about a pair of hosts. + * A host is an IP address, UDP port pair. This is a debatable choice: + * - should port be considered (no choice of port in standard)? + * - should ID be considered (hard because not always known)? + * - should IP address matter on our end (we don't know our end)? + * Only oriented connections are registered. + * Unoriented connections are kept on the unoriented_connections + * linked list (using hp_next). For them, host_pair is NULL. + */ + +struct host_pair { + struct { + ip_address addr; + u_int16_t port; /* host order */ + } me, him; + bool initial_connection_sent; + struct connection *connections; /* connections with this pair */ + struct pending *pending; /* awaiting Keying Channel */ + struct host_pair *next; +}; + +static struct host_pair *host_pairs = NULL; + +static struct connection *unoriented_connections = NULL; + +/* check to see that Ids of peers match */ +bool +same_peer_ids(const struct connection *c, const struct connection *d +, const struct id *his_id) +{ + return same_id(&c->spd.this.id, &d->spd.this.id) + && same_id(his_id == NULL? &c->spd.that.id : his_id, &d->spd.that.id); +} + +static struct host_pair * +find_host_pair(const ip_address *myaddr, u_int16_t myport +, const ip_address *hisaddr, u_int16_t hisport) +{ + struct host_pair *p, *prev; + + /* default hisaddr to an appropriate any */ + if (hisaddr == NULL) + hisaddr = aftoinfo(addrtypeof(myaddr))->any; + + if (nat_traversal_enabled) + { + /** + * port is not relevant in host_pair. with nat_traversal we + * always use pluto_port (500) + */ + myport = pluto_port; + hisport = pluto_port; + } + + for (prev = NULL, p = host_pairs; p != NULL; prev = p, p = p->next) + { + if (sameaddr(&p->me.addr, myaddr) && p->me.port == myport + && sameaddr(&p->him.addr, hisaddr) && p->him.port == hisport) + { + if (prev != NULL) + { + prev->next = p->next; /* remove p from list */ + p->next = host_pairs; /* and stick it on front */ + host_pairs = p; + } + break; + } + } + return p; +} + +/* find head of list of connections with this pair of hosts */ +static struct connection * +find_host_pair_connections(const ip_address *myaddr, u_int16_t myport +, const ip_address *hisaddr, u_int16_t hisport) +{ + struct host_pair *hp = find_host_pair(myaddr, myport, hisaddr, hisport); + + if (nat_traversal_enabled && hp && hisaddr) + { + struct connection *c; + + for (c = hp->connections; c != NULL; c = c->hp_next) + { + if (c->spd.this.host_port == myport && c->spd.that.host_port == hisport) + return c; + } + return NULL; + } + return hp == NULL? NULL : hp->connections; +} + +static void +connect_to_host_pair(struct connection *c) +{ + if (oriented(*c)) + { + struct host_pair *hp = find_host_pair(&c->spd.this.host_addr, c->spd.this.host_port + , &c->spd.that.host_addr, c->spd.that.host_port); + + if (hp == NULL) + { + /* no suitable host_pair -- build one */ + hp = alloc_thing(struct host_pair, "host_pair"); + hp->me.addr = c->spd.this.host_addr; + hp->him.addr = c->spd.that.host_addr; + hp->me.port = nat_traversal_enabled ? pluto_port : c->spd.this.host_port; + hp->him.port = nat_traversal_enabled ? pluto_port : c->spd.that.host_port; + hp->initial_connection_sent = FALSE; + hp->connections = NULL; + hp->pending = NULL; + hp->next = host_pairs; + host_pairs = hp; + } + c->host_pair = hp; + c->hp_next = hp->connections; + hp->connections = c; + } + else + { + /* since this connection isn't oriented, we place it + * in the unoriented_connections list instead. + */ + c->host_pair = NULL; + c->hp_next = unoriented_connections; + unoriented_connections = c; + } +} + +/* find a connection by name. + * If strict, don't accept a CK_INSTANCE. + * Move the winner (if any) to the front. + * If none is found, and strict, a diagnostic is logged to whack. + */ +struct connection * +con_by_name(const char *nm, bool strict) +{ + struct connection *p, *prev; + + for (prev = NULL, p = connections; ; prev = p, p = p->ac_next) + { + if (p == NULL) + { + if (strict) + whack_log(RC_UNKNOWN_NAME + , "no connection named \"%s\"", nm); + break; + } + if (streq(p->name, nm) + && (!strict || p->kind != CK_INSTANCE)) + { + if (prev != NULL) + { + prev->ac_next = p->ac_next; /* remove p from list */ + p->ac_next = connections; /* and stick it on front */ + connections = p; + } + break; + } + } + return p; +} + +void +release_connection(struct connection *c, bool relations) +{ + if (c->kind == CK_INSTANCE) + { + /* This does everything we need. + * Note that we will be called recursively by delete_connection, + * but kind will be CK_GOING_AWAY. + */ + delete_connection(c, relations); + } + else + { + flush_pending_by_connection(c); + delete_states_by_connection(c, relations); + unroute_connection(c); + } +} + +/* Delete a connection */ + +#define list_rm(etype, enext, e, ehead) { \ + etype **ep; \ + for (ep = &(ehead); *ep != (e); ep = &(*ep)->enext) \ + passert(*ep != NULL); /* we must not come up empty-handed */ \ + *ep = (e)->enext; \ + } + + +void +delete_connection(struct connection *c, bool relations) +{ + struct connection *old_cur_connection + = cur_connection == c? NULL : cur_connection; +#ifdef DEBUG + lset_t old_cur_debugging = cur_debugging; +#endif + + set_cur_connection(c); + + /* Must be careful to avoid circularity: + * we mark c as going away so it won't get deleted recursively. + */ + passert(c->kind != CK_GOING_AWAY); + if (c->kind == CK_INSTANCE) + { + plog("deleting connection \"%s\" instance with peer %s {isakmp=#%lu/ipsec=#%lu}" + , c->name + , ip_str(&c->spd.that.host_addr) + , c->newest_isakmp_sa, c->newest_ipsec_sa); + c->kind = CK_GOING_AWAY; + } + else + { + plog("deleting connection"); + } + release_connection(c, relations); /* won't delete c */ + + if (c->kind == CK_GROUP) + delete_group(c); + + /* free up any logging resources */ + perpeer_logfree(c); + + /* find and delete c from connections list */ + list_rm(struct connection, ac_next, c, connections); + cur_connection = old_cur_connection; + + /* find and delete c from the host pair list */ + if (c->host_pair == NULL) + { + if (c->ikev1) + list_rm(struct connection, hp_next, c, unoriented_connections); + } + else + { + struct host_pair *hp = c->host_pair; + + list_rm(struct connection, hp_next, c, hp->connections); + c->host_pair = NULL; /* redundant, but safe */ + + /* if there are no more connections with this host_pair + * and we haven't even made an initial contact, let's delete + * this guy in case we were created by an attempted DOS attack. + */ + if (hp->connections == NULL + && !hp->initial_connection_sent) + { + passert(hp->pending == NULL); /* ??? must deal with this! */ + list_rm(struct host_pair, next, hp, host_pairs); + pfree(hp); + } + } + + if (c->kind != CK_GOING_AWAY) + pfreeany(c->spd.that.virt); + +#ifdef DEBUG + cur_debugging = old_cur_debugging; +#endif + pfreeany(c->name); + free_id_content(&c->spd.this.id); + pfreeany(c->spd.this.updown); + freeanychunk(c->spd.this.ca); + free_ietfAttrList(c->spd.this.groups); + free_id_content(&c->spd.that.id); + pfreeany(c->spd.that.updown); + freeanychunk(c->spd.that.ca); + free_ietfAttrList(c->spd.that.groups); + free_generalNames(c->requested_ca, TRUE); + gw_delref(&c->gw_info); + + lock_certs_and_keys("delete_connection"); + release_cert(c->spd.this.cert); + scx_release(c->spd.this.sc); + release_cert(c->spd.that.cert); + scx_release(c->spd.that.sc); + unlock_certs_and_keys("delete_connection"); + + alg_info_delref((struct alg_info **)&c->alg_info_esp); + alg_info_delref((struct alg_info **)&c->alg_info_ike); + + pfree(c); +} + +/* Delete connections with the specified name */ +void +delete_connections_by_name(const char *name, bool strict) +{ + struct connection *c = con_by_name(name, strict); + + for (; c != NULL; c = con_by_name(name, FALSE)) + delete_connection(c, FALSE); +} + +void +delete_every_connection(void) +{ + while (connections != NULL) + delete_connection(connections, TRUE); +} + +void +release_dead_interfaces(void) +{ + struct host_pair *hp; + + for (hp = host_pairs; hp != NULL; hp = hp->next) + { + struct connection **pp + , *p; + + for (pp = &hp->connections; (p = *pp) != NULL; ) + { + if (p->interface->change == IFN_DELETE) + { + /* this connection's interface is going away */ + enum connection_kind k = p->kind; + + release_connection(p, TRUE); + + if (k <= CK_PERMANENT) + { + /* The connection should have survived release: + * move it to the unoriented_connections list. + */ + passert(p == *pp); + + p->interface = NULL; + + *pp = p->hp_next; /* advance *pp */ + p->host_pair = NULL; + p->hp_next = unoriented_connections; + unoriented_connections = p; + } + else + { + /* The connection should have vanished, + * but the previous connection remains. + */ + passert(p != *pp); + } + } + else + { + pp = &p->hp_next; /* advance pp */ + } + } + } +} + +/* adjust orientations of connections to reflect newly added interfaces */ +void +check_orientations(void) +{ + /* try to orient all the unoriented connections */ + { + struct connection *c = unoriented_connections; + + unoriented_connections = NULL; + + while (c != NULL) + { + struct connection *nxt = c->hp_next; + + (void)orient(c); + connect_to_host_pair(c); + c = nxt; + } + } + + /* Check that no oriented connection has become double-oriented. + * In other words, the far side must not match one of our new interfaces. + */ + { + struct iface *i; + + for (i = interfaces; i != NULL; i = i->next) + { + if (i->change == IFN_ADD) + { + struct host_pair *hp; + + for (hp = host_pairs; hp != NULL; hp = hp->next) + { + if (sameaddr(&hp->him.addr, &i->addr) + && (!no_klips || hp->him.port == pluto_port)) + { + /* bad news: the whole chain of connections + * hanging off this host pair has both sides + * matching an interface. + * We'll get rid of them, using orient and + * connect_to_host_pair. But we'll be lazy + * and not ditch the host_pair itself (the + * cost of leaving it is slight and cannot + * be induced by a foe). + */ + struct connection *c = hp->connections; + + hp->connections = NULL; + while (c != NULL) + { + struct connection *nxt = c->hp_next; + + c->interface = NULL; + (void)orient(c); + connect_to_host_pair(c); + c = nxt; + } + } + } + } + } + } +} + +static err_t +default_end(struct end *e, ip_address *dflt_nexthop) +{ + err_t ugh = NULL; + const struct af_info *afi = aftoinfo(addrtypeof(&e->host_addr)); + + if (afi == NULL) + return "unknown address family in default_end"; + + /* default ID to IP (but only if not NO_IP -- WildCard) */ + if (e->id.kind == ID_NONE && !isanyaddr(&e->host_addr)) + { + e->id.kind = afi->id_addr; + e->id.ip_addr = e->host_addr; + e->has_id_wildcards = FALSE; + } + + /* default nexthop to other side */ + if (isanyaddr(&e->host_nexthop)) + e->host_nexthop = *dflt_nexthop; + + /* default client to subnet containing only self + * XXX This may mean that the client's address family doesn't match + * tunnel_addr_family. + */ + if (!e->has_client) + ugh = addrtosubnet(&e->host_addr, &e->client); + + return ugh; +} + +/* Format the topology of a connection end, leaving out defaults. + * Largest left end looks like: client === host : port [ host_id ] --- hop + * Note: if that==NULL, skip nexthop + * Returns strlen of formated result (length excludes NUL at end). + */ +size_t +format_end(char *buf +, size_t buf_len +, const struct end *this +, const struct end *that +, bool is_left +, lset_t policy) +{ + char client[SUBNETTOT_BUF]; + const char *client_sep = ""; + char protoport[sizeof(":255/65535")]; + const char *host = NULL; + char host_space[ADDRTOT_BUF]; + char host_port[sizeof(":65535")]; + char host_id[BUF_LEN + 2]; + char hop[ADDRTOT_BUF]; + const char *hop_sep = ""; + const char *open_brackets = ""; + const char *close_brackets = ""; + + if (isanyaddr(&this->host_addr)) + { + switch (policy & (POLICY_GROUP | POLICY_OPPO)) + { + case POLICY_GROUP: + host = "%group"; + break; + case POLICY_OPPO: + host = "%opportunistic"; + break; + case POLICY_GROUP | POLICY_OPPO: + host = "%opportunisticgroup"; + break; + default: + host = "%any"; + break; + } + } + + client[0] = '\0'; + + if (is_virtual_end(this) && isanyaddr(&this->host_addr)) + { + host = "%virtual"; + } + + /* [client===] */ + if (this->has_client) + { + ip_address client_net, client_mask; + + networkof(&this->client, &client_net); + maskof(&this->client, &client_mask); + client_sep = "==="; + + /* {client_subnet_wildcard} */ + if (this->has_client_wildcard) + { + open_brackets = "{"; + close_brackets = "}"; + } + + if (isanyaddr(&client_net) && isanyaddr(&client_mask) + && (policy & (POLICY_GROUP | POLICY_OPPO))) + client_sep = ""; /* boring case */ + else if (subnetisnone(&this->client)) + strcpy(client, "?"); + else + subnettot(&this->client, 0, client, sizeof(client)); + } + else if (this->modecfg && isanyaddr(&this->host_srcip)) + { + /* we are mode config client */ + client_sep = "==="; + strcpy(client, "%modecfg"); + } + + /* host */ + if (host == NULL) + { + addrtot(&this->host_addr, 0, host_space, sizeof(host_space)); + host = host_space; + } + + host_port[0] = '\0'; + if (this->host_port != IKE_UDP_PORT) + snprintf(host_port, sizeof(host_port), ":%u" + , this->host_port); + + /* payload portocol and port */ + protoport[0] = '\0'; + if (this->has_port_wildcard) + snprintf(protoport, sizeof(protoport), ":%u/%%any", this->protocol); + else if (this->port || this->protocol) + snprintf(protoport, sizeof(protoport), ":%u/%u", this->protocol + , this->port); + + /* id, if different from host */ + host_id[0] = '\0'; + if (this->id.kind == ID_MYID) + { + strcpy(host_id, "[%myid]"); + } + else if (!(this->id.kind == ID_NONE + || (id_is_ipaddr(&this->id) && sameaddr(&this->id.ip_addr, &this->host_addr)))) + { + int len = idtoa(&this->id, host_id+1, sizeof(host_id)-2); + + host_id[0] = '['; + strcpy(&host_id[len < 0? (ptrdiff_t)sizeof(host_id)-2 : 1 + len], "]"); + } + + /* [---hop] */ + hop[0] = '\0'; + hop_sep = ""; + if (that != NULL && !sameaddr(&this->host_nexthop, &that->host_addr)) + { + addrtot(&this->host_nexthop, 0, hop, sizeof(hop)); + hop_sep = "---"; + } + + if (is_left) + snprintf(buf, buf_len, "%s%s%s%s%s%s%s%s%s%s" + , open_brackets, client, close_brackets + , client_sep, host, host_port, host_id + , protoport, hop_sep, hop); + else + snprintf(buf, buf_len, "%s%s%s%s%s%s%s%s%s%s" + , hop, hop_sep, host, host_port, host_id + , protoport, client_sep + , open_brackets, client, close_brackets); + return strlen(buf); +} + +/* format topology of a connection. + * Two symmetric ends separated by ... + */ +#define CONNECTION_BUF (2 * (END_BUF - 1) + 4) + +static size_t +format_connection(char *buf, size_t buf_len + , const struct connection *c + , struct spd_route *sr) +{ + size_t w = format_end(buf, buf_len, &sr->this, &sr->that, TRUE, LEMPTY); + + w += snprintf(buf + w, buf_len - w, "..."); + return w + format_end(buf + w, buf_len - w, &sr->that, &sr->this, FALSE, c->policy); +} + +static void +unshare_connection_strings(struct connection *c) +{ + c->name = clone_str(c->name, "connection name"); + + unshare_id_content(&c->spd.this.id); + c->spd.this.updown = clone_str(c->spd.this.updown, "updown"); + scx_share(c->spd.this.sc); + share_cert(c->spd.this.cert); + if (c->spd.this.ca.ptr != NULL) + clonetochunk(c->spd.this.ca, c->spd.this.ca.ptr, c->spd.this.ca.len, "ca string"); + + unshare_id_content(&c->spd.that.id); + c->spd.that.updown = clone_str(c->spd.that.updown, "updown"); + scx_share(c->spd.that.sc); + share_cert(c->spd.that.cert); + if (c->spd.that.ca.ptr != NULL) + clonetochunk(c->spd.that.ca, c->spd.that.ca.ptr, c->spd.that.ca.len, "ca string"); + + /* increment references to algo's */ + alg_info_addref((struct alg_info *)c->alg_info_esp); + alg_info_addref((struct alg_info *)c->alg_info_ike); +} + +static void +load_end_certificate(const char *filename, struct end *dst) +{ + time_t valid_until; + cert_t cert; + bool valid_cert = FALSE; + bool cached_cert = FALSE; + + /* initialize end certificate */ + dst->cert.type = CERT_NONE; + dst->cert.u.x509 = NULL; + + /* initialize smartcard info record */ + dst->sc = NULL; + + if (filename != NULL) + { + if (scx_on_smartcard(filename)) + { + /* load cert from smartcard */ + valid_cert = scx_load_cert(filename, &dst->sc, &cert, &cached_cert); + } + else + { + /* load cert from file */ + valid_cert = load_host_cert(filename, &cert); + } + } + + if (valid_cert) + { + err_t ugh = NULL; + + switch (cert.type) + { + case CERT_PGP: + select_pgpcert_id(cert.u.pgp, &dst->id); + + if (cached_cert) + dst->cert = cert; + else + { + valid_until = cert.u.pgp->until; + add_pgp_public_key(cert.u.pgp, cert.u.pgp->until, DAL_LOCAL); + dst->cert.type = cert.type; + dst->cert.u.pgp = add_pgpcert(cert.u.pgp); + } + break; + case CERT_X509_SIGNATURE: + select_x509cert_id(cert.u.x509, &dst->id); + + if (cached_cert) + dst->cert = cert; + else + { + /* check validity of cert */ + valid_until = cert.u.x509->notAfter; + ugh = check_validity(cert.u.x509, &valid_until); + if (ugh != NULL) + { + plog(" %s", ugh); + free_x509cert(cert.u.x509); + break; + } + + DBG(DBG_CONTROL, + DBG_log("certificate is valid") + ) + add_x509_public_key(cert.u.x509, valid_until, DAL_LOCAL); + dst->cert.type = cert.type; + dst->cert.u.x509 = add_x509cert(cert.u.x509); + } + /* if no CA is defined, use issuer as default */ + if (dst->ca.ptr == NULL) + dst->ca = dst->cert.u.x509->issuer; + break; + default: + break; + } + + /* cache the certificate that was last retrieved from the smartcard */ + if (dst->sc != NULL) + { + if (!same_cert(&dst->sc->last_cert, &dst->cert)) + { + lock_certs_and_keys("load_end_certificates"); + release_cert(dst->sc->last_cert); + dst->sc->last_cert = dst->cert; + share_cert(dst->cert); + unlock_certs_and_keys("load_end_certificates"); + } + time(&dst->sc->last_load); + } + } +} + +static bool +extract_end(struct end *dst, const whack_end_t *src, const char *which) +{ + bool same_ca = FALSE; + + /* decode id, if any */ + if (src->id == NULL) + { + dst->id.kind = ID_NONE; + } + else + { + err_t ugh = atoid(src->id, &dst->id, TRUE); + + if (ugh != NULL) + { + loglog(RC_BADID, "bad %s --id: %s (ignored)", which, ugh); + dst->id = empty_id; /* ignore bad one */ + } + } + + dst->ca = empty_chunk; + + /* decode CA distinguished name, if any */ + if (src->ca != NULL) + { + if streq(src->ca, "%same") + same_ca = TRUE; + else if (!streq(src->ca, "%any")) + { + err_t ugh; + + dst->ca.ptr = temporary_cyclic_buffer(); + ugh = atodn(src->ca, &dst->ca); + if (ugh != NULL) + { + plog("bad CA string '%s': %s (ignored)", src->ca, ugh); + dst->ca = empty_chunk; + } + } + } + + /* load local end certificate and extract ID, if any */ + load_end_certificate(src->cert, dst); + + /* does id has wildcards? */ + dst->has_id_wildcards = id_count_wildcards(&dst->id) > 0; + + /* decode group attributes, if any */ + decode_groups(src->groups, &dst->groups); + + /* the rest is simple copying of corresponding fields */ + dst->host_addr = src->host_addr; + dst->host_nexthop = src->host_nexthop; + dst->host_srcip = src->host_srcip; + dst->has_natip = src->has_natip; + dst->client = src->client; + dst->protocol = src->protocol; + dst->port = src->port; + dst->has_port_wildcard = src->has_port_wildcard; + dst->key_from_DNS_on_demand = src->key_from_DNS_on_demand; + dst->has_client = src->has_client; + dst->has_client_wildcard = src->has_client_wildcard; + dst->modecfg = src->modecfg; + dst->hostaccess = src->hostaccess; + dst->sendcert = src->sendcert; + dst->updown = src->updown; + dst->host_port = src->host_port; + + /* if host sourceip is defined but no client is present + * behind the host then set client to sourceip/32 + */ + if (addrbytesptr(&dst->host_srcip, NULL) + && !isanyaddr(&dst->host_srcip) + && !dst->has_natip + && !dst->has_client) + { + err_t ugh = addrtosubnet(&dst->host_srcip, &dst->client); + + if (ugh != NULL) + plog("could not assign host sourceip to client subnet"); + else + dst->has_client = TRUE; + } + return same_ca; +} + +static bool +check_connection_end(const whack_end_t *this, const whack_end_t *that +, const whack_message_t *wm) +{ + if (wm->addr_family != addrtypeof(&this->host_addr) + || wm->addr_family != addrtypeof(&this->host_nexthop) + || (this->has_client? wm->tunnel_addr_family : wm->addr_family) + != subnettypeof(&this->client) + || subnettypeof(&this->client) != subnettypeof(&that->client)) + { + /* this should have been diagnosed by whack, so we need not be clear + * !!! overloaded use of RC_CLASH + */ + loglog(RC_CLASH, "address family inconsistency in connection"); + return FALSE; + } + + if (isanyaddr(&that->host_addr)) + { + /* other side is wildcard: we must check if other conditions met */ + if (isanyaddr(&this->host_addr)) + { + loglog(RC_ORIENT, "connection must specify host IP address for our side"); + return FALSE; + } + } + + if (this->virt && (!isanyaddr(&this->host_addr) || this->has_client)) + { + loglog(RC_CLASH, + "virtual IP must only be used with %%any and without client"); + return FALSE; + } + + return TRUE; /* happy */ +} + +struct connection * +find_connection_by_reqid(uint32_t reqid) +{ + struct connection *c; + + reqid &= ~3; + for (c = connections; c != NULL; c = c->ac_next) + { + if (c->spd.reqid == reqid) + return c; + } + + return NULL; +} + +static uint32_t +gen_reqid(void) +{ + uint32_t start; + static uint32_t reqid = IPSEC_MANUAL_REQID_MAX & ~3; + + start = reqid; + do { + reqid += 4; + if (reqid == 0) + reqid = (IPSEC_MANUAL_REQID_MAX & ~3) + 4; + if (!find_connection_by_reqid(reqid)) + return reqid; + } while (reqid != start); + + exit_log("unable to allocate reqid"); +} + +void +add_connection(const whack_message_t *wm) +{ + if (con_by_name(wm->name, FALSE) != NULL) + { + loglog(RC_DUPNAME, "attempt to redefine connection \"%s\"", wm->name); + } + else if (wm->right.protocol != wm->left.protocol) + { + /* this should haven been diagnosed by whack + * !!! overloaded use of RC_CLASH + */ + loglog(RC_CLASH, "the protocol must be the same for leftport and rightport"); + } + else if (check_connection_end(&wm->right, &wm->left, wm) + && check_connection_end(&wm->left, &wm->right, wm)) + { + bool same_rightca, same_leftca; + struct connection *c = alloc_thing(struct connection, "struct connection"); + + c->name = wm->name; + c->ikev1 = wm->ikev1; + c->policy = wm->policy; + + if ((c->policy & POLICY_COMPRESS) && !can_do_IPcomp) + loglog(RC_COMMENT + , "ignoring --compress in \"%s\" because KLIPS is not configured to do IPCOMP" + , c->name); + + if (wm->esp) + { + const char *ugh; + + DBG(DBG_CONTROL, + DBG_log("from whack: got --esp=%s", wm->esp ? wm->esp: "NULL") + ) + c->alg_info_esp= alg_info_esp_create_from_str(wm->esp? wm->esp : "", &ugh); + + DBG(DBG_CRYPT|DBG_CONTROL, + static char buf[256]="<NULL>"; + + if (c->alg_info_esp) + alg_info_snprint(buf, sizeof(buf) + ,(struct alg_info *)c->alg_info_esp); + DBG_log("esp string values: %s", buf); + ) + if (c->alg_info_esp) + { + if (c->alg_info_esp->alg_info_cnt==0) + loglog(RC_LOG_SERIOUS + , "got 0 transforms for esp=\"%s\"", wm->esp); + } + else + { + loglog(RC_LOG_SERIOUS + , "esp string error: %s", ugh? ugh : "Unknown"); + } + } + + if (wm->ike) + { + const char *ugh; + + DBG(DBG_CONTROL, + DBG_log("from whack: got --ike=%s", wm->ike ? wm->ike: "NULL") + ) + c->alg_info_ike= alg_info_ike_create_from_str(wm->ike? wm->ike : "", &ugh); + + DBG(DBG_CRYPT|DBG_CONTROL, + static char buf[256]="<NULL>"; + + if (c->alg_info_ike) + alg_info_snprint(buf, sizeof(buf) + , (struct alg_info *)c->alg_info_ike); + DBG_log("ike string values: %s", buf); + ) + if (c->alg_info_ike) + { + if (c->alg_info_ike->alg_info_cnt==0) + loglog(RC_LOG_SERIOUS + , "got 0 transforms for ike=\"%s\"", wm->ike); + } + else + { + loglog(RC_LOG_SERIOUS + , "ike string error: %s", ugh? ugh : "Unknown"); + } + } + + c->sa_ike_life_seconds = wm->sa_ike_life_seconds; + c->sa_ipsec_life_seconds = wm->sa_ipsec_life_seconds; + c->sa_rekey_margin = wm->sa_rekey_margin; + c->sa_rekey_fuzz = wm->sa_rekey_fuzz; + c->sa_keying_tries = wm->sa_keying_tries; + + /* RFC 3706 DPD */ + c->dpd_delay = wm->dpd_delay; + c->dpd_timeout = wm->dpd_timeout; + c->dpd_action = wm->dpd_action; + + c->addr_family = wm->addr_family; + c->tunnel_addr_family = wm->tunnel_addr_family; + + c->requested_ca = NULL; + + same_leftca = extract_end(&c->spd.this, &wm->left, "left"); + same_rightca = extract_end(&c->spd.that, &wm->right, "right"); + + if (same_rightca) + c->spd.that.ca = c->spd.this.ca; + else if (same_leftca) + c->spd.this.ca = c->spd.that.ca; + + default_end(&c->spd.this, &c->spd.that.host_addr); + default_end(&c->spd.that, &c->spd.this.host_addr); + + /* force any wildcard host IP address, any wildcard subnet + * or any wildcard ID to that end + */ + if (isanyaddr(&c->spd.this.host_addr) || c->spd.this.has_client_wildcard + || c->spd.this.has_port_wildcard || c->spd.this.has_id_wildcards) + { + struct end t = c->spd.this; + + c->spd.this = c->spd.that; + c->spd.that = t; + } + + c->spd.next = NULL; + c->spd.reqid = gen_reqid(); + + /* set internal fields */ + c->instance_serial = 0; + c->ac_next = connections; + connections = c; + c->interface = NULL; + c->spd.routing = RT_UNROUTED; + c->newest_isakmp_sa = SOS_NOBODY; + c->newest_ipsec_sa = SOS_NOBODY; + c->spd.eroute_owner = SOS_NOBODY; + + if (c->policy & POLICY_GROUP) + { + c->kind = CK_GROUP; + add_group(c); + } + else if ((isanyaddr(&c->spd.that.host_addr) && !NEVER_NEGOTIATE(c->policy)) + || c->spd.that.has_client_wildcard || c->spd.that.has_port_wildcard + || c->spd.that.has_id_wildcards) + { + /* Opportunistic or Road Warrior or wildcard client subnet + * or wildcard ID */ + c->kind = CK_TEMPLATE; + } + else + { + c->kind = CK_PERMANENT; + } + set_policy_prio(c); /* must be after kind is set */ + +#ifdef DEBUG + c->extra_debugging = wm->debugging; +#endif + + c->gw_info = NULL; + + passert(!(wm->left.virt && wm->right.virt)); + if (wm->left.virt || wm->right.virt) + { + passert(isanyaddr(&c->spd.that.host_addr)); + c->spd.that.virt = create_virtual(c, + wm->left.virt ? wm->left.virt : wm->right.virt); + if (c->spd.that.virt) + c->spd.that.has_client = TRUE; + } + + unshare_connection_strings(c); + (void)orient(c); + + if (c->ikev1) + connect_to_host_pair(c); + + /* log all about this connection */ + plog("added connection description \"%s\"", c->name); + DBG(DBG_CONTROL, + char topo[CONNECTION_BUF]; + + (void) format_connection(topo, sizeof(topo), c, &c->spd); + + DBG_log("%s", topo); + + /* Make sure that address families can be correctly inferred + * from printed ends. + */ + passert(c->addr_family == addrtypeof(&c->spd.this.host_addr) + && c->addr_family == addrtypeof(&c->spd.this.host_nexthop) + && (c->spd.this.has_client? c->tunnel_addr_family : c->addr_family) + == subnettypeof(&c->spd.this.client) + + && c->addr_family == addrtypeof(&c->spd.that.host_addr) + && c->addr_family == addrtypeof(&c->spd.that.host_nexthop) + && (c->spd.that.has_client? c->tunnel_addr_family : c->addr_family) + == subnettypeof(&c->spd.that.client)); + + DBG_log("ike_life: %lus; ipsec_life: %lus; rekey_margin: %lus;" + " rekey_fuzz: %lu%%; keyingtries: %lu; policy: %s" + , (unsigned long) c->sa_ike_life_seconds + , (unsigned long) c->sa_ipsec_life_seconds + , (unsigned long) c->sa_rekey_margin + , (unsigned long) c->sa_rekey_fuzz + , (unsigned long) c->sa_keying_tries + , prettypolicy(c->policy)); + ); + } +} + +/* Derive a template connection from a group connection and target. + * Similar to instantiate(). Happens at whack --listen. + * Returns name of new connection. May be NULL. + * Caller is responsible for pfreeing. + */ +char * +add_group_instance(struct connection *group, const ip_subnet *target) +{ + char namebuf[100] + , targetbuf[SUBNETTOT_BUF]; + struct connection *t; + char *name = NULL; + + passert(group->kind == CK_GROUP); + passert(oriented(*group)); + + /* manufacture a unique name for this template */ + subnettot(target, 0, targetbuf, sizeof(targetbuf)); + snprintf(namebuf, sizeof(namebuf), "%s#%s", group->name, targetbuf); + + if (con_by_name(namebuf, FALSE) != NULL) + { + loglog(RC_DUPNAME, "group name + target yields duplicate name \"%s\"" + , namebuf); + } + else + { + t = clone_thing(*group, "group instance"); + t->name = namebuf; + unshare_connection_strings(t); + name = clone_str(t->name, "group instance name"); + t->spd.that.client = *target; + t->policy &= ~(POLICY_GROUP | POLICY_GROUTED); + t->kind = isanyaddr(&t->spd.that.host_addr) && !NEVER_NEGOTIATE(t->policy) + ? CK_TEMPLATE : CK_INSTANCE; + + /* reset log file info */ + t->log_file_name = NULL; + t->log_file = NULL; + t->log_file_err = FALSE; + + t->spd.reqid = gen_reqid(); + + if (t->spd.that.virt) + { + DBG_log("virtual_ip not supported in group instance"); + t->spd.that.virt = NULL; + } + + /* add to connections list */ + t->ac_next = connections; + connections = t; + + /* same host_pair as parent: stick after parent on list */ + group->hp_next = t; + + /* route if group is routed */ + if (group->policy & POLICY_GROUTED) + { + if (!trap_connection(t)) + whack_log(RC_ROUTE, "could not route"); + } + } + return name; +} + +/* an old target has disappeared for a group: delete instance */ +void +remove_group_instance(const struct connection *group USED_BY_DEBUG +, const char *name) +{ + passert(group->kind == CK_GROUP); + passert(oriented(*group)); + + delete_connections_by_name(name, FALSE); +} + +/* Common part of instantiating a Road Warrior or Opportunistic connection. + * his_id can be used to carry over an ID discovered in Phase 1. + * It must not disagree with the one in c, but if that is unspecified, + * the new connection will use his_id. + * If his_id is NULL, and c.that.id is uninstantiated (ID_NONE), the + * new connection will continue to have an uninstantiated that.id. + * Note: instantiation does not affect port numbers. + * + * Note that instantiate can only deal with a single SPD/eroute. + */ +static struct connection * +instantiate(struct connection *c, const ip_address *him +, u_int16_t his_port +, const struct id *his_id) +{ + struct connection *d; + int wildcards; + + passert(c->kind == CK_TEMPLATE); + passert(c->spd.next == NULL); + + c->instance_serial++; + d = clone_thing(*c, "temporary connection"); + if (his_id != NULL) + { + passert(match_id(his_id, &d->spd.that.id, &wildcards)); + d->spd.that.id = *his_id; + d->spd.that.has_id_wildcards = FALSE; + } + unshare_connection_strings(d); + unshare_ietfAttrList(&d->spd.this.groups); + unshare_ietfAttrList(&d->spd.that.groups); + d->kind = CK_INSTANCE; + + passert(oriented(*d)); + d->spd.that.host_addr = *him; + setportof(htons(c->spd.that.port), &d->spd.that.host_addr); + + if (his_port) d->spd.that.host_port = his_port; + + default_end(&d->spd.that, &d->spd.this.host_addr); + + /* We cannot guess what our next_hop should be, but if it was + * explicitly specified as 0.0.0.0, we set it to be him. + * (whack will not allow nexthop to be elided in RW case.) + */ + default_end(&d->spd.this, &d->spd.that.host_addr); + d->spd.next = NULL; + d->spd.reqid = gen_reqid(); + + /* set internal fields */ + d->ac_next = connections; + connections = d; + d->spd.routing = RT_UNROUTED; + d->newest_isakmp_sa = SOS_NOBODY; + d->newest_ipsec_sa = SOS_NOBODY; + d->spd.eroute_owner = SOS_NOBODY; + + /* reset log file info */ + d->log_file_name = NULL; + d->log_file = NULL; + d->log_file_err = FALSE; + + connect_to_host_pair(d); + + return d; +} + +struct connection * +rw_instantiate(struct connection *c, const ip_address *him, u_int16_t his_port +, const ip_subnet *his_net, const struct id *his_id) +{ + struct connection *d = instantiate(c, him, his_port, his_id); + + if (d && his_net && is_virtual_connection(c)) + { + d->spd.that.client = *his_net; + d->spd.that.virt = NULL; + if (subnetishost(his_net) && addrinsubnet(him, his_net)) + d->spd.that.has_client = FALSE; + } + + if (d->policy & POLICY_OPPO) + { + /* This must be before we know the client addresses. + * Fill in one that is impossible. This prevents anyone else from + * trying to use this connection to get to a particular client + */ + d->spd.that.client = *aftoinfo(subnettypeof(&d->spd.that.client))->none; + } + DBG(DBG_CONTROL + , DBG_log("instantiated \"%s\" for %s" , d->name, ip_str(him))); + return d; +} + +struct connection * +oppo_instantiate(struct connection *c +, const ip_address *him +, const struct id *his_id +, struct gw_info *gw +, const ip_address *our_client USED_BY_DEBUG +, const ip_address *peer_client) +{ + struct connection *d = instantiate(c, him, 0, his_id); + + passert(d->spd.next == NULL); + + /* fill in our client side */ + if (d->spd.this.has_client) + { + /* there was a client in the abstract connection + * so we demand that the required client is within that subnet. + */ + passert(addrinsubnet(our_client, &d->spd.this.client)); + happy(addrtosubnet(our_client, &d->spd.this.client)); + /* opportunistic connections do not use port selectors */ + setportof(0, &d->spd.this.client.addr); + } + else + { + /* there was no client in the abstract connection + * so we demand that the required client be the host + */ + passert(sameaddr(our_client, &d->spd.this.host_addr)); + } + + /* fill in peer's client side. + * If the client is the peer, excise the client from the connection. + */ + passert((d->policy & POLICY_OPPO) + && addrinsubnet(peer_client, &d->spd.that.client)); + happy(addrtosubnet(peer_client, &d->spd.that.client)); + /* opportunistic connections do not use port selectors */ + setportof(0, &d->spd.that.client.addr); + + if (sameaddr(peer_client, &d->spd.that.host_addr)) + d->spd.that.has_client = FALSE; + + passert(d->gw_info == NULL); + gw_addref(gw); + d->gw_info = gw; + + /* Adjust routing if something is eclipsing c. + * It must be a %hold for us (hard to passert this). + * If there was another instance eclipsing, we'd be using it. + */ + if (c->spd.routing == RT_ROUTED_ECLIPSED) + d->spd.routing = RT_ROUTED_PROSPECTIVE; + + /* Remember if the template is routed: + * if so, this instance applies for initiation + * even if it is created for responding. + */ + if (routed(c->spd.routing)) + d->instance_initiation_ok = TRUE; + + DBG(DBG_CONTROL, + char topo[CONNECTION_BUF]; + + (void) format_connection(topo, sizeof(topo), d, &d->spd); + DBG_log("instantiated \"%s\": %s", d->name, topo); + ); + return d; +} + +/* priority formatting */ +void +fmt_policy_prio(policy_prio_t pp, char buf[POLICY_PRIO_BUF]) +{ + if (pp == BOTTOM_PRIO) + snprintf(buf, POLICY_PRIO_BUF, "0"); + else + snprintf(buf, POLICY_PRIO_BUF, "%lu,%lu" + , pp>>16, (pp & ~(~(policy_prio_t)0 << 16)) >> 8); +} + +/* Format any information needed to identify an instance of a connection. + * Fills any needed information into buf which MUST be big enough. + * Road Warrior: peer's IP address + * Opportunistic: [" " myclient "==="] " ..." peer ["===" hisclient] '\0' + */ +static size_t +fmt_client(const ip_subnet *client, const ip_address *gw, const char *prefix, char buf[ADDRTOT_BUF]) +{ + if (subnetisaddr(client, gw)) + { + buf[0] = '\0'; /* compact denotation for "self" */ + } + else + { + char *ap; + + strcpy(buf, prefix); + ap = buf + strlen(prefix); + if (subnetisnone(client)) + strcpy(ap, "?"); /* unknown */ + else + subnettot(client, 0, ap, SUBNETTOT_BUF); + } + return strlen(buf); +} + +void +fmt_conn_instance(const struct connection *c, char buf[CONN_INST_BUF]) +{ + char *p = buf; + + *p = '\0'; + + if (c->kind == CK_INSTANCE) + { + if (c->instance_serial != 0) + { + snprintf(p, CONN_INST_BUF, "[%lu]", c->instance_serial); + p += strlen(p); + } + + if (c->policy & POLICY_OPPO) + { + size_t w = fmt_client(&c->spd.this.client, &c->spd.this.host_addr, " ", p); + + p += w; + + strcpy(p, w == 0? " ..." : "=== ..."); + p += strlen(p); + + addrtot(&c->spd.that.host_addr, 0, p, ADDRTOT_BUF); + p += strlen(p); + + (void) fmt_client(&c->spd.that.client, &c->spd.that.host_addr, "===", p); + } + else + { + *p++ = ' '; + addrtot(&c->spd.that.host_addr, 0, p, ADDRTOT_BUF); +# + if (c->spd.that.host_port != pluto_port) + { + p += strlen(p); + sprintf(p, ":%d", c->spd.that.host_port); + } + } + } +} + +/* Find an existing connection for a trapped outbound packet. + * This is attempted before we bother with gateway discovery. + * + this connection is routed or instance_of_routed_template + * (i.e. approved for on-demand) + * + this subnet contains our_client (or we are our_client) + * + that subnet contains peer_client (or peer is peer_client) + * + don't care about Phase 1 IDs (we don't know) + * Note: result may still need to be instantiated. + * The winner has the highest policy priority. + * + * If there are several with that priority, we give preference to + * the first one that is an instance. + * + * See also build_outgoing_opportunistic_connection. + */ +struct connection * +find_connection_for_clients(struct spd_route **srp, + const ip_address *our_client, + const ip_address *peer_client, + int transport_proto) +{ + struct connection *c = connections, *best = NULL; + policy_prio_t best_prio = BOTTOM_PRIO; + struct spd_route *sr; + struct spd_route *best_sr = NULL; + int our_port = ntohs(portof(our_client)); + int peer_port = ntohs(portof(peer_client)); + + passert(!isanyaddr(our_client) && !isanyaddr(peer_client)); +#ifdef DEBUG + if (DBGP(DBG_CONTROL)) + { + char ocb[ADDRTOT_BUF], pcb[ADDRTOT_BUF]; + + addrtot(our_client, 0, ocb, sizeof(ocb)); + addrtot(peer_client, 0, pcb, sizeof(pcb)); + DBG_log("find_connection: " + "looking for policy for connection: %s:%d/%d -> %s:%d/%d" + , ocb, transport_proto, our_port, pcb, transport_proto, peer_port); + } +#endif /* DEBUG */ + + for (c = connections; c != NULL; c = c->ac_next) + { + if (c->kind == CK_GROUP) + continue; + + for (sr = &c->spd; best!=c && sr; sr = sr->next) + { + if ((routed(sr->routing) || c->instance_initiation_ok) + && addrinsubnet(our_client, &sr->this.client) + && addrinsubnet(peer_client, &sr->that.client) + && addrinsubnet(peer_client, &sr->that.client) + && (!sr->this.protocol || transport_proto == sr->this.protocol) + && (!sr->this.port || our_port == sr->this.port) + && (!sr->that.port || peer_port == sr->that.port)) + { + char cib[CONN_INST_BUF]; + char cib2[CONN_INST_BUF]; + + policy_prio_t prio = 8 * (c->prio + (c->kind == CK_INSTANCE)) + + 2 * (sr->this.port == our_port) + + 2 * (sr->that.port == peer_port) + + (sr->this.protocol == transport_proto); + +#ifdef DEBUG + if (DBGP(DBG_CONTROL|DBG_CONTROLMORE)) + { + char c_ocb[SUBNETTOT_BUF], c_pcb[SUBNETTOT_BUF]; + + subnettot(&c->spd.this.client, 0, c_ocb, sizeof(c_ocb)); + subnettot(&c->spd.that.client, 0, c_pcb, sizeof(c_pcb)); + DBG_log("find_connection: conn \"%s\"%s has compatible peers: %s->%s [pri: %ld]" + , c->name + , (fmt_conn_instance(c, cib), cib) + , c_ocb, c_pcb, prio); + } +#endif /* DEBUG */ + + if (best == NULL) + { + best = c; + best_sr = sr; + best_prio = prio; + } + + DBG(DBG_CONTROLMORE, + DBG_log("find_connection: " + "comparing best \"%s\"%s [pri:%ld]{%p} (child %s) to \"%s\"%s [pri:%ld]{%p} (child %s)" + , best->name + , (fmt_conn_instance(best, cib), cib) + , best_prio + , best + , (best->policy_next ? best->policy_next->name : "none") + , c->name + , (fmt_conn_instance(c, cib2), cib2) + , prio + , c + , (c->policy_next ? c->policy_next->name : "none"))); + + if (prio > best_prio) + { + best = c; + best_sr = sr; + best_prio = prio; + } + } + } + } + + if (best!= NULL && NEVER_NEGOTIATE(best->policy)) + best = NULL; + + if (srp != NULL && best != NULL) + *srp = best_sr; + +#ifdef DEBUG + if (DBGP(DBG_CONTROL)) + { + if (best) + { + char cib[CONN_INST_BUF]; + DBG_log("find_connection: concluding with \"%s\"%s [pri:%ld]{%p} kind=%s" + , best->name + , (fmt_conn_instance(best, cib), cib) + , best_prio + , best + , enum_name(&connection_kind_names, best->kind)); + } else { + DBG_log("find_connection: concluding with empty"); + } + } +#endif /* DEBUG */ + + return best; +} + +/* Find and instantiate a connection for an outgoing Opportunistic connection. + * We've already discovered its gateway. + * We look for a the connection such that: + * + this is one of our interfaces + * + this subnet contains our_client (or we are our_client) + * (we will specialize the client). We prefer the smallest such subnet. + * + that subnet contains peer_clent (we will specialize the client). + * We prefer the smallest such subnet. + * + is opportunistic + * + that peer is NO_IP + * + don't care about Phase 1 IDs (probably should be default) + * We could look for a connection that already had the desired peer + * (rather than NO_IP) specified, but it doesn't seem worth the + * bother. + * + * We look for the routed policy applying to the narrowest subnets. + * We only succeed if we find such a policy AND it is satisfactory. + * + * The body of the inner loop is a lot like that in + * find_connection_for_clients. In this case, we know the gateways + * that we need to instantiate an opportunistic connection. + */ +struct connection * +build_outgoing_opportunistic_connection(struct gw_info *gw + ,const ip_address *our_client + ,const ip_address *peer_client) +{ + struct iface *p; + struct connection *best = NULL; + struct spd_route *sr, *bestsr; + char ocb[ADDRTOT_BUF], pcb[ADDRTOT_BUF]; + + addrtot(our_client, 0, ocb, sizeof(ocb)); + addrtot(peer_client, 0, pcb, sizeof(pcb)); + + passert(!isanyaddr(our_client) && !isanyaddr(peer_client)); + + /* We don't know his ID yet, so gw id must be an ipaddr */ + passert(gw->key != NULL); + passert(id_is_ipaddr(&gw->gw_id)); + + /* for each of our addresses... */ + for (p = interfaces; p != NULL; p = p->next) + { + /* go through those connections with our address and NO_IP as hosts + * We cannot know what port the peer would use, so we assume + * that it is pluto_port (makes debugging easier). + */ + struct connection *c = find_host_pair_connections(&p->addr + , pluto_port, (ip_address *)NULL, pluto_port); + + for (; c != NULL; c = c->hp_next) + { + DBG(DBG_OPPO, + DBG_log("checking %s", c->name)); + if (c->kind == CK_GROUP) + { + continue; + } + + for (sr = &c->spd; best!=c && sr; sr = sr->next) + { + if (routed(sr->routing) + && addrinsubnet(our_client, &sr->this.client) + && addrinsubnet(peer_client, &sr->that.client)) + { + if (best == NULL) + { + best = c; + break; + } + + DBG(DBG_OPPO, + DBG_log("comparing best %s to %s" + , best->name, c->name)); + + for (bestsr = &best->spd; best!=c && bestsr; bestsr=bestsr->next) + { + if (!subnetinsubnet(&bestsr->this.client, &sr->this.client) + || (samesubnet(&bestsr->this.client, &sr->this.client) + && !subnetinsubnet(&bestsr->that.client + , &sr->that.client))) + { + best = c; + } + } + } + } + } + } + + if (best == NULL + || NEVER_NEGOTIATE(best->policy) + || (best->policy & POLICY_OPPO) == LEMPTY + || best->kind != CK_TEMPLATE) + return NULL; + else + return oppo_instantiate(best, &gw->gw_id.ip_addr, NULL, gw + , our_client, peer_client); +} + +bool +orient(struct connection *c) +{ + struct spd_route *sr; + + if (!oriented(*c)) + { + struct iface *p; + + for (sr = &c->spd; sr; sr = sr->next) + { + /* Note: this loop does not stop when it finds a match: + * it continues checking to catch any ambiguity. + */ + for (p = interfaces; p != NULL; p = p->next) + { + if (p->ike_float) + continue; + + for (;;) + { + /* check if this interface matches this end */ + if (sameaddr(&sr->this.host_addr, &p->addr) + && (!no_klips || sr->this.host_port == pluto_port)) + { + if (oriented(*c)) + { + if (c->interface == p) + loglog(RC_LOG_SERIOUS + , "both sides of \"%s\" are our interface %s!" + , c->name, p->rname); + else + loglog(RC_LOG_SERIOUS, "two interfaces match \"%s\" (%s, %s)" + , c->name, c->interface->rname, p->rname); + c->interface = NULL; /* withdraw orientation */ + return FALSE; + } + c->interface = p; + } + + /* done with this interface if it doesn't match that end */ + if (!(sameaddr(&sr->that.host_addr, &p->addr) + && (!no_klips || sr->that.host_port == pluto_port))) + break; + + /* swap ends and try again. + * It is a little tricky to see that this loop will stop. + * Only continue if the far side matches. + * If both sides match, there is an error-out. + */ + { + struct end t = sr->this; + + sr->this = sr->that; + sr->that = t; + } + } + } + } + } + return oriented(*c); +} + +void +initiate_connection(const char *name, int whackfd) +{ + struct connection *c = con_by_name(name, TRUE); + + if (c != NULL && c->ikev1) + { + set_cur_connection(c); + if (!oriented(*c)) + { + loglog(RC_ORIENT, "we have no ipsecN interface for either end of this connection"); + } + else if (NEVER_NEGOTIATE(c->policy)) + { + loglog(RC_INITSHUNT + , "cannot initiate an authby=never connection"); + } + else if (c->kind != CK_PERMANENT) + { + if (isanyaddr(&c->spd.that.host_addr)) + loglog(RC_NOPEERIP, "cannot initiate connection without knowing peer IP address"); + else + loglog(RC_WILDCARD, "cannot initiate connection with ID wildcards"); + } + else + { + /* We will only request an IPsec SA if policy isn't empty + * (ignoring Main Mode items). + * This is a fudge, but not yet important. + * If we are to proceed asynchronously, whackfd will be NULL_FD. + */ + c->policy |= POLICY_UP; + /* do we have to prompt for a PIN code? */ + if (c->spd.this.sc != NULL && !c->spd.this.sc->valid && whackfd != NULL_FD) + scx_get_pin(c->spd.this.sc, whackfd); + + if (c->spd.this.sc != NULL && !c->spd.this.sc->valid) + { + loglog(RC_NOVALIDPIN, "cannot initiate connection without valid PIN"); + } + else + { + ipsecdoi_initiate(whackfd, c, c->policy, 1, SOS_NOBODY); + whackfd = NULL_FD; /* protect from close */ + } + } + reset_cur_connection(); + } + close_any(whackfd); +} + +/* (Possibly) Opportunistic Initiation: + * Knowing clients (single IP addresses), try to build an tunnel. + * This may involve discovering a gateway and instantiating an + * Opportunistic connection. Called when a packet is caught by + * a %trap, or when whack --oppohere --oppothere is used. + * It may turn out that an existing or non-opporunistic connnection + * can handle the traffic. + * + * Most of the code will be restarted if an ADNS request is made + * to discover the gateway. The only difference between the first + * and second entry is whether gateways_from_dns is NULL or not. + * initiate_opportunistic: initial entrypoint + * continue_oppo: where we pickup when ADNS result arrives + * initiate_opportunistic_body: main body shared by above routines + * cannot_oppo: a helper function to log a diagnostic + * This structure repeats a lot of code when the ADNS result arrives. + * This seems like a waste, but anything learned the first time through + * may no longer be true! + * + * After the first IKE message is sent, the regular state machinery + * carries negotiation forward. + */ + +enum find_oppo_step { + fos_start, + fos_myid_ip_txt, + fos_myid_hostname_txt, + fos_myid_ip_key, + fos_myid_hostname_key, + fos_our_client, + fos_our_txt, +#ifdef USE_KEYRR + fos_our_key, +#endif /* USE_KEYRR */ + fos_his_client, + fos_done +}; + +#ifdef DEBUG +static const char *const oppo_step_name[] = { + "fos_start", + "fos_myid_ip_txt", + "fos_myid_hostname_txt", + "fos_myid_ip_key", + "fos_myid_hostname_key", + "fos_our_client", + "fos_our_txt", +#ifdef USE_KEYRR + "fos_our_key", +#endif /* USE_KEYRR */ + "fos_his_client", + "fos_done" +}; +#endif /* DEBUG */ + +struct find_oppo_bundle { + enum find_oppo_step step; + err_t want; + bool failure_ok; /* if true, continue_oppo should not die on DNS failure */ + ip_address our_client; /* not pointer! */ + ip_address peer_client; + int transport_proto; + bool held; + policy_prio_t policy_prio; + ipsec_spi_t failure_shunt; /* in host order! 0 for delete. */ + int whackfd; +}; + +struct find_oppo_continuation { + struct adns_continuation ac; /* common prefix */ + struct find_oppo_bundle b; +}; + +static void +cannot_oppo(struct connection *c + , struct find_oppo_bundle *b + , err_t ugh) +{ + char pcb[ADDRTOT_BUF]; + char ocb[ADDRTOT_BUF]; + + addrtot(&b->peer_client, 0, pcb, sizeof(pcb)); + addrtot(&b->our_client, 0, ocb, sizeof(ocb)); + + DBG(DBG_DNS | DBG_OPPO, DBG_log("Can't Opportunistically initiate for %s to %s: %s" + , ocb, pcb, ugh)); + + whack_log(RC_OPPOFAILURE + , "Can't Opportunistically initiate for %s to %s: %s" + , ocb, pcb, ugh); + + if (c != NULL && c->policy_next != NULL) + { + /* there is some policy that comes afterwards */ + struct spd_route *shunt_spd; + struct connection *nc = c->policy_next; + struct state *st; + + passert(c->kind == CK_TEMPLATE); + passert(c->policy_next->kind == CK_PERMANENT); + + DBG(DBG_OPPO, DBG_log("OE failed for %s to %s, but %s overrides shunt" + , ocb, pcb, c->policy_next->name)); + + /* + * okay, here we need add to the "next" policy, which is ought + * to be an instance. + * We will add another entry to the spd_route list for the specific + * situation that we have. + */ + + shunt_spd = clone_thing(nc->spd, "shunt eroute policy"); + + shunt_spd->next = nc->spd.next; + nc->spd.next = shunt_spd; + + happy(addrtosubnet(&b->peer_client, &shunt_spd->that.client)); + + if (sameaddr(&b->peer_client, &shunt_spd->that.host_addr)) + shunt_spd->that.has_client = FALSE; + + /* + * override the tunnel destination with the one from the secondaried + * policy + */ + shunt_spd->that.host_addr = nc->spd.that.host_addr; + + /* now, lookup the state, and poke it up. + */ + + st = state_with_serialno(nc->newest_ipsec_sa); + + /* XXX what to do if the IPSEC SA has died? */ + passert(st != NULL); + + /* link the new connection instance to the state's list of + * connections + */ + + DBG(DBG_OPPO, DBG_log("installing state: %ld for %s to %s" + , nc->newest_ipsec_sa + , ocb, pcb)); + +#ifdef DEBUG + if (DBGP(DBG_OPPO | DBG_CONTROLMORE)) + { + char state_buf[LOG_WIDTH]; + char state_buf2[LOG_WIDTH]; + time_t n = now(); + + fmt_state(FALSE, st, n + , state_buf, sizeof(state_buf) + , state_buf2, sizeof(state_buf2)); + DBG_log("cannot_oppo, failure SA1: %s", state_buf); + DBG_log("cannot_oppo, failure SA2: %s", state_buf2); + } +#endif /* DEBUG */ + + if (!route_and_eroute(c, shunt_spd, st)) + { + whack_log(RC_OPPOFAILURE + , "failed to instantiate shunt policy %s for %s to %s" + , c->name + , ocb, pcb); + } + return; + } + +#ifdef KLIPS + if (b->held) + { + /* Replace HOLD with b->failure_shunt. + * If no b->failure_shunt specified, use SPI_PASS -- THIS MAY CHANGE. + */ + if (b->failure_shunt == 0) + { + DBG(DBG_OPPO, DBG_log("no explicit failure shunt for %s to %s; installing %%pass" + , ocb, pcb)); + } + + (void) replace_bare_shunt(&b->our_client, &b->peer_client + , b->policy_prio + , b->failure_shunt + , b->failure_shunt != 0 + , b->transport_proto + , ugh); + } +#endif +} + +static void initiate_opportunistic_body(struct find_oppo_bundle *b + , struct adns_continuation *ac, err_t ac_ugh); /* forward */ + +void +initiate_opportunistic(const ip_address *our_client +, const ip_address *peer_client +, int transport_proto +, bool held +, int whackfd) +{ + struct find_oppo_bundle b; + + b.want = (whackfd == NULL_FD ? "whack" : "acquire"); + b.failure_ok = FALSE; + b.our_client = *our_client; + b.peer_client = *peer_client; + b.transport_proto = transport_proto; + b.held = held; + b.policy_prio = BOTTOM_PRIO; + b.failure_shunt = 0; + b.whackfd = whackfd; + b.step = fos_start; + initiate_opportunistic_body(&b, NULL, NULL); +} + +static void +continue_oppo(struct adns_continuation *acr, err_t ugh) +{ + struct find_oppo_continuation *cr = (void *)acr; /* inherit, damn you! */ + struct connection *c; + bool was_held = cr->b.held; + int whackfd = cr->b.whackfd; + + /* note: cr->id has no resources; cr->sgw_id is id_none: + * neither need freeing. + */ + whack_log_fd = whackfd; + +#ifdef KLIPS + /* Discover and record whether %hold has gone away. + * This could have happened while we were awaiting DNS. + * We must check BEFORE any call to cannot_oppo. + */ + if (was_held) + cr->b.held = has_bare_hold(&cr->b.our_client, &cr->b.peer_client + , cr->b.transport_proto); +#endif + +#ifdef DEBUG + /* if we're going to ignore the error, at least note it in debugging log */ + if (cr->b.failure_ok && ugh != NULL) + { + DBG(DBG_CONTROL | DBG_DNS, + { + char ocb[ADDRTOT_BUF]; + char pcb[ADDRTOT_BUF]; + + addrtot(&cr->b.our_client, 0, ocb, sizeof(ocb)); + addrtot(&cr->b.peer_client, 0, pcb, sizeof(pcb)); + DBG_log("continuing from failed DNS lookup for %s, %s to %s: %s" + , cr->b.want, ocb, pcb, ugh); + }); + } +#endif + + if (!cr->b.failure_ok && ugh != NULL) + { + c = find_connection_for_clients(NULL, &cr->b.our_client, &cr->b.peer_client + , cr->b.transport_proto); + cannot_oppo(c, &cr->b + , builddiag("%s: %s", cr->b.want, ugh)); + } + else if (was_held && !cr->b.held) + { + /* was_held indicates we were started due to a %trap firing + * (as opposed to a "whack --oppohere --oppothere"). + * Since the %hold has gone, we can assume that somebody else + * has beaten us to the punch. We can go home. But lets log it. + */ + char ocb[ADDRTOT_BUF]; + char pcb[ADDRTOT_BUF]; + + addrtot(&cr->b.our_client, 0, ocb, sizeof(ocb)); + addrtot(&cr->b.peer_client, 0, pcb, sizeof(pcb)); + + loglog(RC_COMMENT + , "%%hold otherwise handled during DNS lookup for Opportunistic Initiation for %s to %s" + , ocb, pcb); + } + else + { + initiate_opportunistic_body(&cr->b, &cr->ac, ugh); + whackfd = NULL_FD; /* was handed off */ + } + + whack_log_fd = NULL_FD; + close_any(whackfd); +} + +#ifdef USE_KEYRR +static err_t +check_key_recs(enum myid_state try_state +, const struct connection *c +, struct adns_continuation *ac) +{ + /* Check if KEY lookup yielded good results. + * Looking up based on our ID. Used if + * client is ourself, or if TXT had no public key. + * Note: if c is different this time, there is + * a chance that we did the wrong query. + * If so, treat as a kind of failure. + */ + enum myid_state old_myid_state = myid_state; + const struct RSA_private_key *our_RSA_pri; + err_t ugh = NULL; + + myid_state = try_state; + + if (old_myid_state != myid_state + && old_myid_state == MYID_SPECIFIED) + { + ugh = "%myid was specified while we were guessing"; + } + else if ((our_RSA_pri = get_RSA_private_key(c)) == NULL) + { + ugh = "we don't know our own RSA key"; + } + else if (!same_id(&ac->id, &c->spd.this.id)) + { + ugh = "our ID changed underfoot"; + } + else + { + /* Similar to code in RSA_check_signature + * for checking the other side. + */ + pubkey_list_t *kr; + + ugh = "no KEY RR found for us"; + for (kr = ac->keys_from_dns; kr != NULL; kr = kr->next) + { + ugh = "all our KEY RRs have the wrong public key"; + if (kr->key->alg == PUBKEY_ALG_RSA + && same_RSA_public_key(&our_RSA_pri->pub, &kr->key->u.rsa)) + { + ugh = NULL; /* good! */ + break; + } + } + } + if (ugh != NULL) + myid_state = old_myid_state; + return ugh; +} +#endif /* USE_KEYRR */ + +static err_t +check_txt_recs(enum myid_state try_state +, const struct connection *c +, struct adns_continuation *ac) +{ + /* Check if TXT lookup yielded good results. + * Looking up based on our ID. Used if + * client is ourself, or if TXT had no public key. + * Note: if c is different this time, there is + * a chance that we did the wrong query. + * If so, treat as a kind of failure. + */ + enum myid_state old_myid_state = myid_state; + const struct RSA_private_key *our_RSA_pri; + err_t ugh = NULL; + + myid_state = try_state; + + if (old_myid_state != myid_state + && old_myid_state == MYID_SPECIFIED) + { + ugh = "%myid was specified while we were guessing"; + } + else if ((our_RSA_pri = get_RSA_private_key(c)) == NULL) + { + ugh = "we don't know our own RSA key"; + } + else if (!same_id(&ac->id, &c->spd.this.id)) + { + ugh = "our ID changed underfoot"; + } + else + { + /* Similar to code in RSA_check_signature + * for checking the other side. + */ + struct gw_info *gwp; + + ugh = "no TXT RR found for us"; + for (gwp = ac->gateways_from_dns; gwp != NULL; gwp = gwp->next) + { + ugh = "all our TXT RRs have the wrong public key"; + if (gwp->key->alg == PUBKEY_ALG_RSA + && same_RSA_public_key(&our_RSA_pri->pub, &gwp->key->u.rsa)) + { + ugh = NULL; /* good! */ + break; + } + } + } + if (ugh != NULL) + myid_state = old_myid_state; + return ugh; +} + + +/* note: gateways_from_dns must be NULL iff this is the first call */ +static void +initiate_opportunistic_body(struct find_oppo_bundle *b +, struct adns_continuation *ac +, err_t ac_ugh) +{ + struct connection *c; + struct spd_route *sr; + + /* What connection shall we use? + * First try for one that explicitly handles the clients. + */ + DBG(DBG_CONTROL, + { + char ours[ADDRTOT_BUF]; + char his[ADDRTOT_BUF]; + int ourport; + int hisport; + + addrtot(&b->our_client, 0, ours, sizeof(ours)); + addrtot(&b->peer_client, 0, his, sizeof(his)); + ourport = ntohs(portof(&b->our_client)); + hisport = ntohs(portof(&b->peer_client)); + DBG_log("initiate on demand from %s:%d to %s:%d proto=%d state: %s because: %s" + , ours, ourport, his, hisport, b->transport_proto + , oppo_step_name[b->step], b->want); + }); + if (isanyaddr(&b->our_client) || isanyaddr(&b->peer_client)) + { + cannot_oppo(NULL, b, "impossible IP address"); + } + else if ((c = find_connection_for_clients(&sr + , &b->our_client + , &b->peer_client + , b->transport_proto)) == NULL) + { + /* No connection explicitly handles the clients and there + * are no Opportunistic connections -- whine and give up. + * The failure policy cannot be gotten from a connection; we pick %pass. + */ + cannot_oppo(NULL, b, "no routed Opportunistic template covers this pair"); + } + else if (c->kind != CK_TEMPLATE) + { + /* We've found a connection that can serve. + * Do we have to initiate it? + * Not if there is currently an IPSEC SA. + * But if there is an IPSEC SA, then KLIPS would not + * have generated the acquire. So we assume that there isn't one. + * This may be redundant if a non-opportunistic + * negotiation is already being attempted. + */ + + /* If we are to proceed asynchronously, b->whackfd will be NULL_FD. */ + + if(c->kind == CK_INSTANCE) + { + char cib[CONN_INST_BUF]; + /* there is already an instance being negotiated, no nothing */ + DBG(DBG_CONTROL, DBG_log("found existing instance \"%s\"%s, rekeying it" + , c->name + , (fmt_conn_instance(c, cib), cib))); + /* XXX-mcr - return; */ + } + + /* otherwise, there is some kind of static conn that can handle + * this connection, so we initiate it */ + +#ifdef KLIPS + if (b->held) + { + /* what should we do on failure? */ + (void) assign_hold(c, sr, b->transport_proto, &b->our_client, &b->peer_client); + } +#endif + ipsecdoi_initiate(b->whackfd, c, c->policy, 1, SOS_NOBODY); + b->whackfd = NULL_FD; /* protect from close */ + } + else + { + /* We are handling an opportunistic situation. + * This involves several DNS lookup steps that require suspension. + * Note: many facts might change while we're suspended. + * Here be dragons. + * + * The first chunk of code handles the result of the previous + * DNS query (if any). It also selects the kind of the next step. + * The second chunk initiates the next DNS query (if any). + */ + enum find_oppo_step next_step; + err_t ugh = ac_ugh; + char mycredentialstr[BUF_LEN]; + char cib[CONN_INST_BUF]; + + DBG(DBG_CONTROL, DBG_log("creating new instance from \"%s\"%s" + , c->name + , (fmt_conn_instance(c, cib), cib))); + + + idtoa(&sr->this.id, mycredentialstr, sizeof(mycredentialstr)); + + passert(c->policy & POLICY_OPPO); /* can't initiate Road Warrior connections */ + + /* handle any DNS answer; select next step */ + + switch (b->step) + { + case fos_start: + /* just starting out: select first query step */ + next_step = fos_myid_ip_txt; + break; + + case fos_myid_ip_txt: /* TXT for our default IP address as %myid */ + ugh = check_txt_recs(MYID_IP, c, ac); + if (ugh != NULL) + { + /* cannot use our IP as OE identitiy for initiation */ + DBG(DBG_OPPO, DBG_log("can not use our IP (%s:TXT) as identity: %s" + , myid_str[MYID_IP] + , ugh)); + if (!logged_myid_ip_txt_warning) + { + loglog(RC_LOG_SERIOUS + , "can not use our IP (%s:TXT) as identity: %s" + , myid_str[MYID_IP] + , ugh); + logged_myid_ip_txt_warning = TRUE; + } + + next_step = fos_myid_hostname_txt; + ugh = NULL; /* failure can be recovered from */ + } + else + { + /* we can use our IP as OE identity for initiation */ + if (!logged_myid_ip_txt_warning) + { + loglog(RC_LOG_SERIOUS + , "using our IP (%s:TXT) as identity!" + , myid_str[MYID_IP]); + logged_myid_ip_txt_warning = TRUE; + } + + next_step = fos_our_client; + } + break; + + case fos_myid_hostname_txt: /* TXT for our hostname as %myid */ + ugh = check_txt_recs(MYID_HOSTNAME, c, ac); + if (ugh != NULL) + { + /* cannot use our hostname as OE identitiy for initiation */ + DBG(DBG_OPPO, DBG_log("can not use our hostname (%s:TXT) as identity: %s" + , myid_str[MYID_HOSTNAME] + , ugh)); + if (!logged_myid_fqdn_txt_warning) + { + loglog(RC_LOG_SERIOUS + , "can not use our hostname (%s:TXT) as identity: %s" + , myid_str[MYID_HOSTNAME] + , ugh); + logged_myid_fqdn_txt_warning = TRUE; + } +#ifdef USE_KEYRR + next_step = fos_myid_ip_key; + ugh = NULL; /* failure can be recovered from */ +#endif + } + else + { + /* we can use our hostname as OE identity for initiation */ + if (!logged_myid_fqdn_txt_warning) + { + loglog(RC_LOG_SERIOUS + , "using our hostname (%s:TXT) as identity!" + , myid_str[MYID_HOSTNAME]); + logged_myid_fqdn_txt_warning = TRUE; + } + next_step = fos_our_client; + } + break; + +#ifdef USE_KEYRR + case fos_myid_ip_key: /* KEY for our default IP address as %myid */ + ugh = check_key_recs(MYID_IP, c, ac); + if (ugh != NULL) + { + /* cannot use our IP as OE identitiy for initiation */ + DBG(DBG_OPPO, DBG_log("can not use our IP (%s:KEY) as identity: %s" + , myid_str[MYID_IP] + , ugh)); + if (!logged_myid_ip_key_warning) + { + loglog(RC_LOG_SERIOUS + , "can not use our IP (%s:KEY) as identity: %s" + , myid_str[MYID_IP] + , ugh); + logged_myid_ip_key_warning = TRUE; + } + + next_step = fos_myid_hostname_key; + ugh = NULL; /* failure can be recovered from */ + } + else + { + /* we can use our IP as OE identity for initiation */ + if (!logged_myid_ip_key_warning) + { + loglog(RC_LOG_SERIOUS + , "using our IP (%s:KEY) as identity!" + , myid_str[MYID_IP]); + logged_myid_ip_key_warning = TRUE; + } + next_step = fos_our_client; + } + break; + + case fos_myid_hostname_key: /* KEY for our hostname as %myid */ + ugh = check_key_recs(MYID_HOSTNAME, c, ac); + if (ugh != NULL) + { + /* cannot use our IP as OE identitiy for initiation */ + DBG(DBG_OPPO, DBG_log("can not use our hostname (%s:KEY) as identity: %s" + , myid_str[MYID_HOSTNAME] + , ugh)); + if (!logged_myid_fqdn_key_warning) + { + loglog(RC_LOG_SERIOUS + , "can not use our hostname (%s:KEY) as identity: %s" + , myid_str[MYID_HOSTNAME] + , ugh); + logged_myid_fqdn_key_warning = TRUE; + } + + next_step = fos_myid_hostname_key; + ugh = NULL; /* failure can be recovered from */ + } + else + { + /* we can use our IP as OE identity for initiation */ + if (!logged_myid_fqdn_key_warning) + { + loglog(RC_LOG_SERIOUS + , "using our hostname (%s:KEY) as identity!" + , myid_str[MYID_HOSTNAME]); + logged_myid_fqdn_key_warning = TRUE; + } + next_step = fos_our_client; + } + break; +#endif + + case fos_our_client: /* TXT for our client */ + { + /* Our client is not us: we must check the TXT records. + * Note: if c is different this time, there is + * a chance that we did the wrong query. + * If so, treat as a kind of failure. + */ + const struct RSA_private_key *our_RSA_pri = get_RSA_private_key(c); + + next_step = fos_his_client; /* normal situation */ + + passert(sr != NULL); + + if (our_RSA_pri == NULL) + { + ugh = "we don't know our own RSA key"; + } + else if (sameaddr(&sr->this.host_addr, &b->our_client)) + { + /* this wasn't true when we started -- bail */ + ugh = "our IP address changed underfoot"; + } + else if (!same_id(&ac->sgw_id, &sr->this.id)) + { + /* this wasn't true when we started -- bail */ + ugh = "our ID changed underfoot"; + } + else + { + /* Similar to code in quick_inI1_outR1_tail + * for checking the other side. + */ + struct gw_info *gwp; + + ugh = "no TXT RR for our client delegates us"; + for (gwp = ac->gateways_from_dns; gwp != NULL; gwp = gwp->next) + { + passert(same_id(&gwp->gw_id, &sr->this.id)); + + ugh = "TXT RR for our client has wrong key"; + /* If there is a key from the TXT record, + * we count it as a win if we match the key. + * If there was no key, we have a tentative win: + * we need to check our KEY record to be sure. + */ + if (!gwp->gw_key_present) + { + /* Success, but the TXT had no key + * so we must check our our own KEY records. + */ + next_step = fos_our_txt; + ugh = NULL; /* good! */ + break; + } + if (same_RSA_public_key(&our_RSA_pri->pub, &gwp->key->u.rsa)) + { + ugh = NULL; /* good! */ + break; + } + } + } + } + break; + + case fos_our_txt: /* TXT for us */ + { + /* Check if TXT lookup yielded good results. + * Looking up based on our ID. Used if + * client is ourself, or if TXT had no public key. + * Note: if c is different this time, there is + * a chance that we did the wrong query. + * If so, treat as a kind of failure. + */ + const struct RSA_private_key *our_RSA_pri = get_RSA_private_key(c); + + next_step = fos_his_client; /* unless we decide to look for KEY RR */ + + if (our_RSA_pri == NULL) + { + ugh = "we don't know our own RSA key"; + } + else if (!same_id(&ac->id, &c->spd.this.id)) + { + ugh = "our ID changed underfoot"; + } + else + { + /* Similar to code in RSA_check_signature + * for checking the other side. + */ + struct gw_info *gwp; + + ugh = "no TXT RR for us"; + for (gwp = ac->gateways_from_dns; gwp != NULL; gwp = gwp->next) + { + passert(same_id(&gwp->gw_id, &sr->this.id)); + + ugh = "TXT RR for us has wrong key"; + if (gwp->gw_key_present + && same_RSA_public_key(&our_RSA_pri->pub, &gwp->key->u.rsa)) + { + DBG(DBG_CONTROL, + DBG_log("initiate on demand found TXT with right public key at: %s" + , mycredentialstr)); + ugh = NULL; + break; + } + } +#ifdef USE_KEYRR + if (ugh != NULL) + { + /* if no TXT with right key, try KEY */ + DBG(DBG_CONTROL, + DBG_log("will try for KEY RR since initiate on demand found %s: %s" + , ugh, mycredentialstr)); + next_step = fos_our_key; + ugh = NULL; + } +#endif + } + } + break; + +#ifdef USE_KEYRR + case fos_our_key: /* KEY for us */ + { + /* Check if KEY lookup yielded good results. + * Looking up based on our ID. Used if + * client is ourself, or if TXT had no public key. + * Note: if c is different this time, there is + * a chance that we did the wrong query. + * If so, treat as a kind of failure. + */ + const struct RSA_private_key *our_RSA_pri = get_RSA_private_key(c); + + next_step = fos_his_client; /* always */ + + if (our_RSA_pri == NULL) + { + ugh = "we don't know our own RSA key"; + } + else if (!same_id(&ac->id, &c->spd.this.id)) + { + ugh = "our ID changed underfoot"; + } + else + { + /* Similar to code in RSA_check_signature + * for checking the other side. + */ + pubkey_list_t *kr; + + ugh = "no KEY RR found for us (and no good TXT RR)"; + for (kr = ac->keys_from_dns; kr != NULL; kr = kr->next) + { + ugh = "all our KEY RRs have the wrong public key (and no good TXT RR)"; + if (kr->key->alg == PUBKEY_ALG_RSA + && same_RSA_public_key(&our_RSA_pri->pub, &kr->key->u.rsa)) + { + /* do this only once a day */ + if (!logged_txt_warning) + { + loglog(RC_LOG_SERIOUS + , "found KEY RR but not TXT RR for %s. See http://www.freeswan.org/err/txt-change.html." + , mycredentialstr); + logged_txt_warning = TRUE; + } + ugh = NULL; /* good! */ + break; + } + } + } + } + break; +#endif /* USE_KEYRR */ + + case fos_his_client: /* TXT for his client */ + { + /* We've finished last DNS queries: TXT for his client. + * Using the information, try to instantiate a connection + * and start negotiating. + * We now know the peer. The chosing of "c" ignored this, + * so we will disregard its current value. + * !!! We need to randomize the entry in gw that we choose. + */ + next_step = fos_done; /* no more queries */ + + c = build_outgoing_opportunistic_connection(ac->gateways_from_dns + , &b->our_client + , &b->peer_client); + + if (c == NULL) + { + /* We cannot seem to instantiate a suitable connection: + * complain clearly. + */ + char ocb[ADDRTOT_BUF] + , pcb[ADDRTOT_BUF] + , pb[ADDRTOT_BUF]; + + addrtot(&b->our_client, 0, ocb, sizeof(ocb)); + addrtot(&b->peer_client, 0, pcb, sizeof(pcb)); + passert(id_is_ipaddr(&ac->gateways_from_dns->gw_id)); + addrtot(&ac->gateways_from_dns->gw_id.ip_addr, 0, pb, sizeof(pb)); + loglog(RC_OPPOFAILURE + , "no suitable connection for opportunism" + " between %s and %s with %s as peer" + , ocb, pcb, pb); + +#ifdef KLIPS + if (b->held) + { + /* Replace HOLD with PASS. + * The type of replacement *ought* to be + * specified by policy. + */ + (void) replace_bare_shunt(&b->our_client, &b->peer_client + , BOTTOM_PRIO + , SPI_PASS /* fail into PASS */ + , TRUE, b->transport_proto + , "no suitable connection"); + } +#endif + } + else + { + /* If we are to proceed asynchronously, b->whackfd will be NULL_FD. */ + passert(c->kind == CK_INSTANCE); + passert(c->gw_info != NULL); + passert(HAS_IPSEC_POLICY(c->policy)); + passert(LHAS(LELEM(RT_UNROUTED) | LELEM(RT_ROUTED_PROSPECTIVE), c->spd.routing)); +#ifdef KLIPS + if (b->held) + { + /* what should we do on failure? */ + (void) assign_hold(c, &c->spd + , b->transport_proto + , &b->our_client, &b->peer_client); + } +#endif + c->gw_info->key->last_tried_time = now(); + ipsecdoi_initiate(b->whackfd, c, c->policy, 1, SOS_NOBODY); + b->whackfd = NULL_FD; /* protect from close */ + } + } + break; + + default: + bad_case(b->step); + } + + /* the second chunk: initiate the next DNS query (if any) */ + DBG(DBG_CONTROL, + { + char ours[ADDRTOT_BUF]; + char his[ADDRTOT_BUF]; + + addrtot(&b->our_client, 0, ours, sizeof(ours)); + addrtot(&b->peer_client, 0, his, sizeof(his)); + DBG_log("initiate on demand from %s to %s new state: %s with ugh: %s" + , ours, his, oppo_step_name[b->step], ugh ? ugh : "ok"); + }); + + if (ugh != NULL) + { + b->policy_prio = c->prio; + b->failure_shunt = shunt_policy_spi(c, FALSE); + cannot_oppo(c, b, ugh); + } + else if (next_step == fos_done) + { + /* nothing to do */ + } + else + { + /* set up the next query */ + struct find_oppo_continuation *cr = alloc_thing(struct find_oppo_continuation + , "opportunistic continuation"); + struct id id; + + b->policy_prio = c->prio; + b->failure_shunt = shunt_policy_spi(c, FALSE); + cr->b = *b; /* copy; start hand off of whackfd */ + cr->b.failure_ok = FALSE; + cr->b.step = next_step; + + for (sr = &c->spd + ; sr!=NULL && !sameaddr(&sr->this.host_addr, &b->our_client) + ; sr = sr->next) + ; + + if (sr == NULL) + sr = &c->spd; + + /* If a %hold shunt has replaced the eroute for this template, + * record this fact. + */ + if (b->held + && sr->routing == RT_ROUTED_PROSPECTIVE && eclipsable(sr)) + { + sr->routing = RT_ROUTED_ECLIPSED; + eclipse_count++; + } + + /* Switch to issue next query. + * A case may turn out to be unnecessary. If so, it falls + * through to the next case. + * Figuring out what %myid can stand for must be done before + * our client credentials are looked up: we must know what + * the client credentials may use to identify us. + * On the other hand, our own credentials should be looked + * up after our clients in case our credentials are not + * needed at all. + * XXX this is a wasted effort if we don't have credentials + * BUT they are not needed. + */ + switch (next_step) + { + case fos_myid_ip_txt: + if (c->spd.this.id.kind == ID_MYID + && myid_state != MYID_SPECIFIED) + { + cr->b.failure_ok = TRUE; + cr->b.want = b->want = "TXT record for IP address as %myid"; + ugh = start_adns_query(&myids[MYID_IP] + , &myids[MYID_IP] + , T_TXT + , continue_oppo + , &cr->ac); + break; + } + cr->b.step = fos_myid_hostname_txt; + /* fall through */ + + case fos_myid_hostname_txt: + if (c->spd.this.id.kind == ID_MYID + && myid_state != MYID_SPECIFIED) + { +#ifdef USE_KEYRR + cr->b.failure_ok = TRUE; +#else + cr->b.failure_ok = FALSE; +#endif + cr->b.want = b->want = "TXT record for hostname as %myid"; + ugh = start_adns_query(&myids[MYID_HOSTNAME] + , &myids[MYID_HOSTNAME] + , T_TXT + , continue_oppo + , &cr->ac); + break; + } + +#ifdef USE_KEYRR + cr->b.step = fos_myid_ip_key; + /* fall through */ + + case fos_myid_ip_key: + if (c->spd.this.id.kind == ID_MYID + && myid_state != MYID_SPECIFIED) + { + cr->b.failure_ok = TRUE; + cr->b.want = b->want = "KEY record for IP address as %myid (no good TXT)"; + ugh = start_adns_query(&myids[MYID_IP] + , (const struct id *) NULL /* security gateway meaningless */ + , T_KEY + , continue_oppo + , &cr->ac); + break; + } + cr->b.step = fos_myid_hostname_key; + /* fall through */ + + case fos_myid_hostname_key: + if (c->spd.this.id.kind == ID_MYID + && myid_state != MYID_SPECIFIED) + { + cr->b.failure_ok = FALSE; /* last attempt! */ + cr->b.want = b->want = "KEY record for hostname as %myid (no good TXT)"; + ugh = start_adns_query(&myids[MYID_HOSTNAME] + , (const struct id *) NULL /* security gateway meaningless */ + , T_KEY + , continue_oppo + , &cr->ac); + break; + } +#endif + cr->b.step = fos_our_client; + /* fall through */ + + case fos_our_client: /* TXT for our client */ + if (!sameaddr(&c->spd.this.host_addr, &b->our_client)) + { + /* Check that at least one TXT(reverse(b->our_client)) is workable. + * Note: {unshare|free}_id_content not needed for id: ephemeral. + */ + cr->b.want = b->want = "our client's TXT record"; + iptoid(&b->our_client, &id); + ugh = start_adns_query(&id + , &c->spd.this.id /* we are the security gateway */ + , T_TXT + , continue_oppo + , &cr->ac); + break; + } + cr->b.step = fos_our_txt; + /* fall through */ + + case fos_our_txt: /* TXT for us */ + cr->b.failure_ok = b->failure_ok = TRUE; + cr->b.want = b->want = "our TXT record"; + ugh = start_adns_query(&sr->this.id + , &sr->this.id /* we are the security gateway XXX - maybe ignore? mcr */ + , T_TXT + , continue_oppo + , &cr->ac); + break; + +#ifdef USE_KEYRR + case fos_our_key: /* KEY for us */ + cr->b.want = b->want = "our KEY record"; + cr->b.failure_ok = b->failure_ok = FALSE; + ugh = start_adns_query(&sr->this.id + , (const struct id *) NULL /* security gateway meaningless */ + , T_KEY + , continue_oppo + , &cr->ac); + break; +#endif /* USE_KEYRR */ + + case fos_his_client: /* TXT for his client */ + /* note: {unshare|free}_id_content not needed for id: ephemeral */ + cr->b.want = b->want = "target's TXT record"; + cr->b.failure_ok = b->failure_ok = FALSE; + iptoid(&b->peer_client, &id); + ugh = start_adns_query(&id + , (const struct id *) NULL /* security gateway unconstrained */ + , T_TXT + , continue_oppo + , &cr->ac); + break; + + default: + bad_case(next_step); + } + + if (ugh == NULL) + b->whackfd = NULL_FD; /* complete hand-off */ + else + cannot_oppo(c, b, ugh); + } + } + close_any(b->whackfd); +} + +void +terminate_connection(const char *nm) +{ + /* Loop because more than one may match (master and instances) + * But at least one is required (enforced by con_by_name). + */ + struct connection *c = con_by_name(nm, TRUE); + + if (c == NULL || !c->ikev1) + return; + + do + { + struct connection *n = c->ac_next; /* grab this before c might disappear */ + + if (streq(c->name, nm) + && c->kind >= CK_PERMANENT + && !NEVER_NEGOTIATE(c->policy)) + { + set_cur_connection(c); + plog("terminating SAs using this connection"); + c->policy &= ~POLICY_UP; + flush_pending_by_connection(c); + delete_states_by_connection(c, FALSE); + reset_cur_connection(); + } + c = n; + } while (c != NULL); +} + +/* check nexthop safety + * Our nexthop must not be within a routed client subnet, and vice versa. + * Note: we don't think this is true. We think that KLIPS will + * not process a packet output by an eroute. + */ +#ifdef NEVER +//bool +//check_nexthop(const struct connection *c) +//{ +// struct connection *d; +// +// if (addrinsubnet(&c->spd.this.host_nexthop, &c->spd.that.client)) +// { +// loglog(RC_LOG_SERIOUS, "cannot perform routing for connection \"%s\"" +// " because nexthop is within peer's client network", +// c->name); +// return FALSE; +// } +// +// for (d = connections; d != NULL; d = d->next) +// { +// if (d->routing != RT_UNROUTED) +// { +// if (addrinsubnet(&c->spd.this.host_nexthop, &d->spd.that.client)) +// { +// loglog(RC_LOG_SERIOUS, "cannot do routing for connection \"%s\" +// " because nexthop is contained in" +// " existing routing for connection \"%s\"", +// c->name, d->name); +// return FALSE; +// } +// if (addrinsubnet(&d->spd.this.host_nexthop, &c->spd.that.client)) +// { +// loglog(RC_LOG_SERIOUS, "cannot do routing for connection \"%s\" +// " because it contains nexthop of" +// " existing routing for connection \"%s\"", +// c->name, d->name); +// return FALSE; +// } +// } +// } +// return TRUE; +//} +#endif /* NEVER */ + +/* an ISAKMP SA has been established. + * Note the serial number, and release any connections with + * the same peer ID but different peer IP address. + */ +bool uniqueIDs = FALSE; /* --uniqueids? */ + +void +ISAKMP_SA_established(struct connection *c, so_serial_t serial) +{ + c->newest_isakmp_sa = serial; + + /* the connection is now oriented so that we are able to determine + * whether we are a mode config server with a virtual IP to send. + */ + if (!isanyaddr(&c->spd.that.host_srcip) && !c->spd.that.has_natip) + c->spd.that.modecfg = TRUE; + + if (uniqueIDs) + { + /* for all connections: if the same Phase 1 IDs are used + * for a different IP address, unorient that connection. + */ + struct connection *d; + + for (d = connections; d != NULL; ) + { + struct connection *next = d->ac_next; /* might move underneath us */ + + if (d->kind >= CK_PERMANENT + && same_id(&c->spd.this.id, &d->spd.this.id) + && same_id(&c->spd.that.id, &d->spd.that.id) + && (!sameaddr(&c->spd.that.host_addr, &d->spd.that.host_addr) || + (c->spd.that.host_port != d->spd.that.host_port))) + { + release_connection(d, FALSE); + } + d = next; + } + } +} + +/* Find the connection to connection c's peer's client with the + * largest value of .routing. All other things being equal, + * preference is given to c. If none is routed, return NULL. + * + * If erop is non-null, set *erop to a connection sharing both + * our client subnet and peer's client subnet with the largest value + * of .routing. If none is erouted, set *erop to NULL. + * + * The return value is used to find other connections sharing a route. + * *erop is used to find other connections sharing an eroute. + */ +struct connection * +route_owner(struct connection *c + , struct spd_route **srp + , struct connection **erop + , struct spd_route **esrp) +{ + struct connection *d + , *best_ro = c + , *best_ero = c; + struct spd_route *srd, *src; + struct spd_route *best_sr, *best_esr; + enum routing_t best_routing, best_erouting; + + passert(oriented(*c)); + best_sr = NULL; + best_esr = NULL; + best_routing = c->spd.routing; + best_erouting = best_routing; + + for (d = connections; d != NULL; d = d->ac_next) + { + for (srd = &d->spd; srd; srd = srd->next) + { + if (srd->routing == RT_UNROUTED) + continue; + + for (src = &c->spd; src; src=src->next) + { + if (!samesubnet(&src->that.client, &srd->that.client)) + continue; + if (src->that.protocol != srd->that.protocol) + continue; + if (src->that.port != srd->that.port) + continue; + passert(oriented(*d)); + if (srd->routing > best_routing) + { + best_ro = d; + best_sr = srd; + best_routing = srd->routing; + } + + if (!samesubnet(&src->this.client, &srd->this.client)) + continue; + if (src->this.protocol != srd->this.protocol) + continue; + if (src->this.port != srd->this.port) + continue; + if (srd->routing > best_erouting) + { + best_ero = d; + best_esr = srd; + best_erouting = srd->routing; + } + } + } + } + + DBG(DBG_CONTROL, + { + char cib[CONN_INST_BUF]; + err_t m = builddiag("route owner of \"%s\"%s %s:" + , c->name + , (fmt_conn_instance(c, cib), cib) + , enum_name(&routing_story, c->spd.routing)); + + if (!routed(best_ro->spd.routing)) + m = builddiag("%s NULL", m); + else if (best_ro == c) + m = builddiag("%s self", m); + else + m = builddiag("%s \"%s\"%s %s", m + , best_ro->name + , (fmt_conn_instance(best_ro, cib), cib) + , enum_name(&routing_story, best_ro->spd.routing)); + + if (erop != NULL) + { + m = builddiag("%s; eroute owner:", m); + if (!erouted(best_ero->spd.routing)) + m = builddiag("%s NULL", m); + else if (best_ero == c) + m = builddiag("%s self", m); + else + m = builddiag("%s \"%s\"%s %s", m + , best_ero->name + , (fmt_conn_instance(best_ero, cib), cib) + , enum_name(&routing_story, best_ero->spd.routing)); + } + + DBG_log("%s", m); + }); + + if (erop != NULL) + *erop = erouted(best_erouting)? best_ero : NULL; + + if (srp != NULL ) + { + *srp = best_sr; + if (esrp != NULL ) + *esrp = best_esr; + } + + return routed(best_routing)? best_ro : NULL; +} + +/* Find a connection that owns the shunt eroute between subnets. + * There ought to be only one. + * This might get to be a bottleneck -- try hashing if it does. + */ +struct connection * +shunt_owner(const ip_subnet *ours, const ip_subnet *his) +{ + struct connection *c; + struct spd_route *sr; + + for (c = connections; c != NULL; c = c->ac_next) + { + for (sr = &c->spd; sr; sr = sr->next) + { + if (shunt_erouted(sr->routing) + && samesubnet(ours, &sr->this.client) + && samesubnet(his, &sr->that.client)) + return c; + } + } + return NULL; +} + +/* Find some connection with this pair of hosts. + * We don't know enough to chose amongst those available. + * ??? no longer usefully different from find_host_pair_connections + */ +struct connection * +find_host_connection(const ip_address *me, u_int16_t my_port +, const ip_address *him, u_int16_t his_port, lset_t policy) +{ + struct connection *c = find_host_pair_connections(me, my_port, him, his_port); + + if (policy != LEMPTY) + { + lset_t auth_requested = policy & POLICY_ID_AUTH_MASK; + + /* if we have requirements for the policy, + * choose the first matching connection. + */ + while (c != NULL) + { + if (c->policy & auth_requested) + { + break; + } + c = c->hp_next; + } + } + return c; +} + +/* given an up-until-now satisfactory connection, find the best connection + * now that we just got the Phase 1 Id Payload from the peer. + * + * Comments in the code describe the (tricky!) matching criteria. + * Although this routine could handle the initiator case, + * it isn't currently called in this case. + * If it were, it could "upgrade" an Opportunistic Connection + * to a Road Warrior Connection if a suitable Peer ID were found. + * + * In RFC 2409 "The Internet Key Exchange (IKE)", + * in 5.1 "IKE Phase 1 Authenticated With Signatures", describing Main + * Mode: + * + * Initiator Responder + * ----------- ----------- + * HDR, SA --> + * <-- HDR, SA + * HDR, KE, Ni --> + * <-- HDR, KE, Nr + * HDR*, IDii, [ CERT, ] SIG_I --> + * <-- HDR*, IDir, [ CERT, ] SIG_R + * + * In 5.4 "Phase 1 Authenticated With a Pre-Shared Key": + * + * HDR, SA --> + * <-- HDR, SA + * HDR, KE, Ni --> + * <-- HDR, KE, Nr + * HDR*, IDii, HASH_I --> + * <-- HDR*, IDir, HASH_R + * + * refine_host_connection could be called in two case: + * + * - the Responder receives the IDii payload: + * + [PSK] after using PSK to decode this message + * + before sending its IDir payload + * + before using its ID in HASH_R computation + * + [DSig] before using its private key to sign SIG_R + * + before using the Initiator's ID in HASH_I calculation + * + [DSig] before using the Initiator's public key to check SIG_I + * + * - the Initiator receives the IDir payload: + * + [PSK] after using PSK to encode previous message and decode this message + * + after sending its IDii payload + * + after using its ID in HASH_I computation + * + [DSig] after using its private key to sign SIG_I + * + before using the Responder's ID to compute HASH_R + * + [DSig] before using Responder's public key to check SIG_R + * + * refine_host_connection can choose a different connection, as long as + * nothing already used is changed. + * + * In the Initiator case, the particular connection might have been + * specified by whatever provoked Pluto to initiate. For example: + * whack --initiate connection-name + * The advantages of switching connections when we're the Initiator seem + * less important than the disadvantages, so after FreeS/WAN 1.9, we + * don't do this. + */ +struct connection * +refine_host_connection(const struct state *st, const struct id *peer_id +, chunk_t peer_ca) +{ + struct connection *c = st->st_connection; + u_int16_t auth = st->st_oakley.auth; + struct connection *d; + struct connection *best_found = NULL; + lset_t auth_policy; + const chunk_t *psk = NULL; + bool wcpip; /* wildcard Peer IP? */ + + int wildcards, our_pathlen, peer_pathlen; + int best_wildcards = MAX_WILDCARDS; + int best_our_pathlen = MAX_CA_PATH_LEN; + int best_peer_pathlen = MAX_CA_PATH_LEN; + + if (same_id(&c->spd.that.id, peer_id) + && trusted_ca(peer_ca, c->spd.that.ca, &peer_pathlen) + && peer_pathlen == 0 + && match_requested_ca(c->requested_ca, c->spd.this.ca, &our_pathlen) + && our_pathlen == 0) + { + DBG(DBG_CONTROL, + DBG_log("current connection is a full match" + " -- no need to look further"); + ) + return c; + } + + switch (auth) + { + case OAKLEY_PRESHARED_KEY: + auth_policy = POLICY_PSK; + psk = get_preshared_secret(c); + /* It should be virtually impossible to fail to find PSK: + * we just used it to decode the current message! + */ + if (psk == NULL) + return NULL; /* cannot determine PSK! */ + break; + case XAUTHInitPreShared: + case XAUTHRespPreShared: + auth_policy = POLICY_XAUTH_PSK; + psk = get_preshared_secret(c); + if (psk == NULL) + return NULL; /* cannot determine PSK! */ + break; + case OAKLEY_RSA_SIG: + auth_policy = POLICY_RSASIG; + break; + case XAUTHInitRSA: + case XAUTHRespRSA: + auth_policy = POLICY_XAUTH_RSASIG; + break; + default: + bad_case(auth); + } + + /* The current connection won't do: search for one that will. + * First search for one with the same pair of hosts. + * If that fails, search for a suitable Road Warrior or Opportunistic + * connection (i.e. wildcard peer IP). + * We need to match: + * - peer_id (slightly complicated by instantiation) + * - if PSK auth, the key must not change (we used it to decode message) + * - policy-as-used must be acceptable to new connection + */ + d = c->host_pair->connections; + for (wcpip = FALSE; ; wcpip = TRUE) + { + for (; d != NULL; d = d->hp_next) + { + const char *match_name[] = {"no", "ok"}; + + bool matching_id = match_id(peer_id + , &d->spd.that.id, &wildcards); + bool matching_auth = (d->policy & auth_policy) != LEMPTY; + + bool matching_trust = trusted_ca(peer_ca + , d->spd.that.ca, &peer_pathlen); + bool matching_request = match_requested_ca(c->requested_ca + , d->spd.this.ca, &our_pathlen); + bool match = matching_id && matching_auth && + matching_trust && matching_request; + + DBG(DBG_CONTROLMORE, + DBG_log("%s: %s match (id: %s, auth: %s, trust: %s, request: %s)" + , d->name + , match ? "full":" no" + , match_name[matching_id] + , match_name[matching_auth] + , match_name[matching_trust] + , match_name[matching_request]) + ) + + /* do we have a match? */ + if (!match) + continue; + + /* ignore group connections */ + if (d->policy & POLICY_GROUP) + continue; + + if (c->spd.that.host_port != d->spd.that.host_port + && d->kind == CK_INSTANCE) + { + continue; + } + + switch (auth) + { + case OAKLEY_PRESHARED_KEY: + case XAUTHInitPreShared: + case XAUTHRespPreShared: + /* secret must match the one we already used */ + { + const chunk_t *dpsk = get_preshared_secret(d); + + if (dpsk == NULL) + continue; /* no secret */ + + if (psk != dpsk) + if (psk->len != dpsk->len + || memcmp(psk->ptr, dpsk->ptr, psk->len) != 0) + continue; /* different secret */ + } + break; + + case OAKLEY_RSA_SIG: + case XAUTHInitRSA: + case XAUTHRespRSA: + /* + * We must at least be able to find our private key + .*/ + if (d->spd.this.sc == NULL /* no smartcard */ + && get_RSA_private_key(d) == NULL) /* no private key */ + continue; + break; + + default: + bad_case(auth); + } + + /* d has passed all the tests. + * We'll go with it if the Peer ID was an exact match. + */ + if (match && wildcards == 0 && peer_pathlen == 0 && our_pathlen == 0) + return d; + + /* We'll remember it as best_found in case an exact + * match doesn't come along. + */ + if (best_found == NULL || wildcards < best_wildcards + || ((wildcards == best_wildcards && peer_pathlen < best_peer_pathlen) + || (peer_pathlen == best_peer_pathlen && our_pathlen < best_our_pathlen))) + { + best_found = d; + best_wildcards = wildcards; + best_peer_pathlen = peer_pathlen; + best_our_pathlen = our_pathlen; + } + } + if (wcpip) + return best_found; /* been around twice already */ + + /* Starting second time around. + * We're willing to settle for a connection that needs Peer IP + * instantiated: Road Warrior or Opportunistic. + * Look on list of connections for host pair with wildcard Peer IP + */ + d = find_host_pair_connections(&c->spd.this.host_addr, c->spd.this.host_port + , (ip_address *)NULL, c->spd.that.host_port); + } +} + +/** + * With virtual addressing, we must not allow someone to use an already + * used (by another id) addr/net. + */ +static bool +is_virtual_net_used(const ip_subnet *peer_net, const struct id *peer_id) +{ + struct connection *d; + + for (d = connections; d != NULL; d = d->ac_next) + { + switch (d->kind) + { + case CK_PERMANENT: + case CK_INSTANCE: + if ((subnetinsubnet(peer_net,&d->spd.that.client) || + subnetinsubnet(&d->spd.that.client,peer_net)) + && !same_id(&d->spd.that.id, peer_id)) + { + char buf[BUF_LEN]; + char client[SUBNETTOT_BUF]; + + subnettot(peer_net, 0, client, sizeof(client)); + idtoa(&d->spd.that.id, buf, sizeof(buf)); + plog("Virtual IP %s is already used by '%s'", client, buf); + idtoa(peer_id, buf, sizeof(buf)); + plog("Your ID is '%s'", buf); + return TRUE; /* already used by another one */ + } + break; + case CK_GOING_AWAY: + default: + break; + } + } + return FALSE; /* you can safely use it */ +} + +/* find_client_connection: given a connection suitable for ISAKMP + * (i.e. the hosts match), find a one suitable for IPSEC + * (i.e. with matching clients). + * + * If we don't find an exact match (not even our current connection), + * we try for one that still needs instantiation. Try Road Warrior + * abstract connections and the Opportunistic abstract connections. + * This requires inverse instantiation: abstraction. + * + * After failing to find an exact match, we abstract the peer + * to be NO_IP (the wildcard value). This enables matches with + * Road Warrior and Opportunistic abstract connections. + * + * After failing that search, we also abstract the Phase 1 peer ID + * if possible. If the peer's ID was the peer's IP address, we make + * it NO_ID; instantiation will make it the peer's IP address again. + * + * If searching for a Road Warrior abstract connection fails, + * and conditions are suitable, we search for the best Opportunistic + * abstract connection. + * + * Note: in the end, both Phase 1 IDs must be preserved, after any + * instantiation. They are the IDs that have been authenticated. + */ + +#define PATH_WEIGHT 1 +#define WILD_WEIGHT (MAX_CA_PATH_LEN+1) +#define PRIO_WEIGHT (MAX_WILDCARDS+1)*WILD_WEIGHT + +/* fc_try: a helper function for find_client_connection */ +static struct connection * +fc_try(const struct connection *c +, struct host_pair *hp +, const struct id *peer_id +, const ip_subnet *our_net +, const ip_subnet *peer_net +, const u_int8_t our_protocol +, const u_int16_t our_port +, const u_int8_t peer_protocol +, const u_int16_t peer_port +, chunk_t peer_ca +, const ietfAttrList_t *peer_list) +{ + struct connection *d; + struct connection *best = NULL; + policy_prio_t best_prio = BOTTOM_PRIO; + int wildcards, pathlen; + + const bool peer_net_is_host = subnetisaddr(peer_net, &c->spd.that.host_addr); + + for (d = hp->connections; d != NULL; d = d->hp_next) + { + struct spd_route *sr; + + if (d->policy & POLICY_GROUP) + continue; + + if (!(same_id(&c->spd.this.id, &d->spd.this.id) + && match_id(&c->spd.that.id, &d->spd.that.id, &wildcards) + && trusted_ca(peer_ca, d->spd.that.ca, &pathlen) + && group_membership(peer_list, d->name, d->spd.that.groups))) + continue; + + /* compare protocol and ports */ + if (d->spd.this.protocol != our_protocol + || d->spd.this.port != our_port + || d->spd.that.protocol != peer_protocol + || (d->spd.that.port != peer_port && !d->spd.that.has_port_wildcard)) + continue; + + /* non-Opportunistic case: + * our_client must match. + * + * So must peer_client, but the testing is complicated + * by the fact that the peer might be a wildcard + * and if so, the default value of that.client + * won't match the default peer_net. The appropriate test: + * + * If d has a peer client, it must match peer_net. + * If d has no peer client, peer_net must just have peer itself. + */ + + for (sr = &d->spd; best != d && sr != NULL; sr = sr->next) + { + policy_prio_t prio; +#ifdef DEBUG + if (DBGP(DBG_CONTROLMORE)) + { + char s1[SUBNETTOT_BUF],d1[SUBNETTOT_BUF]; + char s3[SUBNETTOT_BUF],d3[SUBNETTOT_BUF]; + + subnettot(our_net, 0, s1, sizeof(s1)); + subnettot(peer_net, 0, d1, sizeof(d1)); + subnettot(&sr->this.client, 0, s3, sizeof(s3)); + subnettot(&sr->that.client, 0, d3, sizeof(d3)); + DBG_log(" fc_try trying " + "%s:%s:%d/%d -> %s:%d/%d vs %s:%s:%d/%d -> %s:%d/%d" + , c->name, s1, c->spd.this.protocol, c->spd.this.port + , d1, c->spd.that.protocol, c->spd.that.port + , d->name, s3, sr->this.protocol, sr->this.port + , d3, sr->that.protocol, sr->that.port); + } +#endif /* DEBUG */ + + if (!samesubnet(&sr->this.client, our_net)) + continue; + + if (sr->that.has_client) + { + if (sr->that.has_client_wildcard) + { + if (!subnetinsubnet(peer_net, &sr->that.client)) + continue; + } + else + { + if (!samesubnet(&sr->that.client, peer_net) && !is_virtual_connection(d)) + continue; + if (is_virtual_connection(d) + && (!is_virtual_net_allowed(d, peer_net, &c->spd.that.host_addr) + || is_virtual_net_used(peer_net, peer_id?peer_id:&c->spd.that.id))) + continue; + } + } + else + { + if (!peer_net_is_host) + continue; + } + + /* We've run the gauntlet -- success: + * We've got an exact match of subnets. + * The connection is feasible, but we continue looking for the best. + * The highest priority wins, implementing eroute-like rule. + * - a routed connection is preferrred + * - given that, the smallest number of ID wildcards are preferred + * - given that, the shortest CA pathlength is preferred + */ + prio = PRIO_WEIGHT * routed(sr->routing) + + WILD_WEIGHT * (MAX_WILDCARDS - wildcards) + + PATH_WEIGHT * (MAX_CA_PATH_LEN - pathlen) + + 1; + if (prio > best_prio) + { + best = d; + best_prio = prio; + } + } + } + + if (best != NULL && NEVER_NEGOTIATE(best->policy)) + best = NULL; + + DBG(DBG_CONTROLMORE, + DBG_log(" fc_try concluding with %s [%ld]" + , (best ? best->name : "none"), best_prio) + ) + return best; +} + +static struct connection * +fc_try_oppo(const struct connection *c +, struct host_pair *hp +, const ip_subnet *our_net +, const ip_subnet *peer_net +, const u_int8_t our_protocol +, const u_int16_t our_port +, const u_int8_t peer_protocol +, const u_int16_t peer_port +, chunk_t peer_ca +, const ietfAttrList_t *peer_list) +{ + struct connection *d; + struct connection *best = NULL; + policy_prio_t best_prio = BOTTOM_PRIO; + int wildcards, pathlen; + + for (d = hp->connections; d != NULL; d = d->hp_next) + { + struct spd_route *sr; + policy_prio_t prio; + + if (d->policy & POLICY_GROUP) + continue; + + if (!(same_id(&c->spd.this.id, &d->spd.this.id) + && match_id(&c->spd.that.id, &d->spd.that.id, &wildcards) + && trusted_ca(peer_ca, d->spd.that.ca, &pathlen) + && group_membership(peer_list, d->name, d->spd.that.groups))) + continue; + + /* compare protocol and ports */ + if (d->spd.this.protocol != our_protocol + || d->spd.this.port != our_port + || d->spd.that.protocol != peer_protocol + || (d->spd.that.port != peer_port && !d->spd.that.has_port_wildcard)) + continue; + + /* Opportunistic case: + * our_net must be inside d->spd.this.client + * and peer_net must be inside d->spd.that.client + * Note: this host_pair chain also has shunt + * eroute conns (clear, drop), but they won't + * be marked as opportunistic. + */ + for (sr = &d->spd; sr != NULL; sr = sr->next) + { +#ifdef DEBUG + if (DBGP(DBG_CONTROLMORE)) + { + char s1[SUBNETTOT_BUF],d1[SUBNETTOT_BUF]; + char s3[SUBNETTOT_BUF],d3[SUBNETTOT_BUF]; + + subnettot(our_net, 0, s1, sizeof(s1)); + subnettot(peer_net, 0, d1, sizeof(d1)); + subnettot(&sr->this.client, 0, s3, sizeof(s3)); + subnettot(&sr->that.client, 0, d3, sizeof(d3)); + DBG_log(" fc_try_oppo trying %s:%s -> %s vs %s:%s -> %s" + , c->name, s1, d1, d->name, s3, d3); + } +#endif /* DEBUG */ + + if (!subnetinsubnet(our_net, &sr->this.client) + || !subnetinsubnet(peer_net, &sr->that.client)) + continue; + + /* The connection is feasible, but we continue looking for the best. + * The highest priority wins, implementing eroute-like rule. + * - our smallest client subnet is preferred (longest mask) + * - given that, his smallest client subnet is preferred + * - given that, a routed connection is preferrred + * - given that, the smallest number of ID wildcards are preferred + * - given that, the shortest CA pathlength is preferred + */ + prio = PRIO_WEIGHT * (d->prio + routed(sr->routing)) + + WILD_WEIGHT * (MAX_WILDCARDS - wildcards) + + PATH_WEIGHT * (MAX_CA_PATH_LEN - pathlen); + if (prio > best_prio) + { + best = d; + best_prio = prio; + } + } + } + + /* if the best wasn't opportunistic, we fail: it must be a shunt */ + if (best != NULL + && (NEVER_NEGOTIATE(best->policy) + || (best->policy & POLICY_OPPO) == LEMPTY)) + { + best = NULL; + } + + DBG(DBG_CONTROLMORE, + DBG_log(" fc_try_oppo concluding with %s [%ld]" + , (best ? best->name : "none"), best_prio) + ) + return best; + +} + +/* + * get the peer's CA and group attributes + */ +chunk_t +get_peer_ca_and_groups(struct connection *c, const ietfAttrList_t **peer_list) +{ + struct state *p1st = find_phase1_state(c, ISAKMP_SA_ESTABLISHED_STATES); + + *peer_list = NULL; + + if (p1st != NULL + && p1st->st_peer_pubkey != NULL + && p1st->st_peer_pubkey->issuer.ptr != NULL) + { + x509acert_t *ac = get_x509acert(p1st->st_peer_pubkey->issuer + , p1st->st_peer_pubkey->serial);; + + if (ac != NULL && verify_x509acert(ac, strict_crl_policy)) + *peer_list = ac->groups; + else + { + DBG(DBG_CONTROL, + DBG_log("no valid attribute cert found") + ) + } + return p1st->st_peer_pubkey->issuer; + } + return empty_chunk; +} + +struct connection * +find_client_connection(struct connection *c +, const ip_subnet *our_net, const ip_subnet *peer_net +, const u_int8_t our_protocol, const u_int16_t our_port +, const u_int8_t peer_protocol, const u_int16_t peer_port) +{ + struct connection *d; + struct spd_route *sr; + + const ietfAttrList_t *peer_list = NULL; + chunk_t peer_ca = get_peer_ca_and_groups(c, &peer_list); + +#ifdef DEBUG + if (DBGP(DBG_CONTROLMORE)) + { + char s1[SUBNETTOT_BUF],d1[SUBNETTOT_BUF]; + + subnettot(our_net, 0, s1, sizeof(s1)); + subnettot(peer_net, 0, d1, sizeof(d1)); + + DBG_log("find_client_connection starting with %s" + , (c ? c->name : "(none)")); + DBG_log(" looking for %s:%d/%d -> %s:%d/%d" + , s1, our_protocol, our_port + , d1, peer_protocol, peer_port); + } +#endif /* DEBUG */ + + /* give priority to current connection + * but even greater priority to a routed concrete connection + */ + { + struct connection *unrouted = NULL; + int srnum = -1; + + for (sr = &c->spd; unrouted == NULL && sr != NULL; sr = sr->next) + { + srnum++; + +#ifdef DEBUG + if (DBGP(DBG_CONTROLMORE)) + { + char s2[SUBNETTOT_BUF],d2[SUBNETTOT_BUF]; + + subnettot(&sr->this.client, 0, s2, sizeof(s2)); + subnettot(&sr->that.client, 0, d2, sizeof(d2)); + DBG_log(" concrete checking against sr#%d %s -> %s" + , srnum, s2, d2); + } +#endif /* DEBUG */ + + if (samesubnet(&sr->this.client, our_net) + && samesubnet(&sr->that.client, peer_net) + && sr->this.protocol == our_protocol + && sr->this.port == our_port + && sr->that.protocol == peer_protocol + && sr->that.port == peer_port + && group_membership(peer_list, c->name, sr->that.groups)) + { + passert(oriented(*c)); + if (routed(sr->routing)) + return c; + + unrouted = c; + } + } + + /* exact match? */ + d = fc_try(c, c->host_pair, NULL, our_net, peer_net + , our_protocol, our_port, peer_protocol, peer_port + , peer_ca, peer_list); + + DBG(DBG_CONTROLMORE, + DBG_log(" fc_try %s gives %s" + , c->name + , (d ? d->name : "none")) + ) + + if (d == NULL) + d = unrouted; + } + + if (d == NULL) + { + /* look for an abstract connection to match */ + struct spd_route *sr; + struct host_pair *hp = NULL; + + for (sr = &c->spd; hp==NULL && sr != NULL; sr = sr->next) + { + hp = find_host_pair(&sr->this.host_addr + , sr->this.host_port + , NULL + , sr->that.host_port); +#ifdef DEBUG + if (DBGP(DBG_CONTROLMORE)) + { + char s2[SUBNETTOT_BUF],d2[SUBNETTOT_BUF]; + + subnettot(&sr->this.client, 0, s2, sizeof(s2)); + subnettot(&sr->that.client, 0, d2, sizeof(d2)); + + DBG_log(" checking hostpair %s -> %s is %s" + , s2, d2 + , (hp ? "found" : "not found")); + } +#endif /* DEBUG */ + } + + if (hp != NULL) + { + /* RW match with actual peer_id or abstract peer_id? */ + d = fc_try(c, hp, NULL, our_net, peer_net + , our_protocol, our_port, peer_protocol, peer_port + , peer_ca, peer_list); + + if (d == NULL + && subnetishost(our_net) + && subnetishost(peer_net)) + { + /* Opportunistic match? + * Always use abstract peer_id. + * Note that later instantiation will result in the same peer_id. + */ + d = fc_try_oppo(c, hp, our_net, peer_net + , our_protocol, our_port, peer_protocol, peer_port + , peer_ca, peer_list); + } + } + } + + DBG(DBG_CONTROLMORE, + DBG_log(" concluding with d = %s" + , (d ? d->name : "none")) + ) + return d; +} + +int +connection_compare(const struct connection *ca +, const struct connection *cb) +{ + int ret; + + /* DBG_log("comparing %s to %s", ca->name, cb->name); */ + + ret = strcasecmp(ca->name, cb->name); + if (ret != 0) + return ret; + + ret = ca->kind - cb->kind; /* note: enum connection_kind behaves like int */ + if (ret != 0) + return ret; + + /* same name, and same type */ + switch (ca->kind) + { + case CK_INSTANCE: + return ca->instance_serial < cb->instance_serial ? -1 + : ca->instance_serial > cb->instance_serial ? 1 + : 0; + + default: + return ca->prio < cb->prio ? -1 + : ca->prio > cb->prio ? 1 + : 0; + } +} + +static int +connection_compare_qsort(const void *a, const void *b) +{ + return connection_compare(*(const struct connection *const *)a + , *(const struct connection *const *)b); +} + +void +show_connections_status(bool all, const char *name) +{ + struct connection *c; + int count, i; + struct connection **array; + + /* make an array of connections, and sort it */ + count = 0; + for (c = connections; c != NULL; c = c->ac_next) + { + if (c->ikev1 && (name == NULL || streq(c->name, name))) + count++; + } + array = alloc_bytes(sizeof(struct connection *)*count, "connection array"); + + count=0; + for (c = connections; c != NULL; c = c->ac_next) + { + if (c->ikev1 && (name == NULL || streq(c->name, name))) + array[count++]=c; + } + + /* sort it! */ + qsort(array, count, sizeof(struct connection *), connection_compare_qsort); + + for (i = 0; i < count; i++) + { + const char *ifn; + char instance[1 + 10 + 1]; + char prio[POLICY_PRIO_BUF]; + + c = array[i]; + + ifn = oriented(*c)? c->interface->rname : ""; + + instance[0] = '\0'; + if (c->kind == CK_INSTANCE && c->instance_serial != 0) + snprintf(instance, sizeof(instance), "[%lu]", c->instance_serial); + + /* show topology */ + { + char topo[CONNECTION_BUF]; + struct spd_route *sr = &c->spd; + int num=0; + + while (sr != NULL) + { + (void) format_connection(topo, sizeof(topo), c, sr); + whack_log(RC_COMMENT, "\"%s\"%s: %s; %s; eroute owner: #%lu" + , c->name, instance, topo + , enum_name(&routing_story, sr->routing) + , sr->eroute_owner); + sr = sr->next; + num++; + } + } + + if (all) + { + /* show CAs if defined */ + if (c->spd.this.ca.ptr != NULL || c->spd.that.ca.ptr != NULL) + { + char this_ca[BUF_LEN], that_ca[BUF_LEN]; + + dntoa_or_null(this_ca, BUF_LEN, c->spd.this.ca, "%any"); + dntoa_or_null(that_ca, BUF_LEN, c->spd.that.ca, "%any"); + + whack_log(RC_COMMENT + , "\"%s\"%s: CAs: '%s'...'%s'" + , c->name + , instance + , this_ca + , that_ca); + } + + /* show group attributes if defined */ + if (c->spd.that.groups != NULL) + { + char buf[BUF_LEN]; + + format_groups(c->spd.that.groups, buf, BUF_LEN); + whack_log(RC_COMMENT + , "\"%s\"%s: groups: %s" + , c->name + , instance + , buf); + } + + whack_log(RC_COMMENT + , "\"%s\"%s: ike_life: %lus; ipsec_life: %lus;" + " rekey_margin: %lus; rekey_fuzz: %lu%%; keyingtries: %lu" + , c->name + , instance + , (unsigned long) c->sa_ike_life_seconds + , (unsigned long) c->sa_ipsec_life_seconds + , (unsigned long) c->sa_rekey_margin + , (unsigned long) c->sa_rekey_fuzz + , (unsigned long) c->sa_keying_tries); + + /* show DPD parameters if defined */ + + if (c->dpd_action != DPD_ACTION_NONE) + whack_log(RC_COMMENT + , "\"%s\"%s: dpd_action: %s;" + " dpd_delay: %lus; dpd_timeout: %lus;" + , c->name + , instance + , enum_show(&dpd_action_names, c->dpd_action) + , (unsigned long) c->dpd_delay + , (unsigned long) c->dpd_timeout); + + if (c->policy_next) + { + whack_log(RC_COMMENT + , "\"%s\"%s: policy_next: %s" + , c->name, instance, c->policy_next->name); + } + + /* Note: we display key_from_DNS_on_demand as if policy [lr]KOD */ + fmt_policy_prio(c->prio, prio); + whack_log(RC_COMMENT + , "\"%s\"%s: policy: %s%s%s; prio: %s; interface: %s; " + , c->name + , instance + , prettypolicy(c->policy) + , c->spd.this.key_from_DNS_on_demand? "+lKOD" : "" + , c->spd.that.key_from_DNS_on_demand? "+rKOD" : "" + , prio + , ifn); + } + + whack_log(RC_COMMENT + , "\"%s\"%s: newest ISAKMP SA: #%ld; newest IPsec SA: #%ld; " + , c->name + , instance + , c->newest_isakmp_sa + , c->newest_ipsec_sa); + + if (all) + { + ike_alg_show_connection(c, instance); + kernel_alg_show_connection(c, instance); + } + } + if (count > 0) + whack_log(RC_COMMENT, BLANK_FORMAT); /* spacer */ + + pfree(array); +} + +/* struct pending, the structure representing Quick Mode + * negotiations delayed until a Keying Channel has been negotiated. + * Essentially, a pending call to quick_outI1. + */ + +struct pending { + int whack_sock; + struct state *isakmp_sa; + struct connection *connection; + lset_t policy; + unsigned long try; + so_serial_t replacing; + + struct pending *next; +}; + +/* queue a Quick Mode negotiation pending completion of a suitable Main Mode */ +void +add_pending(int whack_sock +, struct state *isakmp_sa +, struct connection *c +, lset_t policy +, unsigned long try +, so_serial_t replacing) +{ + bool already_queued = FALSE; + struct pending *p = c->host_pair->pending; + + while (p != NULL) + { + if (streq(c->name, p->connection->name)) + { + already_queued = TRUE; + break; + } + p = p->next; + } + DBG(DBG_CONTROL, + DBG_log("Queuing pending Quick Mode with %s \"%s\"%s" + , ip_str(&c->spd.that.host_addr) + , c->name + , already_queued? " already done" : "") + ) + if (already_queued) + return; + + p = alloc_thing(struct pending, "struct pending"); + p->whack_sock = whack_sock; + p->isakmp_sa = isakmp_sa; + p->connection = c; + p->policy = policy; + p->try = try; + p->replacing = replacing; + p->next = c->host_pair->pending; + c->host_pair->pending = p; +} + +/* Release all the whacks awaiting the completion of this state. + * This is accomplished by closing all the whack socket file descriptors. + * We go to a lot of trouble to tell each whack, but to not tell it twice. + */ +void +release_pending_whacks(struct state *st, err_t story) +{ + struct pending *p; + struct stat stst; + + if (st->st_whack_sock == NULL_FD || fstat(st->st_whack_sock, &stst) != 0) + zero(&stst); /* resulting st_dev/st_ino ought to be distinct */ + + release_whack(st); + + for (p = st->st_connection->host_pair->pending; p != NULL; p = p->next) + { + if (p->isakmp_sa == st && p->whack_sock != NULL_FD) + { + struct stat pst; + + if (fstat(p->whack_sock, &pst) == 0 + && (stst.st_dev != pst.st_dev || stst.st_ino != pst.st_ino)) + { + passert(whack_log_fd == NULL_FD); + whack_log_fd = p->whack_sock; + whack_log(RC_COMMENT + , "%s for ISAKMP SA, but releasing whack for pending IPSEC SA" + , story); + whack_log_fd = NULL_FD; + } + close(p->whack_sock); + p->whack_sock = NULL_FD; + } + } +} + +static void +delete_pending(struct pending **pp) +{ + struct pending *p = *pp; + + *pp = p->next; + if (p->connection != NULL) + connection_discard(p->connection); + close_any(p->whack_sock); + pfree(p); +} + +void +unpend(struct state *st) +{ + struct pending **pp + , *p; + + for (pp = &st->st_connection->host_pair->pending; (p = *pp) != NULL; ) + { + if (p->isakmp_sa == st) + { + DBG(DBG_CONTROL, DBG_log("unqueuing pending Quick Mode with %s \"%s\"" + , ip_str(&p->connection->spd.that.host_addr) + , p->connection->name)); + (void) quick_outI1(p->whack_sock, st, p->connection, p->policy + , p->try, p->replacing); + p->whack_sock = NULL_FD; /* ownership transferred */ + p->connection = NULL; /* ownership transferred */ + delete_pending(pp); + } + else + { + pp = &p->next; + } + } +} + +/* a Main Mode negotiation has been replaced; update any pending */ +void +update_pending(struct state *os, struct state *ns) +{ + struct pending *p; + + for (p = os->st_connection->host_pair->pending; p != NULL; p = p->next) + { + if (p->isakmp_sa == os) + p->isakmp_sa = ns; + if (p->connection->spd.this.host_port != ns->st_connection->spd.this.host_port) + { + p->connection->spd.this.host_port = ns->st_connection->spd.this.host_port; + p->connection->spd.that.host_port = ns->st_connection->spd.that.host_port; + } + } +} + +/* a Main Mode negotiation has failed; discard any pending */ +void +flush_pending_by_state(struct state *st) +{ + struct host_pair *hp = st->st_connection->host_pair; + + if (hp != NULL) + { + struct pending **pp + , *p; + + for (pp = &hp->pending; (p = *pp) != NULL; ) + { + if (p->isakmp_sa == st) + delete_pending(pp); + else + pp = &p->next; + } + } +} + +/* a connection has been deleted; discard any related pending */ +static void +flush_pending_by_connection(struct connection *c) +{ + if (c->host_pair != NULL) + { + struct pending **pp + , *p; + + for (pp = &c->host_pair->pending; (p = *pp) != NULL; ) + { + if (p->connection == c) + { + p->connection = NULL; /* prevent delete_pending from releasing */ + delete_pending(pp); + } + else + { + pp = &p->next; + } + } + } +} + +void +show_pending_phase2(const struct host_pair *hp, const struct state *st) +{ + const struct pending *p; + + for (p = hp->pending; p != NULL; p = p->next) + { + if (p->isakmp_sa == st) + { + /* connection-name state-number [replacing state-number] */ + char cip[CONN_INST_BUF]; + + fmt_conn_instance(p->connection, cip); + whack_log(RC_COMMENT, "#%lu: pending Phase 2 for \"%s\"%s replacing #%lu" + , p->isakmp_sa->st_serialno + , p->connection->name + , cip + , p->replacing); + } + } +} + +/* Delete a connection if it is an instance and it is no longer in use. + * We must be careful to avoid circularity: + * we don't touch it if it is CK_GOING_AWAY. + */ +void +connection_discard(struct connection *c) +{ + if (c->kind == CK_INSTANCE) + { + /* see if it is being used by a pending */ + struct pending *p; + + for (p = c->host_pair->pending; p != NULL; p = p->next) + if (p->connection == c) + return; /* in use, so we're done */ + + if (!states_use_connection(c)) + delete_connection(c, FALSE); + } +} + + +/* A template connection's eroute can be eclipsed by + * either a %hold or an eroute for an instance iff + * the template is a /32 -> /32. This requires some special casing. + */ + +long eclipse_count = 0; + +struct connection * +eclipsed(struct connection *c, struct spd_route **esrp) +{ + struct connection *ue; + struct spd_route *sr1 = &c->spd; + + ue = NULL; + + while (sr1 != NULL && ue != NULL) + { + for (ue = connections; ue != NULL; ue = ue->ac_next) + { + struct spd_route *srue = &ue->spd; + + while (srue != NULL + && srue->routing == RT_ROUTED_ECLIPSED + && !(samesubnet(&sr1->this.client, &srue->this.client) + && samesubnet(&sr1->that.client, &srue->that.client))) + { + srue = srue->next; + } + if (srue != NULL && srue->routing==RT_ROUTED_ECLIPSED) + { + *esrp = srue; + break; + } + } + } + return ue; +} + +/* + * Local Variables: + * c-basic-offset:4 + * c-style: pluto + * End: + */ diff --git a/src/pluto/connections.h b/src/pluto/connections.h new file mode 100644 index 000000000..df3af9dd4 --- /dev/null +++ b/src/pluto/connections.h @@ -0,0 +1,367 @@ +/* information about connections between hosts and clients + * Copyright (C) 1998-2001 D. Hugh Redelmeier + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: connections.h,v 1.18 2006/04/22 21:59:20 as Exp $ + */ + +#ifndef _CONNECTIONS_H +#define _CONNECTIONS_H + +#include <sys/queue.h> + +#include "id.h" +#include "certs.h" +#include "ac.h" +#include "smartcard.h" +#include "whack.h" + +/* There are two kinds of connections: + * - ISAKMP connections, between hosts (for IKE communication) + * - IPsec connections, between clients (for secure IP communication) + * + * An ISAKMP connection looks like: + * host<--->host + * + * An IPsec connection looks like: + * client-subnet<-->host<->nexthop<--->nexthop<->host<-->client-subnet + * + * For the connection to be relevant to this instance of Pluto, + * exactly one of the hosts must be a public interface of our machine + * known to this instance. + * + * The client subnet might simply be the host -- this is a + * representation of "host mode". + * + * Each nexthop defaults to the neighbouring host's IP address. + * The nexthop is a property of the pair of hosts, not each + * individually. It is only needed for IPsec because of the + * way IPsec is mixed into the kernel routing logic. Furthermore, + * only this end's nexthop is actually used. Eventually, nexthop + * will be unnecessary. + * + * Other information represented: + * - each connection has a name: a chunk of uninterpreted text + * that is unique for each connection. + * - security requirements (currently just the "policy" flags from + * the whack command to initiate the connection, but eventually + * much more. Different for ISAKMP and IPsec connections. + * - rekeying parameters: + * + time an SA may live + * + time before SA death that a rekeying should be attempted + * (only by the initiator) + * + number of times to attempt rekeying + * - With the current KLIPS, we must route packets for a client + * subnet through the ipsec interface (ipsec0). Only one + * gateway can get traffic for a specific (client) subnet. + * Furthermore, if the routing isn't in place, packets will + * be sent in the clear. + * "routing" indicates whether the routing has been done for + * this connection. Note that several connections may claim + * the same routing, as long as they agree about where the + * packets are to be sent. + * - With the current KLIPS, only one outbound IPsec SA bundle can be + * used for a particular client. This is due to a limitation + * of using only routing for selection. So only one IPsec state (SA) + * may "own" the eroute. "eroute_owner" is the serial number of + * this state, SOS_NOBODY if there is none. "routing" indicates + * what kind of erouting has been done for this connection, if any. + * + * Details on routing is in constants.h + * + * Operations on Connections: + * + * - add a new connection (with all details) [whack command] + * - delete a connection (by name) [whack command] + * - initiate a connection (by name) [whack command] + * - find a connection (by IP addresses of hosts) + * [response to peer request; finding ISAKMP connection for IPsec connection] + * + * Some connections are templates, missing the address of the peer + * (represented by INADDR_ANY). These are always arranged so that the + * missing end is "that" (there can only be one missing end). These can + * be instantiated (turned into real connections) by Pluto in one of two + * different ways: Road Warrior Instantiation or Opportunistic + * Instantiation. A template connection is marked for Opportunistic + * Instantiation by specifying the peer client as 0.0.0.0/32 (or the IPV6 + * equivalent). Otherwise, it is suitable for Road Warrior Instantiation. + * + * Instantiation creates a new temporary connection, with the missing + * details filled in. The resulting template lasts only as long as there + * is a state that uses it. + */ + +/* connection policy priority: how important this policy is + * - used to implement eroute-like precedence (augmented by a small + * bonus for a routed connection). + * - a whole number + * - larger is more important + * - three subcomponents. In order of decreasing significance: + * + length of source subnet mask (8 bits) + * + length of destination subnet mask (8 bits) + * + bias (8 bit) + * - a bias of 1 is added to allow prio BOTTOM_PRIO to be less than all + * normal priorities + * - other bias values are created on the fly to give mild preference + * to certaion conditions (eg. routedness) + * - priority is inherited -- an instance of a policy has the same priority + * as the original policy, even though its subnets might be smaller. + * - display format: n,m + */ +typedef unsigned long policy_prio_t; +#define BOTTOM_PRIO ((policy_prio_t)0) /* smaller than any real prio */ +#define set_policy_prio(c) { (c)->prio = \ + ((policy_prio_t)(c)->spd.this.client.maskbits << 16) \ + | ((policy_prio_t)(c)->spd.that.client.maskbits << 8) \ + | (policy_prio_t)1; } +#define POLICY_PRIO_BUF (3+1+3+1) +extern void fmt_policy_prio(policy_prio_t pp, char buf[POLICY_PRIO_BUF]); + +struct virtual_t; + +struct end { + struct id id; + ip_address + host_addr, + host_nexthop, + host_srcip; + ip_subnet client; + + bool key_from_DNS_on_demand; + bool has_client; + bool has_client_wildcard; + bool has_port_wildcard; + bool has_id_wildcards; + bool has_natip; + char *updown; + u_int16_t host_port; /* host order */ + u_int16_t port; /* host order */ + u_int8_t protocol; + cert_t cert; /* end certificate */ + chunk_t ca; /* CA distinguished name */ + struct ietfAttrList *groups;/* access control groups */ + smartcard_t *sc; /* smartcard reader and key info */ + struct virtual_t *virt; + bool modecfg; /* this end: request local address from server */ + /* that end: give local addresses to clients */ + bool hostaccess; /* allow access to host via iptables INPUT/OUTPUT */ + /* rules if client behind host is a subnet */ + certpolicy_t sendcert; /* whether or not to send the certificate */ +}; + +struct spd_route { + struct spd_route *next; + struct end this; + struct end that; + so_serial_t eroute_owner; + enum routing_t routing; /* level of routing in place */ + uint32_t reqid; +}; + +struct connection { + char *name; + bool ikev1; + + lset_t policy; + time_t sa_ike_life_seconds; + time_t sa_ipsec_life_seconds; + time_t sa_rekey_margin; + unsigned long sa_rekey_fuzz; + unsigned long sa_keying_tries; + + /* RFC 3706 DPD */ + time_t dpd_delay; + time_t dpd_timeout; + dpd_action_t dpd_action; + + char *log_file_name; /* name of log file */ + FILE *log_file; /* possibly open FILE */ + CIRCLEQ_ENTRY(connection) log_link; /* linked list of open conns */ + bool log_file_err; /* only bitch once */ + + struct spd_route spd; + + /* internal fields: */ + + unsigned long instance_serial; + policy_prio_t prio; + bool instance_initiation_ok; /* this is an instance of a policy that mandates initiate */ + enum connection_kind kind; + const struct iface *interface; /* filled in iff oriented */ + + so_serial_t /* state object serial number */ + newest_isakmp_sa, + newest_ipsec_sa; + + +#ifdef DEBUG + lset_t extra_debugging; +#endif + + /* note: if the client is the gateway, the following must be equal */ + sa_family_t addr_family; /* between gateways */ + sa_family_t tunnel_addr_family; /* between clients */ + + struct connection *policy_next; /* if multiple policies, + next one to apply */ + + struct gw_info *gw_info; + struct alg_info_esp *alg_info_esp; + struct alg_info_ike *alg_info_ike; + + struct host_pair *host_pair; + struct connection *hp_next; /* host pair list link */ + + struct connection *ac_next; /* all connections list link */ + + generalName_t *requested_ca; /* collected certificate requests */ + bool got_certrequest; +}; + +#define oriented(c) ((c).interface != NULL) +extern bool orient(struct connection *c); + +extern bool same_peer_ids(const struct connection *c + , const struct connection *d, const struct id *his_id); + +/* Format the topology of a connection end, leaving out defaults. + * Largest left end looks like: client === host : port [ host_id ] --- hop + * Note: if that==NULL, skip nexthop + */ +#define END_BUF (SUBNETTOT_BUF + ADDRTOT_BUF + IDTOA_BUF + ADDRTOT_BUF + 10) +extern size_t format_end(char *buf, size_t buf_len + , const struct end *this, const struct end *that + , bool is_left, lset_t policy); + +extern void add_connection(const whack_message_t *wm); +extern void initiate_connection(const char *name, int whackfd); +extern void initiate_opportunistic(const ip_address *our_client + , const ip_address *peer_client, int transport_proto, bool held, int whackfd); +extern void terminate_connection(const char *nm); +extern void release_connection(struct connection *c, bool relations); +extern void delete_connection(struct connection *c, bool relations); +extern void delete_connections_by_name(const char *name, bool strict); +extern void delete_every_connection(void); +extern char *add_group_instance(struct connection *group, const ip_subnet *target); +extern void remove_group_instance(const struct connection *group, const char *name); +extern void release_dead_interfaces(void); +extern void check_orientations(void); +extern struct connection *route_owner(struct connection *c + , struct spd_route **srp + , struct connection **erop + , struct spd_route **esrp); +extern struct connection *shunt_owner(const ip_subnet *ours + , const ip_subnet *his); + +extern bool uniqueIDs; /* --uniqueids? */ +extern void ISAKMP_SA_established(struct connection *c, so_serial_t serial); + +#define his_id_was_instantiated(c) ((c)->kind == CK_INSTANCE \ + && (id_is_ipaddr(&(c)->spd.that.id)? \ + sameaddr(&(c)->spd.that.id.ip_addr, &(c)->spd.that.host_addr) : TRUE)) + +struct state; /* forward declaration of tag (defined in state.h) */ +extern struct connection + *con_by_name(const char *nm, bool strict), + *find_host_connection(const ip_address *me, u_int16_t my_port + , const ip_address *him, u_int16_t his_port, lset_t policy), + *refine_host_connection(const struct state *st, const struct id *id + , chunk_t peer_ca), + *find_client_connection(struct connection *c + , const ip_subnet *our_net + , const ip_subnet *peer_net + , const u_int8_t our_protocol + , const u_int16_t out_port + , const u_int8_t peer_protocol + , const u_int16_t peer_port), + *find_connection_by_reqid(uint32_t reqid); + +extern struct connection * +find_connection_for_clients(struct spd_route **srp + , const ip_address *our_client + , const ip_address *peer_client + , int transport_proto); + +extern chunk_t get_peer_ca_and_groups(struct connection *c + , const ietfAttrList_t **peer_list); + +/* instantiating routines + * Note: connection_discard() is in state.h because all its work + * is looking through state objects. + */ +struct gw_info; /* forward declaration of tag (defined in dnskey.h) */ +struct alg_info; /* forward declaration of tag (defined in alg_info.h) */ +extern struct connection *rw_instantiate(struct connection *c + , const ip_address *him + , u_int16_t his_port + , const ip_subnet *his_net + , const struct id *his_id); + +extern struct connection *oppo_instantiate(struct connection *c + , const ip_address *him + , const struct id *his_id + , struct gw_info *gw + , const ip_address *our_client + , const ip_address *peer_client); + +extern struct connection + *build_outgoing_opportunistic_connection(struct gw_info *gw + , const ip_address *our_client + , const ip_address *peer_client); + +/* worst case: "[" serial "] " myclient "=== ..." peer "===" hisclient '\0' */ +#define CONN_INST_BUF \ + (2 + 10 + 1 + SUBNETTOT_BUF + 7 + ADDRTOT_BUF + 3 + SUBNETTOT_BUF + 1) + +extern void fmt_conn_instance(const struct connection *c + , char buf[CONN_INST_BUF]); + +/* operations on "pending", the structure representing Quick Mode + * negotiations delayed until a Keying Channel has been negotiated. + */ + +struct pending; /* forward declaration (opaque outside connections.c) */ + +extern void add_pending(int whack_sock + , struct state *isakmp_sa + , struct connection *c + , lset_t policy + , unsigned long try + , so_serial_t replacing); + +extern void release_pending_whacks(struct state *st, err_t story); +extern void unpend(struct state *st); +extern void update_pending(struct state *os, struct state *ns); +extern void flush_pending_by_state(struct state *st); +extern void show_pending_phase2(const struct host_pair *hp, const struct state *st); + +extern void connection_discard(struct connection *c); + +/* A template connection's eroute can be eclipsed by + * either a %hold or an eroute for an instance iff + * the template is a /32 -> /32. This requires some special casing. + */ +#define eclipsable(sr) (subnetishost(&(sr)->this.client) && subnetishost(&(sr)->that.client)) +extern long eclipse_count; +extern struct connection *eclipsed(struct connection *c, struct spd_route **); + + +/* print connection status */ + +extern void show_connections_status(bool all, const char *name); +extern int connection_compare(const struct connection *ca + , const struct connection *cb); +extern void update_host_pair(const char *why, struct connection *c + , const ip_address *myaddr, u_int16_t myport + , const ip_address *hisaddr, u_int16_t hisport); + +#endif /* _CONNECTIONS_H */ diff --git a/src/pluto/constants.c b/src/pluto/constants.c new file mode 100644 index 000000000..e7d7216ee --- /dev/null +++ b/src/pluto/constants.c @@ -0,0 +1,1353 @@ +/* tables of names for values defined in constants.h + * Copyright (C) 1998-2002 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: constants.c,v 1.21 2006/03/27 07:38:59 as Exp $ + */ + +/* + * Note that the array sizes are all specified; this is to enable range + * checking by code that only includes constants.h. + */ + +#include <stddef.h> +#include <string.h> +#include <stdio.h> +#include <netinet/in.h> + +#include <freeswan.h> +#include <ipsec_policy.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "packet.h" + +/* string naming compile-time options that have interop implications */ + +const char compile_time_interop_options[] = "" +#ifdef THREADS + " THREADS" +#endif +#ifdef LIBCURL + " LIBCURL" +#endif +#ifdef LIBLDAP + " LIBLDAP" +#endif +#ifdef SMARTCARD + " SMARTCARD" +#endif +#ifdef VENDORID + " VENDORID" +#endif +#ifdef CISCO_QUIRKS + " CISCO_QUIRKS" +#endif +#ifdef USE_KEYRR + " KEYRR" +#endif + ; + +/* version */ + +static const char *const version_name[] = { + "ISAKMP Version 1.0", +}; + +enum_names version_names = + { ISAKMP_MAJOR_VERSION<<ISA_MAJ_SHIFT | ISAKMP_MINOR_VERSION, + ISAKMP_MAJOR_VERSION<<ISA_MAJ_SHIFT | ISAKMP_MINOR_VERSION, + version_name, NULL }; + +/* RFC 2459 CRL reason codes */ + +static const char *const crl_reason_name[] = { + "unspecified", + "key compromise", + "ca compromise", + "affiliation changed", + "superseded", + "cessation of operation", + "certificate hold", + "reason #7", + "remove from crl" + }; + +enum_names crl_reason_names = + { REASON_UNSPECIFIED, REASON_REMOVE_FROM_CRL, crl_reason_name, NULL}; + +/* RFC 3706 Dead Peer Detection */ + +static const char *const dpd_action_name[] = { + "none", + "clear", + "hold", + "restart" + }; + +enum_names dpd_action_names = + { DPD_ACTION_NONE, DPD_ACTION_RESTART, dpd_action_name, NULL}; + +/* Timer events */ + +static const char *const timer_event_name[] = { + "EVENT_NULL", + "EVENT_REINIT_SECRET", + "EVENT_SHUNT_SCAN", + "EVENT_SO_DISCARD", + "EVENT_RETRANSMIT", + "EVENT_SA_REPLACE", + "EVENT_SA_REPLACE_IF_USED", + "EVENT_SA_EXPIRE", + "EVENT_NAT_T_KEEPALIVE", + "EVENT_DPD", + "EVENT_DPD_TIMEOUT", + "EVENT_LOG_DAILY" + }; + +enum_names timer_event_names = + { EVENT_NULL, EVENT_LOG_DAILY, timer_event_name, NULL }; + +/* Domain of Interpretation */ + +static const char *const doi_name[] = { + "ISAKMP_DOI_ISAKMP", + "ISAKMP_DOI_IPSEC", +}; + +enum_names doi_names = { ISAKMP_DOI_ISAKMP, ISAKMP_DOI_IPSEC, doi_name, NULL }; + +/* debugging settings: a set of selections for reporting + * These would be more naturally situated in log.h, + * but they are shared with whack. + * It turns out that "debug-" is clutter in all contexts this is used, + * so we leave it off. + */ +#ifdef DEBUG +const char *const debug_bit_names[] = { + "raw", + "crypt", + "parsing", + "emitting", + "control", + "lifecycle", + "klips", + "dns", + "natt", + "oppo", + "controlmore", + + "private", + + "impair-delay-adns-key-answer", + "impair-delay-adns-txt-answer", + "impair-bust-mi2", + "impair-bust-mr2", + + NULL + }; +#endif + +/* State of exchanges */ + +static const char *const state_name[] = { + "STATE_MAIN_R0", + "STATE_MAIN_I1", + "STATE_MAIN_R1", + "STATE_MAIN_I2", + "STATE_MAIN_R2", + "STATE_MAIN_I3", + "STATE_MAIN_R3", + "STATE_MAIN_I4", + + "STATE_QUICK_R0", + "STATE_QUICK_I1", + "STATE_QUICK_R1", + "STATE_QUICK_I2", + "STATE_QUICK_R2", + + "STATE_INFO", + "STATE_INFO_PROTECTED", + + "STATE_XAUTH_I0", + "STATE_XAUTH_R1", + "STATE_XAUTH_I1", + "STATE_XAUTH_R2", + "STATE_XAUTH_I2", + "STATE_XAUTH_R3", + + "STATE_MODE_CFG_R0", + "STATE_MODE_CFG_I1", + "STATE_MODE_CFG_R1", + "STATE_MODE_CFG_I2", + + "STATE_MODE_CFG_I0", + "STATE_MODE_CFG_R3", + "STATE_MODE_CFG_I3", + "STATE_MODE_CFG_R4", + + "STATE_IKE_ROOF" + }; + +enum_names state_names = + { STATE_MAIN_R0, STATE_IKE_ROOF-1, state_name, NULL }; + +/* story for state */ + +const char *const state_story[] = { + "expecting MI1", /* STATE_MAIN_R0 */ + "sent MI1, expecting MR1", /* STATE_MAIN_I1 */ + "sent MR1, expecting MI2", /* STATE_MAIN_R1 */ + "sent MI2, expecting MR2", /* STATE_MAIN_I2 */ + "sent MR2, expecting MI3", /* STATE_MAIN_R2 */ + "sent MI3, expecting MR3", /* STATE_MAIN_I3 */ + "sent MR3, ISAKMP SA established", /* STATE_MAIN_R3 */ + "ISAKMP SA established", /* STATE_MAIN_I4 */ + + "expecting QI1", /* STATE_QUICK_R0 */ + "sent QI1, expecting QR1", /* STATE_QUICK_I1 */ + "sent QR1, inbound IPsec SA installed, expecting QI2", /* STATE_QUICK_R1 */ + "sent QI2, IPsec SA established", /* STATE_QUICK_I2 */ + "IPsec SA established", /* STATE_QUICK_R2 */ + + "got Informational Message in clear", /* STATE_INFO */ + "got encrypted Informational Message", /* STATE_INFO_PROTECTED */ + + "expecting XAUTH request", /* STATE_XAUTH_I0 */ + "sent XAUTH request, expecting reply", /* STATE_XAUTH_R1 */ + "sent XAUTH reply, expecting status", /* STATE_XAUTH_I1 */ + "sent XAUTH status, expecting ack", /* STATE_XAUTH_R2 */ + "sent XAUTH ack, established", /* STATE_XAUTH_I2 */ + "received XAUTH ack, established", /* STATE_XAUTH_R3 */ + + "expecting ModeCfg request", /* STATE_MODE_CFG_R0 */ + "sent ModeCfg request, expecting reply", /* STATE_MODE_CFG_I1 */ + "sent ModeCfg reply, established", /* STATE_MODE_CFG_R1 */ + "received ModeCfg reply, established", /* STATE_MODE_CFG_I2 */ + + "expecting ModeCfg set", /* STATE_MODE_CFG_I0 */ + "sent ModeCfg set, expecting ack", /* STATE_MODE_CFG_R3 */ + "sent ModeCfg ack, established", /* STATE_MODE_CFG_I3 */ + "received ModeCfg ack, established", /* STATE_MODE_CFG_R4 */ + }; + +/* kind of struct connection */ + +static const char *const connection_kind_name[] = { + "CK_GROUP", /* policy group: instantiates to template */ + "CK_TEMPLATE", /* abstract connection, with wildcard */ + "CK_PERMANENT", /* normal connection */ + "CK_INSTANCE", /* instance of template, created for a particular attempt */ + "CK_GOING_AWAY" /* instance being deleted -- don't delete again */ +}; + +enum_names connection_kind_names = + { CK_GROUP, CK_GOING_AWAY, connection_kind_name, NULL }; + +/* routing status names */ + +static const char *const routing_story_strings[] = { + "unrouted", /* RT_UNROUTED: unrouted */ + "unrouted HOLD", /* RT_UNROUTED_HOLD: unrouted, but HOLD shunt installed */ + "eroute eclipsed", /* RT_ROUTED_ECLIPSED: RT_ROUTED_PROSPECTIVE except bare HOLD or instance has eroute */ + "prospective erouted", /* RT_ROUTED_PROSPECTIVE: routed, and prospective shunt installed */ + "erouted HOLD", /* RT_ROUTED_HOLD: routed, and HOLD shunt installed */ + "fail erouted", /* RT_ROUTED_FAILURE: routed, and failure-context shunt eroute installed */ + "erouted", /* RT_ROUTED_TUNNEL: routed, and erouted to an IPSEC SA group */ + "keyed, unrouted", /* RT_UNROUTED_KEYED: was routed+keyed, but it got turned into an outer policy */ + }; + +enum_names routing_story = + { RT_UNROUTED, RT_ROUTED_TUNNEL, routing_story_strings, NULL}; + +/* Payload types (RFC 2408 "ISAKMP" section 3.1) */ + +const char *const payload_name[] = { + "ISAKMP_NEXT_NONE", + "ISAKMP_NEXT_SA", + "ISAKMP_NEXT_P", + "ISAKMP_NEXT_T", + "ISAKMP_NEXT_KE", + "ISAKMP_NEXT_ID", + "ISAKMP_NEXT_CERT", + "ISAKMP_NEXT_CR", + "ISAKMP_NEXT_HASH", + "ISAKMP_NEXT_SIG", + "ISAKMP_NEXT_NONCE", + "ISAKMP_NEXT_N", + "ISAKMP_NEXT_D", + "ISAKMP_NEXT_VID", + "ISAKMP_NEXT_MODECFG", + "ISAKMP_NEXT_15", + "ISAKMP_NEXT_16", + "ISAKMP_NEXT_17", + "ISAKMP_NEXT_18", + "ISAKMP_NEXT_19", + "ISAKMP_NEXT_NAT-D", + "ISAKMP_NEXT_NAT-OA", + NULL + }; + +const char *const payload_name_nat_d[] = { "ISAKMP_NEXT_NAT-D", + "ISAKMP_NEXT_NAT-OA", NULL }; + +static enum_names payload_names_nat_d = + { ISAKMP_NEXT_NATD_DRAFTS, ISAKMP_NEXT_NATOA_DRAFTS, payload_name_nat_d, NULL }; + +enum_names payload_names = + { ISAKMP_NEXT_NONE, ISAKMP_NEXT_NATOA_RFC, payload_name, &payload_names_nat_d }; + +/* Exchange types (note: two discontinuous ranges) */ + +static const char *const exchange_name[] = { + "ISAKMP_XCHG_NONE", + "ISAKMP_XCHG_BASE", + "ISAKMP_XCHG_IDPROT", + "ISAKMP_XCHG_AO", + "ISAKMP_XCHG_AGGR", + "ISAKMP_XCHG_INFO", + "ISAKMP_XCHG_MODE_CFG", + }; + +static const char *const exchange_name2[] = { + "ISAKMP_XCHG_QUICK", + "ISAKMP_XCHG_NGRP", + "ISAKMP_XCHG_ACK_INFO", + }; + +static enum_names exchange_desc2 = + { ISAKMP_XCHG_QUICK, ISAKMP_XCHG_ACK_INFO, exchange_name2, NULL }; + +enum_names exchange_names = + { ISAKMP_XCHG_NONE, ISAKMP_XCHG_MODE_CFG, exchange_name, &exchange_desc2 }; + +/* Flag BITS */ +const char *const flag_bit_names[] = { + "ISAKMP_FLAG_ENCRYPTION", + "ISAKMP_FLAG_COMMIT", + NULL + }; + +/* Situation BITS definition for IPsec DOI */ + +const char *const sit_bit_names[] = { + "SIT_IDENTITY_ONLY", + "SIT_SECRECY", + "SIT_INTEGRITY", + NULL + }; + +/* Protocol IDs (RFC 2407 "IPsec DOI" section 4.4.1) */ + +static const char *const protocol_name[] = { + "PROTO_ISAKMP", + "PROTO_IPSEC_AH", + "PROTO_IPSEC_ESP", + "PROTO_IPCOMP", + }; + +enum_names protocol_names = + { PROTO_ISAKMP, PROTO_IPCOMP, protocol_name, NULL }; + +/* IPsec ISAKMP transform values */ + +static const char *const isakmp_transform_name[] = { + "KEY_IKE", + }; + +enum_names isakmp_transformid_names = + { KEY_IKE, KEY_IKE, isakmp_transform_name, NULL }; + +/* IPsec AH transform values */ + +static const char *const ah_transform_name[] = { + "AH_MD5", + "AH_SHA", + "AH_DES", + "AH_SHA2_256", + "AH_SHA2_384", + "AH_SHA2_512", + "AH_RIPEMD" + }; + +enum_names ah_transformid_names = + { AH_MD5, AH_RIPEMD, ah_transform_name, NULL }; + +/* IPsec ESP transform values */ + +static const char *const esp_transform_name[] = { + "ESP_DES_IV64", + "ESP_DES", + "ESP_3DES", + "ESP_RC5", + "ESP_IDEA", + "ESP_CAST", + "ESP_BLOWFISH", + "ESP_3IDEA", + "ESP_DES_IV32", + "ESP_RC4", + "ESP_NULL", + "ESP_AES", + "ESP_AES-CTR", + "ESP_AES-CCM_8", + "ESP_AES-CCM_12", + "ESP_AES-CCM_16" + }; + +/* + * ipsec drafts suggest "high" ESP ids values for testing, + * assign generic ESP_ID<num> if not officially defined + */ +static const char *const esp_transform_name_high[] = { + "ESP_SERPENT", + "ESP_TWOFISH" + }; + +enum_names esp_transformid_names_high = + { ESP_SERPENT, ESP_TWOFISH, esp_transform_name_high, NULL }; + +enum_names esp_transformid_names = + { ESP_DES_IV64, ESP_AES_CCM_16, esp_transform_name, &esp_transformid_names_high }; + +/* IPCOMP transform values */ + +static const char *const ipcomp_transform_name[] = { + "IPCOMP_OUI", + "IPCOMP_DEFLAT", + "IPCOMP_LZS", + "IPCOMP_LZJH", + }; + +enum_names ipcomp_transformid_names = + { IPCOMP_OUI, IPCOMP_LZJH, ipcomp_transform_name, NULL }; + +/* Identification type values */ + +static const char *const ident_name[] = { + "ID_IPV4_ADDR", + "ID_FQDN", + "ID_USER_FQDN", + "ID_IPV4_ADDR_SUBNET", + "ID_IPV6_ADDR", + "ID_IPV6_ADDR_SUBNET", + "ID_IPV4_ADDR_RANGE", + "ID_IPV6_ADDR_RANGE", + "ID_DER_ASN1_DN", + "ID_DER_ASN1_GN", + "ID_KEY_ID", + }; + +enum_names ident_names = + { ID_IPV4_ADDR, ID_KEY_ID, ident_name, NULL }; + +/* Certificate type values */ + +static const char *const cert_type_name[] = { + "CERT_NONE", + "CERT_PKCS7_WRAPPED_X509", + "CERT_PGP", + "CERT_DNS_SIGNED_KEY", + "CERT_X509_SIGNATURE", + "CERT_X509_KEY_EXCHANGE", + "CERT_KERBEROS_TOKENS", + "CERT_CRL", + "CERT_ARL", + "CERT_SPKI", + "CERT_X509_ATTRIBUTE", + }; + +enum_names cert_type_names = + { CERT_NONE, CERT_X509_ATTRIBUTE, cert_type_name, NULL }; + +/* Certificate policy names */ + +static const char *const cert_policy_name[] = { + "ALWAYS_SEND", + "SEND_IF_ASKED", + "NEVER_SEND", + }; + +enum_names cert_policy_names = + { CERT_ALWAYS_SEND, CERT_NEVER_SEND, cert_policy_name, NULL }; + +/* Goal BITs for establishing an SA + * Note: we drop the POLICY_ prefix so that logs are more concise. + */ + +const char *const sa_policy_bit_names[] = { + "PSK", + "RSASIG", + "ENCRYPT", + "AUTHENTICATE", + "COMPRESS", + "TUNNEL", + "PFS", + "DISABLEARRIVALCHECK", + "SHUNT0", + "SHUNT1", + "FAILSHUNT0", + "FAILSHUNT1", + "DONTREKEY", + "OPPORTUNISTIC", + "GROUP", + "GROUTED", + "UP", + "MODECFGPUSH", + "XAUTHPSK", + "XAUTHRSASIG", + "XAUTHSERVER", + "DONTREAUTH", + NULL + }; + +const char *const policy_shunt_names[4] = { + "TRAP", + "PASS", + "DROP", + "REJECT", + }; + +const char *const policy_fail_names[4] = { + "NONE", + "PASS", + "DROP", + "REJECT", + }; + +/* Oakley transform attributes + * oakley_attr_bit_names does double duty: it is used for enum names + * and bit names. + */ + +const char *const oakley_attr_bit_names[] = { + "OAKLEY_ENCRYPTION_ALGORITHM", + "OAKLEY_HASH_ALGORITHM", + "OAKLEY_AUTHENTICATION_METHOD", + "OAKLEY_GROUP_DESCRIPTION", + "OAKLEY_GROUP_TYPE", + "OAKLEY_GROUP_PRIME", + "OAKLEY_GROUP_GENERATOR_ONE", + "OAKLEY_GROUP_GENERATOR_TWO", + "OAKLEY_GROUP_CURVE_A", + "OAKLEY_GROUP_CURVE_B", + "OAKLEY_LIFE_TYPE", + "OAKLEY_LIFE_DURATION", + "OAKLEY_PRF", + "OAKLEY_KEY_LENGTH", + "OAKLEY_FIELD_SIZE", + "OAKLEY_GROUP_ORDER", + "OAKLEY_BLOCK_SIZE", + NULL + }; + +static const char *const oakley_var_attr_name[] = { + "OAKLEY_GROUP_PRIME (variable length)", + "OAKLEY_GROUP_GENERATOR_ONE (variable length)", + "OAKLEY_GROUP_GENERATOR_TWO (variable length)", + "OAKLEY_GROUP_CURVE_A (variable length)", + "OAKLEY_GROUP_CURVE_B (variable length)", + NULL, + "OAKLEY_LIFE_DURATION (variable length)", + NULL, + NULL, + NULL, + "OAKLEY_GROUP_ORDER (variable length)", + }; + +static enum_names oakley_attr_desc_tv = { + OAKLEY_ENCRYPTION_ALGORITHM + ISAKMP_ATTR_AF_TV, + OAKLEY_GROUP_ORDER + ISAKMP_ATTR_AF_TV, oakley_attr_bit_names, NULL }; + +enum_names oakley_attr_names = { + OAKLEY_GROUP_PRIME, OAKLEY_GROUP_ORDER, + oakley_var_attr_name, &oakley_attr_desc_tv }; + +/* for each Oakley attribute, which enum_names describes its values? */ +enum_names *oakley_attr_val_descs[] = { + NULL, /* (none) */ + &oakley_enc_names, /* OAKLEY_ENCRYPTION_ALGORITHM */ + &oakley_hash_names, /* OAKLEY_HASH_ALGORITHM */ + &oakley_auth_names, /* OAKLEY_AUTHENTICATION_METHOD */ + &oakley_group_names, /* OAKLEY_GROUP_DESCRIPTION */ + &oakley_group_type_names,/* OAKLEY_GROUP_TYPE */ + NULL, /* OAKLEY_GROUP_PRIME */ + NULL, /* OAKLEY_GROUP_GENERATOR_ONE */ + NULL, /* OAKLEY_GROUP_GENERATOR_TWO */ + NULL, /* OAKLEY_GROUP_CURVE_A */ + NULL, /* OAKLEY_GROUP_CURVE_B */ + &oakley_lifetime_names, /* OAKLEY_LIFE_TYPE */ + NULL, /* OAKLEY_LIFE_DURATION */ + &oakley_prf_names, /* OAKLEY_PRF */ + NULL, /* OAKLEY_KEY_LENGTH */ + NULL, /* OAKLEY_FIELD_SIZE */ + NULL, /* OAKLEY_GROUP_ORDER */ + }; + +/* IPsec DOI attributes (RFC 2407 "IPsec DOI" section 4.5) */ + +static const char *const ipsec_attr_name[] = { + "SA_LIFE_TYPE", + "SA_LIFE_DURATION", + "GROUP_DESCRIPTION", + "ENCAPSULATION_MODE", + "AUTH_ALGORITHM", + "KEY_LENGTH", + "KEY_ROUNDS", + "COMPRESS_DICT_SIZE", + "COMPRESS_PRIVATE_ALG", + }; + +static const char *const ipsec_var_attr_name[] = { + "SA_LIFE_DURATION (variable length)", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + "COMPRESS_PRIVATE_ALG (variable length)", + }; + +static enum_names ipsec_attr_desc_tv = { + SA_LIFE_TYPE + ISAKMP_ATTR_AF_TV, + COMPRESS_PRIVATE_ALG + ISAKMP_ATTR_AF_TV, + ipsec_attr_name, NULL }; + +enum_names ipsec_attr_names = { + SA_LIFE_DURATION, COMPRESS_PRIVATE_ALG, + ipsec_var_attr_name, &ipsec_attr_desc_tv }; + +/* for each IPsec attribute, which enum_names describes its values? */ +enum_names *ipsec_attr_val_descs[] = { + NULL, /* (none) */ + &sa_lifetime_names, /* SA_LIFE_TYPE */ + NULL, /* SA_LIFE_DURATION */ + &oakley_group_names, /* GROUP_DESCRIPTION */ + &enc_mode_names, /* ENCAPSULATION_MODE */ + &auth_alg_names, /* AUTH_ALGORITHM */ + NULL, /* KEY_LENGTH */ + NULL, /* KEY_ROUNDS */ + NULL, /* COMPRESS_DICT_SIZE */ + NULL, /* COMPRESS_PRIVATE_ALG */ + }; + +/* SA Lifetime Type attribute */ + +static const char *const sa_lifetime_name[] = { + "SA_LIFE_TYPE_SECONDS", + "SA_LIFE_TYPE_KBYTES", + }; + +enum_names sa_lifetime_names = + { SA_LIFE_TYPE_SECONDS, SA_LIFE_TYPE_KBYTES, sa_lifetime_name, NULL }; + +/* Encapsulation Mode attribute */ + +static const char *const enc_mode_name[] = { + "ENCAPSULATION_MODE_TUNNEL", + "ENCAPSULATION_MODE_TRANSPORT", + "ENCAPSULATION_MODE_UDP_TUNNEL", + "ENCAPSULATION_MODE_UDP_TRANSPORT", + }; + +static const char *const enc_udp_mode_name[] = { + "ENCAPSULATION_MODE_UDP_TUNNEL", + "ENCAPSULATION_MODE_UDP_TRANSPORT", + }; + +static enum_names enc_udp_mode_names = + { ENCAPSULATION_MODE_UDP_TUNNEL_DRAFTS, ENCAPSULATION_MODE_UDP_TRANSPORT_DRAFTS, enc_udp_mode_name, NULL }; + +enum_names enc_mode_names = + { ENCAPSULATION_MODE_TUNNEL, ENCAPSULATION_MODE_UDP_TRANSPORT_RFC, enc_mode_name, &enc_udp_mode_names }; + +/* Auth Algorithm attribute */ + +static const char *const auth_alg_name[] = { + "AUTH_ALGORITHM_HMAC_MD5", + "AUTH_ALGORITHM_HMAC_SHA1", + "AUTH_ALGORITHM_DES_MAC", + "AUTH_ALGORITHM_KPDK", + "AUTH_ALGORITHM_HMAC_SHA2_256", + "AUTH_ALGORITHM_HMAC_SHA2_384", + "AUTH_ALGORITHM_HMAC_SHA2_512", + "AUTH_ALGORITHM_HMAC_RIPEMD", + }; + +static const char *const extended_auth_alg_name[] = { + "AUTH_ALGORITHM_NULL" + }; + +enum_names extended_auth_alg_names = + { AUTH_ALGORITHM_NULL, AUTH_ALGORITHM_NULL, extended_auth_alg_name, NULL }; + +enum_names auth_alg_names = + { AUTH_ALGORITHM_HMAC_MD5, AUTH_ALGORITHM_HMAC_RIPEMD, auth_alg_name + , &extended_auth_alg_names }; + +/* From draft-beaulieu-ike-xauth */ +static const char *const xauth_type_name[] = { + "Generic", + "RADIUS-CHAP", + "OTP", + "S/KEY", +}; + +enum_names xauth_type_names = + { XAUTH_TYPE_GENERIC, XAUTH_TYPE_SKEY, xauth_type_name, NULL}; + +/* From draft-beaulieu-ike-xauth */ +static const char *const xauth_attr_tv_name[] = { + "XAUTH_TYPE", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + "XAUTH_STATUS", + }; + +enum_names xauth_attr_tv_names = { + XAUTH_TYPE + ISAKMP_ATTR_AF_TV, + XAUTH_STATUS + ISAKMP_ATTR_AF_TV, xauth_attr_tv_name, NULL }; + +static const char *const unity_attr_name[] = { + "UNITY_BANNER", + "UNITY_SAVE_PASSWD", + "UNITY_DEF_DOMAIN", + "UNITY_SPLITDNS_NAME", + "UNITY_SPLIT_INCLUDE", + "UNITY_NATT_PORT", + "UNITY_LOCAL_LAN", + "UNITY_PFS", + "UNITY_FW_TYPE", + "UNITY_BACKUP_SERVERS", + "UNITY_DDNS_HOSTNAME", +}; + +enum_names unity_attr_names = + { UNITY_BANNER , UNITY_DDNS_HOSTNAME, unity_attr_name , &xauth_attr_tv_names }; + + +static const char *const xauth_attr_name[] = { + "XAUTH_USER_NAME", + "XAUTH_USER_PASSWORD", + "XAUTH_PASSCODE", + "XAUTH_MESSAGE", + "XAUTH_CHALLENGE", + "XAUTH_DOMAIN", + "XAUTH_STATUS (wrong TLV syntax, should be TV)", + "XAUTH_NEXT_PIN", + "XAUTH_ANSWER", + }; + +enum_names xauth_attr_names = + { XAUTH_USER_NAME , XAUTH_ANSWER, xauth_attr_name , &unity_attr_names }; + +static const char *const modecfg_attr_name[] = { + "INTERNAL_IP4_ADDRESS", + "INTERNAL_IP4_NETMASK", + "INTERNAL_IP4_DNS", + "INTERNAL_IP4_NBNS", + "INTERNAL_ADDRESS_EXPIRY", + "INTERNAL_IP4_DHCP", + "APPLICATION_VERSION", + "INTERNAL_IP6_ADDRESS", + "INTERNAL_IP6_NETMASK", + "INTERNAL_IP6_DNS", + "INTERNAL_IP6_NBNS", + "INTERNAL_IP6_DHCP", + "INTERNAL_IP4_SUBNET", + "SUPPORTED_ATTRIBUTES", + "INTERNAL_IP6_SUBNET", + }; + +enum_names modecfg_attr_names = + { INTERNAL_IP4_ADDRESS, INTERNAL_IP6_SUBNET, modecfg_attr_name , &xauth_attr_names }; + +/* Oakley Lifetime Type attribute */ + +static const char *const oakley_lifetime_name[] = { + "OAKLEY_LIFE_SECONDS", + "OAKLEY_LIFE_KILOBYTES", + }; + +enum_names oakley_lifetime_names = + { OAKLEY_LIFE_SECONDS, OAKLEY_LIFE_KILOBYTES, oakley_lifetime_name, NULL }; + +/* Oakley PRF attribute (none defined) */ + +enum_names oakley_prf_names = + { 1, 0, NULL, NULL }; + +/* Oakley Encryption Algorithm attribute */ + +static const char *const oakley_enc_name[] = { + "OAKLEY_DES_CBC", + "OAKLEY_IDEA_CBC", + "OAKLEY_BLOWFISH_CBC", + "OAKLEY_RC5_R16_B64_CBC", + "OAKLEY_3DES_CBC", + "OAKLEY_CAST_CBC", + "OAKLEY_AES_CBC", + }; + +#ifdef NO_EXTRA_IKE +enum_names oakley_enc_names = + { OAKLEY_DES_CBC, OAKLEY_AES_CBC, oakley_enc_name, NULL }; +#else +static const char *const oakley_enc_name_draft_aes_cbc_02[] = { + "OAKLEY_MARS_CBC" /* 65001 */, + "OAKLEY_RC6_CBC" /* 65002 */, + "OAKLEY_ID_65003" /* 65003 */, + "OAKLEY_SERPENT_CBC" /* 65004 */, + "OAKLEY_TWOFISH_CBC" /* 65005 */, +}; +static const char *const oakley_enc_name_ssh[] = { + "OAKLEY_TWOFISH_CBC_SSH", +}; +enum_names oakley_enc_names_ssh = + { OAKLEY_TWOFISH_CBC_SSH, OAKLEY_TWOFISH_CBC_SSH, oakley_enc_name_ssh + , NULL }; + +enum_names oakley_enc_names_draft_aes_cbc_02 = + { OAKLEY_MARS_CBC, OAKLEY_TWOFISH_CBC, oakley_enc_name_draft_aes_cbc_02 + , &oakley_enc_names_ssh }; + +enum_names oakley_enc_names = + { OAKLEY_DES_CBC, OAKLEY_AES_CBC, oakley_enc_name + , &oakley_enc_names_draft_aes_cbc_02 }; +#endif + +/* Oakley Hash Algorithm attribute */ + +static const char *const oakley_hash_name[] = { + "OAKLEY_MD5", + "OAKLEY_SHA", + "OAKLEY_TIGER", + "OAKLEY_SHA2_256", + "OAKLEY_SHA2_384", + "OAKLEY_SHA2_512", + }; + +enum_names oakley_hash_names = + { OAKLEY_MD5, OAKLEY_SHA2_512, oakley_hash_name, NULL }; + +/* Oakley Authentication Method attribute */ + +static const char *const oakley_auth_name1[] = { + "OAKLEY_PRESHARED_KEY", + "OAKLEY_DSS_SIG", + "OAKLEY_RSA_SIG", + "OAKLEY_RSA_ENC", + "OAKLEY_RSA_ENC_REV", + "OAKLEY_ELGAMAL_ENC", + "OAKLEY_ELGAMAL_ENC_REV", + }; + +static const char *const oakley_auth_name2[] = { + "HybridInitRSA", + "HybridRespRSA", + "HybridInitDSS", + "HybridRespDSS", + }; + +static const char *const oakley_auth_name3[] = { + "XAUTHInitPreShared", + "XAUTHRespPreShared", + "XAUTHInitDSS", + "XAUTHRespDSS", + "XAUTHInitRSA", + "XAUTHRespRSA", + "XAUTHInitRSAEncryption", + "XAUTHRespRSAEncryption", + "XAUTHInitRSARevisedEncryption", + "XAUTHRespRSARevisedEncryption", + }; + +static enum_names oakley_auth_names1 = + { OAKLEY_PRESHARED_KEY, OAKLEY_ELGAMAL_ENC_REV + , oakley_auth_name1, NULL }; + +static enum_names oakley_auth_names2 = + { HybridInitRSA, HybridRespDSS + , oakley_auth_name2, &oakley_auth_names1 }; + +enum_names oakley_auth_names = + { XAUTHInitPreShared, XAUTHRespRSARevisedEncryption + , oakley_auth_name3, &oakley_auth_names2 }; + +/* Oakley Group Description attribute */ + +static const char *const oakley_group_name[] = { + "OAKLEY_GROUP_MODP768", + "OAKLEY_GROUP_MODP1024", + "OAKLEY_GROUP_GP155", + "OAKLEY_GROUP_GP185", + "OAKLEY_GROUP_MODP1536", + }; + +static const char *const oakley_group_name_rfc3526[] = { + "OAKLEY_GROUP_MODP2048", + "OAKLEY_GROUP_MODP3072", + "OAKLEY_GROUP_MODP4096", + "OAKLEY_GROUP_MODP6144", + "OAKLEY_GROUP_MODP8192" +}; +enum_names oakley_group_names_rfc3526 = + { OAKLEY_GROUP_MODP2048, OAKLEY_GROUP_MODP8192, + oakley_group_name_rfc3526, NULL }; + +enum_names oakley_group_names = + { OAKLEY_GROUP_MODP768, OAKLEY_GROUP_MODP1536, + oakley_group_name, &oakley_group_names_rfc3526 }; + +/* Oakley Group Type attribute */ + +static const char *const oakley_group_type_name[] = { + "OAKLEY_GROUP_TYPE_MODP", + "OAKLEY_GROUP_TYPE_ECP", + "OAKLEY_GROUP_TYPE_EC2N", + }; + +enum_names oakley_group_type_names = + { OAKLEY_GROUP_TYPE_MODP, OAKLEY_GROUP_TYPE_EC2N, oakley_group_type_name, NULL }; + +/* Notify messages -- error types */ + +static const char *const notification_name[] = { + "INVALID_PAYLOAD_TYPE", + "DOI_NOT_SUPPORTED", + "SITUATION_NOT_SUPPORTED", + "INVALID_COOKIE", + "INVALID_MAJOR_VERSION", + "INVALID_MINOR_VERSION", + "INVALID_EXCHANGE_TYPE", + "INVALID_FLAGS", + "INVALID_MESSAGE_ID", + "INVALID_PROTOCOL_ID", + "INVALID_SPI", + "INVALID_TRANSFORM_ID", + "ATTRIBUTES_NOT_SUPPORTED", + "NO_PROPOSAL_CHOSEN", + "BAD_PROPOSAL_SYNTAX", + "PAYLOAD_MALFORMED", + "INVALID_KEY_INFORMATION", + "INVALID_ID_INFORMATION", + "INVALID_CERT_ENCODING", + "INVALID_CERTIFICATE", + "CERT_TYPE_UNSUPPORTED", + "INVALID_CERT_AUTHORITY", + "INVALID_HASH_INFORMATION", + "AUTHENTICATION_FAILED", + "INVALID_SIGNATURE", + "ADDRESS_NOTIFICATION", + "NOTIFY_SA_LIFETIME", + "CERTIFICATE_UNAVAILABLE", + "UNSUPPORTED_EXCHANGE_TYPE", + "UNEQUAL_PAYLOAD_LENGTHS", + }; + +static const char *const notification_status_name[] = { + "CONNECTED", + }; + +static const char *const ipsec_notification_name[] = { + "IPSEC_RESPONDER_LIFETIME", + "IPSEC_REPLAY_STATUS", + "IPSEC_INITIAL_CONTACT", + }; + +static const char *const notification_dpd_name[] = { + "R_U_THERE", + "R_U_THERE_ACK", +}; + +enum_names notification_dpd_names = + { R_U_THERE, R_U_THERE_ACK, + notification_dpd_name, NULL }; + +enum_names ipsec_notification_names = + { IPSEC_RESPONDER_LIFETIME, IPSEC_INITIAL_CONTACT, + ipsec_notification_name, ¬ification_dpd_names }; + +enum_names notification_status_names = + { CONNECTED, CONNECTED, + notification_status_name, &ipsec_notification_names }; + +enum_names notification_names = + { INVALID_PAYLOAD_TYPE, UNEQUAL_PAYLOAD_LENGTHS, + notification_name, ¬ification_status_names }; + +/* MODECFG + * From draft-dukes-ike-mode-cfg + */ +const char *const attr_msg_type_name[] = { + "ISAKMP_CFG_RESERVED", + "ISAKMP_CFG_REQUEST", + "ISAKMP_CFG_REPLY", + "ISAKMP_CFG_SET", + "ISAKMP_CFG_ACK", + NULL + }; + +enum_names attr_msg_type_names = + { 0 , ISAKMP_CFG_ACK, attr_msg_type_name , NULL }; + +/* socket address family info */ + +static const char *const af_inet_name[] = { + "AF_INET", + }; + +static const char *const af_inet6_name[] = { + "AF_INET6", + }; + +static enum_names af_names6 = { AF_INET6, AF_INET6, af_inet6_name, NULL }; + +enum_names af_names = { AF_INET, AF_INET, af_inet_name, &af_names6 }; + +static ip_address ipv4_any, ipv6_any; +static ip_subnet ipv4_wildcard, ipv6_wildcard; +static ip_subnet ipv4_all, ipv6_all; + +const struct af_info af_inet4_info = { + AF_INET, + "AF_INET", + sizeof(struct in_addr), + sizeof(struct sockaddr_in), + 32, + ID_IPV4_ADDR, ID_IPV4_ADDR_SUBNET, ID_IPV4_ADDR_RANGE, + &ipv4_any, &ipv4_wildcard, &ipv4_all, + }; + +const struct af_info af_inet6_info = { + AF_INET6, + "AF_INET6", + sizeof(struct in6_addr), + sizeof(struct sockaddr_in6), + 128, + ID_IPV6_ADDR, ID_IPV6_ADDR_SUBNET, ID_IPV6_ADDR_RANGE, + &ipv6_any, &ipv6_wildcard, &ipv6_all, + }; + +const struct af_info * +aftoinfo(int af) +{ + switch (af) + { + case AF_INET: + return &af_inet4_info; + case AF_INET6: + return &af_inet6_info; + default: + return NULL; + } +} + +bool +subnetisnone(const ip_subnet *sn) +{ + ip_address base; + + networkof(sn, &base); + return isanyaddr(&base) && subnetishost(sn); +} + +/* BIND enumerated types */ + +#include <arpa/nameser.h> + +static const char *const rr_type_name[] = { + "T_A", /* 1 host address */ + "T_NS", /* 2 authoritative server */ + "T_MD", /* 3 mail destination */ + "T_MF", /* 4 mail forwarder */ + "T_CNAME", /* 5 canonical name */ + "T_SOA", /* 6 start of authority zone */ + "T_MB", /* 7 mailbox domain name */ + "T_MG", /* 8 mail group member */ + "T_MR", /* 9 mail rename name */ + "T_NULL", /* 10 null resource record */ + "T_WKS", /* 11 well known service */ + "T_PTR", /* 12 domain name pointer */ + "T_HINFO", /* 13 host information */ + "T_MINFO", /* 14 mailbox information */ + "T_MX", /* 15 mail routing information */ + "T_TXT", /* 16 text strings */ + "T_RP", /* 17 responsible person */ + "T_AFSDB", /* 18 AFS cell database */ + "T_X25", /* 19 X_25 calling address */ + "T_ISDN", /* 20 ISDN calling address */ + "T_RT", /* 21 router */ + "T_NSAP", /* 22 NSAP address */ + "T_NSAP_PTR", /* 23 reverse NSAP lookup (deprecated) */ + "T_SIG", /* 24 security signature */ + "T_KEY", /* 25 security key */ + "T_PX", /* 26 X.400 mail mapping */ + "T_GPOS", /* 27 geographical position (withdrawn) */ + "T_AAAA", /* 28 IP6 Address */ + "T_LOC", /* 29 Location Information */ + "T_NXT", /* 30 Next Valid Name in Zone */ + "T_EID", /* 31 Endpoint identifier */ + "T_NIMLOC", /* 32 Nimrod locator */ + "T_SRV", /* 33 Server selection */ + "T_ATMA", /* 34 ATM Address */ + "T_NAPTR", /* 35 Naming Authority PoinTeR */ + NULL + }; + +enum_names rr_type_names = { T_A, T_NAPTR, rr_type_name, NULL }; + +/* Query type values which do not appear in resource records */ +static const char *const rr_qtype_name[] = { + "T_IXFR", /* 251 incremental zone transfer */ + "T_AXFR", /* 252 transfer zone of authority */ + "T_MAILB", /* 253 transfer mailbox records */ + "T_MAILA", /* 254 transfer mail agent records */ + "T_ANY", /* 255 wildcard match */ + NULL + }; + +enum_names rr_qtype_names = { T_IXFR, T_ANY, rr_qtype_name, &rr_type_names }; + +static const char *const rr_class_name[] = { + "C_IN", /* 1 the arpa internet */ + NULL + }; + +enum_names rr_class_names = { C_IN, C_IN, rr_class_name, NULL }; + +/* + * NAT-Traversal defines for nat_traveral type from nat_traversal.h + * + */ +const char *const natt_type_bitnames[] = { + "draft-ietf-ipsec-nat-t-ike-00/01", /* 0 */ + "draft-ietf-ipsec-nat-t-ike-02/03", + "RFC 3947", + "3", /* 3 */ + "4", "5", "6", "7", + "8", "9", "10", "11", + "12", "13", "14", "15", + "16", "17", "18", "19", + "20", "21", "22", "23", + "24", "25", "26", "27", + "28", "29", + "nat is behind me", + "nat is behind peer" +}; + +/* look up enum names in an enum_names */ + +const char * +enum_name(enum_names *ed, unsigned long val) +{ + enum_names *p; + + for (p = ed; p != NULL; p = p->en_next_range) + { + if (p->en_first <= val && val <= p->en_last) + return p->en_names[val - p->en_first]; + } + return NULL; +} + +/* find or construct a string to describe an enum value + * Result may be in STATIC buffer! + */ +const char * +enum_show(enum_names *ed, unsigned long val) +{ + const char *p = enum_name(ed, val); + + if (p == NULL) + { + static char buf[12]; /* only one! I hope that it is big enough */ + + snprintf(buf, sizeof(buf), "%lu??", val); + p = buf; + } + return p; +} + + +static char bitnamesbuf[200]; /* only one! I hope that it is big enough! */ + +int +enum_search(enum_names *ed, const char *str) +{ + enum_names *p; + const char *ptr; + unsigned en; + + for (p = ed; p != NULL; p = p->en_next_range) + for (en = p->en_first; en <= p->en_last ;en++) + { + ptr = p->en_names[en - p->en_first]; + if (ptr == 0) continue; + /* if (strncmp(ptr, str, strlen(ptr))==0) */ + if (strcmp(ptr, str) == 0) + return en; + } + return -1; +} + +/* construct a string to name the bits on in a set + * Result may be in STATIC buffer! + * Note: prettypolicy depends on internal details. + */ +const char * +bitnamesof(const char *const table[], lset_t val) +{ + char *p = bitnamesbuf; + lset_t bit; + const char *const *tp; + + if (val == 0) + return "none"; + + for (tp = table, bit = 01; val != 0; bit <<= 1) + { + if (val & bit) + { + const char *n = *tp; + size_t nl; + + if (n == NULL || *n == '\0') + { + /* no name for this bit, so use hex */ + static char flagbuf[sizeof("0x80000000")]; + + snprintf(flagbuf, sizeof(flagbuf), "0x%llx", bit); + n = flagbuf; + } + + nl = strlen(n); + + if (p != bitnamesbuf && p < bitnamesbuf+sizeof(bitnamesbuf) - 1) + *p++ = '+'; + + if (bitnamesbuf+sizeof(bitnamesbuf) - p > (ptrdiff_t)nl) + { + strcpy(p, n); + p += nl; + } + val -= bit; + } + if (*tp != NULL) + tp++; /* move on, but not past end */ + } + *p = '\0'; + return bitnamesbuf; +} + +/* print a policy: like bitnamesof, but it also does the non-bitfields. + * Suppress the shunt and fail fields if 0. + */ +const char * +prettypolicy(lset_t policy) +{ + const char *bn = bitnamesof(sa_policy_bit_names + , policy & ~(POLICY_SHUNT_MASK | POLICY_FAIL_MASK)); + size_t len; + lset_t shunt = (policy & POLICY_SHUNT_MASK) >> POLICY_SHUNT_SHIFT; + lset_t fail = (policy & POLICY_FAIL_MASK) >> POLICY_FAIL_SHIFT; + + if (bn != bitnamesbuf) + bitnamesbuf[0] = '\0'; + len = strlen(bitnamesbuf); + if (shunt != 0) + { + snprintf(bitnamesbuf + len, sizeof(bitnamesbuf) - len, "+%s" + , policy_shunt_names[shunt]); + len += strlen(bitnamesbuf + len); + } + if (fail != 0) + { + snprintf(bitnamesbuf + len, sizeof(bitnamesbuf) - len, "+failure%s" + , policy_fail_names[fail]); + len += strlen(bitnamesbuf + len); + } + if (NEVER_NEGOTIATE(policy)) + { + snprintf(bitnamesbuf + len, sizeof(bitnamesbuf) - len, "+NEVER_NEGOTIATE"); + len += strlen(bitnamesbuf + len); + } + return bitnamesbuf; +} + +/* test a set by seeing if all bits have names */ + +bool +testset(const char *const table[], lset_t val) +{ + lset_t bit; + const char *const *tp; + + for (tp = table, bit = 01; val != 0; bit <<= 1, tp++) + { + const char *n = *tp; + + if (n == NULL || ((val & bit) && *n == '\0')) + return FALSE; + val &= ~bit; + } + return TRUE; +} + + +const char sparse_end[] = "end of sparse names"; + +/* look up enum names in a sparse_names */ +const char *sparse_name(sparse_names sd, unsigned long val) +{ + const struct sparse_name *p; + + for (p = sd; p->name != sparse_end; p++) + if (p->val == val) + return p->name; + return NULL; +} + +/* find or construct a string to describe an sparse value + * Result may be in STATIC buffer! + */ +const char * +sparse_val_show(sparse_names sd, unsigned long val) +{ + const char *p = sparse_name(sd, val); + + if (p == NULL) + { + static char buf[12]; /* only one! I hope that it is big enough */ + + snprintf(buf, sizeof(buf), "%lu??", val); + p = buf; + } + return p; +} + +void init_constants(void) +{ + happy(anyaddr(AF_INET, &ipv4_any)); + happy(anyaddr(AF_INET6, &ipv6_any)); + + happy(addrtosubnet(&ipv4_any, &ipv4_wildcard)); + happy(addrtosubnet(&ipv6_any, &ipv6_wildcard)); + + happy(initsubnet(&ipv4_any, 0, '0', &ipv4_all)); + happy(initsubnet(&ipv6_any, 0, '0', &ipv6_all)); +} diff --git a/src/pluto/constants.h b/src/pluto/constants.h new file mode 100644 index 000000000..3ab10be61 --- /dev/null +++ b/src/pluto/constants.h @@ -0,0 +1,1269 @@ + +/* manifest constants + * Copyright (C) 1997 Angelos D. Keromytis. + * Copyright (C) 1998-2002 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: constants.h,v 1.20 2006/02/28 19:13:33 as Exp $ + */ + +#ifndef _CONSTANTS_H +#define _CONSTANTS_H + +extern const char compile_time_interop_options[]; + +extern void init_constants(void); + +/* + * NOTE:For debugging purposes, constants.c has tables to map numbers back to names. + * Any changes here should be reflected there. + */ + +#define elemsof(array) (sizeof(array) / sizeof(*(array))) /* number of elements in an array */ + +/* Many routines return only success or failure, but wish to describe + * the failure in a message. We use the convention that they return + * a NULL on success and a pointer to constant string on failure. + * The fact that the string is a constant is limiting, but it + * avoids storage management issues: the recipient is allowed to assume + * that the string will live "long enough" (usually forever). + * <freeswan.h> defines err_t for this return type. + */ + +typedef int bool; +#define FALSE 0 +#define TRUE 1 + +#define NULL_FD (-1) /* NULL file descriptor */ +#define dup_any(fd) ((fd) == NULL_FD? NULL_FD : dup(fd)) +#define close_any(fd) { if ((fd) != NULL_FD) { close(fd); (fd) = NULL_FD; } } + +#define BITS_PER_BYTE 8 + +#define streq(a, b) (strcmp((a), (b)) == 0) /* clearer shorthand */ +#define strcaseeq(a, b) (strcasecmp((a), (b)) == 0) /* clearer shorthand */ + +/* set type with room for at least 64 elements for ALG opts (was 32 in stock FS) */ + +typedef unsigned long long lset_t; +#define LEMPTY 0ULL +#define LELEM(opt) (1ULL << (opt)) +#define LRANGE(lwb, upb) LRANGES(LELEM(lwb), LELEM(upb)) +#define LRANGES(first, last) (last - first + last) +#define LHAS(set, elem) ((LELEM(elem) & (set)) != LEMPTY) +#define LIN(subset, set) (((subset) & (set)) == (subset)) +#define LDISJOINT(a, b) (((a) & (b)) == LEMPTY) + +/* Control and lock pathnames */ +#ifndef IPSEC_PIDDIR +# define IPSEC_PIDDIR "/var/run" +#endif +#ifndef DEFAULT_CTLBASE +# define DEFAULT_CTLBASE IPSEC_PIDDIR "/pluto" +#endif + +#define CTL_SUFFIX ".ctl" /* for UNIX domain socket pathname */ +#define LOCK_SUFFIX ".pid" /* for pluto's lock */ +#define INFO_SUFFIX ".info" /* for UNIX domain socket for apps */ + +/* Routines to check and display values. + * + * An enum_names describes an enumeration. + * enum_name() returns the name of an enum value, or NULL if invalid. + * enum_show() is like enum_name, except it formats a numeric representation + * for any invalid value (in a static area!) + * + * bitnames() formats a display of a set of named bits (in a static area) + */ + +struct enum_names { + unsigned long en_first; /* first value in range */ + unsigned long en_last; /* last value in range (inclusive) */ + const char *const *en_names; + const struct enum_names *en_next_range; /* descriptor of next range */ +}; + +typedef const struct enum_names enum_names; + +extern const char *enum_name(enum_names *ed, unsigned long val); +extern const char *enum_show(enum_names *ed, unsigned long val); +extern int enum_search(enum_names *ed, const char *string); + +extern bool testset(const char *const table[], lset_t val); +extern const char *bitnamesof(const char *const table[], lset_t val); + +/* sparse_names is much like enum_names, except values are + * not known to be contiguous or ordered. + * The array of names is ended with one with the name sparse_end + * (this avoids having to reserve a value to signify the end). + * Often appropriate for enums defined by others. + */ +struct sparse_name { + unsigned long val; + const char *const name; +}; +typedef const struct sparse_name sparse_names[]; + +extern const char *sparse_name(sparse_names sd, unsigned long val); +extern const char *sparse_val_show(sparse_names sd, unsigned long val); +extern const char sparse_end[]; + +#define FULL_INET_ADDRESS_SIZE 6 + +/* Group parameters from draft-ietf-ike-01.txt section 6 */ + +#define MODP_GENERATOR "2" + +#define MODP768_MODULUS \ + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 " \ + "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD " \ + "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 " \ + "E485B576 625E7EC6 F44C42E9 A63A3620 FFFFFFFF FFFFFFFF" + +#define MODP1024_MODULUS \ + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 " \ + "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD " \ + "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 " \ + "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED " \ + "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE65381 " \ + "FFFFFFFF FFFFFFFF" + +#define MODP1536_MODULUS \ + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 " \ + "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD " \ + "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 " \ + "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED " \ + "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D " \ + "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F " \ + "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D " \ + "670C354E 4ABC9804 F1746C08 CA237327 FFFFFFFF FFFFFFFF " + +/* draft-ietf-ipsec-ike-modp-groups-03.txt */ +#define MODP2048_MODULUS \ + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" \ + "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" \ + "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" \ + "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" \ + "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" \ + "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" \ + "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" \ + "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" \ + "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" \ + "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" \ + "15728E5A 8AACAA68 FFFFFFFF FFFFFFFF" + +#define MODP3072_MODULUS \ + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" \ + "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" \ + "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" \ + "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" \ + "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" \ + "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" \ + "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" \ + "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" \ + "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" \ + "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" \ + "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" \ + "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" \ + "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" \ + "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" \ + "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" \ + "43DB5BFC E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF" + +#define MODP4096_MODULUS \ + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" \ + "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" \ + "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" \ + "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" \ + "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" \ + "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" \ + "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" \ + "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" \ + "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" \ + "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" \ + "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" \ + "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" \ + "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" \ + "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" \ + "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" \ + "43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" \ + "88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" \ + "2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" \ + "287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" \ + "1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" \ + "93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199" \ + "FFFFFFFF FFFFFFFF" + +/* copy&pasted from rfc3526: */ +#define MODP6144_MODULUS \ + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08" \ + "8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B" \ + "302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9" \ + "A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6" \ + "49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8" \ + "FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" \ + "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C" \ + "180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718" \ + "3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D" \ + "04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D" \ + "B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226" \ + "1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" \ + "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC" \ + "E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26" \ + "99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB" \ + "04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2" \ + "233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127" \ + "D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492" \ + "36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD F8FF9406" \ + "AD9E530E E5DB382F 413001AE B06A53ED 9027D831 179727B0 865A8918" \ + "DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B DB7F1447 E6CC254B 33205151" \ + "2BD7AF42 6FB8F401 378CD2BF 5983CA01 C64B92EC F032EA15 D1721D03" \ + "F482D7CE 6E74FEF6 D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F" \ + "BEC7E8F3 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA" \ + "CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328 06A1D58B" \ + "B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C DA56C9EC 2EF29632" \ + "387FE8D7 6E3C0468 043E8F66 3F4860EE 12BF2D5B 0B7474D6 E694F91E" \ + "6DCC4024 FFFFFFFF FFFFFFFF" + +/* copy&pasted from rfc3526: */ +#define MODP8192_MODULUS \ + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" \ + "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" \ + "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" \ + "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" \ + "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" \ + "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" \ + "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" \ + "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" \ + "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" \ + "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" \ + "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" \ + "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" \ + "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" \ + "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" \ + "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" \ + "43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" \ + "88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" \ + "2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" \ + "287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" \ + "1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" \ + "93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492" \ + "36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD" \ + "F8FF9406 AD9E530E E5DB382F 413001AE B06A53ED 9027D831" \ + "179727B0 865A8918 DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B" \ + "DB7F1447 E6CC254B 33205151 2BD7AF42 6FB8F401 378CD2BF" \ + "5983CA01 C64B92EC F032EA15 D1721D03 F482D7CE 6E74FEF6" \ + "D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F BEC7E8F3" \ + "23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA" \ + "CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328" \ + "06A1D58B B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C" \ + "DA56C9EC 2EF29632 387FE8D7 6E3C0468 043E8F66 3F4860EE" \ + "12BF2D5B 0B7474D6 E694F91E 6DBE1159 74A3926F 12FEE5E4" \ + "38777CB6 A932DF8C D8BEC4D0 73B931BA 3BC832B6 8D9DD300" \ + "741FA7BF 8AFC47ED 2576F693 6BA42466 3AAB639C 5AE4F568" \ + "3423B474 2BF1C978 238F16CB E39D652D E3FDB8BE FC848AD9" \ + "22222E04 A4037C07 13EB57A8 1A23F0C7 3473FC64 6CEA306B" \ + "4BCBC886 2F8385DD FA9D4B7F A2C087E8 79683303 ED5BDD3A" \ + "062B3CF5 B3A278A6 6D2A13F8 3F44F82D DF310EE0 74AB6A36" \ + "4597E899 A0255DC1 64F31CC5 0846851D F9AB4819 5DED7EA1" \ + "B1D510BD 7EE74D73 FAF36BC3 1ECFA268 359046F4 EB879F92" \ + "4009438B 481C6CD7 889A002E D5EE382B C9190DA6 FC026E47" \ + "9558E447 5677E9AA 9E3050E2 765694DF C81F56E8 80B96E71" \ + "60C980DD 98EDD3DF FFFFFFFF FFFFFFFF" +#define LOCALSECRETSIZE (256 / BITS_PER_BYTE) + +/* limits on nonce sizes. See RFC2409 "The internet key exchange (IKE)" 5 */ +#define MINIMUM_NONCE_SIZE 8 /* bytes */ +#define DEFAULT_NONCE_SIZE 16 /* bytes */ +#define MAXIMUM_NONCE_SIZE 256 /* bytes */ + +#define COOKIE_SIZE 8 +#define MAX_ISAKMP_SPI_SIZE 16 + +#define MD2_DIGEST_SIZE (128 / BITS_PER_BYTE) +#define MD5_DIGEST_SIZE (128 / BITS_PER_BYTE) +#define SHA1_DIGEST_SIZE (160 / BITS_PER_BYTE) +#define SHA2_256_DIGEST_SIZE (256 / BITS_PER_BYTE) +#define SHA2_384_DIGEST_SIZE (384 / BITS_PER_BYTE) +#define SHA2_512_DIGEST_SIZE (512 / BITS_PER_BYTE) + +#define MD5_BLOCK_SIZE (512 / BITS_PER_BYTE) +#define SHA1_BLOCK_SIZE (512 / BITS_PER_BYTE) +#define SHA2_256_BLOCK_SIZE (512 / BITS_PER_BYTE) +#define SHA2_384_BLOCK_SIZE (1024 / BITS_PER_BYTE) +#define SHA2_512_BLOCK_SIZE (1024 / BITS_PER_BYTE) + +#define DES_CBC_BLOCK_SIZE (64 / BITS_PER_BYTE) + +#define DSS_QBITS 160 /* bits in DSS's "q" (FIPS 186-1) */ + +/* Maximum is required for SHA2_512 */ +#define MAX_DIGEST_LEN SHA2_512_DIGEST_SIZE +#define MAX_HASH_BLOCK_SIZE SHA2_512_BLOCK_SIZE + +/* RFC 2404 "HMAC-SHA-1-96" section 3 */ +#define HMAC_SHA1_KEY_LEN SHA1_DIGEST_SIZE + +/* RFC 2403 "HMAC-MD5-96" section 3 */ +#define HMAC_MD5_KEY_LEN MD5_DIGEST_SIZE + +#define IKE_UDP_PORT 500 + +/* RFC 2560 OCSP - certificate status */ + +typedef enum { + CERT_GOOD = 0, + CERT_REVOKED = 1, + CERT_UNKNOWN = 2, + CERT_UNDEFINED = 3 +} cert_status_t; + +/* RFC 2459 CRL reason codes */ + +extern enum_names crl_reason_names; + +typedef enum { + REASON_UNSPECIFIED = 0, + REASON_KEY_COMPROMISE = 1, + REASON_CA_COMPROMISE = 2, + REASON_AFFILIATION_CHANGED = 3, + REASON_SUPERSEDED = 4, + REASON_CESSATION_OF_OPERATON = 5, + REASON_CERTIFICATE_HOLD = 6, + REASON_REMOVE_FROM_CRL = 8 +} crl_reason_t; + +/* RFC 3706 Dead Peer Detection */ + +extern enum_names dpd_action_names; + +typedef enum { + DPD_ACTION_NONE = 0, + DPD_ACTION_CLEAR = 1, + DPD_ACTION_HOLD = 2, + DPD_ACTION_RESTART = 3, + DPD_ACTION_UNKNOWN = 4 +} dpd_action_t; + +/* Timer events */ + +extern enum_names timer_event_names; + +enum event_type { + EVENT_NULL, /* non-event */ + EVENT_REINIT_SECRET, /* Refresh cookie secret */ +#ifdef KLIPS + EVENT_SHUNT_SCAN, /* scan shunt eroutes known to kernel */ +#endif + EVENT_SO_DISCARD, /* discard unfinished state object */ + EVENT_RETRANSMIT, /* Retransmit packet */ + EVENT_SA_REPLACE, /* SA replacement event */ + EVENT_SA_REPLACE_IF_USED, /* SA replacement event */ + EVENT_SA_EXPIRE, /* SA expiration event */ + EVENT_NAT_T_KEEPALIVE, /* NAT Traversal Keepalive */ + EVENT_DPD, /* dead peer detection */ + EVENT_DPD_TIMEOUT, /* dead peer detection timeout */ + EVENT_LOG_DAILY /* reset certain log events/stats */ +}; + +#define EVENT_REINIT_SECRET_DELAY 3600 /* 1 hour */ +#define EVENT_RETRANSMIT_DELAY_0 10 /* 10 seconds */ + +/* Misc. stuff */ + +#define MAXIMUM_RETRANSMISSIONS 2 +#define MAXIMUM_RETRANSMISSIONS_INITIAL 20 + +#define MAX_INPUT_UDP_SIZE 65536 +#define MAX_OUTPUT_UDP_SIZE 65536 + +/* Version numbers */ + +#define ISAKMP_MAJOR_VERSION 0x1 +#define ISAKMP_MINOR_VERSION 0x0 + +extern enum_names version_names; + +/* Domain of Interpretation */ + +extern enum_names doi_names; + +#define ISAKMP_DOI_ISAKMP 0 +#define ISAKMP_DOI_IPSEC 1 + +/* IPsec DOI things */ + +#define IPSEC_DOI_SITUATION_LENGTH 4 +#define IPSEC_DOI_LDI_LENGTH 4 +#define IPSEC_DOI_SPI_SIZE 4 + +/* SPI value 0 is invalid and values 1-255 are reserved to IANA. + * ESP: RFC 2402 2.4; AH: RFC 2406 2.1 + * IPComp RFC 2393 substitutes a CPI in the place of an SPI. + * see also draft-shacham-ippcp-rfc2393bis-05.txt. + * We (FreeS/WAN) reserve 0x100 to 0xFFF for manual keying, so + * Pluto won't generate these values. + */ +#define IPSEC_DOI_SPI_MIN 0x100 +#define IPSEC_DOI_SPI_OUR_MIN 0x1000 + +/* debugging settings: a set of selections for reporting + * These would be more naturally situated in log.h, + * but they are shared with whack. + * IMPAIR_* actually change behaviour, usually badly, + * to aid in testing. Naturally, these are not included in ALL. + * + * NOTE: changes here must be done in concert with changes to DBGOPT_* + * in whack.c. A change to WHACK_MAGIC in whack.h will be required too. + */ +#ifdef DEBUG +extern const char *const debug_bit_names[]; +#endif + +#define DBG_RAW LELEM(0) /* raw packet I/O */ +#define DBG_CRYPT LELEM(1) /* encryption/decryption of messages */ +#define DBG_PARSING LELEM(2) /* show decoding of messages */ +#define DBG_EMITTING LELEM(3) /* show encoding of messages */ +#define DBG_CONTROL LELEM(4) /* control flow within Pluto */ +#define DBG_LIFECYCLE LELEM(5) /* SA lifecycle */ +#define DBG_KLIPS LELEM(6) /* messages to KLIPS */ +#define DBG_DNS LELEM(7) /* DNS activity */ +#define DBG_NATT LELEM(8) /* NAT-T */ +#define DBG_OPPO LELEM(9) /* opportunism */ +#define DBG_CONTROLMORE LELEM(10) /* more detailed debugging */ + +#define DBG_PRIVATE LELEM(11) /* private information: DANGER! */ + +#define IMPAIR0 12 /* first bit for IMPAIR_* */ + +#define IMPAIR_DELAY_ADNS_KEY_ANSWER LELEM(IMPAIR0+0) /* sleep before answering */ +#define IMPAIR_DELAY_ADNS_TXT_ANSWER LELEM(IMPAIR0+1) /* sleep before answering */ +#define IMPAIR_BUST_MI2 LELEM(IMPAIR0+2) /* make MI2 really large */ +#define IMPAIR_BUST_MR2 LELEM(IMPAIR0+3) /* make MI2 really large */ + +#define DBG_NONE 0 /* no options on, including impairments */ +#define DBG_ALL LRANGES(DBG_RAW, DBG_CONTROLMORE) /* all logging options on EXCEPT DBG_PRIVATE */ + +/* State of exchanges + * + * The name of the state describes the last message sent, not the + * message currently being input or output (except during retry). + * In effect, the state represents the last completed action. + * + * Messages are named [MQ][IR]n where + * - M stands for Main Mode (Phase 1); + * Q stands for Quick Mode (Phase 2) + * - I stands for Initiator; + * R stands for Responder + * - n, a digit, stands for the number of the message + * + * It would be more convenient if each state accepted a message + * and produced one. This is the case for states at the start + * or end of an exchange. To fix this, we pretend that there are + * MR0 and QR0 messages before the MI1 and QR1 messages. Similarly, + * we pretend that there are MR4 and QR2 messages. + * + * STATE_MAIN_R0 and STATE_QUICK_R0 are intermediate states (not + * retained between messages) representing the state that accepts the + * first message of an exchange has been read but not processed. + * + * state_microcode state_microcode_table in demux.c describes + * other important details. + */ + +extern enum_names state_names; +extern const char *const state_story[]; + +enum state_kind { + STATE_UNDEFINED, /* 0 -- most likely accident */ + + /* Opportunism states: see "Opportunistic Encryption" 2.2 */ + + OPPO_ACQUIRE, /* got an ACQUIRE message for this pair */ + OPPO_GW_DISCOVERED, /* got TXT specifying gateway */ + + /* IKE states */ + + STATE_MAIN_R0, + STATE_MAIN_I1, + STATE_MAIN_R1, + STATE_MAIN_I2, + STATE_MAIN_R2, + STATE_MAIN_I3, + STATE_MAIN_R3, + STATE_MAIN_I4, + + STATE_QUICK_R0, + STATE_QUICK_I1, + STATE_QUICK_R1, + STATE_QUICK_I2, + STATE_QUICK_R2, + + STATE_INFO, + STATE_INFO_PROTECTED, + + /* XAUTH states */ + + STATE_XAUTH_I0, /* initiator state (client) */ + STATE_XAUTH_R1, /* responder state (server) */ + STATE_XAUTH_I1, + STATE_XAUTH_R2, + STATE_XAUTH_I2, + STATE_XAUTH_R3, + + /* Mode Config pull states */ + + STATE_MODE_CFG_R0, /* responder state (server) */ + STATE_MODE_CFG_I1, /* initiator state (client) */ + STATE_MODE_CFG_R1, + STATE_MODE_CFG_I2, + + /* Mode Config push states */ + + STATE_MODE_CFG_I0, /* initiator state (client) */ + STATE_MODE_CFG_R3, /* responder state (server) */ + STATE_MODE_CFG_I3, + STATE_MODE_CFG_R4, + + STATE_IKE_ROOF +}; + +#define STATE_IKE_FLOOR STATE_MAIN_R0 + +#define PHASE1_INITIATOR_STATES (LELEM(STATE_MAIN_I1) | LELEM(STATE_MAIN_I2) \ + | LELEM(STATE_MAIN_I3) | LELEM(STATE_MAIN_I4)) +#define ISAKMP_SA_ESTABLISHED_STATES ( \ + LELEM(STATE_MAIN_R3) | LELEM(STATE_MAIN_I4) \ + | LELEM(STATE_XAUTH_R1) | LELEM(STATE_XAUTH_R2) | LELEM(STATE_XAUTH_R3) \ + | LELEM(STATE_XAUTH_I1) | LELEM(STATE_XAUTH_I2) \ + | LELEM(STATE_MODE_CFG_I1) | LELEM(STATE_MODE_CFG_R1) | LELEM(STATE_MODE_CFG_I2) \ + | LELEM(STATE_MODE_CFG_R3) | LELEM(STATE_MODE_CFG_I3) | LELEM(STATE_MODE_CFG_R4)) + +#define IS_PHASE1(s) ((STATE_MAIN_R0 <= (s) && (s) <= STATE_MAIN_I4) \ + || (STATE_XAUTH_I0 <= (s) && (s) <= STATE_XAUTH_R3) \ + || (STATE_MODE_CFG_R0 <= (s) && (s) <= STATE_MODE_CFG_R4)) + +#define IS_QUICK(s) (STATE_QUICK_R0 <= (s) && (s) <= STATE_QUICK_R2) +#define IS_ISAKMP_ENCRYPTED(s) (STATE_MAIN_I2 <= (s)) + +#define IS_ISAKMP_SA_ESTABLISHED(s) ( \ + (s) == STATE_MAIN_R3 \ + || (s) == STATE_MAIN_I4 \ + || (s) == STATE_XAUTH_I2 \ + || (s) == STATE_XAUTH_R3 \ + || (s) == STATE_MODE_CFG_R1 \ + || (s) == STATE_MODE_CFG_I2 \ + || (s) == STATE_MODE_CFG_I3 \ + || (s) == STATE_MODE_CFG_R4) + +#define IS_IPSEC_SA_ESTABLISHED(s) ((s) == STATE_QUICK_I2 || (s) == STATE_QUICK_R2) +#define IS_ONLY_INBOUND_IPSEC_SA_ESTABLISHED(s) ((s) == STATE_QUICK_R1) + +/* kind of struct connection + * Ordered (mostly) by concreteness. Order is exploited. + */ + +extern enum_names connection_kind_names; + +enum connection_kind { + CK_GROUP, /* policy group: instantiates to template */ + CK_TEMPLATE, /* abstract connection, with wildcard */ + CK_PERMANENT, /* normal connection */ + CK_INSTANCE, /* instance of template, created for a particular attempt */ + CK_GOING_AWAY /* instance being deleted -- don't delete again */ +}; + + +/* routing status. + * Note: routing ignores source address, but erouting does not! + * Note: a connection can only be routed if it is NEVER_NEGOTIATE + * or HAS_IPSEC_POLICY. + */ + +extern enum_names routing_story; + +/* note that this is assumed to be ordered! */ +enum routing_t { + RT_UNROUTED, /* unrouted */ + RT_UNROUTED_HOLD, /* unrouted, but HOLD shunt installed */ + RT_ROUTED_ECLIPSED, /* RT_ROUTED_PROSPECTIVE except bare HOLD or instance has eroute */ + RT_ROUTED_PROSPECTIVE, /* routed, and prospective shunt installed */ + RT_ROUTED_HOLD, /* routed, and HOLD shunt installed */ + RT_ROUTED_FAILURE, /* routed, and failure-context shunt installed */ + RT_ROUTED_TUNNEL, /* routed, and erouted to an IPSEC SA group */ + RT_UNROUTED_KEYED /* keyed, but not routed, on purpose */ +}; + +#define routed(rs) ((rs) > RT_UNROUTED_HOLD) +#define erouted(rs) ((rs) != RT_UNROUTED) +#define shunt_erouted(rs) (erouted(rs) && (rs) != RT_ROUTED_TUNNEL) + +/* Payload types + * RFC2408 Internet Security Association and Key Management Protocol (ISAKMP) + * section 3.1 + * + * RESERVED 14-127 + * Private USE 128-255 + */ + +extern enum_names payload_names; +extern const char *const payload_name[]; + +#define ISAKMP_NEXT_NONE 0 /* No other payload following */ +#define ISAKMP_NEXT_SA 1 /* Security Association */ +#define ISAKMP_NEXT_P 2 /* Proposal */ +#define ISAKMP_NEXT_T 3 /* Transform */ +#define ISAKMP_NEXT_KE 4 /* Key Exchange */ +#define ISAKMP_NEXT_ID 5 /* Identification */ +#define ISAKMP_NEXT_CERT 6 /* Certificate */ +#define ISAKMP_NEXT_CR 7 /* Certificate Request */ +#define ISAKMP_NEXT_HASH 8 /* Hash */ +#define ISAKMP_NEXT_SIG 9 /* Signature */ +#define ISAKMP_NEXT_NONCE 10 /* Nonce */ +#define ISAKMP_NEXT_N 11 /* Notification */ +#define ISAKMP_NEXT_D 12 /* Delete */ +#define ISAKMP_NEXT_VID 13 /* Vendor ID */ +#define ISAKMP_NEXT_ATTR 14 /* Mode config Attribute */ + +#define ISAKMP_NEXT_NATD_RFC 20 /* NAT-Traversal: NAT-D (rfc) */ +#define ISAKMP_NEXT_NATOA_RFC 21 /* NAT-Traversal: NAT-OA (rfc) */ +#define ISAKMP_NEXT_ROOF 22 /* roof on payload types */ + +#define ISAKMP_NEXT_NATD_DRAFTS 130 /* NAT-Traversal: NAT-D (drafts) */ +#define ISAKMP_NEXT_NATOA_DRAFTS 131 /* NAT-Traversal: NAT-OA (drafts) */ + +/* These values are to be used within the Type field of an Attribute (14) + * ISAKMP payload. + */ +#define ISAKMP_CFG_REQUEST 1 +#define ISAKMP_CFG_REPLY 2 +#define ISAKMP_CFG_SET 3 +#define ISAKMP_CFG_ACK 4 + +extern enum_names attr_msg_type_names; + +/* Mode Config attribute values */ +#define INTERNAL_IP4_ADDRESS 1 +#define INTERNAL_IP4_NETMASK 2 +#define INTERNAL_IP4_DNS 3 +#define INTERNAL_IP4_NBNS 4 +#define INTERNAL_ADDRESS_EXPIRY 5 +#define INTERNAL_IP4_DHCP 6 +#define APPLICATION_VERSION 7 +#define INTERNAL_IP6_ADDRESS 8 +#define INTERNAL_IP6_NETMASK 9 +#define INTERNAL_IP6_DNS 10 +#define INTERNAL_IP6_NBNS 11 +#define INTERNAL_IP6_DHCP 12 +#define INTERNAL_IP4_SUBNET 13 +#define SUPPORTED_ATTRIBUTES 14 +#define INTERNAL_IP6_SUBNET 15 + + +extern enum_names modecfg_attr_names; + +/* XAUTH attribute values */ +#define XAUTH_TYPE 16520 +#define XAUTH_USER_NAME 16521 +#define XAUTH_USER_PASSWORD 16522 +#define XAUTH_PASSCODE 16523 +#define XAUTH_MESSAGE 16524 +#define XAUTH_CHALLENGE 16525 +#define XAUTH_DOMAIN 16526 +#define XAUTH_STATUS 16527 +#define XAUTH_NEXT_PIN 16528 +#define XAUTH_ANSWER 16529 + +#define XAUTH_BASE XAUTH_TYPE + +extern enum_names xauth_attr_names; + +/* ISAKMP mode config attributes specific to the Unity vendor Id */ +#define UNITY_BANNER 28672 +#define UNITY_SAVE_PASSWD 28673 +#define UNITY_DEF_DOMAIN 28674 +#define UNITY_SPLITDNS_NAME 28675 +#define UNITY_SPLIT_INCLUDE 28676 +#define UNITY_NATT_PORT 28677 +#define UNITY_LOCAL_LAN 28678 +#define UNITY_PFS 28679 +#define UNITY_FW_TYPE 28680 +#define UNITY_BACKUP_SERVERS 28681 +#define UNITY_DDNS_HOSTNAME 28682 + +#define UNITY_BASE UNITY_BANNER + +extern enum_names unity_attr_names; + +/* XAUTH authentication types */ +#define XAUTH_TYPE_GENERIC 0 +#define XAUTH_TYPE_CHAP 1 +#define XAUTH_TYPE_OTP 2 +#define XAUTH_TYPE_SKEY 3 + +/* Values for XAUTH_STATUS */ +#define XAUTH_STATUS_FAIL 0 +#define XAUTH_STATUS_OK 1 + +extern enum_names xauth_type_names; + +/* Exchange types + * RFC2408 "Internet Security Association and Key Management Protocol (ISAKMP)" + * section 3.1 + * + * ISAKMP Future Use 6 - 31 + * DOI Specific Use 32 - 239 + * Private Use 240 - 255 + * + * Note: draft-ietf-ipsec-dhless-enc-mode-00.txt Appendix A + * defines "DHless RSA Encryption" as 6. + */ + +extern enum_names exchange_names; + +#define ISAKMP_XCHG_NONE 0 +#define ISAKMP_XCHG_BASE 1 +#define ISAKMP_XCHG_IDPROT 2 /* ID Protection */ +#define ISAKMP_XCHG_AO 3 /* Authentication Only */ +#define ISAKMP_XCHG_AGGR 4 /* Aggressive */ +#define ISAKMP_XCHG_INFO 5 /* Informational */ +#define ISAKMP_XCHG_MODE_CFG 6 /* Mode Config */ + +/* Extra exchange types, defined by Oakley + * RFC2409 "The Internet Key Exchange (IKE)", near end of Appendix A + */ +#define ISAKMP_XCHG_QUICK 32 /* Oakley Quick Mode */ +#define ISAKMP_XCHG_NGRP 33 /* Oakley New Group Mode */ +/* added in draft-ietf-ipsec-ike-01.txt, near end of Appendix A */ +#define ISAKMP_XCHG_ACK_INFO 34 /* Oakley Acknowledged Informational */ + +/* Flag bits */ + +extern const char *const flag_bit_names[]; + +#define ISAKMP_FLAG_ENCRYPTION 0x1 +#define ISAKMP_FLAG_COMMIT 0x2 + +/* Situation definition for IPsec DOI */ + +extern const char *const sit_bit_names[]; + +#define SIT_IDENTITY_ONLY 0x01 +#define SIT_SECRECY 0x02 +#define SIT_INTEGRITY 0x04 + +/* Protocol IDs + * RFC2407 The Internet IP security Domain of Interpretation for ISAKMP 4.4.1 + */ + +extern enum_names protocol_names; + +#define PROTO_ISAKMP 1 +#define PROTO_IPSEC_AH 2 +#define PROTO_IPSEC_ESP 3 +#define PROTO_IPCOMP 4 + +/* warning: trans_show uses enum_show, so same static buffer is used */ +#define trans_show(p, t) \ + ((p)==PROTO_IPSEC_AH ? enum_show(&ah_transformid_names, (t)) \ + : (p)==PROTO_IPSEC_ESP ? enum_show(&esp_transformid_names, (t)) \ + : (p)==PROTO_IPCOMP ? enum_show(&ipcomp_transformid_names, (t)) \ + : "??") + +/* many transform values are moved to freeswan/ipsec_policy.h */ + +extern enum_names isakmp_transformid_names; + +#define KEY_IKE 1 + +extern enum_names ah_transformid_names; +extern enum_names esp_transformid_names; +extern enum_names ipcomp_transformid_names; + +/* the following are from RFC 2393/draft-shacham-ippcp-rfc2393bis-05.txt 3.3 */ +typedef u_int16_t cpi_t; +#define IPCOMP_CPI_SIZE 2 +#define IPCOMP_FIRST_NEGOTIATED 256 +#define IPCOMP_LAST_NEGOTIATED 61439 + +/* Identification type values + * RFC 2407 The Internet IP security Domain of Interpretation for ISAKMP 4.6.2.1 + */ + +extern enum_names ident_names; +extern enum_names cert_type_names; +extern enum_names cert_policy_names; + +typedef enum certpolicy { + CERT_ALWAYS_SEND = 0, /* the default */ + CERT_SEND_IF_ASKED = 1, + CERT_NEVER_SEND = 2, + + CERT_YES_SEND = 3, /* synonym for CERT_ALWAYS_SEND */ + CERT_NO_SEND = 4 /* synonym for CERT_NEVER_SEND */ +} certpolicy_t; + +/* Policies for establishing an SA + * + * These are used to specify attributes (eg. encryption) and techniques + * (eg PFS) for an SA. + * Note: certain CD_ definitions in whack.c parallel these -- keep them + * in sync! + */ + +extern const char *const sa_policy_bit_names[]; +extern const char *prettypolicy(lset_t policy); + +/* ISAKMP auth techniques (none means never negotiate) */ +#define POLICY_PSK LELEM(0) +#define POLICY_RSASIG LELEM(1) + +#define POLICY_ISAKMP_SHIFT 0 /* log2(POLICY_PSK) */ +#define POLICY_ID_AUTH_MASK (POLICY_PSK | POLICY_RSASIG | POLICY_XAUTH_PSK | POLICY_XAUTH_RSASIG) +#define POLICY_ISAKMP_MASK POLICY_ID_AUTH_MASK /* all so far */ + +/* Quick Mode (IPSEC) attributes */ +#define POLICY_ENCRYPT LELEM(2) /* must be first of IPSEC policies */ +#define POLICY_AUTHENTICATE LELEM(3) /* must be second */ +#define POLICY_COMPRESS LELEM(4) /* must be third */ +#define POLICY_TUNNEL LELEM(5) +#define POLICY_PFS LELEM(6) +#define POLICY_DISABLEARRIVALCHECK LELEM(7) /* supress tunnel egress address checking */ + +#define POLICY_IPSEC_SHIFT 2 /* log2(POLICY_ENCRYPT) */ +#define POLICY_IPSEC_MASK LRANGES(POLICY_ENCRYPT, POLICY_DISABLEARRIVALCHECK) + +/* shunt attributes: what to do when routed without tunnel (2 bits) */ +#define POLICY_SHUNT_SHIFT 8 /* log2(POLICY_SHUNT_PASS) */ +#define POLICY_SHUNT_MASK (03ul << POLICY_SHUNT_SHIFT) + +#define POLICY_SHUNT_TRAP (0ul << POLICY_SHUNT_SHIFT) /* default: negotiate */ +#define POLICY_SHUNT_PASS (1ul << POLICY_SHUNT_SHIFT) +#define POLICY_SHUNT_DROP (2ul << POLICY_SHUNT_SHIFT) +#define POLICY_SHUNT_REJECT (3ul << POLICY_SHUNT_SHIFT) + +/* fail attributes: what to do with failed negotiation (2 bits) */ + +#define POLICY_FAIL_SHIFT 10 /* log2(POLICY_FAIL_PASS) */ +#define POLICY_FAIL_MASK (03ul << POLICY_FAIL_SHIFT) + +#define POLICY_FAIL_NONE (0ul << POLICY_FAIL_SHIFT) /* default */ +#define POLICY_FAIL_PASS (1ul << POLICY_FAIL_SHIFT) +#define POLICY_FAIL_DROP (2ul << POLICY_FAIL_SHIFT) +#define POLICY_FAIL_REJECT (3ul << POLICY_FAIL_SHIFT) + +/* connection policy + * Other policies could vary per state object. These live in connection. + */ +#define POLICY_DONT_REKEY LELEM(12) /* don't rekey state either Phase */ +#define POLICY_OPPO LELEM(13) /* is this opportunistic? */ +#define POLICY_GROUP LELEM(14) /* is this a group template? */ +#define POLICY_GROUTED LELEM(15) /* do we want this group routed? */ +#define POLICY_UP LELEM(16) /* do we want this up? */ +#define POLICY_MODECFG_PUSH LELEM(17) /* is modecfg pushed by server? */ +#define POLICY_XAUTH_PSK LELEM(18) /* do we support XAUTH????PreShared? */ +#define POLICY_XAUTH_RSASIG LELEM(19) /* do we support XAUTH????RSA? */ +#define POLICY_XAUTH_SERVER LELEM(20) /* are we an XAUTH server? */ +#define POLICY_DONT_REAUTH LELEM(21) /* don't reauthenticate on rekeying, IKEv2 only */ +#define POLICY_BEET LELEM(22) /* bound end2end tunnel, IKEv2 */ + +/* Any IPsec policy? If not, a connection description + * is only for ISAKMP SA, not IPSEC SA. (A pun, I admit.) + * Note: a connection can only be routed if it is NEVER_NEGOTIATE + * or HAS_IPSEC_POLICY. + */ +#define HAS_IPSEC_POLICY(p) (((p) & POLICY_IPSEC_MASK) != 0) + +/* Don't allow negotiation? */ +#define NEVER_NEGOTIATE(p) (LDISJOINT((p), POLICY_ID_AUTH_MASK)) + + +/* Oakley transform attributes + * draft-ietf-ipsec-ike-01.txt appendix A + */ + +extern enum_names oakley_attr_names; +extern const char *const oakley_attr_bit_names[]; + +#define OAKLEY_ENCRYPTION_ALGORITHM 1 +#define OAKLEY_HASH_ALGORITHM 2 +#define OAKLEY_AUTHENTICATION_METHOD 3 +#define OAKLEY_GROUP_DESCRIPTION 4 +#define OAKLEY_GROUP_TYPE 5 +#define OAKLEY_GROUP_PRIME 6 /* B/V */ +#define OAKLEY_GROUP_GENERATOR_ONE 7 /* B/V */ +#define OAKLEY_GROUP_GENERATOR_TWO 8 /* B/V */ +#define OAKLEY_GROUP_CURVE_A 9 /* B/V */ +#define OAKLEY_GROUP_CURVE_B 10 /* B/V */ +#define OAKLEY_LIFE_TYPE 11 +#define OAKLEY_LIFE_DURATION 12 /* B/V */ +#define OAKLEY_PRF 13 +#define OAKLEY_KEY_LENGTH 14 +#define OAKLEY_FIELD_SIZE 15 +#define OAKLEY_GROUP_ORDER 16 /* B/V */ +#define OAKLEY_BLOCK_SIZE 17 + +/* for each Oakley attribute, which enum_names describes its values? */ +extern enum_names *oakley_attr_val_descs[]; + +/* IPsec DOI attributes + * RFC2407 The Internet IP security Domain of Interpretation for ISAKMP 4.5 + */ + +extern enum_names ipsec_attr_names; + +#define SA_LIFE_TYPE 1 +#define SA_LIFE_DURATION 2 /* B/V */ +#define GROUP_DESCRIPTION 3 +#define ENCAPSULATION_MODE 4 +#define AUTH_ALGORITHM 5 +#define KEY_LENGTH 6 +#define KEY_ROUNDS 7 +#define COMPRESS_DICT_SIZE 8 +#define COMPRESS_PRIVATE_ALG 9 /* B/V */ + +/* for each IPsec attribute, which enum_names describes its values? */ +extern enum_names *ipsec_attr_val_descs[]; + +/* SA Lifetime Type attribute + * RFC2407 The Internet IP security Domain of Interpretation for ISAKMP 4.5 + * Default time specified in 4.5 + * + * There are two defaults for IPSEC SA lifetime, SA_LIFE_DURATION_DEFAULT, + * and PLUTO_SA_LIFE_DURATION_DEFAULT. + * SA_LIFE_DURATION_DEFAULT is specified in RFC2407 "The Internet IP + * Security Domain of Interpretation for ISAKMP" 4.5. It applies when + * an ISAKMP negotiation does not explicitly specify a life duration. + * PLUTO_SA_LIFE_DURATION_DEFAULT is specified in pluto(8). It applies + * when a connection description does not specify --ipseclifetime. + * The value of SA_LIFE_DURATION_MAXIMUM is our local policy. + */ + +extern enum_names sa_lifetime_names; + +#define SA_LIFE_TYPE_SECONDS 1 +#define SA_LIFE_TYPE_KBYTES 2 + +#define SA_LIFE_DURATION_DEFAULT 28800 /* eight hours (RFC2407 4.5) */ +#define PLUTO_SA_LIFE_DURATION_DEFAULT 3600 /* one hour (pluto(8)) */ +#define SA_LIFE_DURATION_MAXIMUM 86400 /* one day */ + +#define SA_REPLACEMENT_MARGIN_DEFAULT 540 /* (IPSEC & IKE) nine minutes */ +#define SA_REPLACEMENT_FUZZ_DEFAULT 100 /* (IPSEC & IKE) 100% of MARGIN */ +#define SA_REPLACEMENT_RETRIES_DEFAULT 3 /* (IPSEC & IKE) */ + +#define SA_LIFE_DURATION_K_DEFAULT 0xFFFFFFFFlu + +/* Encapsulation Mode attribute */ + +extern enum_names enc_mode_names; + +#define ENCAPSULATION_MODE_UNSPECIFIED 0 /* not legal -- used internally */ +#define ENCAPSULATION_MODE_TUNNEL 1 +#define ENCAPSULATION_MODE_TRANSPORT 2 + +#define ENCAPSULATION_MODE_UDP_TUNNEL_RFC 3 +#define ENCAPSULATION_MODE_UDP_TRANSPORT_RFC 4 + +#define ENCAPSULATION_MODE_UDP_TUNNEL_DRAFTS 61443 +#define ENCAPSULATION_MODE_UDP_TRANSPORT_DRAFTS 61444 + +/* Auth Algorithm attribute */ + +extern enum_names auth_alg_names, extended_auth_alg_names; + +#define AUTH_ALGORITHM_NONE 0 /* our private designation */ +#define AUTH_ALGORITHM_HMAC_MD5 1 +#define AUTH_ALGORITHM_HMAC_SHA1 2 +#define AUTH_ALGORITHM_DES_MAC 3 +#define AUTH_ALGORITHM_KPDK 4 +#define AUTH_ALGORITHM_HMAC_SHA2_256 5 +#define AUTH_ALGORITHM_HMAC_SHA2_384 6 +#define AUTH_ALGORITHM_HMAC_SHA2_512 7 +#define AUTH_ALGORITHM_HMAC_RIPEMD 8 +#define AUTH_ALGORITHM_NULL 251 + +/* Oakley Lifetime Type attribute + * draft-ietf-ipsec-ike-01.txt appendix A + * As far as I can see, there is not specification for + * OAKLEY_ISAKMP_SA_LIFETIME_DEFAULT. This could lead to interop problems! + * For no particular reason, we chose three hours. + * The value of OAKLEY_ISAKMP_SA_LIFETIME_MAXIMUM is our local policy. + */ +extern enum_names oakley_lifetime_names; + +#define OAKLEY_LIFE_SECONDS 1 +#define OAKLEY_LIFE_KILOBYTES 2 + +#define OAKLEY_ISAKMP_SA_LIFETIME_DEFAULT 10800 /* three hours */ +#define OAKLEY_ISAKMP_SA_LIFETIME_MAXIMUM 86400 /* one day */ + +/* Oakley PRF attribute (none defined) + * draft-ietf-ipsec-ike-01.txt appendix A + */ +extern enum_names oakley_prf_names; + +/* HMAC (see rfc2104.txt) */ + +#define HMAC_IPAD 0x36 +#define HMAC_OPAD 0x5C + +/* Oakley Encryption Algorithm attribute + * draft-ietf-ipsec-ike-01.txt appendix A + * and from http://www.isi.edu/in-notes/iana/assignments/ipsec-registry + */ + +extern enum_names oakley_enc_names; + +#define OAKLEY_DES_CBC 1 +#define OAKLEY_IDEA_CBC 2 +#define OAKLEY_BLOWFISH_CBC 3 +#define OAKLEY_RC5_R16_B64_CBC 4 +#define OAKLEY_3DES_CBC 5 +#define OAKLEY_CAST_CBC 6 +#define OAKLEY_AES_CBC 7 + +#define OAKLEY_MARS_CBC 65001 +#define OAKLEY_RC6_CBC 65002 +#define OAKLEY_ID_65003 65003 +#define OAKLEY_SERPENT_CBC 65004 +#define OAKLEY_TWOFISH_CBC 65005 + +#define OAKLEY_TWOFISH_CBC_SSH 65289 + +#define OAKLEY_ENCRYPT_MAX 65535 /* pretty useless :) */ + +/* Oakley Hash Algorithm attribute + * draft-ietf-ipsec-ike-01.txt appendix A + * and from http://www.isi.edu/in-notes/iana/assignments/ipsec-registry + */ + +extern enum_names oakley_hash_names; + +#define OAKLEY_MD5 1 +#define OAKLEY_SHA 2 +#define OAKLEY_TIGER 3 +#define OAKLEY_SHA2_256 4 +#define OAKLEY_SHA2_384 5 +#define OAKLEY_SHA2_512 6 + +#define OAKLEY_HASH_MAX 7 + +/* Oakley Authentication Method attribute + * draft-ietf-ipsec-ike-01.txt appendix A + * Goofy Hybrid extensions from draft-ietf-ipsec-isakmp-hybrid-auth-05.txt + * Goofy XAUTH extensions from draft-ietf-ipsec-isakmp-xauth-06.txt + */ + +extern enum_names oakley_auth_names; + +#define OAKLEY_PRESHARED_KEY 1 +#define OAKLEY_DSS_SIG 2 +#define OAKLEY_RSA_SIG 3 +#define OAKLEY_RSA_ENC 4 +#define OAKLEY_RSA_ENC_REV 5 +#define OAKLEY_ELGAMAL_ENC 6 +#define OAKLEY_ELGAMAL_ENC_REV 7 + +#define OAKLEY_AUTH_ROOF 8 /* roof on auth values THAT WE SUPPORT */ + +#define HybridInitRSA 64221 +#define HybridRespRSA 64222 +#define HybridInitDSS 64223 +#define HybridRespDSS 64224 + +#define XAUTHInitPreShared 65001 +#define XAUTHRespPreShared 65002 +#define XAUTHInitDSS 65003 +#define XAUTHRespDSS 65004 +#define XAUTHInitRSA 65005 +#define XAUTHRespRSA 65006 +#define XAUTHInitRSAEncryption 65007 +#define XAUTHRespRSAEncryption 65008 +#define XAUTHInitRSARevisedEncryption 65009 +#define XAUTHRespRSARevisedEncryption 65010 + +/* Oakley Group Description attribute + * draft-ietf-ipsec-ike-01.txt appendix A + */ +extern enum_names oakley_group_names; + +#define OAKLEY_GROUP_MODP768 1 +#define OAKLEY_GROUP_MODP1024 2 +#define OAKLEY_GROUP_GP155 3 +#define OAKLEY_GROUP_GP185 4 +#define OAKLEY_GROUP_MODP1536 5 + +#define OAKLEY_GROUP_MODP2048 14 +#define OAKLEY_GROUP_MODP3072 15 +#define OAKLEY_GROUP_MODP4096 16 +#define OAKLEY_GROUP_MODP6144 17 +#define OAKLEY_GROUP_MODP8192 18 +/* you must also touch: constants.c, crypto.c */ + +/* Oakley Group Type attribute + * draft-ietf-ipsec-ike-01.txt appendix A + */ +extern enum_names oakley_group_type_names; + +#define OAKLEY_GROUP_TYPE_MODP 1 +#define OAKLEY_GROUP_TYPE_ECP 2 +#define OAKLEY_GROUP_TYPE_EC2N 3 + + +/* Notify messages -- error types + * See RFC2408 ISAKMP 3.14.1 + */ + +extern enum_names notification_names; +extern enum_names ipsec_notification_names; + +typedef enum { + NOTHING_WRONG = 0, /* unofficial! */ + + INVALID_PAYLOAD_TYPE = 1, + DOI_NOT_SUPPORTED = 2, + SITUATION_NOT_SUPPORTED = 3, + INVALID_COOKIE = 4, + INVALID_MAJOR_VERSION = 5, + INVALID_MINOR_VERSION = 6, + INVALID_EXCHANGE_TYPE = 7, + INVALID_FLAGS = 8, + INVALID_MESSAGE_ID = 9, + INVALID_PROTOCOL_ID = 10, + INVALID_SPI = 11, + INVALID_TRANSFORM_ID = 12, + ATTRIBUTES_NOT_SUPPORTED = 13, + NO_PROPOSAL_CHOSEN = 14, + BAD_PROPOSAL_SYNTAX = 15, + PAYLOAD_MALFORMED = 16, + INVALID_KEY_INFORMATION = 17, + INVALID_ID_INFORMATION = 18, + INVALID_CERT_ENCODING = 19, + INVALID_CERTIFICATE = 20, + CERT_TYPE_UNSUPPORTED = 21, + INVALID_CERT_AUTHORITY = 22, + INVALID_HASH_INFORMATION = 23, + AUTHENTICATION_FAILED = 24, + INVALID_SIGNATURE = 25, + ADDRESS_NOTIFICATION = 26, + NOTIFY_SA_LIFETIME = 27, + CERTIFICATE_UNAVAILABLE = 28, + UNSUPPORTED_EXCHANGE_TYPE = 29, + UNEQUAL_PAYLOAD_LENGTHS = 30, + + /* ISAKMP status type */ + CONNECTED = 16384, + + /* IPSEC DOI additions; status types (RFC2407 IPSEC DOI 4.6.3) + * These must be sent under the protection of an ISAKMP SA. + */ + IPSEC_RESPONDER_LIFETIME = 24576, + IPSEC_REPLAY_STATUS = 24577, + IPSEC_INITIAL_CONTACT = 24578, + + /* RFC 3706 DPD */ + R_U_THERE = 36136, + R_U_THERE_ACK = 36137 + + } notification_t; + + +/* Public key algorithm number + * Same numbering as used in DNSsec + * See RFC 2535 DNSsec 3.2 The KEY Algorithm Number Specification. + * Also found in BIND 8.2.2 include/isc/dst.h as DST algorithm codes. + */ + +enum pubkey_alg +{ + PUBKEY_ALG_RSA = 1, + PUBKEY_ALG_DSA = 3, +}; + +/* Limits on size of RSA moduli. + * The upper bound matches that of DNSsec (see RFC 2537). + * The lower bound must be more than 11 octets for certain + * the encoding to work, but it must be much larger for any + * real security. For now, we require 512 bits. + */ + +#define RSA_MIN_OCTETS_RFC 12 + +#define RSA_MIN_OCTETS (512 / BITS_PER_BYTE) +#define RSA_MIN_OCTETS_UGH "RSA modulus too small for security: less than 512 bits" + +#define RSA_MAX_OCTETS (8192 / BITS_PER_BYTE) +#define RSA_MAX_OCTETS_UGH "RSA modulus too large: more than 8192 bits" + +/* Note: RFC 2537 encoding adds a few bytes. If you use a small + * modulus like 3, the overhead is only 2 bytes + */ +#define RSA_MAX_ENCODING_BYTES (RSA_MAX_OCTETS + 2) + +/* socket address family info */ + +struct af_info +{ + int af; + const char *name; + size_t ia_sz; + size_t sa_sz; + int mask_cnt; + u_int8_t id_addr, id_subnet, id_range; + const ip_address *any; + const ip_subnet *none; /* 0.0.0.0/32 or IPv6 equivalent */ + const ip_subnet *all; /* 0.0.0.0/0 or IPv6 equivalent */ +}; + +extern const struct af_info + af_inet4_info, + af_inet6_info; + +extern const struct af_info *aftoinfo(int af); + +extern enum_names af_names; + +#define subnetisaddr(sn, a) (subnetishost(sn) && addrinsubnet((a), (sn))) +extern bool subnetisnone(const ip_subnet *sn); + +/* BIND enumerated types */ + +extern enum_names + rr_qtype_names, + rr_type_names, + rr_class_names; + +/* How authenticated is info that might have come from DNS? + * In order of increasing confidence. + */ +enum dns_auth_level { + DAL_UNSIGNED, /* AD in response, but no signature: no authentication */ + DAL_NOTSEC, /* no AD in response: authentication impossible */ + DAL_SIGNED, /* AD and signature in response: authentic */ + DAL_LOCAL /* locally provided (pretty good) */ +}; + +/* + * define a macro for use in error messages + */ + +#ifdef USE_KEYRR +#define RRNAME "TXT or KEY" +#else +#define RRNAME "TXT" +#endif + +/* natt traversal types */ +extern const char *const natt_type_bitnames[]; + +#endif /* _CONSTANTS_H */ diff --git a/src/pluto/cookie.c b/src/pluto/cookie.c new file mode 100644 index 000000000..458120e46 --- /dev/null +++ b/src/pluto/cookie.c @@ -0,0 +1,67 @@ +/* cookie generation/verification routines. + * Copyright (C) 1997 Angelos D. Keromytis. + * Copyright (C) 1998-2002 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: cookie.c,v 1.2 2005/08/17 16:38:20 as Exp $ + */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "sha1.h" +#include "rnd.h" +#include "cookie.h" + +const u_char zero_cookie[COOKIE_SIZE]; /* guaranteed 0 */ + +/* Generate a cookie. + * First argument is true if we're to create an Initiator cookie. + * Length SHOULD be a multiple of sizeof(u_int32_t). + */ +void +get_cookie(bool initiator, u_int8_t *cookie, int length, const ip_address *addr) +{ + u_char buffer[SHA1_DIGEST_SIZE]; + SHA1_CTX ctx; + + do { + if (initiator) + { + get_rnd_bytes(cookie, length); + } + else /* Responder cookie */ + { + /* This looks as good as any way */ + size_t addr_length; + static u_int32_t counter = 0; + unsigned char addr_buff[ + sizeof(union {struct in_addr A; struct in6_addr B;})]; + + addr_length = addrbytesof(addr, addr_buff, sizeof(addr_buff)); + SHA1Init(&ctx); + SHA1Update(&ctx, addr_buff, addr_length); + SHA1Update(&ctx, secret_of_the_day, sizeof(secret_of_the_day)); + counter++; + SHA1Update(&ctx, (const void *) &counter, sizeof(counter)); + SHA1Final(buffer, &ctx); + memcpy(cookie, buffer, length); + } + } while (is_zero_cookie(cookie)); /* probably never loops */ +} diff --git a/src/pluto/cookie.h b/src/pluto/cookie.h new file mode 100644 index 000000000..f5b0e64d1 --- /dev/null +++ b/src/pluto/cookie.h @@ -0,0 +1,24 @@ +/* cookie generation/verification routines. + * Copyright (C) 1998-2002 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: cookie.h,v 1.1 2004/03/15 20:35:28 as Exp $ + */ + +#include <freeswan.h> + +extern const u_char zero_cookie[COOKIE_SIZE]; /* guaranteed 0 */ + +extern void get_cookie(bool initiator, u_int8_t *cookie, int length + , const ip_address *addr); + +#define is_zero_cookie(cookie) all_zero((cookie), COOKIE_SIZE) diff --git a/src/pluto/crl.c b/src/pluto/crl.c new file mode 100644 index 000000000..05e8d1402 --- /dev/null +++ b/src/pluto/crl.c @@ -0,0 +1,763 @@ +/* Support of X.509 certificate revocation lists (CRLs) + * Copyright (C) 2000-2004 Andreas Steffen, Zuercher Hochschule Winterthur + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: crl.c,v 1.12 2005/12/06 22:49:57 as Exp $ + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <dirent.h> +#include <time.h> +#include <sys/types.h> + +#include <freeswan.h> +#include <ipsec_policy.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "asn1.h" +#include "oid.h" +#include "x509.h" +#include "crl.h" +#include "ca.h" +#include "certs.h" +#include "keys.h" +#include "whack.h" +#include "fetch.h" +#include "sha1.h" + +/* chained lists of X.509 crls */ + +static x509crl_t *x509crls = NULL; + +/* ASN.1 definition of an X.509 certificate list */ + +static const asn1Object_t crlObjects[] = { + { 0, "certificateList", ASN1_SEQUENCE, ASN1_OBJ }, /* 0 */ + { 1, "tbsCertList", ASN1_SEQUENCE, ASN1_OBJ }, /* 1 */ + { 2, "version", ASN1_INTEGER, ASN1_OPT | + ASN1_BODY }, /* 2 */ + { 2, "end opt", ASN1_EOC, ASN1_END }, /* 3 */ + { 2, "signature", ASN1_EOC, ASN1_RAW }, /* 4 */ + { 2, "issuer", ASN1_SEQUENCE, ASN1_OBJ }, /* 5 */ + { 2, "thisUpdate", ASN1_EOC, ASN1_RAW }, /* 6 */ + { 2, "nextUpdate", ASN1_EOC, ASN1_RAW }, /* 7 */ + { 2, "revokedCertificates", ASN1_SEQUENCE, ASN1_OPT | + ASN1_LOOP }, /* 8 */ + { 3, "certList", ASN1_SEQUENCE, ASN1_NONE }, /* 9 */ + { 4, "userCertificate", ASN1_INTEGER, ASN1_BODY }, /* 10 */ + { 4, "revocationDate", ASN1_EOC, ASN1_RAW }, /* 11 */ + { 4, "crlEntryExtensions", ASN1_SEQUENCE, ASN1_OPT | + ASN1_LOOP }, /* 12 */ + { 5, "extension", ASN1_SEQUENCE, ASN1_NONE }, /* 13 */ + { 6, "extnID", ASN1_OID, ASN1_BODY }, /* 14 */ + { 6, "critical", ASN1_BOOLEAN, ASN1_DEF | + ASN1_BODY }, /* 15 */ + { 6, "extnValue", ASN1_OCTET_STRING, ASN1_BODY }, /* 16 */ + { 4, "end opt or loop", ASN1_EOC, ASN1_END }, /* 17 */ + { 2, "end opt or loop", ASN1_EOC, ASN1_END }, /* 18 */ + { 2, "optional extensions", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 19 */ + { 3, "crlExtensions", ASN1_SEQUENCE, ASN1_LOOP }, /* 20 */ + { 4, "extension", ASN1_SEQUENCE, ASN1_NONE }, /* 21 */ + { 5, "extnID", ASN1_OID, ASN1_BODY }, /* 22 */ + { 5, "critical", ASN1_BOOLEAN, ASN1_DEF | + ASN1_BODY }, /* 23 */ + { 5, "extnValue", ASN1_OCTET_STRING, ASN1_BODY }, /* 24 */ + { 3, "end loop", ASN1_EOC, ASN1_END }, /* 25 */ + { 2, "end opt", ASN1_EOC, ASN1_END }, /* 26 */ + { 1, "signatureAlgorithm", ASN1_EOC, ASN1_RAW }, /* 27 */ + { 1, "signatureValue", ASN1_BIT_STRING, ASN1_BODY } /* 28 */ + }; + +#define CRL_OBJ_CERTIFICATE_LIST 0 +#define CRL_OBJ_TBS_CERT_LIST 1 +#define CRL_OBJ_VERSION 2 +#define CRL_OBJ_SIG_ALG 4 +#define CRL_OBJ_ISSUER 5 +#define CRL_OBJ_THIS_UPDATE 6 +#define CRL_OBJ_NEXT_UPDATE 7 +#define CRL_OBJ_USER_CERTIFICATE 10 +#define CRL_OBJ_REVOCATION_DATE 11 +#define CRL_OBJ_CRL_ENTRY_EXTN_ID 14 +#define CRL_OBJ_CRL_ENTRY_CRITICAL 15 +#define CRL_OBJ_CRL_ENTRY_EXTN_VALUE 16 +#define CRL_OBJ_EXTN_ID 22 +#define CRL_OBJ_CRITICAL 23 +#define CRL_OBJ_EXTN_VALUE 24 +#define CRL_OBJ_ALGORITHM 27 +#define CRL_OBJ_SIGNATURE 28 +#define CRL_OBJ_ROOF 29 + + +const x509crl_t empty_x509crl = { + NULL , /* *next */ + UNDEFINED_TIME, /* installed */ + NULL , /* distributionPoints */ + { NULL, 0 } , /* certificateList */ + { NULL, 0 } , /* tbsCertList */ + 1 , /* version */ + OID_UNKNOWN , /* sigAlg */ + { NULL, 0 } , /* issuer */ + UNDEFINED_TIME, /* thisUpdate */ + UNDEFINED_TIME, /* nextUpdate */ + NULL , /* revokedCertificates */ + /* crlExtensions */ + /* extension */ + /* extnID */ + /* critical */ + /* extnValue */ + { NULL, 0 } , /* authKeyID */ + { NULL, 0 } , /* authKeySerialNumber */ + OID_UNKNOWN , /* algorithm */ + { NULL, 0 } /* signature */ +}; + +/* + * get the X.509 CRL with a given issuer + */ +static x509crl_t* +get_x509crl(chunk_t issuer, chunk_t serial, chunk_t keyid) +{ + x509crl_t *crl = x509crls; + x509crl_t *prev_crl = NULL; + + while (crl != NULL) + { + if ((keyid.ptr != NULL && crl->authKeyID.ptr != NULL) + ? same_keyid(keyid, crl->authKeyID) + : (same_dn(crl->issuer, issuer) && same_serial(serial, crl->authKeySerialNumber))) + { + if (crl != x509crls) + { + /* bring the CRL up front */ + prev_crl->next = crl->next; + crl->next = x509crls; + x509crls = crl; + } + return crl; + } + prev_crl = crl; + crl = crl->next; + } + return NULL; +} + +/* + * free the dynamic memory used to store revoked certificates + */ +static void +free_revoked_certs(revokedCert_t* revokedCerts) +{ + while (revokedCerts != NULL) + { + revokedCert_t * revokedCert = revokedCerts; + revokedCerts = revokedCert->next; + pfree(revokedCert); + } +} + +/* + * free the dynamic memory used to store CRLs + */ +void +free_crl(x509crl_t *crl) +{ + free_revoked_certs(crl->revokedCertificates); + free_generalNames(crl->distributionPoints, TRUE); + pfree(crl->certificateList.ptr); + pfree(crl); +} + +static void +free_first_crl(void) +{ + x509crl_t *crl = x509crls; + + x509crls = crl->next; + free_crl(crl); +} + +void +free_crls(void) +{ + lock_crl_list("free_crls"); + + while (x509crls != NULL) + free_first_crl(); + + unlock_crl_list("free_crls"); +} + +/* + * Insert X.509 CRL into chained list + */ +bool +insert_crl(chunk_t blob, chunk_t crl_uri, bool cache_crl) +{ + x509crl_t *crl = alloc_thing(x509crl_t, "x509crl"); + + *crl = empty_x509crl; + + if (parse_x509crl(blob, 0, crl)) + { + x509cert_t *issuer_cert; + x509crl_t *oldcrl; + bool valid_sig; + generalName_t *gn; + + /* add distribution point */ + gn = alloc_thing(generalName_t, "generalName"); + gn->kind = GN_URI; + gn->name = crl_uri; + gn->next = crl->distributionPoints; + crl->distributionPoints = gn; + + lock_authcert_list("insert_crl"); + /* get the issuer cacert */ + issuer_cert = get_authcert(crl->issuer, crl->authKeySerialNumber, + crl->authKeyID, AUTH_CA); + if (issuer_cert == NULL) + { + plog("crl issuer cacert not found"); + free_crl(crl); + unlock_authcert_list("insert_crl"); + return FALSE; + } + DBG(DBG_CONTROL, + DBG_log("crl issuer cacert found") + ) + + /* check the issuer's signature of the crl */ + valid_sig = check_signature(crl->tbsCertList, crl->signature + , crl->algorithm, crl->algorithm, issuer_cert); + unlock_authcert_list("insert_crl"); + + if (!valid_sig) + { + free_crl(crl); + return FALSE; + } + DBG(DBG_CONTROL, + DBG_log("crl signature is valid") + ) + + lock_crl_list("insert_crl"); + oldcrl = get_x509crl(crl->issuer, crl->authKeySerialNumber + , crl->authKeyID); + + if (oldcrl != NULL) + { + if (crl->thisUpdate > oldcrl->thisUpdate) + { + /* keep any known CRL distribution points */ + add_distribution_points(oldcrl->distributionPoints + , &crl->distributionPoints); + + /* now delete the old CRL */ + free_first_crl(); + DBG(DBG_CONTROL, + DBG_log("thisUpdate is newer - existing crl deleted") + ) + } + else + { + unlock_crl_list("insert_crls"); + DBG(DBG_CONTROL, + DBG_log("thisUpdate is not newer - existing crl not replaced"); + ) + free_crl(crl); + return oldcrl->nextUpdate - time(NULL) > 2*crl_check_interval; + } + } + + /* insert new CRL */ + crl->next = x509crls; + x509crls = crl; + + unlock_crl_list("insert_crl"); + + /* If crl caching is enabled then the crl is saved locally. + * Only http or ldap URIs are cached but not local file URIs. + * The issuer's subjectKeyID is used as a unique filename + */ + if (cache_crl && strncasecmp(crl_uri.ptr, "file", 4) != 0) + { + char path[BUF_LEN]; + char buf[BUF_LEN]; + char digest_buf[SHA1_DIGEST_SIZE]; + chunk_t subjectKeyID = { digest_buf, SHA1_DIGEST_SIZE }; + + if (issuer_cert->subjectKeyID.ptr == NULL) + compute_subjectKeyID(issuer_cert, subjectKeyID); + else + subjectKeyID = issuer_cert->subjectKeyID; + + datatot(subjectKeyID.ptr, subjectKeyID.len, 16, buf, BUF_LEN); + snprintf(path, BUF_LEN, "%s/%s.crl", CRL_PATH, buf); + write_chunk(path, "crl", crl->certificateList, 0022, TRUE); + } + + /* is the fetched crl valid? */ + return crl->nextUpdate - time(NULL) > 2*crl_check_interval; + } + else + { + plog(" error in X.509 crl"); + free_crl(crl); + return FALSE; + } +} + +/* + * Loads CRLs + */ +void +load_crls(void) +{ + struct dirent **filelist; + u_char buf[BUF_LEN]; + u_char *save_dir; + int n; + + /* change directory to specified path */ + save_dir = getcwd(buf, BUF_LEN); + if (chdir(CRL_PATH)) + { + plog("Could not change to directory '%s'", CRL_PATH); + } + else + { + plog("Changing to directory '%s'", CRL_PATH); + n = scandir(CRL_PATH, &filelist, file_select, alphasort); + + if (n < 0) + plog(" scandir() error"); + else + { + while (n--) + { + bool pgp = FALSE; + chunk_t blob = empty_chunk; + char *filename = filelist[n]->d_name; + + if (load_coded_file(filename, NULL, "crl", &blob, &pgp)) + { + chunk_t crl_uri; + + crl_uri.len = 7 + sizeof(CRL_PATH) + strlen(filename); + crl_uri.ptr = alloc_bytes(crl_uri.len + 1, "crl uri"); + + /* build CRL file URI */ + snprintf(crl_uri.ptr, crl_uri.len + 1, "file://%s/%s" + , CRL_PATH, filename); + + insert_crl(blob, crl_uri, FALSE); + } + free(filelist[n]); + } + free(filelist); + } + } + /* restore directory path */ + chdir(save_dir); +} + +/* + * Parses a CRL revocation reason code + */ +static crl_reason_t +parse_crl_reasonCode(chunk_t object) +{ + crl_reason_t reason = REASON_UNSPECIFIED; + + if (*object.ptr == ASN1_ENUMERATED + && asn1_length(&object) == 1) + { + reason = *object.ptr; + } + + DBG(DBG_PARSING, + DBG_log(" '%s'", enum_name(&crl_reason_names, reason)) + ) + return reason; +} + +/* + * Parses an X.509 CRL + */ +bool +parse_x509crl(chunk_t blob, u_int level0, x509crl_t *crl) +{ + u_char buf[BUF_LEN]; + asn1_ctx_t ctx; + bool critical; + chunk_t extnID; + chunk_t userCertificate; + chunk_t object; + u_int level; + int objectID = 0; + + asn1_init(&ctx, blob, level0, FALSE, DBG_RAW); + + while (objectID < CRL_OBJ_ROOF) + { + if (!extract_object(crlObjects, &objectID, &object, &level, &ctx)) + return FALSE; + + /* those objects which will parsed further need the next higher level */ + level++; + + switch (objectID) { + case CRL_OBJ_CERTIFICATE_LIST: + crl->certificateList = object; + break; + case CRL_OBJ_TBS_CERT_LIST: + crl->tbsCertList = object; + break; + case CRL_OBJ_VERSION: + crl->version = (object.len) ? (1+(u_int)*object.ptr) : 1; + DBG(DBG_PARSING, + DBG_log(" v%d", crl->version); + ) + break; + case CRL_OBJ_SIG_ALG: + crl->sigAlg = parse_algorithmIdentifier(object, level, NULL); + break; + case CRL_OBJ_ISSUER: + crl->issuer = object; + DBG(DBG_PARSING, + dntoa(buf, BUF_LEN, object); + DBG_log(" '%s'",buf) + ) + break; + case CRL_OBJ_THIS_UPDATE: + crl->thisUpdate = parse_time(object, level); + break; + case CRL_OBJ_NEXT_UPDATE: + crl->nextUpdate = parse_time(object, level); + break; + case CRL_OBJ_USER_CERTIFICATE: + userCertificate = object; + break; + case CRL_OBJ_REVOCATION_DATE: + { + /* put all the serial numbers and the revocation date in a chained list + with revocedCertificates pointing to the first revoked certificate */ + + revokedCert_t *revokedCert = alloc_thing(revokedCert_t, "revokedCert"); + revokedCert->userCertificate = userCertificate; + revokedCert->revocationDate = parse_time(object, level); + revokedCert->revocationReason = REASON_UNSPECIFIED; + revokedCert->next = crl->revokedCertificates; + crl->revokedCertificates = revokedCert; + } + break; + case CRL_OBJ_CRL_ENTRY_EXTN_ID: + case CRL_OBJ_EXTN_ID: + extnID = object; + break; + case CRL_OBJ_CRL_ENTRY_CRITICAL: + case CRL_OBJ_CRITICAL: + critical = object.len && *object.ptr; + DBG(DBG_PARSING, + DBG_log(" %s",(critical)?"TRUE":"FALSE"); + ) + break; + case CRL_OBJ_CRL_ENTRY_EXTN_VALUE: + case CRL_OBJ_EXTN_VALUE: + { + u_int extn_oid = known_oid(extnID); + + if (extn_oid == OID_CRL_REASON_CODE) + { + crl->revokedCertificates->revocationReason = + parse_crl_reasonCode(object); + } + else if (extn_oid == OID_AUTHORITY_KEY_ID) + { + parse_authorityKeyIdentifier(object, level + , &crl->authKeyID, &crl->authKeySerialNumber); + } + } + break; + case CRL_OBJ_ALGORITHM: + crl->algorithm = parse_algorithmIdentifier(object, level, NULL); + break; + case CRL_OBJ_SIGNATURE: + crl->signature = object; + break; + default: + break; + } + objectID++; + } + time(&crl->installed); + return TRUE; +} + +/* Checks if the current certificate is revoked. It goes through the + * list of revoked certificates of the corresponding crl. Either the + * status CERT_GOOD or CERT_REVOKED is returned + */ +static cert_status_t +check_revocation(const x509crl_t *crl, chunk_t serial +, time_t *revocationDate, crl_reason_t * revocationReason) +{ + revokedCert_t *revokedCert = crl->revokedCertificates; + + *revocationDate = UNDEFINED_TIME; + *revocationReason = REASON_UNSPECIFIED; + + DBG(DBG_CONTROL, + DBG_dump_chunk("serial number:", serial) + ) + + while(revokedCert != NULL) + { + /* compare serial numbers */ + if (revokedCert->userCertificate.len == serial.len && + memcmp(revokedCert->userCertificate.ptr, serial.ptr, serial.len) == 0) + { + *revocationDate = revokedCert->revocationDate; + *revocationReason = revokedCert->revocationReason; + return CERT_REVOKED; + } + revokedCert = revokedCert->next; + } + return CERT_GOOD; +} + +/* + * check if any crls are about to expire + */ +void +check_crls(void) +{ + x509crl_t *crl; + + lock_crl_list("check_crls"); + crl = x509crls; + + while (crl != NULL) + { + time_t time_left = crl->nextUpdate - time(NULL); + u_char buf[BUF_LEN]; + + DBG(DBG_CONTROL, + dntoa(buf, BUF_LEN, crl->issuer); + DBG_log("issuer: '%s'",buf); + if (crl->authKeyID.ptr != NULL) + { + datatot(crl->authKeyID.ptr, crl->authKeyID.len, ':' + , buf, BUF_LEN); + DBG_log("authkey: %s", buf); + } + DBG_log("%ld seconds left", time_left) + ) + if (time_left < 2*crl_check_interval) + { + fetch_req_t *req = build_crl_fetch_request(crl->issuer + , crl->authKeySerialNumber + , crl->authKeyID, crl->distributionPoints); + add_crl_fetch_request(req); + } + crl = crl->next; + } + unlock_crl_list("check_crls"); +} + +/* + * verify if a cert hasn't been revoked by a crl + */ +cert_status_t +verify_by_crl(const x509cert_t *cert, time_t *until, time_t *revocationDate +, crl_reason_t *revocationReason) +{ + x509crl_t *crl; + + ca_info_t *ca = get_ca_info(cert->issuer, cert->authKeySerialNumber + , cert->authKeyID); + + generalName_t *crluri = (ca == NULL)? NULL : ca->crluri; + + *revocationDate = UNDEFINED_TIME; + *revocationReason = REASON_UNSPECIFIED; + + lock_crl_list("verify_by_crl"); + crl = get_x509crl(cert->issuer, cert->authKeySerialNumber, cert->authKeyID); + + if (crl == NULL) + { + unlock_crl_list("verify_by_crl"); + plog("crl not found"); + + if (cert->crlDistributionPoints != NULL) + { + fetch_req_t *req = build_crl_fetch_request(cert->issuer + , cert->authKeySerialNumber + , cert->authKeyID, cert->crlDistributionPoints); + add_crl_fetch_request(req); + } + + if (crluri != NULL) + { + fetch_req_t *req = build_crl_fetch_request(cert->issuer + , cert->authKeySerialNumber + , cert->authKeyID, crluri); + add_crl_fetch_request(req); + } + + if (cert->crlDistributionPoints != 0 || crluri != NULL) + { + wake_fetch_thread("verify_by_crl"); + return CERT_UNKNOWN; + } + else + return CERT_UNDEFINED; + } + else + { + x509cert_t *issuer_cert; + bool valid; + + DBG(DBG_CONTROL, + DBG_log("crl found") + ) + + add_distribution_points(cert->crlDistributionPoints + , &crl->distributionPoints); + + add_distribution_points(crluri + , &crl->distributionPoints); + + lock_authcert_list("verify_by_crl"); + + issuer_cert = get_authcert(crl->issuer, crl->authKeySerialNumber + , crl->authKeyID, AUTH_CA); + valid = check_signature(crl->tbsCertList, crl->signature + , crl->algorithm, crl->algorithm, issuer_cert); + + unlock_authcert_list("verify_by_crl"); + + if (valid) + { + cert_status_t status; + + DBG(DBG_CONTROL, + DBG_log("crl signature is valid") + ) + /* return the expiration date */ + *until = crl->nextUpdate; + + /* has the certificate been revoked? */ + status = check_revocation(crl, cert->serialNumber, revocationDate + , revocationReason); + + if (*until < time(NULL)) + { + fetch_req_t *req; + + plog("crl update is overdue since %s" + , timetoa(until, TRUE)); + + /* try to fetch a crl update */ + req = build_crl_fetch_request(crl->issuer + , crl->authKeySerialNumber + , crl->authKeyID, crl->distributionPoints); + unlock_crl_list("verify_by_crl"); + + add_crl_fetch_request(req); + wake_fetch_thread("verify_by_crl"); + } + else + { + unlock_crl_list("verify_by_crl"); + DBG(DBG_CONTROL, + DBG_log("crl is valid") + ) + } + return status; + } + else + { + unlock_crl_list("verify_by_crl"); + plog("crl signature is invalid"); + return CERT_UNKNOWN; + } + } +} + +/* + * list all X.509 crls in the chained list + */ +void +list_crls(bool utc, bool strict) +{ + x509crl_t *crl; + + lock_crl_list("list_crls"); + crl = x509crls; + + if (crl != NULL) + { + whack_log(RC_COMMENT, " "); + whack_log(RC_COMMENT, "List of X.509 CRLs:"); + whack_log(RC_COMMENT, " "); + } + + while (crl != NULL) + { + u_char buf[BUF_LEN]; + u_int revoked = 0; + revokedCert_t *revokedCert = crl->revokedCertificates; + + /* count number of revoked certificates in CRL */ + while (revokedCert != NULL) + { + revoked++; + revokedCert = revokedCert->next; + } + + whack_log(RC_COMMENT, "%s, revoked certs: %d", + timetoa(&crl->installed, utc), revoked); + dntoa(buf, BUF_LEN, crl->issuer); + whack_log(RC_COMMENT, " issuer: '%s'", buf); + + list_distribution_points(crl->distributionPoints); + + whack_log(RC_COMMENT, " updates: this %s", + timetoa(&crl->thisUpdate, utc)); + whack_log(RC_COMMENT, " next %s %s", + timetoa(&crl->nextUpdate, utc), + check_expiry(crl->nextUpdate, CRL_WARNING_INTERVAL, strict)); + if (crl->authKeyID.ptr != NULL) + { + datatot(crl->authKeyID.ptr, crl->authKeyID.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT, " authkey: %s", buf); + } + if (crl->authKeySerialNumber.ptr != NULL) + { + datatot(crl->authKeySerialNumber.ptr, crl->authKeySerialNumber.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT, " aserial: %s", buf); + } + + crl = crl->next; + } + unlock_crl_list("list_crls"); +} + diff --git a/src/pluto/crl.h b/src/pluto/crl.h new file mode 100644 index 000000000..9f985b6cd --- /dev/null +++ b/src/pluto/crl.h @@ -0,0 +1,87 @@ +/* Support of X.509 certificate revocation lists (CRLs) + * Copyright (C) 2000-2004 Andreas Steffen, Zuercher Hochschule Winterthur + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: crl.h,v 1.4 2005/07/18 19:36:22 as Exp $ + */ + +#include "constants.h" + +/* access structure for a revoked serial number */ + +typedef struct revokedCert revokedCert_t; + +struct revokedCert{ + revokedCert_t *next; + chunk_t userCertificate; + time_t revocationDate; + crl_reason_t revocationReason; +}; + +/* storage structure for an X.509 CRL */ + +typedef struct x509crl x509crl_t; + +struct x509crl { + x509crl_t *next; + time_t installed; + generalName_t *distributionPoints; + chunk_t certificateList; + chunk_t tbsCertList; + u_int version; + /* signature */ + int sigAlg; + chunk_t issuer; + time_t thisUpdate; + time_t nextUpdate; + revokedCert_t *revokedCertificates; + /* v2 extensions */ + /* crlExtensions */ + /* extension */ + /* extnID */ + /* critical */ + /* extnValue */ + chunk_t authKeyID; + chunk_t authKeySerialNumber; + + /* signatureAlgorithm */ + int algorithm; + chunk_t signature; +}; + +/* apply a strict CRL policy + * flag set in plutomain.c and used in ipsec_doi.c and rcv_whack.c + */ +extern bool strict_crl_policy; + +/* + * cache the retrieved CRLs by storing them locally as a file + */ +extern bool cache_crls; + +/* + * check periodically for expired crls + */ +extern long crl_check_interval; + +/* used for initialization */ +extern const x509crl_t empty_x509crl; + +extern bool parse_x509crl(chunk_t blob, u_int level0, x509crl_t *crl); +extern void load_crls(void); +extern void check_crls(void); +extern bool insert_crl(chunk_t blob, chunk_t crl_uri, bool cache_crl); +extern cert_status_t verify_by_crl(const x509cert_t *cert, time_t *until + , time_t *revocationDate, crl_reason_t *revocationReason); +extern void list_crls(bool utc, bool strict); +extern void free_crls(void); +extern void free_crl(x509crl_t *crl); diff --git a/src/pluto/crypto.c b/src/pluto/crypto.c new file mode 100644 index 000000000..f1b7c3f5f --- /dev/null +++ b/src/pluto/crypto.c @@ -0,0 +1,627 @@ +/* crypto interfaces + * Copyright (C) 1998-2001 D. Hugh Redelmeier + * Copyright (C) 2007 Andreas Steffen + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: crypto.c,v 1.5 2005/12/06 22:51:34 as Exp $ + */ + +#include <stdio.h> +#include <string.h> +#include <stddef.h> +#include <sys/types.h> + +#include <freeswan.h> +#define HEADER_DES_LOCL_H /* stupid trick to force prototype decl in <des.h> */ +#include <libdes/des.h> + +#include <errno.h> + +#include "constants.h" +#include "defs.h" +#include "state.h" +#include "log.h" +#include "md5.h" +#include "sha1.h" +#include "crypto.h" /* requires sha1.h and md5.h */ +#include "alg_info.h" +#include "ike_alg.h" + + +/* moduli and generator. */ + +static MP_INT + modp1024_modulus, + modp1536_modulus, + modp2048_modulus, + modp3072_modulus, + modp4096_modulus, + modp6144_modulus, + modp8192_modulus; + +MP_INT groupgenerator; /* MODP group generator (2) */ + +static void do_3des(u_int8_t *buf, size_t buf_len, u_int8_t *key, size_t key_size, u_int8_t *iv, bool enc); + +static struct encrypt_desc crypto_encryptor_3des = +{ + algo_type: IKE_ALG_ENCRYPT, + algo_id: OAKLEY_3DES_CBC, + algo_next: NULL, + enc_ctxsize: sizeof(des_key_schedule) * 3, + enc_blocksize: DES_CBC_BLOCK_SIZE, + keydeflen: DES_CBC_BLOCK_SIZE * 3 * BITS_PER_BYTE, + keyminlen: DES_CBC_BLOCK_SIZE * 3 * BITS_PER_BYTE, + keymaxlen: DES_CBC_BLOCK_SIZE * 3 * BITS_PER_BYTE, + do_crypt: do_3des, +}; + +/* MD5 hash test vectors + * from RFC 1321 "MD5 Message-Digest Algorithm" + * April 1992, R. Rivest, RSA Data Security + */ + +static const u_char md5_test0_msg[] = { + +}; + +static const u_char md5_test0_msg_digest[] = { + 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, + 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e +}; + +static const u_char md5_test1_msg[] = { + 0x61 +}; + +static const u_char md5_test1_msg_digest[] = { + 0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8, + 0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77, 0x26, 0x61 +}; + +static const u_char md5_test2_msg[] = { + 0x61, 0x62, 0x63 +}; + +static const u_char md5_test2_msg_digest[] = { + 0x90, 0x01, 0x50, 0x98, 0x3c, 0xd2, 0x4f, 0xb0, + 0xd6, 0x96, 0x3f, 0x7d, 0x28, 0xe1, 0x7f, 0x72 +}; + +static const u_char md5_test3_msg[] = { + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, + 0x64, 0x69, 0x67, 0x65, 0x73, 0x74 +}; + +static const u_char md5_test3_msg_digest[] = { + 0xf9, 0x6b, 0x69, 0x7d, 0x7c, 0xb7, 0x93, 0x8d, + 0x52, 0x5a, 0x2f, 0x31, 0xaa, 0xf1, 0x61, 0xd0 +}; + +static const u_char md5_test4_msg[] = { + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a +}; + +static const u_char md5_test4_msg_digest[] = { + 0xc3, 0xfc, 0xd3, 0xd7, 0x61, 0x92, 0xe4, 0x00, + 0x7d, 0xfb, 0x49, 0x6c, 0xca, 0x67, 0xe1, 0x3b +}; + +static const u_char md5_test5_msg[] = { + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, + 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, + 0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39 +}; + +static const u_char md5_test5_msg_digest[] = { + 0xd1, 0x74, 0xab, 0x98, 0xd2, 0x77, 0xd9, 0xf5, + 0xa5, 0x61, 0x1c, 0x2c, 0x9f, 0x41, 0x9d, 0x9f +}; + +static const u_char md5_test6_msg[] = { + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, + 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, + 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, + 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, + 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30 +}; + +static const u_char md5_test6_msg_digest[] = { + 0x57, 0xed, 0xf4, 0xa2, 0x2b, 0xe3, 0xc9, 0x55, + 0xac, 0x49, 0xda, 0x2e, 0x21, 0x07, 0xb6, 0x7a +}; + +static const hash_testvector_t md5_hash_testvectors[] = { + { sizeof(md5_test0_msg), md5_test0_msg, md5_test0_msg_digest }, + { sizeof(md5_test1_msg), md5_test1_msg, md5_test1_msg_digest }, + { sizeof(md5_test2_msg), md5_test2_msg, md5_test2_msg_digest }, + { sizeof(md5_test3_msg), md5_test3_msg, md5_test3_msg_digest }, + { sizeof(md5_test4_msg), md5_test4_msg, md5_test4_msg_digest }, + { sizeof(md5_test5_msg), md5_test5_msg, md5_test5_msg_digest }, + { sizeof(md5_test6_msg), md5_test6_msg, md5_test6_msg_digest }, + { 0, NULL, NULL } +}; + +/* MD5 hmac test vectors + * from RFC 2202 "Test Cases for HMAC-MD5 and HMAC-SHA-1" + * September 1997, P. Cheng, IBM & R. Glenn, NIST + */ + +static const u_char md5_hmac1_key[] = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b +}; + +static const u_char md5_hmac1_msg[] = { + 0x48, 0x69, 0x20, 0x54, 0x68, 0x65, 0x72, 0x65 +}; + +static const u_char md5_hmac1[] = { + 0x92, 0x94, 0x72, 0x7a, 0x36, 0x38, 0xbb, 0x1c, + 0x13, 0xf4, 0x8e, 0xf8, 0x15, 0x8b, 0xfc, 0x9d +}; + +static const u_char md5_hmac2_key[] = { + 0x4a, 0x65, 0x66, 0x65 +}; + +static const u_char md5_hmac2_msg[] = { + 0x77, 0x68, 0x61, 0x74, 0x20, 0x64, 0x6f, 0x20, + 0x79, 0x61, 0x20, 0x77, 0x61, 0x6e, 0x74, 0x20, + 0x66, 0x6f, 0x72, 0x20, 0x6e, 0x6f, 0x74, 0x68, + 0x69, 0x6e, 0x67, 0x3f +}; + +static const u_char md5_hmac2[] = { + 0x75, 0x0c, 0x78, 0x3e, 0x6a, 0xb0, 0xb5, 0x03, + 0xea, 0xa8, 0x6e, 0x31, 0x0a, 0x5d, 0xb7, 0x38 +}; + +static const u_char md5_hmac3_key[] = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa +}; + +static const u_char md5_hmac3_msg[] = { + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd +}; + +static const u_char md5_hmac3[] = { + 0x56, 0xbe, 0x34, 0x52, 0x1d, 0x14, 0x4c, 0x88, + 0xdb, 0xb8, 0xc7, 0x33, 0xf0, 0xe8, 0xb3, 0xf6 +}; + +static const u_char md5_hmac4_key[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19 +}; + +static const u_char md5_hmac4_msg[] = { + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd +}; + +static const u_char md5_hmac4[] = { + 0x69, 0x7e, 0xaf, 0x0a, 0xca, 0x3a, 0x3a, 0xea, + 0x3a, 0x75, 0x16, 0x47, 0x46, 0xff, 0xaa, 0x79 +}; + +static const u_char md5_hmac6_key[] = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, +}; + +static const u_char md5_hmac6_msg[] = { + 0x54, 0x65, 0x73, 0x74, 0x20, 0x55, 0x73, 0x69, + 0x6e, 0x67, 0x20, 0x4c, 0x61, 0x72, 0x67, 0x65, + 0x72, 0x20, 0x54, 0x68, 0x61, 0x6e, 0x20, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x2d, 0x53, 0x69, 0x7a, + 0x65, 0x20, 0x4b, 0x65, 0x79, 0x20, 0x2d, 0x20, + 0x48, 0x61, 0x73, 0x68, 0x20, 0x4b, 0x65, 0x79, + 0x20, 0x46, 0x69, 0x72, 0x73, 0x74 +}; + +static const u_char md5_hmac6[] = { + 0x6b, 0x1a, 0xb7, 0xfe, 0x4b, 0xd7, 0xbf, 0x8f, + 0x0b, 0x62, 0xe6, 0xce, 0x61, 0xb9, 0xd0, 0xcd +}; + +static const u_char md5_hmac7_msg[] = { + 0x54, 0x65, 0x73, 0x74, 0x20, 0x55, 0x73, 0x69, + 0x6e, 0x67, 0x20, 0x4c, 0x61, 0x72, 0x67, 0x65, + 0x72, 0x20, 0x54, 0x68, 0x61, 0x6e, 0x20, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x2d, 0x53, 0x69, 0x7a, + 0x65, 0x20, 0x4b, 0x65, 0x79, 0x20, 0x61, 0x6e, + 0x64, 0x20, 0x4c, 0x61, 0x72, 0x67, 0x65, 0x72, + 0x20, 0x54, 0x68, 0x61, 0x6e, 0x20, 0x4f, 0x6e, + 0x65, 0x20, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x2d, + 0x53, 0x69, 0x7a, 0x65, 0x20, 0x44, 0x61, 0x74, + 0x61 +}; + +static const u_char md5_hmac7[] = { + 0x6f, 0x63, 0x0f, 0xad, 0x67, 0xcd, 0xa0, 0xee, + 0x1f, 0xb1, 0xf5, 0x62, 0xdb, 0x3a, 0xa5, 0x3e +}; + +static const hmac_testvector_t md5_hmac_testvectors[] = { + { sizeof(md5_hmac1_key), md5_hmac1_key, sizeof(md5_hmac1_msg), md5_hmac1_msg, md5_hmac1 }, + { sizeof(md5_hmac2_key), md5_hmac2_key, sizeof(md5_hmac2_msg), md5_hmac2_msg, md5_hmac2 }, + { sizeof(md5_hmac3_key), md5_hmac3_key, sizeof(md5_hmac3_msg), md5_hmac3_msg, md5_hmac3 }, + { sizeof(md5_hmac4_key), md5_hmac4_key, sizeof(md5_hmac4_msg), md5_hmac4_msg, md5_hmac4 }, + { sizeof(md5_hmac6_key), md5_hmac6_key, sizeof(md5_hmac6_msg), md5_hmac6_msg, md5_hmac6 }, + { sizeof(md5_hmac6_key), md5_hmac6_key, sizeof(md5_hmac7_msg), md5_hmac7_msg, md5_hmac7 }, + { 0, NULL, 0, NULL, NULL } +}; + +static struct hash_desc crypto_hasher_md5 = +{ + algo_type: IKE_ALG_HASH, + algo_id: OAKLEY_MD5, + algo_next: NULL, + hash_ctx_size: sizeof(MD5_CTX), + hash_block_size: MD5_BLOCK_SIZE, + hash_digest_size: MD5_DIGEST_SIZE, + hash_testvectors: md5_hash_testvectors, + hmac_testvectors: md5_hmac_testvectors, + hash_init: (void (*)(void *)) MD5Init, + hash_update: (void (*)(void *, const u_int8_t *, size_t)) MD5Update, + hash_final: (void (*)(u_char *, void *)) MD5Final +}; + +/* SHA-1 test vectors + * from "The Secure Hash Algorithm Validation System (SHAVS)" + * July 22, 2004, Lawrence E. Bassham III, NIST + */ + +static const u_char sha1_short2_msg[] = { + 0x5e +}; + +static const u_char sha1_short2_msg_digest[] = { + 0x5e, 0x6f, 0x80, 0xa3, 0x4a, 0x97, 0x98, 0xca, + 0xfc, 0x6a, 0x5d, 0xb9, 0x6c, 0xc5, 0x7b, 0xa4, + 0xc4, 0xdb, 0x59, 0xc2 +}; + +static const u_char sha1_short4_msg[] = { + 0x9a, 0x7d, 0xfd, 0xf1, 0xec, 0xea, 0xd0, 0x6e, + 0xd6, 0x46, 0xaa, 0x55, 0xfe, 0x75, 0x71, 0x46 +}; + +static const u_char sha1_short4_msg_digest[] = { + 0x82, 0xab, 0xff, 0x66, 0x05, 0xdb, 0xe1, 0xc1, + 0x7d, 0xef, 0x12, 0xa3, 0x94, 0xfa, 0x22, 0xa8, + 0x2b, 0x54, 0x4a, 0x35 +}; + +static const u_char sha1_long2_msg[] = { + 0xf7, 0x8f, 0x92, 0x14, 0x1b, 0xcd, 0x17, 0x0a, + 0xe8, 0x9b, 0x4f, 0xba, 0x15, 0xa1, 0xd5, 0x9f, + 0x3f, 0xd8, 0x4d, 0x22, 0x3c, 0x92, 0x51, 0xbd, + 0xac, 0xbb, 0xae, 0x61, 0xd0, 0x5e, 0xd1, 0x15, + 0xa0, 0x6a, 0x7c, 0xe1, 0x17, 0xb7, 0xbe, 0xea, + 0xd2, 0x44, 0x21, 0xde, 0xd9, 0xc3, 0x25, 0x92, + 0xbd, 0x57, 0xed, 0xea, 0xe3, 0x9c, 0x39, 0xfa, + 0x1f, 0xe8, 0x94, 0x6a, 0x84, 0xd0, 0xcf, 0x1f, + 0x7b, 0xee, 0xad, 0x17, 0x13, 0xe2, 0xe0, 0x95, + 0x98, 0x97, 0x34, 0x7f, 0x67, 0xc8, 0x0b, 0x04, + 0x00, 0xc2, 0x09, 0x81, 0x5d, 0x6b, 0x10, 0xa6, + 0x83, 0x83, 0x6f, 0xd5, 0x56, 0x2a, 0x56, 0xca, + 0xb1, 0xa2, 0x8e, 0x81, 0xb6, 0x57, 0x66, 0x54, + 0x63, 0x1c, 0xf1, 0x65, 0x66, 0xb8, 0x6e, 0x3b, + 0x33, 0xa1, 0x08, 0xb0, 0x53, 0x07, 0xc0, 0x0a, + 0xff, 0x14, 0xa7, 0x68, 0xed, 0x73, 0x50, 0x60, + 0x6a, 0x0f, 0x85, 0xe6, 0xa9, 0x1d, 0x39, 0x6f, + 0x5b, 0x5c, 0xbe, 0x57, 0x7f, 0x9b, 0x38, 0x80, + 0x7c, 0x7d, 0x52, 0x3d, 0x6d, 0x79, 0x2f, 0x6e, + 0xbc, 0x24, 0xa4, 0xec, 0xf2, 0xb3, 0xa4, 0x27, + 0xcd, 0xbb, 0xfb +}; + +static const u_char sha1_long2_msg_digest[] = { + 0xcb, 0x00, 0x82, 0xc8, 0xf1, 0x97, 0xd2, 0x60, + 0x99, 0x1b, 0xa6, 0xa4, 0x60, 0xe7, 0x6e, 0x20, + 0x2b, 0xad, 0x27, 0xb3 +}; + +static const hash_testvector_t sha1_hash_testvectors[] = { + { sizeof(sha1_short2_msg), sha1_short2_msg, sha1_short2_msg_digest }, + { sizeof(sha1_short4_msg), sha1_short4_msg, sha1_short4_msg_digest }, + { sizeof(sha1_long2_msg), sha1_long2_msg, sha1_long2_msg_digest }, + { 0, NULL, NULL } +}; + +/* SHA-1 hmac test vectors + * from RFC 2202 "Test Cases for HMAC-MD5 and HMAC-SHA-1" + * September 1997, P. Cheng, IBM & R. Glenn, NIST + */ + +static const u_char sha1_hmac1_key[] = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b +}; + +static const u_char sha1_hmac1[] = { + 0xb6, 0x17, 0x31, 0x86, 0x55, 0x05, 0x72, 0x64, + 0xe2, 0x8b, 0xc0, 0xb6, 0xfb, 0x37, 0x8c, 0x8e, + 0xf1, 0x46, 0xbe, 0x00 +}; + +static const u_char sha1_hmac2[] = { + 0xef, 0xfc, 0xdf, 0x6a, 0xe5, 0xeb, 0x2f, 0xa2, + 0xd2, 0x74, 0x16, 0xd5, 0xf1, 0x84, 0xdf, 0x9c, + 0x25, 0x9a, 0x7c, 0x79 +}; + +static const u_char sha1_hmac3_key[] = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa +}; + +static const u_char sha1_hmac3[] = { + 0x12, 0x5d, 0x73, 0x42, 0xb9, 0xac, 0x11, 0xcd, + 0x91, 0xa3, 0x9a, 0xf4, 0x8a, 0xa1, 0x7b, 0x4f, + 0x63, 0xf1, 0x75, 0xd3 +}; + +static const u_char sha1_hmac4[] = { + 0x4c, 0x90, 0x07, 0xf4, 0x02, 0x62, 0x50, 0xc6, + 0xbc, 0x84, 0x14, 0xf9, 0xbf, 0x50, 0xc8, 0x6c, + 0x2d, 0x72, 0x35, 0xda +}; + +static const u_char sha1_hmac6[] = { + 0xaa, 0x4a, 0xe5, 0xe1, 0x52, 0x72, 0xd0, 0x0e, + 0x95, 0x70, 0x56, 0x37, 0xce, 0x8a, 0x3b, 0x55, + 0xed, 0x40, 0x21, 0x12 +}; + +static const u_char sha1_hmac7[] = { + 0xe8, 0xe9, 0x9d, 0x0f, 0x45, 0x23, 0x7d, 0x78, + 0x6d, 0x6b, 0xba, 0xa7, 0x96, 0x5c, 0x78, 0x08, + 0xbb, 0xff, 0x1a, 0x91 +}; + +static const hmac_testvector_t sha1_hmac_testvectors[] = { + { sizeof(sha1_hmac1_key), sha1_hmac1_key, sizeof(md5_hmac1_msg), md5_hmac1_msg, sha1_hmac1 }, + { sizeof(md5_hmac2_key), md5_hmac2_key, sizeof(md5_hmac2_msg), md5_hmac2_msg, sha1_hmac2 }, + { sizeof(sha1_hmac3_key), sha1_hmac3_key, sizeof(md5_hmac3_msg), md5_hmac3_msg, sha1_hmac3 }, + { sizeof(md5_hmac4_key), md5_hmac4_key, sizeof(md5_hmac4_msg), md5_hmac4_msg, sha1_hmac4 }, + { sizeof(md5_hmac6_key), md5_hmac6_key, sizeof(md5_hmac6_msg), md5_hmac6_msg, sha1_hmac6 }, + { sizeof(md5_hmac6_key), md5_hmac6_key, sizeof(md5_hmac7_msg), md5_hmac7_msg, sha1_hmac7 }, + { 0, NULL, 0, NULL, NULL } +}; + +static struct hash_desc crypto_hasher_sha1 = +{ + algo_type: IKE_ALG_HASH, + algo_id: OAKLEY_SHA, + algo_next: NULL, + hash_ctx_size: sizeof(SHA1_CTX), + hash_block_size: SHA1_BLOCK_SIZE, + hash_digest_size: SHA1_DIGEST_SIZE, + hash_testvectors: sha1_hash_testvectors, + hmac_testvectors: sha1_hmac_testvectors, + hash_init: (void (*)(void *)) SHA1Init, + hash_update: (void (*)(void *, const u_int8_t *, size_t)) SHA1Update, + hash_final: (void (*)(u_char *, void *)) SHA1Final +}; + +void +init_crypto(void) +{ + if (mpz_init_set_str(&groupgenerator, MODP_GENERATOR, 10) != 0 + || mpz_init_set_str(&modp1024_modulus, MODP1024_MODULUS, 16) != 0 + || mpz_init_set_str(&modp1536_modulus, MODP1536_MODULUS, 16) != 0 + || mpz_init_set_str(&modp2048_modulus, MODP2048_MODULUS, 16) != 0 + || mpz_init_set_str(&modp3072_modulus, MODP3072_MODULUS, 16) != 0 + || mpz_init_set_str(&modp4096_modulus, MODP4096_MODULUS, 16) != 0 + || mpz_init_set_str(&modp6144_modulus, MODP6144_MODULUS, 16) != 0 + || mpz_init_set_str(&modp8192_modulus, MODP8192_MODULUS, 16) != 0) + exit_log("mpz_init_set_str() failed in init_crypto()"); + + ike_alg_add((struct ike_alg *) &crypto_encryptor_3des); + ike_alg_add((struct ike_alg *) &crypto_hasher_sha1); + ike_alg_add((struct ike_alg *) &crypto_hasher_md5); + ike_alg_init(); + ike_alg_test(); +} + +/* Oakley group description + * + * See RFC2409 "The Internet key exchange (IKE)" 6. + */ + +const struct oakley_group_desc unset_group = {0, NULL, 0}; /* magic signifier */ + +const struct oakley_group_desc oakley_group[OAKLEY_GROUP_SIZE] = { +# define BYTES(bits) (((bits) + BITS_PER_BYTE - 1) / BITS_PER_BYTE) + { OAKLEY_GROUP_MODP1024, &modp1024_modulus, BYTES(1024) }, + { OAKLEY_GROUP_MODP1536, &modp1536_modulus, BYTES(1536) }, + { OAKLEY_GROUP_MODP2048, &modp2048_modulus, BYTES(2048) }, + { OAKLEY_GROUP_MODP3072, &modp3072_modulus, BYTES(3072) }, + { OAKLEY_GROUP_MODP4096, &modp4096_modulus, BYTES(4096) }, + { OAKLEY_GROUP_MODP6144, &modp6144_modulus, BYTES(6144) }, + { OAKLEY_GROUP_MODP8192, &modp8192_modulus, BYTES(8192) }, +# undef BYTES +}; + +const struct oakley_group_desc * +lookup_group(u_int16_t group) +{ + int i; + + for (i = 0; i != elemsof(oakley_group); i++) + if (group == oakley_group[i].group) + return &oakley_group[i]; + return NULL; +} + +/* Encryption Routines + * + * Each uses and updates the state object's st_new_iv. + * This must already be initialized. + */ + +/* encrypt or decrypt part of an IKE message using DES + * See RFC 2409 "IKE" Appendix B + */ +static void __attribute__ ((unused)) +do_des(bool enc, void *buf, size_t buf_len, struct state *st) +{ + des_key_schedule ks; + + (void) des_set_key((des_cblock *)st->st_enc_key.ptr, ks); + + passert(st->st_new_iv_len >= DES_CBC_BLOCK_SIZE); + st->st_new_iv_len = DES_CBC_BLOCK_SIZE; /* truncate */ + + des_ncbc_encrypt((des_cblock *)buf, (des_cblock *)buf, buf_len, + ks, + (des_cblock *)st->st_new_iv, enc); +} + +/* encrypt or decrypt part of an IKE message using 3DES + * See RFC 2409 "IKE" Appendix B + */ +static void +do_3des(u_int8_t *buf, size_t buf_len, u_int8_t *key, size_t key_size, u_int8_t *iv, bool enc) +{ + des_key_schedule ks[3]; + + passert (!key_size || (key_size==(DES_CBC_BLOCK_SIZE * 3))) + (void) des_set_key((des_cblock *)key + 0, ks[0]); + (void) des_set_key((des_cblock *)key + 1, ks[1]); + (void) des_set_key((des_cblock *)key + 2, ks[2]); + + des_ede3_cbc_encrypt((des_cblock *)buf, (des_cblock *)buf, buf_len, + ks[0], ks[1], ks[2], + (des_cblock *)iv, enc); +} + +/* hash and prf routines */ +void +crypto_cbc_encrypt(const struct encrypt_desc *e, bool enc, u_int8_t *buf, size_t size, struct state *st) +{ + passert(st->st_new_iv_len >= e->enc_blocksize); + st->st_new_iv_len = e->enc_blocksize; /* truncate */ + + e->do_crypt(buf, size, st->st_enc_key.ptr, st->st_enc_key.len, st->st_new_iv, enc); + /* + e->set_key(&ctx, st->st_enc_key.ptr, st->st_enc_key.len); + e->cbc_crypt(&ctx, buf, size, st->st_new_iv, enc); + */ +} + +/* HMAC package + * rfc2104.txt specifies how HMAC works. + */ + +void +hmac_init(struct hmac_ctx *ctx, + const struct hash_desc *h, + const u_char *key, size_t key_len) +{ + int k; + + ctx->h = h; + ctx->hmac_digest_size = h->hash_digest_size; + + /* Prepare the two pads for the HMAC */ + + memset(ctx->buf1, '\0', h->hash_block_size); + + if (key_len <= h->hash_block_size) + { + memcpy(ctx->buf1, key, key_len); + } + else + { + h->hash_init(&ctx->hash_ctx); + h->hash_update(&ctx->hash_ctx, key, key_len); + h->hash_final(ctx->buf1, &ctx->hash_ctx); + } + + memcpy(ctx->buf2, ctx->buf1, h->hash_block_size); + + for (k = 0; k < h->hash_block_size; k++) + { + ctx->buf1[k] ^= HMAC_IPAD; + ctx->buf2[k] ^= HMAC_OPAD; + } + + hmac_reinit(ctx); +} + +void +hmac_reinit(struct hmac_ctx *ctx) +{ + ctx->h->hash_init(&ctx->hash_ctx); + ctx->h->hash_update(&ctx->hash_ctx, ctx->buf1, ctx->h->hash_block_size); +} + +void +hmac_update(struct hmac_ctx *ctx, + const u_char *data, size_t data_len) +{ + ctx->h->hash_update(&ctx->hash_ctx, data, data_len); +} + +void +hmac_final(u_char *output, struct hmac_ctx *ctx) +{ + const struct hash_desc *h = ctx->h; + + h->hash_final(output, &ctx->hash_ctx); + + h->hash_init(&ctx->hash_ctx); + h->hash_update(&ctx->hash_ctx, ctx->buf2, h->hash_block_size); + h->hash_update(&ctx->hash_ctx, output, h->hash_digest_size); + h->hash_final(output, &ctx->hash_ctx); +} diff --git a/src/pluto/crypto.h b/src/pluto/crypto.h new file mode 100644 index 000000000..48c983349 --- /dev/null +++ b/src/pluto/crypto.h @@ -0,0 +1,108 @@ +/* crypto interfaces + * Copyright (C) 1998, 1999 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: crypto.h,v 1.6 2005/04/07 20:13:30 as Exp $ + */ + +#include <gmp.h> /* GNU MP library */ + +#include "libsha2/sha2.h" +#include "ike_alg.h" + +extern void init_crypto(void); + +/* Oakley group descriptions */ + +extern MP_INT groupgenerator; /* MODP group generator (2) */ + +struct oakley_group_desc { + u_int16_t group; + MP_INT *modulus; + size_t bytes; +}; + +extern const struct oakley_group_desc unset_group; /* magic signifier */ +extern const struct oakley_group_desc *lookup_group(u_int16_t group); +#define OAKLEY_GROUP_SIZE 7 +extern const struct oakley_group_desc oakley_group[OAKLEY_GROUP_SIZE]; + +/* unification of cryptographic encoding/decoding algorithms + * The IV is taken from and returned to st->st_new_iv. + * This allows the old IV to be retained. + * Use update_iv to commit to the new IV (for example, once a packet has + * been validated). + */ + +#define MAX_OAKLEY_KEY_LEN0 (3 * DES_CBC_BLOCK_SIZE) +#define MAX_OAKLEY_KEY_LEN (256/BITS_PER_BYTE) + +struct state; /* forward declaration, dammit */ + +void crypto_cbc_encrypt(const struct encrypt_desc *e, bool enc, u_int8_t *buf, size_t size, struct state *st); + +#define update_iv(st) memcpy((st)->st_iv, (st)->st_new_iv \ + , (st)->st_iv_len = (st)->st_new_iv_len) + +#define set_ph1_iv(st, iv) \ + passert((st)->st_ph1_iv_len <= sizeof((st)->st_ph1_iv)); \ + memcpy((st)->st_ph1_iv, (iv), (st)->st_ph1_iv_len); + +/* unification of cryptographic hashing mechanisms */ + +#ifndef NO_HASH_CTX +union hash_ctx { + MD5_CTX ctx_md5; + SHA1_CTX ctx_sha1; + sha256_context ctx_sha256; + sha512_context ctx_sha512; + }; + +/* HMAC package + * Note that hmac_ctx can be (and is) copied since there are + * no persistent pointers into it. + */ + +struct hmac_ctx { + const struct hash_desc *h; /* underlying hash function */ + size_t hmac_digest_size; /* copy of h->hash_digest_size */ + union hash_ctx hash_ctx; /* ctx for hash function */ + u_char buf1[MAX_HASH_BLOCK_SIZE]; + u_char buf2[MAX_HASH_BLOCK_SIZE]; + }; + +extern void hmac_init( + struct hmac_ctx *ctx, + const struct hash_desc *h, + const u_char *key, + size_t key_len); + +#define hmac_init_chunk(ctx, h, ch) hmac_init((ctx), (h), (ch).ptr, (ch).len) + +extern void hmac_reinit(struct hmac_ctx *ctx); /* saves recreating pads */ + +extern void hmac_update( + struct hmac_ctx *ctx, + const u_char *data, + size_t data_len); + +#define hmac_update_chunk(ctx, ch) hmac_update((ctx), (ch).ptr, (ch).len) + +extern void hmac_final(u_char *output, struct hmac_ctx *ctx); + +#define hmac_final_chunk(ch, name, ctx) { \ + pfreeany((ch).ptr); \ + (ch).len = (ctx)->hmac_digest_size; \ + (ch).ptr = alloc_bytes((ch).len, name); \ + hmac_final((ch).ptr, (ctx)); \ + } +#endif diff --git a/src/pluto/db_ops.c b/src/pluto/db_ops.c new file mode 100644 index 000000000..bbcd7918f --- /dev/null +++ b/src/pluto/db_ops.c @@ -0,0 +1,439 @@ +/* Dynamic db (proposal, transforms, attributes) handling. + * Author: JuanJo Ciarlante <jjo-ipsec@mendoza.gov.ar> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: db_ops.c,v 1.4 2005/04/07 20:13:44 as Exp $ + */ + +/* + * The stratedy is to have (full contained) struct db_prop in db_context + * pointing to ONE dynamically sizable transform vector (trans0). + * Each transform stores attrib. in ONE dyn. sizable attribute vector (attrs0) + * in a "serialized" way (attributes storage is used in linear sequence for + * subsecuent transforms). + * + * Resizing for both trans0 and attrs0 is supported: + * - For trans0: quite simple, just allocate and copy trans. vector content + * also update trans_cur (by offset) + * - For attrs0: after allocating and copying attrs, I must rewrite each + * trans->attrs present in trans0; to achieve this, calculate + * attrs pointer offset (new minus old) and iterate over + * each transform "adding" this difference. + * also update attrs_cur (by offset) + * + * db_context structure: + * +---------------------+ + * | prop | + * | .protoid | + * | .trans | --+ + * | .trans_cnt | | + * +---------------------+ <-+ + * | trans0 | ----> { trans#1 | ... | trans#i | ... } + * +---------------------+ ^ + * | trans_cur | ----------------------' current transf. + * +---------------------+ + * | attrs0 | ----> { attr#1 | ... | attr#j | ... } + * +---------------------+ ^ + * | attrs_cur | ---------------------' current attr. + * +---------------------+ + * | max_trans,max_attrs | max_trans/attrs: number of elem. of each vector + * +---------------------+ + * + * See testing examples at end for interface usage. + */ +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <malloc.h> +#include <sys/types.h> + +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "state.h" +#include "packet.h" +#include "spdb.h" +#include "db_ops.h" +#include "log.h" +#include "whack.h" + +#include <assert.h> + +#ifndef NO_PLUTO +#else +#define passert(x) assert(x) +extern int debug; /* eg: spi.c */ +#define DBG(cond, action) { if (debug) { action ; } } +#define DBG_log(x, args...) fprintf(stderr, x "\n" , ##args); +#define alloc_thing(thing, name) alloc_bytes(sizeof (thing), name) +void * alloc_bytes(size_t size, const char *name) { + void *p=malloc(size); + if (p == NULL) + fprintf(stderr, "unable to malloc %lu bytes for %s", + (unsigned long) size, name); + memset(p, '\0', size); + return p; +} +#define pfreeany(ptr) free(ptr) + +#endif + +#ifdef NOT_YET +/* + * Allocator cache: + * Because of the single-threaded nature of pluto/spdb.c, + * alloc()/free() is exercised many times with very small + * lifetime objects. + * Just caching last object (currently it will select the + * largest) will avoid this allocation mas^Wperturbations + * + */ +struct db_ops_alloc_cache { + void *ptr; + int size; +}; +#endif + +#ifndef NO_DB_OPS_STATS +/* + * stats: do account for allocations + * displayed in db_ops_show_status() + */ +struct db_ops_stats { + int st_curr_cnt; /* current number of allocations */ + int st_total_cnt; /* total allocations so far */ + size_t st_maxsz; /* max. size requested */ +}; +#define DB_OPS_ZERO { 0, 0, 0}; +#define DB_OPS_STATS_DESC "{curr_cnt, total_cnt, maxsz}" +#define DB_OPS_STATS_STR(name) name "={%d,%d,%d} " +#define DB_OPS_STATS_F(st) (st).st_curr_cnt, (st).st_total_cnt, (int)(st).st_maxsz +static struct db_ops_stats db_context_st = DB_OPS_ZERO; +static struct db_ops_stats db_trans_st = DB_OPS_ZERO; +static struct db_ops_stats db_attrs_st = DB_OPS_ZERO; +static __inline__ void * alloc_bytes_st (size_t size, const char *str, struct db_ops_stats *st) +{ + void *ptr = alloc_bytes(size, str); + if (ptr) { + st->st_curr_cnt++; + st->st_total_cnt++; + if (size > st->st_maxsz) st->st_maxsz=size; + } + return ptr; +} +#define ALLOC_BYTES_ST(z,s,st) alloc_bytes_st(z, s, &st); +#define PFREE_ST(p,st) do { st.st_curr_cnt--; pfree(p); } while (0); + +#else + +#define ALLOC_BYTES_ST(z,s,n) alloc_bytes(z, s); +#define PFREE_ST(p,n) pfree(p); + +#endif /* NO_DB_OPS_STATS */ +/* Initialize db object + * max_trans and max_attrs can be 0, will be dynamically expanded + * as a result of "add" operations + */ +int +db_prop_init(struct db_context *ctx, u_int8_t protoid, int max_trans, int max_attrs) +{ + int ret=-1; + + ctx->trans0 = NULL; + ctx->attrs0 = NULL; + + if (max_trans > 0) { /* quite silly if not */ + ctx->trans0 = ALLOC_BYTES_ST ( sizeof (struct db_trans) * max_trans, + "db_context->trans", db_trans_st); + if (!ctx->trans0) goto out; + } + + if (max_attrs > 0) { /* quite silly if not */ + ctx->attrs0 = ALLOC_BYTES_ST (sizeof (struct db_attr) * max_attrs, + "db_context->attrs", db_attrs_st); + if (!ctx->attrs0) goto out; + } + ret = 0; +out: + if (ret < 0 && ctx->trans0) { + PFREE_ST(ctx->trans0, db_trans_st); + ctx->trans0 = NULL; + } + ctx->max_trans = max_trans; + ctx->max_attrs = max_attrs; + ctx->trans_cur = ctx->trans0; + ctx->attrs_cur = ctx->attrs0; + ctx->prop.protoid = protoid; + ctx->prop.trans = ctx->trans0; + ctx->prop.trans_cnt = 0; + return ret; +} + +/* Expand storage for transforms by number delta_trans */ +static int +db_trans_expand(struct db_context *ctx, int delta_trans) +{ + int ret = -1; + struct db_trans *new_trans, *old_trans; + int max_trans = ctx->max_trans + delta_trans; + int offset; + + old_trans = ctx->trans0; + new_trans = ALLOC_BYTES_ST ( sizeof (struct db_trans) * max_trans, + "db_context->trans (expand)", db_trans_st); + if (!new_trans) + goto out; + memcpy(new_trans, old_trans, ctx->max_trans * sizeof(struct db_trans)); + + /* update trans0 (obviously) */ + ctx->trans0 = ctx->prop.trans = new_trans; + /* update trans_cur (by offset) */ + offset = (char *)(new_trans) - (char *)(old_trans); + + { + char *cctx = (char *)(ctx->trans_cur); + + cctx += offset; + ctx->trans_cur = (struct db_trans *)cctx; + } + /* update elem count */ + ctx->max_trans = max_trans; + PFREE_ST(old_trans, db_trans_st); + ret = 0; +out: + return ret; +} +/* + * Expand storage for attributes by delta_attrs number AND + * rewrite trans->attr pointers + */ +static int +db_attrs_expand(struct db_context *ctx, int delta_attrs) +{ + int ret = -1; + struct db_attr *new_attrs, *old_attrs; + struct db_trans *t; + int ti; + int max_attrs = ctx->max_attrs + delta_attrs; + int offset; + + old_attrs = ctx->attrs0; + new_attrs = ALLOC_BYTES_ST ( sizeof (struct db_attr) * max_attrs, + "db_context->attrs (expand)", db_attrs_st); + if (!new_attrs) + goto out; + + memcpy(new_attrs, old_attrs, ctx->max_attrs * sizeof(struct db_attr)); + + /* update attrs0 and attrs_cur (obviously) */ + offset = (char *)(new_attrs) - (char *)(old_attrs); + + { + char *actx = (char *)(ctx->attrs0); + + actx += offset; + ctx->attrs0 = (struct db_attr *)actx; + + actx = (char *)ctx->attrs_cur; + actx += offset; + ctx->attrs_cur = (struct db_attr *)actx; + } + + /* for each transform, rewrite attrs pointer by offsetting it */ + for (t=ctx->prop.trans, ti=0; ti < ctx->prop.trans_cnt; t++, ti++) { + char *actx = (char *)(t->attrs); + + actx += offset; + t->attrs = (struct db_attr *)actx; + } + /* update elem count */ + ctx->max_attrs = max_attrs; + PFREE_ST(old_attrs, db_attrs_st); + ret = 0; +out: + return ret; +} +/* Allocate a new db object */ +struct db_context * +db_prop_new(u_int8_t protoid, int max_trans, int max_attrs) +{ + struct db_context *ctx; + ctx = ALLOC_BYTES_ST ( sizeof (struct db_context), "db_context", db_context_st); + if (!ctx) goto out; + + if (db_prop_init(ctx, protoid, max_trans, max_attrs) < 0) { + PFREE_ST(ctx, db_context_st); + ctx=NULL; + } +out: + return ctx; +} +/* Free a db object */ +void +db_destroy(struct db_context *ctx) +{ + if (ctx->trans0) PFREE_ST(ctx->trans0, db_trans_st); + if (ctx->attrs0) PFREE_ST(ctx->attrs0, db_attrs_st); + PFREE_ST(ctx, db_context_st); +} +/* Start a new transform, expand trans0 is needed */ +int +db_trans_add(struct db_context *ctx, u_int8_t transid) +{ + /* skip incrementing current trans pointer the 1st time*/ + if (ctx->trans_cur && ctx->trans_cur->attr_cnt) + ctx->trans_cur++; + /* + * Strategy: if more space is needed, expand by + * <current_size>/2 + 1 + * + * This happens to produce a "reasonable" sequence + * after few allocations, eg.: + * 0,1,2,4,8,13,20,31,47 + */ + if ((ctx->trans_cur - ctx->trans0) >= ctx->max_trans) { + /* XXX:jjo if fails should shout and flag it */ + if (db_trans_expand(ctx, ctx->max_trans/2 + 1)<0) + return -1; + } + ctx->trans_cur->transid = transid; + ctx->trans_cur->attrs=ctx->attrs_cur; + ctx->trans_cur->attr_cnt = 0; + ctx->prop.trans_cnt++; + return 0; +} +/* Add attr copy to current transform, expanding attrs0 if needed */ +int +db_attr_add(struct db_context *ctx, const struct db_attr *a) +{ + /* + * Strategy: if more space is needed, expand by + * <current_size>/2 + 1 + */ + if ((ctx->attrs_cur - ctx->attrs0) >= ctx->max_attrs) { + /* XXX:jjo if fails should shout and flag it */ + if (db_attrs_expand(ctx, ctx->max_attrs/2 + 1) < 0) + return -1; + } + *ctx->attrs_cur++=*a; + ctx->trans_cur->attr_cnt++; + return 0; +} +/* Add attr copy (by value) to current transform, + * expanding attrs0 if needed, just calls db_attr_add(). + */ +int +db_attr_add_values(struct db_context *ctx, u_int16_t type, u_int16_t val) +{ + struct db_attr attr; + attr.type = type; + attr.val = val; + return db_attr_add (ctx, &attr); +} +#ifndef NO_DB_OPS_STATS +int +db_ops_show_status(void) +{ + whack_log(RC_COMMENT, "stats " __FILE__ ": " + DB_OPS_STATS_DESC " :" + DB_OPS_STATS_STR("context") + DB_OPS_STATS_STR("trans") + DB_OPS_STATS_STR("attrs"), + DB_OPS_STATS_F(db_context_st), + DB_OPS_STATS_F(db_trans_st), + DB_OPS_STATS_F(db_attrs_st) + ); + return 0; +} +#endif /* NO_DB_OPS_STATS */ +/* + * From below to end just testing stuff .... + */ +#ifdef TEST +static void db_prop_print(struct db_prop *p) +{ + struct db_trans *t; + struct db_attr *a; + int ti, ai; + enum_names *n, *n_at, *n_av; + printf("protoid=\"%s\"\n", enum_name(&protocol_names, p->protoid)); + for (ti=0, t=p->trans; ti< p->trans_cnt; ti++, t++) { + switch( t->transid) { + case PROTO_ISAKMP: + n=&isakmp_transformid_names;break; + case PROTO_IPSEC_ESP: + n=&esp_transformid_names;break; + default: + continue; + } + printf(" transid=\"%s\"\n", + enum_name(n, t->transid)); + for (ai=0, a=t->attrs; ai < t->attr_cnt; ai++, a++) { + int i; + switch( t->transid) { + case PROTO_ISAKMP: + n_at=&oakley_attr_names; + i=a->type|ISAKMP_ATTR_AF_TV; + n_av=oakley_attr_val_descs[(i)&ISAKMP_ATTR_RTYPE_MASK]; + break; + case PROTO_IPSEC_ESP: + n_at=&ipsec_attr_names; + i=a->type|ISAKMP_ATTR_AF_TV; + n_av=ipsec_attr_val_descs[(i)&ISAKMP_ATTR_RTYPE_MASK]; + break; + default: + continue; + } + printf(" type=\"%s\" value=\"%s\"\n", + enum_name(n_at, i), + enum_name(n_av, a->val)); + } + } + +} +static void db_print(struct db_context *ctx) +{ + printf("trans_cur diff=%d, attrs_cur diff=%d\n", + ctx->trans_cur - ctx->trans0, + ctx->attrs_cur - ctx->attrs0); + db_prop_print(&ctx->prop); +} + +void +passert_fail(const char *pred_str, const char *file_str, unsigned long line_no); +void abort(void); +void +passert_fail(const char *pred_str, const char *file_str, unsigned long line_no) +{ + fprintf(stderr, "ASSERTION FAILED at %s:%lu: %s", file_str, line_no, pred_str); + abort(); /* exiting correctly doesn't always work */ +} +int main(void) { + struct db_context *ctx=db_prop_new(PROTO_ISAKMP, 0, 0); + db_trans_add(ctx, KEY_IKE); + db_attr_add_values(ctx, OAKLEY_ENCRYPTION_ALGORITHM, OAKLEY_3DES_CBC); + db_attr_add_values(ctx, OAKLEY_HASH_ALGORITHM, OAKLEY_MD5); + db_attr_add_values(ctx, OAKLEY_AUTHENTICATION_METHOD, OAKLEY_RSA_SIG); + db_attr_add_values(ctx, OAKLEY_GROUP_DESCRIPTION, OAKLEY_GROUP_MODP1024); + db_trans_add(ctx, KEY_IKE); + db_attr_add_values(ctx, OAKLEY_ENCRYPTION_ALGORITHM, OAKLEY_AES_CBC); + db_attr_add_values(ctx, OAKLEY_HASH_ALGORITHM, OAKLEY_MD5); + db_attr_add_values(ctx, OAKLEY_AUTHENTICATION_METHOD, OAKLEY_PRESHARED_KEY); + db_attr_add_values(ctx, OAKLEY_GROUP_DESCRIPTION, OAKLEY_GROUP_MODP1536); + db_trans_add(ctx, ESP_3DES); + db_attr_add_values(ctx, AUTH_ALGORITHM, AUTH_ALGORITHM_HMAC_SHA1); + db_print(ctx); + db_destroy(ctx); + return 0; +} +#endif diff --git a/src/pluto/db_ops.h b/src/pluto/db_ops.h new file mode 100644 index 000000000..433e75280 --- /dev/null +++ b/src/pluto/db_ops.h @@ -0,0 +1,56 @@ +/* Dynamic db (proposal, transforms, attributes) handling. + * Author: JuanJo Ciarlante <jjo-ipsec@mendoza.gov.ar> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: db_ops.h,v 1.3 2004/09/17 12:37:37 as Exp $ + */ + +#ifndef _DB_OPS_H +#define _DB_OPS_H + +/* + * Main db object, (quite proposal "oriented") + */ +#ifndef NO_DB_CONTEXT +struct db_context { + struct db_prop prop; /* proposal buffer (not pointer) */ + struct db_trans *trans0; /* transf. list, dynamically sized */ + struct db_trans *trans_cur; /* current transform ptr */ + struct db_attr *attrs0; /* attr. list, dynamically sized */ + struct db_attr *attrs_cur; /* current attribute ptr */ + int max_trans; /* size of trans list */ + int max_attrs; /* size of attrs list */ +}; +/* + * Allocate a new db object + */ +struct db_context * db_prop_new(u_int8_t protoid, int max_trans, int max_attrs); +/* Initialize object for proposal building */ +int db_prop_init(struct db_context *ctx, u_int8_t protoid, int max_trans, int max_attrs); +/* Free all resourses for this db */ +void db_destroy(struct db_context *ctx); + +/* Start a new transform */ +int db_trans_add(struct db_context *ctx, u_int8_t transid); +/* Add a new attribute by copying db_attr content */ +int db_attr_add(struct db_context *db_ctx, const struct db_attr *attr); +/* Add a new attribute by value */ +int db_attr_add_values(struct db_context *ctx, u_int16_t type, u_int16_t val); + +/* Get proposal from db object */ +static __inline__ struct db_prop *db_prop_get(struct db_context *ctx) { + return &ctx->prop; +} +/* Show stats (allocation, etc) */ +#endif /* NO_DB_CONTEXT */ +int db_ops_show_status(void); +#endif /* _DB_OPS_H */ diff --git a/src/pluto/defs.c b/src/pluto/defs.c new file mode 100644 index 000000000..9ae32a480 --- /dev/null +++ b/src/pluto/defs.c @@ -0,0 +1,374 @@ +/* misc. universal things + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: defs.c,v 1.9 2006/01/04 21:00:43 as Exp $ + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <dirent.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "whack.h" /* for RC_LOG_SERIOUS */ + +const chunk_t empty_chunk = { NULL, 0 }; + +bool +all_zero(const unsigned char *m, size_t len) +{ + size_t i; + + for (i = 0; i != len; i++) + if (m[i] != '\0') + return FALSE; + return TRUE; +} + +/* memory allocation + * + * LEAK_DETECTIVE puts a wrapper around each allocation and maintains + * a list of live ones. If a dead one is freed, an assertion MIGHT fail. + * If the live list is currupted, that will often be detected. + * In the end, report_leaks() is called, and the names of remaining + * live allocations are printed. At the moment, it is hoped, not that + * the list is empty, but that there will be no surprises. + * + * Accepted Leaks: + * - "struct iface" and "device name" (for "discovered" net interfaces) + * - "struct event in event_schedule()" (events not associated with states) + * - "Pluto lock name" (one only, needed until end -- why bother?) + */ + +#ifdef LEAK_DETECTIVE + +/* this magic number is 3671129837 decimal (623837458 complemented) */ +#define LEAK_MAGIC 0xDAD0FEEDul + +union mhdr { + struct { + const char *name; + union mhdr *older, *newer; + unsigned long magic; + } i; /* info */ + unsigned long junk; /* force maximal alignment */ +}; + +static union mhdr *allocs = NULL; + +void *alloc_bytes(size_t size, const char *name) +{ + union mhdr *p = malloc(sizeof(union mhdr) + size); + + if (p == NULL) + exit_log("unable to malloc %lu bytes for %s" + , (unsigned long) size, name); + p->i.name = name; + p->i.older = allocs; + if (allocs != NULL) + allocs->i.newer = p; + allocs = p; + p->i.newer = NULL; + p->i.magic = LEAK_MAGIC; + + memset(p+1, '\0', size); + return p+1; +} + +void * +clone_bytes(const void *orig, size_t size, const char *name) +{ + void *p = alloc_bytes(size, name); + + memcpy(p, orig, size); + return p; +} + +void +pfree(void *ptr) +{ + union mhdr *p; + + passert(ptr != NULL); + p = ((union mhdr *)ptr) - 1; + passert(p->i.magic == LEAK_MAGIC); + if (p->i.older != NULL) + { + passert(p->i.older->i.newer == p); + p->i.older->i.newer = p->i.newer; + } + if (p->i.newer == NULL) + { + passert(p == allocs); + allocs = p->i.older; + } + else + { + passert(p->i.newer->i.older == p); + p->i.newer->i.older = p->i.older; + } + p->i.magic = ~LEAK_MAGIC; + free(p); +} + +void +report_leaks(void) +{ + union mhdr + *p = allocs, + *pprev = NULL; + unsigned long n = 0; + + while (p != NULL) + { + passert(p->i.magic == LEAK_MAGIC); + passert(pprev == p->i.newer); + pprev = p; + p = p->i.older; + n++; + if (p == NULL || pprev->i.name != p->i.name) + { + if (n != 1) + plog("leak: %lu * %s", n, pprev->i.name); + else + plog("leak: %s", pprev->i.name); + n = 0; + } + } +} + +#else /* !LEAK_DETECTIVE */ + +void *alloc_bytes(size_t size, const char *name) +{ + void *p = malloc(size); + + if (p == NULL) + exit_log("unable to malloc %lu bytes for %s" + , (unsigned long) size, name); + memset(p, '\0', size); + return p; +} + +void *clone_bytes(const void *orig, size_t size, const char *name) +{ + void *p = malloc(size); + + if (p == NULL) + exit_log("unable to malloc %lu bytes for %s" + , (unsigned long) size, name); + memcpy(p, orig, size); + return p; +} +#endif /* !LEAK_DETECTIVE */ + +/* Note that there may be as many as six IDs that are temporary at + * one time before unsharing the two ends of a connection. So we need + * at least six temporary buffers for DER_ASN1_DN IDs. + * We rotate them. Be careful! + */ +#define MAX_BUF 10 + +char* +temporary_cyclic_buffer(void) +{ + static char buf[MAX_BUF][BUF_LEN]; /* MAX_BUF internal buffers */ + static int counter = 0; /* cyclic counter */ + + if (++counter == MAX_BUF) counter = 0; /* next internal buffer */ + return buf[counter]; /* assign temporary buffer */ +} + +/* concatenates two sub paths into a string with a maximum size of BUF_LEN + * use for temporary storage only + */ +const char* +concatenate_paths(const char *a, const char *b) +{ + char *c; + + if (*b == '/' || *b == '.') + return b; + + c = temporary_cyclic_buffer(); + snprintf(c, BUF_LEN, "%s/%s", a, b); + return c; +} + +/* compare two chunks, returns zero if a equals b + * negative/positive if a is earlier/later in the alphabet than b + */ +int +cmp_chunk(chunk_t a, chunk_t b) +{ + int cmp_len, len, cmp_value; + + cmp_len = a.len - b.len; + len = (cmp_len < 0)? a.len : b.len; + cmp_value = memcmp(a.ptr, b.ptr, len); + + return (cmp_value == 0)? cmp_len : cmp_value; +}; + +/* moves a chunk to a memory position, chunk is freed afterwards + * position pointer is advanced after the insertion point + */ +void +mv_chunk(u_char **pos, chunk_t content) +{ + if (content.len > 0) + { + chunkcpy(*pos, content); + freeanychunk(content); + } +} + +/* + * write the binary contents of a chunk_t to a file + */ +bool +write_chunk(const char *filename, const char *label, chunk_t ch +, mode_t mask, bool force) +{ + mode_t oldmask; + FILE *fd; + + if (!force) + { + fd = fopen(filename, "r"); + if (fd) + { + fclose(fd); + plog(" %s file '%s' already exists", label, filename); + return FALSE; + } + } + + /* set umask */ + oldmask = umask(mask); + + fd = fopen(filename, "w"); + + if (fd) + { + fwrite(ch.ptr, sizeof(u_char), ch.len, fd); + fclose(fd); + plog(" written %s file '%s' (%d bytes)", label, filename, (int)ch.len); + umask(oldmask); + return TRUE; + } + else + { + plog(" could not open %s file '%s' for writing", label, filename); + umask(oldmask); + return FALSE; + } +} + +/* Names of the months */ + +static const char* months[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + + +/* + * Display a date either in local or UTC time + */ +char* +timetoa(const time_t *time, bool utc) +{ + static char buf[TIMETOA_BUF]; + + if (*time == UNDEFINED_TIME) + sprintf(buf, "--- -- --:--:--%s----", (utc)?" UTC ":" "); + else + { + struct tm *t = (utc)? gmtime(time) : localtime(time); + + sprintf(buf, "%s %02d %02d:%02d:%02d%s%04d", + months[t->tm_mon], t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, + (utc)?" UTC ":" ", t->tm_year + 1900 + ); + } + return buf; +} + +/* checks if the expiration date has been reached and + * warns during the warning_interval of the imminent + * expiry. strict=TRUE declares a fatal error, + * strict=FALSE issues a warning upon expiry. + */ +const char* +check_expiry(time_t expiration_date, int warning_interval, bool strict) +{ + time_t now; + int time_left; + + if (expiration_date == UNDEFINED_TIME) + return "ok (expires never)"; + + /* determine the current time */ + time(&now); + + time_left = (expiration_date - now); + if (time_left < 0) + return strict? "fatal (expired)" : "warning (expired)"; + + if (time_left > 86400*warning_interval) + return "ok"; + { + static char buf[35]; /* temporary storage */ + const char* unit = "second"; + + if (time_left > 172800) + { + time_left /= 86400; + unit = "day"; + } + else if (time_left > 7200) + { + time_left /= 3600; + unit = "hour"; + } + else if (time_left > 120) + { + time_left /= 60; + unit = "minute"; + } + snprintf(buf, 35, "warning (expires in %d %s%s)", time_left, + unit, (time_left == 1)?"":"s"); + return buf; + } +} + + +/* + * Filter eliminating the directory entries '.' and '..' + */ +int +file_select(const struct dirent *entry) +{ + return strcmp(entry->d_name, "." ) && + strcmp(entry->d_name, ".."); +} + + diff --git a/src/pluto/defs.h b/src/pluto/defs.h new file mode 100644 index 000000000..3bfb29a22 --- /dev/null +++ b/src/pluto/defs.h @@ -0,0 +1,145 @@ +/* misc. universal things + * Copyright (C) 1997 Angelos D. Keromytis. + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: defs.h,v 1.10 2006/01/04 21:00:43 as Exp $ + */ + +#ifndef _DEFS_H +#define _DEFS_H + +#include <sys/types.h> + +#ifdef KLIPS +# define USED_BY_KLIPS /* ignore */ +#else +# define USED_BY_KLIPS UNUSED +#endif + +#ifdef DEBUG +# define USED_BY_DEBUG /* ignore */ +#else +# define USED_BY_DEBUG UNUSED +#endif + +/* Length of temporary buffers */ + +#define BUF_LEN 512 + +/* type of serial number of a state object + * Needed in connections.h and state.h; here to simplify dependencies. + */ +typedef unsigned long so_serial_t; +#define SOS_NOBODY 0 /* null serial number */ +#define SOS_FIRST 1 /* first normal serial number */ + +/* memory allocation */ + +extern void *alloc_bytes(size_t size, const char *name); +#define alloc_thing(thing, name) (alloc_bytes(sizeof(thing), (name))) + +extern void *clone_bytes(const void *orig, size_t size, const char *name); +#define clone_thing(orig, name) clone_bytes((const void *)&(orig), sizeof(orig), (name)) +#define clone_str(str, name) \ + ((str) == NULL? NULL : clone_bytes((str), strlen((str))+1, (name))) + +#ifdef LEAK_DETECTIVE + extern void pfree(void *ptr); + extern void report_leaks(void); +#else +# define pfree(ptr) free(ptr) /* ordinary stdc free */ +#endif +#define pfreeany(p) { if ((p) != NULL) pfree(p); } +#define replace(p, q) { pfreeany(p); (p) = (q); } + + +/* chunk is a simple pointer-and-size abstraction */ + +struct chunk { + u_char *ptr; + size_t len; + }; +typedef struct chunk chunk_t; + +#define setchunk(ch, addr, size) { (ch).ptr = (addr); (ch).len = (size); } +#define strchunk(str) { str, sizeof(str) } +/* NOTE: freeanychunk, unlike pfreeany, NULLs .ptr */ +#define freeanychunk(ch) { pfreeany((ch).ptr); (ch).ptr = NULL; } +#define clonetochunk(ch, addr, size, name) \ + { (ch).ptr = clone_bytes((addr), (ch).len = (size), name); } +#define clonereplacechunk(ch, addr, size, name) \ + { pfreeany((ch).ptr); clonetochunk(ch, addr, size, name); } +#define chunkcpy(dst, chunk) \ + { memcpy(dst, chunk.ptr, chunk.len); dst += chunk.len;} +#define same_chunk(a, b) \ + ( (a).len == (b).len && memcmp((a).ptr, (b).ptr, (b).len) == 0 ) + +extern char* temporary_cyclic_buffer(void); +extern const char* concatenate_paths(const char *a, const char *b); + +extern const chunk_t empty_chunk; + +/* compare two chunks */ +extern int cmp_chunk(chunk_t a, chunk_t b); + +/* move a chunk to a memory position and free it after insertion */ +extern void mv_chunk(u_char **pos, chunk_t content); + +/* write the binary contents of a chunk_t to a file */ +extern bool write_chunk(const char *filename, const char *label, chunk_t ch + ,mode_t mask, bool force); + +/* display a date either in local or UTC time */ +extern char* timetoa(const time_t *time, bool utc); + +/* warns a predefined interval before expiry */ +extern const char* check_expiry(time_t expiration_date, + int warning_interval, bool strict); + +#define MAX_PROMPT_PASS_TRIALS 5 +#define PROMPT_PASS_LEN 64 + +/* struct used to prompt for a secret passphrase + * from a console with file descriptor fd + */ +typedef struct { + char secret[PROMPT_PASS_LEN+1]; + bool prompt; + int fd; +} prompt_pass_t; + +/* no time defined in time_t */ +#define UNDEFINED_TIME 0 + +/* size of timetoa string buffer */ +#define TIMETOA_BUF 30 + +/* filter eliminating the directory entries '.' and '..' */ +typedef struct dirent dirent_t; +extern int file_select(const dirent_t *entry); + +/* cleanly exit Pluto */ + +extern void exit_pluto(int /*status*/) NEVER_RETURNS; + + +/* zero all bytes */ +#define zero(x) memset((x), '\0', sizeof(*(x))) + +/* are all bytes 0? */ +extern bool all_zero(const unsigned char *m, size_t len); + +/* pad_up(n, m) is the amount to add to n to make it a multiple of m */ +#define pad_up(n, m) (((m) - 1) - (((n) + (m) - 1) % (m))) + +#endif /* _DEFS_H */ diff --git a/src/pluto/demux.c b/src/pluto/demux.c new file mode 100644 index 000000000..7e59b184d --- /dev/null +++ b/src/pluto/demux.c @@ -0,0 +1,2499 @@ +/* demultiplex incoming IKE messages + * Copyright (C) 1997 Angelos D. Keromytis. + * Copyright (C) 1998-2002 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: demux.c,v 1.14 2006/06/22 11:58:25 as Exp $ + */ + +/* Ordering Constraints on Payloads + * + * rfc2409: The Internet Key Exchange (IKE) + * + * 5 Exchanges: + * "The SA payload MUST precede all other payloads in a phase 1 exchange." + * + * "Except where otherwise noted, there are no requirements for ISAKMP + * payloads in any message to be in any particular order." + * + * 5.3 Phase 1 Authenticated With a Revised Mode of Public Key Encryption: + * + * "If the HASH payload is sent it MUST be the first payload of the + * second message exchange and MUST be followed by the encrypted + * nonce. If the HASH payload is not sent, the first payload of the + * second message exchange MUST be the encrypted nonce." + * + * "Save the requirements on the location of the optional HASH payload + * and the mandatory nonce payload there are no further payload + * requirements. All payloads-- in whatever order-- following the + * encrypted nonce MUST be encrypted with Ke_i or Ke_r depending on the + * direction." + * + * 5.5 Phase 2 - Quick Mode + * + * "In Quick Mode, a HASH payload MUST immediately follow the ISAKMP + * header and a SA payload MUST immediately follow the HASH." + * [NOTE: there may be more than one SA payload, so this is not + * totally reasonable. Probably all SAs should be so constrained.] + * + * "If ISAKMP is acting as a client negotiator on behalf of another + * party, the identities of the parties MUST be passed as IDci and + * then IDcr." + * + * "With the exception of the HASH, SA, and the optional ID payloads, + * there are no payload ordering restrictions on Quick Mode." + */ + +/* Unfolding of Identity -- a central mystery + * + * This concerns Phase 1 identities, those of the IKE hosts. + * These are the only ones that are authenticated. Phase 2 + * identities are for IPsec SAs. + * + * There are three case of interest: + * + * (1) We initiate, based on a whack command specifying a Connection. + * We know the identity of the peer from the Connection. + * + * (2) (to be implemented) we initiate based on a flow from our client + * to some IP address. + * We immediately know one of the peer's client IP addresses from + * the flow. We must use this to figure out the peer's IP address + * and Id. To be solved. + * + * (3) We respond to an IKE negotiation. + * We immediately know the peer's IP address. + * We get an ID Payload in Main I2. + * + * Unfortunately, this is too late for a number of things: + * - the ISAKMP SA proposals have already been made (Main I1) + * AND one accepted (Main R1) + * - the SA includes a specification of the type of ID + * authentication so this is negotiated without being told the ID. + * - with Preshared Key authentication, Main I2 is encrypted + * using the key, so it cannot be decoded to reveal the ID + * without knowing (or guessing) which key to use. + * + * There are three reasonable choices here for the responder: + * + assume that the initiator is making wise offers since it + * knows the IDs involved. We can balk later (but not gracefully) + * when we find the actual initiator ID + * + attempt to infer identity by IP address. Again, we can balk + * when the true identity is revealed. Actually, it is enough + * to infer properties of the identity (eg. SA properties and + * PSK, if needed). + * + make all properties universal so discrimination based on + * identity isn't required. For example, always accept the same + * kinds of encryption. Accept Public Key Id authentication + * since the Initiator presumably has our public key and thinks + * we must have / can find his. This approach is weakest + * for preshared key since the actual key must be known to + * decrypt the Initiator's ID Payload. + * These choices can be blended. For example, a class of Identities + * can be inferred, sufficient to select a preshared key but not + * sufficient to infer a unique identity. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/time.h> /* only used for belt-and-suspenders select call */ +#include <sys/poll.h> /* only used for forensic poll call */ +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/queue.h> + +#if defined(IP_RECVERR) && defined(MSG_ERRQUEUE) +# include <asm/types.h> /* for __u8, __u32 */ +# include <linux/errqueue.h> +# include <sys/uio.h> /* struct iovec */ +#endif + +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "cookie.h" +#include "connections.h" +#include "state.h" +#include "packet.h" +#include "md5.h" +#include "sha1.h" +#include "crypto.h" /* requires sha1.h and md5.h */ +#include "ike_alg.h" +#include "log.h" +#include "demux.h" /* needs packet.h */ +#include "ipsec_doi.h" /* needs demux.h and state.h */ +#include "timer.h" +#include "whack.h" /* requires connections.h */ +#include "server.h" +#include "nat_traversal.h" +#include "vendor.h" +#include "modecfg.h" + +/* This file does basic header checking and demux of + * incoming packets. + */ + +/* forward declarations */ +static bool read_packet(struct msg_digest *md); +static void process_packet(struct msg_digest **mdp); + +/* Reply messages are built in this buffer. + * Only one state transition function can be using it at a time + * so suspended STFs must save and restore it. + * It could be an auto variable of complete_state_transition except for the fact + * that when a suspended STF resumes, its reply message buffer + * must be at the same location -- there are pointers into it. + */ +u_int8_t reply_buffer[MAX_OUTPUT_UDP_SIZE]; + +/* state_microcode is a tuple of information parameterizing certain + * centralized processing of a packet. For example, it roughly + * specifies what payloads are expected in this message. + * The microcode is selected primarily based on the state. + * In Phase 1, the payload structure often depends on the + * authentication technique, so that too plays a part in selecting + * the state_microcode to use. + */ + +struct state_microcode { + enum state_kind state, next_state; + lset_t flags; + lset_t req_payloads; /* required payloads (allows just one) */ + lset_t opt_payloads; /* optional payloads (any mumber) */ + /* if not ISAKMP_NEXT_NONE, process_packet will emit HDR with this as np */ + u_int8_t first_out_payload; + enum event_type timeout_event; + state_transition_fn *processor; +}; + +/* State Microcode Flags, in several groups */ + +/* Oakley Auth values: to which auth values does this entry apply? + * Most entries will use SMF_ALL_AUTH because they apply to all. + * Note: SMF_ALL_AUTH matches 0 for those circumstances when no auth + * has been set. + */ +#define SMF_ALL_AUTH LRANGE(0, OAKLEY_AUTH_ROOF-1) +#define SMF_PSK_AUTH LELEM(OAKLEY_PRESHARED_KEY) +#define SMF_DS_AUTH (LELEM(OAKLEY_DSS_SIG) | LELEM(OAKLEY_RSA_SIG)) +#define SMF_PKE_AUTH (LELEM(OAKLEY_RSA_ENC) | LELEM(OAKLEY_ELGAMAL_ENC)) +#define SMF_RPKE_AUTH (LELEM(OAKLEY_RSA_ENC_REV) | LELEM(OAKLEY_ELGAMAL_ENC_REV)) + +/* misc flags */ + +#define SMF_INITIATOR LELEM(OAKLEY_AUTH_ROOF + 0) +#define SMF_FIRST_ENCRYPTED_INPUT LELEM(OAKLEY_AUTH_ROOF + 1) +#define SMF_INPUT_ENCRYPTED LELEM(OAKLEY_AUTH_ROOF + 2) +#define SMF_OUTPUT_ENCRYPTED LELEM(OAKLEY_AUTH_ROOF + 3) +#define SMF_RETRANSMIT_ON_DUPLICATE LELEM(OAKLEY_AUTH_ROOF + 4) + +#define SMF_ENCRYPTED (SMF_INPUT_ENCRYPTED | SMF_OUTPUT_ENCRYPTED) + +/* this state generates a reply message */ +#define SMF_REPLY LELEM(OAKLEY_AUTH_ROOF + 5) + +/* this state completes P1, so any pending P2 negotiations should start */ +#define SMF_RELEASE_PENDING_P2 LELEM(OAKLEY_AUTH_ROOF + 6) + +/* end of flags */ + + +static state_transition_fn /* forward declaration */ + unexpected, + informational; + +/* state_microcode_table is a table of all state_microcode tuples. + * It must be in order of state (the first element). + * After initialization, ike_microcode_index[s] points to the + * first entry in state_microcode_table for state s. + * Remember that each state name in Main or Quick Mode describes + * what has happened in the past, not what this message is. + */ + +static const struct state_microcode + *ike_microcode_index[STATE_IKE_ROOF - STATE_IKE_FLOOR]; + +static const struct state_microcode state_microcode_table[] = { +#define PT(n) ISAKMP_NEXT_##n +#define P(n) LELEM(PT(n)) + + /***** Phase 1 Main Mode *****/ + + /* No state for main_outI1: --> HDR, SA */ + + /* STATE_MAIN_R0: I1 --> R1 + * HDR, SA --> HDR, SA + */ + { STATE_MAIN_R0, STATE_MAIN_R1 + , SMF_ALL_AUTH | SMF_REPLY + , P(SA), P(VID) | P(CR), PT(NONE) + , EVENT_RETRANSMIT, main_inI1_outR1}, + + /* STATE_MAIN_I1: R1 --> I2 + * HDR, SA --> auth dependent + * SMF_PSK_AUTH, SMF_DS_AUTH: --> HDR, KE, Ni + * SMF_PKE_AUTH: + * --> HDR, KE, [ HASH(1), ] <IDi1_b>PubKey_r, <Ni_b>PubKey_r + * SMF_RPKE_AUTH: + * --> HDR, [ HASH(1), ] <Ni_b>Pubkey_r, <KE_b>Ke_i, <IDi1_b>Ke_i [,<<Cert-I_b>Ke_i] + * Note: since we don't know auth at start, we cannot differentiate + * microcode entries based on it. + */ + { STATE_MAIN_I1, STATE_MAIN_I2 + , SMF_ALL_AUTH | SMF_INITIATOR | SMF_REPLY + , P(SA), P(VID) | P(CR), PT(NONE) /* don't know yet */ + , EVENT_RETRANSMIT, main_inR1_outI2 }, + + /* STATE_MAIN_R1: I2 --> R2 + * SMF_PSK_AUTH, SMF_DS_AUTH: HDR, KE, Ni --> HDR, KE, Nr + * SMF_PKE_AUTH: HDR, KE, [ HASH(1), ] <IDi1_b>PubKey_r, <Ni_b>PubKey_r + * --> HDR, KE, <IDr1_b>PubKey_i, <Nr_b>PubKey_i + * SMF_RPKE_AUTH: + * HDR, [ HASH(1), ] <Ni_b>Pubkey_r, <KE_b>Ke_i, <IDi1_b>Ke_i [,<<Cert-I_b>Ke_i] + * --> HDR, <Nr_b>PubKey_i, <KE_b>Ke_r, <IDr1_b>Ke_r + */ + { STATE_MAIN_R1, STATE_MAIN_R2 + , SMF_PSK_AUTH | SMF_DS_AUTH | SMF_REPLY + , P(KE) | P(NONCE), P(VID) | P(CR) | P(NATD_RFC), PT(KE) + , EVENT_RETRANSMIT, main_inI2_outR2 }, + + { STATE_MAIN_R1, STATE_UNDEFINED + , SMF_PKE_AUTH | SMF_REPLY + , P(KE) | P(ID) | P(NONCE), P(VID) | P(CR) | P(HASH), PT(KE) + , EVENT_RETRANSMIT, unexpected /* ??? not yet implemented */ }, + + { STATE_MAIN_R1, STATE_UNDEFINED + , SMF_RPKE_AUTH | SMF_REPLY + , P(NONCE) | P(KE) | P(ID), P(VID) | P(CR) | P(HASH) | P(CERT), PT(NONCE) + , EVENT_RETRANSMIT, unexpected /* ??? not yet implemented */ }, + + /* for states from here on, output message must be encrypted */ + + /* STATE_MAIN_I2: R2 --> I3 + * SMF_PSK_AUTH: HDR, KE, Nr --> HDR*, IDi1, HASH_I + * SMF_DS_AUTH: HDR, KE, Nr --> HDR*, IDi1, [ CERT, ] SIG_I + * SMF_PKE_AUTH: HDR, KE, <IDr1_b>PubKey_i, <Nr_b>PubKey_i + * --> HDR*, HASH_I + * SMF_RPKE_AUTH: HDR, <Nr_b>PubKey_i, <KE_b>Ke_r, <IDr1_b>Ke_r + * --> HDR*, HASH_I + */ + { STATE_MAIN_I2, STATE_MAIN_I3 + , SMF_PSK_AUTH | SMF_DS_AUTH | SMF_INITIATOR | SMF_OUTPUT_ENCRYPTED | SMF_REPLY + , P(KE) | P(NONCE), P(VID) | P(CR) | P(NATD_RFC), PT(ID) + , EVENT_RETRANSMIT, main_inR2_outI3 }, + + { STATE_MAIN_I2, STATE_UNDEFINED + , SMF_PKE_AUTH | SMF_INITIATOR | SMF_OUTPUT_ENCRYPTED | SMF_REPLY + , P(KE) | P(ID) | P(NONCE), P(VID) | P(CR), PT(HASH) + , EVENT_RETRANSMIT, unexpected /* ??? not yet implemented */ }, + + { STATE_MAIN_I2, STATE_UNDEFINED + , SMF_ALL_AUTH | SMF_INITIATOR | SMF_OUTPUT_ENCRYPTED | SMF_REPLY + , P(NONCE) | P(KE) | P(ID), P(VID) | P(CR), PT(HASH) + , EVENT_RETRANSMIT, unexpected /* ??? not yet implemented */ }, + + /* for states from here on, input message must be encrypted */ + + /* STATE_MAIN_R2: I3 --> R3 + * SMF_PSK_AUTH: HDR*, IDi1, HASH_I --> HDR*, IDr1, HASH_R + * SMF_DS_AUTH: HDR*, IDi1, [ CERT, ] SIG_I --> HDR*, IDr1, [ CERT, ] SIG_R + * SMF_PKE_AUTH, SMF_RPKE_AUTH: HDR*, HASH_I --> HDR*, HASH_R + */ + { STATE_MAIN_R2, STATE_MAIN_R3 + , SMF_PSK_AUTH | SMF_FIRST_ENCRYPTED_INPUT | SMF_ENCRYPTED + | SMF_REPLY | SMF_RELEASE_PENDING_P2 + , P(ID) | P(HASH), P(VID) | P(CR), PT(NONE) + , EVENT_SA_REPLACE, main_inI3_outR3 }, + + { STATE_MAIN_R2, STATE_MAIN_R3 + , SMF_DS_AUTH | SMF_FIRST_ENCRYPTED_INPUT | SMF_ENCRYPTED + | SMF_REPLY | SMF_RELEASE_PENDING_P2 + , P(ID) | P(SIG), P(VID) | P(CR) | P(CERT), PT(NONE) + , EVENT_SA_REPLACE, main_inI3_outR3 }, + + { STATE_MAIN_R2, STATE_UNDEFINED + , SMF_PKE_AUTH | SMF_RPKE_AUTH | SMF_FIRST_ENCRYPTED_INPUT | SMF_ENCRYPTED + | SMF_REPLY | SMF_RELEASE_PENDING_P2 + , P(HASH), P(VID) | P(CR), PT(NONE) + , EVENT_SA_REPLACE, unexpected /* ??? not yet implemented */ }, + + /* STATE_MAIN_I3: R3 --> done + * SMF_PSK_AUTH: HDR*, IDr1, HASH_R --> done + * SMF_DS_AUTH: HDR*, IDr1, [ CERT, ] SIG_R --> done + * SMF_PKE_AUTH, SMF_RPKE_AUTH: HDR*, HASH_R --> done + * May initiate quick mode by calling quick_outI1 + */ + { STATE_MAIN_I3, STATE_MAIN_I4 + , SMF_PSK_AUTH | SMF_INITIATOR + | SMF_FIRST_ENCRYPTED_INPUT | SMF_ENCRYPTED | SMF_RELEASE_PENDING_P2 + , P(ID) | P(HASH), P(VID) | P(CR), PT(NONE) + , EVENT_SA_REPLACE, main_inR3 }, + + { STATE_MAIN_I3, STATE_MAIN_I4 + , SMF_DS_AUTH | SMF_INITIATOR + | SMF_FIRST_ENCRYPTED_INPUT | SMF_ENCRYPTED | SMF_RELEASE_PENDING_P2 + , P(ID) | P(SIG), P(VID) | P(CR) | P(CERT), PT(NONE) + , EVENT_SA_REPLACE, main_inR3 }, + + { STATE_MAIN_I3, STATE_UNDEFINED + , SMF_PKE_AUTH | SMF_RPKE_AUTH | SMF_INITIATOR + | SMF_FIRST_ENCRYPTED_INPUT | SMF_ENCRYPTED | SMF_RELEASE_PENDING_P2 + , P(HASH), P(VID) | P(CR), PT(NONE) + , EVENT_SA_REPLACE, unexpected /* ??? not yet implemented */ }, + + /* STATE_MAIN_R3: can only get here due to packet loss */ + { STATE_MAIN_R3, STATE_UNDEFINED + , SMF_ALL_AUTH | SMF_ENCRYPTED | SMF_RETRANSMIT_ON_DUPLICATE + , LEMPTY, LEMPTY + , PT(NONE), EVENT_NULL, unexpected }, + + /* STATE_MAIN_I4: can only get here due to packet loss */ + { STATE_MAIN_I4, STATE_UNDEFINED + , SMF_ALL_AUTH | SMF_INITIATOR | SMF_ENCRYPTED + , LEMPTY, LEMPTY + , PT(NONE), EVENT_NULL, unexpected }, + + + /***** Phase 2 Quick Mode *****/ + + /* No state for quick_outI1: + * --> HDR*, HASH(1), SA, Nr [, KE ] [, IDci, IDcr ] + */ + + /* STATE_QUICK_R0: + * HDR*, HASH(1), SA, Ni [, KE ] [, IDci, IDcr ] --> + * HDR*, HASH(2), SA, Nr [, KE ] [, IDci, IDcr ] + * Installs inbound IPsec SAs. + * Because it may suspend for asynchronous DNS, first_out_payload + * is set to NONE to suppress early emission of HDR*. + * ??? it is legal to have multiple SAs, but we don't support it yet. + */ + { STATE_QUICK_R0, STATE_QUICK_R1 + , SMF_ALL_AUTH | SMF_ENCRYPTED | SMF_REPLY + , P(HASH) | P(SA) | P(NONCE), /* P(SA) | */ P(KE) | P(ID) | P(NATOA_RFC), PT(NONE) + , EVENT_RETRANSMIT, quick_inI1_outR1 }, + + /* STATE_QUICK_I1: + * HDR*, HASH(2), SA, Nr [, KE ] [, IDci, IDcr ] --> + * HDR*, HASH(3) + * Installs inbound and outbound IPsec SAs, routing, etc. + * ??? it is legal to have multiple SAs, but we don't support it yet. + */ + { STATE_QUICK_I1, STATE_QUICK_I2 + , SMF_ALL_AUTH | SMF_INITIATOR | SMF_ENCRYPTED | SMF_REPLY + , P(HASH) | P(SA) | P(NONCE), /* P(SA) | */ P(KE) | P(ID) | P(NATOA_RFC), PT(HASH) + , EVENT_SA_REPLACE, quick_inR1_outI2 }, + + /* STATE_QUICK_R1: HDR*, HASH(3) --> done + * Installs outbound IPsec SAs, routing, etc. + */ + { STATE_QUICK_R1, STATE_QUICK_R2 + , SMF_ALL_AUTH | SMF_ENCRYPTED + , P(HASH), LEMPTY, PT(NONE) + , EVENT_SA_REPLACE, quick_inI2 }, + + /* STATE_QUICK_I2: can only happen due to lost packet */ + { STATE_QUICK_I2, STATE_UNDEFINED + , SMF_ALL_AUTH | SMF_INITIATOR | SMF_ENCRYPTED | SMF_RETRANSMIT_ON_DUPLICATE + , LEMPTY, LEMPTY, PT(NONE) + , EVENT_NULL, unexpected }, + + /* STATE_QUICK_R2: can only happen due to lost packet */ + { STATE_QUICK_R2, STATE_UNDEFINED + , SMF_ALL_AUTH | SMF_ENCRYPTED + , LEMPTY, LEMPTY, PT(NONE) + , EVENT_NULL, unexpected }, + + + /***** informational messages *****/ + + /* STATE_INFO: */ + { STATE_INFO, STATE_UNDEFINED + , SMF_ALL_AUTH + , LEMPTY, LEMPTY, PT(NONE) + , EVENT_NULL, informational }, + + /* STATE_INFO_PROTECTED: */ + { STATE_INFO_PROTECTED, STATE_UNDEFINED + , SMF_ALL_AUTH | SMF_ENCRYPTED + , P(HASH), LEMPTY, PT(NONE) + , EVENT_NULL, informational }, + + /* XAUTH state transitions */ + { STATE_XAUTH_I0, STATE_XAUTH_I1 + , SMF_ALL_AUTH | SMF_ENCRYPTED | SMF_REPLY + , P(ATTR) | P(HASH), P(VID), PT(HASH) + , EVENT_RETRANSMIT, xauth_inI0 }, + + { STATE_XAUTH_R1, STATE_XAUTH_R2 + , SMF_ALL_AUTH | SMF_ENCRYPTED + , P(ATTR) | P(HASH), P(VID), PT(HASH) + , EVENT_RETRANSMIT, xauth_inR1 }, + + { STATE_XAUTH_I1, STATE_XAUTH_I2 + , SMF_ALL_AUTH | SMF_ENCRYPTED | SMF_REPLY | SMF_RELEASE_PENDING_P2 + , P(ATTR) | P(HASH), P(VID), PT(HASH) + , EVENT_SA_REPLACE, xauth_inI1 }, + + { STATE_XAUTH_R2, STATE_XAUTH_R3 + , SMF_ALL_AUTH | SMF_ENCRYPTED | SMF_RELEASE_PENDING_P2 + , P(ATTR) | P(HASH), P(VID), PT(NONE) + , EVENT_SA_REPLACE, xauth_inR2 }, + + { STATE_XAUTH_I2, STATE_UNDEFINED + , SMF_ALL_AUTH | SMF_ENCRYPTED + , LEMPTY, LEMPTY, PT(NONE) + , EVENT_NULL, unexpected }, + + { STATE_XAUTH_R3, STATE_UNDEFINED + , SMF_ALL_AUTH | SMF_ENCRYPTED + , LEMPTY, LEMPTY, PT(NONE) + , EVENT_NULL, unexpected }, + + /* ModeCfg pull mode state transitions */ + + { STATE_MODE_CFG_R0, STATE_MODE_CFG_R1 + , SMF_ALL_AUTH | SMF_ENCRYPTED | SMF_REPLY | SMF_RELEASE_PENDING_P2 + , P(ATTR) | P(HASH), P(VID), PT(HASH) + , EVENT_SA_REPLACE, modecfg_inR0 }, + + { STATE_MODE_CFG_I1, STATE_MODE_CFG_I2 + , SMF_ALL_AUTH | SMF_ENCRYPTED | SMF_RELEASE_PENDING_P2 + , P(ATTR) | P(HASH), P(VID), PT(HASH) + , EVENT_SA_REPLACE, modecfg_inI1 }, + + { STATE_MODE_CFG_R1, STATE_UNDEFINED + , SMF_ALL_AUTH | SMF_ENCRYPTED + , LEMPTY, LEMPTY, PT(NONE) + , EVENT_NULL, unexpected }, + + { STATE_MODE_CFG_I2, STATE_UNDEFINED + , SMF_ALL_AUTH | SMF_ENCRYPTED + , LEMPTY, LEMPTY, PT(NONE) + , EVENT_NULL, unexpected }, + + /* ModeCfg push mode state transitions */ + + { STATE_MODE_CFG_I0, STATE_MODE_CFG_I3 + , SMF_ALL_AUTH | SMF_ENCRYPTED | SMF_REPLY | SMF_RELEASE_PENDING_P2 + , P(ATTR) | P(HASH), P(VID), PT(HASH) + , EVENT_SA_REPLACE, modecfg_inI0 }, + + { STATE_MODE_CFG_R3, STATE_MODE_CFG_R4 + , SMF_ALL_AUTH | SMF_ENCRYPTED | SMF_RELEASE_PENDING_P2 + , P(ATTR) | P(HASH), P(VID), PT(HASH) + , EVENT_SA_REPLACE, modecfg_inR3 }, + + { STATE_MODE_CFG_I3, STATE_UNDEFINED + , SMF_ALL_AUTH | SMF_ENCRYPTED + , LEMPTY, LEMPTY, PT(NONE) + , EVENT_NULL, unexpected }, + + { STATE_MODE_CFG_R4, STATE_UNDEFINED + , SMF_ALL_AUTH | SMF_ENCRYPTED + , LEMPTY, LEMPTY, PT(NONE) + , EVENT_NULL, unexpected }, + +#undef P +#undef PT +}; + +void +init_demux(void) +{ + /* fill ike_microcode_index: + * make ike_microcode_index[s] point to first entry in + * state_microcode_table for state s (backward scan makes this easier). + * Check that table is in order -- catch coding errors. + * For what it's worth, this routine is idempotent. + */ + const struct state_microcode *t; + + for (t = &state_microcode_table[elemsof(state_microcode_table) - 1];;) + { + passert(STATE_IKE_FLOOR <= t->state && t->state < STATE_IKE_ROOF); + ike_microcode_index[t->state - STATE_IKE_FLOOR] = t; + if (t == state_microcode_table) + break; + t--; + passert(t[0].state <= t[1].state); + } +} + +/* Process any message on the MSG_ERRQUEUE + * + * This information is generated because of the IP_RECVERR socket option. + * The API is sparsely documented, and may be LINUX-only, and only on + * fairly recent versions at that (hence the conditional compilation). + * + * - ip(7) describes IP_RECVERR + * - recvmsg(2) describes MSG_ERRQUEUE + * - readv(2) describes iovec + * - cmsg(3) describes how to process auxilliary messages + * + * ??? we should link this message with one we've sent + * so that the diagnostic can refer to that negotiation. + * + * ??? how long can the messge be? + * + * ??? poll(2) has a very incomplete description of the POLL* events. + * We assume that POLLIN, POLLOUT, and POLLERR are all we need to deal with + * and that POLLERR will be on iff there is a MSG_ERRQUEUE message. + * + * We have to code around a couple of surprises: + * + * - Select can say that a socket is ready to read from, and + * yet a read will hang. It turns out that a message available on the + * MSG_ERRQUEUE will cause select to say something is pending, but + * a normal read will hang. poll(2) can tell when a MSG_ERRQUEUE + * message is pending. + * + * This is dealt with by calling check_msg_errqueue after select + * has indicated that there is something to read, but before the + * read is performed. check_msg_errqueue will return TRUE if there + * is something left to read. + * + * - A write to a socket may fail because there is a pending MSG_ERRQUEUE + * message, without there being anything wrong with the write. This + * makes for confusing diagnostics. + * + * To avoid this, we call check_msg_errqueue before a write. True, + * there is a race condition (a MSG_ERRQUEUE message might arrive + * between the check and the write), but we should eliminate many + * of the problematic events. To narrow the window, the poll(2) + * will await until an event happens (in the case or a write, + * POLLOUT; this should be benign for POLLIN). + */ + +#if defined(IP_RECVERR) && defined(MSG_ERRQUEUE) +static bool +check_msg_errqueue(const struct iface *ifp, short interest) +{ + struct pollfd pfd; + + pfd.fd = ifp->fd; + pfd.events = interest | POLLPRI | POLLOUT; + + while (pfd.revents = 0 + , poll(&pfd, 1, -1) > 0 && (pfd.revents & POLLERR)) + { + u_int8_t buffer[3000]; /* hope that this is big enough */ + union + { + struct sockaddr sa; + struct sockaddr_in sa_in4; + struct sockaddr_in6 sa_in6; + } from; + + int from_len = sizeof(from); + + int packet_len; + + struct msghdr emh; + struct iovec eiov; + union { + /* force alignment (not documented as necessary) */ + struct cmsghdr ecms; + + /* how much space is enough? */ + unsigned char space[256]; + } ecms_buf; + + struct cmsghdr *cm; + char fromstr[sizeof(" for message to port 65536") + INET6_ADDRSTRLEN]; + struct state *sender = NULL; + + zero(&from.sa); + from_len = sizeof(from); + + emh.msg_name = &from.sa; /* ??? filled in? */ + emh.msg_namelen = sizeof(from); + emh.msg_iov = &eiov; + emh.msg_iovlen = 1; + emh.msg_control = &ecms_buf; + emh.msg_controllen = sizeof(ecms_buf); + emh.msg_flags = 0; + + eiov.iov_base = buffer; /* see readv(2) */ + eiov.iov_len = sizeof(buffer); + + packet_len = recvmsg(ifp->fd, &emh, MSG_ERRQUEUE); + + if (packet_len == -1) + { + log_errno((e, "recvmsg(,, MSG_ERRQUEUE) on %s failed in comm_handle" + , ifp->rname)); + break; + } + else if (packet_len == sizeof(buffer)) + { + plog("MSG_ERRQUEUE message longer than %lu bytes; truncated" + , (unsigned long) sizeof(buffer)); + } + else + { + sender = find_sender((size_t) packet_len, buffer); + } + + DBG_cond_dump(DBG_ALL, "rejected packet:\n", buffer, packet_len); + DBG_cond_dump(DBG_ALL, "control:\n", emh.msg_control, emh.msg_controllen); + /* ??? Andi Kleen <ak@suse.de> and misc documentation + * suggests that name will have the original destination + * of the packet. We seem to see msg_namelen == 0. + * Andi says that this is a kernel bug and has fixed it. + * Perhaps in 2.2.18/2.4.0. + */ + passert(emh.msg_name == &from.sa); + DBG_cond_dump(DBG_ALL, "name:\n", emh.msg_name + , emh.msg_namelen); + + fromstr[0] = '\0'; /* usual case :-( */ + switch (from.sa.sa_family) + { + char as[INET6_ADDRSTRLEN]; + + case AF_INET: + if (emh.msg_namelen == sizeof(struct sockaddr_in)) + snprintf(fromstr, sizeof(fromstr) + , " for message to %s port %u" + , inet_ntop(from.sa.sa_family + , &from.sa_in4.sin_addr, as, sizeof(as)) + , ntohs(from.sa_in4.sin_port)); + break; + case AF_INET6: + if (emh.msg_namelen == sizeof(struct sockaddr_in6)) + snprintf(fromstr, sizeof(fromstr) + , " for message to %s port %u" + , inet_ntop(from.sa.sa_family + , &from.sa_in6.sin6_addr, as, sizeof(as)) + , ntohs(from.sa_in6.sin6_port)); + break; + } + + for (cm = CMSG_FIRSTHDR(&emh) + ; cm != NULL + ; cm = CMSG_NXTHDR(&emh,cm)) + { + if (cm->cmsg_level == SOL_IP + && cm->cmsg_type == IP_RECVERR) + { + /* ip(7) and recvmsg(2) specify: + * ee_origin is SO_EE_ORIGIN_ICMP for ICMP + * or SO_EE_ORIGIN_LOCAL for locally generated errors. + * ee_type and ee_code are from the ICMP header. + * ee_info is the discovered MTU for EMSGSIZE errors + * ee_data is not used. + * + * ??? recvmsg(2) says "SOCK_EE_OFFENDER" but + * means "SO_EE_OFFENDER". The OFFENDER is really + * the router that complained. As such, the port + * is meaningless. + */ + + /* ??? cmsg(3) claims that CMSG_DATA returns + * void *, but RFC 2292 and /usr/include/bits/socket.h + * say unsigned char *. The manual is being fixed. + */ + struct sock_extended_err *ee = (void *)CMSG_DATA(cm); + const char *offstr = "unspecified"; + char offstrspace[INET6_ADDRSTRLEN]; + char orname[50]; + + if (cm->cmsg_len > CMSG_LEN(sizeof(struct sock_extended_err))) + { + const struct sockaddr *offender = SO_EE_OFFENDER(ee); + + switch (offender->sa_family) + { + case AF_INET: + offstr = inet_ntop(offender->sa_family + , &((const struct sockaddr_in *)offender)->sin_addr + , offstrspace, sizeof(offstrspace)); + break; + case AF_INET6: + offstr = inet_ntop(offender->sa_family + , &((const struct sockaddr_in6 *)offender)->sin6_addr + , offstrspace, sizeof(offstrspace)); + break; + default: + offstr = "unknown"; + break; + } + } + + switch (ee->ee_origin) + { + case SO_EE_ORIGIN_NONE: + snprintf(orname, sizeof(orname), "none"); + break; + case SO_EE_ORIGIN_LOCAL: + snprintf(orname, sizeof(orname), "local"); + break; + case SO_EE_ORIGIN_ICMP: + snprintf(orname, sizeof(orname) + , "ICMP type %d code %d (not authenticated)" + , ee->ee_type, ee->ee_code + ); + break; + case SO_EE_ORIGIN_ICMP6: + snprintf(orname, sizeof(orname) + , "ICMP6 type %d code %d (not authenticated)" + , ee->ee_type, ee->ee_code + ); + break; + default: + snprintf(orname, sizeof(orname), "invalid origin %lu" + , (unsigned long) ee->ee_origin); + break; + } + + { + struct state *old_state = cur_state; + + cur_state = sender; + + /* note dirty trick to suppress ~ at start of format + * if we know what state to blame. + */ + if ((packet_len == 1) && (buffer[0] = 0xff) +#ifdef DEBUG + && ((cur_debugging & DBG_NATT) == 0) +#endif + ) { + /* don't log NAT-T keepalive related errors unless NATT debug is + * enabled + */ + } + else + plog((sender != NULL) + "~" + "ERROR: asynchronous network error report on %s" + "%s" + ", complainant %s" + ": %s" + " [errno %lu, origin %s" + /* ", pad %d, info %ld" */ + /* ", data %ld" */ + "]" + , ifp->rname + , fromstr + , offstr + , strerror(ee->ee_errno) + , (unsigned long) ee->ee_errno + , orname + /* , ee->ee_pad, (unsigned long)ee->ee_info */ + /* , (unsigned long)ee->ee_data */ + ); + cur_state = old_state; + } + } + else + { + /* .cmsg_len is a kernel_size_t(!), but the value + * certainly ought to fit in an unsigned long. + */ + plog("unknown cmsg: level %d, type %d, len %lu" + , cm->cmsg_level, cm->cmsg_type + , (unsigned long) cm->cmsg_len); + } + } + } + return (pfd.revents & interest) != 0; +} +#endif /* defined(IP_RECVERR) && defined(MSG_ERRQUEUE) */ + +bool +send_packet(struct state *st, const char *where) +{ + struct connection *c = st->st_connection; + int port_buf; + bool err; + u_int8_t ike_pkt[MAX_OUTPUT_UDP_SIZE]; + u_int8_t *ptr; + unsigned long len; + + if (c->interface->ike_float && st->st_tpacket.len != 1) + { + if ((unsigned long) st->st_tpacket.len > (MAX_OUTPUT_UDP_SIZE-sizeof(u_int32_t))) + { + DBG_log("send_packet(): really too big"); + return FALSE; + } + ptr = ike_pkt; + /** Add Non-ESP marker **/ + memset(ike_pkt, 0, sizeof(u_int32_t)); + memcpy(ike_pkt + sizeof(u_int32_t), st->st_tpacket.ptr, + (unsigned long)st->st_tpacket.len); + len = (unsigned long) st->st_tpacket.len + sizeof(u_int32_t); + } + else + { + ptr = st->st_tpacket.ptr; + len = (unsigned long) st->st_tpacket.len; + } + + DBG(DBG_RAW, + { + DBG_log("sending %lu bytes for %s through %s to %s:%u:" + , (unsigned long) st->st_tpacket.len + , where + , c->interface->rname + , ip_str(&c->spd.that.host_addr) + , (unsigned)c->spd.that.host_port); + DBG_dump_chunk(NULL, st->st_tpacket); + }); + + /* XXX: Not very clean. We manipulate the port of the ip_address to + * have a port in the sockaddr*, but we retain the original port + * and restore it afterwards. + */ + + port_buf = portof(&c->spd.that.host_addr); + setportof(htons(c->spd.that.host_port), &c->spd.that.host_addr); + +#if defined(IP_RECVERR) && defined(MSG_ERRQUEUE) + (void) check_msg_errqueue(c->interface, POLLOUT); +#endif /* defined(IP_RECVERR) && defined(MSG_ERRQUEUE) */ + + err = sendto(c->interface->fd + , ptr, len, 0 + , sockaddrof(&c->spd.that.host_addr) + , sockaddrlenof(&c->spd.that.host_addr)) != (ssize_t)len; + + /* restore port */ + setportof(port_buf, &c->spd.that.host_addr); + + if (err) + { + /* do not log NAT-T Keep Alive packets */ + if (streq(where, "NAT-T Keep Alive")) + return FALSE; + log_errno((e, "sendto on %s to %s:%u failed in %s" + , c->interface->rname + , ip_str(&c->spd.that.host_addr) + , (unsigned)c->spd.that.host_port + , where)); + return FALSE; + } + else + { + return TRUE; + } +} + +static stf_status +unexpected(struct msg_digest *md) +{ + loglog(RC_LOG_SERIOUS, "unexpected message received in state %s" + , enum_name(&state_names, md->st->st_state)); + return STF_IGNORE; +} + +static stf_status +informational(struct msg_digest *md UNUSED) +{ + struct payload_digest *const n_pld = md->chain[ISAKMP_NEXT_N]; + + /* If the Notification Payload is not null... */ + if (n_pld != NULL) + { + pb_stream *const n_pbs = &n_pld->pbs; + struct isakmp_notification *const n = &n_pld->payload.notification; + int disp_len; + char disp_buf[200]; + + /* Switch on Notification Type (enum) */ + switch (n->isan_type) + { + case R_U_THERE: + return dpd_inI_outR(md->st, n, n_pbs); + + case R_U_THERE_ACK: + return dpd_inR(md->st, n, n_pbs); + default: + if (pbs_left(n_pbs) >= sizeof(disp_buf)-1) + disp_len = sizeof(disp_buf)-1; + else + disp_len = pbs_left(n_pbs); + memcpy(disp_buf, n_pbs->cur, disp_len); + disp_buf[disp_len] = '\0'; + break; + } + } + return STF_IGNORE; +} + +/* message digest allocation and deallocation */ + +static struct msg_digest *md_pool = NULL; + +/* free_md_pool is only used to avoid leak reports */ +void +free_md_pool(void) +{ + for (;;) + { + struct msg_digest *md = md_pool; + + if (md == NULL) + break; + md_pool = md->next; + pfree(md); + } +} + +static struct msg_digest * +alloc_md(void) +{ + struct msg_digest *md = md_pool; + + /* convenient initializer: + * - all pointers NULL + * - .note = NOTHING_WRONG + * - .encrypted = FALSE + */ + static const struct msg_digest blank_md; + + if (md == NULL) + md = alloc_thing(struct msg_digest, "msg_digest"); + else + md_pool = md->next; + + *md = blank_md; + md->digest_roof = md->digest; + + /* note: although there may be multiple msg_digests at once + * (due to suspended state transitions), there is a single + * global reply_buffer. It will need to be saved and restored. + */ + init_pbs(&md->reply, reply_buffer, sizeof(reply_buffer), "reply packet"); + + return md; +} + +void +release_md(struct msg_digest *md) +{ + freeanychunk(md->raw_packet); + pfreeany(md->packet_pbs.start); + md->packet_pbs.start = NULL; + md->next = md_pool; + md_pool = md; +} + +/* wrapper for read_packet and process_packet + * + * The main purpose of this wrapper is to factor out teardown code + * from the many return points in process_packet. This amounts to + * releasing the msg_digest and resetting global variables. + * + * When processing of a packet is suspended (STF_SUSPEND), + * process_packet sets md to NULL to prevent the msg_digest being freed. + * Someone else must ensure that msg_digest is freed eventually. + * + * read_packet is broken out to minimize the lifetime of the + * enormous input packet buffer, an auto. + */ +void +comm_handle(const struct iface *ifp) +{ + static struct msg_digest *md; + +#if defined(IP_RECVERR) && defined(MSG_ERRQUEUE) + /* Even though select(2) says that there is a message, + * it might only be a MSG_ERRQUEUE message. At least + * sometimes that leads to a hanging recvfrom. To avoid + * what appears to be a kernel bug, check_msg_errqueue + * uses poll(2) and tells us if there is anything for us + * to read. + * + * This is early enough that teardown isn't required: + * just return on failure. + */ + if (!check_msg_errqueue(ifp, POLLIN)) + return; /* no normal message to read */ +#endif /* defined(IP_RECVERR) && defined(MSG_ERRQUEUE) */ + + md = alloc_md(); + md->iface = ifp; + + if (read_packet(md)) + process_packet(&md); + + if (md != NULL) + release_md(md); + + cur_state = NULL; + reset_cur_connection(); + cur_from = NULL; +} + +/* read the message. + * Since we don't know its size, we read it into + * an overly large buffer and then copy it to a + * new, properly sized buffer. + */ +static bool +read_packet(struct msg_digest *md) +{ + const struct iface *ifp = md->iface; + int packet_len; + u_int8_t *buffer; + u_int8_t *buffer_nat; + union + { + struct sockaddr sa; + struct sockaddr_in sa_in4; + struct sockaddr_in6 sa_in6; + } from; + int from_len = sizeof(from); + err_t from_ugh = NULL; + static const char undisclosed[] = "unknown source"; + + happy(anyaddr(addrtypeof(&ifp->addr), &md->sender)); + zero(&from.sa); + ioctl(ifp->fd, FIONREAD, &packet_len); + buffer = alloc_bytes(packet_len, "buffer read packet"); + packet_len = recvfrom(ifp->fd, buffer, packet_len, 0 + , &from.sa, &from_len); + + /* First: digest the from address. + * We presume that nothing here disturbs errno. + */ + if (packet_len == -1 + && from_len == sizeof(from) + && all_zero((const void *)&from.sa, sizeof(from))) + { + /* "from" is untouched -- not set by recvfrom */ + from_ugh = undisclosed; + } + else if (from_len + < (int) (offsetof(struct sockaddr, sa_family) + sizeof(from.sa.sa_family))) + { + from_ugh = "truncated"; + } + else + { + const struct af_info *afi = aftoinfo(from.sa.sa_family); + + if (afi == NULL) + { + from_ugh = "unexpected Address Family"; + } + else if (from_len != (int)afi->sa_sz) + { + from_ugh = "wrong length"; + } + else + { + switch (from.sa.sa_family) + { + case AF_INET: + from_ugh = initaddr((void *) &from.sa_in4.sin_addr + , sizeof(from.sa_in4.sin_addr), AF_INET, &md->sender); + md->sender_port = ntohs(from.sa_in4.sin_port); + break; + case AF_INET6: + from_ugh = initaddr((void *) &from.sa_in6.sin6_addr + , sizeof(from.sa_in6.sin6_addr), AF_INET6, &md->sender); + md->sender_port = ntohs(from.sa_in6.sin6_port); + break; + } + } + } + + /* now we report any actual I/O error */ + if (packet_len == -1) + { + if (from_ugh == undisclosed + && errno == ECONNREFUSED) + { + /* Tone down scary message for vague event: + * We get "connection refused" in response to some + * datagram we sent, but we cannot tell which one. + */ + plog("some IKE message we sent has been rejected with ECONNREFUSED (kernel supplied no details)"); + } + else if (from_ugh != NULL) + { + log_errno((e, "recvfrom on %s failed; Pluto cannot decode source sockaddr in rejection: %s" + , ifp->rname, from_ugh)); + } + else + { + log_errno((e, "recvfrom on %s from %s:%u failed" + , ifp->rname + , ip_str(&md->sender), (unsigned)md->sender_port)); + } + + return FALSE; + } + else if (from_ugh != NULL) + { + plog("recvfrom on %s returned misformed source sockaddr: %s" + , ifp->rname, from_ugh); + return FALSE; + } + cur_from = &md->sender; + cur_from_port = md->sender_port; + + if (ifp->ike_float == TRUE) + { + u_int32_t non_esp; + + if (packet_len < (int)sizeof(u_int32_t)) + { + plog("recvfrom %s:%u too small packet (%d)" + , ip_str(cur_from), (unsigned) cur_from_port, packet_len); + return FALSE; + } + memcpy(&non_esp, buffer, sizeof(u_int32_t)); + if (non_esp != 0) + { + plog("recvfrom %s:%u has no Non-ESP marker" + , ip_str(cur_from), (unsigned) cur_from_port); + return FALSE; + } + packet_len -= sizeof(u_int32_t); + buffer_nat = alloc_bytes(packet_len, "buffer read packet"); + memcpy(buffer_nat, buffer + sizeof(u_int32_t), packet_len); + pfree(buffer); + buffer = buffer_nat; + } + + /* Clone actual message contents + * and set up md->packet_pbs to describe it. + */ + init_pbs(&md->packet_pbs, buffer, packet_len, "packet"); + + DBG(DBG_RAW | DBG_CRYPT | DBG_PARSING | DBG_CONTROL, + { + DBG_log(BLANK_FORMAT); + DBG_log("*received %d bytes from %s:%u on %s" + , (int) pbs_room(&md->packet_pbs) + , ip_str(cur_from), (unsigned) cur_from_port + , ifp->rname); + }); + + DBG(DBG_RAW, + DBG_dump("", md->packet_pbs.start, pbs_room(&md->packet_pbs))); + + if ((pbs_room(&md->packet_pbs)==1) && (md->packet_pbs.start[0]==0xff)) + { + /** + * NAT-T Keep-alive packets should be discared by kernel ESPinUDP + * layer. But boggus keep-alive packets (sent with a non-esp marker) + * can reach this point. Complain and discard them. + */ + DBG(DBG_NATT, + DBG_log("NAT-T keep-alive (boggus ?) should not reach this point. " + "Ignored. Sender: %s:%u", ip_str(cur_from), + (unsigned) cur_from_port); + ) + return FALSE; + } + +#define IKEV2_VERSION_OFFSET 17 +#define IKEV2_VERSION 0x20 + + /* ignore IKEv2 packets - they will be handled by charon */ + if (pbs_room(&md->packet_pbs) > IKEV2_VERSION_OFFSET + && md->packet_pbs.start[IKEV2_VERSION_OFFSET] == IKEV2_VERSION) + { + DBG(DBG_CONTROLMORE, + DBG_log(" ignoring IKEv2 packet") + ) + return FALSE; + } + + return TRUE; +} + +/* process an input packet, possibly generating a reply. + * + * If all goes well, this routine eventually calls a state-specific + * transition function. + */ +static void +process_packet(struct msg_digest **mdp) +{ + struct msg_digest *md = *mdp; + const struct state_microcode *smc; + bool new_iv_set = FALSE; + bool restore_iv = FALSE; + u_char new_iv[MAX_DIGEST_LEN]; + u_int new_iv_len = 0; + + struct state *st = NULL; + enum state_kind from_state = STATE_UNDEFINED; /* state we started in */ + +#define SEND_NOTIFICATION(t) { \ + if (st) send_notification_from_state(st, from_state, t); \ + else send_notification_from_md(md, t); } + + if (!in_struct(&md->hdr, &isakmp_hdr_desc, &md->packet_pbs, &md->message_pbs)) + { + /* Identify specific failures: + * - bad ISAKMP major/minor version numbers + */ + if (md->packet_pbs.roof - md->packet_pbs.cur >= (ptrdiff_t)isakmp_hdr_desc.size) + { + struct isakmp_hdr *hdr = (struct isakmp_hdr *)md->packet_pbs.cur; + if ((hdr->isa_version >> ISA_MAJ_SHIFT) != ISAKMP_MAJOR_VERSION) + { + SEND_NOTIFICATION(INVALID_MAJOR_VERSION); + return; + } + else if ((hdr->isa_version & ISA_MIN_MASK) != ISAKMP_MINOR_VERSION) + { + SEND_NOTIFICATION(INVALID_MINOR_VERSION); + return; + } + } + SEND_NOTIFICATION(PAYLOAD_MALFORMED); + return; + } + + if (md->packet_pbs.roof != md->message_pbs.roof) + { + plog("size (%u) differs from size specified in ISAKMP HDR (%u)" + , (unsigned) pbs_room(&md->packet_pbs), md->hdr.isa_length); +#ifdef CISCO_QUIRKS + if (pbs_room(&md->packet_pbs) - md->hdr.isa_length == 16) + plog("Cisco VPN client appends 16 surplus NULL bytes"); + else +#endif + return; + } + + switch (md->hdr.isa_xchg) + { +#ifdef NOTYET + case ISAKMP_XCHG_NONE: + case ISAKMP_XCHG_BASE: +#endif + + case ISAKMP_XCHG_IDPROT: /* part of a Main Mode exchange */ + if (md->hdr.isa_msgid != MAINMODE_MSGID) + { + plog("Message ID was 0x%08lx but should be zero in Main Mode", + (unsigned long) md->hdr.isa_msgid); + SEND_NOTIFICATION(INVALID_MESSAGE_ID); + return; + } + + if (is_zero_cookie(md->hdr.isa_icookie)) + { + plog("Initiator Cookie must not be zero in Main Mode message"); + SEND_NOTIFICATION(INVALID_COOKIE); + return; + } + + if (is_zero_cookie(md->hdr.isa_rcookie)) + { + /* initial message from initiator + * ??? what if this is a duplicate of another message? + */ + if (md->hdr.isa_flags & ISAKMP_FLAG_ENCRYPTION) + { + plog("initial Main Mode message is invalid:" + " its Encrypted Flag is on"); + SEND_NOTIFICATION(INVALID_FLAGS); + return; + } + + /* don't build a state until the message looks tasty */ + from_state = STATE_MAIN_R0; + } + else + { + /* not an initial message */ + + st = find_state(md->hdr.isa_icookie, md->hdr.isa_rcookie + , &md->sender, md->hdr.isa_msgid); + + if (st == NULL) + { + /* perhaps this is a first message from the responder + * and contains a responder cookie that we've not yet seen. + */ + st = find_state(md->hdr.isa_icookie, zero_cookie + , &md->sender, md->hdr.isa_msgid); + + if (st == NULL) + { + plog("Main Mode message is part of an unknown exchange"); + /* XXX Could send notification back */ + return; + } + } + set_cur_state(st); + from_state = st->st_state; + } + break; + +#ifdef NOTYET + case ISAKMP_XCHG_AO: + case ISAKMP_XCHG_AGGR: +#endif + + case ISAKMP_XCHG_INFO: /* an informational exchange */ + st = find_state(md->hdr.isa_icookie, md->hdr.isa_rcookie + , &md->sender, MAINMODE_MSGID); + + if (st != NULL) + set_cur_state(st); + + if (md->hdr.isa_flags & ISAKMP_FLAG_ENCRYPTION) + { + if (st == NULL) + { + plog("Informational Exchange is for an unknown (expired?) SA"); + /* XXX Could send notification back */ + return; + } + + if (!IS_ISAKMP_ENCRYPTED(st->st_state)) + { + loglog(RC_LOG_SERIOUS, "encrypted Informational Exchange message is invalid" + " because no key is known"); + /* XXX Could send notification back */ + return; + } + + if (md->hdr.isa_msgid == MAINMODE_MSGID) + { + loglog(RC_LOG_SERIOUS, "Informational Exchange message is invalid because" + " it has a Message ID of 0"); + /* XXX Could send notification back */ + return; + } + + if (!reserve_msgid(st, md->hdr.isa_msgid)) + { + loglog(RC_LOG_SERIOUS, "Informational Exchange message is invalid because" + " it has a previously used Message ID (0x%08lx)" + , (unsigned long)md->hdr.isa_msgid); + /* XXX Could send notification back */ + return; + } + + if (!IS_ISAKMP_SA_ESTABLISHED(st->st_state)) + { + memcpy(st->st_ph1_iv, st->st_new_iv, st->st_new_iv_len); + st->st_ph1_iv_len = st->st_new_iv_len; + + /* backup new_iv */ + new_iv_len = st->st_new_iv_len; + passert(new_iv_len <= MAX_DIGEST_LEN) + memcpy(new_iv, st->st_new_iv, new_iv_len); + restore_iv = TRUE; + } + init_phase2_iv(st, &md->hdr.isa_msgid); + new_iv_set = TRUE; + + from_state = STATE_INFO_PROTECTED; + } + else + { + if (st != NULL && IS_ISAKMP_ENCRYPTED(st->st_state)) + { + loglog(RC_LOG_SERIOUS, "Informational Exchange message" + " must be encrypted"); + /* XXX Could send notification back */ + return; + } + from_state = STATE_INFO; + } + break; + + case ISAKMP_XCHG_QUICK: /* part of a Quick Mode exchange */ + if (is_zero_cookie(md->hdr.isa_icookie)) + { + plog("Quick Mode message is invalid because" + " it has an Initiator Cookie of 0"); + SEND_NOTIFICATION(INVALID_COOKIE); + return; + } + + if (is_zero_cookie(md->hdr.isa_rcookie)) + { + plog("Quick Mode message is invalid because" + " it has a Responder Cookie of 0"); + SEND_NOTIFICATION(INVALID_COOKIE); + return; + } + + if (md->hdr.isa_msgid == MAINMODE_MSGID) + { + plog("Quick Mode message is invalid because" + " it has a Message ID of 0"); + SEND_NOTIFICATION(INVALID_MESSAGE_ID); + return; + } + + st = find_state(md->hdr.isa_icookie, md->hdr.isa_rcookie + , &md->sender, md->hdr.isa_msgid); + + if (st == NULL) + { + /* No appropriate Quick Mode state. + * See if we have a Main Mode state. + * ??? what if this is a duplicate of another message? + */ + st = find_state(md->hdr.isa_icookie, md->hdr.isa_rcookie + , &md->sender, MAINMODE_MSGID); + + if (st == NULL) + { + plog("Quick Mode message is for a non-existent (expired?)" + " ISAKMP SA"); + /* XXX Could send notification back */ + return; + } + + set_cur_state(st); + + if (!IS_ISAKMP_SA_ESTABLISHED(st->st_state)) + { + loglog(RC_LOG_SERIOUS, "Quick Mode message is unacceptable because" + " it is for an incomplete ISAKMP SA"); + SEND_NOTIFICATION(PAYLOAD_MALFORMED /* XXX ? */); + return; + } + + /* only accept this new Quick Mode exchange if it has a unique message ID */ + if (!reserve_msgid(st, md->hdr.isa_msgid)) + { + loglog(RC_LOG_SERIOUS, "Quick Mode I1 message is unacceptable because" + " it uses a previously used Message ID 0x%08lx" + " (perhaps this is a duplicated packet)" + , (unsigned long) md->hdr.isa_msgid); + SEND_NOTIFICATION(INVALID_MESSAGE_ID); + return; + } + + /* Quick Mode Initial IV */ + init_phase2_iv(st, &md->hdr.isa_msgid); + new_iv_set = TRUE; + + from_state = STATE_QUICK_R0; + } + else + { + set_cur_state(st); + from_state = st->st_state; + } + + break; + + case ISAKMP_XCHG_MODE_CFG: + if (is_zero_cookie(md->hdr.isa_icookie)) + { + plog("ModeCfg message is invalid because" + " it has an Initiator Cookie of 0"); + /* XXX Could send notification back */ + return; + } + + if (is_zero_cookie(md->hdr.isa_rcookie)) + { + plog("ModeCfg message is invalid because" + " it has a Responder Cookie of 0"); + /* XXX Could send notification back */ + return; + } + + if (md->hdr.isa_msgid == 0) + { + plog("ModeCfg message is invalid because" + " it has a Message ID of 0"); + /* XXX Could send notification back */ + return; + } + + st = find_state(md->hdr.isa_icookie, md->hdr.isa_rcookie + , &md->sender, md->hdr.isa_msgid); + + if (st == NULL) + { + bool has_xauth_policy; + + /* No appropriate ModeCfg state. + * See if we have a Main Mode state. + * ??? what if this is a duplicate of another message? + */ + st = find_state(md->hdr.isa_icookie, md->hdr.isa_rcookie + , &md->sender, 0); + + if (st == NULL) + { + plog("ModeCfg message is for a non-existent (expired?)" + " ISAKMP SA"); + /* XXX Could send notification back */ + return; + } + + set_cur_state(st); + + /* the XAUTH_STATUS message might have a new msgid */ + if (st->st_state == STATE_XAUTH_I1) + { + init_phase2_iv(st, &md->hdr.isa_msgid); + new_iv_set = TRUE; + from_state = st->st_state; + break; + } + + if (!IS_ISAKMP_SA_ESTABLISHED(st->st_state)) + { + loglog(RC_LOG_SERIOUS, "ModeCfg message is unacceptable because" + " it is for an incomplete ISAKMP SA (state=%s)" + , enum_name(&state_names, st->st_state)); + /* XXX Could send notification back */ + return; + } + init_phase2_iv(st, &md->hdr.isa_msgid); + new_iv_set = TRUE; + + /* + * okay, now we have to figure out if we are receiving a bogus + * new message in an oustanding XAUTH server conversation + * (i.e. a reply to our challenge) + * (this occurs with some broken other implementations). + * + * or if receiving for the first time, an XAUTH challenge. + * + * or if we are getting a MODECFG request. + * + * we distinguish these states because we can not both be an + * XAUTH server and client, and our policy tells us which + * one we are. + * + * to complicate further, it is normal to start a new msgid + * when going from one state to another, or when restarting + * the challenge. + * + */ + + has_xauth_policy = (st->st_connection->policy + & (POLICY_XAUTH_RSASIG | POLICY_XAUTH_PSK)) + != LEMPTY; + + if (has_xauth_policy && !st->st_xauth.started + && IS_PHASE1(st->st_state)) + { + from_state = STATE_XAUTH_I0; + } + else if (st->st_connection->spd.that.modecfg + && IS_PHASE1(st->st_state)) + { + from_state = STATE_MODE_CFG_R0; + } + else if (st->st_connection->spd.this.modecfg + && IS_PHASE1(st->st_state)) + { + from_state = STATE_MODE_CFG_I0; + } + else + { + /* XXX check if we are being a mode config server here */ + plog("received ModeCfg message when in state %s, and we aren't mode config client" + , enum_name(&state_names, st->st_state)); + return; + } + } + else + { + set_cur_state(st); + from_state = st->st_state; + } + break; + +#ifdef NOTYET + case ISAKMP_XCHG_NGRP: + case ISAKMP_XCHG_ACK_INFO: +#endif + + default: + plog("unsupported exchange type %s in message" + , enum_show(&exchange_names, md->hdr.isa_xchg)); + SEND_NOTIFICATION(UNSUPPORTED_EXCHANGE_TYPE); + return; + } + + /* We have found a from_state, and perhaps a state object. + * If we need to build a new state object, + * we wait until the packet has been sanity checked. + */ + + /* We don't support the Commit Flag. It is such a bad feature. + * It isn't protected -- neither encrypted nor authenticated. + * A man in the middle turns it on, leading to DoS. + * We just ignore it, with a warning. + * By placing the check here, we could easily add a policy bit + * to a connection to suppress the warning. This might be useful + * because the Commit Flag is expected from some peers. + */ + if (md->hdr.isa_flags & ISAKMP_FLAG_COMMIT) + { + plog("IKE message has the Commit Flag set but Pluto doesn't implement this feature; ignoring flag"); + } + + /* Set smc to describe this state's properties. + * Look up the appropriate microcode based on state and + * possibly Oakley Auth type. + */ + passert(STATE_IKE_FLOOR <= from_state && from_state <= STATE_IKE_ROOF); + smc = ike_microcode_index[from_state - STATE_IKE_FLOOR]; + + if (st != NULL) + { + u_int16_t auth; + + switch (st->st_oakley.auth) + { + case XAUTHInitPreShared: + case XAUTHRespPreShared: + auth = OAKLEY_PRESHARED_KEY; + break; + case XAUTHInitRSA: + case XAUTHRespRSA: + auth = OAKLEY_RSA_SIG; + break; + default: + auth = st->st_oakley.auth; + } + + while (!LHAS(smc->flags, auth)) + { + smc++; + passert(smc->state == from_state); + } + } + + /* Ignore a packet if the state has a suspended state transition + * Probably a duplicated packet but the original packet is not yet + * recorded in st->st_rpacket, so duplicate checking won't catch. + * ??? Should the packet be recorded earlier to improve diagnosis? + */ + if (st != NULL && st->st_suspended_md != NULL) + { + loglog(RC_LOG, "discarding packet received during DNS lookup in %s" + , enum_name(&state_names, st->st_state)); + return; + } + + /* Detect and handle duplicated packets. + * This won't work for the initial packet of an exchange + * because we won't have a state object to remember it. + * If we are in a non-receiving state (terminal), and the preceding + * state did transmit, then the duplicate may indicate that that + * transmission wasn't received -- retransmit it. + * Otherwise, just discard it. + * ??? Notification packets are like exchanges -- I hope that + * they are idempotent! + */ + if (st != NULL + && st->st_rpacket.ptr != NULL + && st->st_rpacket.len == pbs_room(&md->packet_pbs) + && memcmp(st->st_rpacket.ptr, md->packet_pbs.start, st->st_rpacket.len) == 0) + { + if (smc->flags & SMF_RETRANSMIT_ON_DUPLICATE) + { + if (st->st_retransmit < MAXIMUM_RETRANSMISSIONS) + { + st->st_retransmit++; + loglog(RC_RETRANSMISSION + , "retransmitting in response to duplicate packet; already %s" + , enum_name(&state_names, st->st_state)); + send_packet(st, "retransmit in response to duplicate"); + } + else + { + loglog(RC_LOG_SERIOUS, "discarding duplicate packet -- exhausted retransmission; already %s" + , enum_name(&state_names, st->st_state)); + } + } + else + { + loglog(RC_LOG_SERIOUS, "discarding duplicate packet; already %s" + , enum_name(&state_names, st->st_state)); + } + return; + } + + if (md->hdr.isa_flags & ISAKMP_FLAG_ENCRYPTION) + { + DBG(DBG_CRYPT, DBG_log("received encrypted packet from %s:%u" + , ip_str(&md->sender), (unsigned)md->sender_port)); + + if (st == NULL) + { + plog("discarding encrypted message for an unknown ISAKMP SA"); + SEND_NOTIFICATION(PAYLOAD_MALFORMED /* XXX ? */); + return; + } + if (st->st_skeyid_e.ptr == (u_char *) NULL) + { + loglog(RC_LOG_SERIOUS, "discarding encrypted message" + " because we haven't yet negotiated keying materiel"); + SEND_NOTIFICATION(INVALID_FLAGS); + return; + } + + /* Mark as encrypted */ + md->encrypted = TRUE; + + DBG(DBG_CRYPT, DBG_log("decrypting %u bytes using algorithm %s" + , (unsigned) pbs_left(&md->message_pbs) + , enum_show(&oakley_enc_names, st->st_oakley.encrypt))); + + /* do the specified decryption + * + * IV is from st->st_iv or (if new_iv_set) st->st_new_iv. + * The new IV is placed in st->st_new_iv + * + * See RFC 2409 "IKE" Appendix B + * + * XXX The IV should only be updated really if the packet + * is successfully processed. + * We should keep this value, check for a success return + * value from the parsing routines and then replace. + * + * Each post phase 1 exchange generates IVs from + * the last phase 1 block, not the last block sent. + */ + { + const struct encrypt_desc *e = st->st_oakley.encrypter; + + if (pbs_left(&md->message_pbs) % e->enc_blocksize != 0) + { + loglog(RC_LOG_SERIOUS, "malformed message: not a multiple of encryption blocksize"); + SEND_NOTIFICATION(PAYLOAD_MALFORMED); + return; + } + + /* XXX Detect weak keys */ + + /* grab a copy of raw packet (for duplicate packet detection) */ + clonetochunk(md->raw_packet, md->packet_pbs.start + , pbs_room(&md->packet_pbs), "raw packet"); + + /* Decrypt everything after header */ + if (!new_iv_set) + { + /* use old IV */ + passert(st->st_iv_len <= sizeof(st->st_new_iv)); + st->st_new_iv_len = st->st_iv_len; + memcpy(st->st_new_iv, st->st_iv, st->st_new_iv_len); + } + crypto_cbc_encrypt(e, FALSE, md->message_pbs.cur, + pbs_left(&md->message_pbs) , st); + if (restore_iv) + { + memcpy(st->st_new_iv, new_iv, new_iv_len); + st->st_new_iv_len = new_iv_len; + } + } + + DBG_cond_dump(DBG_CRYPT, "decrypted:\n", md->message_pbs.cur + , md->message_pbs.roof - md->message_pbs.cur); + + DBG_cond_dump(DBG_CRYPT, "next IV:" + , st->st_new_iv, st->st_new_iv_len); + } + else + { + /* packet was not encryped -- should it have been? */ + + if (smc->flags & SMF_INPUT_ENCRYPTED) + { + loglog(RC_LOG_SERIOUS, "packet rejected: should have been encrypted"); + SEND_NOTIFICATION(INVALID_FLAGS); + return; + } + } + + /* Digest the message. + * Padding must be removed to make hashing work. + * Padding comes from encryption (so this code must be after decryption). + * Padding rules are described before the definition of + * struct isakmp_hdr in packet.h. + */ + { + struct payload_digest *pd = md->digest; + int np = md->hdr.isa_np; + lset_t needed = smc->req_payloads; + const char *excuse + = LIN(SMF_PSK_AUTH | SMF_FIRST_ENCRYPTED_INPUT, smc->flags) + ? "probable authentication failure (mismatch of preshared secrets?): " + : ""; + + while (np != ISAKMP_NEXT_NONE) + { + struct_desc *sd = np < ISAKMP_NEXT_ROOF? payload_descs[np] : NULL; + + if (pd == &md->digest[PAYLIMIT]) + { + loglog(RC_LOG_SERIOUS, "more than %d payloads in message; ignored", PAYLIMIT); + SEND_NOTIFICATION(PAYLOAD_MALFORMED); + return; + } + + switch (np) + { + case ISAKMP_NEXT_NATD_RFC: + case ISAKMP_NEXT_NATOA_RFC: + if (!st || !(st->nat_traversal & NAT_T_WITH_RFC_VALUES)) + { + /* + * don't accept NAT-D/NAT-OA reloc directly in message, unless + * we're using NAT-T RFC + */ + sd = NULL; + } + break; + } + + if (sd == NULL) + { + /* payload type is out of range or requires special handling */ + switch (np) + { + case ISAKMP_NEXT_ID: + sd = IS_PHASE1(from_state) + ? &isakmp_identification_desc : &isakmp_ipsec_identification_desc; + break; + case ISAKMP_NEXT_NATD_DRAFTS: + np = ISAKMP_NEXT_NATD_RFC; /* NAT-D relocated */ + sd = payload_descs[np]; + break; + case ISAKMP_NEXT_NATOA_DRAFTS: + np = ISAKMP_NEXT_NATOA_RFC; /* NAT-OA relocated */ + sd = payload_descs[np]; + break; + default: + loglog(RC_LOG_SERIOUS, "%smessage ignored because it contains an unknown or" + " unexpected payload type (%s) at the outermost level" + , excuse, enum_show(&payload_names, np)); + SEND_NOTIFICATION(INVALID_PAYLOAD_TYPE); + return; + } + } + + { + lset_t s = LELEM(np); + + if (LDISJOINT(s + , needed | smc->opt_payloads| LELEM(ISAKMP_NEXT_N) | LELEM(ISAKMP_NEXT_D))) + { + loglog(RC_LOG_SERIOUS, "%smessage ignored because it " + "contains an unexpected payload type (%s)" + , excuse, enum_show(&payload_names, np)); + SEND_NOTIFICATION(INVALID_PAYLOAD_TYPE); + return; + } + needed &= ~s; + } + + if (!in_struct(&pd->payload, sd, &md->message_pbs, &pd->pbs)) + { + loglog(RC_LOG_SERIOUS, "%smalformed payload in packet", excuse); + if (md->hdr.isa_xchg != ISAKMP_XCHG_INFO) + SEND_NOTIFICATION(PAYLOAD_MALFORMED); + return; + } + + /* place this payload at the end of the chain for this type */ + { + struct payload_digest **p; + + for (p = &md->chain[np]; *p != NULL; p = &(*p)->next) + ; + *p = pd; + pd->next = NULL; + } + + np = pd->payload.generic.isag_np; + pd++; + + /* since we've digested one payload happily, it is probably + * the case that any decryption worked. So we will not suggest + * encryption failure as an excuse for subsequent payload + * problems. + */ + excuse = ""; + } + + md->digest_roof = pd; + + DBG(DBG_PARSING, + if (pbs_left(&md->message_pbs) != 0) + DBG_log("removing %d bytes of padding", (int) pbs_left(&md->message_pbs))); + + md->message_pbs.roof = md->message_pbs.cur; + + /* check that all mandatory payloads appeared */ + + if (needed != 0) + { + loglog(RC_LOG_SERIOUS, "message for %s is missing payloads %s" + , enum_show(&state_names, from_state) + , bitnamesof(payload_name, needed)); + SEND_NOTIFICATION(PAYLOAD_MALFORMED); + return; + } + } + + /* more sanity checking: enforce most ordering constraints */ + + if (IS_PHASE1(from_state)) + { + /* rfc2409: The Internet Key Exchange (IKE), 5 Exchanges: + * "The SA payload MUST precede all other payloads in a phase 1 exchange." + */ + if (md->chain[ISAKMP_NEXT_SA] != NULL + && md->hdr.isa_np != ISAKMP_NEXT_SA) + { + loglog(RC_LOG_SERIOUS, "malformed Phase 1 message: does not start with an SA payload"); + SEND_NOTIFICATION(PAYLOAD_MALFORMED); + return; + } + } + else if (IS_QUICK(from_state)) + { + /* rfc2409: The Internet Key Exchange (IKE), 5.5 Phase 2 - Quick Mode + * + * "In Quick Mode, a HASH payload MUST immediately follow the ISAKMP + * header and a SA payload MUST immediately follow the HASH." + * [NOTE: there may be more than one SA payload, so this is not + * totally reasonable. Probably all SAs should be so constrained.] + * + * "If ISAKMP is acting as a client negotiator on behalf of another + * party, the identities of the parties MUST be passed as IDci and + * then IDcr." + * + * "With the exception of the HASH, SA, and the optional ID payloads, + * there are no payload ordering restrictions on Quick Mode." + */ + + if (md->hdr.isa_np != ISAKMP_NEXT_HASH) + { + loglog(RC_LOG_SERIOUS, "malformed Quick Mode message: does not start with a HASH payload"); + SEND_NOTIFICATION(PAYLOAD_MALFORMED); + return; + } + + { + struct payload_digest *p; + int i; + + for (p = md->chain[ISAKMP_NEXT_SA], i = 1; p != NULL + ; p = p->next, i++) + { + if (p != &md->digest[i]) + { + loglog(RC_LOG_SERIOUS, "malformed Quick Mode message: SA payload is in wrong position"); + SEND_NOTIFICATION(PAYLOAD_MALFORMED); + return; + } + } + } + + /* rfc2409: The Internet Key Exchange (IKE), 5.5 Phase 2 - Quick Mode: + * "If ISAKMP is acting as a client negotiator on behalf of another + * party, the identities of the parties MUST be passed as IDci and + * then IDcr." + */ + { + struct payload_digest *id = md->chain[ISAKMP_NEXT_ID]; + + if (id != NULL) + { + if (id->next == NULL || id->next->next != NULL) + { + loglog(RC_LOG_SERIOUS, "malformed Quick Mode message:" + " if any ID payload is present," + " there must be exactly two"); + SEND_NOTIFICATION(PAYLOAD_MALFORMED); + return; + } + if (id+1 != id->next) + { + loglog(RC_LOG_SERIOUS, "malformed Quick Mode message:" + " the ID payloads are not adjacent"); + SEND_NOTIFICATION(PAYLOAD_MALFORMED); + return; + } + } + } + } + + /* Ignore payloads that we don't handle: + * Delete, Notification, VendorID + */ + /* XXX Handle deletions */ + /* XXX Handle Notifications */ + /* XXX Handle VID payloads */ + { + struct payload_digest *p; + + for (p = md->chain[ISAKMP_NEXT_N]; p != NULL; p = p->next) + { + if (p->payload.notification.isan_type != R_U_THERE + && p->payload.notification.isan_type != R_U_THERE_ACK) + { + loglog(RC_LOG_SERIOUS, "ignoring informational payload, type %s" + , enum_show(¬ification_names, p->payload.notification.isan_type)); + } + DBG_cond_dump(DBG_PARSING, "info:", p->pbs.cur, pbs_left(&p->pbs)); + } + + for (p = md->chain[ISAKMP_NEXT_D]; p != NULL; p = p->next) + { + accept_delete(st, md, p); + DBG_cond_dump(DBG_PARSING, "del:", p->pbs.cur, pbs_left(&p->pbs)); + } + + for (p = md->chain[ISAKMP_NEXT_VID]; p != NULL; p = p->next) + { + handle_vendorid(md, p->pbs.cur, pbs_left(&p->pbs)); + } + } + md->from_state = from_state; + md->smc = smc; + md->st = st; + + /* possibly fill in hdr */ + if (smc->first_out_payload != ISAKMP_NEXT_NONE) + echo_hdr(md, (smc->flags & SMF_OUTPUT_ENCRYPTED) != 0 + , smc->first_out_payload); + + complete_state_transition(mdp, smc->processor(md)); +} + +/* complete job started by the state-specific state transition function */ + +void +complete_state_transition(struct msg_digest **mdp, stf_status result) +{ + bool has_xauth_policy; + bool is_xauth_server; + struct msg_digest *md = *mdp; + const struct state_microcode *smc = md->smc; + enum state_kind from_state = md->from_state; + struct state *st; + + cur_state = st = md->st; /* might have changed */ + + /* If state has DPD support, import it */ + if (st && md->dpd) + st->st_dpd = TRUE; + + switch (result) + { + case STF_IGNORE: + break; + + case STF_SUSPEND: + /* the stf didn't complete its job: don't relase md */ + *mdp = NULL; + break; + + case STF_OK: + /* advance the state */ + st->st_state = smc->next_state; + + /* Delete previous retransmission event. + * New event will be scheduled below. + */ + delete_event(st); + + /* replace previous receive packet with latest */ + + pfreeany(st->st_rpacket.ptr); + + if (md->encrypted) + { + /* if encrypted, duplication already done */ + st->st_rpacket = md->raw_packet; + md->raw_packet.ptr = NULL; + } + else + { + clonetochunk(st->st_rpacket + , md->packet_pbs.start + , pbs_room(&md->packet_pbs), "raw packet"); + } + + /* free previous transmit packet */ + freeanychunk(st->st_tpacket); + + /* if requested, send the new reply packet */ + if (smc->flags & SMF_REPLY) + { + close_output_pbs(&md->reply); /* good form, but actually a no-op */ + + clonetochunk(st->st_tpacket, md->reply.start + , pbs_offset(&md->reply), "reply packet"); + + if (nat_traversal_enabled) + nat_traversal_change_port_lookup(md, md->st); + + /* actually send the packet + * Note: this is a great place to implement "impairments" + * for testing purposes. Suppress or duplicate the + * send_packet call depending on st->st_state. + */ + send_packet(st, enum_name(&state_names, from_state)); + } + + /* Schedule for whatever timeout is specified */ + { + time_t delay; + enum event_type kind = smc->timeout_event; + bool agreed_time = FALSE; + struct connection *c = st->st_connection; + + switch (kind) + { + case EVENT_RETRANSMIT: /* Retransmit packet */ + delay = EVENT_RETRANSMIT_DELAY_0; + break; + + case EVENT_SA_REPLACE: /* SA replacement event */ + if (IS_PHASE1(st->st_state)) + { + /* Note: we will defer to the "negotiated" (dictated) + * lifetime if we are POLICY_DONT_REKEY. + * This allows the other side to dictate + * a time we would not otherwise accept + * but it prevents us from having to initiate + * rekeying. The negative consequences seem + * minor. + */ + delay = c->sa_ike_life_seconds; + if ((c->policy & POLICY_DONT_REKEY) + || delay >= st->st_oakley.life_seconds) + { + agreed_time = TRUE; + delay = st->st_oakley.life_seconds; + } + } + else + { + /* Delay is min of up to four things: + * each can limit the lifetime. + */ + delay = c->sa_ipsec_life_seconds; + if (st->st_ah.present + && delay >= st->st_ah.attrs.life_seconds) + { + agreed_time = TRUE; + delay = st->st_ah.attrs.life_seconds; + } + if (st->st_esp.present + && delay >= st->st_esp.attrs.life_seconds) + { + agreed_time = TRUE; + delay = st->st_esp.attrs.life_seconds; + } + if (st->st_ipcomp.present + && delay >= st->st_ipcomp.attrs.life_seconds) + { + agreed_time = TRUE; + delay = st->st_ipcomp.attrs.life_seconds; + } + } + + /* By default, we plan to rekey. + * + * If there isn't enough time to rekey, plan to + * expire. + * + * If we are --dontrekey, a lot more rules apply. + * If we are the Initiator, use REPLACE_IF_USED. + * If we are the Responder, and the dictated time + * was unacceptable (too large), plan to REPLACE + * (the only way to ratchet down the time). + * If we are the Responder, and the dictated time + * is acceptable, plan to EXPIRE. + * + * Important policy lies buried here. + * For example, we favour the initiator over the + * responder by making the initiator start rekeying + * sooner. Also, fuzz is only added to the + * initiator's margin. + * + * Note: for ISAKMP SA, we let the negotiated + * time stand (implemented by earlier logic). + */ + if (agreed_time + && (c->policy & POLICY_DONT_REKEY)) + { + kind = (smc->flags & SMF_INITIATOR) + ? EVENT_SA_REPLACE_IF_USED + : EVENT_SA_EXPIRE; + } + if (kind != EVENT_SA_EXPIRE) + { + unsigned long marg = c->sa_rekey_margin; + + if (smc->flags & SMF_INITIATOR) + marg += marg + * c->sa_rekey_fuzz / 100.E0 + * (rand() / (RAND_MAX + 1.E0)); + else + marg /= 2; + + if ((unsigned long)delay > marg) + { + delay -= marg; + st->st_margin = marg; + } + else + { + kind = EVENT_SA_EXPIRE; + } + } + break; + + case EVENT_NULL: /* non-event */ + case EVENT_REINIT_SECRET: /* Refresh cookie secret */ + default: + bad_case(kind); + } + event_schedule(kind, delay, st); + } + + /* tell whack and log of progress */ + { + const char *story = state_story[st->st_state - STATE_MAIN_R0]; + enum rc_type w = RC_NEW_STATE + st->st_state; + char sadetails[128]; + + sadetails[0]='\0'; + + if (IS_IPSEC_SA_ESTABLISHED(st->st_state)) + { + char *b = sadetails; + const char *ini = " {"; + const char *fin = ""; + + /* -1 is to leave space for "fin" */ + + if (st->st_esp.present) + { + snprintf(b, sizeof(sadetails)-(b-sadetails)-1 + , "%sESP=>0x%08x <0x%08x" + , ini + , ntohl(st->st_esp.attrs.spi) + , ntohl(st->st_esp.our_spi)); + ini = " "; + fin = "}"; + } + /* advance b to end of string */ + b = b + strlen(b); + + if (st->st_ah.present) + { + snprintf(b, sizeof(sadetails)-(b-sadetails)-1 + , "%sAH=>0x%08x <0x%08x" + , ini + , ntohl(st->st_ah.attrs.spi) + , ntohl(st->st_ah.our_spi)); + ini = " "; + fin = "}"; + } + /* advance b to end of string */ + b = b + strlen(b); + + if (st->st_ipcomp.present) + { + snprintf(b, sizeof(sadetails)-(b-sadetails)-1 + , "%sIPCOMP=>0x%08x <0x%08x" + , ini + , ntohl(st->st_ipcomp.attrs.spi) + , ntohl(st->st_ipcomp.our_spi)); + ini = " "; + fin = "}"; + } + /* advance b to end of string */ + b = b + strlen(b); + + if (st->nat_traversal) + { + char oa[ADDRTOT_BUF]; + addrtot(&st->nat_oa, 0, oa, sizeof(oa)); + snprintf(b, sizeof(sadetails)-(b-sadetails)-1 + , "%sNATOA=%s" + , ini, oa); + ini = " "; + fin = "}"; + } + + /* advance b to end of string */ + b = b + strlen(b); + + if (st->st_dpd) + { + snprintf(b, sizeof(sadetails)-(b-sadetails)-1 + , "%sDPD" + , ini); + ini = " "; + fin = "}"; + } + + strcat(b, fin); + } + + if (IS_ISAKMP_SA_ESTABLISHED(st->st_state) + || IS_IPSEC_SA_ESTABLISHED(st->st_state)) + { + /* log our success */ + plog("%s%s", story, sadetails); + w = RC_SUCCESS; + } + + /* tell whack our progress */ + whack_log(w + , "%s: %s%s" + , enum_name(&state_names, st->st_state) + , story, sadetails); + } + + has_xauth_policy = (st->st_connection->policy + & (POLICY_XAUTH_RSASIG | POLICY_XAUTH_PSK)) + != LEMPTY; + is_xauth_server = (st->st_connection->policy + & POLICY_XAUTH_SERVER) + != LEMPTY; + + /* Should we start XAUTH as a server */ + if (has_xauth_policy && is_xauth_server + && IS_ISAKMP_SA_ESTABLISHED(st->st_state) + && !st->st_xauth.started) + { + DBG(DBG_CONTROL, + DBG_log("starting XAUTH server") + ) + xauth_send_request(st); + break; + } + + /* Wait for XAUTH request from server */ + if (has_xauth_policy && !is_xauth_server + && IS_ISAKMP_SA_ESTABLISHED(st->st_state) + && !st->st_xauth.started) + { + DBG(DBG_CONTROL, + DBG_log("waiting for XAUTH request from server") + ) + break; + } + + /* Should we start ModeConfig as a client? */ + if (st->st_connection->spd.this.modecfg + && IS_ISAKMP_SA_ESTABLISHED(st->st_state) + && !(st->st_connection->policy & POLICY_MODECFG_PUSH) + && !st->st_modecfg.started) + { + DBG(DBG_CONTROL, + DBG_log("starting ModeCfg client in pull mode") + ) + modecfg_send_request(st); + break; + } + + /* Should we start ModeConfig as a server? */ + if (st->st_connection->spd.that.modecfg + && IS_ISAKMP_SA_ESTABLISHED(st->st_state) + && !st->st_modecfg.started + && (st->st_connection->policy & POLICY_MODECFG_PUSH)) + { + DBG(DBG_CONTROL, + DBG_log("starting ModeCfg server in push mode") + ) + modecfg_send_set(st); + break; + } + + /* Wait for ModeConfig set from server */ + if (st->st_connection->spd.this.modecfg + && IS_ISAKMP_SA_ESTABLISHED(st->st_state) + && !st->st_modecfg.vars_set) + { + DBG(DBG_CONTROL, + DBG_log("waiting for ModeCfg set from server") + ) + break; + } + + if (smc->flags & SMF_RELEASE_PENDING_P2) + { + /* Initiate any Quick Mode negotiations that + * were waiting to piggyback on this Keying Channel. + * + * ??? there is a potential race condition + * if we are the responder: the initial Phase 2 + * message might outrun the final Phase 1 message. + * I think that retransmission will recover. + */ + unpend(st); + } + + if (IS_ISAKMP_SA_ESTABLISHED(st->st_state) + || IS_IPSEC_SA_ESTABLISHED(st->st_state)) + release_whack(st); + break; + + case STF_INTERNAL_ERROR: + whack_log(RC_INTERNALERR + md->note + , "%s: internal error" + , enum_name(&state_names, st->st_state)); + + DBG(DBG_CONTROL, + DBG_log("state transition function for %s had internal error" + , enum_name(&state_names, from_state))); + break; + + default: /* a shortcut to STF_FAIL, setting md->note */ + passert(result > STF_FAIL); + md->note = result - STF_FAIL; + result = STF_FAIL; + /* FALL THROUGH ... */ + case STF_FAIL: + /* As it is, we act as if this message never happened: + * whatever retrying was in place, remains in place. + */ + whack_log(RC_NOTIFICATION + md->note + , "%s: %s" + , enum_name(&state_names, (st == NULL)? STATE_MAIN_R0:st->st_state) + , enum_name(¬ification_names, md->note)); + + SEND_NOTIFICATION(md->note); + + DBG(DBG_CONTROL, + DBG_log("state transition function for %s failed: %s" + , enum_name(&state_names, from_state) + , enum_name(¬ification_names, md->note))); + break; + } +} diff --git a/src/pluto/demux.h b/src/pluto/demux.h new file mode 100644 index 000000000..373dd6315 --- /dev/null +++ b/src/pluto/demux.h @@ -0,0 +1,93 @@ +/* demultiplex incoming IKE messages + * Copyright (C) 1998-2002 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: demux.h,v 1.4 2004/07/22 22:57:25 as Exp $ + */ + +#include "packet.h" + +struct state; /* forward declaration of tag */ +extern void init_demux(void); +extern bool send_packet(struct state *st, const char *where); +extern void comm_handle(const struct iface *ifp); + +extern u_int8_t reply_buffer[MAX_OUTPUT_UDP_SIZE]; + +/* State transition function infrastructure + * + * com_handle parses a message, decides what state object it applies to, + * and calls the appropriate state transition function (STF). + * These declarations define the interface to these functions. + * + * Each STF must be able to be restarted up to any failure point: + * a later message will cause the state to be re-entered. This + * explains the use of the replace macro and the care in handling + * MP_INT members of struct state. + */ + +struct payload_digest { + pb_stream pbs; + union payload payload; + struct payload_digest *next; /* of same kind */ +}; + +/* message digest + * Note: raw_packet and packet_pbs are "owners" of space on heap. + */ + +struct msg_digest { + struct msg_digest *next; /* for free list */ + chunk_t raw_packet; /* if encrypted, received packet before decryption */ + const struct iface *iface; /* interface on which message arrived */ + ip_address sender; /* where message came from */ + u_int16_t sender_port; /* host order */ + pb_stream packet_pbs; /* whole packet */ + pb_stream message_pbs; /* message to be processed */ + struct isakmp_hdr hdr; /* message's header */ + bool encrypted; /* was it encrypted? */ + enum state_kind from_state; /* state we started in */ + const struct state_microcode *smc; /* microcode for initial state */ + struct state *st; /* current state object */ + pb_stream reply; /* room for reply */ + pb_stream rbody; /* room for reply body (after header) */ + notification_t note; /* reason for failure */ + bool dpd; /* peer supports RFC 3706 DPD */ + bool openpgp; /* peer supports OpenPGP certificates */ + +# define PAYLIMIT 40 + struct payload_digest + digest[PAYLIMIT], + *digest_roof, + *chain[ISAKMP_NEXT_ROOF]; + unsigned short nat_traversal_vid; +}; + +extern void release_md(struct msg_digest *md); + +/* status for state-transition-function + * Note: STF_FAIL + notification_t means fail with that notification + */ + +typedef enum { + STF_IGNORE, /* don't respond */ + STF_SUSPEND, /* unfinished -- don't release resources */ + STF_OK, /* success */ + STF_INTERNAL_ERROR, /* discard everything, we failed */ + STF_FAIL /* discard everything, something failed. notification_t added. */ +} stf_status; + +typedef stf_status state_transition_fn(struct msg_digest *md); + +extern void complete_state_transition(struct msg_digest **mdp, stf_status result); + +extern void free_md_pool(void); diff --git a/src/pluto/dnskey.c b/src/pluto/dnskey.c new file mode 100644 index 000000000..23863b0a2 --- /dev/null +++ b/src/pluto/dnskey.c @@ -0,0 +1,1962 @@ +/* Find public key in DNS + * Copyright (C) 2000-2002 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: dnskey.c,v 1.5 2005/09/08 16:26:30 as Exp $ + */ + +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#include <resolv.h> +#include <netdb.h> /* ??? for h_errno */ +#include <sys/queue.h> + +#include <freeswan.h> +#include <ipsec_policy.h> + +#include "constants.h" +#include "adns.h" /* needs <resolv.h> */ +#include "defs.h" +#include "log.h" +#include "id.h" +#include "connections.h" +#include "keys.h" /* needs connections.h */ +#include "dnskey.h" +#include "packet.h" +#include "timer.h" + +/* somebody has to decide */ +#define MAX_TXT_RDATA ((MAX_KEY_BYTES * 8 / 6) + 40) /* somewhat arbitrary overkill */ + +/* ADNS stuff */ + +int adns_qfd = NULL_FD, /* file descriptor for sending queries to adns (O_NONBLOCK) */ + adns_afd = NULL_FD; /* file descriptor for receiving answers from adns */ +static pid_t adns_pid = 0; +const char *pluto_adns_option = NULL; /* path from --pluto_adns */ + +int adns_restart_count; +#define ADNS_RESTART_MAX 20 + +void +init_adns(void) +{ + const char *adns_path = pluto_adns_option; +#ifndef USE_LWRES + static const char adns_name[] = "_pluto_adns"; + const char *helper_bin_dir = getenv("IPSEC_LIBDIR"); +#else /* USE_LWRES */ + static const char adns_name[] = "lwdnsq"; + const char *helper_bin_dir = getenv("IPSEC_EXECDIR"); +#endif /* USE_LWRES */ + char adns_path_space[4096]; /* plenty long? */ + int qfds[2]; + int afds[2]; + + /* find a pathname to the ADNS program */ + if (adns_path == NULL) + { + /* pathname was not specified as an option: build it. + * First, figure out the directory to be used. + */ + ssize_t n; + + if (helper_bin_dir != NULL) + { + n = strlen(helper_bin_dir); + if ((size_t)n <= sizeof(adns_path_space) - sizeof(adns_name)) + { + strcpy(adns_path_space, helper_bin_dir); + if (n > 0 && adns_path_space[n -1] != '/') + adns_path_space[n++] = '/'; + } + } + else + { + /* The program will be in the same directory as Pluto, + * so we use the sympolic link /proc/self/exe to + * tell us of the path prefix. + */ + n = readlink("/proc/self/exe", adns_path_space, sizeof(adns_path_space)); + + if (n < 0) + exit_log_errno((e + , "readlink(\"/proc/self/exe\") failed in init_adns()")); + + } + + if ((size_t)n > sizeof(adns_path_space) - sizeof(adns_name)) + exit_log("path to %s is too long", adns_name); + + while (n > 0 && adns_path_space[n - 1] != '/') + n--; + + strcpy(adns_path_space + n, adns_name); + adns_path = adns_path_space; + } + if (access(adns_path, X_OK) < 0) + exit_log_errno((e, "%s missing or not executable", adns_path)); + + if (pipe(qfds) != 0 || pipe(afds) != 0) + exit_log_errno((e, "pipe(2) failed in init_adns()")); + + adns_pid = fork(); + switch (adns_pid) + { + case -1: + exit_log_errno((e, "fork() failed in init_adns()")); + + case 0: + /* child */ + { + /* Make stdin and stdout our pipes. + * Take care to handle case where pipes already use these fds. + */ + if (afds[1] == 0) + afds[1] = dup(afds[1]); /* avoid being overwritten */ + if (qfds[0] != 0) + { + dup2(qfds[0], 0); + close(qfds[0]); + } + if (afds[1] != 1) + { + dup2(afds[1], 1); + close(qfds[1]); + } + if (afds[0] > 1) + close(afds[0]); + if (afds[1] > 1) + close(afds[1]); + + DBG(DBG_DNS, execlp(adns_path, adns_name, "-d", NULL)); + + execlp(adns_path, adns_name, NULL); + exit_log_errno((e, "execlp of %s failed", adns_path)); + } + + default: + /* parent */ + close(qfds[0]); + adns_qfd = qfds[1]; + adns_afd = afds[0]; + close(afds[1]); + fcntl(adns_qfd, F_SETFD, FD_CLOEXEC); + fcntl(adns_afd, F_SETFD, FD_CLOEXEC); + fcntl(adns_qfd, F_SETFL, O_NONBLOCK); + break; + } +} + +void +stop_adns(void) +{ + close_any(adns_qfd); + adns_qfd = NULL_FD; + close_any(adns_afd); + adns_afd = NULL_FD; + + if (adns_pid != 0) + { + int status; + pid_t p = waitpid(adns_pid, &status, 0); + + if (p == -1) + { + log_errno((e, "waitpid for ADNS process failed")); + } + else if (WIFEXITED(status)) + { + if (WEXITSTATUS(status) != 0) + plog("ADNS process exited with status %d" + , (int) WEXITSTATUS(status)); + } + else if (WIFSIGNALED(status)) + { + plog("ADNS process terminated by signal %d", (int)WTERMSIG(status)); + } + else + { + plog("wait for end of ADNS process returned odd status 0x%x\n" + , status); + } + } +} + + + +/* tricky macro to pass any hot potato */ +#define TRY(x) { err_t ugh = x; if (ugh != NULL) return ugh; } + + +/* Process TXT X-IPsec-Server record, accumulating relevant ones + * in cr->gateways_from_dns, a list sorted by "preference". + * + * Format of TXT record body: X-IPsec-Server ( nnn ) = iii kkk + * nnn is a 16-bit unsigned integer preference + * iii is @FQDN or dotted-decimal IPv4 address or colon-hex IPv6 address + * kkk is an optional RSA public signing key in base 64. + * + * NOTE: we've got to be very wary of anything we find -- bad guys + * might have prepared it. + */ + +#define our_TXT_attr_string "X-IPsec-Server" +static const char our_TXT_attr[] = our_TXT_attr_string; + +static err_t +decode_iii(u_char **pp, struct id *gw_id) +{ + u_char *p = *pp + strspn(*pp, " \t"); + u_char *e = p + strcspn(p, " \t"); + u_char under = *e; + + if (p == e) + return "TXT " our_TXT_attr_string " badly formed (no gateway specified)"; + + *e = '\0'; + if (*p == '@') + { + /* gateway specification in this record is @FQDN */ + err_t ugh = atoid(p, gw_id, FALSE); + + if (ugh != NULL) + return builddiag("malformed FQDN in TXT " our_TXT_attr_string ": %s" + , ugh); + } + else + { + /* gateway specification is numeric */ + ip_address ip; + err_t ugh = tnatoaddr(p, e-p + , strchr(p, ':') == NULL? AF_INET : AF_INET6 + , &ip); + + if (ugh != NULL) + return builddiag("malformed IP address in TXT " our_TXT_attr_string ": %s" + , ugh); + + if (isanyaddr(&ip)) + return "gateway address must not be 0.0.0.0 or 0::0"; + + iptoid(&ip, gw_id); + } + + *e = under; + *pp = e + strspn(e, " \t"); + + return NULL; +} + +static err_t +process_txt_rr_body(u_char *str +, bool doit /* should we capture information? */ +, enum dns_auth_level dns_auth_level +, struct adns_continuation *const cr) +{ + const struct id *client_id = &cr->id; /* subject of query */ + u_char *p = str; + unsigned long pref = 0; + struct gw_info gi; + + p += strspn(p, " \t"); /* ignore leading whitespace */ + + /* is this for us? */ + if (strncasecmp(p, our_TXT_attr, sizeof(our_TXT_attr)-1) != 0) + return NULL; /* neither interesting nor bad */ + + p += sizeof(our_TXT_attr) - 1; /* ignore our attribute name */ + p += strspn(p, " \t"); /* ignore leading whitespace */ + + /* decode '(' nnn ')' */ + if (*p != '(') + return "X-IPsec-Server missing '('"; + + { + char *e; + + p++; + pref = strtoul(p, &e, 0); + if ((u_char *)e == p) + return "malformed X-IPsec-Server priority"; + + p = e + strspn(e, " \t"); + + if (*p != ')') + return "X-IPsec-Server priority missing ')'"; + + p++; + p += strspn(p, " \t"); + + if (pref > 0xFFFF) + return "X-IPsec-Server priority larger than 0xFFFF"; + } + + /* time for '=' */ + + if (*p != '=') + return "X-IPsec-Server priority missing '='"; + + p++; + p += strspn(p, " \t"); + + /* Decode iii (Security Gateway ID). */ + + zero(&gi); /* before first use */ + + TRY(decode_iii(&p, &gi.gw_id)); /* will need to unshare_id_content */ + + if (!cr->sgw_specified) + { + /* we don't know the peer's ID (because we are initiating + * and we don't know who to initiate with. + * So we're looking for gateway specs with an IP address + */ + if (!id_is_ipaddr(&gi.gw_id)) + { + DBG(DBG_DNS, + { + char cidb[BUF_LEN]; + char gwidb[BUF_LEN]; + + idtoa(client_id, cidb, sizeof(cidb)); + idtoa(&gi.gw_id, gwidb, sizeof(gwidb)); + DBG_log("TXT %s record for %s: security gateway %s;" + " ignored because gateway's IP is unspecified" + , our_TXT_attr, cidb, gwidb); + }); + return NULL; /* we cannot use this record, but it isn't wrong */ + } + } + else + { + /* We do know the peer's ID (because we are responding) + * So we're looking for gateway specs specifying this known ID. + */ + const struct id *peer_id = &cr->sgw_id; + + if (!same_id(peer_id, &gi.gw_id)) + { + DBG(DBG_DNS, + { + char cidb[BUF_LEN]; + char gwidb[BUF_LEN]; + char pidb[BUF_LEN]; + + idtoa(client_id, cidb, sizeof(cidb)); + idtoa(&gi.gw_id, gwidb, sizeof(gwidb)); + idtoa(peer_id, pidb, sizeof(pidb)); + DBG_log("TXT %s record for %s: security gateway %s;" + " ignored -- looking to confirm %s as gateway" + , our_TXT_attr, cidb, gwidb, pidb); + }); + return NULL; /* we cannot use this record, but it isn't wrong */ + } + } + + if (doit) + { + /* really accept gateway */ + struct gw_info **gwip; /* gateway insertion point */ + + gi.client_id = *client_id; /* will need to unshare_id_content */ + + /* decode optional kkk: base 64 encoding of key */ + + gi.gw_key_present = *p != '\0'; + if (gi.gw_key_present) + { + /* Decode base 64 encoding of key. + * Similar code is in process_lwdnsq_key. + */ + u_char kb[RSA_MAX_ENCODING_BYTES]; /* plenty of space for binary form of public key */ + chunk_t kbc; + struct RSA_public_key r; + + err_t ugh = ttodatav(p, 0, 64, kb, sizeof(kb), &kbc.len + , diag_space, sizeof(diag_space), TTODATAV_SPACECOUNTS); + + if (ugh != NULL) + return builddiag("malformed key data: %s", ugh); + + if (kbc.len > sizeof(kb)) + return builddiag("key data larger than %lu bytes" + , (unsigned long) sizeof(kb)); + + kbc.ptr = kb; + ugh = unpack_RSA_public_key(&r, &kbc); + if (ugh != NULL) + return builddiag("invalid key data: %s", ugh); + + /* now find a key entry to put it in */ + gi.key = public_key_from_rsa(&r); + + free_RSA_public_content(&r); + + unreference_key(&cr->last_info); + cr->last_info = reference_key(gi.key); + } + + /* we're home free! Allocate everything and add to gateways list. */ + gi.refcnt = 1; + gi.pref = pref; + gi.key->dns_auth_level = dns_auth_level; + gi.key->last_tried_time = gi.key->last_worked_time = NO_TIME; + + /* find insertion point */ + for (gwip = &cr->gateways_from_dns; *gwip != NULL && (*gwip)->pref < pref; gwip = &(*gwip)->next) + ; + + DBG(DBG_DNS, + { + char cidb[BUF_LEN]; + char gwidb[BUF_LEN]; + + idtoa(client_id, cidb, sizeof(cidb)); + idtoa(&gi.gw_id, gwidb, sizeof(gwidb)); + if (gi.gw_key_present) + { + DBG_log("gateway for %s is %s with key %s" + , cidb, gwidb, gi.key->u.rsa.keyid); + } + else + { + DBG_log("gateway for %s is %s; no key specified" + , cidb, gwidb); + } + }); + + gi.next = *gwip; + *gwip = clone_thing(gi, "gateway info"); + unshare_id_content(&(*gwip)->gw_id); + unshare_id_content(&(*gwip)->client_id); + } + + return NULL; +} + +static const char * +rr_typename(int type) +{ + switch (type) + { + case T_TXT: + return "TXT"; + case T_KEY: + return "KEY"; + default: + return "???"; + } +} + + +#ifdef USE_LWRES + +# ifdef USE_KEYRR +static err_t +process_lwdnsq_key(u_char *str +, enum dns_auth_level dns_auth_level +, struct adns_continuation *const cr) +{ + /* fields of KEY record. See RFC 2535 3.1 KEY RDATA format. */ + unsigned long flags /* 16 bits */ + , protocol /* 8 bits */ + , algorithm; /* 8 bits */ + + char *rest = str + , *p + , *endofnumber; + + /* flags */ + p = strsep(&rest, " \t"); + if (p == NULL) + return "lwdnsq KEY: missing flags"; + + flags = strtoul(p, &endofnumber, 10); + if (*endofnumber != '\0') + return "lwdnsq KEY: malformed flags"; + + /* protocol */ + p = strsep(&rest, " \t"); + if (p == NULL) + return "lwdnsq KEY: missing protocol"; + + protocol = strtoul(p, &endofnumber, 10); + if (*endofnumber != '\0') + return "lwdnsq KEY: malformed protocol"; + + /* algorithm */ + p = strsep(&rest, " \t"); + if (p == NULL) + return "lwdnsq KEY: missing algorithm"; + + algorithm = strtoul(p, &endofnumber, 10); + if (*endofnumber != '\0') + return "lwdnsq KEY: malformed algorithm"; + + /* is this key interesting? */ + if (protocol == 4 /* IPSEC (RFC 2535 3.1.3) */ + && algorithm == 1 /* RSA/MD5 (RFC 2535 3.2) */ + && (flags & 0x8000ul) == 0 /* use for authentication (3.1.2) */ + && (flags & 0x2CF0ul) == 0) /* must be zero */ + { + /* Decode base 64 encoding of key. + * Similar code is in process_txt_rr_body. + */ + u_char kb[RSA_MAX_ENCODING_BYTES]; /* plenty of space for binary form of public key */ + chunk_t kbc; + err_t ugh = ttodatav(rest, 0, 64, kb, sizeof(kb), &kbc.len + , diag_space, sizeof(diag_space), TTODATAV_IGNORESPACE); + + if (ugh != NULL) + return builddiag("malformed key data: %s", ugh); + + if (kbc.len > sizeof(kb)) + return builddiag("key data larger than %lu bytes" + , (unsigned long) sizeof(kb)); + + kbc.ptr = kb; + TRY(add_public_key(&cr->id, dns_auth_level, PUBKEY_ALG_RSA, &kbc + , &cr->keys_from_dns)); + + /* keep a reference to last one */ + unreference_key(&cr->last_info); + cr->last_info = reference_key(cr->keys_from_dns->key); + } + return NULL; +} +# endif /* USE_KEYRR */ + +#else /* ! USE_LWRES */ + +/* structure of Query Reply (RFC 1035 4.1.1): + * + * +---------------------+ + * | Header | + * +---------------------+ + * | Question | the question for the name server + * +---------------------+ + * | Answer | RRs answering the question + * +---------------------+ + * | Authority | RRs pointing toward an authority + * +---------------------+ + * | Additional | RRs holding additional information + * +---------------------+ + */ + +/* Header section format (as modified by RFC 2535 6.1): + * 1 1 1 1 1 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | ID | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * |QR| Opcode |AA|TC|RD|RA| Z|AD|CD| RCODE | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | QDCOUNT | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | ANCOUNT | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | NSCOUNT | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | ARCOUNT | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + */ +struct qr_header { + u_int16_t id; /* 16-bit identifier to match query */ + + u_int16_t stuff; /* packed crud: */ + +#define QRS_QR 0x8000 /* QR: on if this is a response */ + +#define QRS_OPCODE_SHIFT 11 /* OPCODE field */ +#define QRS_OPCODE_MASK 0xF +#define QRSO_QUERY 0 /* standard query */ +#define QRSO_IQUERY 1 /* inverse query */ +#define QRSO_STATUS 2 /* server status request query */ + +#define QRS_AA 0x0400 /* AA: on if Authoritative Answer */ +#define QRS_TC 0x0200 /* TC: on if truncation happened */ +#define QRS_RD 0x0100 /* RD: on if recursion desired */ +#define QRS_RA 0x0080 /* RA: on if recursion available */ +#define QRS_Z 0x0040 /* Z: reserved; must be zero */ +#define QRS_AD 0x0020 /* AD: on if authentic data (RFC 2535) */ +#define QRS_CD 0x0010 /* AD: on if checking disabled (RFC 2535) */ + +#define QRS_RCODE_SHIFT 0 /* RCODE field: response code */ +#define QRS_RCODE_MASK 0xF +#define QRSR_OK 0 + + + u_int16_t qdcount; /* number of entries in question section */ + u_int16_t ancount; /* number of resource records in answer section */ + u_int16_t nscount; /* number of name server resource records in authority section */ + u_int16_t arcount; /* number of resource records in additional records section */ +}; + +static field_desc qr_header_fields[] = { + { ft_nat, 16/BITS_PER_BYTE, "ID", NULL }, + { ft_nat, 16/BITS_PER_BYTE, "stuff", NULL }, + { ft_nat, 16/BITS_PER_BYTE, "QD Count", NULL }, + { ft_nat, 16/BITS_PER_BYTE, "Answer Count", NULL }, + { ft_nat, 16/BITS_PER_BYTE, "Authority Count", NULL }, + { ft_nat, 16/BITS_PER_BYTE, "Additional Count", NULL }, + { ft_end, 0, NULL, NULL } +}; + +static struct_desc qr_header_desc = { + "Query Response Header", + qr_header_fields, + sizeof(struct qr_header) +}; + +/* Messages for codes in RCODE (see RFC 1035 4.1.1) */ +static const err_t rcode_text[QRS_RCODE_MASK + 1] = { + NULL, /* not an error */ + "Format error - The name server was unable to interpret the query", + "Server failure - The name server was unable to process this query" + " due to a problem with the name server", + "Name Error - Meaningful only for responses from an authoritative name" + " server, this code signifies that the domain name referenced in" + " the query does not exist", + "Not Implemented - The name server does not support the requested" + " kind of query", + "Refused - The name server refuses to perform the specified operation" + " for policy reasons", + /* the rest are reserved for future use */ + }; + +/* throw away a possibly compressed domain name */ + +static err_t +eat_name(pb_stream *pbs) +{ + u_char name_buf[NS_MAXDNAME + 2]; + u_char *ip = pbs->cur; + unsigned oi = 0; + unsigned jump_count = 0; + + for (;;) + { + u_int8_t b; + + if (ip >= pbs->roof) + return "ran out of message while skipping domain name"; + + b = *ip++; + if (jump_count == 0) + pbs->cur = ip; + + if (b == 0) + break; + + switch (b & 0xC0) + { + case 0x00: + /* we grab the next b characters */ + if (oi + b > NS_MAXDNAME) + return "domain name too long"; + + if (pbs->roof - ip <= b) + return "domain name falls off end of message"; + + if (oi != 0) + name_buf[oi++] = '.'; + + memcpy(name_buf + oi, ip, b); + oi += b; + ip += b; + if (jump_count == 0) + pbs->cur = ip; + break; + + case 0xC0: + { + unsigned ix; + + if (ip >= pbs->roof) + return "ran out of message in middle of compressed domain name"; + + ix = ((b & ~0xC0u) << 8) | *ip++; + if (jump_count == 0) + pbs->cur = ip; + + if (ix >= pbs_room(pbs)) + return "impossible compressed domain name"; + + /* Avoid infinite loop. + * There can be no more jumps than there are bytes + * in the packet. Not a tight limit, but good enough. + */ + jump_count++; + if (jump_count > pbs_room(pbs)) + return "loop in compressed domain name"; + + ip = pbs->start + ix; + } + break; + + default: + return "invalid code in label"; + } + } + + name_buf[oi++] = '\0'; + + DBG(DBG_DNS, DBG_log("skipping name %s", name_buf)); + + return NULL; +} + +static err_t +eat_name_helpfully(pb_stream *pbs, const char *context) +{ + err_t ugh = eat_name(pbs); + + return ugh == NULL? ugh + : builddiag("malformed name within DNS record of %s: %s", context, ugh); +} + +/* non-variable part of 4.1.2 Question Section entry: + * 1 1 1 1 1 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | | + * / QNAME / + * / / + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | QTYPE | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | QCLASS | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + */ + +struct qs_fixed { + u_int16_t qtype; + u_int16_t qclass; +}; + +static field_desc qs_fixed_fields[] = { + { ft_loose_enum, 16/BITS_PER_BYTE, "QTYPE", &rr_qtype_names }, + { ft_loose_enum, 16/BITS_PER_BYTE, "QCLASS", &rr_class_names }, + { ft_end, 0, NULL, NULL } +}; + +static struct_desc qs_fixed_desc = { + "Question Section entry fixed part", + qs_fixed_fields, + sizeof(struct qs_fixed) +}; + +/* 4.1.3. Resource record format: + * 1 1 1 1 1 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | | + * / / + * / NAME / + * | | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | TYPE | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | CLASS | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | TTL | + * | | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | RDLENGTH | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| + * / RDATA / + * / / + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + */ + +struct rr_fixed { + u_int16_t type; + u_int16_t class; + u_int32_t ttl; /* actually signed */ + u_int16_t rdlength; +}; + + +static field_desc rr_fixed_fields[] = { + { ft_loose_enum, 16/BITS_PER_BYTE, "type", &rr_type_names }, + { ft_loose_enum, 16/BITS_PER_BYTE, "class", &rr_class_names }, + { ft_nat, 32/BITS_PER_BYTE, "TTL", NULL }, + { ft_nat, 16/BITS_PER_BYTE, "RD length", NULL }, + { ft_end, 0, NULL, NULL } +}; + +static struct_desc rr_fixed_desc = { + "Resource Record fixed part", + rr_fixed_fields, + /* note: following is tricky: avoids padding problems */ + offsetof(struct rr_fixed, rdlength) + sizeof(u_int16_t) +}; + +/* RFC 1035 3.3.14: TXT RRs have text in the RDATA field. + * It is in the form of a sequence of <character-string>s as described in 3.3. + * unpack_txt_rdata() deals with this peculiar representation. + */ + +/* RFC 2535 3.1 KEY RDATA format: + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | flags | protocol | algorithm | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | / + * / public key / + * / / + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| + */ + +struct key_rdata { + u_int16_t flags; + u_int8_t protocol; + u_int8_t algorithm; +}; + +static field_desc key_rdata_fields[] = { + { ft_nat, 16/BITS_PER_BYTE, "flags", NULL }, + { ft_nat, 8/BITS_PER_BYTE, "protocol", NULL }, + { ft_nat, 8/BITS_PER_BYTE, "algorithm", NULL }, + { ft_end, 0, NULL, NULL } +}; + +static struct_desc key_rdata_desc = { + "KEY RR RData fixed part", + key_rdata_fields, + sizeof(struct key_rdata) +}; + +/* RFC 2535 4.1 SIG RDATA format: + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | type covered | algorithm | labels | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | original TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | signature expiration | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | signature inception | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | key tag | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ signer's name + + * | / + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-/ + * / / + * / signature / + * / / + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +struct sig_rdata { + u_int16_t type_covered; + u_int8_t algorithm; + u_int8_t labels; + u_int32_t original_ttl; + u_int32_t sig_expiration; + u_int32_t sig_inception; + u_int16_t key_tag; +}; + +static field_desc sig_rdata_fields[] = { + { ft_nat, 16/BITS_PER_BYTE, "type_covered", NULL}, + { ft_nat, 8/BITS_PER_BYTE, "algorithm", NULL}, + { ft_nat, 8/BITS_PER_BYTE, "labels", NULL}, + { ft_nat, 32/BITS_PER_BYTE, "original ttl", NULL}, + { ft_nat, 32/BITS_PER_BYTE, "sig expiration", NULL}, + { ft_nat, 32/BITS_PER_BYTE, "sig inception", NULL}, + { ft_nat, 16/BITS_PER_BYTE, "key tag", NULL}, + { ft_end, 0, NULL, NULL } +}; + +static struct_desc sig_rdata_desc = { + "SIG RR RData fixed part", + sig_rdata_fields, + sizeof(struct sig_rdata) +}; + +/* handle a KEY Resource Record. */ + +#ifdef USE_KEYRR +static err_t +process_key_rr(u_char *ptr, size_t len +, bool doit /* should we capture information? */ +, enum dns_auth_level dns_auth_level +, struct adns_continuation *const cr) +{ + pb_stream pbs; + struct key_rdata kr; + + if (len < sizeof(struct key_rdata)) + return "KEY Resource Record's RD Length is too small"; + + init_pbs(&pbs, ptr, len, "KEY RR"); + + if (!in_struct(&kr, &key_rdata_desc, &pbs, NULL)) + return "failed to get fixed part of KEY Resource Record RDATA"; + + if (kr.protocol == 4 /* IPSEC (RFC 2535 3.1.3) */ + && kr.algorithm == 1 /* RSA/MD5 (RFC 2535 3.2) */ + && (kr.flags & 0x8000) == 0 /* use for authentication (3.1.2) */ + && (kr.flags & 0x2CF0) == 0) /* must be zero */ + { + /* we have what seems to be a tasty key */ + + if (doit) + { + chunk_t k; + + setchunk(k, pbs.cur, pbs_left(&pbs)); + TRY(add_public_key(&cr->id, dns_auth_level, PUBKEY_ALG_RSA, &k + , &cr->keys_from_dns)); + } + } + return NULL; +} +#endif /* USE_KEYRR */ + + +/* unpack TXT rr RDATA into C string. + * A sequence of <character-string>s as described in RFC 1035 3.3. + * We concatenate them. + */ +static err_t +unpack_txt_rdata(u_char *d, size_t dlen, const u_char *s, size_t slen) +{ + size_t i = 0 + , o = 0; + + while (i < slen) + { + size_t cl = s[i++]; + + if (i + cl > slen) + return "TXT rr RDATA representation malformed"; + + if (o + cl >= dlen) + return "TXT rr RDATA too large"; + + memcpy(d + o, s + i, cl); + i += cl; + o += cl; + } + d[o] = '\0'; + if (strlen(d) != o) + return "TXT rr RDATA contains a NUL"; + + return NULL; +} + +static err_t +process_txt_rr(u_char *rdata, size_t rdlen +, bool doit /* should we capture information? */ +, enum dns_auth_level dns_auth_level +, struct adns_continuation *const cr) +{ + u_char str[RSA_MAX_ENCODING_BYTES * 8 / 6 + 20]; /* space for unpacked RDATA */ + + TRY(unpack_txt_rdata(str, sizeof(str), rdata, rdlen)); + return process_txt_rr_body(str, doit, dns_auth_level, cr); +} + +static err_t +process_answer_section(pb_stream *pbs +, bool doit /* should we capture information? */ +, enum dns_auth_level *dns_auth_level +, u_int16_t ancount /* number of RRs in the answer section */ +, struct adns_continuation *const cr) +{ + const int type = cr->query.type; /* type of RR of interest */ + unsigned c; + + DBG(DBG_DNS, DBG_log("*Answer Section:")); + + for (c = 0; c != ancount; c++) + { + struct rr_fixed rrf; + size_t tail; + + /* ??? do we need to match the name? */ + + TRY(eat_name_helpfully(pbs, "Answer Section")); + + if (!in_struct(&rrf, &rr_fixed_desc, pbs, NULL)) + return "failed to get fixed part of Answer Section Resource Record"; + + if (rrf.rdlength > pbs_left(pbs)) + return "RD Length extends beyond end of message"; + + /* ??? should we care about ttl? */ + + tail = rrf.rdlength; + + if (rrf.type == type && rrf.class == C_IN) + { + err_t ugh = NULL; + + switch (type) + { +#ifdef USE_KEYRR + case T_KEY: + ugh = process_key_rr(pbs->cur, tail, doit, *dns_auth_level, cr); + break; +#endif /* USE_KEYRR */ + case T_TXT: + ugh = process_txt_rr(pbs->cur, tail, doit, *dns_auth_level, cr); + break; + case T_SIG: + /* Check if SIG RR authenticates what we are learning. + * The RRset covered by a SIG must have the same owner, + * class, and type. + * For us, the class is always C_IN, so that matches. + * We decode the SIG RR's fixed part to check + * that the type_covered field matches our query type + * (this may be redundant). + * We don't check the owner (apparently this is the + * name on the record) -- we assume that it matches + * or we would not have been given this SIG in the + * Answer Section. + * + * We only look on first pass, and only if we've something + * to learn. This cuts down on useless decoding. + */ + if (!doit && *dns_auth_level == DAL_UNSIGNED) + { + struct sig_rdata sr; + + if (!in_struct(&sr, &sig_rdata_desc, pbs, NULL)) + ugh = "failed to get fixed part of SIG Resource Record RDATA"; + else if (sr.type_covered == type) + *dns_auth_level = DAL_SIGNED; + } + break; + default: + ugh = builddiag("unexpected RR type %d", type); + break; + } + if (ugh != NULL) + return ugh; + } + in_raw(NULL, tail, pbs, "RR RDATA"); + } + + return doit + && cr->gateways_from_dns == NULL +#ifdef USE_KEYRR + && cr->keys_from_dns == NULL +#endif /* USE_KEYRR */ + ? builddiag("no suitable %s record found in DNS", rr_typename(type)) + : NULL; +} + +/* process DNS answer -- TXT or KEY query */ + +static err_t +process_dns_answer(struct adns_continuation *const cr +, u_char ans[], int anslen) +{ + const int type = cr->query.type; /* type of record being sought */ + int r; /* all-purpose return value holder */ + u_int16_t c; /* number of current RR in current answer section */ + pb_stream pbs; + u_int8_t *ans_start; /* saved position of answer section */ + struct qr_header qr_header; + enum dns_auth_level dns_auth_level; + + init_pbs(&pbs, ans, anslen, "Query Response Message"); + + /* decode and check header */ + + if (!in_struct(&qr_header, &qr_header_desc, &pbs, NULL)) + return "malformed header"; + + /* ID: nothing to do with us */ + + /* stuff -- lots of things */ + if ((qr_header.stuff & QRS_QR) == 0) + return "not a response?!?"; + + if (((qr_header.stuff >> QRS_OPCODE_SHIFT) & QRS_OPCODE_MASK) != QRSO_QUERY) + return "unexpected opcode"; + + /* I don't think we care about AA */ + + if (qr_header.stuff & QRS_TC) + return "response truncated"; + + /* I don't think we care about RD, RA, or CD */ + + /* AD means "authentic data" */ + dns_auth_level = qr_header.stuff & QRS_AD? DAL_UNSIGNED : DAL_NOTSEC; + + if (qr_header.stuff & QRS_Z) + return "Z bit is not zero"; + + r = (qr_header.stuff >> QRS_RCODE_SHIFT) & QRS_RCODE_MASK; + if (r != 0) + return r < (int)elemsof(rcode_text)? rcode_text[r] : "unknown rcode"; + + if (qr_header.ancount == 0) + return builddiag("no %s RR found by DNS", rr_typename(type)); + + /* end of header checking */ + + /* Question Section processing */ + + /* 4.1.2. Question section format: + * 1 1 1 1 1 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | | + * / QNAME / + * / / + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | QTYPE | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | QCLASS | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + */ + + DBG(DBG_DNS, DBG_log("*Question Section:")); + + for (c = 0; c != qr_header.qdcount; c++) + { + struct qs_fixed qsf; + + TRY(eat_name_helpfully(&pbs, "Question Section")); + + if (!in_struct(&qsf, &qs_fixed_desc, &pbs, NULL)) + return "failed to get fixed part of Question Section"; + + if (qsf.qtype != type) + return "unexpected QTYPE in Question Section"; + + if (qsf.qclass != C_IN) + return "unexpected QCLASS in Question Section"; + } + + /* rest of sections are made up of Resource Records */ + + /* Answer Section processing -- error checking, noting T_SIG */ + + ans_start = pbs.cur; /* remember start of answer section */ + + TRY(process_answer_section(&pbs, FALSE, &dns_auth_level + , qr_header.ancount, cr)); + + /* Authority Section processing (just sanity checking) */ + + DBG(DBG_DNS, DBG_log("*Authority Section:")); + + for (c = 0; c != qr_header.nscount; c++) + { + struct rr_fixed rrf; + size_t tail; + + TRY(eat_name_helpfully(&pbs, "Authority Section")); + + if (!in_struct(&rrf, &rr_fixed_desc, &pbs, NULL)) + return "failed to get fixed part of Authority Section Resource Record"; + + if (rrf.rdlength > pbs_left(&pbs)) + return "RD Length extends beyond end of message"; + + /* ??? should we care about ttl? */ + + tail = rrf.rdlength; + + in_raw(NULL, tail, &pbs, "RR RDATA"); + } + + /* Additional Section processing (just sanity checking) */ + + DBG(DBG_DNS, DBG_log("*Additional Section:")); + + for (c = 0; c != qr_header.arcount; c++) + { + struct rr_fixed rrf; + size_t tail; + + TRY(eat_name_helpfully(&pbs, "Additional Section")); + + if (!in_struct(&rrf, &rr_fixed_desc, &pbs, NULL)) + return "failed to get fixed part of Additional Section Resource Record"; + + if (rrf.rdlength > pbs_left(&pbs)) + return "RD Length extends beyond end of message"; + + /* ??? should we care about ttl? */ + + tail = rrf.rdlength; + + in_raw(NULL, tail, &pbs, "RR RDATA"); + } + + /* done all sections */ + + /* ??? is padding legal, or can we complain if more left in record? */ + + /* process Answer Section again -- accept contents */ + + pbs.cur = ans_start; /* go back to start of answer section */ + + return process_answer_section(&pbs, TRUE, &dns_auth_level + , qr_header.ancount, cr); +} + +#endif /* ! USE_LWRES */ + + +/****************************************************************/ + +static err_t +build_dns_name(u_char name_buf[NS_MAXDNAME + 2] +, unsigned long serial USED_BY_DEBUG +, const struct id *id +, const char *typename USED_BY_DEBUG +, const char *gwname USED_BY_DEBUG) +{ + /* note: all end in "." to suppress relative searches */ + id = resolve_myid(id); + switch (id->kind) + { + case ID_IPV4_ADDR: + { + /* XXX: this is really ugly and only temporary until addrtot can + * generate the correct format + */ + const unsigned char *b; + size_t bl USED_BY_DEBUG = addrbytesptr(&id->ip_addr, &b); + + passert(bl == 4); + snprintf(name_buf, NS_MAXDNAME + 2, "%d.%d.%d.%d.in-addr.arpa." + , b[3], b[2], b[1], b[0]); + break; + } + + case ID_IPV6_ADDR: + { + /* ??? is this correct? */ + const unsigned char *b; + size_t bl; + u_char *op = name_buf; + static const char suffix[] = "IP6.INT."; + + for (bl = addrbytesptr(&id->ip_addr, &b); bl-- != 0; ) + { + if (op + 4 + sizeof(suffix) >= name_buf + NS_MAXDNAME + 1) + return "IPv6 reverse name too long"; + op += sprintf(op, "%x.%x.", b[bl] & 0xF, b[bl] >> 4); + } + strcpy(op, suffix); + break; + } + + case ID_FQDN: + /* strip trailing "." characters, then add one */ + { + size_t il = id->name.len; + + while (il > 0 && id->name.ptr[il - 1] == '.') + il--; + if (il > NS_MAXDNAME) + return "FQDN too long for domain name"; + + memcpy(name_buf, id->name.ptr, il); + strcpy(name_buf + il, "."); + } + break; + + default: + return "can only query DNS for key for ID that is a FQDN, IPV4_ADDR, or IPV6_ADDR"; + } + + DBG(DBG_CONTROL | DBG_DNS, DBG_log("DNS query %lu for %s for %s (gw: %s)" + , serial, typename, name_buf, gwname)); + return NULL; +} + +void +gw_addref(struct gw_info *gw) +{ + if (gw != NULL) + { + DBG(DBG_DNS, DBG_log("gw_addref: %p refcnt: %d++", gw, gw->refcnt)) + gw->refcnt++; + } +} + +void +gw_delref(struct gw_info **gwp) +{ + struct gw_info *gw = *gwp; + + if (gw != NULL) + { + DBG(DBG_DNS, DBG_log("gw_delref: %p refcnt: %d--", gw, gw->refcnt)); + + passert(gw->refcnt != 0); + gw->refcnt--; + if (gw->refcnt == 0) + { + free_id_content(&gw->client_id); + free_id_content(&gw->gw_id); + if (gw->gw_key_present) + unreference_key(&gw->key); + gw_delref(&gw->next); + pfree(gw); /* trickery could make this a tail-call */ + } + *gwp = NULL; + } +} + +static int adns_in_flight = 0; /* queries outstanding */ + +/* Start an asynchronous DNS query. + * + * For KEY record, the result will be a list in cr->keys_from_dns. + * For TXT records, the result will be a list in cr->gateways_from_dns. + * + * If sgw_id is null, only consider TXT records that specify an + * IP address for the gatway: we need this in the initiation case. + * + * If sgw_id is non-null, only consider TXT records that specify + * this id as the security gatway; this is useful to the Responder + * for confirming claims of gateways. + * + * Continuation cr gives information for continuing when the result shows up. + * + * Two kinds of errors must be handled: synchronous (immediate) + * and asynchronous. Synchronous errors are indicated by the returned + * value of start_adns_query; in this case, the continuation will + * have been freed and the continuation routine will not be called. + * Asynchronous errors are indicated by the ugh parameter passed to the + * continuation routine. + * + * After the continuation routine has completed, handle_adns_answer + * will free the continuation. The continuation routine should have + * freed any axiliary resources. + * + * Note: in the synchronous error case, start_adns_query will have + * freed the continuation; this means that the caller will have to + * be very careful to release any auxiliary resources that were in + * the continuation record without using the continuation record. + * + * Either there will be an error result passed to the continuation routine, + * or the results will be in cr->keys_from_dns or cr->gateways_from_dns. + * The result variables must by left NULL by the continutation routine. + * The continuation routine is responsible for establishing and + * disestablishing any logging context (whack_log_fd, cur_*). + */ + +static struct adns_continuation *continuations = NULL; /* newest of queue */ +static struct adns_continuation *next_query = NULL; /* oldest not sent */ + +static struct adns_continuation * +continuation_for_qtid(unsigned long qtid) +{ + struct adns_continuation *cr = NULL; + + if (qtid != 0) + for (cr = continuations; cr != NULL && cr->qtid != qtid; cr = cr->previous) + ; + return cr; +} + +static void +release_adns_continuation(struct adns_continuation *cr) +{ + passert(cr != next_query); + gw_delref(&cr->gateways_from_dns); +#ifdef USE_KEYRR + free_public_keys(&cr->keys_from_dns); +#endif /* USE_KEYRR */ + unshare_id_content(&cr->id); + unshare_id_content(&cr->sgw_id); + + /* unlink from doubly-linked list */ + if (cr->next == NULL) + { + passert(continuations == cr); + continuations = cr->previous; + } + else + { + passert(cr->next->previous == cr); + cr->next->previous = cr->previous; + } + + if (cr->previous != NULL) + { + passert(cr->previous->next == cr); + cr->previous->next = cr->next; + } + + pfree(cr); +} + +err_t +start_adns_query(const struct id *id /* domain to query */ +, const struct id *sgw_id /* if non-null, any accepted gw_info must match */ +, int type /* T_TXT or T_KEY, selecting rr type of interest */ +, cont_fn_t cont_fn +, struct adns_continuation *cr) +{ + static unsigned long qtid = 1; /* query transaction id; NOTE: static */ + const char *typename = rr_typename(type); + char gwidb[BUF_LEN]; + + if(adns_pid == 0 + && adns_restart_count < ADNS_RESTART_MAX) + { + plog("ADNS helper was not running. Restarting attempt %d",adns_restart_count); + init_adns(); + } + + + /* Splice this in at head of doubly-linked list of continuations. + * Note: this must be done before any release_adns_continuation(). + */ + cr->next = NULL; + cr->previous = continuations; + if (continuations != NULL) + { + passert(continuations->next == NULL); + continuations->next = cr; + } + continuations = cr; + + cr->qtid = qtid++; + cr->type = type; + cr->cont_fn = cont_fn; + cr->id = *id; + unshare_id_content(&cr->id); + cr->sgw_specified = sgw_id != NULL; + cr->sgw_id = cr->sgw_specified? *sgw_id : empty_id; + unshare_id_content(&cr->sgw_id); + cr->gateways_from_dns = NULL; +#ifdef USE_KEYRR + cr->keys_from_dns = NULL; +#endif /* USE_KEYRR */ + +#ifdef DEBUG + cr->debugging = cur_debugging; +#else + cr->debugging = LEMPTY; +#endif + + idtoa(&cr->sgw_id, gwidb, sizeof(gwidb)); + + zero(&cr->query); + + { + err_t ugh = build_dns_name(cr->query.name_buf, cr->qtid + , id, typename, gwidb); + + if (ugh != NULL) + { + release_adns_continuation(cr); + return ugh; + } + } + + if (next_query == NULL) + next_query = cr; + + unsent_ADNS_queries = TRUE; + + return NULL; +} + +/* send remaining ADNS queries (until pipe full or none left) + * + * This is a co-routine, so it uses static variables to + * preserve state across calls. + */ +bool unsent_ADNS_queries = FALSE; + +void +send_unsent_ADNS_queries(void) +{ + static const unsigned char *buf_end = NULL; /* NOTE STATIC */ + static const unsigned char *buf_cur = NULL; /* NOTE STATIC */ + + if (adns_qfd == NULL_FD) + return; /* nothing useful to do */ + + for (;;) + { + if (buf_cur != buf_end) + { + static int try = 0; /* NOTE STATIC */ + size_t n = buf_end - buf_cur; + ssize_t r = write(adns_qfd, buf_cur, n); + + if (r == -1) + { + switch (errno) + { + case EINTR: + continue; /* try again now */ + case EAGAIN: + DBG(DBG_DNS, DBG_log("EAGAIN writing to ADNS")); + break; /* try again later */ + default: + try++; + log_errno((e, "error %d writing DNS query", try)); + break; /* try again later */ + } + unsent_ADNS_queries = TRUE; + break; /* done! */ + } + else + { + passert(r >= 0); + try = 0; + buf_cur += r; + } + } + else + { + if (next_query == NULL) + { + unsent_ADNS_queries = FALSE; + break; /* done! */ + } + +#ifdef USE_LWRES + next_query->used = FALSE; + { + /* NOTE STATIC: */ + static unsigned char qbuf[LWDNSQ_CMDBUF_LEN + 1]; /* room for NUL */ + + snprintf(qbuf, sizeof(qbuf), "%s %lu %s\n" + , rr_typename(next_query->type) + , next_query->qtid + , next_query->query.name_buf); + DBG(DBG_DNS, DBG_log("lwdnsq query: %.*s", (int)(strlen(qbuf) - 1), qbuf)); + buf_cur = qbuf; + buf_end = qbuf + strlen(qbuf); + } +#else /* !USE_LWRES */ + next_query->query.debugging = next_query->debugging; + next_query->query.serial = next_query->qtid; + next_query->query.len = sizeof(next_query->query); + next_query->query.qmagic = ADNS_Q_MAGIC; + next_query->query.type = next_query->type; + buf_cur = (const void *)&next_query->query; + buf_end = buf_cur + sizeof(next_query->query); +#endif /* !USE_LWRES */ + next_query = next_query->next; + adns_in_flight++; + } + } +} + +#ifdef USE_LWRES +/* Process a line of lwdnsq answer. + * Returns with error message iff lwdnsq result is malformed. + * Most errors will be in DNS data and will be handled by cr->cont_fn. + */ +static err_t +process_lwdnsq_answer(char *ts) +{ + err_t ugh = NULL; + char *rest; + char *p; + char *endofnumber; + struct adns_continuation *cr = NULL; + unsigned long qtid; + time_t anstime; /* time of answer */ + char *atype; /* type of answer */ + long ttl; /* ttl of answer; int, but long for conversion */ + bool AuthenticatedData = FALSE; + static char scratch_null_str[] = ""; /* cannot be const, but isn't written */ + + /* query transaction id */ + rest = ts; + p = strsep(&rest, " \t"); + if (p == NULL) + return "lwdnsq: answer missing query transaction ID"; + + qtid = strtoul(p, &endofnumber, 10); + if (*endofnumber != '\0') + return "lwdnsq: malformed query transaction ID"; + + cr = continuation_for_qtid(qtid); + if (qtid != 0 && cr == NULL) + return "lwdnsq: unrecognized qtid"; /* can't happen! */ + + /* time */ + p = strsep(&rest, " \t"); + if (p == NULL) + return "lwdnsq: missing time"; + + anstime = strtoul(p, &endofnumber, 10); + if (*endofnumber != '\0') + return "lwdnsq: malformed time"; + + /* TTL */ + p = strsep(&rest, " \t"); + if (p == NULL) + return "lwdnsq: missing TTL"; + + ttl = strtol(p, &endofnumber, 10); + if (*endofnumber != '\0') + return "lwdnsq: malformed TTL"; + + /* type */ + atype = strsep(&rest, " \t"); + if (atype == NULL) + return "lwdnsq: missing type"; + + /* if rest is NULL, make it "", otherwise eat whitespace after type */ + rest = rest == NULL? scratch_null_str : rest + strspn(rest, " \t"); + + if (strncasecmp(atype, "AD-", 3) == 0) + { + AuthenticatedData = TRUE; + atype += 3; + } + + /* deal with each type */ + + if (cr == NULL) + { + /* we don't actually know which this applies to */ + return builddiag("lwdnsq: 0 qtid invalid with %s", atype); + } + else if (strcaseeq(atype, "START")) + { + /* ignore */ + } + else if (strcaseeq(atype, "DONE")) + { + if (!cr->used) + { + /* "no results returned by lwdnsq" should not happen */ + cr->cont_fn(cr + , cr->gateways_from_dns == NULL +#ifdef USE_KEYRR + && cr->keys_from_dns == NULL +#endif /* USE_KEYRR */ + ? "no results returned by lwdnsq" : NULL); + cr->used = TRUE; + } + reset_globals(); + release_adns_continuation(cr); + adns_in_flight--; + } + else if (strcaseeq(atype, "RETRY")) + { + if (!cr->used) + { + cr->cont_fn(cr, rest); + cr->used = TRUE; + } + } + else if (strcaseeq(atype, "FATAL")) + { + if (!cr->used) + { + cr->cont_fn(cr, rest); + cr->used = TRUE; + } + } + else if (strcaseeq(atype, "DNSSEC")) + { + /* ignore */ + } + else if (strcaseeq(atype, "NAME")) + { + /* ignore */ + } + else if (strcaseeq(atype, "TXT")) + { + char *end = rest + strlen(rest); + err_t txt_ugh; + + if (*rest == '"' && end[-1] == '"') + { + /* strip those pesky quotes */ + rest++; + *--end = '\0'; + } + + txt_ugh = process_txt_rr_body(rest + , TRUE + , AuthenticatedData? DAL_SIGNED : DAL_NOTSEC + , cr); + + if (txt_ugh != NULL) + { + DBG(DBG_DNS, + DBG_log("error processing TXT resource record (%s) while processing: %s" + , txt_ugh, rest)); + cr->cont_fn(cr, txt_ugh); + cr->used = TRUE; + } + } + else if (strcaseeq(atype, "SIG")) + { + /* record the SIG records for posterity */ + if (cr->last_info != NULL) + { + pfreeany(cr->last_info->dns_sig); + cr->last_info->dns_sig = clone_str(rest, "sigrecord"); + } + } + else if (strcaseeq(atype, "A")) + { + /* ignore */ + } + else if (strcaseeq(atype, "AAAA")) + { + /* ignore */ + } + else if (strcaseeq(atype, "CNAME")) + { + /* ignore */ + } + else if (strcaseeq(atype, "CNAMEFROM")) + { + /* ignore */ + } + else if (strcaseeq(atype, "PTR")) + { + /* ignore */ + } +#ifdef USE_KEYRR + else if (strcaseeq(atype, "KEY")) + { + err_t key_ugh = process_lwdnsq_key(rest + , AuthenticatedData? DAL_SIGNED : DAL_NOTSEC + , cr); + + if (key_ugh != NULL) + { + DBG(DBG_DNS, + DBG_log("error processing KEY resource record (%s) while processing: %s" + , key_ugh, rest)); + cr->cont_fn(cr, key_ugh); + cr->used = TRUE; + } + } +#endif /* USE_KEYRR */ + else + { + ugh = "lwdnsq: unrecognized type"; + } + return ugh; +} +#endif /* USE_LWRES */ + +static void +recover_adns_die(void) +{ + struct adns_continuation *cr = NULL; + + adns_pid = 0; + if(adns_restart_count < ADNS_RESTART_MAX) { + adns_restart_count++; + + /* next DNS query will restart it */ + + /* we have to walk the list of the outstanding requests, + * and redo them! + */ + + cr = continuations; + + /* find the head of the list */ + if(continuations != NULL) { + for (; cr->previous != NULL; cr = cr->previous); + } + + next_query = cr; + + if(next_query != NULL) { + unsent_ADNS_queries = TRUE; + } + } +} + +void reset_adns_restart_count(void) +{ + adns_restart_count=0; +} + +void +handle_adns_answer(void) +{ + /* These are retained across calls to handle_adns_answer. */ + static size_t buflen = 0; /* bytes in answer buffer */ +#ifndef USE_LWRES + static struct adns_answer buf; +#else /* USE_LWRES */ + static char buf[LWDNSQ_RESULT_LEN_MAX]; + static char buf_copy[LWDNSQ_RESULT_LEN_MAX]; +#endif /* USE_LWRES */ + + ssize_t n; + + passert(buflen < sizeof(buf)); + n = read(adns_afd, (unsigned char *)&buf + buflen, sizeof(buf) - buflen); + + if (n < 0) + { + if (errno != EINTR) + { + log_errno((e, "error reading answer from adns")); + /* ??? how can we recover? */ + } + n = 0; /* now n reflects amount read */ + } + else if (n == 0) + { + /* EOF */ + if (adns_in_flight != 0) + { + plog("EOF from ADNS with %d queries outstanding (restarts %d)" + , adns_in_flight, adns_restart_count); + recover_adns_die(); + } + if (buflen != 0) + { + plog("EOF from ADNS with %lu bytes of a partial answer outstanding" + "(restarts %d)" + , (unsigned long)buflen + , adns_restart_count); + recover_adns_die(); + } + stop_adns(); + return; + } + else + { + passert(adns_in_flight > 0); + } + + buflen += n; +#ifndef USE_LWRES + while (buflen >= offsetof(struct adns_answer, ans) && buflen >= buf.len) + { + /* we've got a tasty answer -- process it */ + err_t ugh; + struct adns_continuation *cr = continuation_for_qtid(buf.serial); /* assume it works */ + const char *typename = rr_typename(cr->query.type); + const char *name_buf = cr->query.name_buf; + +#ifdef USE_KEYRR + passert(cr->keys_from_dns == NULL); +#endif /* USE_KEYRR */ + passert(cr->gateways_from_dns == NULL); + adns_in_flight--; + if (buf.result == -1) + { + /* newer resolvers support statp->res_h_errno as well as h_errno. + * That might be better, but older resolvers don't. + * See resolver(3), if you have it. + * The undocumented(!) h_errno values are defined in + * /usr/include/netdb.h. + */ + switch (buf.h_errno_val) + { + case NO_DATA: + ugh = builddiag("no %s record for %s", typename, name_buf); + break; + case HOST_NOT_FOUND: + ugh = builddiag("no host %s for %s record", name_buf, typename); + break; + default: + ugh = builddiag("failure querying DNS for %s of %s: %s" + , typename, name_buf, hstrerror(buf.h_errno_val)); + break; + } + } + else if (buf.result > (int) sizeof(buf.ans)) + { + ugh = builddiag("(INTERNAL ERROR) answer too long (%ld) for buffer" + , (long)buf.result); + } + else + { + ugh = process_dns_answer(cr, buf.ans, buf.result); + if (ugh != NULL) + ugh = builddiag("failure processing %s record of DNS answer for %s: %s" + , typename, name_buf, ugh); + } + DBG(DBG_RAW | DBG_CRYPT | DBG_PARSING | DBG_CONTROL | DBG_DNS, + DBG_log(BLANK_FORMAT); + if (ugh == NULL) + DBG_log("asynch DNS answer %lu for %s of %s" + , cr->query.serial, typename, name_buf); + else + DBG_log("asynch DNS answer %lu %s", cr->query.serial, ugh); + ); + + passert(GLOBALS_ARE_RESET()); + cr->cont_fn(cr, ugh); + reset_globals(); + release_adns_continuation(cr); + + /* shift out answer that we've consumed */ + buflen -= buf.len; + memmove((unsigned char *)&buf, (unsigned char *)&buf + buf.len, buflen); + } +#else /* USE_LWRES */ + for (;;) + { + err_t ugh; + char *nlp = memchr(buf, '\n', buflen); + + if (nlp == NULL) + break; + + /* we've got a line */ + *nlp++ = '\0'; + + DBG(DBG_RAW | DBG_CRYPT | DBG_PARSING | DBG_CONTROL | DBG_DNS + , DBG_log("lwdns: %s", buf)); + + /* process lwdnsq_answer may modify buf, so make a copy. */ + buf_copy[0]='\0'; + strncat(buf_copy, buf, sizeof(buf_copy)); + + ugh = process_lwdnsq_answer(buf_copy); + if (ugh != NULL) + plog("failure processing lwdnsq output: %s; record: %s" + , ugh, buf); + + passert(GLOBALS_ARE_RESET()); + reset_globals(); + + /* shift out answer that we've consumed */ + buflen -= nlp - buf; + memmove(buf, nlp, buflen); + } +#endif /* USE_LWRES */ +} diff --git a/src/pluto/dnskey.h b/src/pluto/dnskey.h new file mode 100644 index 000000000..0b9f0ee33 --- /dev/null +++ b/src/pluto/dnskey.h @@ -0,0 +1,84 @@ +/* Find public key in DNS + * Copyright (C) 2000-2002 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: dnskey.h,v 1.1 2004/03/15 20:35:28 as Exp $ + */ + +extern int + adns_qfd, /* file descriptor for sending queries to adns */ + adns_afd; /* file descriptor for receiving answers from adns */ +extern const char *pluto_adns_option; /* path from --pluto_adns */ +extern void init_adns(void); +extern void stop_adns(void); +extern void handle_adns_answer(void); + +extern bool unsent_ADNS_queries; +extern void send_unsent_ADNS_queries(void); + +/* (common prefix of) stuff remembered between async query and answer. + * Filled in by start_adns_query. + * Freed by call to release_adns_continuation. + */ + +struct adns_continuation; /* forward declaration (not far!) */ + +typedef void (*cont_fn_t)(struct adns_continuation *cr, err_t ugh); + +struct adns_continuation { + unsigned long qtid; /* query transaction id number */ + int type; /* T_TXT or T_KEY, selecting rr type of interest */ + cont_fn_t cont_fn; /* function to carry on suspended work */ + struct id id; /* subject of query */ + bool sgw_specified; + struct id sgw_id; /* peer, if constrained */ + lset_t debugging; /* only used #ifdef DEBUG, but don't want layout to change */ + struct gw_info *gateways_from_dns; /* answer, if looking for our TXT rrs */ +#ifdef USE_KEYRR + struct pubkey_list *keys_from_dns; /* answer, if looking for KEY rrs */ +#endif + struct adns_continuation *previous, *next; + struct pubkey *last_info; /* the last structure we accumulated */ +#ifdef USE_LWRES + bool used; /* have we called the cont_fn yet? */ + struct { + u_char name_buf[NS_MAXDNAME + 2]; + } query; +#else /* ! USE_LWRES */ + struct adns_query query; +#endif /* ! USE_LWRES */ +}; + +extern err_t start_adns_query(const struct id *id /* domain to query */ + , const struct id *sgw_id /* if non-null, any accepted gw_info must match */ + , int type /* T_TXT or T_KEY, selecting rr type of interest */ + , cont_fn_t cont_fn /* continuation function */ + , struct adns_continuation *cr); + + +/* Gateway info gleaned from reverse DNS of client */ +struct gw_info { + unsigned refcnt; /* reference counted! */ + unsigned pref; /* preference: lower is better */ +#define NO_TIME ((time_t) -2) /* time_t value meaning "not_yet" */ + struct id client_id; /* id of client of peer */ + struct id gw_id; /* id of peer (if id_is_ipaddr, .ip_addr is address) */ + bool gw_key_present; + struct pubkey *key; + struct gw_info *next; +}; + +extern void gw_addref(struct gw_info *gw) + , gw_delref(struct gw_info **gwp); + +extern void reset_adns_restart_count(void); + diff --git a/src/pluto/dsa.c b/src/pluto/dsa.c new file mode 100644 index 000000000..c5982fbf4 --- /dev/null +++ b/src/pluto/dsa.c @@ -0,0 +1,476 @@ +/* dsa.c - DSA signature scheme + * Copyright (C) 1998 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifdef PLUTO +#include <gmp.h> +#include <freeswan.h> +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "rnd.h" +#include "gcryptfix.h" +#else /*! PLUTO */ +/* #include <config.h> */ +#endif /* !PLUTO */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifndef PLUTO +/* #include <assert.h> */ +/* #include "util.h" */ +/* #include "mpi.h" */ +/* #include "cipher.h" */ +#endif + +#include "dsa.h" + +typedef struct { + MPI p; /* prime */ + MPI q; /* group order */ + MPI g; /* group generator */ + MPI y; /* g^x mod p */ +} DSA_public_key; + + +typedef struct { + MPI p; /* prime */ + MPI q; /* group order */ + MPI g; /* group generator */ + MPI y; /* g^x mod p */ + MPI x; /* secret exponent */ +} DSA_secret_key; + + +static MPI gen_k( MPI q ); +static void test_keys( DSA_secret_key *sk, unsigned qbits ); +static int check_secret_key( DSA_secret_key *sk ); +static void generate( DSA_secret_key *sk, unsigned nbits, MPI **ret_factors ); +static void sign(MPI r, MPI s, MPI input, DSA_secret_key *skey); +static int verify(MPI r, MPI s, MPI input, DSA_public_key *pkey); + +static void +progress( int c ) +{ + fputc( c, stderr ); +} + + +/**************** + * Generate a random secret exponent k less than q + */ +static MPI +gen_k( MPI q ) +{ + MPI k = mpi_alloc_secure( mpi_get_nlimbs(q) ); + unsigned int nbits = mpi_get_nbits(q); + unsigned int nbytes = (nbits+7)/8; + char *rndbuf = NULL; + + if( DBG_CIPHER ) + log_debug("choosing a random k "); + for(;;) { + if( DBG_CIPHER ) + progress('.'); + + if( !rndbuf || nbits < 32 ) { + m_free(rndbuf); + rndbuf = get_random_bits( nbits, 1, 1 ); + } + else { /* change only some of the higher bits */ + /* we could imporove this by directly requesting more memory + * at the first call to get_random_bits() and use this the here + * maybe it is easier to do this directly in random.c */ + char *pp = get_random_bits( 32, 1, 1 ); + memcpy( rndbuf,pp, 4 ); + m_free(pp); + } + mpi_set_buffer( k, rndbuf, nbytes, 0 ); + if( mpi_test_bit( k, nbits-1 ) ) + mpi_set_highbit( k, nbits-1 ); + else { + mpi_set_highbit( k, nbits-1 ); + mpi_clear_bit( k, nbits-1 ); + } + + if( !(mpi_cmp( k, q ) < 0) ) { /* check: k < q */ + if( DBG_CIPHER ) + progress('+'); + continue; /* no */ + } + if( !(mpi_cmp_ui( k, 0 ) > 0) ) { /* check: k > 0 */ + if( DBG_CIPHER ) + progress('-'); + continue; /* no */ + } + break; /* okay */ + } + m_free(rndbuf); + if( DBG_CIPHER ) + progress('\n'); + + return k; +} + + +static void +test_keys( DSA_secret_key *sk, unsigned qbits ) +{ + DSA_public_key pk; + MPI test = mpi_alloc( qbits / BITS_PER_MPI_LIMB ); + MPI out1_a = mpi_alloc( qbits / BITS_PER_MPI_LIMB ); + MPI out1_b = mpi_alloc( qbits / BITS_PER_MPI_LIMB ); + + pk.p = sk->p; + pk.q = sk->q; + pk.g = sk->g; + pk.y = sk->y; + /*mpi_set_bytes( test, qbits, get_random_byte, 0 );*/ + { char *p = get_random_bits( qbits, 0, 0 ); + mpi_set_buffer( test, p, (qbits+7)/8, 0 ); + m_free(p); + } + + sign( out1_a, out1_b, test, sk ); + if( !verify( out1_a, out1_b, test, &pk ) ) + log_fatal("DSA:: sign, verify failed\n"); + + mpi_free( test ); + mpi_free( out1_a ); + mpi_free( out1_b ); +} + + + +/**************** + * Generate a DSA key pair with a key of size NBITS + * Returns: 2 structures filled with all needed values + * and an array with the n-1 factors of (p-1) + */ +static void +generate( DSA_secret_key *sk, unsigned nbits, MPI **ret_factors ) +{ + MPI p; /* the prime */ + MPI q; /* the 160 bit prime factor */ + MPI g; /* the generator */ + MPI y; /* g^x mod p */ + MPI x; /* the secret exponent */ + MPI h, e; /* helper */ + unsigned qbits; + byte *rndbuf; + + assert( nbits >= 512 && nbits <= 1024 ); + + qbits = 160; + p = generate_elg_prime( 1, nbits, qbits, NULL, ret_factors ); + /* get q out of factors */ + q = mpi_copy((*ret_factors)[0]); + if( mpi_get_nbits(q) != qbits ) + BUG(); + + /* find a generator g (h and e are helpers)*/ + /* e = (p-1)/q */ + e = mpi_alloc( mpi_get_nlimbs(p) ); + mpi_sub_ui( e, p, 1 ); + mpi_fdiv_q( e, e, q ); + g = mpi_alloc( mpi_get_nlimbs(p) ); + h = mpi_alloc_set_ui( 1 ); /* we start with 2 */ + do { + mpi_add_ui( h, h, 1 ); + /* g = h^e mod p */ + mpi_powm( g, h, e, p ); + } while( !mpi_cmp_ui( g, 1 ) ); /* continue until g != 1 */ + + /* select a random number which has these properties: + * 0 < x < q-1 + * This must be a very good random number because this + * is the secret part. */ + if( DBG_CIPHER ) + log_debug("choosing a random x "); + assert( qbits >= 160 ); + x = mpi_alloc_secure( mpi_get_nlimbs(q) ); + mpi_sub_ui( h, q, 1 ); /* put q-1 into h */ + rndbuf = NULL; + do { + if( DBG_CIPHER ) + progress('.'); + if( !rndbuf ) + rndbuf = get_random_bits( qbits, 2, 1 ); + else { /* change only some of the higher bits (= 2 bytes)*/ + char *r = get_random_bits( 16, 2, 1 ); + memcpy(rndbuf, r, 16/8 ); + m_free(r); + } + mpi_set_buffer( x, rndbuf, (qbits+7)/8, 0 ); + mpi_clear_highbit( x, qbits+1 ); + } while( !( mpi_cmp_ui( x, 0 )>0 && mpi_cmp( x, h )<0 ) ); + m_free(rndbuf); + mpi_free( e ); + mpi_free( h ); + + /* y = g^x mod p */ + y = mpi_alloc( mpi_get_nlimbs(p) ); + mpi_powm( y, g, x, p ); + + if( DBG_CIPHER ) { + progress('\n'); + log_mpidump("dsa p= ", p ); + log_mpidump("dsa q= ", q ); + log_mpidump("dsa g= ", g ); + log_mpidump("dsa y= ", y ); + log_mpidump("dsa x= ", x ); + } + + /* copy the stuff to the key structures */ + sk->p = p; + sk->q = q; + sk->g = g; + sk->y = y; + sk->x = x; + + /* now we can test our keys (this should never fail!) */ + test_keys( sk, qbits ); +} + + + +/**************** + * Test whether the secret key is valid. + * Returns: if this is a valid key. + */ +static int +check_secret_key( DSA_secret_key *sk ) +{ + int rc; + MPI y = mpi_alloc( mpi_get_nlimbs(sk->y) ); + + mpi_powm( y, sk->g, sk->x, sk->p ); + rc = !mpi_cmp( y, sk->y ); + mpi_free( y ); + return rc; +} + + + +/**************** + * Make a DSA signature from HASH and put it into r and s. + */ + +static void +sign(MPI r, MPI s, MPI hash, DSA_secret_key *skey ) +{ + MPI k; + MPI kinv; + MPI tmp; + + /* select a random k with 0 < k < q */ + k = gen_k( skey->q ); + + /* r = (a^k mod p) mod q */ + mpi_powm( r, skey->g, k, skey->p ); + mpi_fdiv_r( r, r, skey->q ); + + /* kinv = k^(-1) mod q */ + kinv = mpi_alloc( mpi_get_nlimbs(k) ); + mpi_invm(kinv, k, skey->q ); + + /* s = (kinv * ( hash + x * r)) mod q */ + tmp = mpi_alloc( mpi_get_nlimbs(skey->p) ); + mpi_mul( tmp, skey->x, r ); + mpi_add( tmp, tmp, hash ); + mpi_mulm( s , kinv, tmp, skey->q ); + + mpi_free(k); + mpi_free(kinv); + mpi_free(tmp); +} + + +/**************** + * Returns true if the signature composed from R and S is valid. + */ +static int +verify(MPI r, MPI s, MPI hash, DSA_public_key *pkey ) +{ + int rc; + MPI w, u1, u2, v; + MPI base[3]; + MPI exp[3]; + + + if( !(mpi_cmp_ui( r, 0 ) > 0 && mpi_cmp( r, pkey->q ) < 0) ) + return 0; /* assertion 0 < r < q failed */ + if( !(mpi_cmp_ui( s, 0 ) > 0 && mpi_cmp( s, pkey->q ) < 0) ) + return 0; /* assertion 0 < s < q failed */ + + w = mpi_alloc( mpi_get_nlimbs(pkey->q) ); + u1 = mpi_alloc( mpi_get_nlimbs(pkey->q) ); + u2 = mpi_alloc( mpi_get_nlimbs(pkey->q) ); + v = mpi_alloc( mpi_get_nlimbs(pkey->p) ); + + /* w = s^(-1) mod q */ + mpi_invm( w, s, pkey->q ); + + /* u1 = (hash * w) mod q */ + mpi_mulm( u1, hash, w, pkey->q ); + + /* u2 = r * w mod q */ + mpi_mulm( u2, r, w, pkey->q ); + + /* v = g^u1 * y^u2 mod p mod q */ + base[0] = pkey->g; exp[0] = u1; + base[1] = pkey->y; exp[1] = u2; + base[2] = NULL; exp[2] = NULL; + mpi_mulpowm( v, base, exp, pkey->p ); + mpi_fdiv_r( v, v, pkey->q ); + + rc = !mpi_cmp( v, r ); + + mpi_free(w); + mpi_free(u1); + mpi_free(u2); + mpi_free(v); + return rc; +} + + +/********************************************* + ************** interface ****************** + *********************************************/ + +int +dsa_generate( int algo, unsigned nbits, MPI *skey, MPI **retfactors ) +{ + DSA_secret_key sk; + + if( algo != PUBKEY_ALGO_DSA ) + return G10ERR_PUBKEY_ALGO; + + generate( &sk, nbits, retfactors ); + skey[0] = sk.p; + skey[1] = sk.q; + skey[2] = sk.g; + skey[3] = sk.y; + skey[4] = sk.x; + return 0; +} + + +int +dsa_check_secret_key( int algo, MPI *skey ) +{ + DSA_secret_key sk; + + if( algo != PUBKEY_ALGO_DSA ) + return G10ERR_PUBKEY_ALGO; + if( !skey[0] || !skey[1] || !skey[2] || !skey[3] || !skey[4] ) + return G10ERR_BAD_MPI; + + sk.p = skey[0]; + sk.q = skey[1]; + sk.g = skey[2]; + sk.y = skey[3]; + sk.x = skey[4]; + if( !check_secret_key( &sk ) ) + return G10ERR_BAD_SECKEY; + + return 0; +} + + + +int +dsa_sign( int algo, MPI *resarr, MPI data, MPI *skey ) +{ + DSA_secret_key sk; + + if( algo != PUBKEY_ALGO_DSA ) + return G10ERR_PUBKEY_ALGO; + if( !data || !skey[0] || !skey[1] || !skey[2] || !skey[3] || !skey[4] ) + return G10ERR_BAD_MPI; + + sk.p = skey[0]; + sk.q = skey[1]; + sk.g = skey[2]; + sk.y = skey[3]; + sk.x = skey[4]; + resarr[0] = mpi_alloc( mpi_get_nlimbs( sk.p ) ); + resarr[1] = mpi_alloc( mpi_get_nlimbs( sk.p ) ); + sign( resarr[0], resarr[1], data, &sk ); + return 0; +} + +int +dsa_verify( int algo, MPI hash, MPI *data, MPI *pkey, + int (*cmp)(void *, MPI) UNUSED, void *opaquev UNUSED) +{ + DSA_public_key pk; + + if( algo != PUBKEY_ALGO_DSA ) + return G10ERR_PUBKEY_ALGO; + if( !data[0] || !data[1] || !hash + || !pkey[0] || !pkey[1] || !pkey[2] || !pkey[3] ) + return G10ERR_BAD_MPI; + + pk.p = pkey[0]; + pk.q = pkey[1]; + pk.g = pkey[2]; + pk.y = pkey[3]; + if( !verify( data[0], data[1], hash, &pk ) ) + return G10ERR_BAD_SIGN; + return 0; +} + + + +unsigned +dsa_get_nbits( int algo, MPI *pkey ) +{ + if( algo != PUBKEY_ALGO_DSA ) + return 0; + return mpi_get_nbits( pkey[0] ); +} + + +/**************** + * Return some information about the algorithm. We need algo here to + * distinguish different flavors of the algorithm. + * Returns: A pointer to string describing the algorithm or NULL if + * the ALGO is invalid. + * Usage: Bit 0 set : allows signing + * 1 set : allows encryption + */ +const char * +dsa_get_info( int algo, int *npkey, int *nskey, int *nenc, int *nsig, + int *use ) +{ + *npkey = 4; + *nskey = 5; + *nenc = 0; + *nsig = 2; + + switch( algo ) { + case PUBKEY_ALGO_DSA: *use = PUBKEY_USAGE_SIG; return "DSA"; + default: *use = 0; return NULL; + } +} + + diff --git a/src/pluto/dsa.h b/src/pluto/dsa.h new file mode 100644 index 000000000..1456d65b6 --- /dev/null +++ b/src/pluto/dsa.h @@ -0,0 +1,32 @@ +/* dsa.h - DSA signature scheme + * Copyright (C) 1998 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ +#ifndef G10_DSA_H +#define G10_DSA_H + +int dsa_generate( int algo, unsigned nbits, MPI *skey, MPI **retfactors ); +int dsa_check_secret_key( int algo, MPI *skey ); +int dsa_sign( int algo, MPI *resarr, MPI data, MPI *skey ); +int dsa_verify( int algo, MPI hash, MPI *data, MPI *pkey, + int (*cmp)(void *, MPI), void *opaquev ); +unsigned dsa_get_nbits( int algo, MPI *pkey ); +const char *dsa_get_info( int algo, int *npkey, int *nskey, + int *nenc, int *nsig, int *use ); + +#endif /*G10_DSA_H*/ diff --git a/src/pluto/elgamal.c b/src/pluto/elgamal.c new file mode 100644 index 000000000..0c099bb90 --- /dev/null +++ b/src/pluto/elgamal.c @@ -0,0 +1,613 @@ +/* elgamal.c - ElGamal Public Key encryption + * Copyright (C) 1998 Free Software Foundation, Inc. + * + * For a description of the algorithm, see: + * Bruce Schneier: Applied Cryptography. John Wiley & Sons, 1996. + * ISBN 0-471-11709-9. Pages 476 ff. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifdef PLUTO +#include <gmp.h> +#include <freeswan.h> +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "rnd.h" +#include "gcryptfix.h" +#else /*! PLUTO */ +/* #include <config.h> */ +#endif /* !PLUTO */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifndef PLUTO +/* #include "util.h" */ +/* #include "mpi.h" */ +/* #include "cipher.h" */ +#endif + +#include "elgamal.h" + +typedef struct { + MPI p; /* prime */ + MPI g; /* group generator */ + MPI y; /* g^x mod p */ +} ELG_public_key; + + +typedef struct { + MPI p; /* prime */ + MPI g; /* group generator */ + MPI y; /* g^x mod p */ + MPI x; /* secret exponent */ +} ELG_secret_key; + + +static void test_keys( ELG_secret_key *sk, unsigned nbits ); +static MPI gen_k( MPI p ); +static void generate( ELG_secret_key *sk, unsigned nbits, MPI **factors ); +static int check_secret_key( ELG_secret_key *sk ); +static void encrypt(MPI a, MPI b, MPI input, ELG_public_key *pkey ); +static void decrypt(MPI output, MPI a, MPI b, ELG_secret_key *skey ); +static void sign(MPI a, MPI b, MPI input, ELG_secret_key *skey); +static int verify(MPI a, MPI b, MPI input, ELG_public_key *pkey); + + +static void +progress( int c ) +{ + fputc( c, stderr ); +} + + +static void +test_keys( ELG_secret_key *sk, unsigned nbits ) +{ + ELG_public_key pk; + MPI test = mpi_alloc( 0 ); + MPI out1_a = mpi_alloc( nbits / BITS_PER_MPI_LIMB ); + MPI out1_b = mpi_alloc( nbits / BITS_PER_MPI_LIMB ); + MPI out2 = mpi_alloc( nbits / BITS_PER_MPI_LIMB ); + + pk.p = sk->p; + pk.g = sk->g; + pk.y = sk->y; + + /*mpi_set_bytes( test, nbits, get_random_byte, 0 );*/ + { char *p = get_random_bits( nbits, 0, 0 ); + mpi_set_buffer( test, p, (nbits+7)/8, 0 ); + m_free(p); + } + + encrypt( out1_a, out1_b, test, &pk ); + decrypt( out2, out1_a, out1_b, sk ); + if( mpi_cmp( test, out2 ) ) + log_fatal("ElGamal operation: encrypt, decrypt failed\n"); + + sign( out1_a, out1_b, test, sk ); + if( !verify( out1_a, out1_b, test, &pk ) ) + log_fatal("ElGamal operation: sign, verify failed\n"); + + mpi_free( test ); + mpi_free( out1_a ); + mpi_free( out1_b ); + mpi_free( out2 ); +} + + +/**************** + * generate a random secret exponent k from prime p, so + * that k is relatively prime to p-1 + */ +static MPI +gen_k( MPI p ) +{ + MPI k = mpi_alloc_secure( 0 ); + MPI temp = mpi_alloc( mpi_get_nlimbs(p) ); + MPI p_1 = mpi_copy(p); + unsigned int nbits = mpi_get_nbits(p); + unsigned int nbytes = (nbits+7)/8; + char *rndbuf = NULL; + + if( DBG_CIPHER ) + log_debug("choosing a random k "); + mpi_sub_ui( p_1, p, 1); + for(;;) { + if( DBG_CIPHER ) + progress('.'); + if( !rndbuf || nbits < 32 ) { + m_free(rndbuf); + rndbuf = get_random_bits( nbits, 1, 1 ); + } + else { /* change only some of the higher bits */ + /* we could imporove this by directly requesting more memory + * at the first call to get_random_bits() and use this the here + * maybe it is easier to do this directly in random.c */ + char *pp = get_random_bits( 32, 1, 1 ); + memcpy( rndbuf,pp, 4 ); + m_free(pp); + } + mpi_set_buffer( k, rndbuf, nbytes, 0 ); + + for(;;) { + /* make sure that the number is of the exact lenght */ + if( mpi_test_bit( k, nbits-1 ) ) + mpi_set_highbit( k, nbits-1 ); + else { + mpi_set_highbit( k, nbits-1 ); + mpi_clear_bit( k, nbits-1 ); + } + if( !(mpi_cmp( k, p_1 ) < 0) ) { /* check: k < (p-1) */ + if( DBG_CIPHER ) + progress('+'); + break; /* no */ + } + if( !(mpi_cmp_ui( k, 0 ) > 0) ) { /* check: k > 0 */ + if( DBG_CIPHER ) + progress('-'); + break; /* no */ + } + if( mpi_gcd( temp, k, p_1 ) ) + goto found; /* okay, k is relatively prime to (p-1) */ + mpi_add_ui( k, k, 1 ); + } + } + found: + m_free(rndbuf); + if( DBG_CIPHER ) + progress('\n'); + mpi_free(p_1); + mpi_free(temp); + + return k; +} + +/**************** + * Generate a key pair with a key of size NBITS + * Returns: 2 structures filles with all needed values + * and an array with n-1 factors of (p-1) + */ +static void +generate( ELG_secret_key *sk, unsigned nbits, MPI **ret_factors ) +{ + MPI p; /* the prime */ + MPI p_min1; + MPI g; + MPI x; /* the secret exponent */ + MPI y; + MPI temp; + unsigned qbits; + byte *rndbuf; + + p_min1 = mpi_alloc( (nbits+BITS_PER_MPI_LIMB-1)/BITS_PER_MPI_LIMB ); + temp = mpi_alloc( (nbits+BITS_PER_MPI_LIMB-1)/BITS_PER_MPI_LIMB ); + if( nbits < 512 ) + qbits = 120; + else if( nbits <= 1024 ) + qbits = 160; + else if( nbits <= 2048 ) + qbits = 200; + else + qbits = 240; + g = mpi_alloc(1); + p = generate_elg_prime( 0, nbits, qbits, g, ret_factors ); + mpi_sub_ui(p_min1, p, 1); + + + /* select a random number which has these properties: + * 0 < x < p-1 + * This must be a very good random number because this is the + * secret part. The prime is public and may be shared anyway, + * so a random generator level of 1 is used for the prime. + */ + x = mpi_alloc_secure( nbits/BITS_PER_MPI_LIMB ); + if( DBG_CIPHER ) + log_debug("choosing a random x "); + rndbuf = NULL; + do { + if( DBG_CIPHER ) + progress('.'); + if( rndbuf ) { /* change only some of the higher bits */ + if( nbits < 16 ) {/* should never happen ... */ + m_free(rndbuf); + rndbuf = get_random_bits( nbits, 2, 1 ); + } + else { + char *r = get_random_bits( 16, 2, 1 ); + memcpy(rndbuf, r, 16/8 ); + m_free(r); + } + } + else + rndbuf = get_random_bits( nbits, 2, 1 ); + mpi_set_buffer( x, rndbuf, (nbits+7)/8, 0 ); + mpi_clear_highbit( x, nbits+1 ); + } while( !( mpi_cmp_ui( x, 0 )>0 && mpi_cmp( x, p_min1 )<0 ) ); + m_free(rndbuf); + + y = mpi_alloc(nbits/BITS_PER_MPI_LIMB); + mpi_powm( y, g, x, p ); + + if( DBG_CIPHER ) { + progress('\n'); + log_mpidump("elg p= ", p ); + log_mpidump("elg g= ", g ); + log_mpidump("elg y= ", y ); + log_mpidump("elg x= ", x ); + } + + /* copy the stuff to the key structures */ + sk->p = p; + sk->g = g; + sk->y = y; + sk->x = x; + + /* now we can test our keys (this should never fail!) */ + test_keys( sk, nbits - 64 ); + + mpi_free( p_min1 ); + mpi_free( temp ); +} + + +/**************** + * Test whether the secret key is valid. + * Returns: if this is a valid key. + */ +static int +check_secret_key( ELG_secret_key *sk ) +{ + int rc; + MPI y = mpi_alloc( mpi_get_nlimbs(sk->y) ); + + mpi_powm( y, sk->g, sk->x, sk->p ); + rc = !mpi_cmp( y, sk->y ); + mpi_free( y ); + return rc; +} + + +static void +encrypt(MPI a, MPI b, MPI input, ELG_public_key *pkey ) +{ + MPI k; + + /* Note: maybe we should change the interface, so that it + * is possible to check that input is < p and return an + * error code. + */ + + k = gen_k( pkey->p ); + mpi_powm( a, pkey->g, k, pkey->p ); + /* b = (y^k * input) mod p + * = ((y^k mod p) * (input mod p)) mod p + * and because input is < p + * = ((y^k mod p) * input) mod p + */ + mpi_powm( b, pkey->y, k, pkey->p ); + mpi_mulm( b, b, input, pkey->p ); + #if 0 + if( DBG_CIPHER ) { + log_mpidump("elg encrypted y= ", pkey->y); + log_mpidump("elg encrypted p= ", pkey->p); + log_mpidump("elg encrypted k= ", k); + log_mpidump("elg encrypted M= ", input); + log_mpidump("elg encrypted a= ", a); + log_mpidump("elg encrypted b= ", b); + } + #endif + mpi_free(k); +} + + + + +static void +decrypt(MPI output, MPI a, MPI b, ELG_secret_key *skey ) +{ + MPI t1 = mpi_alloc_secure( mpi_get_nlimbs( skey->p ) ); + + /* output = b/(a^x) mod p */ + + mpi_powm( t1, a, skey->x, skey->p ); + mpi_invm( t1, t1, skey->p ); + mpi_mulm( output, b, t1, skey->p ); + #if 0 + if( DBG_CIPHER ) { + log_mpidump("elg decrypted x= ", skey->x); + log_mpidump("elg decrypted p= ", skey->p); + log_mpidump("elg decrypted a= ", a); + log_mpidump("elg decrypted b= ", b); + log_mpidump("elg decrypted M= ", output); + } + #endif + mpi_free(t1); +} + + +/**************** + * Make an Elgamal signature out of INPUT + */ + +static void +sign(MPI a, MPI b, MPI input, ELG_secret_key *skey ) +{ + MPI k; + MPI t = mpi_alloc( mpi_get_nlimbs(a) ); + MPI inv = mpi_alloc( mpi_get_nlimbs(a) ); + MPI p_1 = mpi_copy(skey->p); + + /* + * b = (t * inv) mod (p-1) + * b = (t * inv(k,(p-1),(p-1)) mod (p-1) + * b = (((M-x*a) mod (p-1)) * inv(k,(p-1),(p-1))) mod (p-1) + * + */ + mpi_sub_ui(p_1, p_1, 1); + k = gen_k( skey->p ); + mpi_powm( a, skey->g, k, skey->p ); + mpi_mul(t, skey->x, a ); + mpi_subm(t, input, t, p_1 ); + while( mpi_is_neg(t) ) + mpi_add(t, t, p_1); + mpi_invm(inv, k, p_1 ); + mpi_mulm(b, t, inv, p_1 ); + + #if 0 + if( DBG_CIPHER ) { + log_mpidump("elg sign p= ", skey->p); + log_mpidump("elg sign g= ", skey->g); + log_mpidump("elg sign y= ", skey->y); + log_mpidump("elg sign x= ", skey->x); + log_mpidump("elg sign k= ", k); + log_mpidump("elg sign M= ", input); + log_mpidump("elg sign a= ", a); + log_mpidump("elg sign b= ", b); + } + #endif + mpi_free(k); + mpi_free(t); + mpi_free(inv); + mpi_free(p_1); +} + + +/**************** + * Returns true if the signature composed of A and B is valid. + */ +static int +verify(MPI a, MPI b, MPI input, ELG_public_key *pkey ) +{ + int rc; + MPI t1; + MPI t2; + MPI base[4]; + MPI exp[4]; + + if( !(mpi_cmp_ui( a, 0 ) > 0 && mpi_cmp( a, pkey->p ) < 0) ) + return 0; /* assertion 0 < a < p failed */ + + t1 = mpi_alloc( mpi_get_nlimbs(a) ); + t2 = mpi_alloc( mpi_get_nlimbs(a) ); + + #if 0 + /* t1 = (y^a mod p) * (a^b mod p) mod p */ + mpi_powm( t1, pkey->y, a, pkey->p ); + mpi_powm( t2, a, b, pkey->p ); + mpi_mulm( t1, t1, t2, pkey->p ); + + /* t2 = g ^ input mod p */ + mpi_powm( t2, pkey->g, input, pkey->p ); + + rc = !mpi_cmp( t1, t2 ); + #elif 0 + /* t1 = (y^a mod p) * (a^b mod p) mod p */ + base[0] = pkey->y; exp[0] = a; + base[1] = a; exp[1] = b; + base[2] = NULL; exp[2] = NULL; + mpi_mulpowm( t1, base, exp, pkey->p ); + + /* t2 = g ^ input mod p */ + mpi_powm( t2, pkey->g, input, pkey->p ); + + rc = !mpi_cmp( t1, t2 ); + #else + /* t1 = g ^ - input * y ^ a * a ^ b mod p */ + mpi_invm(t2, pkey->g, pkey->p ); + base[0] = t2 ; exp[0] = input; + base[1] = pkey->y; exp[1] = a; + base[2] = a; exp[2] = b; + base[3] = NULL; exp[3] = NULL; + mpi_mulpowm( t1, base, exp, pkey->p ); + rc = !mpi_cmp_ui( t1, 1 ); + + #endif + + mpi_free(t1); + mpi_free(t2); + return rc; +} + +/********************************************* + ************** interface ****************** + *********************************************/ + +int +elg_generate( int algo, unsigned nbits, MPI *skey, MPI **retfactors ) +{ + ELG_secret_key sk; + + if( !is_ELGAMAL(algo) ) + return G10ERR_PUBKEY_ALGO; + + generate( &sk, nbits, retfactors ); + skey[0] = sk.p; + skey[1] = sk.g; + skey[2] = sk.y; + skey[3] = sk.x; + return 0; +} + + +int +elg_check_secret_key( int algo, MPI *skey ) +{ + ELG_secret_key sk; + + if( !is_ELGAMAL(algo) ) + return G10ERR_PUBKEY_ALGO; + if( !skey[0] || !skey[1] || !skey[2] || !skey[3] ) + return G10ERR_BAD_MPI; + + sk.p = skey[0]; + sk.g = skey[1]; + sk.y = skey[2]; + sk.x = skey[3]; + if( !check_secret_key( &sk ) ) + return G10ERR_BAD_SECKEY; + + return 0; +} + + + +int +elg_encrypt( int algo, MPI *resarr, MPI data, MPI *pkey ) +{ + ELG_public_key pk; + + if( !is_ELGAMAL(algo) ) + return G10ERR_PUBKEY_ALGO; + if( !data || !pkey[0] || !pkey[1] || !pkey[2] ) + return G10ERR_BAD_MPI; + + pk.p = pkey[0]; + pk.g = pkey[1]; + pk.y = pkey[2]; + resarr[0] = mpi_alloc( mpi_get_nlimbs( pk.p ) ); + resarr[1] = mpi_alloc( mpi_get_nlimbs( pk.p ) ); + encrypt( resarr[0], resarr[1], data, &pk ); + return 0; +} + +int +elg_decrypt( int algo, MPI *result, MPI *data, MPI *skey ) +{ + ELG_secret_key sk; + + if( !is_ELGAMAL(algo) ) + return G10ERR_PUBKEY_ALGO; + if( !data[0] || !data[1] + || !skey[0] || !skey[1] || !skey[2] || !skey[3] ) + return G10ERR_BAD_MPI; + + sk.p = skey[0]; + sk.g = skey[1]; + sk.y = skey[2]; + sk.x = skey[3]; + *result = mpi_alloc_secure( mpi_get_nlimbs( sk.p ) ); + decrypt( *result, data[0], data[1], &sk ); + return 0; +} + +int +elg_sign( int algo, MPI *resarr, MPI data, MPI *skey ) +{ + ELG_secret_key sk; + + if( !is_ELGAMAL(algo) ) + return G10ERR_PUBKEY_ALGO; + if( !data || !skey[0] || !skey[1] || !skey[2] || !skey[3] ) + return G10ERR_BAD_MPI; + + sk.p = skey[0]; + sk.g = skey[1]; + sk.y = skey[2]; + sk.x = skey[3]; + resarr[0] = mpi_alloc( mpi_get_nlimbs( sk.p ) ); + resarr[1] = mpi_alloc( mpi_get_nlimbs( sk.p ) ); + sign( resarr[0], resarr[1], data, &sk ); + return 0; +} + +int +elg_verify( int algo, MPI hash, MPI *data, MPI *pkey, + int (*cmp)(void *, MPI) UNUSED, void *opaquev UNUSED) +{ + ELG_public_key pk; + + if( !is_ELGAMAL(algo) ) + return G10ERR_PUBKEY_ALGO; + if( !data[0] || !data[1] || !hash + || !pkey[0] || !pkey[1] || !pkey[2] ) + return G10ERR_BAD_MPI; + + pk.p = pkey[0]; + pk.g = pkey[1]; + pk.y = pkey[2]; + if( !verify( data[0], data[1], hash, &pk ) ) + return G10ERR_BAD_SIGN; + return 0; +} + + + +unsigned +elg_get_nbits( int algo, MPI *pkey ) +{ + if( !is_ELGAMAL(algo) ) + return 0; + return mpi_get_nbits( pkey[0] ); +} + + +/**************** + * Return some information about the algorithm. We need algo here to + * distinguish different flavors of the algorithm. + * Returns: A pointer to string describing the algorithm or NULL if + * the ALGO is invalid. + * Usage: Bit 0 set : allows signing + * 1 set : allows encryption + * NOTE: This function allows signing also for ELG-E, which is not + * okay but a bad hack to allow to work with old gpg keys. The real check + * is done in the gnupg ocde depending on the packet version. + */ +const char * +elg_get_info( int algo, int *npkey, int *nskey, int *nenc, int *nsig, + int *use ) +{ + *npkey = 3; + *nskey = 4; + *nenc = 2; + *nsig = 2; + + switch( algo ) { + case PUBKEY_ALGO_ELGAMAL: + *use = PUBKEY_USAGE_SIG|PUBKEY_USAGE_ENC; + return "ELG"; + case PUBKEY_ALGO_ELGAMAL_E: + *use = PUBKEY_USAGE_SIG|PUBKEY_USAGE_ENC; + return "ELG-E"; + default: *use = 0; return NULL; + } +} + + diff --git a/src/pluto/elgamal.h b/src/pluto/elgamal.h new file mode 100644 index 000000000..f104c2a52 --- /dev/null +++ b/src/pluto/elgamal.h @@ -0,0 +1,35 @@ +/* elgamal.h + * Copyright (C) 1998 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ +#ifndef G10_ELGAMAL_H +#define G10_ELGAMAL_H + +int elg_generate( int algo, unsigned nbits, MPI *skey, MPI **retfactors ); +int elg_check_secret_key( int algo, MPI *skey ); +int elg_encrypt( int algo, MPI *resarr, MPI data, MPI *pkey ); +int elg_decrypt( int algo, MPI *result, MPI *data, MPI *skey ); +int elg_sign( int algo, MPI *resarr, MPI data, MPI *skey ); +int elg_verify( int algo, MPI hash, MPI *data, MPI *pkey, + int (*cmp)(void *, MPI), void *opaquev ); +unsigned elg_get_nbits( int algo, MPI *pkey ); +const char *elg_get_info( int algo, int *npkey, int *nskey, + int *nenc, int *nsig, int *use ); + + +#endif /*G10_ELGAMAL_H*/ diff --git a/src/pluto/fetch.c b/src/pluto/fetch.c new file mode 100644 index 000000000..e3e56d3a8 --- /dev/null +++ b/src/pluto/fetch.c @@ -0,0 +1,1081 @@ +/* Dynamic fetching of X.509 CRLs + * Copyright (C) 2002 Stephane Laroche <stephane.laroche@colubris.com> + * Copyright (C) 2002-2004 Andreas Steffen, Zuercher Hochschule Winterthur + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: fetch.c,v 1.12 2006/05/16 14:19:27 as Exp $ + */ + +#include <stdlib.h> +#include <errno.h> +#include <sys/time.h> +#include <time.h> +#include <string.h> + +#ifdef THREADS +#include <pthread.h> +#endif + +#ifdef LIBCURL +#include <curl/curl.h> +#endif + +#include <freeswan.h> + +#ifdef LIBLDAP +#include <ldap.h> +#endif + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "id.h" +#include "asn1.h" +#include "pem.h" +#include "x509.h" +#include "ca.h" +#include "whack.h" +#include "ocsp.h" +#include "crl.h" +#include "fetch.h" + +fetch_req_t empty_fetch_req = { + NULL , /* next */ + 0 , /* installed */ + 0 , /* trials */ + { NULL, 0}, /* issuer */ + { NULL, 0}, /* authKeyID */ + { NULL, 0}, /* authKeySerialNumber */ + NULL /* distributionPoints */ +}; + +/* chained list of crl fetch requests */ +static fetch_req_t *crl_fetch_reqs = NULL; + +/* chained list of ocsp fetch requests */ +static ocsp_location_t *ocsp_fetch_reqs = NULL; + +#ifdef THREADS +static pthread_t thread; +static pthread_mutex_t certs_and_keys_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t authcert_list_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t crl_list_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t ocsp_cache_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t ca_info_list_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t crl_fetch_list_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t ocsp_fetch_list_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t fetch_wake_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t fetch_wake_cond = PTHREAD_COND_INITIALIZER; + +/* + * lock access to my certs and keys + */ +void +lock_certs_and_keys(const char *who) +{ + pthread_mutex_lock(&certs_and_keys_mutex); + DBG(DBG_CONTROLMORE, + DBG_log("certs and keys locked by '%s'", who) + ) +} + +/* + * unlock access to my certs and keys + */ +void +unlock_certs_and_keys(const char *who) +{ + DBG(DBG_CONTROLMORE, + DBG_log("certs and keys unlocked by '%s'", who) + ) + pthread_mutex_unlock(&certs_and_keys_mutex); +} + +/* + * lock access to the chained authcert list + */ +void +lock_authcert_list(const char *who) +{ + pthread_mutex_lock(&authcert_list_mutex); + DBG(DBG_CONTROLMORE, + DBG_log("authcert list locked by '%s'", who) + ) +} + +/* + * unlock access to the chained authcert list + */ +void +unlock_authcert_list(const char *who) +{ + DBG(DBG_CONTROLMORE, + DBG_log("authcert list unlocked by '%s'", who) + ) + pthread_mutex_unlock(&authcert_list_mutex); +} + +/* + * lock access to the chained crl list + */ +void +lock_crl_list(const char *who) +{ + pthread_mutex_lock(&crl_list_mutex); + DBG(DBG_CONTROLMORE, + DBG_log("crl list locked by '%s'", who) + ) +} + +/* + * unlock access to the chained crl list + */ +void +unlock_crl_list(const char *who) +{ + DBG(DBG_CONTROLMORE, + DBG_log("crl list unlocked by '%s'", who) + ) + pthread_mutex_unlock(&crl_list_mutex); +} + +/* + * lock access to the ocsp cache + */ +extern void +lock_ocsp_cache(const char *who) +{ + pthread_mutex_lock(&ocsp_cache_mutex); + DBG(DBG_CONTROLMORE, + DBG_log("ocsp cache locked by '%s'", who) + ) +} + +/* + * unlock access to the ocsp cache + */ +extern void +unlock_ocsp_cache(const char *who) +{ + DBG(DBG_CONTROLMORE, + DBG_log("ocsp cache unlocked by '%s'", who) + ) + pthread_mutex_unlock(&ocsp_cache_mutex); +} + +/* + * lock access to the ca info list + */ +extern void +lock_ca_info_list(const char *who) +{ + pthread_mutex_lock(&ca_info_list_mutex); + DBG(DBG_CONTROLMORE, + DBG_log("ca info list locked by '%s'", who) + ) +} + +/* + * unlock access to the ca info list + */ +extern void +unlock_ca_info_list(const char *who) +{ + DBG(DBG_CONTROLMORE, + DBG_log("ca info list unlocked by '%s'", who) + ) + pthread_mutex_unlock(&ca_info_list_mutex); +} + +/* + * lock access to the chained crl fetch request list + */ +static void +lock_crl_fetch_list(const char *who) +{ + pthread_mutex_lock(&crl_fetch_list_mutex); + DBG(DBG_CONTROLMORE, + DBG_log("crl fetch request list locked by '%s'", who) + ) +} + +/* + * unlock access to the chained crl fetch request list + */ +static void +unlock_crl_fetch_list(const char *who) +{ + DBG(DBG_CONTROLMORE, + DBG_log("crl fetch request list unlocked by '%s'", who) + ) + pthread_mutex_unlock(&crl_fetch_list_mutex); +} + +/* + * lock access to the chained ocsp fetch request list + */ +static void +lock_ocsp_fetch_list(const char *who) +{ + pthread_mutex_lock(&ocsp_fetch_list_mutex); + DBG(DBG_CONTROLMORE, + DBG_log("ocsp fetch request list locked by '%s'", who) + ) +} + +/* + * unlock access to the chained ocsp fetch request list + */ +static void +unlock_ocsp_fetch_list(const char *who) +{ + DBG(DBG_CONTROLMORE, + DBG_log("ocsp fetch request list unlocked by '%s'", who) + ) + pthread_mutex_unlock(&ocsp_fetch_list_mutex); +} + +/* + * wakes up the sleeping fetch thread + */ +void +wake_fetch_thread(const char *who) +{ + if (crl_check_interval > 0) + { + DBG(DBG_CONTROLMORE, + DBG_log("fetch thread wake call by '%s'", who) + ) + pthread_mutex_lock(&fetch_wake_mutex); + pthread_cond_signal(&fetch_wake_cond); + pthread_mutex_unlock(&fetch_wake_mutex); + } +} +#else /* !THREADS */ +#define lock_crl_fetch_list(who) /* do nothing */ +#define unlock_crl_fetch_list(who) /* do nothing */ +#define lock_ocsp_fetch_list(who) /* do nothing */ +#define unlock_ocsp_fetch_list(who) /* do nothing */ +#endif /* !THREADS */ + +/* + * free the dynamic memory used to store fetch requests + */ +static void +free_fetch_request(fetch_req_t *req) +{ + pfree(req->issuer.ptr); + pfreeany(req->authKeySerialNumber.ptr); + pfreeany(req->authKeyID.ptr); + free_generalNames(req->distributionPoints, TRUE); + pfree(req); +} + +/* writes data into a dynamically resizeable chunk_t + * needed for libcurl responses + */ +size_t +write_buffer(void *ptr, size_t size, size_t nmemb, void *data) +{ + size_t realsize = size * nmemb; + chunk_t *mem = (chunk_t*)data; + + mem->ptr = (u_char *)realloc(mem->ptr, mem->len + realsize); + if (mem->ptr) { + memcpy(&(mem->ptr[mem->len]), ptr, realsize); + mem->len += realsize; + } + return realsize; +} + +#ifdef THREADS +/* + * fetches a binary blob from a url with libcurl + */ +static err_t +fetch_curl(char *url, chunk_t *blob) +{ +#ifdef LIBCURL + char errorbuffer[CURL_ERROR_SIZE] = ""; + chunk_t response = empty_chunk; + CURLcode res; + + /* get it with libcurl */ + CURL *curl = curl_easy_init(); + + if (curl != NULL) + { + DBG(DBG_CONTROL, + DBG_log("Trying cURL '%s'", url) + ) + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_buffer); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &errorbuffer); + curl_easy_setopt(curl, CURLOPT_FAILONERROR, TRUE); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, FETCH_CMD_TIMEOUT); + + res = curl_easy_perform(curl); + + if (res == CURLE_OK) + { + blob->len = response.len; + blob->ptr = alloc_bytes(response.len, "curl blob"); + memcpy(blob->ptr, response.ptr, response.len); + } + else + { + plog("fetching uri (%s) with libcurl failed: %s", url, errorbuffer); + } + curl_easy_cleanup(curl); + /* not using freeanychunk because of realloc (no leak detective) */ + curl_free(response.ptr); + } + return strlen(errorbuffer) > 0 ? "libcurl error" : NULL; +#else /* !LIBCURL */ + return "warning: not compiled with libcurl support"; +#endif /* !LIBCURL */ +} + +#ifdef LIBLDAP +/* + * parses the result returned by an ldap query + */ +static err_t +parse_ldap_result(LDAP * ldap, LDAPMessage *result, chunk_t *blob) +{ + err_t ugh = NULL; + + LDAPMessage * entry = ldap_first_entry(ldap, result); + + if (entry != NULL) + { + BerElement *ber = NULL; + char *attr; + + attr = ldap_first_attribute(ldap, entry, &ber); + + if (attr != NULL) + { + struct berval **values = ldap_get_values_len(ldap, entry, attr); + + if (values != NULL) + { + if (values[0] != NULL) + { + blob->len = values[0]->bv_len; + blob->ptr = alloc_bytes(blob->len, "ldap blob"); + memcpy(blob->ptr, values[0]->bv_val, blob->len); + if (values[1] != NULL) + { + plog("warning: more than one value was fetched from LDAP URL"); + } + } + else + { + ugh = "no values in attribute"; + } + ldap_value_free_len(values); + } + else + { + ugh = ldap_err2string(ldap_result2error(ldap, entry, 0)); + } + ldap_memfree(attr); + } + else + { + ugh = ldap_err2string(ldap_result2error(ldap, entry, 0)); + } + ber_free(ber, 0); + } + else + { + ugh = ldap_err2string(ldap_result2error(ldap, result, 0)); + } + return ugh; +} + +/* + * fetches a binary blob from an ldap url + */ +static err_t +fetch_ldap_url(char *url, chunk_t *blob) +{ + LDAPURLDesc *lurl; + err_t ugh = NULL; + int rc; + + DBG(DBG_CONTROL, + DBG_log("Trying LDAP URL '%s'", url) + ) + + rc = ldap_url_parse(url, &lurl); + + if (rc == LDAP_SUCCESS) + { + LDAP *ldap = ldap_init(lurl->lud_host, lurl->lud_port); + + if (ldap != NULL) + { + int ldap_version = LDAP_VERSION3; + struct timeval timeout; + + timeout.tv_sec = FETCH_CMD_TIMEOUT; + timeout.tv_usec = 0; + ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &ldap_version); + ldap_set_option(ldap, LDAP_OPT_NETWORK_TIMEOUT, &timeout); + + rc = ldap_simple_bind_s(ldap, NULL, NULL); + + if (rc == LDAP_SUCCESS) + { + LDAPMessage *result; + + timeout.tv_sec = FETCH_CMD_TIMEOUT; + timeout.tv_usec = 0; + + rc = ldap_search_st(ldap, lurl->lud_dn + , lurl->lud_scope + , lurl->lud_filter + , lurl->lud_attrs + , 0, &timeout, &result); + + if (rc == LDAP_SUCCESS) + { + ugh = parse_ldap_result(ldap, result, blob); + ldap_msgfree(result); + } + else + { + ugh = ldap_err2string(rc); + } + } + else + { + ugh = ldap_err2string(rc); + } + ldap_unbind_s(ldap); + } + else + { + ugh = "ldap init"; + } + ldap_free_urldesc(lurl); + } + else + { + ugh = ldap_err2string(rc); + } + return ugh; +} +#else /* !LIBLDAP */ +static err_t +fetch_ldap_url(char *url, chunk_t *blob) +{ + return "LDAP URL fetching not activated in pluto source code"; +} +#endif /* !LIBLDAP */ + +/* + * fetch an ASN.1 blob coded in PEM or DER format from a URL + */ +static err_t +fetch_asn1_blob(char *url, chunk_t *blob) +{ + err_t ugh = NULL; + + if (strlen(url) >= 4 && strncasecmp(url, "ldap", 4) == 0) + { + ugh = fetch_ldap_url(url, blob); + } + else + { + ugh = fetch_curl(url, blob); + } + if (ugh != NULL) + return ugh; + + if (is_asn1(*blob)) + { + DBG(DBG_PARSING, + DBG_log(" fetched blob coded in DER format") + ) + } + else + { + bool pgp = FALSE; + + ugh = pemtobin(blob, NULL, "", &pgp); + if (ugh == NULL) + { + if (is_asn1(*blob)) + { + DBG(DBG_PARSING, + DBG_log(" fetched blob coded in PEM format") + ) + } + else + { + ugh = "blob coded in unknown format"; + pfree(blob->ptr); + } + } + else + { + pfree(blob->ptr); + } + } + return ugh; +} + +/* + * complete a distributionPoint URI with ca information + */ +static char* +complete_uri(chunk_t distPoint, const char *ldaphost) +{ + char *uri; + char *ptr = distPoint.ptr; + size_t len = distPoint.len; + + char *symbol = memchr(ptr, ':', len); + + if (symbol != NULL) + { + size_t type_len = symbol - ptr; + + if (type_len >= 4 && strncasecmp(ptr, "ldap", 4) == 0) + { + ptr = symbol + 1; + len -= (type_len + 1); + + if (len > 2 && *ptr++ == '/' && *ptr++ == '/') + { + len -= 2; + symbol = memchr(ptr, '/', len); + + if (symbol != NULL && symbol - ptr == 0 && ldaphost != NULL) + { + uri = alloc_bytes(distPoint.len+strlen(ldaphost)+1, "uri"); + + /* insert the ldaphost into the uri */ + sprintf(uri, "%.*s%s%.*s" + , (int)(distPoint.len - len), distPoint.ptr + , ldaphost + , (int)len, symbol); + return uri; + } + } + } + } + + /* default action: copy distributionPoint without change */ + uri = alloc_bytes(distPoint.len+1, "uri"); + sprintf(uri, "%.*s", (int)distPoint.len, distPoint.ptr); + return uri; +} + +/* + * try to fetch the crls defined by the fetch requests + */ +static void +fetch_crls(bool cache_crls) +{ + fetch_req_t *req; + fetch_req_t **reqp; + + lock_crl_fetch_list("fetch_crls"); + req = crl_fetch_reqs; + reqp = &crl_fetch_reqs; + + while (req != NULL) + { + bool valid_crl = FALSE; + chunk_t blob = empty_chunk; + generalName_t *gn = req->distributionPoints; + const char *ldaphost; + ca_info_t *ca; + + lock_ca_info_list("fetch_crls"); + + ca = get_ca_info(req->issuer, req->authKeySerialNumber, req->authKeyID); + ldaphost = (ca == NULL)? NULL : ca->ldaphost; + + while (gn != NULL) + { + char *uri = complete_uri(gn->name, ldaphost); + + err_t ugh = fetch_asn1_blob(uri, &blob); + pfree(uri); + + if (ugh != NULL) + { + plog("fetch failed: %s", ugh); + } + else + { + chunk_t crl_uri; + + clonetochunk(crl_uri, gn->name.ptr, gn->name.len, "crl uri"); + if (insert_crl(blob, crl_uri, cache_crls)) + { + DBG(DBG_CONTROL, + DBG_log("we have a valid crl") + ) + valid_crl = TRUE; + break; + } + } + gn = gn->next; + } + + unlock_ca_info_list("fetch_crls"); + + if (valid_crl) + { + /* delete fetch request */ + fetch_req_t *req_free = req; + + req = req->next; + *reqp = req; + free_fetch_request(req_free); + } + else + { + /* try again next time */ + req->trials++; + reqp = &req->next; + req = req->next; + } + } + unlock_crl_fetch_list("fetch_crls"); +} + +static void +fetch_ocsp_status(ocsp_location_t* location) +{ +#ifdef LIBCURL + chunk_t request; + chunk_t response = empty_chunk; + + CURL* curl; + CURLcode res; + + request = build_ocsp_request(location); + + DBG(DBG_CONTROL, + DBG_log("sending ocsp request to location '%.*s'" + , (int)location->uri.len, location->uri.ptr) + ) + DBG(DBG_RAW, + DBG_dump_chunk("OCSP request", request) + ) + + /* send via http post using libcurl */ + curl = curl_easy_init(); + + if (curl != NULL) + { + char errorbuffer[CURL_ERROR_SIZE]; + struct curl_slist *headers = NULL; + char* uri = alloc_bytes(location->uri.len+1, "ocsp uri"); + + /* we need a null terminated string for curl */ + memcpy(uri, location->uri.ptr, location->uri.len); + *(uri + location->uri.len) = '\0'; + + /* set content type header */ + headers = curl_slist_append(headers, "Content-Type: application/ocsp-request"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + curl_easy_setopt(curl, CURLOPT_URL, uri); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_buffer); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.ptr); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, request.len); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &errorbuffer); + curl_easy_setopt(curl, CURLOPT_FAILONERROR, TRUE); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, FETCH_CMD_TIMEOUT); + + res = curl_easy_perform(curl); + + if (res == CURLE_OK) + { + DBG(DBG_CONTROL, + DBG_log("received ocsp response") + ) + DBG(DBG_RAW, + DBG_dump_chunk("OCSP response:\n", response) + ) + parse_ocsp(location, response); + } + else + { + plog("failed to fetch ocsp status from '%s': %s", uri, errorbuffer); + } + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + pfree(uri); + /* not using freeanychunk because of realloc (no leak detective) */ + curl_free(response.ptr); + } + freeanychunk(location->nonce); + freeanychunk(request); + + /* increment the trial counter of the unresolved fetch requests */ + { + ocsp_certinfo_t *certinfo = location->certinfo; + + while (certinfo != NULL) + { + certinfo->trials++; + certinfo = certinfo->next; + } + } + return; +#else /* !LIBCURL */ + plog("ocsp error: pluto wasn't compiled with libcurl support"); +#endif /* !LIBCURL */ +} + +/* + * try to fetch the necessary ocsp information + */ +static void +fetch_ocsp(void) +{ + ocsp_location_t *location; + + lock_ocsp_fetch_list("fetch_ocsp"); + location = ocsp_fetch_reqs; + + /* fetch the ocps status for all locations */ + while (location != NULL) + { + if (location->certinfo != NULL) + fetch_ocsp_status(location); + location = location->next; + } + + unlock_ocsp_fetch_list("fetch_ocsp"); +} + +static void* +fetch_thread(void *arg) +{ + struct timespec wait_interval; + + DBG(DBG_CONTROL, + DBG_log("fetch thread started") + ) + + pthread_mutex_lock(&fetch_wake_mutex); + + while(1) + { + int status; + + wait_interval.tv_nsec = 0; + wait_interval.tv_sec = time(NULL) + crl_check_interval; + + DBG(DBG_CONTROL, + DBG_log("next regular crl check in %ld seconds", crl_check_interval) + ) + status = pthread_cond_timedwait(&fetch_wake_cond, &fetch_wake_mutex + , &wait_interval); + + if (status == ETIMEDOUT) + { + DBG(DBG_CONTROL, + DBG_log(" "); + DBG_log("*time to check crls and the ocsp cache") + ) + check_ocsp(); + check_crls(); + } + else + { + DBG(DBG_CONTROL, + DBG_log("fetch thread was woken up") + ) + } + fetch_ocsp(); + fetch_crls(cache_crls); + } +} +#endif /* THREADS*/ + +/* + * initializes curl and starts the fetching thread + */ +void +init_fetch(void) +{ + int status; + +#ifdef LIBCURL + /* init curl */ + status = curl_global_init(CURL_GLOBAL_NOTHING); + if (status != CURLE_OK) + { + plog("libcurl could not be initialized, status = %d", status); + } +#endif /* LIBCURL */ + + if (crl_check_interval > 0) + { +#ifdef THREADS + status = pthread_create( &thread, NULL, fetch_thread, NULL); + if (status != 0) + { + plog("fetching thread could not be started, status = %d", status); + } +#else /* !THREADS */ + plog("warning: not compiled with pthread support"); +#endif /* !THREADS */ + } +} + +void +free_crl_fetch(void) +{ + lock_crl_fetch_list("free_crl_fetch"); + + while (crl_fetch_reqs != NULL) + { + fetch_req_t *req = crl_fetch_reqs; + crl_fetch_reqs = req->next; + free_fetch_request(req); + } + + unlock_crl_fetch_list("free_crl_fetch"); + +#ifdef LIBCURL + if (crl_check_interval > 0) + { + /* cleanup curl */ + curl_global_cleanup(); + } +#endif /* LIBCURL */ +} + +/* + * free the chained list of ocsp requests + */ +void +free_ocsp_fetch(void) +{ + lock_ocsp_fetch_list("free_ocsp_fetch"); + free_ocsp_locations(&ocsp_fetch_reqs); + unlock_ocsp_fetch_list("free_ocsp_fetch"); +} + + +/* + * add additional distribution points + */ +void +add_distribution_points(const generalName_t *newPoints ,generalName_t **distributionPoints) +{ + while (newPoints != NULL) + { + /* skip empty distribution point */ + if (newPoints->name.len > 0) + { + bool add = TRUE; + generalName_t *gn = *distributionPoints; + + while (gn != NULL) + { + if (gn->kind == newPoints->kind + && gn->name.len == newPoints->name.len + && memcmp(gn->name.ptr, newPoints->name.ptr, gn->name.len) == 0) + { + /* skip if the distribution point is already present */ + add = FALSE; + break; + } + gn = gn->next; + } + + if (add) + { + /* clone additional distribution point */ + gn = clone_thing(*newPoints, "generalName"); + clonetochunk(gn->name, newPoints->name.ptr, newPoints->name.len + , "crl uri"); + + /* insert additional CRL distribution point */ + gn->next = *distributionPoints; + *distributionPoints = gn; + } + } + newPoints = newPoints->next; + } +} + +fetch_req_t* +build_crl_fetch_request(chunk_t issuer, chunk_t authKeySerialNumber +, chunk_t authKeyID, const generalName_t *gn) +{ + fetch_req_t *req = alloc_thing(fetch_req_t, "fetch request"); + *req = empty_fetch_req; + + /* note current time */ + req->installed = time(NULL); + + /* clone fields */ + clonetochunk(req->issuer, issuer.ptr, issuer.len, "issuer"); + if (authKeySerialNumber.ptr != NULL) + { + clonetochunk(req->authKeySerialNumber, authKeySerialNumber.ptr + , authKeySerialNumber.len, "authKeySerialNumber"); + } + if (authKeyID.ptr != NULL) + { + clonetochunk(req->authKeyID, authKeyID.ptr, authKeyID.len, "authKeyID"); + } + + /* copy distribution points */ + add_distribution_points(gn, &req->distributionPoints); + + return req; +} + +/* + * add a crl fetch request to the chained list + */ +void +add_crl_fetch_request(fetch_req_t *req) +{ + fetch_req_t *r; + + lock_crl_fetch_list("add_crl_fetch_request"); + r = crl_fetch_reqs; + + while (r != NULL) + { + if ((req->authKeyID.ptr != NULL)? same_keyid(req->authKeyID, r->authKeyID) + : (same_dn(req->issuer, r->issuer) + && same_serial(req->authKeySerialNumber, r->authKeySerialNumber))) + { + /* there is already a fetch request */ + DBG(DBG_CONTROL, + DBG_log("crl fetch request already exists") + ) + + /* there might be new distribution points */ + add_distribution_points(req->distributionPoints, &r->distributionPoints); + + unlock_crl_fetch_list("add_crl_fetch_request"); + free_fetch_request(req); + return; + } + r = r->next; + } + + /* insert new fetch request at the head of the queue */ + req->next = crl_fetch_reqs; + crl_fetch_reqs = req; + + DBG(DBG_CONTROL, + DBG_log("crl fetch request added") + ) + unlock_crl_fetch_list("add_crl_fetch_request"); +} + +/* + * add an ocsp fetch request to the chained list + */ +void +add_ocsp_fetch_request(ocsp_location_t *location, chunk_t serialNumber) +{ + ocsp_certinfo_t certinfo; + + certinfo.serialNumber = serialNumber; + + lock_ocsp_fetch_list("add_ocsp_fetch_request"); + add_certinfo(location, &certinfo, &ocsp_fetch_reqs, TRUE); + unlock_ocsp_fetch_list("add_ocsp_fetch_request"); +} + +/* + * list all distribution points + */ +void +list_distribution_points(const generalName_t *gn) +{ + bool first_gn = TRUE; + + while (gn != NULL) + { + whack_log(RC_COMMENT, " %s '%.*s'", (first_gn)? "distPts: " + :" ", (int)gn->name.len, gn->name.ptr); + first_gn = FALSE; + gn = gn->next; + } +} + +/* + * list all fetch requests in the chained list + */ +void +list_crl_fetch_requests(bool utc) +{ + fetch_req_t *req; + + lock_crl_fetch_list("list_crl_fetch_requests"); + req = crl_fetch_reqs; + + if (req != NULL) + { + whack_log(RC_COMMENT, " "); + whack_log(RC_COMMENT, "List of CRL fetch requests:"); + whack_log(RC_COMMENT, " "); + } + + while (req != NULL) + { + u_char buf[BUF_LEN]; + + whack_log(RC_COMMENT, "%s, trials: %d" + , timetoa(&req->installed, utc), req->trials); + dntoa(buf, BUF_LEN, req->issuer); + whack_log(RC_COMMENT, " issuer: '%s'", buf); + if (req->authKeyID.ptr != NULL) + { + datatot(req->authKeyID.ptr, req->authKeyID.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT, " authkey: %s", buf); + } + if (req->authKeySerialNumber.ptr != NULL) + { + datatot(req->authKeySerialNumber.ptr, req->authKeySerialNumber.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT, " aserial: %s", buf); + } + list_distribution_points(req->distributionPoints); + req = req->next; + } + unlock_crl_fetch_list("list_crl_fetch_requests"); +} + +void +list_ocsp_fetch_requests(bool utc) +{ + lock_ocsp_fetch_list("list_ocsp_fetch_requests"); + list_ocsp_locations(ocsp_fetch_reqs, TRUE, utc, FALSE); + unlock_ocsp_fetch_list("list_ocsp_fetch_requests"); + +} diff --git a/src/pluto/fetch.h b/src/pluto/fetch.h new file mode 100644 index 000000000..6303f37e4 --- /dev/null +++ b/src/pluto/fetch.h @@ -0,0 +1,79 @@ +/* Dynamic fetching of X.509 CRLs + * Copyright (C) 2002 Stephane Laroche <stephane.laroche@colubris.com> + * Copyright (C) 2002-2004 Andreas Steffen, Zuercher Hochschule Winterthur + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: fetch.h,v 1.6 2005/11/25 10:08:00 as Exp $ + */ + +#include "x509.h" + +#define FETCH_CMD_TIMEOUT 10 /* seconds */ + +struct ocsp_location; /* forward declaration of ocsp_location defined in ocsp.h */ + +typedef enum { + FETCH_GET = 1, + FETCH_POST = 2 +} fetch_request_t; + +typedef struct fetch_req fetch_req_t; + +struct fetch_req { + fetch_req_t *next; + time_t installed; + int trials; + chunk_t issuer; + chunk_t authKeyID; + chunk_t authKeySerialNumber; + generalName_t *distributionPoints; +}; + +#ifdef THREADS +extern void lock_crl_list(const char *who); +extern void unlock_crl_list(const char *who); +extern void lock_ocsp_cache(const char *who); +extern void unlock_ocsp_cache(const char *who); +extern void lock_ca_info_list(const char *who); +extern void unlock_ca_info_list(const char *who); +extern void lock_authcert_list(const char *who); +extern void unlock_authcert_list(const char *who); +extern void lock_certs_and_keys(const char *who); +extern void unlock_certs_and_keys(const char *who); +extern void wake_fetch_thread(const char *who); +#else +#define lock_crl_list(who) /* do nothing */ +#define unlock_crl_list(who) /* do nothing */ +#define lock_ocsp_cache(who) /* do nothing */ +#define unlock_ocsp_cache(who) /* do nothing */ +#define lock_ca_info_list(who) /* do nothing */ +#define unlock_ca_info_list(who) /* do nothing */ +#define lock_authcert_list(who) /* do nothing */ +#define unlock_authcert_list(who) /* do nothing */ +#define lock_certs_and_keys(who) /* do nothing */ +#define unlock_certs_and_keys(who) /* do nothing */ +#define wake_fetch_thread(who) /* do nothing */ +#endif +extern void init_fetch(void); +extern void free_crl_fetch(void); +extern void free_ocsp_fetch(void); +extern void add_distribution_points(const generalName_t *newPoints + , generalName_t **distributionPoints); +extern fetch_req_t* build_crl_fetch_request(chunk_t issuer, chunk_t authKeySerialNumber + , chunk_t authKeyID, const generalName_t *gn); +extern void add_crl_fetch_request(fetch_req_t *req); +extern void add_ocsp_fetch_request(struct ocsp_location *location, chunk_t serialNumber); +extern void list_distribution_points(const generalName_t *gn); +extern void list_crl_fetch_requests(bool utc); +extern void list_ocsp_fetch_requests(bool utc); +extern size_t write_buffer(void *ptr, size_t size, size_t nmemb, void *data); + diff --git a/src/pluto/foodgroups.c b/src/pluto/foodgroups.c new file mode 100644 index 000000000..c92bdb3d4 --- /dev/null +++ b/src/pluto/foodgroups.c @@ -0,0 +1,462 @@ +/* Implement policy groups-style control files (aka "foodgroups") + * Copyright (C) 2002 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: foodgroups.c,v 1.2 2004/04/01 18:28:32 as Exp $ + */ + +#include <string.h> +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <sys/queue.h> + +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "connections.h" +#include "foodgroups.h" +#include "kernel.h" +#include "lex.h" +#include "log.h" +#include "whack.h" + + +/* Food group config files are found in directory fg_path */ + +#ifndef POLICYGROUPSDIR +#define POLICYGROUPSDIR IPSEC_CONFDIR "/ipsec.d/policies" +#endif + +const char *policygroups_dir = POLICYGROUPSDIR; + +static char *fg_path = NULL; +static size_t fg_path_space = 0; + + +/* Groups is a list of connections that are policy groups. + * The list is updated as group connections are added and deleted. + */ + +struct fg_groups { + struct fg_groups *next; + struct connection *connection; +}; + +static struct fg_groups *groups = NULL; + + +/* Targets is a list of pairs: subnet and its policy group. + * This list is bulk-updated on whack --listen and + * incrementally updated when group connections are deleted. + * + * It is ordered by source subnet, and if those are equal, then target subnet. + * A subnet is compared by comparing the network, and if those are equal, + * comparing the mask. + */ + +struct fg_targets { + struct fg_targets *next; + struct fg_groups *group; + ip_subnet subnet; + char *name; /* name of instance of group conn */ +}; + +static struct fg_targets *targets = NULL; + +struct fg_targets *new_targets; + +/* ipcmp compares the two ip_address values a and b. + * It returns -1, 0, or +1 if a is, respectively, + * less than, equal to, or greater than b. + */ +static int +ipcmp(ip_address *a, ip_address *b) +{ + if (addrtypeof(a) != addrtypeof(b)) + { + return addrtypeof(a) < addrtypeof(b)? -1 : 1; + } + else if (sameaddr(a, b)) + { + return 0; + } + else + { + const struct sockaddr *sa = sockaddrof(a) + , *sb = sockaddrof(b); + + passert(addrtypeof(a) == AF_INET); /* not yet implemented IPv6 version :-( */ + return (ntohl(((const struct sockaddr_in *)sa)->sin_addr.s_addr) + < ntohl(((const struct sockaddr_in *)sb)->sin_addr.s_addr)) + ? -1 : 1; + } +} + +/* subnetcmp compares the two ip_subnet values a and b. + * It returns -1, 0, or +1 if a is, respectively, + * less than, equal to, or greater than b. + */ +static int +subnetcmp(const ip_subnet *a, const ip_subnet *b) +{ + ip_address neta, maska, netb, maskb; + int r; + + networkof(a, &neta); + maskof(a, &maska); + networkof(b, &netb); + maskof(b, &maskb); + r = ipcmp(&neta, &netb); + if (r == 0) + r = ipcmp(&maska, &maskb); + return r; +} + +static void +read_foodgroup(struct fg_groups *g) +{ + const char *fgn = g->connection->name; + const ip_subnet *lsn = &g->connection->spd.this.client; + size_t plen = strlen(policygroups_dir) + 1 + strlen(fgn) + 1; + struct file_lex_position flp_space; + + if (plen > fg_path_space) + { + pfreeany(fg_path); + fg_path_space = plen + 10; + fg_path = alloc_bytes(fg_path_space, "policy group path"); + } + snprintf(fg_path, fg_path_space, "%s/%s", policygroups_dir, fgn); + if (!lexopen(&flp_space, fg_path, TRUE)) + { + DBG(DBG_CONTROL, DBG_log("no group file \"%s\"", fg_path)); + } + else + { + plog("loading group \"%s\"", fg_path); + for (;;) + { + switch (flp->bdry) + { + case B_none: + { + /* !!! this test is not sufficient for distinguishing address families. + * We need a notation to specify that a FQDN is to be resolved to IPv6. + */ + const struct af_info *afi = strchr(tok, ':') == NULL + ? &af_inet4_info: &af_inet6_info; + ip_subnet sn; + err_t ugh; + + if (strchr(tok, '/') == NULL) + { + /* no /, so treat as /32 or V6 equivalent */ + ip_address t; + + ugh = ttoaddr(tok, 0, afi->af, &t); + if (ugh == NULL) + ugh = addrtosubnet(&t, &sn); + } + else + { + ugh = ttosubnet(tok, 0, afi->af, &sn); + } + + if (ugh != NULL) + { + loglog(RC_LOG_SERIOUS, "\"%s\" line %d: %s \"%s\"" + , flp->filename, flp->lino, ugh, tok); + } + else if (afi->af != AF_INET) + { + loglog(RC_LOG_SERIOUS + , "\"%s\" line %d: unsupported Address Family \"%s\"" + , flp->filename, flp->lino, tok); + } + else + { + /* Find where new entry ought to go in new_targets. */ + struct fg_targets **pp; + int r; + + for (pp = &new_targets; ; pp = &(*pp)->next) + { + if (*pp == NULL) + { + r = -1; /* end of list is infinite */ + break; + } + r = subnetcmp(lsn, &(*pp)->group->connection->spd.this.client); + if (r == 0) + r = subnetcmp(&sn, &(*pp)->subnet); + if (r <= 0) + break; + } + + if (r == 0) + { + char source[SUBNETTOT_BUF]; + + subnettot(lsn, 0, source, sizeof(source)); + loglog(RC_LOG_SERIOUS + , "\"%s\" line %d: subnet \"%s\", source %s, already \"%s\"" + , flp->filename + , flp->lino + , tok + , source + , (*pp)->group->connection->name); + } + else + { + struct fg_targets *f = alloc_thing(struct fg_targets, "fg_target"); + + f->next = *pp; + f->group = g; + f->subnet = sn; + f->name = NULL; + *pp = f; + } + } + } + (void)shift(); /* next */ + continue; + + case B_record: + flp->bdry = B_none; /* eat the Record Boundary */ + (void)shift(); /* get real first token */ + continue; + + case B_file: + break; /* done */ + } + break; /* if we reach here, out of loop */ + } + lexclose(); + } +} + +static void +free_targets(void) +{ + while (targets != NULL) + { + struct fg_targets *t = targets; + + targets = t->next; + pfreeany(t->name); + pfree(t); + } +} + +void +load_groups(void) +{ + passert(new_targets == NULL); + + /* for each group, add config file targets into new_targets */ + { + struct fg_groups *g; + + for (g = groups; g != NULL; g = g->next) + if (oriented(*g->connection)) + read_foodgroup(g); + } + + /* dump new_targets */ + DBG(DBG_CONTROL, + { + struct fg_targets *t; + + for (t = new_targets; t != NULL; t = t->next) + { + char asource[SUBNETTOT_BUF]; + char atarget[SUBNETTOT_BUF]; + + subnettot(&t->group->connection->spd.this.client + , 0, asource, sizeof(asource)); + subnettot(&t->subnet, 0, atarget, sizeof(atarget)); + DBG_log("%s->%s %s" + , asource, atarget + , t->group->connection->name); + } + }); + + /* determine and deal with differences between targets and new_targets. + * structured like a merge. + */ + { + struct fg_targets *op = targets + , *np = new_targets; + + while (op != NULL && np != NULL) + { + int r = subnetcmp(&op->group->connection->spd.this.client + , &np->group->connection->spd.this.client); + + if (r == 0) + r = subnetcmp(&op->subnet, &np->subnet); + + if (r == 0 && op->group == np->group) + { + /* unchanged -- steal name & skip over */ + np->name = op->name; + op->name = NULL; + op = op->next; + np = np->next; + } + else + { + /* note: following cases overlap! */ + if (r <= 0) + { + remove_group_instance(op->group->connection, op->name); + op = op->next; + } + if (r >= 0) + { + np->name = add_group_instance(np->group->connection, &np->subnet); + np = np->next; + } + } + } + for (; op != NULL; op = op->next) + remove_group_instance(op->group->connection, op->name); + for (; np != NULL; np = np->next) + np->name = add_group_instance(np->group->connection, &np->subnet); + + /* update: new_targets replaces targets */ + free_targets(); + targets = new_targets; + new_targets = NULL; + } +} + + +void +add_group(struct connection *c) +{ + struct fg_groups *g = alloc_thing(struct fg_groups, "policy group"); + + g->next = groups; + groups = g; + + g->connection = c; +} + +static struct fg_groups * +find_group(const struct connection *c) +{ + struct fg_groups *g; + + for (g = groups; g != NULL && g->connection != c; g = g->next) + ; + return g; +} + +void +route_group(struct connection *c) +{ + /* it makes no sense to route a connection that is ISAKMP-only */ + if (!NEVER_NEGOTIATE(c->policy) && !HAS_IPSEC_POLICY(c->policy)) + { + loglog(RC_ROUTE, "cannot route an ISAKMP-only group connection"); + } + else + { + struct fg_groups *g = find_group(c); + struct fg_targets *t; + + passert(g != NULL); + g->connection->policy |= POLICY_GROUTED; + for (t = targets; t != NULL; t = t->next) + { + if (t->group == g) + { + struct connection *ci = con_by_name(t->name, FALSE); + + if (ci != NULL) + { + set_cur_connection(ci); + if (!trap_connection(ci)) + whack_log(RC_ROUTE, "could not route"); + set_cur_connection(c); + } + } + } + } +} + +void +unroute_group(struct connection *c) +{ + struct fg_groups *g = find_group(c); + struct fg_targets *t; + + passert(g != NULL); + g->connection->policy &= ~POLICY_GROUTED; + for (t = targets; t != NULL; t = t->next) + { + if (t->group == g) + { + struct connection *ci = con_by_name(t->name, FALSE); + + if (ci != NULL) + { + set_cur_connection(ci); + unroute_connection(ci); + set_cur_connection(c); + } + } + } +} + +void +delete_group(const struct connection *c) +{ + struct fg_groups *g; + + /* find and remove from groups */ + { + struct fg_groups **pp; + + for (pp = &groups; (g = *pp)->connection != c; pp = &(*pp)->next) + ; + + *pp = g->next; + } + + /* find and remove from targets */ + { + struct fg_targets **pp; + + for (pp = &targets; *pp != NULL; ) + { + struct fg_targets *t = *pp; + + if (t->group == g) + { + *pp = t->next; + remove_group_instance(t->group->connection, t->name); + pfree(t); + /* pp is ready for next iteration */ + } + else + { + pp = &t->next; + } + } + } + + pfree(g); +} diff --git a/src/pluto/foodgroups.h b/src/pluto/foodgroups.h new file mode 100644 index 000000000..7cbbccc44 --- /dev/null +++ b/src/pluto/foodgroups.h @@ -0,0 +1,24 @@ +/* Implement policygroups-style control files (aka "foodgroups") + * Copyright (C) 2002 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: foodgroups.h,v 1.1 2004/03/15 20:35:28 as Exp $ + */ + +struct connection; /* forward declaration */ +extern void add_group(struct connection *c); +extern void route_group(struct connection *c); +extern void unroute_group(struct connection *c); +extern void delete_group(const struct connection *c); + +extern const char *policygroups_dir; +extern void load_groups(void); diff --git a/src/pluto/gcryptfix.c b/src/pluto/gcryptfix.c new file mode 100644 index 000000000..1ebacdcf6 --- /dev/null +++ b/src/pluto/gcryptfix.c @@ -0,0 +1,283 @@ +/* Routines to make gcrypt routines feel at home in Pluto. + * Copyright (C) 1999 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: gcryptfix.c,v 1.1 2004/03/15 20:35:28 as Exp $ + */ + +#include <stdlib.h> + +#include <gmp.h> +#include <freeswan.h> +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "rnd.h" +#include "gcryptfix.h" /* includes <gmp.h> "defs.h" "rnd.h" */ + +MPI +mpi_alloc( unsigned nlimbs UNUSED ) +{ + MPI n = alloc_bytes(sizeof *n, "mpi_alloc"); + + mpz_init(n); + return n; +} + +MPI +mpi_alloc_secure( unsigned nlimbs ) +{ + return mpi_alloc(nlimbs); +} + +MPI +mpi_alloc_set_ui( unsigned long u) +{ + MPI n = alloc_bytes(sizeof *n, "mpi_copy"); + + mpz_init_set_ui(n, u); + return n; +} + +MPI +mpi_copy( MPI a ) +{ + MPI n = alloc_bytes(sizeof *n, "mpi_copy"); + + mpz_init_set(n, a); + return n; +} + +void +mpi_free( MPI a ) +{ + mpz_clear(a); + pfree(a); +} + +int +mpi_divisible_ui(MPI dividend, ulong divisor ) +{ + ulong rem; + mpz_t remtoo; + + mpz_init(remtoo); + rem = mpz_mod_ui(remtoo, dividend, divisor); + mpz_clear(remtoo); + return rem == 0; +} + +unsigned +mpi_trailing_zeros( MPI a ) +{ + return mpz_scan1(a, 0); +} + +unsigned +mpi_get_nbits( MPI a ) +{ + return mpz_sizeinbase(a, 2); +} + +int +mpi_test_bit( MPI a, unsigned n ) +{ + /* inspired by gmp/mpz/clrbit.c */ + mp_size_t li = n / mp_bits_per_limb; + + if (li >= a->_mp_size) + return 0; + return (a->_mp_d[li] & ((mp_limb_t) 1 << (n % mp_bits_per_limb))) != 0; +} + +void +mpi_set_bit( MPI a, unsigned n ) +{ + mpz_setbit(a, n); +} + +void +mpi_clear_bit( MPI a, unsigned n ) +{ + mpz_clrbit(a, n); +} + +void +mpi_clear_highbit( MPI a, unsigned n ) +{ + /* This seems whacky, but what do I know. */ + mpz_fdiv_r_2exp(a, a, n); +} + +void +mpi_set_highbit( MPI a, unsigned n ) +{ + /* This seems whacky, but what do I know. */ + mpz_fdiv_r_2exp(a, a, n+1); + mpz_setbit(a, n); +} + +void +mpi_set_buffer( MPI a, const u_char *buffer, unsigned nbytes, int sign ) +{ + /* this is a lot like n_to_mpz */ + size_t i; + + passert(sign == 0); /* we won't hit any negative numbers */ + mpz_init_set_ui(a, 0); + + for (i = 0; i != nbytes; i++) + { + mpz_mul_ui(a, a, 1 << BITS_PER_BYTE); + mpz_add_ui(a, a, buffer[i]); + } +} + +u_char * +get_random_bits(size_t nbits, int level UNUSED, int secure UNUSED) +{ + size_t nbytes = (nbits+7)/8; + u_char *b = alloc_bytes(nbytes, "random bytes"); + + get_rnd_bytes(b, nbytes); + return b; +} +/**************** from gnupg-1.0.0/mpi/mpi-mpow.c + * RES = (BASE[0] ^ EXP[0]) * (BASE[1] ^ EXP[1]) * ... * mod M + */ +#define barrett_mulm( w, u, v, m, y, k, r1, r2 ) mpi_mulm( (w), (u), (v), (m) ) + +static int +build_index( MPI *exparray, int k, int i, int t ) +{ + int j, bitno; + int index = 0; + + bitno = t-i; + for(j=k-1; j >= 0; j-- ) { + index <<= 1; + if( mpi_test_bit( exparray[j], bitno ) ) + index |= 1; + } + /*log_debug("t=%d i=%d index=%d\n", t, i, index );*/ + return index; +} + +void +mpi_mulpowm( MPI res, MPI *basearray, MPI *exparray, MPI m) +{ + int k; /* number of elements */ + int t; /* bit size of largest exponent */ + int i, j, idx; + MPI *G; /* table with precomputed values of size 2^k */ + MPI tmp; + #ifdef USE_BARRETT + MPI barrett_y, barrett_r1, barrett_r2; + int barrett_k; + #endif + + for(k=0; basearray[k]; k++ ) + ; + passert(k); + for(t=0, i=0; (tmp=exparray[i]); i++ ) { + /*log_mpidump("exp: ", tmp );*/ + j = mpi_get_nbits(tmp); + if( j > t ) + t = j; + } + /*log_mpidump("mod: ", m );*/ + passert(i==k); + passert(t); + passert( k < 10 ); + +#ifdef PLUTO + m_alloc_ptrs_clear(G, 1<<k); +#else + G = m_alloc_clear( (1<<k) * sizeof *G ); +#endif + + #ifdef USE_BARRETT + barrett_y = init_barrett( m, &barrett_k, &barrett_r1, &barrett_r2 ); + #endif + /* and calculate */ + tmp = mpi_alloc( mpi_get_nlimbs(m)+1 ); + mpi_set_ui( res, 1 ); + for(i = 1; i <= t; i++ ) { + barrett_mulm(tmp, res, res, m, barrett_y, barrett_k, + barrett_r1, barrett_r2 ); + idx = build_index( exparray, k, i, t ); + passert( idx >= 0 && idx < (1<<k) ); + if( !G[idx] ) { + if( !idx ) + G[0] = mpi_alloc_set_ui( 1 ); + else { + for(j=0; j < k; j++ ) { + if( (idx & (1<<j) ) ) { + if( !G[idx] ) + G[idx] = mpi_copy( basearray[j] ); + else + barrett_mulm( G[idx], G[idx], basearray[j], + m, barrett_y, barrett_k, barrett_r1, barrett_r2 ); + } + } + if( !G[idx] ) + G[idx] = mpi_alloc(0); + } + } + barrett_mulm(res, tmp, G[idx], m, barrett_y, barrett_k, barrett_r1, barrett_r2 ); + } + + /* cleanup */ + mpi_free(tmp); + #ifdef USE_BARRETT + mpi_free(barrett_y); + mpi_free(barrett_r1); + mpi_free(barrett_r2); + #endif + for(i=0; i < (1<<k); i++ ) + mpi_free(G[i]); + m_free(G); +} + +void +log_mpidump( const char *text UNUSED, MPI a ) +{ + /* Print number in hex -- helpful to see if they match bytes. + * Humans are not going to do arithmetic with the large numbers! + * Much code adapted from mpz_to_n. + */ + u_char buf[8048]; /* this ought to be big enough */ + size_t len = (mpz_sizeinbase(a, 16) + 1) / 2; /* bytes */ + MP_INT temp1, temp2; + int i; + + passert(len <= sizeof(buf)); + + mpz_init(&temp1); + mpz_init(&temp2); + + mpz_set(&temp1, a); + + for (i = len-1; i >= 0; i--) + { + buf[i] = mpz_mdivmod_ui(&temp2, NULL, &temp1, 1 << BITS_PER_BYTE); + mpz_set(&temp1, &temp2); + } + + passert(mpz_sgn(&temp1) == 0); /* we must have done all the bits */ + mpz_clear(&temp1); + mpz_clear(&temp2); + +#ifdef DEBUG + DBG_dump(text, buf, len); +#endif /* DEBUG */ +} diff --git a/src/pluto/gcryptfix.h b/src/pluto/gcryptfix.h new file mode 100644 index 000000000..637ecbc8d --- /dev/null +++ b/src/pluto/gcryptfix.h @@ -0,0 +1,111 @@ +/* Definitions to make gcrypt routines feel at home in Pluto. + * Copyright (C) 1999 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: gcryptfix.h,v 1.1 2004/03/15 20:35:28 as Exp $ + */ + +#define DBG_CIPHER 1 /* some day we'll do this right */ + +/* Simulate MPI routines with gmp routines. + * gmp's MP_INT is a stuct; MPI's MPI is a pointer to an analogous struct. + * gmp's mpz_t is an array of one of these structs to enable magic pointer + * conversions to make the notation convenient (but confusing). + */ +typedef u_char byte; +typedef MP_INT *MPI; + +#define BITS_PER_MPI_LIMB mp_bits_per_limb + +extern MPI mpi_alloc( unsigned nlimbs ); +extern MPI mpi_alloc_secure( unsigned nlimbs ); +#define mpi_alloc_like(n) mpi_alloc(mpi_get_nlimbs(n)) +extern MPI mpi_alloc_set_ui( unsigned long u); +#define mpi_set_ui(w, u) mpz_set_ui(w, u) +#define mpi_set(w, u) mpz_set(w, u) +extern void mpi_free( MPI a ); +extern MPI mpi_copy( MPI a ); +extern unsigned mpi_get_nbits( MPI a ); +#define mpi_get_nlimbs(a) ((a)->_mp_alloc) /* dirty, but useless */ +extern void mpi_set_buffer( MPI a, const u_char *buffer, unsigned nbytes, int sign ); +extern unsigned mpi_trailing_zeros( MPI a ); +extern int mpi_test_bit( MPI a, unsigned n ); +extern void mpi_set_bit( MPI a, unsigned n ); +extern void mpi_clear_bit( MPI a, unsigned n ); +extern void mpi_clear_highbit( MPI a, unsigned n ); +extern void mpi_set_highbit( MPI a, unsigned n ); +#define mpi_cmp_ui(u, v) mpz_cmp_ui((u), (v)) +#define mpi_cmp(u, v) mpz_cmp((u), (v)) +#define mpi_is_neg(n) (mpz_sgn(n) < 0) +#define mpi_add(w, u, v) mpz_add((w), (u), (v)) +#define mpi_add_ui(w, u, v) mpz_add_ui((w), (u), (v)) +#define mpi_sub_ui(w, u, v) mpz_sub_ui((w), (u), (v)) +#define mpi_subm( w, u, v, m) { mpz_sub( (w), (u), (v)) ; mpz_fdiv_r((w), (w), (m)); } +#define mpi_mul( w, u, v) mpz_mul( (w), (u), (v)) +#define mpi_mul_ui( w, u, v) mpz_mul_ui( (w), (u), (v)) +#define mpi_mulm( w, u, v, m) { mpz_mul( (w), (u), (v)) ; mpz_fdiv_r((w), (w), (m)); } +#define mpi_fdiv_q(quot, dividend, divisor) mpz_fdiv_q((quot), (dividend), (divisor)) +#define mpi_fdiv_r( rem, dividend, divisor ) mpz_fdiv_r( (rem), (dividend), (divisor) ) +#define mpi_fdiv_r_ui( rem, dividend, divisor ) mpz_fdiv_r_ui( (rem), (dividend), (divisor) ) +#define mpi_tdiv_q_2exp( w, u, count ) mpz_tdiv_q_2exp( (w), (u), (count) ) +extern int mpi_divisible_ui(MPI dividend, ulong divisor ); +#define mpi_powm( res, base, exp, mod) mpz_powm( res, base, exp, mod) +extern void mpi_mulpowm( MPI res, MPI *basearray, MPI *exparray, MPI mod); +#define mpi_gcd( g, a, b ) ( mpz_gcd( (g), (a), (b) ), !mpi_cmp_ui( (g), 1)) +#define mpi_invm( x, a, n ) mpz_invert( (x), (a), (n) ) + +#ifdef DEBUG +# define log_debug(f...) DBG_log(f) +#else +# define log_debug(f...) do ; while (0) /* do nothing, carefully */ +#endif +#define log_fatal(f...) exit_log(f) /* overreaction? */ +extern void log_mpidump( const char *text, MPI a ); + +#define assert(p) passert(p) +#define BUG() passert(FALSE) + +#define m_alloc_ptrs_clear(pp, n) { \ + int c = (n); \ + (pp) = alloc_bytes((n) * sizeof(*(pp)), "m_alloc_ptrs_clear"); \ + while (c > 0) (pp)[--c] = NULL; \ + } + +extern u_char *get_random_bits(size_t nbits, int level, int secure); +#define m_alloc(sz) alloc_bytes((sz), "m_alloc") /* not initialized */ +#define m_free(n) pfree(n) /* always freeing something from get_random_bits */ + +/* declarations from gnupg-1.0.0/include/cipher.h */ +/*-- primegen.c --*/ +MPI generate_secret_prime( unsigned nbits ); +MPI generate_public_prime( unsigned nbits ); +MPI generate_elg_prime( int mode, unsigned pbits, unsigned qbits, + MPI g, MPI **factors ); + +#define PUBKEY_ALGO_ELGAMAL_E 16 /* encrypt only ElGamal (but not for v3)*/ +#define PUBKEY_ALGO_DSA 17 +#define PUBKEY_ALGO_ELGAMAL 20 /* sign and encrypt elgamal */ + +#define is_ELGAMAL(a) ((a)==PUBKEY_ALGO_ELGAMAL || (a)==PUBKEY_ALGO_ELGAMAL_E) + +#define PUBKEY_USAGE_SIG 1 /* key is good for signatures */ +#define PUBKEY_USAGE_ENC 2 /* key is good for encryption */ + +/* from gnupg-1.0.0/include/errors.h */ + +#define G10ERR_PUBKEY_ALGO 4 /* Unknown pubkey algorithm */ +#define G10ERR_BAD_SECKEY 7 /* Bad secret key */ +#define G10ERR_BAD_SIGN 8 /* Bad signature */ +#define G10ERR_BAD_MPI 30 + +/*-- smallprime.c --*/ +extern ushort small_prime_numbers[]; diff --git a/src/pluto/id.c b/src/pluto/id.c new file mode 100644 index 000000000..4e75ec2e9 --- /dev/null +++ b/src/pluto/id.c @@ -0,0 +1,509 @@ +/* identity representation, as in IKE ID Payloads (RFC 2407 DOI 4.6.2.1) + * Copyright (C) 1999-2001 D. Hugh Redelmeier + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: id.c,v 1.4 2005/08/15 20:07:08 as Exp $ + */ + +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#ifndef HOST_NAME_MAX /* POSIX 1003.1-2001 says <unistd.h> defines this */ +# define HOST_NAME_MAX 255 /* upper bound, according to SUSv2 */ +#endif +#include <sys/queue.h> + +#include <freeswan.h> +#include <ipsec_policy.h> + +#include "constants.h" +#include "defs.h" +#include "id.h" +#include "log.h" +#include "connections.h" +#include "packet.h" +#include "whack.h" + +const struct id empty_id; /* ID_NONE */ + +enum myid_state myid_state = MYID_UNKNOWN; +struct id myids[MYID_SPECIFIED+1]; /* %myid */ +char *myid_str[MYID_SPECIFIED+1]; /* string form of IDs */ + +/* initialize id module + * Fills in myid from environment variable IPSECmyid or defaultrouteaddr + */ +void +init_id(void) +{ + passert(empty_id.kind == ID_NONE); + myid_state = MYID_UNKNOWN; + { + enum myid_state s; + + for (s = MYID_UNKNOWN; s <= MYID_SPECIFIED; s++) + { + myids[s] = empty_id; + myid_str[s] = NULL; + } + } + set_myid(MYID_SPECIFIED, getenv("IPSECmyid")); + set_myid(MYID_IP, getenv("defaultrouteaddr")); + set_myFQDN(); +} + +static void +calc_myid_str(enum myid_state s) +{ + /* preformat the ID name */ + char buf[BUF_LEN]; + + idtoa(&myids[s], buf, BUF_LEN); + replace(myid_str[s], clone_str(buf, "myid string")); +} + + +void +set_myid(enum myid_state s, char *idstr) +{ + if (idstr != NULL) + { + struct id id; + err_t ugh = atoid(idstr, &id, FALSE); + + if (ugh != NULL) + { + loglog(RC_BADID, "myid malformed: %s \"%s\"", ugh, idstr); + } + else + { + free_id_content(&myids[s]); + unshare_id_content(&id); + myids[s] = id; + if (s == MYID_SPECIFIED) + myid_state = MYID_SPECIFIED; + + calc_myid_str(s); + } + } +} + +void +set_myFQDN(void) +{ + char FQDN[HOST_NAME_MAX + 1]; + int r = gethostname(FQDN, sizeof(FQDN)); + + free_id_content(&myids[MYID_HOSTNAME]); + myids[MYID_HOSTNAME] = empty_id; + if (r != 0) + { + log_errno((e, "gethostname() failed in set_myFQDN")); + } + else + { + FQDN[sizeof(FQDN) - 1] = '\0'; /* insurance */ + + { + size_t len = strlen(FQDN); + + if (len > 0 && FQDN[len-1] == '.') + { + /* nuke trailing . */ + FQDN[len-1]='\0'; + } + } + + if (!strcaseeq(FQDN, "localhost.localdomain")) + { + clonetochunk(myids[MYID_HOSTNAME].name, FQDN, strlen(FQDN), "my FQDN"); + myids[MYID_HOSTNAME].kind = ID_FQDN; + calc_myid_str(MYID_HOSTNAME); + } + } +} + +void +show_myid_status(void) +{ + char idstr[BUF_LEN]; + + (void)idtoa(&myids[myid_state], idstr, sizeof(idstr)); + whack_log(RC_COMMENT, "%%myid = %s", idstr); +} + +/* Convert textual form of id into a (temporary) struct id. + * Note that if the id is to be kept, unshare_id_content will be necessary. + */ +err_t +atoid(char *src, struct id *id, bool myid_ok) +{ + err_t ugh = NULL; + + *id = empty_id; + + if (myid_ok && streq("%myid", src)) + { + id->kind = ID_MYID; + } + else if (strchr(src, '=') != NULL) + { + /* we interpret this as an ASCII X.501 ID_DER_ASN1_DN */ + id->kind = ID_DER_ASN1_DN; + id->name.ptr = temporary_cyclic_buffer(); /* assign temporary buffer */ + id->name.len = 0; + /* convert from LDAP style or openssl x509 -subject style to ASN.1 DN + * discard optional @ character in front of DN + */ + ugh = atodn((*src == '@')?src+1:src, &id->name); + } + else if (strchr(src, '@') == NULL) + { + if (streq(src, "%any") || streq(src, "0.0.0.0")) + { + /* any ID will be accepted */ + id->kind = ID_NONE; + } + else + { + /* !!! this test is not sufficient for distinguishing address families. + * We need a notation to specify that a FQDN is to be resolved to IPv6. + */ + const struct af_info *afi = strchr(src, ':') == NULL + ? &af_inet4_info: &af_inet6_info; + + id->kind = afi->id_addr; + ugh = ttoaddr(src, 0, afi->af, &id->ip_addr); + } + } + else + { + if (*src == '@') + { + if (*(src+1) == '#') + { + /* if there is a second specifier (#) on the line + * we interprete this as ID_KEY_ID + */ + id->kind = ID_KEY_ID; + id->name.ptr = src; + /* discard @~, convert from hex to bin */ + ugh = ttodata(src+2, 0, 16, id->name.ptr, strlen(src), &id->name.len); + } + else if (*(src+1) == '~') + { + /* if there is a second specifier (~) on the line + * we interprete this as a binary ID_DER_ASN1_DN + */ + id->kind = ID_DER_ASN1_DN; + id->name.ptr = src; + /* discard @~, convert from hex to bin */ + ugh = ttodata(src+2, 0, 16, id->name.ptr, strlen(src), &id->name.len); + } + else + { + id->kind = ID_FQDN; + id->name.ptr = src+1; /* discard @ */ + id->name.len = strlen(src)-1; + } + } + else + { + /* We leave in @, as per DOI 4.6.2.4 + * (but DNS wants . instead). + */ + id->kind = ID_USER_FQDN; + id->name.ptr = src; + id->name.len = strlen(src); + } + } + return ugh; +} + + +/* + * Converts a binary key ID into hexadecimal format + */ +int +keyidtoa(char *dst, size_t dstlen, chunk_t keyid) +{ + int n = datatot(keyid.ptr, keyid.len, 'x', dst, dstlen); + return (((size_t)n < dstlen)? n : dstlen) - 1; +} + +void +iptoid(const ip_address *ip, struct id *id) +{ + *id = empty_id; + + switch (addrtypeof(ip)) + { + case AF_INET: + id->kind = ID_IPV4_ADDR; + break; + case AF_INET6: + id->kind = ID_IPV6_ADDR; + break; + default: + bad_case(addrtypeof(ip)); + } + id->ip_addr = *ip; +} + +int +idtoa(const struct id *id, char *dst, size_t dstlen) +{ + int n; + + id = resolve_myid(id); + switch (id->kind) + { + case ID_NONE: + n = snprintf(dst, dstlen, "(none)"); + break; + case ID_IPV4_ADDR: + case ID_IPV6_ADDR: + n = (int)addrtot(&id->ip_addr, 0, dst, dstlen) - 1; + break; + case ID_FQDN: + n = snprintf(dst, dstlen, "@%.*s", (int)id->name.len, id->name.ptr); + break; + case ID_USER_FQDN: + n = snprintf(dst, dstlen, "%.*s", (int)id->name.len, id->name.ptr); + break; + case ID_DER_ASN1_DN: + n = dntoa(dst, dstlen, id->name); + break; + case ID_KEY_ID: + n = keyidtoa(dst, dstlen, id->name); + break; + default: + n = snprintf(dst, dstlen, "unknown id kind %d", id->kind); + break; + } + + /* "Sanitize" string so that log isn't endangered: + * replace unprintable characters with '?'. + */ + if (n > 0) + { + for ( ; *dst != '\0'; dst++) + if (!isprint(*dst)) + *dst = '?'; + } + + return n; +} + +/* Replace the shell metacharacters ', \, ", `, and $ in a character string + * by escape sequences consisting of their octal values + */ +void +escape_metachar(const char *src, char *dst, size_t dstlen) +{ + while (*src != '\0' && dstlen > 4) + { + switch (*src) + { + case '\'': + case '\\': + case '"': + case '`': + case '$': + sprintf(dst,"\\%s%o", (*src < 64)?"0":"", *src); + dst += 4; + dstlen -= 4; + break; + default: + *dst++ = *src; + dstlen--; + } + src++; + } + *dst = '\0'; +} + + +/* Make private copy of string in struct id. + * This is needed if the result of atoid is to be kept. + */ +void +unshare_id_content(struct id *id) +{ + switch (id->kind) + { + case ID_FQDN: + case ID_USER_FQDN: + case ID_DER_ASN1_DN: + case ID_KEY_ID: + id->name.ptr = clone_bytes(id->name.ptr, id->name.len, "keep id name"); + break; + case ID_MYID: + case ID_NONE: + case ID_IPV4_ADDR: + case ID_IPV6_ADDR: + break; + default: + bad_case(id->kind); + } +} + +void +free_id_content(struct id *id) +{ + switch (id->kind) + { + case ID_FQDN: + case ID_USER_FQDN: + case ID_DER_ASN1_DN: + case ID_KEY_ID: + freeanychunk(id->name); + break; + case ID_MYID: + case ID_NONE: + case ID_IPV4_ADDR: + case ID_IPV6_ADDR: + break; + default: + bad_case(id->kind); + } +} + +/* compare two struct id values */ +bool +same_id(const struct id *a, const struct id *b) +{ + a = resolve_myid(a); + b = resolve_myid(b); + if (a->kind != b->kind) + return FALSE; + switch (a->kind) + { + case ID_NONE: + return TRUE; /* kind of vacuous */ + + case ID_IPV4_ADDR: + case ID_IPV6_ADDR: + return sameaddr(&a->ip_addr, &b->ip_addr); + + case ID_FQDN: + case ID_USER_FQDN: + /* assumptions: + * - case should be ignored + * - trailing "." should be ignored (even if the only character?) + */ + { + size_t al = a->name.len + , bl = b->name.len; + + while (al > 0 && a->name.ptr[al - 1] == '.') + al--; + while (bl > 0 && b->name.ptr[bl - 1] == '.') + bl--; + return al == bl + && strncasecmp(a->name.ptr, b->name.ptr, al) == 0; + } + + case ID_DER_ASN1_DN: + return same_dn(a->name, b->name); + + case ID_KEY_ID: + return a->name.len == b->name.len + && memcmp(a->name.ptr, b->name.ptr, a->name.len) == 0; + + default: + bad_case(a->kind); + } + return FALSE; +} + +/* compare two struct id values, DNs can contain wildcards */ +bool +match_id(const struct id *a, const struct id *b, int *wildcards) +{ + if (b->kind == ID_NONE) + { + *wildcards = MAX_WILDCARDS; + return TRUE; + } + if (a->kind != b->kind) + return FALSE; + if (a->kind == ID_DER_ASN1_DN) + return match_dn(a->name, b->name, wildcards); + else + { + *wildcards = 0; + return same_id(a, b); + } +} + +/* count the numer of wildcards in an id */ +int +id_count_wildcards(const struct id *id) +{ + switch (id->kind) + { + case ID_NONE: + return MAX_WILDCARDS; + case ID_DER_ASN1_DN: + return dn_count_wildcards(id->name); + default: + return 0; + } +} + +/* build an ID payload + * Note: no memory is allocated for the body of the payload (tl->ptr). + * We assume it will end up being a pointer into a sufficiently + * stable datastructure. It only needs to last a short time. + */ +void +build_id_payload(struct isakmp_ipsec_id *hd, chunk_t *tl, struct end *end) +{ + const struct id *id = resolve_myid(&end->id); + + zero(hd); + hd->isaiid_idtype = id->kind; + switch (id->kind) + { + case ID_NONE: + hd->isaiid_idtype = aftoinfo(addrtypeof(&end->host_addr))->id_addr; + tl->len = addrbytesptr(&end->host_addr + , (const unsigned char **)&tl->ptr); /* sets tl->ptr too */ + break; + case ID_FQDN: + case ID_USER_FQDN: + case ID_DER_ASN1_DN: + case ID_KEY_ID: + *tl = id->name; + break; + case ID_IPV4_ADDR: + case ID_IPV6_ADDR: + tl->len = addrbytesptr(&id->ip_addr + , (const unsigned char **)&tl->ptr); /* sets tl->ptr too */ + break; + default: + bad_case(id->kind); + } +} + +/* + * Local Variables: + * c-basic-offset:4 + * c-style: pluto + * End: + */ diff --git a/src/pluto/id.h b/src/pluto/id.h new file mode 100644 index 000000000..4fe9ef227 --- /dev/null +++ b/src/pluto/id.h @@ -0,0 +1,67 @@ +/* identity representation, as in IKE ID Payloads (RFC 2407 DOI 4.6.2.1) + * Copyright (C) 1999-2001 D. Hugh Redelmeier + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: id.h,v 1.5 2005/08/15 20:07:08 as Exp $ + */ + +#ifndef _ID_H +#define _ID_H + +#include "defs.h" + +struct id { + int kind; /* ID_* value */ + ip_address ip_addr; /* ID_IPV4_ADDR, ID_IPV6_ADDR */ + chunk_t name; /* ID_FQDN, ID_USER_FQDN (with @) */ + /* ID_KEY_ID, ID_DER_ASN_DN */ +}; + +extern void init_id(void); + +extern const struct id empty_id; /* ID_NONE */ + +enum myid_state { + MYID_UNKNOWN, /* not yet figured out */ + MYID_HOSTNAME, /* our current hostname */ + MYID_IP, /* our default IP address */ + MYID_SPECIFIED /* as specified by ipsec.conf */ +}; + +extern enum myid_state myid_state; +extern struct id myids[MYID_SPECIFIED+1]; /* %myid */ +extern char *myid_str[MYID_SPECIFIED+1]; /* strings */ +extern void set_myid(enum myid_state s, char *); +extern void show_myid_status(void); +#define resolve_myid(id) ((id)->kind == ID_MYID? &myids[myid_state] : (id)) +extern void set_myFQDN(void); + +extern err_t atoid(char *src, struct id *id, bool myid_ok); +extern int keyidtoa(char *dst, size_t dstlen, chunk_t keyid); +extern void iptoid(const ip_address *ip, struct id *id); +extern int idtoa(const struct id *id, char *dst, size_t dstlen); +#define IDTOA_BUF 512 +extern void escape_metachar(const char *src, char *dst, size_t dstlen); +struct end; /* forward declaration of tag (defined in connections.h) */ +extern void unshare_id_content(struct id *id); +extern void free_id_content(struct id *id); +extern bool same_id(const struct id *a, const struct id *b); +#define MAX_WILDCARDS 15 +extern bool match_id(const struct id *a, const struct id *b, int *wildcards); +extern int id_count_wildcards(const struct id *id); +#define id_is_ipaddr(id) ((id)->kind == ID_IPV4_ADDR || (id)->kind == ID_IPV6_ADDR) + +struct isakmp_ipsec_id; /* forward declaration of tag (defined in packet.h) */ +extern void + build_id_payload(struct isakmp_ipsec_id *hd, chunk_t *tl, struct end *end); + +#endif /* _ID_H */ diff --git a/src/pluto/ike_alg.c b/src/pluto/ike_alg.c new file mode 100644 index 000000000..1c6514b4b --- /dev/null +++ b/src/pluto/ike_alg.c @@ -0,0 +1,592 @@ +/* IKE modular algorithm handling interface + * Author: JuanJo Ciarlante <jjo-ipsec@mendoza.gov.ar> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: ike_alg.c,v 1.6 2004/09/17 21:29:50 as Exp $ + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/queue.h> + +#include <freeswan.h> +#include <ipsec_policy.h> + +#include "constants.h" +#include "defs.h" +#include "sha1.h" +#include "md5.h" +#include "crypto.h" + +#include "state.h" +#include "packet.h" +#include "log.h" +#include "whack.h" +#include "spdb.h" +#include "alg_info.h" +#include "ike_alg.h" +#include "db_ops.h" +#include "connections.h" +#include "kernel.h" + +#define return_on(var, val) do { var=val;goto return_out; } while(0); + +/* + * IKE algorithm list handling - registration and lookup + */ + +/* Modular IKE algorithm storage structure */ + +static struct ike_alg *ike_alg_base[IKE_ALG_MAX+1] = {NULL, NULL}; + +/* + * return ike_algo object by {type, id} + */ +static struct ike_alg * +ike_alg_find(u_int algo_type, u_int algo_id, u_int keysize __attribute__((unused))) +{ + struct ike_alg *e = ike_alg_base[algo_type]; + + while (e != NULL && algo_id > e->algo_id) + { + e = e->algo_next; + } + return (e != NULL && e->algo_id == algo_id) ? e : NULL; +} + +/* + * "raw" ike_alg list adding function + */ +int +ike_alg_add(struct ike_alg* a) +{ + if (a->algo_type > IKE_ALG_MAX) + { + plog("ike_alg: Not added, invalid algorithm type"); + return -EINVAL; + } + + if (ike_alg_find(a->algo_type, a->algo_id, 0) != NULL) + { + plog("ike_alg: Not added, algorithm already exists"); + return -EEXIST; + } + + { + struct ike_alg **ep = &ike_alg_base[a->algo_type]; + struct ike_alg *e = *ep; + + while (e != NULL && a->algo_id > e->algo_id) + { + ep = &e->algo_next; + e = *ep; + } + *ep = a; + a->algo_next = e; + return 0; + } +} + +/* + * get IKE hash algorithm + */ +struct hash_desc *ike_alg_get_hasher(u_int alg) +{ + return (struct hash_desc *) ike_alg_find(IKE_ALG_HASH, alg, 0); +} + +/* + * get IKE encryption algorithm + */ +struct encrypt_desc *ike_alg_get_encrypter(u_int alg) +{ + return (struct encrypt_desc *) ike_alg_find(IKE_ALG_ENCRYPT, alg, 0); +} + +/* + * check if IKE hash algorithm is present + */ +bool +ike_alg_hash_present(u_int halg) +{ + return ike_alg_get_hasher(halg) != NULL; +} + +/* + * check if IKE encryption algorithm is present + */ +bool +ike_alg_enc_present(u_int ealg) +{ + return ike_alg_get_encrypter(ealg) != NULL; +} + +/* + * Validate and register IKE hash algorithm object + */ +int +ike_alg_register_hash(struct hash_desc *hash_desc) +{ + const char *alg_name = NULL; + int ret = 0; + + if (hash_desc->algo_id > OAKLEY_HASH_MAX) + { + plog ("ike_alg: hash alg=%d > max=%d" + , hash_desc->algo_id, OAKLEY_HASH_MAX); + return_on(ret,-EINVAL); + } + + if (hash_desc->hash_ctx_size > sizeof (union hash_ctx)) + { + plog ("ike_alg: hash alg=%d has ctx_size=%d > hash_ctx=%d" + , hash_desc->algo_id + , (int)hash_desc->hash_ctx_size + , (int)sizeof (union hash_ctx)); + return_on(ret,-EOVERFLOW); + } + + if (!(hash_desc->hash_init && hash_desc->hash_update && hash_desc->hash_final)) + { + plog ("ike_alg: hash alg=%d needs hash_init(), hash_update() and hash_final()" + , hash_desc->algo_id); + return_on(ret,-EINVAL); + } + + alg_name = enum_name(&oakley_hash_names, hash_desc->algo_id); + if (!alg_name) + { + plog ("ike_alg: hash alg=%d not found in constants.c:oakley_hash_names" + , hash_desc->algo_id); + alg_name = "<NULL>"; + } + +return_out: + if (ret == 0) + ret = ike_alg_add((struct ike_alg *)hash_desc); + + plog("ike_alg: Activating %s hash: %s" + ,alg_name, ret == 0 ? "Ok" : "FAILED"); + + return ret; +} + +/* + * Validate and register IKE encryption algorithm object + */ +int +ike_alg_register_enc(struct encrypt_desc *enc_desc) +{ + int ret = ike_alg_add((struct ike_alg *)enc_desc); + + const char *alg_name = enum_name(&oakley_enc_names, enc_desc->algo_id); + + char alg_number[20]; + + /* algorithm is not listed in oakley_enc_names */ + if (alg_name == NULL) + { + snprintf(alg_number, sizeof(alg_number), "OAKLEY_ID_%d" + , enc_desc->algo_id); + alg_name = alg_number; + } + + plog("ike_alg: Activating %s encryption: %s" + , alg_name, ret == 0 ? "Ok" : "FAILED"); + + return ret; +} + +/* + * Get pfsgroup for this connection + */ +const struct oakley_group_desc * +ike_alg_pfsgroup(struct connection *c, lset_t policy) +{ + const struct oakley_group_desc * ret = NULL; + + if ((policy & POLICY_PFS) + && c->alg_info_esp + && c->alg_info_esp->esp_pfsgroup) + ret = lookup_group(c->alg_info_esp->esp_pfsgroup); + return ret; +} + +/* + * Create an OAKLEY proposal based on alg_info and policy + */ +struct db_context * +ike_alg_db_new(struct alg_info_ike *ai , lset_t policy) +{ + struct db_context *db_ctx = NULL; + struct ike_info *ike_info; + struct encrypt_desc *enc_desc; + u_int ealg, halg, modp, eklen = 0; + int i; + + bool is_xauth_server = (policy & POLICY_XAUTH_SERVER) != LEMPTY; + + if (!ai) + { + whack_log(RC_LOG_SERIOUS, "no IKE algorithms " + "for this connection " + "(check ike algorithm string)"); + goto fail; + } + policy &= POLICY_ID_AUTH_MASK; + db_ctx = db_prop_new(PROTO_ISAKMP, 8, 8 * 5); + + /* for each group */ + ALG_INFO_IKE_FOREACH(ai, ike_info, i) + { + ealg = ike_info->ike_ealg; + halg = ike_info->ike_halg; + modp = ike_info->ike_modp; + eklen= ike_info->ike_eklen; + + if (!ike_alg_enc_present(ealg)) + { + DBG_log("ike_alg: ike enc ealg=%d not present" + , ealg); + continue; + } + + if (!ike_alg_hash_present(halg)) + { + DBG_log("ike_alg: ike hash halg=%d not present" + , halg); + continue; + } + + enc_desc = ike_alg_get_encrypter(ealg); + passert(enc_desc != NULL); + + if (eklen + && (eklen < enc_desc->keyminlen || eklen > enc_desc->keymaxlen)) + { + DBG_log("ike_alg: ealg=%d (specified) keylen:%d, not valid min=%d, max=%d" + , ealg + , eklen + , enc_desc->keyminlen + , enc_desc->keymaxlen + ); + continue; + } + + if (policy & POLICY_RSASIG) + { + db_trans_add(db_ctx, KEY_IKE); + db_attr_add_values(db_ctx, OAKLEY_ENCRYPTION_ALGORITHM, ealg); + db_attr_add_values(db_ctx, OAKLEY_HASH_ALGORITHM, halg); + if (eklen) + db_attr_add_values(db_ctx, OAKLEY_KEY_LENGTH, eklen); + db_attr_add_values(db_ctx, OAKLEY_AUTHENTICATION_METHOD, OAKLEY_RSA_SIG); + db_attr_add_values(db_ctx, OAKLEY_GROUP_DESCRIPTION, modp); + } + + if (policy & POLICY_PSK) + { + db_trans_add(db_ctx, KEY_IKE); + db_attr_add_values(db_ctx, OAKLEY_ENCRYPTION_ALGORITHM, ealg); + db_attr_add_values(db_ctx, OAKLEY_HASH_ALGORITHM, halg); + if (eklen) + db_attr_add_values(db_ctx, OAKLEY_KEY_LENGTH, eklen); + db_attr_add_values(db_ctx, OAKLEY_AUTHENTICATION_METHOD, OAKLEY_PRESHARED_KEY); + db_attr_add_values(db_ctx, OAKLEY_GROUP_DESCRIPTION, modp); + } + + if (policy & POLICY_XAUTH_RSASIG) + { + db_trans_add(db_ctx, KEY_IKE); + db_attr_add_values(db_ctx, OAKLEY_ENCRYPTION_ALGORITHM, ealg); + db_attr_add_values(db_ctx, OAKLEY_HASH_ALGORITHM, halg); + if (eklen) + db_attr_add_values(db_ctx, OAKLEY_KEY_LENGTH, eklen); + db_attr_add_values(db_ctx, OAKLEY_AUTHENTICATION_METHOD + , is_xauth_server ? XAUTHRespRSA : XAUTHInitRSA); + db_attr_add_values(db_ctx, OAKLEY_GROUP_DESCRIPTION, modp); + } + + if (policy & POLICY_XAUTH_PSK) + { + db_trans_add(db_ctx, KEY_IKE); + db_attr_add_values(db_ctx, OAKLEY_ENCRYPTION_ALGORITHM, ealg); + db_attr_add_values(db_ctx, OAKLEY_HASH_ALGORITHM, halg); + if (eklen) + db_attr_add_values(db_ctx, OAKLEY_KEY_LENGTH, eklen); + db_attr_add_values(db_ctx, OAKLEY_AUTHENTICATION_METHOD + , is_xauth_server ? XAUTHRespPreShared : XAUTHInitPreShared); + db_attr_add_values(db_ctx, OAKLEY_GROUP_DESCRIPTION, modp); + } + } +fail: + return db_ctx; +} + +/* + * Show registered IKE algorithms + */ +void +ike_alg_list(void) +{ + u_int i; + struct ike_alg *a; + + whack_log(RC_COMMENT, " "); + whack_log(RC_COMMENT, "List of registered IKE Encryption Algorithms:"); + whack_log(RC_COMMENT, " "); + + for (a = ike_alg_base[IKE_ALG_ENCRYPT]; a != NULL; a = a->algo_next) + { + struct encrypt_desc *desc = (struct encrypt_desc*)a; + + whack_log(RC_COMMENT, "#%-5d %s, blocksize: %d, keylen: %d-%d-%d" + , a->algo_id + , enum_name(&oakley_enc_names, a->algo_id) + , (int)desc->enc_blocksize*BITS_PER_BYTE + , desc->keyminlen + , desc->keydeflen + , desc->keymaxlen + ); + } + + whack_log(RC_COMMENT, " "); + whack_log(RC_COMMENT, "List of registered IKE Hash Algorithms:"); + whack_log(RC_COMMENT, " "); + + for (a = ike_alg_base[IKE_ALG_HASH]; a != NULL; a = a->algo_next) + { + whack_log(RC_COMMENT, "#%-5d %s, hashsize: %d" + , a->algo_id + , enum_name(&oakley_hash_names, a->algo_id) + , (int)((struct hash_desc *)a)->hash_digest_size*BITS_PER_BYTE + ); + } + + whack_log(RC_COMMENT, " "); + whack_log(RC_COMMENT, "List of registered IKE DH Groups:"); + whack_log(RC_COMMENT, " "); + + for (i = 0; i < elemsof(oakley_group); i++) + { + const struct oakley_group_desc *gdesc=oakley_group + i; + + whack_log(RC_COMMENT, "#%-5d %s, groupsize: %d" + , gdesc->group + , enum_name(&oakley_group_names, gdesc->group) + , (int)gdesc->bytes*BITS_PER_BYTE + ); + } +} + +/* Show IKE algorithms for + * - this connection (result from ike= string) + * - newest SA + */ +void +ike_alg_show_connection(struct connection *c, const char *instance) +{ + char buf[256]; + struct state *st; + + if (c->alg_info_ike) + { + alg_info_snprint(buf, sizeof(buf)-1, (struct alg_info *)c->alg_info_ike); + whack_log(RC_COMMENT + , "\"%s\"%s: IKE algorithms wanted: %s" + , c->name + , instance + , buf + ); + + alg_info_snprint_ike(buf, sizeof(buf)-1, c->alg_info_ike); + whack_log(RC_COMMENT + , "\"%s\"%s: IKE algorithms found: %s" + , c->name + , instance + , buf + ); + } + + st = state_with_serialno(c->newest_isakmp_sa); + if (st) + whack_log(RC_COMMENT + , "\"%s\"%s: IKE algorithm newest: %s_%d-%s-%s" + , c->name + , instance + , enum_show(&oakley_enc_names, st->st_oakley.encrypt) + +7 /* strlen("OAKLEY_") */ + /* , st->st_oakley.encrypter->keydeflen */ + , st->st_oakley.enckeylen + , enum_show(&oakley_hash_names, st->st_oakley.hash) + +7 /* strlen("OAKLEY_") */ + , enum_show(&oakley_group_names, st->st_oakley.group->group) + +13 /* strlen("OAKLEY_GROUP_") */ + ); +} + +/* + * Apply a suite of testvectors to a hash algorithm + */ +static bool +ike_hash_test(const struct hash_desc *desc) +{ + bool hash_results = TRUE; + bool hmac_results = TRUE; + + if (desc->hash_testvectors == NULL) + { + plog(" %s hash self-test not available", enum_name(&oakley_hash_names, desc->algo_id)); + } + else + { + int i; + + for (i = 0; desc->hash_testvectors[i].msg_digest != NULL; i++) + { + u_char digest[MAX_DIGEST_LEN]; + bool result; + + union hash_ctx ctx; + + desc->hash_init(&ctx); + desc->hash_update(&ctx, desc->hash_testvectors[i].msg + ,desc->hash_testvectors[i].msg_size); + desc->hash_final(digest, &ctx); + result = memcmp(digest, desc->hash_testvectors[i].msg_digest + , desc->hash_digest_size) == 0; + DBG(DBG_CRYPT, + DBG_log(" hash testvector %d: %s", i, result ? "ok":"failed") + ) + hash_results &= result; + } + plog(" %s hash self-test %s", enum_name(&oakley_hash_names, desc->algo_id) + , hash_results ? "passed":"failed"); + } + + if (desc->hmac_testvectors == NULL) + { + plog(" %s hmac self-test not available", enum_name(&oakley_hash_names, desc->algo_id)); + } + else + { + int i; + + for (i = 0; desc->hmac_testvectors[i].hmac != NULL; i++) + { + u_char digest[MAX_DIGEST_LEN]; + bool result; + + struct hmac_ctx ctx; + + hmac_init(&ctx, desc, desc->hmac_testvectors[i].key + , desc->hmac_testvectors[i].key_size); + hmac_update(&ctx, desc->hmac_testvectors[i].msg + ,desc->hmac_testvectors[i].msg_size); + hmac_final(digest, &ctx); + result = memcmp(digest, desc->hmac_testvectors[i].hmac + , desc->hash_digest_size) == 0; + DBG(DBG_CRYPT, + DBG_log(" hmac testvector %d: %s", i, result ? "ok":"failed") + ) + hmac_results &= result; + } + plog(" %s hmac self-test %s", enum_name(&oakley_hash_names, desc->algo_id) + , hmac_results ? "passed":"failed"); + } + return hash_results && hmac_results; +} + +/* + * Apply test vectors to registered encryption and hash algorithms + */ +bool +ike_alg_test(void) +{ + bool all_results = TRUE; + struct ike_alg *a; + + plog("Testing registered IKE encryption algorithms:"); + + for (a = ike_alg_base[IKE_ALG_ENCRYPT]; a != NULL; a = a->algo_next) + { + + struct encrypt_desc *desc = (struct encrypt_desc*)a; + + plog(" %s self-test not available", enum_name(&oakley_enc_names, a->algo_id)); + } + + plog("Testing registered IKE hash algorithms:"); + + for (a = ike_alg_base[IKE_ALG_HASH]; a != NULL; a = a->algo_next) + { + struct hash_desc *desc = (struct hash_desc*)a; + + all_results &= ike_hash_test(desc); + } + + if (all_results) + plog("All crypto self-tests passed"); + else + plog("Some crypto self-tests failed"); + return all_results; +} + +/* + * ML: make F_STRICT logic consider enc,hash/auth,modp algorithms + */ +bool +ike_alg_ok_final(u_int ealg, u_int key_len, u_int aalg, u_int group +, struct alg_info_ike *alg_info_ike) +{ + /* + * simple test to discard low key_len, will accept it only + * if specified in "esp" string + */ + bool ealg_insecure = (key_len < 128); + + if (ealg_insecure + || (alg_info_ike && alg_info_ike->alg_info_flags & ALG_INFO_F_STRICT)) + { + int i; + struct ike_info *ike_info; + + if (alg_info_ike) + { + ALG_INFO_IKE_FOREACH(alg_info_ike, ike_info, i) + { + if (ike_info->ike_ealg == ealg + && (ike_info->ike_eklen == 0 || key_len == 0 || ike_info->ike_eklen == key_len) + && ike_info->ike_halg == aalg + && ike_info->ike_modp == group) + { + if (ealg_insecure) + loglog(RC_LOG_SERIOUS, "You should NOT use insecure IKE algorithms (%s)!" + , enum_name(&oakley_enc_names, ealg)); + return TRUE; + } + } + } + plog("Oakley Transform [%s (%d), %s, %s] refused due to %s" + , enum_name(&oakley_enc_names, ealg), key_len + , enum_name(&oakley_hash_names, aalg) + , enum_name(&oakley_group_names, group) + , ealg_insecure ? + "insecure key_len and enc. alg. not listed in \"ike\" string" : "strict flag" + ); + return FALSE; + } + return TRUE; +} + diff --git a/src/pluto/ike_alg.h b/src/pluto/ike_alg.h new file mode 100644 index 000000000..19e2e591c --- /dev/null +++ b/src/pluto/ike_alg.h @@ -0,0 +1,94 @@ +/* IKE modular algorithm handling interface + * Author: JuanJo Ciarlante <jjo-ipsec@mendoza.gov.ar> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: ike_alg.h,v 1.3 2004/09/16 23:22:22 as Exp $ + */ + +#ifndef _IKE_ALG_H +#define _IKE_ALG_H + +#include "connections.h" + +struct ike_alg { + u_int16_t algo_type; + u_int16_t algo_id; + struct ike_alg *algo_next; +}; + +struct encrypt_desc { + u_int16_t algo_type; + u_int16_t algo_id; + struct ike_alg *algo_next; + + size_t enc_ctxsize; + size_t enc_blocksize; + u_int keydeflen; + u_int keymaxlen; + u_int keyminlen; + void (*do_crypt)(u_int8_t *dat, size_t datasize, u_int8_t *key, size_t key_size, u_int8_t *iv, bool enc); +}; + +typedef struct hash_testvector hash_testvector_t; + +struct hash_testvector { + const size_t msg_size; + const u_char *msg; + const u_char *msg_digest; +}; + +typedef struct hmac_testvector hmac_testvector_t; + +struct hmac_testvector { + const size_t key_size; + const u_char *key; + const size_t msg_size; + const u_char *msg; + const u_char *hmac; +}; +struct hash_desc { + u_int16_t algo_type; + u_int16_t algo_id; + struct ike_alg *algo_next; + + size_t hash_ctx_size; + size_t hash_block_size; + size_t hash_digest_size; + const hash_testvector_t *hash_testvectors; + const hmac_testvector_t *hmac_testvectors; + void (*hash_init)(void *ctx); + void (*hash_update)(void *ctx, const u_int8_t *in, size_t datasize); + void (*hash_final)(u_int8_t *out, void *ctx); +}; + +#define IKE_ALG_ENCRYPT 0 +#define IKE_ALG_HASH 1 +#define IKE_ALG_MAX IKE_ALG_HASH + +extern int ike_alg_add(struct ike_alg *a); +extern struct hash_desc *ike_alg_get_hasher(u_int alg); +extern struct encrypt_desc *ike_alg_get_encrypter(u_int alg); +extern bool ike_alg_enc_present(u_int ealg); +extern bool ike_alg_hash_present(u_int halg); +extern int ike_alg_register_hash(struct hash_desc *a); +extern int ike_alg_register_enc(struct encrypt_desc *e); +extern const struct oakley_group_desc* ike_alg_pfsgroup(struct connection *c + , lset_t policy); +extern struct db_context * ike_alg_db_new(struct alg_info_ike *ai, lset_t policy); +extern void ike_alg_list(void); +extern void ike_alg_show_connection(struct connection *c, const char *instance); +extern bool ike_alg_test(void); +extern bool ike_alg_ok_final(u_int ealg, u_int key_len, u_int aalg, u_int group + , struct alg_info_ike *alg_info_ike); +extern int ike_alg_init(void); + +#endif /* _IKE_ALG_H */ diff --git a/src/pluto/ipsec.secrets.5 b/src/pluto/ipsec.secrets.5 new file mode 100644 index 000000000..3cce4d3f8 --- /dev/null +++ b/src/pluto/ipsec.secrets.5 @@ -0,0 +1,175 @@ +.TH IPSEC.SECRETS 5 "28 March 1999" +.SH NAME +ipsec.secrets \- secrets for IKE/IPsec authentication +.SH DESCRIPTION +The file \fIipsec.secrets\fP holds a table of secrets. +These secrets are used by \fIipsec_pluto\fP(8), the FreeS/WAN Internet Key +Exchange daemon, to authenticate other hosts. +Currently there are two kinds of secrets: preshared secrets and +.\" the private part of DSS keys. +RSA private keys. +.LP +It is vital that these secrets be protected. The file should be owned +by the super-user, +and its permissions should be set to block all access by others. +.LP +The file is a sequence of entries and include directives. +Here is an example. Each entry or directive must start at the +left margin, but if it continues beyond a single line, each continuation +line must be indented. +.LP +.RS +.nf +# sample /etc/ipsec.secrets file for 10.1.0.1 +10.1.0.1 10.2.0.1: PSK "secret shared by two hosts" + +# an entry may be split across lines, +# but indentation matters +www.xs4all.nl @www.kremvax.ru +\ \ \ \ 10.6.0.1 10.7.0.1 1.8.0.1: PSK "secret shared by 5" + +.\" # Private part of our DSS key, in base 64, +.\" # as generated by BIND 8.2.1's dnskeygen. +.\" # Since this is the default key for this host, +.\" # there is no need to specify indices. +.\" : DSS 0siMs0N/hfRoCBMXA6plPtuv58/+c= +# an RSA private key. +# note that the lines are too wide for a +# man page, so ... has been substituted for +# the truncated part +@my.com: rsa { +\ \ \ \ Modulus:\ 0syXpo/6waam+ZhSs8Lt6jnBzu3C4grtt... +\ \ \ \ PublicExponent:\ 0sAw== +\ \ \ \ PrivateExponent:\ 0shlGbVR1m8Z+7rhzSyenCaBN... +\ \ \ \ Prime1:\ 0s8njV7WTxzVzRz7AP+0OraDxmEAt1BL5l... +\ \ \ \ Prime2:\ 0s1LgR7/oUMo9BvfU8yRFNos1s211KX5K0... +\ \ \ \ Exponent1:\ 0soaXj85ihM5M2inVf/NfHmtLutVz4r... +\ \ \ \ Exponent2:\ 0sjdAL9VFizF+BKU4ohguJFzOd55OG6... +\ \ \ \ Coefficient:\ 0sK1LWwgnNrNFGZsS/2GuMBg9nYVZ... +\ \ \ \ } + +include ipsec.*.secrets # get secrets from other files +.fi +.RE +.LP +Each entry in the file is a list of indices, followed by a secret. +The two parts are separated by a colon (\fB:\fP) that is +followed by whitespace or a newline. For compatability +with the previous form of this file, if the key part is just a +double-quoted string the colon may be left out. +.LP +An index is an IP address, or a Fully Qualified Domain Name, user@FQDN, +\fB%any\fP or \fB%any6\fP (other kinds may come). An IP address may be written +in the familiar dotted quad form or as a domain name to be looked up +when the file is loaded +(or in any of the forms supported by the FreeS/WAN \fIipsec_ttoaddr\fP(3) +routine). In many cases it is a bad idea to use domain names because +the name server may not be running or may be insecure. To denote a +Fully Qualified Domain Name (as opposed to an IP address denoted by +its domain name), precede the name with an at sign (\fB@\fP). +.LP +Matching IDs with indices is fairly straightforward: they have to be +equal. In the case of a ``Road Warrior'' connection, if an equal +match is not found for the Peer's ID, and it is in the form of an IP +address, an index of \fB%any\fP will match the peer's IP address if IPV4 +and \fB%any6\fP will match a the peer's IP address if IPV6. +Currently, the obsolete notation \fB0.0.0.0\fP may be used in place of +\fB%any\fP. +.LP +An additional complexity +arises in the case of authentication by preshared secret: the +responder will need to look up the secret before the Peer's ID payload has +been decoded, so the ID used will be the IP address. +.LP +To authenticate a connection between two hosts, the entry that most +specifically matches the host and peer IDs is used. An entry with no +index will match any host and peer. More specifically, an entry with one index will +match a host and peer if the index matches the host's ID (the peer isn't +considered). Still more specifically, an entry with multiple indices will match a host and +peer if the host ID and peer ID each match one of the indices. If the key +is for an asymmetric authentication technique (i.e. a public key +system such as RSA), an entry with multiple indices will match a host +and peer even if only the host ID matches an index (it is presumed that the +multiple indices are all identities of the host). +It is acceptable for two entries to be the best match as +long as they agree about the secret or private key. +.LP +Authentication by preshared secret requires that both systems find the +identical secret (the secret is not actually transmitted by the IKE +protocol). If both the host and peer appear in the index list, the +same entry will be suitable for both systems so verbatim copying +between systems can be used. This naturally extends to larger groups +sharing the same secret. Thus multiple-index entries are best for PSK +authentication. +.LP +Authentication by RSA Signatures requires that each host have its own private +key. A host could reasonably use a different private keys +for different interfaces and for different peers. But it would not +be normal to share entries between systems. Thus thus no-index and +one-index forms of entry often make sense for RSA Signature authentication. +.LP +The key part of an entry may start with a token indicating the kind of +key. ``RSA'' signifies RSA private key and ``PSK'' signifies +PreShared Key (case is ignored). For compatability with previous +forms of this file, PSK is the default. +.LP +A preshared secret is most conveniently represented as a sequence of +characters, delimited by the double-quote +character (\fB"\fP). The sequence cannot contain a newline or +double-quote. Strictly speaking, the secret is actually the sequence +of bytes that is used in the file to represent the sequence of +characters (excluding the delimiters). +A preshared secret may also be represented, without quotes, in any form supported by +\fIipsec_ttodata\fP(3). +.LP +An RSA private key is a composite of eight generally large numbers. The notation +used is a brace-enclosed list of field name and value pairs (see the example above). +A suitable key, in a suitable format, may be generated by \fIipsec_rsasigkey\fP(8). +The structure is very similar to that used by BIND 8.2.2 or later, but note that +the numbers must have a ``0s'' prefix if they are in base 64. The order of +the fields is fixed. +.LP +The first token an entry must start in +the first column of its line. Subsequent tokens must be +separated by whitespace, +except for a colon token, which only needs to be followed by whitespace. +A newline is taken as whitespace, but every +line of an entry after the first must be indented. +.LP +Whitespace at the end of a line is ignored (except in the 0t +notation for a key). At the start of line or +after whitespace, \fB#\fP and the following text up to the end of the +line is treated as a comment. Within entries, all lines must be +indented (except for lines with no tokens). +Outside entries, no line may be indented (this is to make sure that +the file layout reflects its structure). +.LP +An include directive causes the contents of the named file to be processed +before continuing with the current file. The filename is subject to +``globbing'' as in \fIsh\fP(1), so every file with a matching name +is processed. Includes may be nested to a modest +depth (10, currently). If the filename doesn't start with a \fB/\fP, the +directory containing the current file is prepended to the name. The +include directive is a line that starts with the word \fBinclude\fP, +followed by whitespace, followed by the filename (which must not contain +whitespace). +.SH FILES +/etc/ipsec.secrets +.SH SEE ALSO +The rest of the FreeS/WAN distribution, in particular +\fIipsec.conf\fP(5), +\fIipsec\fP(8), +\fIipsec_newhostkey\fP(8), +\fIipsec_rsasigkey\fP(8), +\fIipsec_showhostkey\fP(8), +\fIipsec_auto\fP(8) \fB\-\-rereadsecrets\fP, +and \fIipsec_pluto\fP(8) \fB\-\-listen\fP,. +.br +BIND 8.2.2 or later, ftp://ftp.isc.org/isc/bind/src/ +.SH HISTORY +Designed for the FreeS/WAN project +<http://www.freeswan.org> +by D. Hugh Redelmeier. +.SH BUGS +If an ID is \fB0.0.0.0\fP, it will match \fB%any\fP; +if it is \fB0::0\fP, it will match \fB%any6\fP. diff --git a/src/pluto/ipsec_doi.c b/src/pluto/ipsec_doi.c new file mode 100644 index 000000000..1c22b299b --- /dev/null +++ b/src/pluto/ipsec_doi.c @@ -0,0 +1,5630 @@ +/* IPsec DOI and Oakley resolution routines + * Copyright (C) 1997 Angelos D. Keromytis. + * Copyright (C) 1998-2002 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: ipsec_doi.c,v 1.39 2006/04/22 21:59:20 as Exp $ + */ + +#include <stdio.h> +#include <string.h> +#include <stddef.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <resolv.h> +#include <arpa/nameser.h> /* missing from <resolv.h> on old systems */ +#include <sys/queue.h> +#include <sys/time.h> /* for gettimeofday */ + +#include <freeswan.h> +#include <ipsec_policy.h> + +#include "constants.h" +#include "defs.h" +#include "mp_defs.h" +#include "state.h" +#include "id.h" +#include "x509.h" +#include "crl.h" +#include "ca.h" +#include "certs.h" +#include "smartcard.h" +#include "connections.h" +#include "keys.h" +#include "packet.h" +#include "demux.h" /* needs packet.h */ +#include "adns.h" /* needs <resolv.h> */ +#include "dnskey.h" /* needs keys.h and adns.h */ +#include "kernel.h" +#include "log.h" +#include "cookie.h" +#include "server.h" +#include "spdb.h" +#include "timer.h" +#include "rnd.h" +#include "ipsec_doi.h" /* needs demux.h and state.h */ +#include "whack.h" +#include "fetch.h" +#include "pkcs7.h" +#include "asn1.h" + +#include "sha1.h" +#include "md5.h" +#include "crypto.h" /* requires sha1.h and md5.h */ +#include "vendor.h" +#include "alg_info.h" +#include "ike_alg.h" +#include "kernel_alg.h" +#include "nat_traversal.h" +#include "virtual.h" + +/* + * are we sending Pluto's Vendor ID? + */ +#ifdef VENDORID +#define SEND_PLUTO_VID 1 +#else /* !VENDORID */ +#define SEND_PLUTO_VID 0 +#endif /* !VENDORID */ + +/* + * are we sending a Cisco Unity VID? + */ +#ifdef CISCO_QUIRKS +#define SEND_CISCO_UNITY_VID 1 +#else /* !CISCO_QUIRKS */ +#define SEND_CISCO_UNITY_VID 0 +#endif /* !CISCO_QUIRKS */ + +/* MAGIC: perform f, a function that returns notification_t + * and return from the ENCLOSING stf_status returning function if it fails. + */ +#define RETURN_STF_FAILURE(f) \ + { int r = (f); if (r != NOTHING_WRONG) return STF_FAIL + r; } + +/* create output HDR as replica of input HDR */ +void +echo_hdr(struct msg_digest *md, bool enc, u_int8_t np) +{ + struct isakmp_hdr r_hdr = md->hdr; /* mostly same as incoming header */ + + r_hdr.isa_flags &= ~ISAKMP_FLAG_COMMIT; /* we won't ever turn on this bit */ + if (enc) + r_hdr.isa_flags |= ISAKMP_FLAG_ENCRYPTION; + /* some day, we may have to set r_hdr.isa_version */ + r_hdr.isa_np = np; + if (!out_struct(&r_hdr, &isakmp_hdr_desc, &md->reply, &md->rbody)) + impossible(); /* surely must have room and be well-formed */ +} + +/* Compute DH shared secret from our local secret and the peer's public value. + * We make the leap that the length should be that of the group + * (see quoted passage at start of ACCEPT_KE). + */ +static void +compute_dh_shared(struct state *st, const chunk_t g +, const struct oakley_group_desc *group) +{ + MP_INT mp_g, mp_shared; + struct timeval tv0, tv1; + unsigned long tv_diff; + + gettimeofday(&tv0, NULL); + passert(st->st_sec_in_use); + n_to_mpz(&mp_g, g.ptr, g.len); + mpz_init(&mp_shared); + mpz_powm(&mp_shared, &mp_g, &st->st_sec, group->modulus); + mpz_clear(&mp_g); + freeanychunk(st->st_shared); /* happens in odd error cases */ + st->st_shared = mpz_to_n(&mp_shared, group->bytes); + mpz_clear(&mp_shared); + gettimeofday(&tv1, NULL); + tv_diff=(tv1.tv_sec - tv0.tv_sec) * 1000000 + (tv1.tv_usec - tv0.tv_usec); + DBG(DBG_CRYPT, + DBG_log("compute_dh_shared(): time elapsed (%s): %ld usec" + , enum_show(&oakley_group_names, st->st_oakley.group->group) + , tv_diff); + ); + /* if took more than 200 msec ... */ + if (tv_diff > 200000) { + loglog(RC_LOG_SERIOUS, "WARNING: compute_dh_shared(): for %s took " + "%ld usec" + , enum_show(&oakley_group_names, st->st_oakley.group->group) + , tv_diff); + } + + DBG_cond_dump_chunk(DBG_CRYPT, "DH shared secret:\n", st->st_shared); +} + +/* if we haven't already done so, compute a local DH secret (st->st_sec) and + * the corresponding public value (g). This is emitted as a KE payload. + */ +static bool +build_and_ship_KE(struct state *st, chunk_t *g +, const struct oakley_group_desc *group, pb_stream *outs, u_int8_t np) +{ + if (!st->st_sec_in_use) + { + u_char tmp[LOCALSECRETSIZE]; + MP_INT mp_g; + + get_rnd_bytes(tmp, LOCALSECRETSIZE); + st->st_sec_in_use = TRUE; + n_to_mpz(&st->st_sec, tmp, LOCALSECRETSIZE); + + mpz_init(&mp_g); + mpz_powm(&mp_g, &groupgenerator, &st->st_sec, group->modulus); + freeanychunk(*g); /* happens in odd error cases */ + *g = mpz_to_n(&mp_g, group->bytes); + mpz_clear(&mp_g); + DBG(DBG_CRYPT, + DBG_dump("Local DH secret:\n", tmp, LOCALSECRETSIZE); + DBG_dump_chunk("Public DH value sent:\n", *g)); + } + return out_generic_chunk(np, &isakmp_keyex_desc, outs, *g, "keyex value"); +} + +/* accept_ke + * + * Check and accept DH public value (Gi or Gr) from peer's message. + * According to RFC2409 "The Internet key exchange (IKE)" 5: + * The Diffie-Hellman public value passed in a KE payload, in either + * a phase 1 or phase 2 exchange, MUST be the length of the negotiated + * Diffie-Hellman group enforced, if necessary, by pre-pending the + * value with zeros. + */ +static notification_t +accept_KE(chunk_t *dest, const char *val_name +, const struct oakley_group_desc *gr +, pb_stream *pbs) +{ + if (pbs_left(pbs) != gr->bytes) + { + loglog(RC_LOG_SERIOUS, "KE has %u byte DH public value; %u required" + , (unsigned) pbs_left(pbs), (unsigned) gr->bytes); + /* XXX Could send notification back */ + return INVALID_KEY_INFORMATION; + } + clonereplacechunk(*dest, pbs->cur, pbs_left(pbs), val_name); + DBG_cond_dump_chunk(DBG_CRYPT, "DH public value received:\n", *dest); + return NOTHING_WRONG; +} + +/* accept_PFS_KE + * + * Check and accept optional Quick Mode KE payload for PFS. + * Extends ACCEPT_PFS to check whether KE is allowed or required. + */ +static notification_t +accept_PFS_KE(struct msg_digest *md, chunk_t *dest +, const char *val_name, const char *msg_name) +{ + struct state *st = md->st; + struct payload_digest *const ke_pd = md->chain[ISAKMP_NEXT_KE]; + + if (ke_pd == NULL) + { + if (st->st_pfs_group != NULL) + { + loglog(RC_LOG_SERIOUS, "missing KE payload in %s message", msg_name); + return INVALID_KEY_INFORMATION; + } + } + else + { + if (st->st_pfs_group == NULL) + { + loglog(RC_LOG_SERIOUS, "%s message KE payload requires a GROUP_DESCRIPTION attribute in SA" + , msg_name); + return INVALID_KEY_INFORMATION; + } + if (ke_pd->next != NULL) + { + loglog(RC_LOG_SERIOUS, "%s message contains several KE payloads; we accept at most one", msg_name); + return INVALID_KEY_INFORMATION; /* ??? */ + } + return accept_KE(dest, val_name, st->st_pfs_group, &ke_pd->pbs); + } + return NOTHING_WRONG; +} + +static bool +build_and_ship_nonce(chunk_t *n, pb_stream *outs, u_int8_t np +, const char *name) +{ + freeanychunk(*n); + setchunk(*n, alloc_bytes(DEFAULT_NONCE_SIZE, name), DEFAULT_NONCE_SIZE); + get_rnd_bytes(n->ptr, DEFAULT_NONCE_SIZE); + return out_generic_chunk(np, &isakmp_nonce_desc, outs, *n, name); +} + +static bool +collect_rw_ca_candidates(struct msg_digest *md, generalName_t **top) +{ + struct connection *d = find_host_connection(&md->iface->addr + , pluto_port, (ip_address*)NULL, md->sender_port, LEMPTY); + + for (; d != NULL; d = d->hp_next) + { + /* must be a road warrior connection */ + if (d->kind == CK_TEMPLATE && !(d->policy & POLICY_OPPO) + && d->spd.that.ca.ptr != NULL) + { + generalName_t *gn; + bool new_entry = TRUE; + + for (gn = *top; gn != NULL; gn = gn->next) + { + if (same_dn(gn->name, d->spd.that.ca)) + { + new_entry = FALSE; + break; + } + } + if (new_entry) + { + gn = alloc_thing(generalName_t, "generalName"); + gn->kind = GN_DIRECTORY_NAME; + gn->name = d->spd.that.ca; + gn->next = *top; + *top = gn; + } + } + } + return *top != NULL; +} + +static bool +build_and_ship_CR(u_int8_t type, chunk_t ca, pb_stream *outs, u_int8_t np) +{ + pb_stream cr_pbs; + struct isakmp_cr cr_hd; + cr_hd.isacr_np = np; + cr_hd.isacr_type = type; + + /* build CR header */ + if (!out_struct(&cr_hd, &isakmp_ipsec_cert_req_desc, outs, &cr_pbs)) + return FALSE; + + if (ca.ptr != NULL) + { + /* build CR body containing the distinguished name of the CA */ + if (!out_chunk(ca, &cr_pbs, "CA")) + return FALSE; + } + close_output_pbs(&cr_pbs); + return TRUE; +} + +/* Send a notification to the peer. We could decide + * whether to send the notification, based on the type and the + * destination, if we care to. + */ +static void +send_notification(struct state *sndst, u_int16_t type, struct state *encst, + msgid_t msgid, u_char *icookie, u_char *rcookie, + u_char *spi, size_t spisize, u_char protoid) +{ + u_char buffer[1024]; + pb_stream pbs, r_hdr_pbs; + u_char *r_hashval = NULL; /* where in reply to jam hash value */ + u_char *r_hash_start = NULL; /* start of what is to be hashed */ + + passert((sndst) && (sndst->st_connection)); + + plog("sending %snotification %s to %s:%u" + , encst ? "encrypted " : "" + , enum_name(¬ification_names, type) + , ip_str(&sndst->st_connection->spd.that.host_addr) + , (unsigned)sndst->st_connection->spd.that.host_port); + + memset(buffer, 0, sizeof(buffer)); + init_pbs(&pbs, buffer, sizeof(buffer), "ISAKMP notify"); + + /* HDR* */ + { + struct isakmp_hdr hdr; + + hdr.isa_version = ISAKMP_MAJOR_VERSION << ISA_MAJ_SHIFT | ISAKMP_MINOR_VERSION; + hdr.isa_np = encst ? ISAKMP_NEXT_HASH : ISAKMP_NEXT_N; + hdr.isa_xchg = ISAKMP_XCHG_INFO; + hdr.isa_msgid = msgid; + hdr.isa_flags = encst ? ISAKMP_FLAG_ENCRYPTION : 0; + if (icookie) + memcpy(hdr.isa_icookie, icookie, COOKIE_SIZE); + if (rcookie) + memcpy(hdr.isa_rcookie, rcookie, COOKIE_SIZE); + if (!out_struct(&hdr, &isakmp_hdr_desc, &pbs, &r_hdr_pbs)) + impossible(); + } + + /* HASH -- value to be filled later */ + if (encst) + { + pb_stream hash_pbs; + if (!out_generic(ISAKMP_NEXT_N, &isakmp_hash_desc, &r_hdr_pbs, + &hash_pbs)) + impossible(); + r_hashval = hash_pbs.cur; /* remember where to plant value */ + if (!out_zero( + encst->st_oakley.hasher->hash_digest_size, &hash_pbs, "HASH")) + impossible(); + close_output_pbs(&hash_pbs); + r_hash_start = r_hdr_pbs.cur; /* hash from after HASH */ + } + + /* Notification Payload */ + { + pb_stream not_pbs; + struct isakmp_notification isan; + + isan.isan_doi = ISAKMP_DOI_IPSEC; + isan.isan_np = ISAKMP_NEXT_NONE; + isan.isan_type = type; + isan.isan_spisize = spisize; + isan.isan_protoid = protoid; + + if (!out_struct(&isan, &isakmp_notification_desc, &r_hdr_pbs, ¬_pbs) + || !out_raw(spi, spisize, ¬_pbs, "spi")) + impossible(); + close_output_pbs(¬_pbs); + } + + /* calculate hash value and patch into Hash Payload */ + if (encst) + { + struct hmac_ctx ctx; + hmac_init_chunk(&ctx, encst->st_oakley.hasher, encst->st_skeyid_a); + hmac_update(&ctx, (u_char *) &msgid, sizeof(msgid_t)); + hmac_update(&ctx, r_hash_start, r_hdr_pbs.cur-r_hash_start); + hmac_final(r_hashval, &ctx); + + DBG(DBG_CRYPT, + DBG_log("HASH computed:"); + DBG_dump("", r_hashval, ctx.hmac_digest_size); + ) + } + + /* Encrypt message (preserve st_iv and st_new_iv) */ + if (encst) + { + u_char old_iv[MAX_DIGEST_LEN]; + u_char new_iv[MAX_DIGEST_LEN]; + + u_int old_iv_len = encst->st_iv_len; + u_int new_iv_len = encst->st_new_iv_len; + + if (old_iv_len > MAX_DIGEST_LEN || new_iv_len > MAX_DIGEST_LEN) + impossible(); + + memcpy(old_iv, encst->st_iv, old_iv_len); + memcpy(new_iv, encst->st_new_iv, new_iv_len); + + if (!IS_ISAKMP_SA_ESTABLISHED(encst->st_state)) + { + memcpy(encst->st_ph1_iv, encst->st_new_iv, encst->st_new_iv_len); + encst->st_ph1_iv_len = encst->st_new_iv_len; + } + init_phase2_iv(encst, &msgid); + if (!encrypt_message(&r_hdr_pbs, encst)) + impossible(); + + /* restore preserved st_iv and st_new_iv */ + memcpy(encst->st_iv, old_iv, old_iv_len); + memcpy(encst->st_new_iv, new_iv, new_iv_len); + encst->st_iv_len = old_iv_len; + encst->st_new_iv_len = new_iv_len; + } + else + { + close_output_pbs(&r_hdr_pbs); + } + + /* Send packet (preserve st_tpacket) */ + { + chunk_t saved_tpacket = sndst->st_tpacket; + + setchunk(sndst->st_tpacket, pbs.start, pbs_offset(&pbs)); + send_packet(sndst, "ISAKMP notify"); + sndst->st_tpacket = saved_tpacket; + } +} + +void +send_notification_from_state(struct state *st, enum state_kind state, + u_int16_t type) +{ + struct state *p1st; + + passert(st); + + if (state == STATE_UNDEFINED) + state = st->st_state; + + if (IS_QUICK(state)) + { + p1st = find_phase1_state(st->st_connection, ISAKMP_SA_ESTABLISHED_STATES); + if ((p1st == NULL) || (!IS_ISAKMP_SA_ESTABLISHED(p1st->st_state))) + { + loglog(RC_LOG_SERIOUS, + "no Phase1 state for Quick mode notification"); + return; + } + send_notification(st, type, p1st, generate_msgid(p1st), + st->st_icookie, st->st_rcookie, NULL, 0, PROTO_ISAKMP); + } + else if (IS_ISAKMP_ENCRYPTED(state) && st->st_enc_key.ptr != NULL) + { + send_notification(st, type, st, generate_msgid(st), + st->st_icookie, st->st_rcookie, NULL, 0, PROTO_ISAKMP); + } + else + { + /* no ISAKMP SA established - don't encrypt notification */ + send_notification(st, type, NULL, 0, + st->st_icookie, st->st_rcookie, NULL, 0, PROTO_ISAKMP); + } +} + +void +send_notification_from_md(struct msg_digest *md, u_int16_t type) +{ + /** + * Create a dummy state to be able to use send_packet in + * send_notification + * + * we need to set: + * st_connection->that.host_addr + * st_connection->that.host_port + * st_connection->interface + */ + struct state st; + struct connection cnx; + + passert(md); + + memset(&st, 0, sizeof(st)); + memset(&cnx, 0, sizeof(cnx)); + st.st_connection = &cnx; + cnx.spd.that.host_addr = md->sender; + cnx.spd.that.host_port = md->sender_port; + cnx.interface = md->iface; + + send_notification(&st, type, NULL, 0, + md->hdr.isa_icookie, md->hdr.isa_rcookie, NULL, 0, PROTO_ISAKMP); +} + +/* Send a Delete Notification to announce deletion of ISAKMP SA or + * inbound IPSEC SAs. Does nothing if no such SAs are being deleted. + * Delete Notifications cannot announce deletion of outbound IPSEC/ISAKMP SAs. + */ +void +send_delete(struct state *st) +{ + pb_stream reply_pbs; + pb_stream r_hdr_pbs; + msgid_t msgid; + u_char buffer[8192]; + struct state *p1st; + ip_said said[EM_MAXRELSPIS]; + ip_said *ns = said; + u_char + *r_hashval, /* where in reply to jam hash value */ + *r_hash_start; /* start of what is to be hashed */ + bool isakmp_sa = FALSE; + + if (IS_IPSEC_SA_ESTABLISHED(st->st_state)) + { + p1st = find_phase1_state(st->st_connection, ISAKMP_SA_ESTABLISHED_STATES); + if (p1st == NULL) + { + DBG(DBG_CONTROL, DBG_log("no Phase 1 state for Delete")); + return; + } + + if (st->st_ah.present) + { + ns->spi = st->st_ah.our_spi; + ns->dst = st->st_connection->spd.this.host_addr; + ns->proto = PROTO_IPSEC_AH; + ns++; + } + if (st->st_esp.present) + { + ns->spi = st->st_esp.our_spi; + ns->dst = st->st_connection->spd.this.host_addr; + ns->proto = PROTO_IPSEC_ESP; + ns++; + } + + passert(ns != said); /* there must be some SAs to delete */ + } + else if (IS_ISAKMP_SA_ESTABLISHED(st->st_state)) + { + p1st = st; + isakmp_sa = TRUE; + } + else + { + return; /* nothing to do */ + } + + msgid = generate_msgid(p1st); + + zero(buffer); + init_pbs(&reply_pbs, buffer, sizeof(buffer), "delete msg"); + + /* HDR* */ + { + struct isakmp_hdr hdr; + + hdr.isa_version = ISAKMP_MAJOR_VERSION << ISA_MAJ_SHIFT | ISAKMP_MINOR_VERSION; + hdr.isa_np = ISAKMP_NEXT_HASH; + hdr.isa_xchg = ISAKMP_XCHG_INFO; + hdr.isa_msgid = msgid; + hdr.isa_flags = ISAKMP_FLAG_ENCRYPTION; + memcpy(hdr.isa_icookie, p1st->st_icookie, COOKIE_SIZE); + memcpy(hdr.isa_rcookie, p1st->st_rcookie, COOKIE_SIZE); + if (!out_struct(&hdr, &isakmp_hdr_desc, &reply_pbs, &r_hdr_pbs)) + impossible(); + } + + /* HASH -- value to be filled later */ + { + pb_stream hash_pbs; + + if (!out_generic(ISAKMP_NEXT_D, &isakmp_hash_desc, &r_hdr_pbs, &hash_pbs)) + impossible(); + r_hashval = hash_pbs.cur; /* remember where to plant value */ + if (!out_zero(p1st->st_oakley.hasher->hash_digest_size, &hash_pbs, "HASH(1)")) + impossible(); + close_output_pbs(&hash_pbs); + r_hash_start = r_hdr_pbs.cur; /* hash from after HASH(1) */ + } + + /* Delete Payloads */ + if (isakmp_sa) + { + pb_stream del_pbs; + struct isakmp_delete isad; + u_char isakmp_spi[2*COOKIE_SIZE]; + + isad.isad_doi = ISAKMP_DOI_IPSEC; + isad.isad_np = ISAKMP_NEXT_NONE; + isad.isad_spisize = (2 * COOKIE_SIZE); + isad.isad_protoid = PROTO_ISAKMP; + isad.isad_nospi = 1; + + memcpy(isakmp_spi, st->st_icookie, COOKIE_SIZE); + memcpy(isakmp_spi+COOKIE_SIZE, st->st_rcookie, COOKIE_SIZE); + + if (!out_struct(&isad, &isakmp_delete_desc, &r_hdr_pbs, &del_pbs) + || !out_raw(&isakmp_spi, (2*COOKIE_SIZE), &del_pbs, "delete payload")) + impossible(); + close_output_pbs(&del_pbs); + } + else + { + while (ns != said) + { + + pb_stream del_pbs; + struct isakmp_delete isad; + + ns--; + isad.isad_doi = ISAKMP_DOI_IPSEC; + isad.isad_np = ns == said? ISAKMP_NEXT_NONE : ISAKMP_NEXT_D; + isad.isad_spisize = sizeof(ipsec_spi_t); + isad.isad_protoid = ns->proto; + + isad.isad_nospi = 1; + if (!out_struct(&isad, &isakmp_delete_desc, &r_hdr_pbs, &del_pbs) + || !out_raw(&ns->spi, sizeof(ipsec_spi_t), &del_pbs, "delete payload")) + impossible(); + close_output_pbs(&del_pbs); + } + } + + /* calculate hash value and patch into Hash Payload */ + { + struct hmac_ctx ctx; + hmac_init_chunk(&ctx, p1st->st_oakley.hasher, p1st->st_skeyid_a); + hmac_update(&ctx, (u_char *) &msgid, sizeof(msgid_t)); + hmac_update(&ctx, r_hash_start, r_hdr_pbs.cur-r_hash_start); + hmac_final(r_hashval, &ctx); + + DBG(DBG_CRYPT, + DBG_log("HASH(1) computed:"); + DBG_dump("", r_hashval, ctx.hmac_digest_size); + ) + } + + /* Do a dance to avoid needing a new state object. + * We use the Phase 1 State. This is the one with right + * IV, for one thing. + * The tricky bits are: + * - we need to preserve (save/restore) st_iv (but not st_iv_new) + * - we need to preserve (save/restore) st_tpacket. + */ + { + u_char old_iv[MAX_DIGEST_LEN]; + chunk_t saved_tpacket = p1st->st_tpacket; + + memcpy(old_iv, p1st->st_iv, p1st->st_iv_len); + init_phase2_iv(p1st, &msgid); + + if (!encrypt_message(&r_hdr_pbs, p1st)) + impossible(); + + setchunk(p1st->st_tpacket, reply_pbs.start, pbs_offset(&reply_pbs)); + send_packet(p1st, "delete notify"); + p1st->st_tpacket = saved_tpacket; + + /* get back old IV for this state */ + memcpy(p1st->st_iv, old_iv, p1st->st_iv_len); + } +} + +void +accept_delete(struct state *st, struct msg_digest *md, struct payload_digest *p) +{ + struct isakmp_delete *d = &(p->payload.delete); + size_t sizespi; + int i; + + if (!md->encrypted) + { + loglog(RC_LOG_SERIOUS, "ignoring Delete SA payload: not encrypted"); + return; + } + + if (!IS_ISAKMP_SA_ESTABLISHED(st->st_state)) + { + /* can't happen (if msg is encrypt), but just to be sure */ + loglog(RC_LOG_SERIOUS, "ignoring Delete SA payload: " + "ISAKMP SA not established"); + return; + } + + if (d->isad_nospi == 0) + { + loglog(RC_LOG_SERIOUS, "ignoring Delete SA payload: no SPI"); + return; + } + + switch (d->isad_protoid) + { + case PROTO_ISAKMP: + sizespi = 2 * COOKIE_SIZE; + break; + case PROTO_IPSEC_AH: + case PROTO_IPSEC_ESP: + sizespi = sizeof(ipsec_spi_t); + break; + case PROTO_IPCOMP: + /* nothing interesting to delete */ + return; + default: + loglog(RC_LOG_SERIOUS + , "ignoring Delete SA payload: unknown Protocol ID (%s)" + , enum_show(&protocol_names, d->isad_protoid)); + return; + } + + if (d->isad_spisize != sizespi) + { + loglog(RC_LOG_SERIOUS + , "ignoring Delete SA payload: bad SPI size (%d) for %s" + , d->isad_spisize, enum_show(&protocol_names, d->isad_protoid)); + return; + } + + if (pbs_left(&p->pbs) != d->isad_nospi * sizespi) + { + loglog(RC_LOG_SERIOUS + , "ignoring Delete SA payload: invalid payload size"); + return; + } + + for (i = 0; i < d->isad_nospi; i++) + { + u_char *spi = p->pbs.cur + (i * sizespi); + + if (d->isad_protoid == PROTO_ISAKMP) + { + /** + * ISAKMP + */ + struct state *dst = find_state(spi /*iCookie*/ + , spi+COOKIE_SIZE /*rCookie*/ + , &st->st_connection->spd.that.host_addr + , MAINMODE_MSGID); + + if (dst == NULL) + { + loglog(RC_LOG_SERIOUS, "ignoring Delete SA payload: " + "ISAKMP SA not found (maybe expired)"); + } + else if (!same_peer_ids(st->st_connection, dst->st_connection, NULL)) + { + /* we've not authenticated the relevant identities */ + loglog(RC_LOG_SERIOUS, "ignoring Delete SA payload: " + "ISAKMP SA used to convey Delete has different IDs from ISAKMP SA it deletes"); + } + else + { + struct connection *oldc; + + oldc = cur_connection; + set_cur_connection(dst->st_connection); + + if (nat_traversal_enabled) + nat_traversal_change_port_lookup(md, dst); + + loglog(RC_LOG_SERIOUS, "received Delete SA payload: " + "deleting ISAKMP State #%lu", dst->st_serialno); + delete_state(dst); + set_cur_connection(oldc); + } + } + else + { + /** + * IPSEC (ESP/AH) + */ + bool bogus; + struct state *dst = find_phase2_state_to_delete(st + , d->isad_protoid + , *(ipsec_spi_t *)spi /* network order */ + , &bogus); + + if (dst == NULL) + { + loglog(RC_LOG_SERIOUS + , "ignoring Delete SA payload: %s SA(0x%08lx) not found (%s)" + , enum_show(&protocol_names, d->isad_protoid) + , (unsigned long)ntohl((unsigned long)*(ipsec_spi_t *)spi) + , bogus ? "our SPI - bogus implementation" : "maybe expired"); + } + else + { + struct connection *rc = dst->st_connection; + struct connection *oldc; + + oldc = cur_connection; + set_cur_connection(rc); + + if (nat_traversal_enabled) + nat_traversal_change_port_lookup(md, dst); + + if (rc->newest_ipsec_sa == dst->st_serialno + && (rc->policy & POLICY_UP)) + { + /* Last IPSec SA for a permanent connection that we + * have initiated. Replace it in a few seconds. + * + * Useful if the other peer is rebooting. + */ +#define DELETE_SA_DELAY EVENT_RETRANSMIT_DELAY_0 + if (dst->st_event != NULL + && dst->st_event->ev_type == EVENT_SA_REPLACE + && dst->st_event->ev_time <= DELETE_SA_DELAY + now()) + { + /* Patch from Angus Lees to ignore retransmited + * Delete SA. + */ + loglog(RC_LOG_SERIOUS, "received Delete SA payload: " + "already replacing IPSEC State #%lu in %d seconds" + , dst->st_serialno, (int)(dst->st_event->ev_time - now())); + } + else + { + loglog(RC_LOG_SERIOUS, "received Delete SA payload: " + "replace IPSEC State #%lu in %d seconds" + , dst->st_serialno, DELETE_SA_DELAY); + dst->st_margin = DELETE_SA_DELAY; + delete_event(dst); + event_schedule(EVENT_SA_REPLACE, DELETE_SA_DELAY, dst); + } + } + else + { + loglog(RC_LOG_SERIOUS, "received Delete SA(0x%08lx) payload: " + "deleting IPSEC State #%lu" + , (unsigned long)ntohl((unsigned long)*(ipsec_spi_t *)spi) + , dst->st_serialno); + delete_state(dst); + } + + /* reset connection */ + set_cur_connection(oldc); + } + } + } +} + +/* The whole message must be a multiple of 4 octets. + * I'm not sure where this is spelled out, but look at + * rfc2408 3.6 Transform Payload. + * Note: it talks about 4 BYTE boundaries! + */ +void +close_message(pb_stream *pbs) +{ + size_t padding = pad_up(pbs_offset(pbs), 4); + + if (padding != 0) + (void) out_zero(padding, pbs, "message padding"); + close_output_pbs(pbs); +} + +/* Initiate an Oakley Main Mode exchange. + * --> HDR;SA + * Note: this is not called from demux.c + */ +static stf_status +main_outI1(int whack_sock, struct connection *c, struct state *predecessor + , lset_t policy, unsigned long try) +{ + struct state *st = new_state(); + pb_stream reply; /* not actually a reply, but you know what I mean */ + pb_stream rbody; + + int vids_to_send = 0; + + /* set up new state */ + st->st_connection = c; + set_cur_state(st); /* we must reset before exit */ + st->st_policy = policy & ~POLICY_IPSEC_MASK; + st->st_whack_sock = whack_sock; + st->st_try = try; + st->st_state = STATE_MAIN_I1; + + /* determine how many Vendor ID payloads we will be sending */ + if (SEND_PLUTO_VID) + vids_to_send++; + if (SEND_CISCO_UNITY_VID) + vids_to_send++; + if (c->spd.this.cert.type == CERT_PGP) + vids_to_send++; + /* always send XAUTH Vendor ID */ + vids_to_send++; + /* always send DPD Vendor ID */ + vids_to_send++; + if (nat_traversal_enabled) + vids_to_send++; + + get_cookie(TRUE, st->st_icookie, COOKIE_SIZE, &c->spd.that.host_addr); + + insert_state(st); /* needs cookies, connection, and msgid (0) */ + + if (HAS_IPSEC_POLICY(policy)) + add_pending(dup_any(whack_sock), st, c, policy, 1 + , predecessor == NULL? SOS_NOBODY : predecessor->st_serialno); + + if (predecessor == NULL) + plog("initiating Main Mode"); + else + plog("initiating Main Mode to replace #%lu", predecessor->st_serialno); + + /* set up reply */ + init_pbs(&reply, reply_buffer, sizeof(reply_buffer), "reply packet"); + + /* HDR out */ + { + struct isakmp_hdr hdr; + + zero(&hdr); /* default to 0 */ + hdr.isa_version = ISAKMP_MAJOR_VERSION << ISA_MAJ_SHIFT | ISAKMP_MINOR_VERSION; + hdr.isa_np = ISAKMP_NEXT_SA; + hdr.isa_xchg = ISAKMP_XCHG_IDPROT; + memcpy(hdr.isa_icookie, st->st_icookie, COOKIE_SIZE); + /* R-cookie, flags and MessageID are left zero */ + + if (!out_struct(&hdr, &isakmp_hdr_desc, &reply, &rbody)) + { + reset_cur_state(); + return STF_INTERNAL_ERROR; + } + } + + /* SA out */ + { + u_char *sa_start = rbody.cur; + lset_t auth_policy = policy & POLICY_ID_AUTH_MASK; + + if (!out_sa(&rbody, &oakley_sadb, st, TRUE + , vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE)) + { + reset_cur_state(); + return STF_INTERNAL_ERROR; + } + + /* save initiator SA for later HASH */ + passert(st->st_p1isa.ptr == NULL); /* no leak! (MUST be first time) */ + clonetochunk(st->st_p1isa, sa_start, rbody.cur - sa_start + , "sa in main_outI1"); + } + + /* if enabled send Pluto Vendor ID */ + if (SEND_PLUTO_VID) + { + if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE + , &rbody, VID_STRONGSWAN)) + { + reset_cur_state(); + return STF_INTERNAL_ERROR; + } + } + + /* if enabled send Cisco Unity Vendor ID */ + if (SEND_CISCO_UNITY_VID) + { + if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE + , &rbody, VID_CISCO_UNITY)) + { + reset_cur_state(); + return STF_INTERNAL_ERROR; + } + } + /* if we have an OpenPGP certificate we assume an + * OpenPGP peer and have to send the Vendor ID + */ + if (c->spd.this.cert.type == CERT_PGP) + { + if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE + , &rbody, VID_OPENPGP)) + { + reset_cur_state(); + return STF_INTERNAL_ERROR; + } + } + + /* Announce our ability to do eXtended AUTHentication to the peer */ + if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE + , &rbody, VID_MISC_XAUTH)) + { + reset_cur_state(); + return STF_INTERNAL_ERROR; + } + + /* Announce our ability to do Dead Peer Detection to the peer */ + { + if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE + , &rbody, VID_MISC_DPD)) + { + reset_cur_state(); + return STF_INTERNAL_ERROR; + } + } + + if (nat_traversal_enabled) + { + /* Add supported NAT-Traversal VID */ + if (!nat_traversal_add_vid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE + , &rbody)) + { + reset_cur_state(); + return STF_INTERNAL_ERROR; + } + } + + close_message(&rbody); + close_output_pbs(&reply); + + clonetochunk(st->st_tpacket, reply.start, pbs_offset(&reply) + , "reply packet for main_outI1"); + + /* Transmit */ + + send_packet(st, "main_outI1"); + + /* Set up a retransmission event, half a minute henceforth */ + delete_event(st); + event_schedule(EVENT_RETRANSMIT, EVENT_RETRANSMIT_DELAY_0, st); + + if (predecessor != NULL) + { + update_pending(predecessor, st); + whack_log(RC_NEW_STATE + STATE_MAIN_I1 + , "%s: initiate, replacing #%lu" + , enum_name(&state_names, st->st_state) + , predecessor->st_serialno); + } + else + { + whack_log(RC_NEW_STATE + STATE_MAIN_I1 + , "%s: initiate", enum_name(&state_names, st->st_state)); + } + reset_cur_state(); + return STF_OK; +} + +void +ipsecdoi_initiate(int whack_sock +, struct connection *c +, lset_t policy +, unsigned long try +, so_serial_t replacing) +{ + /* If there's already an ISAKMP SA established, use that and + * go directly to Quick Mode. We are even willing to use one + * that is still being negotiated, but only if we are the Initiator + * (thus we can be sure that the IDs are not going to change; + * other issues around intent might matter). + * Note: there is no way to initiate with a Road Warrior. + */ + struct state *st = find_phase1_state(c + , ISAKMP_SA_ESTABLISHED_STATES | PHASE1_INITIATOR_STATES); + + if (st == NULL) + { + (void) main_outI1(whack_sock, c, NULL, policy, try); + } + else if (HAS_IPSEC_POLICY(policy)) + { + if (!IS_ISAKMP_SA_ESTABLISHED(st->st_state)) + { + /* leave our Phase 2 negotiation pending */ + add_pending(whack_sock, st, c, policy, try, replacing); + } + else + { + /* ??? we assume that peer_nexthop_sin isn't important: + * we already have it from when we negotiated the ISAKMP SA! + * It isn't clear what to do with the error return. + */ + (void) quick_outI1(whack_sock, st, c, policy, try, replacing); + } + } + else + { + close_any(whack_sock); + } +} + +/* Replace SA with a fresh one that is similar + * + * Shares some logic with ipsecdoi_initiate, but not the same! + * - we must not reuse the ISAKMP SA if we are trying to replace it! + * - if trying to replace IPSEC SA, use ipsecdoi_initiate to build + * ISAKMP SA if needed. + * - duplicate whack fd, if live. + * Does not delete the old state -- someone else will do that. + */ +void +ipsecdoi_replace(struct state *st, unsigned long try) +{ + int whack_sock = dup_any(st->st_whack_sock); + lset_t policy = st->st_policy; + + if (IS_PHASE1(st->st_state)) + { + passert(!HAS_IPSEC_POLICY(policy)); + (void) main_outI1(whack_sock, st->st_connection, st, policy, try); + } + else + { + /* Add features of actual old state to policy. This ensures + * that rekeying doesn't downgrade security. I admit that + * this doesn't capture everything. + */ + if (st->st_pfs_group != NULL) + policy |= POLICY_PFS; + if (st->st_ah.present) + { + policy |= POLICY_AUTHENTICATE; + if (st->st_ah.attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL) + policy |= POLICY_TUNNEL; + } + if (st->st_esp.present && st->st_esp.attrs.transid != ESP_NULL) + { + policy |= POLICY_ENCRYPT; + if (st->st_esp.attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL) + policy |= POLICY_TUNNEL; + } + if (st->st_ipcomp.present) + { + policy |= POLICY_COMPRESS; + if (st->st_ipcomp.attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL) + policy |= POLICY_TUNNEL; + } + passert(HAS_IPSEC_POLICY(policy)); + ipsecdoi_initiate(whack_sock, st->st_connection, policy, try + , st->st_serialno); + } +} + +/* SKEYID for preshared keys. + * See draft-ietf-ipsec-ike-01.txt 4.1 + */ +static bool +skeyid_preshared(struct state *st) +{ + const chunk_t *pss = get_preshared_secret(st->st_connection); + + if (pss == NULL) + { + loglog(RC_LOG_SERIOUS, "preshared secret disappeared!"); + return FALSE; + } + else + { + struct hmac_ctx ctx; + + hmac_init_chunk(&ctx, st->st_oakley.hasher, *pss); + hmac_update_chunk(&ctx, st->st_ni); + hmac_update_chunk(&ctx, st->st_nr); + hmac_final_chunk(st->st_skeyid, "st_skeyid in skeyid_preshared()", &ctx); + return TRUE; + } +} + +static bool +skeyid_digisig(struct state *st) +{ + struct hmac_ctx ctx; + chunk_t nir; + + /* We need to hmac_init with the concatenation of Ni_b and Nr_b, + * so we have to build a temporary concatentation. + */ + nir.len = st->st_ni.len + st->st_nr.len; + nir.ptr = alloc_bytes(nir.len, "Ni + Nr in skeyid_digisig"); + memcpy(nir.ptr, st->st_ni.ptr, st->st_ni.len); + memcpy(nir.ptr+st->st_ni.len, st->st_nr.ptr, st->st_nr.len); + hmac_init_chunk(&ctx, st->st_oakley.hasher, nir); + pfree(nir.ptr); + + hmac_update_chunk(&ctx, st->st_shared); + hmac_final_chunk(st->st_skeyid, "st_skeyid in skeyid_digisig()", &ctx); + return TRUE; +} + +/* Generate the SKEYID_* and new IV + * See draft-ietf-ipsec-ike-01.txt 4.1 + */ +static bool +generate_skeyids_iv(struct state *st) +{ + /* Generate the SKEYID */ + switch (st->st_oakley.auth) + { + case OAKLEY_PRESHARED_KEY: + case XAUTHInitPreShared: + case XAUTHRespPreShared: + if (!skeyid_preshared(st)) + return FALSE; + break; + + case OAKLEY_RSA_SIG: + case XAUTHInitRSA: + case XAUTHRespRSA: + if (!skeyid_digisig(st)) + return FALSE; + break; + + case OAKLEY_DSS_SIG: + /* XXX */ + + case OAKLEY_RSA_ENC: + case OAKLEY_RSA_ENC_REV: + case OAKLEY_ELGAMAL_ENC: + case OAKLEY_ELGAMAL_ENC_REV: + /* XXX */ + + default: + bad_case(st->st_oakley.auth); + } + + /* generate SKEYID_* from SKEYID */ + { + struct hmac_ctx ctx; + + hmac_init_chunk(&ctx, st->st_oakley.hasher, st->st_skeyid); + + /* SKEYID_D */ + hmac_update_chunk(&ctx, st->st_shared); + hmac_update(&ctx, st->st_icookie, COOKIE_SIZE); + hmac_update(&ctx, st->st_rcookie, COOKIE_SIZE); + hmac_update(&ctx, "\0", 1); + hmac_final_chunk(st->st_skeyid_d, "st_skeyid_d in generate_skeyids_iv()", &ctx); + + /* SKEYID_A */ + hmac_reinit(&ctx); + hmac_update_chunk(&ctx, st->st_skeyid_d); + hmac_update_chunk(&ctx, st->st_shared); + hmac_update(&ctx, st->st_icookie, COOKIE_SIZE); + hmac_update(&ctx, st->st_rcookie, COOKIE_SIZE); + hmac_update(&ctx, "\1", 1); + hmac_final_chunk(st->st_skeyid_a, "st_skeyid_a in generate_skeyids_iv()", &ctx); + + /* SKEYID_E */ + hmac_reinit(&ctx); + hmac_update_chunk(&ctx, st->st_skeyid_a); + hmac_update_chunk(&ctx, st->st_shared); + hmac_update(&ctx, st->st_icookie, COOKIE_SIZE); + hmac_update(&ctx, st->st_rcookie, COOKIE_SIZE); + hmac_update(&ctx, "\2", 1); + hmac_final_chunk(st->st_skeyid_e, "st_skeyid_e in generate_skeyids_iv()", &ctx); + } + + /* generate IV */ + { + union hash_ctx hash_ctx; + const struct hash_desc *h = st->st_oakley.hasher; + + st->st_new_iv_len = h->hash_digest_size; + passert(st->st_new_iv_len <= sizeof(st->st_new_iv)); + + DBG(DBG_CRYPT, + DBG_dump_chunk("DH_i:", st->st_gi); + DBG_dump_chunk("DH_r:", st->st_gr); + ); + h->hash_init(&hash_ctx); + h->hash_update(&hash_ctx, st->st_gi.ptr, st->st_gi.len); + h->hash_update(&hash_ctx, st->st_gr.ptr, st->st_gr.len); + h->hash_final(st->st_new_iv, &hash_ctx); + } + + /* Oakley Keying Material + * Derived from Skeyid_e: if it is not big enough, generate more + * using the PRF. + * See RFC 2409 "IKE" Appendix B + */ + { + /* const size_t keysize = st->st_oakley.encrypter->keydeflen/BITS_PER_BYTE; */ + const size_t keysize = st->st_oakley.enckeylen/BITS_PER_BYTE; + u_char keytemp[MAX_OAKLEY_KEY_LEN + MAX_DIGEST_LEN]; + u_char *k = st->st_skeyid_e.ptr; + + if (keysize > st->st_skeyid_e.len) + { + struct hmac_ctx ctx; + size_t i = 0; + + hmac_init_chunk(&ctx, st->st_oakley.hasher, st->st_skeyid_e); + hmac_update(&ctx, "\0", 1); + for (;;) + { + hmac_final(&keytemp[i], &ctx); + i += ctx.hmac_digest_size; + if (i >= keysize) + break; + hmac_reinit(&ctx); + hmac_update(&ctx, &keytemp[i - ctx.hmac_digest_size], ctx.hmac_digest_size); + } + k = keytemp; + } + clonereplacechunk(st->st_enc_key, k, keysize, "st_enc_key"); + } + + DBG(DBG_CRYPT, + DBG_dump_chunk("Skeyid: ", st->st_skeyid); + DBG_dump_chunk("Skeyid_d:", st->st_skeyid_d); + DBG_dump_chunk("Skeyid_a:", st->st_skeyid_a); + DBG_dump_chunk("Skeyid_e:", st->st_skeyid_e); + DBG_dump_chunk("enc key:", st->st_enc_key); + DBG_dump("IV:", st->st_new_iv, st->st_new_iv_len)); + return TRUE; +} + +/* Generate HASH_I or HASH_R for ISAKMP Phase I. + * This will *not* generate other hash payloads (eg. Phase II or Quick Mode, + * New Group Mode, or ISAKMP Informational Exchanges). + * If the hashi argument is TRUE, generate HASH_I; if FALSE generate HASH_R. + * If hashus argument is TRUE, we're generating a hash for our end. + * See RFC2409 IKE 5. + * + * Generating the SIG_I and SIG_R for DSS is an odd perversion of this: + * Most of the logic is the same, but SHA-1 is used in place of HMAC-whatever. + * The extensive common logic is embodied in main_mode_hash_body(). + * See draft-ietf-ipsec-ike-01.txt 4.1 and 6.1.1.2 + */ + +typedef void (*hash_update_t)(union hash_ctx *, const u_char *, size_t) ; +static void +main_mode_hash_body(struct state *st +, bool hashi /* Initiator? */ +, const pb_stream *idpl /* ID payload, as PBS */ +, union hash_ctx *ctx +, void (*hash_update_void)(void *, const u_char *input, size_t)) +{ +#define HASH_UPDATE_T (union hash_ctx *, const u_char *input, unsigned int len) + hash_update_t hash_update=(hash_update_t) hash_update_void; +#if 0 /* if desperate to debug hashing */ +# define hash_update(ctx, input, len) { \ + DBG_dump("hash input", input, len); \ + (hash_update)(ctx, input, len); \ + } +#endif + +# define hash_update_chunk(ctx, ch) hash_update((ctx), (ch).ptr, (ch).len) + + if (hashi) + { + hash_update_chunk(ctx, st->st_gi); + hash_update_chunk(ctx, st->st_gr); + hash_update(ctx, st->st_icookie, COOKIE_SIZE); + hash_update(ctx, st->st_rcookie, COOKIE_SIZE); + } + else + { + hash_update_chunk(ctx, st->st_gr); + hash_update_chunk(ctx, st->st_gi); + hash_update(ctx, st->st_rcookie, COOKIE_SIZE); + hash_update(ctx, st->st_icookie, COOKIE_SIZE); + } + + DBG(DBG_CRYPT, DBG_log("hashing %lu bytes of SA" + , (unsigned long) (st->st_p1isa.len - sizeof(struct isakmp_generic)))); + + /* SA_b */ + hash_update(ctx, st->st_p1isa.ptr + sizeof(struct isakmp_generic) + , st->st_p1isa.len - sizeof(struct isakmp_generic)); + + /* Hash identification payload, without generic payload header. + * We used to reconstruct ID Payload for this purpose, but now + * we use the bytes as they appear on the wire to avoid + * "spelling problems". + */ + hash_update(ctx + , idpl->start + sizeof(struct isakmp_generic) + , pbs_offset(idpl) - sizeof(struct isakmp_generic)); + +# undef hash_update_chunk +# undef hash_update +} + +static size_t /* length of hash */ +main_mode_hash(struct state *st +, u_char *hash_val /* resulting bytes */ +, bool hashi /* Initiator? */ +, const pb_stream *idpl) /* ID payload, as PBS; cur must be at end */ +{ + struct hmac_ctx ctx; + + hmac_init_chunk(&ctx, st->st_oakley.hasher, st->st_skeyid); + main_mode_hash_body(st, hashi, idpl, &ctx.hash_ctx, ctx.h->hash_update); + hmac_final(hash_val, &ctx); + return ctx.hmac_digest_size; +} + +#if 0 /* only needed for DSS */ +static void +main_mode_sha1(struct state *st +, u_char *hash_val /* resulting bytes */ +, size_t *hash_len /* length of hash */ +, bool hashi /* Initiator? */ +, const pb_stream *idpl) /* ID payload, as PBS */ +{ + union hash_ctx ctx; + + SHA1Init(&ctx.ctx_sha1); + SHA1Update(&ctx.ctx_sha1, st->st_skeyid.ptr, st->st_skeyid.len); + *hash_len = SHA1_DIGEST_SIZE; + main_mode_hash_body(st, hashi, idpl, &ctx + , (void (*)(union hash_ctx *, const u_char *, unsigned int))&SHA1Update); + SHA1Final(hash_val, &ctx.ctx_sha1); +} +#endif + +/* Create an RSA signature of a hash. + * Poorly specified in draft-ietf-ipsec-ike-01.txt 6.1.1.2. + * Use PKCS#1 version 1.5 encryption of hash (called + * RSAES-PKCS1-V1_5) in PKCS#2. + */ +static size_t +RSA_sign_hash(struct connection *c +, u_char sig_val[RSA_MAX_OCTETS] +, const u_char *hash_val, size_t hash_len) +{ + size_t sz = 0; + smartcard_t *sc = c->spd.this.sc; + + if (sc == NULL) /* no smartcard */ + { + const struct RSA_private_key *k = get_RSA_private_key(c); + + if (k == NULL) + return 0; /* failure: no key to use */ + + sz = k->pub.k; + passert(RSA_MIN_OCTETS <= sz && 4 + hash_len < sz && sz <= RSA_MAX_OCTETS); + sign_hash(k, hash_val, hash_len, sig_val, sz); + } + else if (sc->valid) /* if valid pin then sign hash on the smartcard */ + { + lock_certs_and_keys("RSA_sign_hash"); + if (!scx_establish_context(sc) || !scx_login(sc)) + { + scx_release_context(sc); + unlock_certs_and_keys("RSA_sign_hash"); + return 0; + } + + sz = scx_get_keylength(sc); + if (sz == 0) + { + plog("failed to get keylength from smartcard"); + scx_release_context(sc); + unlock_certs_and_keys("RSA_sign_hash"); + return 0; + } + + DBG(DBG_CONTROL | DBG_CRYPT, + DBG_log("signing hash with RSA key from smartcard (slot: %d, id: %s)" + , (int)sc->slot, sc->id) + ) + sz = scx_sign_hash(sc, hash_val, hash_len, sig_val, sz) ? sz : 0; + if (!pkcs11_keep_state) + scx_release_context(sc); + unlock_certs_and_keys("RSA_sign_hash"); + } + return sz; +} + +/* Check a Main Mode RSA Signature against computed hash using RSA public key k. + * + * As a side effect, on success, the public key is copied into the + * state object to record the authenticator. + * + * Can fail because wrong public key is used or because hash disagrees. + * We distinguish because diagnostics should also. + * + * The result is NULL if the Signature checked out. + * Otherwise, the first character of the result indicates + * how far along failure occurred. A greater character signifies + * greater progress. + * + * Classes: + * 0 reserved for caller + * 1 SIG length doesn't match key length -- wrong key + * 2-8 malformed ECB after decryption -- probably wrong key + * 9 decrypted hash != computed hash -- probably correct key + * + * Although the math should be the same for generating and checking signatures, + * it is not: the knowledge of the private key allows more efficient (i.e. + * different) computation for encryption. + */ +static err_t +try_RSA_signature(const u_char hash_val[MAX_DIGEST_LEN], size_t hash_len +, const pb_stream *sig_pbs, pubkey_t *kr +, struct state *st) +{ + const u_char *sig_val = sig_pbs->cur; + size_t sig_len = pbs_left(sig_pbs); + u_char s[RSA_MAX_OCTETS]; /* for decrypted sig_val */ + u_char *hash_in_s = &s[sig_len - hash_len]; + const struct RSA_public_key *k = &kr->u.rsa; + + /* decrypt the signature -- reversing RSA_sign_hash */ + if (sig_len != k->k) + { + /* XXX notification: INVALID_KEY_INFORMATION */ + return "1" "SIG length does not match public key length"; + } + + /* actual exponentiation; see PKCS#1 v2.0 5.1 */ + { + chunk_t temp_s; + mpz_t c; + + n_to_mpz(c, sig_val, sig_len); + mpz_powm(c, c, &k->e, &k->n); + + temp_s = mpz_to_n(c, sig_len); /* back to octets */ + memcpy(s, temp_s.ptr, sig_len); + pfree(temp_s.ptr); + mpz_clear(c); + } + + /* sanity check on signature: see if it matches + * PKCS#1 v1.5 8.1 encryption-block formatting + */ + { + err_t ugh = NULL; + + if (s[0] != 0x00) + ugh = "2" "no leading 00"; + else if (hash_in_s[-1] != 0x00) + ugh = "3" "00 separator not present"; + else if (s[1] == 0x01) + { + const u_char *p; + + for (p = &s[2]; p != hash_in_s - 1; p++) + { + if (*p != 0xFF) + { + ugh = "4" "invalid Padding String"; + break; + } + } + } + else if (s[1] == 0x02) + { + const u_char *p; + + for (p = &s[2]; p != hash_in_s - 1; p++) + { + if (*p == 0x00) + { + ugh = "5" "invalid Padding String"; + break; + } + } + } + else + ugh = "6" "Block Type not 01 or 02"; + + if (ugh != NULL) + { + /* note: it might be a good idea to make sure that + * an observer cannot tell what kind of failure happened. + * I don't know what this means in practice. + */ + /* We probably selected the wrong public key for peer: + * SIG Payload decrypted into malformed ECB + */ + /* XXX notification: INVALID_KEY_INFORMATION */ + return ugh; + } + } + + /* We have the decoded hash: see if it matches. */ + if (memcmp(hash_val, hash_in_s, hash_len) != 0) + { + /* good: header, hash, signature, and other payloads well-formed + * good: we could find an RSA Sig key for the peer. + * bad: hash doesn't match + * Guess: sides disagree about key to be used. + */ + DBG_cond_dump(DBG_CRYPT, "decrypted SIG", s, sig_len); + DBG_cond_dump(DBG_CRYPT, "computed HASH", hash_val, hash_len); + /* XXX notification: INVALID_HASH_INFORMATION */ + return "9" "authentication failure: received SIG does not match computed HASH, but message is well-formed"; + } + + /* Success: copy successful key into state. + * There might be an old one if we previously aborted this + * state transition. + */ + unreference_key(&st->st_peer_pubkey); + st->st_peer_pubkey = reference_key(kr); + + return NULL; /* happy happy */ +} + +/* Check signature against all RSA public keys we can find. + * If we need keys from DNS KEY records, and they haven't been fetched, + * return STF_SUSPEND to ask for asynch DNS lookup. + * + * Note: parameter keys_from_dns contains results of DNS lookup for key + * or is NULL indicating lookup not yet tried. + * + * take_a_crack is a helper function. Mostly forensic. + * If only we had coroutines. + */ +struct tac_state { + /* RSA_check_signature's args that take_a_crack needs */ + struct state *st; + const u_char *hash_val; + size_t hash_len; + const pb_stream *sig_pbs; + + /* state carried between calls */ + err_t best_ugh; /* most successful failure */ + int tried_cnt; /* number of keys tried */ + char tried[50]; /* keyids of tried public keys */ + char *tn; /* roof of tried[] */ +}; + +static bool +take_a_crack(struct tac_state *s +, pubkey_t *kr +, const char *story USED_BY_DEBUG) +{ + err_t ugh = try_RSA_signature(s->hash_val, s->hash_len, s->sig_pbs + , kr, s->st); + const struct RSA_public_key *k = &kr->u.rsa; + + s->tried_cnt++; + if (ugh == NULL) + { + DBG(DBG_CRYPT | DBG_CONTROL + , DBG_log("an RSA Sig check passed with *%s [%s]" + , k->keyid, story)); + return TRUE; + } + else + { + DBG(DBG_CRYPT + , DBG_log("an RSA Sig check failure %s with *%s [%s]" + , ugh + 1, k->keyid, story)); + if (s->best_ugh == NULL || s->best_ugh[0] < ugh[0]) + s->best_ugh = ugh; + if (ugh[0] > '0' + && s->tn - s->tried + KEYID_BUF + 2 < (ptrdiff_t)sizeof(s->tried)) + { + strcpy(s->tn, " *"); + strcpy(s->tn + 2, k->keyid); + s->tn += strlen(s->tn); + } + return FALSE; + } +} + +static stf_status +RSA_check_signature(const struct id* peer +, struct state *st +, const u_char hash_val[MAX_DIGEST_LEN] +, size_t hash_len +, const pb_stream *sig_pbs +#ifdef USE_KEYRR +, const pubkey_list_t *keys_from_dns +#endif /* USE_KEYRR */ +, const struct gw_info *gateways_from_dns +) +{ + const struct connection *c = st->st_connection; + struct tac_state s; + err_t dns_ugh = NULL; + + s.st = st; + s.hash_val = hash_val; + s.hash_len = hash_len; + s.sig_pbs = sig_pbs; + + s.best_ugh = NULL; + s.tried_cnt = 0; + s.tn = s.tried; + + /* try all gateway records hung off c */ + if (c->policy & POLICY_OPPO) + { + struct gw_info *gw; + + for (gw = c->gw_info; gw != NULL; gw = gw->next) + { + /* only consider entries that have a key and are for our peer */ + if (gw->gw_key_present + && same_id(&gw->gw_id, &c->spd.that.id) + && take_a_crack(&s, gw->key, "key saved from DNS TXT")) + return STF_OK; + } + } + + /* try all appropriate Public keys */ + { + pubkey_list_t *p, **pp; + + pp = &pubkeys; + + for (p = pubkeys; p != NULL; p = *pp) + { + pubkey_t *key = p->key; + + if (key->alg == PUBKEY_ALG_RSA && same_id(peer, &key->id)) + { + time_t now = time(NULL); + + /* check if found public key has expired */ + if (key->until_time != UNDEFINED_TIME && key->until_time < now) + { + loglog(RC_LOG_SERIOUS, + "cached RSA public key has expired and has been deleted"); + *pp = free_public_keyentry(p); + continue; /* continue with next public key */ + } + + if (take_a_crack(&s, key, "preloaded key")) + return STF_OK; + } + pp = &p->next; + } + } + + /* if no key was found (evidenced by best_ugh == NULL) + * and that side of connection is key_from_DNS_on_demand + * then go search DNS for keys for peer. + */ + if (s.best_ugh == NULL && c->spd.that.key_from_DNS_on_demand) + { + if (gateways_from_dns != NULL) + { + /* TXT keys */ + const struct gw_info *gwp; + + for (gwp = gateways_from_dns; gwp != NULL; gwp = gwp->next) + if (gwp->gw_key_present + && take_a_crack(&s, gwp->key, "key from DNS TXT")) + return STF_OK; + } +#ifdef USE_KEYRR + else if (keys_from_dns != NULL) + { + /* KEY keys */ + const pubkey_list_t *kr; + + for (kr = keys_from_dns; kr != NULL; kr = kr->next) + if (kr->key->alg == PUBKEY_ALG_RSA + && take_a_crack(&s, kr->key, "key from DNS KEY")) + return STF_OK; + } +#endif /* USE_KEYRR */ + else + { + /* nothing yet: ask for asynch DNS lookup */ + return STF_SUSPEND; + } + } + + /* no acceptable key was found: diagnose */ + { + char id_buf[BUF_LEN]; /* arbitrary limit on length of ID reported */ + + (void) idtoa(peer, id_buf, sizeof(id_buf)); + + if (s.best_ugh == NULL) + { + if (dns_ugh == NULL) + loglog(RC_LOG_SERIOUS, "no RSA public key known for '%s'" + , id_buf); + else + loglog(RC_LOG_SERIOUS, "no RSA public key known for '%s'" + "; DNS search for KEY failed (%s)" + , id_buf, dns_ugh); + + /* ??? is this the best code there is? */ + return STF_FAIL + INVALID_KEY_INFORMATION; + } + + if (s.best_ugh[0] == '9') + { + loglog(RC_LOG_SERIOUS, "%s", s.best_ugh + 1); + /* XXX Could send notification back */ + return STF_FAIL + INVALID_HASH_INFORMATION; + } + else + { + if (s.tried_cnt == 1) + { + loglog(RC_LOG_SERIOUS + , "Signature check (on %s) failed (wrong key?); tried%s" + , id_buf, s.tried); + DBG(DBG_CONTROL, + DBG_log("public key for %s failed:" + " decrypted SIG payload into a malformed ECB (%s)" + , id_buf, s.best_ugh + 1)); + } + else + { + loglog(RC_LOG_SERIOUS + , "Signature check (on %s) failed:" + " tried%s keys but none worked." + , id_buf, s.tried); + DBG(DBG_CONTROL, + DBG_log("all %d public keys for %s failed:" + " best decrypted SIG payload into a malformed ECB (%s)" + , s.tried_cnt, id_buf, s.best_ugh + 1)); + } + return STF_FAIL + INVALID_KEY_INFORMATION; + } + } +} + +static notification_t +accept_nonce(struct msg_digest *md, chunk_t *dest, const char *name) +{ + pb_stream *nonce_pbs = &md->chain[ISAKMP_NEXT_NONCE]->pbs; + size_t len = pbs_left(nonce_pbs); + + if (len < MINIMUM_NONCE_SIZE || MAXIMUM_NONCE_SIZE < len) + { + loglog(RC_LOG_SERIOUS, "%s length not between %d and %d" + , name , MINIMUM_NONCE_SIZE, MAXIMUM_NONCE_SIZE); + return PAYLOAD_MALFORMED; /* ??? */ + } + clonereplacechunk(*dest, nonce_pbs->cur, len, "nonce"); + return NOTHING_WRONG; +} + +/* encrypt message, sans fixed part of header + * IV is fetched from st->st_new_iv and stored into st->st_iv. + * The theory is that there will be no "backing out", so we commit to IV. + * We also close the pbs. + */ +bool +encrypt_message(pb_stream *pbs, struct state *st) +{ + const struct encrypt_desc *e = st->st_oakley.encrypter; + u_int8_t *enc_start = pbs->start + sizeof(struct isakmp_hdr); + size_t enc_len = pbs_offset(pbs) - sizeof(struct isakmp_hdr); + + DBG_cond_dump(DBG_CRYPT | DBG_RAW, "encrypting:\n", enc_start, enc_len); + + /* Pad up to multiple of encryption blocksize. + * See the description associated with the definition of + * struct isakmp_hdr in packet.h. + */ + { + size_t padding = pad_up(enc_len, e->enc_blocksize); + + if (padding != 0) + { + if (!out_zero(padding, pbs, "encryption padding")) + return FALSE; + enc_len += padding; + } + } + + DBG(DBG_CRYPT, DBG_log("encrypting using %s", enum_show(&oakley_enc_names, st->st_oakley.encrypt))); + + /* e->crypt(TRUE, enc_start, enc_len, st); */ + crypto_cbc_encrypt(e, TRUE, enc_start, enc_len, st); + + update_iv(st); + DBG_cond_dump(DBG_CRYPT, "next IV:", st->st_iv, st->st_iv_len); + close_message(pbs); + return TRUE; +} + +/* Compute HASH(1), HASH(2) of Quick Mode. + * HASH(1) is part of Quick I1 message. + * HASH(2) is part of Quick R1 message. + * Used by: quick_outI1, quick_inI1_outR1 (twice), quick_inR1_outI2 + * (see RFC 2409 "IKE" 5.5, pg. 18 or draft-ietf-ipsec-ike-01.txt 6.2 pg 25) + */ +static size_t +quick_mode_hash12(u_char *dest, const u_char *start, const u_char *roof +, const struct state *st, const msgid_t *msgid, bool hash2) +{ + struct hmac_ctx ctx; + +#if 0 /* if desperate to debug hashing */ +# define hmac_update(ctx, ptr, len) { \ + DBG_dump("hash input", (ptr), (len)); \ + (hmac_update)((ctx), (ptr), (len)); \ + } + DBG_dump("hash key", st->st_skeyid_a.ptr, st->st_skeyid_a.len); +#endif + hmac_init_chunk(&ctx, st->st_oakley.hasher, st->st_skeyid_a); + hmac_update(&ctx, (const void *) msgid, sizeof(msgid_t)); + if (hash2) + hmac_update_chunk(&ctx, st->st_ni); /* include Ni_b in the hash */ + hmac_update(&ctx, start, roof-start); + hmac_final(dest, &ctx); + + DBG(DBG_CRYPT, + DBG_log("HASH(%d) computed:", hash2 + 1); + DBG_dump("", dest, ctx.hmac_digest_size)); + return ctx.hmac_digest_size; +# undef hmac_update +} + +/* Compute HASH(3) in Quick Mode (part of Quick I2 message). + * Used by: quick_inR1_outI2, quick_inI2 + * See RFC2409 "The Internet Key Exchange (IKE)" 5.5. + * NOTE: this hash (unlike HASH(1) and HASH(2)) ONLY covers the + * Message ID and Nonces. This is a mistake. + */ +static size_t +quick_mode_hash3(u_char *dest, struct state *st) +{ + struct hmac_ctx ctx; + + hmac_init_chunk(&ctx, st->st_oakley.hasher, st->st_skeyid_a); + hmac_update(&ctx, "\0", 1); + hmac_update(&ctx, (u_char *) &st->st_msgid, sizeof(st->st_msgid)); + hmac_update_chunk(&ctx, st->st_ni); + hmac_update_chunk(&ctx, st->st_nr); + hmac_final(dest, &ctx); + DBG_cond_dump(DBG_CRYPT, "HASH(3) computed:", dest, ctx.hmac_digest_size); + return ctx.hmac_digest_size; +} + +/* Compute Phase 2 IV. + * Uses Phase 1 IV from st_iv; puts result in st_new_iv. + */ +void +init_phase2_iv(struct state *st, const msgid_t *msgid) +{ + const struct hash_desc *h = st->st_oakley.hasher; + union hash_ctx ctx; + + DBG_cond_dump(DBG_CRYPT, "last Phase 1 IV:" + , st->st_ph1_iv, st->st_ph1_iv_len); + + st->st_new_iv_len = h->hash_digest_size; + passert(st->st_new_iv_len <= sizeof(st->st_new_iv)); + + h->hash_init(&ctx); + h->hash_update(&ctx, st->st_ph1_iv, st->st_ph1_iv_len); + passert(*msgid != 0); + h->hash_update(&ctx, (const u_char *)msgid, sizeof(*msgid)); + h->hash_final(st->st_new_iv, &ctx); + + DBG_cond_dump(DBG_CRYPT, "computed Phase 2 IV:" + , st->st_new_iv, st->st_new_iv_len); +} + +/* Initiate quick mode. + * --> HDR*, HASH(1), SA, Nr [, KE ] [, IDci, IDcr ] + * (see RFC 2409 "IKE" 5.5) + * Note: this is not called from demux.c + */ + +static bool +emit_subnet_id(ip_subnet *net +, u_int8_t np, u_int8_t protoid, u_int16_t port, pb_stream *outs) +{ + struct isakmp_ipsec_id id; + pb_stream id_pbs; + ip_address ta; + const unsigned char *tbp; + size_t tal; + + id.isaiid_np = np; + id.isaiid_idtype = subnetishost(net) + ? aftoinfo(subnettypeof(net))->id_addr + : aftoinfo(subnettypeof(net))->id_subnet; + id.isaiid_protoid = protoid; + id.isaiid_port = port; + + if (!out_struct(&id, &isakmp_ipsec_identification_desc, outs, &id_pbs)) + return FALSE; + + networkof(net, &ta); + tal = addrbytesptr(&ta, &tbp); + if (!out_raw(tbp, tal, &id_pbs, "client network")) + return FALSE; + + if (!subnetishost(net)) + { + maskof(net, &ta); + tal = addrbytesptr(&ta, &tbp); + if (!out_raw(tbp, tal, &id_pbs, "client mask")) + return FALSE; + } + + close_output_pbs(&id_pbs); + return TRUE; +} + +stf_status +quick_outI1(int whack_sock +, struct state *isakmp_sa +, struct connection *c +, lset_t policy +, unsigned long try +, so_serial_t replacing) +{ + struct state *st = duplicate_state(isakmp_sa); + pb_stream reply; /* not really a reply */ + pb_stream rbody; + u_char /* set by START_HASH_PAYLOAD: */ + *r_hashval, /* where in reply to jam hash value */ + *r_hash_start; /* start of what is to be hashed */ + bool has_client = c->spd.this.has_client || c->spd.that.has_client || + c->spd.this.protocol || c->spd.that.protocol || + c->spd.this.port || c->spd.that.port; + + bool send_natoa = FALSE; + u_int8_t np = ISAKMP_NEXT_NONE; + + st->st_whack_sock = whack_sock; + st->st_connection = c; + set_cur_state(st); /* we must reset before exit */ + st->st_policy = policy; + st->st_try = try; + + st->st_myuserprotoid = c->spd.this.protocol; + st->st_peeruserprotoid = c->spd.that.protocol; + st->st_myuserport = c->spd.this.port; + st->st_peeruserport = c->spd.that.port; + + st->st_msgid = generate_msgid(isakmp_sa); + st->st_state = STATE_QUICK_I1; + + insert_state(st); /* needs cookies, connection, and msgid */ + + if (replacing == SOS_NOBODY) + plog("initiating Quick Mode %s {using isakmp#%lu}" + , prettypolicy(policy) + , isakmp_sa->st_serialno); + else + plog("initiating Quick Mode %s to replace #%lu {using isakmp#%lu}" + , prettypolicy(policy) + , replacing + , isakmp_sa->st_serialno); + + if (isakmp_sa->nat_traversal & NAT_T_DETECTED) + { + /* Duplicate nat_traversal status in new state */ + st->nat_traversal = isakmp_sa->nat_traversal; + + if (isakmp_sa->nat_traversal & LELEM(NAT_TRAVERSAL_NAT_BHND_ME)) + has_client = TRUE; + + nat_traversal_change_port_lookup(NULL, st); + } + else + st->nat_traversal = 0; + + /* are we going to send a NAT-OA payload? */ + if ((st->nat_traversal & NAT_T_WITH_NATOA) + && !(st->st_policy & POLICY_TUNNEL) + && (st->nat_traversal & LELEM(NAT_TRAVERSAL_NAT_BHND_ME))) + { + send_natoa = TRUE; + np = (st->nat_traversal & NAT_T_WITH_RFC_VALUES) ? + ISAKMP_NEXT_NATOA_RFC : ISAKMP_NEXT_NATOA_DRAFTS; + } + + /* set up reply */ + init_pbs(&reply, reply_buffer, sizeof(reply_buffer), "reply packet"); + + /* HDR* out */ + { + struct isakmp_hdr hdr; + + hdr.isa_version = ISAKMP_MAJOR_VERSION << ISA_MAJ_SHIFT | ISAKMP_MINOR_VERSION; + hdr.isa_np = ISAKMP_NEXT_HASH; + hdr.isa_xchg = ISAKMP_XCHG_QUICK; + hdr.isa_msgid = st->st_msgid; + hdr.isa_flags = ISAKMP_FLAG_ENCRYPTION; + memcpy(hdr.isa_icookie, st->st_icookie, COOKIE_SIZE); + memcpy(hdr.isa_rcookie, st->st_rcookie, COOKIE_SIZE); + if (!out_struct(&hdr, &isakmp_hdr_desc, &reply, &rbody)) + { + reset_cur_state(); + return STF_INTERNAL_ERROR; + } + } + + /* HASH(1) -- create and note space to be filled later */ + START_HASH_PAYLOAD(rbody, ISAKMP_NEXT_SA); + + /* SA out */ + + /* + * See if pfs_group has been specified for this conn, + * if not, fallback to old use-same-as-P1 behaviour + */ +#ifndef NO_IKE_ALG + if (st->st_connection) + st->st_pfs_group = ike_alg_pfsgroup(st->st_connection, policy); + if (!st->st_pfs_group) +#endif + /* If PFS specified, use the same group as during Phase 1: + * since no negotiation is possible, we pick one that is + * very likely supported. + */ + st->st_pfs_group = policy & POLICY_PFS? isakmp_sa->st_oakley.group : NULL; + + /* Emit SA payload based on a subset of the policy bits. + * POLICY_COMPRESS is considered iff we can do IPcomp. + */ + { + lset_t pm = POLICY_ENCRYPT | POLICY_AUTHENTICATE; + + if (can_do_IPcomp) + pm |= POLICY_COMPRESS; + + if (!out_sa(&rbody + , &ipsec_sadb[(st->st_policy & pm) >> POLICY_IPSEC_SHIFT] + , st, FALSE, ISAKMP_NEXT_NONCE)) + { + reset_cur_state(); + return STF_INTERNAL_ERROR; + } + } + + /* Ni out */ + if (!build_and_ship_nonce(&st->st_ni, &rbody + , policy & POLICY_PFS? ISAKMP_NEXT_KE : has_client? ISAKMP_NEXT_ID : np + , "Ni")) + { + reset_cur_state(); + return STF_INTERNAL_ERROR; + } + + /* [ KE ] out (for PFS) */ + + if (st->st_pfs_group != NULL) + { + if (!build_and_ship_KE(st, &st->st_gi, st->st_pfs_group + , &rbody, has_client? ISAKMP_NEXT_ID : np)) + { + reset_cur_state(); + return STF_INTERNAL_ERROR; + } + } + + /* [ IDci, IDcr ] out */ + if (has_client) + { + /* IDci (we are initiator), then IDcr (peer is responder) */ + if (!emit_subnet_id(&c->spd.this.client + , ISAKMP_NEXT_ID, st->st_myuserprotoid, st->st_myuserport, &rbody) + || !emit_subnet_id(&c->spd.that.client + , np, st->st_peeruserprotoid, st->st_peeruserport, &rbody)) + { + reset_cur_state(); + return STF_INTERNAL_ERROR; + } + } + + /* Send NAT-OA if our address is NATed */ + if (send_natoa) + { + if (!nat_traversal_add_natoa(ISAKMP_NEXT_NONE, &rbody, st)) + { + reset_cur_state(); + return STF_INTERNAL_ERROR; + } + } + + /* finish computing HASH(1), inserting it in output */ + (void) quick_mode_hash12(r_hashval, r_hash_start, rbody.cur + , st, &st->st_msgid, FALSE); + + /* encrypt message, except for fixed part of header */ + + init_phase2_iv(isakmp_sa, &st->st_msgid); + st->st_new_iv_len = isakmp_sa->st_new_iv_len; + memcpy(st->st_new_iv, isakmp_sa->st_new_iv, st->st_new_iv_len); + + if (!encrypt_message(&rbody, st)) + { + reset_cur_state(); + return STF_INTERNAL_ERROR; + } + + /* save packet, now that we know its size */ + clonetochunk(st->st_tpacket, reply.start, pbs_offset(&reply) + , "reply packet from quick_outI1"); + + /* send the packet */ + + send_packet(st, "quick_outI1"); + + delete_event(st); + event_schedule(EVENT_RETRANSMIT, EVENT_RETRANSMIT_DELAY_0, st); + + if (replacing == SOS_NOBODY) + whack_log(RC_NEW_STATE + STATE_QUICK_I1 + , "%s: initiate" + , enum_name(&state_names, st->st_state)); + else + whack_log(RC_NEW_STATE + STATE_QUICK_I1 + , "%s: initiate to replace #%lu" + , enum_name(&state_names, st->st_state) + , replacing); + reset_cur_state(); + return STF_OK; +} + + +/* + * Decode the CERT payload of Phase 1. + */ +static void +decode_cert(struct msg_digest *md) +{ + struct payload_digest *p; + + for (p = md->chain[ISAKMP_NEXT_CERT]; p != NULL; p = p->next) + { + struct isakmp_cert *const cert = &p->payload.cert; + chunk_t blob; + time_t valid_until; + blob.ptr = p->pbs.cur; + blob.len = pbs_left(&p->pbs); + if (cert->isacert_type == CERT_X509_SIGNATURE) + { + x509cert_t cert = empty_x509cert; + if (parse_x509cert(blob, 0, &cert)) + { + if (verify_x509cert(&cert, strict_crl_policy, &valid_until)) + { + DBG(DBG_PARSING, + DBG_log("Public key validated") + ) + add_x509_public_key(&cert, valid_until, DAL_SIGNED); + } + else + { + plog("X.509 certificate rejected"); + } + free_generalNames(cert.subjectAltName, FALSE); + free_generalNames(cert.crlDistributionPoints, FALSE); + } + else + plog("Syntax error in X.509 certificate"); + } + else if (cert->isacert_type == CERT_PKCS7_WRAPPED_X509) + { + x509cert_t *cert = NULL; + + if (pkcs7_parse_signedData(blob, NULL, &cert, NULL, NULL)) + store_x509certs(&cert, strict_crl_policy); + else + plog("Syntax error in PKCS#7 wrapped X.509 certificates"); + } + else + { + loglog(RC_LOG_SERIOUS, "ignoring %s certificate payload", + enum_show(&cert_type_names, cert->isacert_type)); + DBG_cond_dump_chunk(DBG_PARSING, "CERT:\n", blob); + } + } +} + +/* + * Decode the CR payload of Phase 1. + */ +static void +decode_cr(struct msg_digest *md, struct connection *c) +{ + struct payload_digest *p; + + for (p = md->chain[ISAKMP_NEXT_CR]; p != NULL; p = p->next) + { + struct isakmp_cr *const cr = &p->payload.cr; + chunk_t ca_name; + + ca_name.len = pbs_left(&p->pbs); + ca_name.ptr = (ca_name.len > 0)? p->pbs.cur : NULL; + + DBG_cond_dump_chunk(DBG_PARSING, "CR", ca_name); + + if (cr->isacr_type == CERT_X509_SIGNATURE) + { + char buf[BUF_LEN]; + + if (ca_name.len > 0) + { + generalName_t *gn; + + if (!is_asn1(ca_name)) + continue; + + gn = alloc_thing(generalName_t, "generalName"); + clonetochunk(ca_name, ca_name.ptr,ca_name.len, "ca name"); + gn->kind = GN_DIRECTORY_NAME; + gn->name = ca_name; + gn->next = c->requested_ca; + c->requested_ca = gn; + } + c->got_certrequest = TRUE; + + DBG(DBG_PARSING | DBG_CONTROL, + dntoa_or_null(buf, BUF_LEN, ca_name, "%any"); + DBG_log("requested CA: '%s'", buf); + ) + } + else + loglog(RC_LOG_SERIOUS, "ignoring %s certificate request payload", + enum_show(&cert_type_names, cr->isacr_type)); + } +} + +/* Decode the ID payload of Phase 1 (main_inI3_outR3 and main_inR3) + * Note: we may change connections as a result. + * We must be called before SIG or HASH are decoded since we + * may change the peer's RSA key or ID. + */ +static bool +decode_peer_id(struct msg_digest *md, struct id *peer) +{ + struct state *const st = md->st; + struct payload_digest *const id_pld = md->chain[ISAKMP_NEXT_ID]; + const pb_stream *const id_pbs = &id_pld->pbs; + struct isakmp_id *const id = &id_pld->payload.id; + + /* I think that RFC2407 (IPSEC DOI) 4.6.2 is confused. + * It talks about the protocol ID and Port fields of the ID + * Payload, but they don't exist as such in Phase 1. + * We use more appropriate names. + * isaid_doi_specific_a is in place of Protocol ID. + * isaid_doi_specific_b is in place of Port. + * Besides, there is no good reason for allowing these to be + * other than 0 in Phase 1. + */ + if ((st->nat_traversal & NAT_T_WITH_PORT_FLOATING) + && id->isaid_doi_specific_a == IPPROTO_UDP + && (id->isaid_doi_specific_b == 0 || id->isaid_doi_specific_b == NAT_T_IKE_FLOAT_PORT)) + { + DBG_log("protocol/port in Phase 1 ID Payload is %d/%d. " + "accepted with port_floating NAT-T", + id->isaid_doi_specific_a, id->isaid_doi_specific_b); + } + else if (!(id->isaid_doi_specific_a == 0 && id->isaid_doi_specific_b == 0) + && !(id->isaid_doi_specific_a == IPPROTO_UDP && id->isaid_doi_specific_b == IKE_UDP_PORT)) + { + loglog(RC_LOG_SERIOUS, "protocol/port in Phase 1 ID Payload must be 0/0 or %d/%d" + " but are %d/%d" + , IPPROTO_UDP, IKE_UDP_PORT + , id->isaid_doi_specific_a, id->isaid_doi_specific_b); + return FALSE; + } + + peer->kind = id->isaid_idtype; + + switch (peer->kind) + { + case ID_IPV4_ADDR: + case ID_IPV6_ADDR: + /* failure mode for initaddr is probably inappropriate address length */ + { + err_t ugh = initaddr(id_pbs->cur, pbs_left(id_pbs) + , peer->kind == ID_IPV4_ADDR? AF_INET : AF_INET6 + , &peer->ip_addr); + + if (ugh != NULL) + { + loglog(RC_LOG_SERIOUS, "improper %s identification payload: %s" + , enum_show(&ident_names, peer->kind), ugh); + /* XXX Could send notification back */ + return FALSE; + } + } + break; + + case ID_USER_FQDN: + if (memchr(id_pbs->cur, '@', pbs_left(id_pbs)) == NULL) + { + loglog(RC_LOG_SERIOUS, "peer's ID_USER_FQDN contains no @"); + return FALSE; + } + /* FALLTHROUGH */ + case ID_FQDN: + if (memchr(id_pbs->cur, '\0', pbs_left(id_pbs)) != NULL) + { + loglog(RC_LOG_SERIOUS, "Phase 1 ID Payload of type %s contains a NUL" + , enum_show(&ident_names, peer->kind)); + return FALSE; + } + + /* ??? ought to do some more sanity check, but what? */ + + setchunk(peer->name, id_pbs->cur, pbs_left(id_pbs)); + break; + + case ID_KEY_ID: + setchunk(peer->name, id_pbs->cur, pbs_left(id_pbs)); + DBG(DBG_PARSING, + DBG_dump_chunk("KEY ID:", peer->name)); + break; + + case ID_DER_ASN1_DN: + setchunk(peer->name, id_pbs->cur, pbs_left(id_pbs)); + DBG(DBG_PARSING, + DBG_dump_chunk("DER ASN1 DN:", peer->name)); + break; + + default: + /* XXX Could send notification back */ + loglog(RC_LOG_SERIOUS, "Unacceptable identity type (%s) in Phase 1 ID Payload" + , enum_show(&ident_names, peer->kind)); + return FALSE; + } + + { + char buf[BUF_LEN]; + + idtoa(peer, buf, sizeof(buf)); + plog("Peer ID is %s: '%s'", + enum_show(&ident_names, id->isaid_idtype), buf); + } + + /* check for certificates */ + decode_cert(md); + return TRUE; +} + +/* Now that we've decoded the ID payload, let's see if we + * need to switch connections. + * We must not switch horses if we initiated: + * - if the initiation was explicit, we'd be ignoring user's intent + * - if opportunistic, we'll lose our HOLD info + */ +static bool +switch_connection(struct msg_digest *md, struct id *peer, bool initiator) +{ + struct state *const st = md->st; + struct connection *c = st->st_connection; + + chunk_t peer_ca = (st->st_peer_pubkey != NULL) + ? st->st_peer_pubkey->issuer : empty_chunk; + + DBG(DBG_CONTROL, + char buf[BUF_LEN]; + + dntoa_or_null(buf, BUF_LEN, peer_ca, "%none"); + DBG_log("peer CA: '%s'", buf); + ) + + if (initiator) + { + int pathlen; + + if (!same_id(&c->spd.that.id, peer)) + { + char expect[BUF_LEN] + , found[BUF_LEN]; + + idtoa(&c->spd.that.id, expect, sizeof(expect)); + idtoa(peer, found, sizeof(found)); + loglog(RC_LOG_SERIOUS + , "we require peer to have ID '%s', but peer declares '%s'" + , expect, found); + return FALSE; + } + + DBG(DBG_CONTROL, + char buf[BUF_LEN]; + + dntoa_or_null(buf, BUF_LEN, c->spd.this.ca, "%none"); + DBG_log("required CA: '%s'", buf); + ) + + if (!trusted_ca(peer_ca, c->spd.that.ca, &pathlen)) + { + loglog(RC_LOG_SERIOUS + , "we don't accept the peer's CA"); + return FALSE; + } + } + else + { + struct connection *r; + + /* check for certificate requests */ + decode_cr(md, c); + + r = refine_host_connection(st, peer, peer_ca); + + /* delete the collected certificate requests */ + free_generalNames(c->requested_ca, TRUE); + c->requested_ca = NULL; + + if (r == NULL) + { + char buf[BUF_LEN]; + + idtoa(peer, buf, sizeof(buf)); + loglog(RC_LOG_SERIOUS, "no suitable connection for peer '%s'", buf); + return FALSE; + } + + DBG(DBG_CONTROL, + char buf[BUF_LEN]; + + dntoa_or_null(buf, BUF_LEN, r->spd.this.ca, "%none"); + DBG_log("offered CA: '%s'", buf); + ) + + if (r != c) + { + /* apparently, r is an improvement on c -- replace */ + + DBG(DBG_CONTROL + , DBG_log("switched from \"%s\" to \"%s\"", c->name, r->name)); + if (r->kind == CK_TEMPLATE) + { + /* instantiate it, filling in peer's ID */ + r = rw_instantiate(r, &c->spd.that.host_addr + , c->spd.that.host_port, NULL, peer); + } + + /* copy certificate request info */ + r->got_certrequest = c->got_certrequest; + + st->st_connection = r; /* kill reference to c */ + set_cur_connection(r); + connection_discard(c); + } + else if (c->spd.that.has_id_wildcards) + { + free_id_content(&c->spd.that.id); + c->spd.that.id = *peer; + c->spd.that.has_id_wildcards = FALSE; + unshare_id_content(&c->spd.that.id); + } + } + return TRUE; +} + +/* Decode the variable part of an ID packet (during Quick Mode). + * This is designed for packets that identify clients, not peers. + * Rejects 0.0.0.0/32 or IPv6 equivalent because + * (1) it is wrong and (2) we use this value for inband signalling. + */ +static bool +decode_net_id(struct isakmp_ipsec_id *id +, pb_stream *id_pbs +, ip_subnet *net +, const char *which) +{ + const struct af_info *afi = NULL; + + /* Note: the following may be a pointer into static memory + * that may be recycled, but only if the type is not known. + * That case is disposed of very early -- in the first switch. + */ + const char *idtypename = enum_show(&ident_names, id->isaiid_idtype); + + switch (id->isaiid_idtype) + { + case ID_IPV4_ADDR: + case ID_IPV4_ADDR_SUBNET: + case ID_IPV4_ADDR_RANGE: + afi = &af_inet4_info; + break; + case ID_IPV6_ADDR: + case ID_IPV6_ADDR_SUBNET: + case ID_IPV6_ADDR_RANGE: + afi = &af_inet6_info; + break; + case ID_FQDN: + return TRUE; + default: + /* XXX support more */ + loglog(RC_LOG_SERIOUS, "unsupported ID type %s" + , idtypename); + /* XXX Could send notification back */ + return FALSE; + } + + switch (id->isaiid_idtype) + { + case ID_IPV4_ADDR: + case ID_IPV6_ADDR: + { + ip_address temp_address; + err_t ugh; + + ugh = initaddr(id_pbs->cur, pbs_left(id_pbs), afi->af, &temp_address); + + if (ugh != NULL) + { + loglog(RC_LOG_SERIOUS, "%s ID payload %s has wrong length in Quick I1 (%s)" + , which, idtypename, ugh); + /* XXX Could send notification back */ + return FALSE; + } + if (isanyaddr(&temp_address)) + { + loglog(RC_LOG_SERIOUS, "%s ID payload %s is invalid (%s) in Quick I1" + , which, idtypename, ip_str(&temp_address)); + /* XXX Could send notification back */ + return FALSE; + } + happy(addrtosubnet(&temp_address, net)); + DBG(DBG_PARSING | DBG_CONTROL + , DBG_log("%s is %s", which, ip_str(&temp_address))); + break; + } + + case ID_IPV4_ADDR_SUBNET: + case ID_IPV6_ADDR_SUBNET: + { + ip_address temp_address, temp_mask; + err_t ugh; + + if (pbs_left(id_pbs) != 2 * afi->ia_sz) + { + loglog(RC_LOG_SERIOUS, "%s ID payload %s wrong length in Quick I1" + , which, idtypename); + /* XXX Could send notification back */ + return FALSE; + } + ugh = initaddr(id_pbs->cur + , afi->ia_sz, afi->af, &temp_address); + if (ugh == NULL) + ugh = initaddr(id_pbs->cur + afi->ia_sz + , afi->ia_sz, afi->af, &temp_mask); + if (ugh == NULL) + ugh = initsubnet(&temp_address, masktocount(&temp_mask) + , '0', net); + if (ugh == NULL && subnetisnone(net)) + ugh = "contains only anyaddr"; + if (ugh != NULL) + { + loglog(RC_LOG_SERIOUS, "%s ID payload %s bad subnet in Quick I1 (%s)" + , which, idtypename, ugh); + /* XXX Could send notification back */ + return FALSE; + } + DBG(DBG_PARSING | DBG_CONTROL, + { + char temp_buff[SUBNETTOT_BUF]; + + subnettot(net, 0, temp_buff, sizeof(temp_buff)); + DBG_log("%s is subnet %s", which, temp_buff); + }); + break; + } + + case ID_IPV4_ADDR_RANGE: + case ID_IPV6_ADDR_RANGE: + { + ip_address temp_address_from, temp_address_to; + err_t ugh; + + if (pbs_left(id_pbs) != 2 * afi->ia_sz) + { + loglog(RC_LOG_SERIOUS, "%s ID payload %s wrong length in Quick I1" + , which, idtypename); + /* XXX Could send notification back */ + return FALSE; + } + ugh = initaddr(id_pbs->cur, afi->ia_sz, afi->af, &temp_address_from); + if (ugh == NULL) + ugh = initaddr(id_pbs->cur + afi->ia_sz + , afi->ia_sz, afi->af, &temp_address_to); + if (ugh != NULL) + { + loglog(RC_LOG_SERIOUS, "%s ID payload %s malformed (%s) in Quick I1" + , which, idtypename, ugh); + /* XXX Could send notification back */ + return FALSE; + } + + ugh = rangetosubnet(&temp_address_from, &temp_address_to, net); + if (ugh == NULL && subnetisnone(net)) + ugh = "contains only anyaddr"; + if (ugh != NULL) + { + char temp_buff1[ADDRTOT_BUF], temp_buff2[ADDRTOT_BUF]; + + addrtot(&temp_address_from, 0, temp_buff1, sizeof(temp_buff1)); + addrtot(&temp_address_to, 0, temp_buff2, sizeof(temp_buff2)); + loglog(RC_LOG_SERIOUS, "%s ID payload in Quick I1, %s" + " %s - %s unacceptable: %s" + , which, idtypename, temp_buff1, temp_buff2, ugh); + return FALSE; + } + DBG(DBG_PARSING | DBG_CONTROL, + { + char temp_buff[SUBNETTOT_BUF]; + + subnettot(net, 0, temp_buff, sizeof(temp_buff)); + DBG_log("%s is subnet %s (received as range)" + , which, temp_buff); + }); + break; + } + } + + /* set the port selector */ + setportof(htons(id->isaiid_port), &net->addr); + + DBG(DBG_PARSING | DBG_CONTROL, + DBG_log("%s protocol/port is %d/%d", which, id->isaiid_protoid, id->isaiid_port) + ) + + return TRUE; +} + +/* like decode, but checks that what is received matches what was sent */ +static bool + +check_net_id(struct isakmp_ipsec_id *id +, pb_stream *id_pbs +, u_int8_t *protoid +, u_int16_t *port +, ip_subnet *net +, const char *which) +{ + ip_subnet net_temp; + + if (!decode_net_id(id, id_pbs, &net_temp, which)) + return FALSE; + + if (!samesubnet(net, &net_temp) + || *protoid != id->isaiid_protoid || *port != id->isaiid_port) + { + loglog(RC_LOG_SERIOUS, "%s ID returned doesn't match my proposal", which); + return FALSE; + } + return TRUE; +} + +/* + * look for the existence of a non-expiring preloaded public key + */ +static bool +has_preloaded_public_key(struct state *st) +{ + struct connection *c = st->st_connection; + + /* do not consider rw connections since + * the peer's identity must be known + */ + if (c->kind == CK_PERMANENT) + { + pubkey_list_t *p; + + /* look for a matching RSA public key */ + for (p = pubkeys; p != NULL; p = p->next) + { + pubkey_t *key = p->key; + + if (key->alg == PUBKEY_ALG_RSA && + same_id(&c->spd.that.id, &key->id) && + key->until_time == UNDEFINED_TIME) + { + /* found a preloaded public key */ + return TRUE; + } + } + } + return FALSE; +} + +/* + * Produce the new key material of Quick Mode. + * RFC 2409 "IKE" section 5.5 + * specifies how this is to be done. + */ +static void +compute_proto_keymat(struct state *st +, u_int8_t protoid +, struct ipsec_proto_info *pi) +{ + size_t needed_len; /* bytes of keying material needed */ + + /* Add up the requirements for keying material + * (It probably doesn't matter if we produce too much!) + */ + switch (protoid) + { + case PROTO_IPSEC_ESP: + switch (pi->attrs.transid) + { + case ESP_NULL: + needed_len = 0; + break; + case ESP_DES: + needed_len = DES_CBC_BLOCK_SIZE; + break; + case ESP_3DES: + needed_len = DES_CBC_BLOCK_SIZE * 3; + break; + default: +#ifndef NO_KERNEL_ALG + if((needed_len=kernel_alg_esp_enc_keylen(pi->attrs.transid))>0) { + /* XXX: check key_len "coupling with kernel.c's */ + if (pi->attrs.key_len) { + needed_len=pi->attrs.key_len/8; + DBG(DBG_PARSING, DBG_log("compute_proto_keymat:" + "key_len=%d from peer", + (int)needed_len)); + } + break; + } +#endif + bad_case(pi->attrs.transid); + } + +#ifndef NO_KERNEL_ALG + DBG(DBG_PARSING, DBG_log("compute_proto_keymat:" + "needed_len (after ESP enc)=%d", + (int)needed_len)); + if (kernel_alg_esp_auth_ok(pi->attrs.auth, NULL)) { + needed_len += kernel_alg_esp_auth_keylen(pi->attrs.auth); + } else +#endif + switch (pi->attrs.auth) + { + case AUTH_ALGORITHM_NONE: + break; + case AUTH_ALGORITHM_HMAC_MD5: + needed_len += HMAC_MD5_KEY_LEN; + break; + case AUTH_ALGORITHM_HMAC_SHA1: + needed_len += HMAC_SHA1_KEY_LEN; + break; + case AUTH_ALGORITHM_DES_MAC: + default: + bad_case(pi->attrs.auth); + } + DBG(DBG_PARSING, DBG_log("compute_proto_keymat:" + "needed_len (after ESP auth)=%d", + (int)needed_len)); + break; + + case PROTO_IPSEC_AH: + switch (pi->attrs.transid) + { + case AH_MD5: + needed_len = HMAC_MD5_KEY_LEN; + break; + case AH_SHA: + needed_len = HMAC_SHA1_KEY_LEN; + break; + default: + bad_case(pi->attrs.transid); + } + break; + + default: + bad_case(protoid); + } + + pi->keymat_len = needed_len; + + /* Allocate space for the keying material. + * Although only needed_len bytes are desired, we + * must round up to a multiple of ctx.hmac_digest_size + * so that our buffer isn't overrun. + */ + { + struct hmac_ctx ctx_me, ctx_peer; + size_t needed_space; /* space needed for keying material (rounded up) */ + size_t i; + + hmac_init_chunk(&ctx_me, st->st_oakley.hasher, st->st_skeyid_d); + ctx_peer = ctx_me; /* duplicate initial conditions */ + + needed_space = needed_len + pad_up(needed_len, ctx_me.hmac_digest_size); + replace(pi->our_keymat, alloc_bytes(needed_space, "keymat in compute_keymat()")); + replace(pi->peer_keymat, alloc_bytes(needed_space, "peer_keymat in quick_inI1_outR1()")); + + for (i = 0;; ) + { + if (st->st_shared.ptr != NULL) + { + /* PFS: include the g^xy */ + hmac_update_chunk(&ctx_me, st->st_shared); + hmac_update_chunk(&ctx_peer, st->st_shared); + } + hmac_update(&ctx_me, &protoid, sizeof(protoid)); + hmac_update(&ctx_peer, &protoid, sizeof(protoid)); + + hmac_update(&ctx_me, (u_char *)&pi->our_spi, sizeof(pi->our_spi)); + hmac_update(&ctx_peer, (u_char *)&pi->attrs.spi, sizeof(pi->attrs.spi)); + + hmac_update_chunk(&ctx_me, st->st_ni); + hmac_update_chunk(&ctx_peer, st->st_ni); + + hmac_update_chunk(&ctx_me, st->st_nr); + hmac_update_chunk(&ctx_peer, st->st_nr); + + hmac_final(pi->our_keymat + i, &ctx_me); + hmac_final(pi->peer_keymat + i, &ctx_peer); + + i += ctx_me.hmac_digest_size; + if (i >= needed_space) + break; + + /* more keying material needed: prepare to go around again */ + + hmac_reinit(&ctx_me); + hmac_reinit(&ctx_peer); + + hmac_update(&ctx_me, pi->our_keymat + i - ctx_me.hmac_digest_size + , ctx_me.hmac_digest_size); + hmac_update(&ctx_peer, pi->peer_keymat + i - ctx_peer.hmac_digest_size + , ctx_peer.hmac_digest_size); + } + } + + DBG(DBG_CRYPT, + DBG_dump("KEYMAT computed:\n", pi->our_keymat, pi->keymat_len); + DBG_dump("Peer KEYMAT computed:\n", pi->peer_keymat, pi->keymat_len)); +} + +static void +compute_keymats(struct state *st) +{ + if (st->st_ah.present) + compute_proto_keymat(st, PROTO_IPSEC_AH, &st->st_ah); + if (st->st_esp.present) + compute_proto_keymat(st, PROTO_IPSEC_ESP, &st->st_esp); +} + +/* State Transition Functions. + * + * The definition of state_microcode_table in demux.c is a good + * overview of these routines. + * + * - Called from process_packet; result handled by complete_state_transition + * - struct state_microcode member "processor" points to these + * - these routine definitionss are in state order + * - these routines must be restartable from any point of error return: + * beware of memory allocated before any error. + * - output HDR is usually emitted by process_packet (if state_microcode + * member first_out_payload isn't ISAKMP_NEXT_NONE). + * + * The transition functions' functions include: + * - process and judge payloads + * - update st_iv (result of decryption is in st_new_iv) + * - build reply packet + */ + +/* Handle a Main Mode Oakley first packet (responder side). + * HDR;SA --> HDR;SA + */ +stf_status +main_inI1_outR1(struct msg_digest *md) +{ + struct payload_digest *const sa_pd = md->chain[ISAKMP_NEXT_SA]; + struct state *st; + struct connection *c; + struct isakmp_proposal proposal; + pb_stream proposal_pbs; + pb_stream r_sa_pbs; + u_int32_t ipsecdoisit; + lset_t policy = LEMPTY; + int vids_to_send = 0; + + /* We preparse the peer's proposal in order to determine + * the requested authentication policy (RSA or PSK) + */ + RETURN_STF_FAILURE(preparse_isakmp_sa_body(&sa_pd->payload.sa + , &sa_pd->pbs, &ipsecdoisit, &proposal_pbs, &proposal)); + + backup_pbs(&proposal_pbs); + RETURN_STF_FAILURE(parse_isakmp_policy(&proposal_pbs + , proposal.isap_notrans, &policy)); + restore_pbs(&proposal_pbs); + + /* We are only considering candidate connections that match + * the requested authentication policy (RSA or PSK) + */ + c = find_host_connection(&md->iface->addr, pluto_port + , &md->sender, md->sender_port, policy); + + if (c == NULL && md->iface->ike_float) + { + c = find_host_connection(&md->iface->addr, NAT_T_IKE_FLOAT_PORT + , &md->sender, md->sender_port, policy); + } + + if (c == NULL) + { + /* See if a wildcarded connection can be found. + * We cannot pick the right connection, so we're making a guess. + * All Road Warrior connections are fair game: + * we pick the first we come across (if any). + * If we don't find any, we pick the first opportunistic + * with the smallest subnet that includes the peer. + * There is, of course, no necessary relationship between + * an Initiator's address and that of its client, + * but Food Groups kind of assumes one. + */ + { + struct connection *d; + + d = find_host_connection(&md->iface->addr + , pluto_port, (ip_address*)NULL, md->sender_port, policy); + + for (; d != NULL; d = d->hp_next) + { + if (d->kind == CK_GROUP) + { + /* ignore */ + } + else + { + if (d->kind == CK_TEMPLATE && !(d->policy & POLICY_OPPO)) + { + /* must be Road Warrior: we have a winner */ + c = d; + break; + } + + /* Opportunistic or Shunt: pick tightest match */ + if (addrinsubnet(&md->sender, &d->spd.that.client) + && (c == NULL || !subnetinsubnet(&c->spd.that.client, &d->spd.that.client))) + c = d; + } + } + } + + if (c == NULL) + { + loglog(RC_LOG_SERIOUS, "initial Main Mode message received on %s:%u" + " but no connection has been authorized%s%s" + , ip_str(&md->iface->addr), ntohs(portof(&md->iface->addr)) + , (policy != LEMPTY) ? " with policy=" : "" + , (policy != LEMPTY) ? bitnamesof(sa_policy_bit_names, policy) : ""); + /* XXX notification is in order! */ + return STF_IGNORE; + } + else if (c->kind != CK_TEMPLATE) + { + loglog(RC_LOG_SERIOUS, "initial Main Mode message received on %s:%u" + " but \"%s\" forbids connection" + , ip_str(&md->iface->addr), pluto_port, c->name); + /* XXX notification is in order! */ + return STF_IGNORE; + } + else + { + /* Create a temporary connection that is a copy of this one. + * His ID isn't declared yet. + */ + c = rw_instantiate(c, &md->sender, md->sender_port, NULL, NULL); + } + } + else if (c->kind == CK_TEMPLATE) + { + /* Create an instance + * This is a rare case: wildcard peer ID but static peer IP address + */ + c = rw_instantiate(c, &md->sender, md->sender_port, NULL, &c->spd.that.id); + } + + /* Set up state */ + md->st = st = new_state(); + st->st_connection = c; + set_cur_state(st); /* (caller will reset cur_state) */ + st->st_try = 0; /* not our job to try again from start */ + st->st_policy = c->policy & ~POLICY_IPSEC_MASK; /* only as accurate as connection */ + + memcpy(st->st_icookie, md->hdr.isa_icookie, COOKIE_SIZE); + get_cookie(FALSE, st->st_rcookie, COOKIE_SIZE, &md->sender); + + insert_state(st); /* needs cookies, connection, and msgid (0) */ + + st->st_doi = ISAKMP_DOI_IPSEC; + st->st_situation = SIT_IDENTITY_ONLY; /* We only support this */ + + if ((c->kind == CK_INSTANCE) && (c->spd.that.host_port != pluto_port)) + { + plog("responding to Main Mode from unknown peer %s:%u" + , ip_str(&c->spd.that.host_addr), c->spd.that.host_port); + } + else if (c->kind == CK_INSTANCE) + { + plog("responding to Main Mode from unknown peer %s" + , ip_str(&c->spd.that.host_addr)); + } + else + { + plog("responding to Main Mode"); + } + + /* parse_isakmp_sa also spits out a winning SA into our reply, + * so we have to build our md->reply and emit HDR before calling it. + */ + + /* determine how many Vendor ID payloads we will be sending */ + if (SEND_PLUTO_VID) + vids_to_send++; + if (SEND_CISCO_UNITY_VID) + vids_to_send++; + if (md->openpgp) + vids_to_send++; + /* always send XAUTH Vendor ID */ + vids_to_send++; + /* always send DPD Vendor ID */ + vids_to_send++; + if (md->nat_traversal_vid && nat_traversal_enabled) + vids_to_send++; + + /* HDR out. + * We can't leave this to comm_handle() because we must + * fill in the cookie. + */ + { + struct isakmp_hdr r_hdr = md->hdr; + + r_hdr.isa_flags &= ~ISAKMP_FLAG_COMMIT; /* we won't ever turn on this bit */ + memcpy(r_hdr.isa_rcookie, st->st_rcookie, COOKIE_SIZE); + r_hdr.isa_np = ISAKMP_NEXT_SA; + if (!out_struct(&r_hdr, &isakmp_hdr_desc, &md->reply, &md->rbody)) + return STF_INTERNAL_ERROR; + } + + /* start of SA out */ + { + struct isakmp_sa r_sa = sa_pd->payload.sa; + + r_sa.isasa_np = vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE; + + if (!out_struct(&r_sa, &isakmp_sa_desc, &md->rbody, &r_sa_pbs)) + return STF_INTERNAL_ERROR; + } + + /* SA body in and out */ + RETURN_STF_FAILURE(parse_isakmp_sa_body(ipsecdoisit, &proposal_pbs + ,&proposal, &r_sa_pbs, st, FALSE)); + + /* if enabled send Pluto Vendor ID */ + if (SEND_PLUTO_VID) + { + if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE + , &md->rbody, VID_STRONGSWAN)) + { + return STF_INTERNAL_ERROR; + } + } + + /* if enabled send Cisco Unity Vendor ID */ + if (SEND_CISCO_UNITY_VID) + { + if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE + , &md->rbody, VID_CISCO_UNITY)) + { + return STF_INTERNAL_ERROR; + } + } + + /* + * if the peer sent an OpenPGP Vendor ID we offer the same capability + */ + if (md->openpgp) + { + if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE + , &md->rbody, VID_OPENPGP)) + { + return STF_INTERNAL_ERROR; + } + } + + /* Announce our ability to do eXtended AUTHentication to the peer */ + if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE + , &md->rbody, VID_MISC_XAUTH)) + { + return STF_INTERNAL_ERROR; + } + + /* Announce our ability to do Dead Peer Detection to the peer */ + if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE + , &md->rbody, VID_MISC_DPD)) + { + return STF_INTERNAL_ERROR; + } + + if (md->nat_traversal_vid && nat_traversal_enabled) + { + /* reply if NAT-Traversal draft is supported */ + st->nat_traversal = nat_traversal_vid_to_method(md->nat_traversal_vid); + + if (st->nat_traversal + && !out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE + , &md->rbody, md->nat_traversal_vid)) + { + return STF_INTERNAL_ERROR; + } + } + + close_message(&md->rbody); + + /* save initiator SA for HASH */ + clonereplacechunk(st->st_p1isa, sa_pd->pbs.start, pbs_room(&sa_pd->pbs), "sa in main_inI1_outR1()"); + + return STF_OK; +} + +/* STATE_MAIN_I1: HDR, SA --> auth dependent + * PSK_AUTH, DS_AUTH: --> HDR, KE, Ni + * + * The following are not yet implemented: + * PKE_AUTH: --> HDR, KE, [ HASH(1), ] <IDi1_b>PubKey_r, <Ni_b>PubKey_r + * RPKE_AUTH: --> HDR, [ HASH(1), ] <Ni_b>Pubkey_r, <KE_b>Ke_i, + * <IDi1_b>Ke_i [,<<Cert-I_b>Ke_i] + * + * We must verify that the proposal received matches one we sent. + */ +stf_status +main_inR1_outI2(struct msg_digest *md) +{ + struct state *const st = md->st; + + u_int8_t np = ISAKMP_NEXT_NONE; + + /* verify echoed SA */ + { + u_int32_t ipsecdoisit; + pb_stream proposal_pbs; + struct isakmp_proposal proposal; + struct payload_digest *const sapd = md->chain[ISAKMP_NEXT_SA]; + + RETURN_STF_FAILURE(preparse_isakmp_sa_body(&sapd->payload.sa + ,&sapd->pbs, &ipsecdoisit, &proposal_pbs, &proposal)); + if (proposal.isap_notrans != 1) + { + loglog(RC_LOG_SERIOUS, "a single Transform is required in a selecting Oakley Proposal; found %u" + , (unsigned)proposal.isap_notrans); + RETURN_STF_FAILURE(BAD_PROPOSAL_SYNTAX); + } + RETURN_STF_FAILURE(parse_isakmp_sa_body(ipsecdoisit + , &proposal_pbs, &proposal, NULL, st, TRUE)); + } + + if (nat_traversal_enabled && md->nat_traversal_vid) + { + st->nat_traversal = nat_traversal_vid_to_method(md->nat_traversal_vid); + plog("enabling possible NAT-traversal with method %s" + , bitnamesof(natt_type_bitnames, st->nat_traversal)); + } + if (st->nat_traversal & NAT_T_WITH_NATD) + { + np = (st->nat_traversal & NAT_T_WITH_RFC_VALUES) ? + ISAKMP_NEXT_NATD_RFC : ISAKMP_NEXT_NATD_DRAFTS; + } + + /**************** build output packet HDR;KE;Ni ****************/ + + /* HDR out. + * We can't leave this to comm_handle() because the isa_np + * depends on the type of Auth (eventually). + */ + echo_hdr(md, FALSE, ISAKMP_NEXT_KE); + + /* KE out */ + if (!build_and_ship_KE(st, &st->st_gi, st->st_oakley.group + , &md->rbody, ISAKMP_NEXT_NONCE)) + return STF_INTERNAL_ERROR; + +#ifdef DEBUG + /* Ni out */ + if (!build_and_ship_nonce(&st->st_ni, &md->rbody + , (cur_debugging & IMPAIR_BUST_MI2)? ISAKMP_NEXT_VID : np, "Ni")) + return STF_INTERNAL_ERROR; + + if (cur_debugging & IMPAIR_BUST_MI2) + { + /* generate a pointless large VID payload to push message over MTU */ + pb_stream vid_pbs; + + if (!out_generic(np, &isakmp_vendor_id_desc, &md->rbody, &vid_pbs)) + return STF_INTERNAL_ERROR; + if (!out_zero(1500 /*MTU?*/, &vid_pbs, "Filler VID")) + return STF_INTERNAL_ERROR; + close_output_pbs(&vid_pbs); + } +#else + /* Ni out */ + if (!build_and_ship_nonce(&st->st_ni, &md->rbody, np, "Ni")) + return STF_INTERNAL_ERROR; +#endif + + if (st->nat_traversal & NAT_T_WITH_NATD) + { + if (!nat_traversal_add_natd(ISAKMP_NEXT_NONE, &md->rbody, md)) + return STF_INTERNAL_ERROR; + } + + /* finish message */ + close_message(&md->rbody); + + /* Reinsert the state, using the responder cookie we just received */ + unhash_state(st); + memcpy(st->st_rcookie, md->hdr.isa_rcookie, COOKIE_SIZE); + insert_state(st); /* needs cookies, connection, and msgid (0) */ + + return STF_OK; +} + +/* STATE_MAIN_R1: + * PSK_AUTH, DS_AUTH: HDR, KE, Ni --> HDR, KE, Nr + * + * The following are not yet implemented: + * PKE_AUTH: HDR, KE, [ HASH(1), ] <IDi1_b>PubKey_r, <Ni_b>PubKey_r + * --> HDR, KE, <IDr1_b>PubKey_i, <Nr_b>PubKey_i + * RPKE_AUTH: + * HDR, [ HASH(1), ] <Ni_b>Pubkey_r, <KE_b>Ke_i, <IDi1_b>Ke_i [,<<Cert-I_b>Ke_i] + * --> HDR, <Nr_b>PubKey_i, <KE_b>Ke_r, <IDr1_b>Ke_r + */ +stf_status +main_inI2_outR2(struct msg_digest *md) +{ + struct state *const st = md->st; + pb_stream *keyex_pbs = &md->chain[ISAKMP_NEXT_KE]->pbs; + + /* send CR if auth is RSA and no preloaded RSA public key exists*/ + bool RSA_auth = st->st_oakley.auth == OAKLEY_RSA_SIG + || st->st_oakley.auth == XAUTHInitRSA + || st->st_oakley.auth == XAUTHRespRSA; + bool send_cr = !no_cr_send && RSA_auth && !has_preloaded_public_key(st); + + u_int8_t np = ISAKMP_NEXT_NONE; + + /* KE in */ + RETURN_STF_FAILURE(accept_KE(&st->st_gi, "Gi", st->st_oakley.group, keyex_pbs)); + + /* Ni in */ + RETURN_STF_FAILURE(accept_nonce(md, &st->st_ni, "Ni")); + + if (st->nat_traversal & NAT_T_WITH_NATD) + { + nat_traversal_natd_lookup(md); + + np = (st->nat_traversal & NAT_T_WITH_RFC_VALUES) ? + ISAKMP_NEXT_NATD_RFC : ISAKMP_NEXT_NATD_DRAFTS; + } + if (st->nat_traversal) + { + nat_traversal_show_result(st->nat_traversal, md->sender_port); + } + if (st->nat_traversal & NAT_T_WITH_KA) + { + nat_traversal_new_ka_event(); + } + + /* decode certificate requests */ + st->st_connection->got_certrequest = FALSE; + decode_cr(md, st->st_connection); + + /**************** build output packet HDR;KE;Nr ****************/ + + /* HDR out done */ + + /* KE out */ + if (!build_and_ship_KE(st, &st->st_gr, st->st_oakley.group + , &md->rbody, ISAKMP_NEXT_NONCE)) + return STF_INTERNAL_ERROR; + +#ifdef DEBUG + /* Nr out */ + if (!build_and_ship_nonce(&st->st_nr, &md->rbody + , (cur_debugging & IMPAIR_BUST_MR2)? ISAKMP_NEXT_VID + : (send_cr? ISAKMP_NEXT_CR : np), "Nr")) + return STF_INTERNAL_ERROR; + + if (cur_debugging & IMPAIR_BUST_MR2) + { + /* generate a pointless large VID payload to push message over MTU */ + pb_stream vid_pbs; + + if (!out_generic((send_cr)? ISAKMP_NEXT_CR : np, + &isakmp_vendor_id_desc, &md->rbody, &vid_pbs)) + return STF_INTERNAL_ERROR; + if (!out_zero(1500 /*MTU?*/, &vid_pbs, "Filler VID")) + return STF_INTERNAL_ERROR; + close_output_pbs(&vid_pbs); + } +#else + /* Nr out */ + if (!build_and_ship_nonce(&st->st_nr, &md->rbody, + (send_cr)? ISAKMP_NEXT_CR : np, "Nr")) + return STF_INTERNAL_ERROR; +#endif + + /* CR out */ + if (send_cr) + { + if (st->st_connection->kind == CK_PERMANENT) + { + if (!build_and_ship_CR(CERT_X509_SIGNATURE + , st->st_connection->spd.that.ca + , &md->rbody, np)) + return STF_INTERNAL_ERROR; + } + else + { + generalName_t *ca = NULL; + + if (collect_rw_ca_candidates(md, &ca)) + { + generalName_t *gn; + + for (gn = ca; gn != NULL; gn = gn->next) + { + if (!build_and_ship_CR(CERT_X509_SIGNATURE, gn->name + , &md->rbody + , gn->next == NULL ? np : ISAKMP_NEXT_CR)) + return STF_INTERNAL_ERROR; + } + free_generalNames(ca, FALSE); + } + else + { + if (!build_and_ship_CR(CERT_X509_SIGNATURE, empty_chunk + , &md->rbody, np)) + return STF_INTERNAL_ERROR; + } + } + } + + if (st->nat_traversal & NAT_T_WITH_NATD) + { + if (!nat_traversal_add_natd(ISAKMP_NEXT_NONE, &md->rbody, md)) + return STF_INTERNAL_ERROR; + } + + /* finish message */ + close_message(&md->rbody); + + /* next message will be encrypted, but not this one. + * We could defer this calculation. + */ + compute_dh_shared(st, st->st_gi, st->st_oakley.group); + if (!generate_skeyids_iv(st)) + return STF_FAIL + AUTHENTICATION_FAILED; + update_iv(st); + + return STF_OK; +} + +/* STATE_MAIN_I2: + * SMF_PSK_AUTH: HDR, KE, Nr --> HDR*, IDi1, HASH_I + * SMF_DS_AUTH: HDR, KE, Nr --> HDR*, IDi1, [ CERT, ] SIG_I + * + * The following are not yet implemented. + * SMF_PKE_AUTH: HDR, KE, <IDr1_b>PubKey_i, <Nr_b>PubKey_i + * --> HDR*, HASH_I + * SMF_RPKE_AUTH: HDR, <Nr_b>PubKey_i, <KE_b>Ke_r, <IDr1_b>Ke_r + * --> HDR*, HASH_I + */ +stf_status +main_inR2_outI3(struct msg_digest *md) +{ + struct state *const st = md->st; + pb_stream *const keyex_pbs = &md->chain[ISAKMP_NEXT_KE]->pbs; + pb_stream id_pbs; /* ID Payload; also used for hash calculation */ + + certpolicy_t cert_policy = st->st_connection->spd.this.sendcert; + cert_t mycert = st->st_connection->spd.this.cert; + bool requested, send_cert, send_cr; + + bool RSA_auth = st->st_oakley.auth == OAKLEY_RSA_SIG + || st->st_oakley.auth == XAUTHInitRSA + || st->st_oakley.auth == XAUTHRespRSA; + + int auth_payload = RSA_auth ? ISAKMP_NEXT_SIG : ISAKMP_NEXT_HASH; + + /* KE in */ + RETURN_STF_FAILURE(accept_KE(&st->st_gr, "Gr", st->st_oakley.group, keyex_pbs)); + + /* Nr in */ + RETURN_STF_FAILURE(accept_nonce(md, &st->st_nr, "Nr")); + + /* decode certificate requests */ + st->st_connection->got_certrequest = FALSE; + decode_cr(md, st->st_connection); + + /* free collected certificate requests since as initiator + * we don't heed them anyway + */ + free_generalNames(st->st_connection->requested_ca, TRUE); + st->st_connection->requested_ca = NULL; + + /* send certificate if auth is RSA, we have one and we want + * or are requested to send it + */ + requested = cert_policy == CERT_SEND_IF_ASKED + && st->st_connection->got_certrequest; + send_cert = RSA_auth && mycert.type != CERT_NONE + && (cert_policy == CERT_ALWAYS_SEND || requested); + + /* send certificate request if we don't have a preloaded RSA public key */ + send_cr = !no_cr_send && send_cert && !has_preloaded_public_key(st); + + /* done parsing; initialize crypto */ + compute_dh_shared(st, st->st_gr, st->st_oakley.group); + if (!generate_skeyids_iv(st)) + return STF_FAIL + AUTHENTICATION_FAILED; + + if (st->nat_traversal & NAT_T_WITH_NATD) + { + nat_traversal_natd_lookup(md); + } + if (st->nat_traversal) + { + nat_traversal_show_result(st->nat_traversal, md->sender_port); + } + if (st->nat_traversal & NAT_T_WITH_KA) + { + nat_traversal_new_ka_event(); + } + + /*************** build output packet HDR*;IDii;HASH/SIG_I ***************/ + /* ??? NOTE: this is almost the same as main_inI3_outR3's code */ + + /* HDR* out done */ + + /* IDii out */ + { + struct isakmp_ipsec_id id_hd; + chunk_t id_b; + + build_id_payload(&id_hd, &id_b, &st->st_connection->spd.this); + id_hd.isaiid_np = (send_cert)? ISAKMP_NEXT_CERT : auth_payload; + if (!out_struct(&id_hd, &isakmp_ipsec_identification_desc, &md->rbody, &id_pbs) + || !out_chunk(id_b, &id_pbs, "my identity")) + return STF_INTERNAL_ERROR; + close_output_pbs(&id_pbs); + } + + /* CERT out */ + if (RSA_auth) + { + DBG(DBG_CONTROL, + DBG_log("our certificate policy is %s" + , enum_name(&cert_policy_names, cert_policy)) + ) + if (mycert.type != CERT_NONE) + { + const char *request_text = ""; + + if (cert_policy == CERT_SEND_IF_ASKED) + request_text = (send_cert)? "upon request":"without request"; + plog("we have a cert %s sending it %s" + , send_cert? "and are":"but are not", request_text); + } + else + { + plog("we don't have a cert"); + } + } + if (send_cert) + { + pb_stream cert_pbs; + + struct isakmp_cert cert_hd; + cert_hd.isacert_np = (send_cr)? ISAKMP_NEXT_CR : ISAKMP_NEXT_SIG; + cert_hd.isacert_type = mycert.type; + + if (!out_struct(&cert_hd, &isakmp_ipsec_certificate_desc, &md->rbody, &cert_pbs)) + return STF_INTERNAL_ERROR; + if (!out_chunk(get_mycert(mycert), &cert_pbs, "CERT")) + return STF_INTERNAL_ERROR; + close_output_pbs(&cert_pbs); + } + + /* CR out */ + if (send_cr) + { + if (!build_and_ship_CR(mycert.type, st->st_connection->spd.that.ca + , &md->rbody, ISAKMP_NEXT_SIG)) + return STF_INTERNAL_ERROR; + } + + /* HASH_I or SIG_I out */ + { + u_char hash_val[MAX_DIGEST_LEN]; + size_t hash_len = main_mode_hash(st, hash_val, TRUE, &id_pbs); + + if (auth_payload == ISAKMP_NEXT_HASH) + { + /* HASH_I out */ + if (!out_generic_raw(ISAKMP_NEXT_NONE, &isakmp_hash_desc, &md->rbody + , hash_val, hash_len, "HASH_I")) + return STF_INTERNAL_ERROR; + } + else + { + /* SIG_I out */ + u_char sig_val[RSA_MAX_OCTETS]; + size_t sig_len = RSA_sign_hash(st->st_connection + , sig_val, hash_val, hash_len); + + if (sig_len == 0) + { + loglog(RC_LOG_SERIOUS, "unable to locate my private key for RSA Signature"); + return STF_FAIL + AUTHENTICATION_FAILED; + } + + if (!out_generic_raw(ISAKMP_NEXT_NONE, &isakmp_signature_desc + , &md->rbody, sig_val, sig_len, "SIG_I")) + return STF_INTERNAL_ERROR; + } + } + + /* encrypt message, except for fixed part of header */ + + /* st_new_iv was computed by generate_skeyids_iv */ + if (!encrypt_message(&md->rbody, st)) + return STF_INTERNAL_ERROR; /* ??? we may be partly committed */ + + return STF_OK; +} + +/* Shared logic for asynchronous lookup of DNS KEY records. + * Used for STATE_MAIN_R2 and STATE_MAIN_I3. + */ + +enum key_oppo_step { + kos_null, + kos_his_txt +#ifdef USE_KEYRR + , kos_his_key +#endif +}; + +struct key_continuation { + struct adns_continuation ac; /* common prefix */ + struct msg_digest *md; + enum key_oppo_step step; + bool failure_ok; + err_t last_ugh; +}; + +typedef stf_status (key_tail_fn)(struct msg_digest *md + , struct key_continuation *kc); +static void +report_key_dns_failure(struct id *id, err_t ugh) +{ + char id_buf[BUF_LEN]; /* arbitrary limit on length of ID reported */ + + (void) idtoa(id, id_buf, sizeof(id_buf)); + loglog(RC_LOG_SERIOUS, "no RSA public key known for '%s'" + "; DNS search for KEY failed (%s)", id_buf, ugh); +} + + +/* Processs the Main Mode ID Payload and the Authenticator + * (Hash or Signature Payload). + * If a DNS query is still needed to get the other host's public key, + * the query is initiated and STF_SUSPEND is returned. + * Note: parameter kc is a continuation containing the results from + * the previous DNS query, or NULL indicating no query has been issued. + */ +static stf_status +main_id_and_auth(struct msg_digest *md + , bool initiator /* are we the Initiator? */ + , cont_fn_t cont_fn /* continuation function */ + , const struct key_continuation *kc /* current state, can be NULL */ +) +{ + struct state *st = md->st; + u_char hash_val[MAX_DIGEST_LEN]; + size_t hash_len; + struct id peer; + stf_status r = STF_OK; + + /* ID Payload in */ + if (!decode_peer_id(md, &peer)) + return STF_FAIL + INVALID_ID_INFORMATION; + + /* Hash the ID Payload. + * main_mode_hash requires idpl->cur to be at end of payload + * so we temporarily set if so. + */ + { + pb_stream *idpl = &md->chain[ISAKMP_NEXT_ID]->pbs; + u_int8_t *old_cur = idpl->cur; + + idpl->cur = idpl->roof; + hash_len = main_mode_hash(st, hash_val, !initiator, idpl); + idpl->cur = old_cur; + } + + switch (st->st_oakley.auth) + { + case OAKLEY_PRESHARED_KEY: + case XAUTHInitPreShared: + case XAUTHRespPreShared: + { + pb_stream *const hash_pbs = &md->chain[ISAKMP_NEXT_HASH]->pbs; + + if (pbs_left(hash_pbs) != hash_len + || memcmp(hash_pbs->cur, hash_val, hash_len) != 0) + { + DBG_cond_dump(DBG_CRYPT, "received HASH:" + , hash_pbs->cur, pbs_left(hash_pbs)); + loglog(RC_LOG_SERIOUS, "received Hash Payload does not match computed value"); + /* XXX Could send notification back */ + r = STF_FAIL + INVALID_HASH_INFORMATION; + } + } + break; + + case OAKLEY_RSA_SIG: + case XAUTHInitRSA: + case XAUTHRespRSA: + r = RSA_check_signature(&peer, st, hash_val, hash_len + , &md->chain[ISAKMP_NEXT_SIG]->pbs +#ifdef USE_KEYRR + , kc == NULL? NULL : kc->ac.keys_from_dns +#endif /* USE_KEYRR */ + , kc == NULL? NULL : kc->ac.gateways_from_dns + ); + + if (r == STF_SUSPEND) + { + /* initiate/resume asynchronous DNS lookup for key */ + struct key_continuation *nkc + = alloc_thing(struct key_continuation, "key continuation"); + enum key_oppo_step step_done = kc == NULL? kos_null : kc->step; + err_t ugh; + + /* Record that state is used by a suspended md */ + passert(st->st_suspended_md == NULL); + st->st_suspended_md = md; + + nkc->failure_ok = FALSE; + nkc->md = md; + + switch (step_done) + { + case kos_null: + /* first try: look for the TXT records */ + nkc->step = kos_his_txt; +#ifdef USE_KEYRR + nkc->failure_ok = TRUE; +#endif + ugh = start_adns_query(&peer + , &peer /* SG itself */ + , T_TXT + , cont_fn + , &nkc->ac); + break; + +#ifdef USE_KEYRR + case kos_his_txt: + /* second try: look for the KEY records */ + nkc->step = kos_his_key; + ugh = start_adns_query(&peer + , NULL /* no sgw for KEY */ + , T_KEY + , cont_fn + , &nkc->ac); + break; +#endif /* USE_KEYRR */ + + default: + bad_case(step_done); + } + + if (ugh != NULL) + { + report_key_dns_failure(&peer, ugh); + st->st_suspended_md = NULL; + r = STF_FAIL + INVALID_KEY_INFORMATION; + } + } + break; + + default: + bad_case(st->st_oakley.auth); + } + if (r != STF_OK) + return r; + + DBG(DBG_CRYPT, DBG_log("authentication succeeded")); + + /* + * With the peer ID known, let's see if we need to switch connections. + */ + if (!switch_connection(md, &peer, initiator)) + return STF_FAIL + INVALID_ID_INFORMATION; + + return r; +} + +/* This continuation is called as part of either + * the main_inI3_outR3 state or main_inR3 state. + * + * The "tail" function is the corresponding tail + * function main_inI3_outR3_tail | main_inR3_tail, + * either directly when the state is started, or via + * adns continuation. + * + * Basically, we go around in a circle: + * main_in?3* -> key_continue + * ^ \ + * / V + * adns main_in?3*_tail + * ^ | + * \ V + * main_id_and_auth + * + * until such time as main_id_and_auth is able + * to find authentication, or we run out of things + * to try. + */ +static void +key_continue(struct adns_continuation *cr +, err_t ugh +, key_tail_fn *tail) +{ + struct key_continuation *kc = (void *)cr; + struct state *st = kc->md->st; + + passert(cur_state == NULL); + + /* if st == NULL, our state has been deleted -- just clean up */ + if (st != NULL) + { + stf_status r; + + passert(st->st_suspended_md == kc->md); + st->st_suspended_md = NULL; /* no longer connected or suspended */ + cur_state = st; + + if (!kc->failure_ok && ugh != NULL) + { + report_key_dns_failure(&st->st_connection->spd.that.id, ugh); + r = STF_FAIL + INVALID_KEY_INFORMATION; + } + else + { + +#ifdef USE_KEYRR + passert(kc->step == kos_his_txt || kc->step == kos_his_key); +#else + passert(kc->step == kos_his_txt); +#endif + kc->last_ugh = ugh; /* record previous error in case we need it */ + r = (*tail)(kc->md, kc); + } + complete_state_transition(&kc->md, r); + } + if (kc->md != NULL) + release_md(kc->md); + cur_state = NULL; +} + +/* STATE_MAIN_R2: + * PSK_AUTH: HDR*, IDi1, HASH_I --> HDR*, IDr1, HASH_R + * DS_AUTH: HDR*, IDi1, [ CERT, ] SIG_I --> HDR*, IDr1, [ CERT, ] SIG_R + * PKE_AUTH, RPKE_AUTH: HDR*, HASH_I --> HDR*, HASH_R + * + * Broken into parts to allow asynchronous DNS lookup. + * + * - main_inI3_outR3 to start + * - main_inI3_outR3_tail to finish or suspend for DNS lookup + * - main_inI3_outR3_continue to start main_inI3_outR3_tail again + */ +static key_tail_fn main_inI3_outR3_tail; /* forward */ + +stf_status +main_inI3_outR3(struct msg_digest *md) +{ + return main_inI3_outR3_tail(md, NULL); +} + +static void +main_inI3_outR3_continue(struct adns_continuation *cr, err_t ugh) +{ + key_continue(cr, ugh, main_inI3_outR3_tail); +} + +static stf_status +main_inI3_outR3_tail(struct msg_digest *md +, struct key_continuation *kc) +{ + struct state *const st = md->st; + u_int8_t auth_payload; + pb_stream r_id_pbs; /* ID Payload; also used for hash calculation */ + certpolicy_t cert_policy; + cert_t mycert; + bool RSA_auth; + bool send_cert; + bool requested; + + /* ID and HASH_I or SIG_I in + * Note: this may switch the connection being used! + */ + { + stf_status r = main_id_and_auth(md, FALSE + , main_inI3_outR3_continue + , kc); + + if (r != STF_OK) + return r; + } + + /* send certificate if auth is RSA, we have one and we want + * or are requested to send it + */ + cert_policy = st->st_connection->spd.this.sendcert; + mycert = st->st_connection->spd.this.cert; + requested = cert_policy == CERT_SEND_IF_ASKED + && st->st_connection->got_certrequest; + RSA_auth = st->st_oakley.auth == OAKLEY_RSA_SIG + || st->st_oakley.auth == XAUTHInitRSA + || st->st_oakley.auth == XAUTHRespRSA; + send_cert = RSA_auth + && mycert.type != CERT_NONE + && (cert_policy == CERT_ALWAYS_SEND || requested); + + /*************** build output packet HDR*;IDir;HASH/SIG_R ***************/ + /* proccess_packet() would automatically generate the HDR* + * payload if smc->first_out_payload is not ISAKMP_NEXT_NONE. + * We don't do this because we wish there to be no partially + * built output packet if we need to suspend for asynch DNS. + */ + /* ??? NOTE: this is almost the same as main_inR2_outI3's code */ + + /* HDR* out + * If auth were PKE_AUTH or RPKE_AUTH, ISAKMP_NEXT_HASH would + * be first payload. + */ + echo_hdr(md, TRUE, ISAKMP_NEXT_ID); + + auth_payload = RSA_auth ? ISAKMP_NEXT_SIG : ISAKMP_NEXT_HASH; + + /* IDir out */ + { + /* id_hd should be struct isakmp_id, but struct isakmp_ipsec_id + * allows build_id_payload() to work for both phases. + */ + struct isakmp_ipsec_id id_hd; + chunk_t id_b; + + build_id_payload(&id_hd, &id_b, &st->st_connection->spd.this); + id_hd.isaiid_np = (send_cert)? ISAKMP_NEXT_CERT : auth_payload; + if (!out_struct(&id_hd, &isakmp_ipsec_identification_desc, &md->rbody, &r_id_pbs) + || !out_chunk(id_b, &r_id_pbs, "my identity")) + return STF_INTERNAL_ERROR; + close_output_pbs(&r_id_pbs); + } + + /* CERT out */ + if (RSA_auth) + { + DBG(DBG_CONTROL, + DBG_log("our certificate policy is %s" + , enum_name(&cert_policy_names, cert_policy)) + ) + if (mycert.type != CERT_NONE) + { + const char *request_text = ""; + + if (cert_policy == CERT_SEND_IF_ASKED) + request_text = (send_cert)? "upon request":"without request"; + plog("we have a cert %s sending it %s" + , send_cert? "and are":"but are not", request_text); + } + else + { + plog("we don't have a cert"); + } + } + if (send_cert) + { + pb_stream cert_pbs; + + struct isakmp_cert cert_hd; + cert_hd.isacert_np = ISAKMP_NEXT_SIG; + cert_hd.isacert_type = mycert.type; + + if (!out_struct(&cert_hd, &isakmp_ipsec_certificate_desc, &md->rbody, &cert_pbs)) + return STF_INTERNAL_ERROR; + if (!out_chunk(get_mycert(mycert), &cert_pbs, "CERT")) + return STF_INTERNAL_ERROR; + close_output_pbs(&cert_pbs); + } + + /* HASH_R or SIG_R out */ + { + u_char hash_val[MAX_DIGEST_LEN]; + size_t hash_len = main_mode_hash(st, hash_val, FALSE, &r_id_pbs); + + if (auth_payload == ISAKMP_NEXT_HASH) + { + /* HASH_R out */ + if (!out_generic_raw(ISAKMP_NEXT_NONE, &isakmp_hash_desc, &md->rbody + , hash_val, hash_len, "HASH_R")) + return STF_INTERNAL_ERROR; + } + else + { + /* SIG_R out */ + u_char sig_val[RSA_MAX_OCTETS]; + size_t sig_len = RSA_sign_hash(st->st_connection + , sig_val, hash_val, hash_len); + + if (sig_len == 0) + { + loglog(RC_LOG_SERIOUS, "unable to locate my private key for RSA Signature"); + return STF_FAIL + AUTHENTICATION_FAILED; + } + + if (!out_generic_raw(ISAKMP_NEXT_NONE, &isakmp_signature_desc + , &md->rbody, sig_val, sig_len, "SIG_R")) + return STF_INTERNAL_ERROR; + } + } + + /* encrypt message, sans fixed part of header */ + + if (!encrypt_message(&md->rbody, st)) + return STF_INTERNAL_ERROR; /* ??? we may be partly committed */ + + /* Last block of Phase 1 (R3), kept for Phase 2 IV generation */ + DBG_cond_dump(DBG_CRYPT, "last encrypted block of Phase 1:" + , st->st_new_iv, st->st_new_iv_len); + + ISAKMP_SA_established(st->st_connection, st->st_serialno); + + /* Save Phase 1 IV */ + st->st_ph1_iv_len = st->st_new_iv_len; + set_ph1_iv(st, st->st_new_iv); + + return STF_OK; +} + +/* STATE_MAIN_I3: + * Handle HDR*;IDir;HASH/SIG_R from responder. + * + * Broken into parts to allow asynchronous DNS for KEY records. + * + * - main_inR3 to start + * - main_inR3_tail to finish or suspend for DNS lookup + * - main_inR3_continue to start main_inR3_tail again + */ + +static key_tail_fn main_inR3_tail; /* forward */ + +stf_status +main_inR3(struct msg_digest *md) +{ + return main_inR3_tail(md, NULL); +} + +static void +main_inR3_continue(struct adns_continuation *cr, err_t ugh) +{ + key_continue(cr, ugh, main_inR3_tail); +} + +static stf_status +main_inR3_tail(struct msg_digest *md +, struct key_continuation *kc) +{ + struct state *const st = md->st; + + /* ID and HASH_R or SIG_R in + * Note: this may switch the connection being used! + */ + { + stf_status r = main_id_and_auth(md, TRUE, main_inR3_continue, kc); + + if (r != STF_OK) + return r; + } + + /**************** done input ****************/ + + ISAKMP_SA_established(st->st_connection, st->st_serialno); + + /* Save Phase 1 IV */ + st->st_ph1_iv_len = st->st_new_iv_len; + set_ph1_iv(st, st->st_new_iv); + + + update_iv(st); /* finalize our Phase 1 IV */ + + return STF_OK; +} + +/* Handle first message of Phase 2 -- Quick Mode. + * HDR*, HASH(1), SA, Ni [, KE ] [, IDci, IDcr ] --> + * HDR*, HASH(2), SA, Nr [, KE ] [, IDci, IDcr ] + * (see RFC 2409 "IKE" 5.5) + * Installs inbound IPsec SAs. + * Although this seems early, we know enough to do so, and + * this way we know that it is soon enough to catch all + * packets that other side could send using this IPsec SA. + * + * Broken into parts to allow asynchronous DNS for TXT records: + * + * - quick_inI1_outR1 starts the ball rolling. + * It checks and parses enough to learn the Phase 2 IDs + * + * - quick_inI1_outR1_tail does the rest of the job + * unless DNS must be consulted. In that case, + * it starts a DNS query, salts away what is needed + * to continue, and suspends. Calls + * + quick_inI1_outR1_start_query + * + quick_inI1_outR1_process_answer + * + * - quick_inI1_outR1_continue will restart quick_inI1_outR1_tail + * when DNS comes back with an answer. + * + * A big chunk of quick_inI1_outR1_tail is executed twice. + * This is necessary because the set of connections + * might change while we are awaiting DNS. + * When first called, gateways_from_dns == NULL. If DNS is + * consulted asynchronously, gateways_from_dns != NULL the second time. + * Remember that our state object might disappear too! + * + * + * If the connection is opportunistic, we must verify delegation. + * + * 1. Check that we are authorized to be SG for + * our client. We look for the TXT record that + * delegates us. We also check that the public + * key (if present) matches the private key we used. + * Eventually, we should probably require DNSsec + * authentication for our side. + * + * 2. If our client TXT record did not include a + * public key, check the KEY record indicated + * by the identity in the TXT record. + * + * 3. If the peer's client is the peer itself, we + * consider it authenticated. Otherwise, we check + * the TXT record for the client to see that + * the identity of the SG matches the peer and + * that some public key (if present in the TXT) + * matches. We need not check the public key if + * it isn't in the TXT record. + * + * Since p isn't yet instantiated, we need to look + * in c for description of peer. + * + * We cannot afford to block waiting for a DNS query. + * The code here is structured as two halves: + * - process the result of just completed + * DNS query (if any) + * - if another query is needed, initiate the next + * DNS query and suspend + */ + +enum verify_oppo_step { + vos_fail, + vos_start, + vos_our_client, + vos_our_txt, +#ifdef USE_KEYRR + vos_our_key, +#endif /* USE_KEYRR */ + vos_his_client, + vos_done +}; + +static const char *const verify_step_name[] = { + "vos_fail", + "vos_start", + "vos_our_client", + "vos_our_txt", +#ifdef USE_KEYRR + "vos_our_key", +#endif /* USE_KEYRR */ + "vos_his_client", + "vos_done" +}; + +/* hold anything we can handle of a Phase 2 ID */ +struct p2id { + ip_subnet net; + u_int8_t proto; + u_int16_t port; +}; + +struct verify_oppo_bundle { + enum verify_oppo_step step; + bool failure_ok; /* if true, quick_inI1_outR1_continue will try + * other things on DNS failure */ + struct msg_digest *md; + struct p2id my, his; + unsigned int new_iv_len; /* p1st's might change */ + u_char new_iv[MAX_DIGEST_LEN]; + /* int whackfd; */ /* not needed because we are Responder */ +}; + +struct verify_oppo_continuation { + struct adns_continuation ac; /* common prefix */ + struct verify_oppo_bundle b; +}; + +static stf_status quick_inI1_outR1_tail(struct verify_oppo_bundle *b + , struct adns_continuation *ac); + +stf_status +quick_inI1_outR1(struct msg_digest *md) +{ + const struct state *const p1st = md->st; + struct connection *c = p1st->st_connection; + struct payload_digest *const id_pd = md->chain[ISAKMP_NEXT_ID]; + struct verify_oppo_bundle b; + + /* HASH(1) in */ + CHECK_QUICK_HASH(md + , quick_mode_hash12(hash_val, hash_pbs->roof, md->message_pbs.roof + , p1st, &md->hdr.isa_msgid, FALSE) + , "HASH(1)", "Quick I1"); + + /* [ IDci, IDcr ] in + * We do this now (probably out of physical order) because + * we wish to select the correct connection before we consult + * it for policy. + */ + + if (id_pd != NULL) + { + /* ??? we are assuming IPSEC_DOI */ + + /* IDci (initiator is peer) */ + + if (!decode_net_id(&id_pd->payload.ipsec_id, &id_pd->pbs + , &b.his.net, "peer client")) + return STF_FAIL + INVALID_ID_INFORMATION; + + /* Hack for MS 818043 NAT-T Update */ + + if (id_pd->payload.ipsec_id.isaiid_idtype == ID_FQDN) + happy(addrtosubnet(&c->spd.that.host_addr, &b.his.net)); + + /* End Hack for MS 818043 NAT-T Update */ + + b.his.proto = id_pd->payload.ipsec_id.isaiid_protoid; + b.his.port = id_pd->payload.ipsec_id.isaiid_port; + b.his.net.addr.u.v4.sin_port = htons(b.his.port); + + /* IDcr (we are responder) */ + + if (!decode_net_id(&id_pd->next->payload.ipsec_id, &id_pd->next->pbs + , &b.my.net, "our client")) + return STF_FAIL + INVALID_ID_INFORMATION; + + b.my.proto = id_pd->next->payload.ipsec_id.isaiid_protoid; + b.my.port = id_pd->next->payload.ipsec_id.isaiid_port; + b.my.net.addr.u.v4.sin_port = htons(b.my.port); + } + else + { + /* implicit IDci and IDcr: peer and self */ + if (!sameaddrtype(&c->spd.this.host_addr, &c->spd.that.host_addr)) + return STF_FAIL; + + happy(addrtosubnet(&c->spd.this.host_addr, &b.my.net)); + happy(addrtosubnet(&c->spd.that.host_addr, &b.his.net)); + b.his.proto = b.my.proto = 0; + b.his.port = b.my.port = 0; + } + b.step = vos_start; + b.md = md; + b.new_iv_len = p1st->st_new_iv_len; + memcpy(b.new_iv, p1st->st_new_iv, p1st->st_new_iv_len); + return quick_inI1_outR1_tail(&b, NULL); +} + +static void +report_verify_failure(struct verify_oppo_bundle *b, err_t ugh) +{ + struct state *st = b->md->st; + char fgwb[ADDRTOT_BUF] + , cb[ADDRTOT_BUF]; + ip_address client; + err_t which; + + switch (b->step) + { + case vos_our_client: + case vos_our_txt: +#ifdef USE_KEYRR + case vos_our_key: +#endif /* USE_KEYRR */ + which = "our"; + networkof(&b->my.net, &client); + break; + + case vos_his_client: + which = "his"; + networkof(&b->his.net, &client); + break; + + case vos_start: + case vos_done: + case vos_fail: + default: + bad_case(b->step); + } + + addrtot(&st->st_connection->spd.that.host_addr, 0, fgwb, sizeof(fgwb)); + addrtot(&client, 0, cb, sizeof(cb)); + loglog(RC_OPPOFAILURE + , "gateway %s wants connection with %s as %s client, but DNS fails to confirm delegation: %s" + , fgwb, cb, which, ugh); +} + +static void +quick_inI1_outR1_continue(struct adns_continuation *cr, err_t ugh) +{ + stf_status r; + struct verify_oppo_continuation *vc = (void *)cr; + struct verify_oppo_bundle *b = &vc->b; + struct state *st = b->md->st; + + passert(cur_state == NULL); + /* if st == NULL, our state has been deleted -- just clean up */ + if (st != NULL) + { + passert(st->st_suspended_md == b->md); + st->st_suspended_md = NULL; /* no longer connected or suspended */ + cur_state = st; + if (!b->failure_ok && ugh != NULL) + { + report_verify_failure(b, ugh); + r = STF_FAIL + INVALID_ID_INFORMATION; + } + else + { + r = quick_inI1_outR1_tail(b, cr); + } + complete_state_transition(&b->md, r); + } + if (b->md != NULL) + release_md(b->md); + cur_state = NULL; +} + +static stf_status +quick_inI1_outR1_start_query(struct verify_oppo_bundle *b +, enum verify_oppo_step next_step) +{ + struct msg_digest *md = b->md; + struct state *p1st = md->st; + struct connection *c = p1st->st_connection; + struct verify_oppo_continuation *vc + = alloc_thing(struct verify_oppo_continuation, "verify continuation"); + struct id id /* subject of query */ + , *our_id /* needed for myid playing */ + , our_id_space; /* ephemeral: no need for unshare_id_content */ + ip_address client; + err_t ugh; + + /* Record that state is used by a suspended md */ + b->step = next_step; /* not just vc->b.step */ + vc->b = *b; + passert(p1st->st_suspended_md == NULL); + p1st->st_suspended_md = b->md; + + DBG(DBG_CONTROL, + { + char ours[SUBNETTOT_BUF]; + char his[SUBNETTOT_BUF]; + + subnettot(&c->spd.this.client, 0, ours, sizeof(ours)); + subnettot(&c->spd.that.client, 0, his, sizeof(his)); + + DBG_log("responding with DNS query - from %s to %s new state: %s" + , ours, his, verify_step_name[b->step]); + }); + + /* Resolve %myid in a cheesy way. + * We have to do the resolution because start_adns_query + * et al have insufficient information to do so. + * If %myid is already known, we'll use that value + * (XXX this may be a mistake: it could be stale). + * If %myid is unknown, we should check to see if + * there are credentials for the IP address or the FQDN. + * Instead, we'll just assume the IP address since we are + * acting as the responder and only the IP address would + * have gotten it to us. + * We don't even try to do this for the other side: + * %myid makes no sense for the other side (but it is syntactically + * legal). + */ + our_id = resolve_myid(&c->spd.this.id); + if (our_id->kind == ID_NONE) + { + iptoid(&c->spd.this.host_addr, &our_id_space); + our_id = &our_id_space; + } + + switch (next_step) + { + case vos_our_client: + networkof(&b->my.net, &client); + iptoid(&client, &id); + vc->b.failure_ok = b->failure_ok = FALSE; + ugh = start_adns_query(&id + , our_id + , T_TXT + , quick_inI1_outR1_continue + , &vc->ac); + break; + + case vos_our_txt: + vc->b.failure_ok = b->failure_ok = TRUE; + ugh = start_adns_query(our_id + , our_id /* self as SG */ + , T_TXT + , quick_inI1_outR1_continue + , &vc->ac); + break; + +#ifdef USE_KEYRR + case vos_our_key: + vc->b.failure_ok = b->failure_ok = FALSE; + ugh = start_adns_query(our_id + , NULL + , T_KEY + , quick_inI1_outR1_continue + , &vc->ac); + break; +#endif + + case vos_his_client: + networkof(&b->his.net, &client); + iptoid(&client, &id); + vc->b.failure_ok = b->failure_ok = FALSE; + ugh = start_adns_query(&id + , &c->spd.that.id + , T_TXT + , quick_inI1_outR1_continue + , &vc->ac); + break; + + default: + bad_case(next_step); + } + + if (ugh != NULL) + { + /* note: we'd like to use vc->b but vc has been freed + * so we have to use b. This is why we plunked next_state + * into b, not just vc->b. + */ + report_verify_failure(b, ugh); + p1st->st_suspended_md = NULL; + return STF_FAIL + INVALID_ID_INFORMATION; + } + else + { + return STF_SUSPEND; + } +} + +static enum verify_oppo_step +quick_inI1_outR1_process_answer(struct verify_oppo_bundle *b +, struct adns_continuation *ac +, struct state *p1st) +{ + struct connection *c = p1st->st_connection; + enum verify_oppo_step next_step; + err_t ugh = NULL; + + DBG(DBG_CONTROL, + { + char ours[SUBNETTOT_BUF]; + char his[SUBNETTOT_BUF]; + + subnettot(&c->spd.this.client, 0, ours, sizeof(ours)); + subnettot(&c->spd.that.client, 0, his, sizeof(his)); + DBG_log("responding on demand from %s to %s state: %s" + , ours, his, verify_step_name[b->step]); + }); + + /* process just completed DNS query (if any) */ + switch (b->step) + { + case vos_start: + /* no query to digest */ + next_step = vos_our_client; + break; + + case vos_our_client: + next_step = vos_his_client; + { + const struct RSA_private_key *pri = get_RSA_private_key(c); + struct gw_info *gwp; + + if (pri == NULL) + { + ugh = "we don't know our own key"; + break; + } + ugh = "our client does not delegate us as its Security Gateway"; + for (gwp = ac->gateways_from_dns; gwp != NULL; gwp = gwp->next) + { + ugh = "our client delegates us as its Security Gateway but with the wrong public key"; + /* If there is no key in the TXT record, + * we count it as a win, but we will have + * to separately fetch and check the KEY record. + * If there is a key from the TXT record, + * we count it as a win if we match the key. + */ + if (!gwp->gw_key_present) + { + next_step = vos_our_txt; + ugh = NULL; /* good! */ + break; + } + else if (same_RSA_public_key(&pri->pub, &gwp->key->u.rsa)) + { + ugh = NULL; /* good! */ + break; + } + } + } + break; + + case vos_our_txt: + next_step = vos_his_client; + { + const struct RSA_private_key *pri = get_RSA_private_key(c); + + if (pri == NULL) + { + ugh = "we don't know our own key"; + break; + } + { + struct gw_info *gwp; + + for (gwp = ac->gateways_from_dns; gwp != NULL; gwp = gwp->next) + { +#ifdef USE_KEYRR + /* not an error yet, because we have to check KEY RR as well */ + ugh = NULL; +#else + ugh = "our client delegation depends on our " RRNAME " record, but it has the wrong public key"; +#endif + if (gwp->gw_key_present + && same_RSA_public_key(&pri->pub, &gwp->key->u.rsa)) + { + ugh = NULL; /* good! */ + break; + } +#ifdef USE_KEYRR + next_step = vos_our_key; +#endif + } + } + } + break; + +#ifdef USE_KEYRR + case vos_our_key: + next_step = vos_his_client; + { + const struct RSA_private_key *pri = get_RSA_private_key(c); + + if (pri == NULL) + { + ugh = "we don't know our own key"; + break; + } + { + pubkey_list_t *kp; + + ugh = "our client delegation depends on our missing " RRNAME " record"; + for (kp = ac->keys_from_dns; kp != NULL; kp = kp->next) + { + ugh = "our client delegation depends on our " RRNAME " record, but it has the wrong public key"; + if (same_RSA_public_key(&pri->pub, &kp->key->u.rsa)) + { + /* do this only once a day */ + if (!logged_txt_warning) + { + loglog(RC_LOG_SERIOUS, "found KEY RR but not TXT RR. See http://www.freeswan.org/err/txt-change.html."); + logged_txt_warning = TRUE; + } + ugh = NULL; /* good! */ + break; + } + } + } + } + break; +#endif /* USE_KEYRR */ + + case vos_his_client: + next_step = vos_done; + { + struct gw_info *gwp; + + /* check that the public key that authenticated + * the ISAKMP SA (p1st) will do for this gateway. + */ + + ugh = "peer's client does not delegate to peer"; + for (gwp = ac->gateways_from_dns; gwp != NULL; gwp = gwp->next) + { + ugh = "peer and its client disagree about public key"; + /* If there is a key from the TXT record, + * we count it as a win if we match the key. + * If there was no key, we claim a match since + * it implies fetching a KEY from the same + * place we must have gotten it. + */ + if (!gwp->gw_key_present + || same_RSA_public_key(&p1st->st_peer_pubkey->u.rsa + , &gwp->key->u.rsa)) + { + ugh = NULL; /* good! */ + break; + } + } + } + break; + + default: + bad_case(b->step); + } + + if (ugh != NULL) + { + report_verify_failure(b, ugh); + next_step = vos_fail; + } + return next_step; +} + +static stf_status +quick_inI1_outR1_tail(struct verify_oppo_bundle *b +, struct adns_continuation *ac) +{ + struct msg_digest *md = b->md; + struct state *const p1st = md->st; + struct connection *c = p1st->st_connection; + struct payload_digest *const id_pd = md->chain[ISAKMP_NEXT_ID]; + ip_subnet *our_net = &b->my.net + , *his_net = &b->his.net; + + u_char /* set by START_HASH_PAYLOAD: */ + *r_hashval, /* where in reply to jam hash value */ + *r_hash_start; /* from where to start hashing */ + + /* Now that we have identities of client subnets, we must look for + * a suitable connection (our current one only matches for hosts). + */ + { + struct connection *p = find_client_connection(c + , our_net, his_net, b->my.proto, b->my.port, b->his.proto, b->his.port); + + if (p == NULL) + { + /* This message occurs in very puzzling circumstances + * so we must add as much information and beauty as we can. + */ + struct end + me = c->spd.this, + he = c->spd.that; + char buf[2*SUBNETTOT_BUF + 2*ADDRTOT_BUF + 2*BUF_LEN + 2*ADDRTOT_BUF + 12]; /* + 12 for separating */ + size_t l; + + me.client = *our_net; + me.has_client = !subnetisaddr(our_net, &me.host_addr); + me.protocol = b->my.proto; + me.port = b->my.port; + + he.client = *his_net; + he.has_client = !subnetisaddr(his_net, &he.host_addr); + he.protocol = b->his.proto; + he.port = b->his.port; + + l = format_end(buf, sizeof(buf), &me, NULL, TRUE, LEMPTY); + l += snprintf(buf + l, sizeof(buf) - l, "..."); + (void)format_end(buf + l, sizeof(buf) - l, &he, NULL, FALSE, LEMPTY); + plog("cannot respond to IPsec SA request" + " because no connection is known for %s" + , buf); + return STF_FAIL + INVALID_ID_INFORMATION; + } + else if (p != c) + { + /* We've got a better connection: it can support the + * specified clients. But it may need instantiation. + */ + if (p->kind == CK_TEMPLATE) + { + /* Yup, it needs instantiation. How much? + * Is it a Road Warrior connection (simple) + * or is it an Opportunistic connection (needing gw validation)? + */ + if (p->policy & POLICY_OPPO) + { + /* Opportunistic case: delegation must be verified. + * Here be dragons. + */ + enum verify_oppo_step next_step; + ip_address our_client, his_client; + + passert(subnetishost(our_net) && subnetishost(his_net)); + networkof(our_net, &our_client); + networkof(his_net, &his_client); + + next_step = quick_inI1_outR1_process_answer(b, ac, p1st); + if (next_step == vos_fail) + return STF_FAIL + INVALID_ID_INFORMATION; + + /* short circuit: if peer's client is self, + * accept that we've verified delegation in Phase 1 + */ + if (next_step == vos_his_client + && sameaddr(&c->spd.that.host_addr, &his_client)) + next_step = vos_done; + + /* the second chunk: initiate the next DNS query (if any) */ + DBG(DBG_CONTROL, + { + char ours[SUBNETTOT_BUF]; + char his[SUBNETTOT_BUF]; + + subnettot(&c->spd.this.client, 0, ours, sizeof(ours)); + subnettot(&c->spd.that.client, 0, his, sizeof(his)); + + DBG_log("responding on demand from %s to %s new state: %s" + , ours, his, verify_step_name[next_step]); + }); + + /* start next DNS query and suspend (if necessary) */ + if (next_step != vos_done) + return quick_inI1_outR1_start_query(b, next_step); + + /* Instantiate inbound Opportunistic connection, + * carrying over authenticated peer ID + * and filling in a few more details. + * We used to include gateways_from_dns, but that + * seems pointless at this stage of negotiation. + * We should record DNS sec use, if any -- belongs in + * state during perhaps. + */ + p = oppo_instantiate(p, &c->spd.that.host_addr, &c->spd.that.id + , NULL, &our_client, &his_client); + } + else + { + /* Plain Road Warrior: + * instantiate, carrying over authenticated peer ID + */ + p = rw_instantiate(p, &c->spd.that.host_addr, md->sender_port + , his_net, &c->spd.that.id); + } + } +#ifdef DEBUG + /* temporarily bump up cur_debugging to get "using..." message + * printed if we'd want it with new connection. + */ + { + lset_t old_cur_debugging = cur_debugging; + + cur_debugging |= p->extra_debugging; + DBG(DBG_CONTROL, DBG_log("using connection \"%s\"", p->name)); + cur_debugging = old_cur_debugging; + } +#endif + c = p; + } + /* fill in the client's true ip address/subnet */ + if (p->spd.that.has_client_wildcard) + { + p->spd.that.client = *his_net; + p->spd.that.has_client_wildcard = FALSE; + } + else if (is_virtual_connection(c)) + { + c->spd.that.client = *his_net; + c->spd.that.virt = NULL; + if (subnetishost(his_net) && addrinsubnet(&c->spd.that.host_addr, his_net)) + c->spd.that.has_client = FALSE; + } + + /* fill in the client's true port */ + if (p->spd.that.has_port_wildcard) + { + int port = htons(b->his.port); + + setportof(port, &p->spd.that.host_addr); + setportof(port, &p->spd.that.client.addr); + + p->spd.that.port = b->his.port; + p->spd.that.has_port_wildcard = FALSE; + } + } + + /* now that we are sure of our connection, create our new state */ + { + struct state *const st = duplicate_state(p1st); + + /* first: fill in missing bits of our new state object + * note: we don't copy over st_peer_pubkey, the public key + * that authenticated the ISAKMP SA. We only need it in this + * routine, so we can "reach back" to p1st to get it. + */ + + if (st->st_connection != c) + { + struct connection *t = st->st_connection; + + st->st_connection = c; + set_cur_connection(c); + connection_discard(t); + } + + st->st_try = 0; /* not our job to try again from start */ + + st->st_msgid = md->hdr.isa_msgid; + + st->st_new_iv_len = b->new_iv_len; + memcpy(st->st_new_iv, b->new_iv, b->new_iv_len); + + set_cur_state(st); /* (caller will reset) */ + md->st = st; /* feed back new state */ + + st->st_peeruserprotoid = b->his.proto; + st->st_peeruserport = b->his.port; + st->st_myuserprotoid = b->my.proto; + st->st_myuserport = b->my.port; + + insert_state(st); /* needs cookies, connection, and msgid */ + + /* copy the connection's + * IPSEC policy into our state. The ISAKMP policy is water under + * the bridge, I think. It will reflect the ISAKMP SA that we + * are using. + */ + st->st_policy = (p1st->st_policy & POLICY_ISAKMP_MASK) + | (c->policy & ~POLICY_ISAKMP_MASK); + + if (p1st->nat_traversal & NAT_T_DETECTED) + { + st->nat_traversal = p1st->nat_traversal; + nat_traversal_change_port_lookup(md, md->st); + } + else + { + st->nat_traversal = 0; + } + if ((st->nat_traversal & NAT_T_DETECTED) + && (st->nat_traversal & NAT_T_WITH_NATOA)) + { + nat_traversal_natoa_lookup(md); + } + + /* Start the output packet. + * + * proccess_packet() would automatically generate the HDR* + * payload if smc->first_out_payload is not ISAKMP_NEXT_NONE. + * We don't do this because we wish there to be no partially + * built output packet if we need to suspend for asynch DNS. + * + * We build the reply packet as we parse the message since + * the parse_ipsec_sa_body emits the reply SA + */ + + /* HDR* out */ + echo_hdr(md, TRUE, ISAKMP_NEXT_HASH); + + /* HASH(2) out -- first pass */ + START_HASH_PAYLOAD(md->rbody, ISAKMP_NEXT_SA); + + /* process SA (in and out) */ + { + struct payload_digest *const sapd = md->chain[ISAKMP_NEXT_SA]; + pb_stream r_sa_pbs; + struct isakmp_sa sa = sapd->payload.sa; + + /* sa header is unchanged -- except for np */ + sa.isasa_np = ISAKMP_NEXT_NONCE; + if (!out_struct(&sa, &isakmp_sa_desc, &md->rbody, &r_sa_pbs)) + return STF_INTERNAL_ERROR; + + /* parse and accept body */ + st->st_pfs_group = &unset_group; + RETURN_STF_FAILURE(parse_ipsec_sa_body(&sapd->pbs + , &sapd->payload.sa, &r_sa_pbs, FALSE, st)); + } + + passert(st->st_pfs_group != &unset_group); + + if ((st->st_policy & POLICY_PFS) && st->st_pfs_group == NULL) + { + loglog(RC_LOG_SERIOUS, "we require PFS but Quick I1 SA specifies no GROUP_DESCRIPTION"); + return STF_FAIL + NO_PROPOSAL_CHOSEN; /* ??? */ + } + + /* Ni in */ + RETURN_STF_FAILURE(accept_nonce(md, &st->st_ni, "Ni")); + + /* [ KE ] in (for PFS) */ + RETURN_STF_FAILURE(accept_PFS_KE(md, &st->st_gi, "Gi", "Quick Mode I1")); + + plog("responding to Quick Mode"); + + /**** finish reply packet: Nr [, KE ] [, IDci, IDcr ] ****/ + + /* Nr out */ + if (!build_and_ship_nonce(&st->st_nr, &md->rbody + , st->st_pfs_group != NULL? ISAKMP_NEXT_KE : id_pd != NULL? ISAKMP_NEXT_ID : ISAKMP_NEXT_NONE + , "Nr")) + return STF_INTERNAL_ERROR; + + /* [ KE ] out (for PFS) */ + + if (st->st_pfs_group != NULL) + { + if (!build_and_ship_KE(st, &st->st_gr, st->st_pfs_group + , &md->rbody, id_pd != NULL? ISAKMP_NEXT_ID : ISAKMP_NEXT_NONE)) + return STF_INTERNAL_ERROR; + + /* MPZ-Operations might be done after sending the packet... */ + compute_dh_shared(st, st->st_gi, st->st_pfs_group); + } + + /* [ IDci, IDcr ] out */ + if (id_pd != NULL) + { + struct isakmp_ipsec_id *p = (void *)md->rbody.cur; /* UGH! */ + + if (!out_raw(id_pd->pbs.start, pbs_room(&id_pd->pbs), &md->rbody, "IDci")) + return STF_INTERNAL_ERROR; + p->isaiid_np = ISAKMP_NEXT_ID; + + p = (void *)md->rbody.cur; /* UGH! */ + + if (!out_raw(id_pd->next->pbs.start, pbs_room(&id_pd->next->pbs), &md->rbody, "IDcr")) + return STF_INTERNAL_ERROR; + p->isaiid_np = ISAKMP_NEXT_NONE; + } + + if ((st->nat_traversal & NAT_T_WITH_NATOA) + && (st->nat_traversal & LELEM(NAT_TRAVERSAL_NAT_BHND_ME)) + && (st->st_esp.attrs.encapsulation == ENCAPSULATION_MODE_TRANSPORT)) + { + /** Send NAT-OA if our address is NATed and if we use Transport Mode */ + if (!nat_traversal_add_natoa(ISAKMP_NEXT_NONE, &md->rbody, md->st)) + { + return STF_INTERNAL_ERROR; + } + } + if ((st->nat_traversal & NAT_T_DETECTED) + && (st->st_esp.attrs.encapsulation == ENCAPSULATION_MODE_TRANSPORT) + && (c->spd.that.has_client)) + { + /** Remove client **/ + addrtosubnet(&c->spd.that.host_addr, &c->spd.that.client); + c->spd.that.has_client = FALSE; + } + + /* Compute reply HASH(2) and insert in output */ + (void)quick_mode_hash12(r_hashval, r_hash_start, md->rbody.cur + , st, &st->st_msgid, TRUE); + + /* Derive new keying material */ + compute_keymats(st); + + /* Tell the kernel to establish the new inbound SA + * (unless the commit bit is set -- which we don't support). + * We do this before any state updating so that + * failure won't look like success. + */ + if (!install_inbound_ipsec_sa(st)) + return STF_INTERNAL_ERROR; /* ??? we may be partly committed */ + + /* encrypt message, except for fixed part of header */ + + if (!encrypt_message(&md->rbody, st)) + return STF_INTERNAL_ERROR; /* ??? we may be partly committed */ + + return STF_OK; + } +} + +/* + * Initialize RFC 3706 Dead Peer Detection + */ +static void +dpd_init(struct state *st) +{ + struct state *p1st = find_state(st->st_icookie, st->st_rcookie + , &st->st_connection->spd.that.host_addr, 0); + + if (p1st == NULL) + loglog(RC_LOG_SERIOUS, "could not find phase 1 state for DPD"); + else if (p1st->st_dpd) + { + plog("Dead Peer Detection (RFC 3706) enabled"); + /* randomize the first DPD event */ + + event_schedule(EVENT_DPD + , (0.5 + rand()/(RAND_MAX + 1.E0)) * st->st_connection->dpd_delay + , st); + } +} + +/* Handle (the single) message from Responder in Quick Mode. + * HDR*, HASH(2), SA, Nr [, KE ] [, IDci, IDcr ] --> + * HDR*, HASH(3) + * (see RFC 2409 "IKE" 5.5) + * Installs inbound and outbound IPsec SAs, routing, etc. + */ +stf_status +quick_inR1_outI2(struct msg_digest *md) +{ + struct state *const st = md->st; + const struct connection *c = st->st_connection; + + /* HASH(2) in */ + CHECK_QUICK_HASH(md + , quick_mode_hash12(hash_val, hash_pbs->roof, md->message_pbs.roof + , st, &st->st_msgid, TRUE) + , "HASH(2)", "Quick R1"); + + /* SA in */ + { + struct payload_digest *const sa_pd = md->chain[ISAKMP_NEXT_SA]; + + RETURN_STF_FAILURE(parse_ipsec_sa_body(&sa_pd->pbs + , &sa_pd->payload.sa, NULL, TRUE, st)); + } + + /* Nr in */ + RETURN_STF_FAILURE(accept_nonce(md, &st->st_nr, "Nr")); + + /* [ KE ] in (for PFS) */ + RETURN_STF_FAILURE(accept_PFS_KE(md, &st->st_gr, "Gr", "Quick Mode R1")); + + if (st->st_pfs_group != NULL) + compute_dh_shared(st, st->st_gr, st->st_pfs_group); + + /* [ IDci, IDcr ] in; these must match what we sent */ + + { + struct payload_digest *const id_pd = md->chain[ISAKMP_NEXT_ID]; + + if (id_pd != NULL) + { + /* ??? we are assuming IPSEC_DOI */ + + /* IDci (we are initiator) */ + + if (!check_net_id(&id_pd->payload.ipsec_id, &id_pd->pbs + , &st->st_myuserprotoid, &st->st_myuserport + , &st->st_connection->spd.this.client + , "our client")) + return STF_FAIL + INVALID_ID_INFORMATION; + + /* IDcr (responder is peer) */ + + if (!check_net_id(&id_pd->next->payload.ipsec_id, &id_pd->next->pbs + , &st->st_peeruserprotoid, &st->st_peeruserport + , &st->st_connection->spd.that.client + , "peer client")) + return STF_FAIL + INVALID_ID_INFORMATION; + } + else + { + /* no IDci, IDcr: we must check that the defaults match our proposal */ + if (!subnetisaddr(&c->spd.this.client, &c->spd.this.host_addr) + || !subnetisaddr(&c->spd.that.client, &c->spd.that.host_addr)) + { + loglog(RC_LOG_SERIOUS, "IDci, IDcr payloads missing in message" + " but default does not match proposal"); + return STF_FAIL + INVALID_ID_INFORMATION; + } + } + } + + /* check the peer's group attributes */ + + { + const ietfAttrList_t *peer_list = NULL; + + get_peer_ca_and_groups(st->st_connection, &peer_list); + + if (!group_membership(peer_list, st->st_connection->name + , st->st_connection->spd.that.groups)) + { + char buf[BUF_LEN]; + + format_groups(st->st_connection->spd.that.groups, buf, BUF_LEN); + loglog(RC_LOG_SERIOUS, "peer is not member of one of the groups: %s" + , buf); + return STF_FAIL + INVALID_ID_INFORMATION; + } + } + + if ((st->nat_traversal & NAT_T_DETECTED) + && (st->nat_traversal & NAT_T_WITH_NATOA)) + { + nat_traversal_natoa_lookup(md); + } + + /* ??? We used to copy the accepted proposal into the state, but it was + * never used. From sa_pd->pbs.start, length pbs_room(&sa_pd->pbs). + */ + + /**************** build reply packet HDR*, HASH(3) ****************/ + + /* HDR* out done */ + + /* HASH(3) out -- since this is the only content, no passes needed */ + { + u_char /* set by START_HASH_PAYLOAD: */ + *r_hashval, /* where in reply to jam hash value */ + *r_hash_start; /* start of what is to be hashed */ + + START_HASH_PAYLOAD(md->rbody, ISAKMP_NEXT_NONE); + (void)quick_mode_hash3(r_hashval, st); + } + + /* Derive new keying material */ + compute_keymats(st); + + /* Tell the kernel to establish the inbound, outbound, and routing part + * of the new SA (unless the commit bit is set -- which we don't support). + * We do this before any state updating so that + * failure won't look like success. + */ + if (!install_ipsec_sa(st, TRUE)) + return STF_INTERNAL_ERROR; + + /* encrypt message, except for fixed part of header */ + + if (!encrypt_message(&md->rbody, st)) + return STF_INTERNAL_ERROR; /* ??? we may be partly committed */ + + { + DBG(DBG_CONTROLMORE, DBG_log("inR1_outI2: instance %s[%ld], setting newest_ipsec_sa to #%ld (was #%ld) (spd.eroute=#%ld)" + , st->st_connection->name + , st->st_connection->instance_serial + , st->st_serialno + , st->st_connection->newest_ipsec_sa + , st->st_connection->spd.eroute_owner)); + } + + st->st_connection->newest_ipsec_sa = st->st_serialno; + + /* note (presumed) success */ + if (c->gw_info != NULL) + c->gw_info->key->last_worked_time = now(); + + /* If we want DPD on this connection then initialize it */ + if (st->st_connection->dpd_action != DPD_ACTION_NONE) + dpd_init(st); + + return STF_OK; +} + +/* Handle last message of Quick Mode. + * HDR*, HASH(3) -> done + * (see RFC 2409 "IKE" 5.5) + * Installs outbound IPsec SAs, routing, etc. + */ +stf_status +quick_inI2(struct msg_digest *md) +{ + struct state *const st = md->st; + + /* HASH(3) in */ + CHECK_QUICK_HASH(md, quick_mode_hash3(hash_val, st) + , "HASH(3)", "Quick I2"); + + /* Tell the kernel to establish the outbound and routing part of the new SA + * (the previous state established inbound) + * (unless the commit bit is set -- which we don't support). + * We do this before any state updating so that + * failure won't look like success. + */ + if (!install_ipsec_sa(st, FALSE)) + return STF_INTERNAL_ERROR; + + { + DBG(DBG_CONTROLMORE, DBG_log("inI2: instance %s[%ld], setting newest_ipsec_sa to #%ld (was #%ld) (spd.eroute=#%ld)" + , st->st_connection->name + , st->st_connection->instance_serial + , st->st_serialno + , st->st_connection->newest_ipsec_sa + , st->st_connection->spd.eroute_owner)); + } + + st->st_connection->newest_ipsec_sa = st->st_serialno; + + update_iv(st); /* not actually used, but tidy */ + + /* note (presumed) success */ + { + struct gw_info *gw = st->st_connection->gw_info; + + if (gw != NULL) + gw->key->last_worked_time = now(); + } + + /* If we want DPD on this connection then initialize it */ + if (st->st_connection->dpd_action != DPD_ACTION_NONE) + dpd_init(st); + + return STF_OK; +} + +static stf_status +send_isakmp_notification(struct state *st, u_int16_t type + , const void *data, size_t len) +{ + msgid_t msgid; + pb_stream reply; + pb_stream rbody; + u_char + *r_hashval, /* where in reply to jam hash value */ + *r_hash_start; /* start of what is to be hashed */ + + msgid = generate_msgid(st); + + init_pbs(&reply, reply_buffer, sizeof(reply_buffer), "ISAKMP notify"); + + /* HDR* */ + { + struct isakmp_hdr hdr; + + hdr.isa_version = ISAKMP_MAJOR_VERSION << ISA_MAJ_SHIFT | ISAKMP_MINOR_VERSION; + hdr.isa_np = ISAKMP_NEXT_HASH; + hdr.isa_xchg = ISAKMP_XCHG_INFO; + hdr.isa_msgid = msgid; + hdr.isa_flags = ISAKMP_FLAG_ENCRYPTION; + memcpy(hdr.isa_icookie, st->st_icookie, COOKIE_SIZE); + memcpy(hdr.isa_rcookie, st->st_rcookie, COOKIE_SIZE); + if (!out_struct(&hdr, &isakmp_hdr_desc, &reply, &rbody)) + impossible(); + } + /* HASH -- create and note space to be filled later */ + START_HASH_PAYLOAD(rbody, ISAKMP_NEXT_N); + + /* NOTIFY */ + { + pb_stream notify_pbs; + struct isakmp_notification isan; + + isan.isan_np = ISAKMP_NEXT_NONE; + isan.isan_doi = ISAKMP_DOI_IPSEC; + isan.isan_protoid = PROTO_ISAKMP; + isan.isan_spisize = COOKIE_SIZE * 2; + isan.isan_type = type; + if (!out_struct(&isan, &isakmp_notification_desc, &rbody, ¬ify_pbs)) + return STF_INTERNAL_ERROR; + if (!out_raw(st->st_icookie, COOKIE_SIZE, ¬ify_pbs, "notify icookie")) + return STF_INTERNAL_ERROR; + if (!out_raw(st->st_rcookie, COOKIE_SIZE, ¬ify_pbs, "notify rcookie")) + return STF_INTERNAL_ERROR; + if (data != NULL && len > 0) + if (!out_raw(data, len, ¬ify_pbs, "notify data")) + return STF_INTERNAL_ERROR; + close_output_pbs(¬ify_pbs); + } + + { + /* finish computing HASH */ + struct hmac_ctx ctx; + hmac_init_chunk(&ctx, st->st_oakley.hasher, st->st_skeyid_a); + hmac_update(&ctx, (const u_char *) &msgid, sizeof(msgid_t)); + hmac_update(&ctx, r_hash_start, rbody.cur-r_hash_start); + hmac_final(r_hashval, &ctx); + + DBG(DBG_CRYPT, + DBG_log("HASH computed:"); + DBG_dump("", r_hashval, ctx.hmac_digest_size)); + } + + /* Encrypt message (preserve st_iv and st_new_iv) */ + { + u_char old_iv[MAX_DIGEST_LEN]; + u_char new_iv[MAX_DIGEST_LEN]; + + u_int old_iv_len = st->st_iv_len; + u_int new_iv_len = st->st_new_iv_len; + + if (old_iv_len > MAX_DIGEST_LEN || new_iv_len > MAX_DIGEST_LEN) + return STF_INTERNAL_ERROR; + + memcpy(old_iv, st->st_iv, old_iv_len); + memcpy(new_iv, st->st_new_iv, new_iv_len); + + init_phase2_iv(st, &msgid); + if (!encrypt_message(&rbody, st)) + return STF_INTERNAL_ERROR; + + /* restore preserved st_iv and st_new_iv */ + memcpy(st->st_iv, old_iv, old_iv_len); + memcpy(st->st_new_iv, new_iv, new_iv_len); + st->st_iv_len = old_iv_len; + st->st_new_iv_len = new_iv_len; + } + + /* Send packet (preserve st_tpacket) */ + { + chunk_t saved_tpacket = st->st_tpacket; + + setchunk(st->st_tpacket, reply.start, pbs_offset(&reply)); + send_packet(st, "ISAKMP notify"); + st->st_tpacket = saved_tpacket; + } + + return STF_IGNORE; +} + +/* + * DPD Out Initiator + */ +void +dpd_outI(struct state *p2st) +{ + struct state *st; + u_int32_t seqno; + time_t tm; + time_t idle_time; + time_t delay = p2st->st_connection->dpd_delay; + time_t timeout = p2st->st_connection->dpd_timeout; + + /* find the newest related Phase 1 state */ + st = find_phase1_state(p2st->st_connection, ISAKMP_SA_ESTABLISHED_STATES); + + if (st == NULL) + { + loglog(RC_LOG_SERIOUS, "DPD: Could not find newest phase 1 state"); + return; + } + + /* If no DPD, then get out of here */ + if (!st->st_dpd) + return; + + /* schedule the next periodic DPD event */ + event_schedule(EVENT_DPD, delay, p2st); + + /* Current time */ + tm = now(); + + /* Make sure we really need to invoke DPD */ + if (!was_eroute_idle(p2st, delay, &idle_time)) + { + DBG(DBG_CONTROL, + DBG_log("recent eroute activity %u seconds ago, " + "no need to send DPD notification" + , (int)idle_time) + ) + st->st_last_dpd = tm; + delete_dpd_event(st); + return; + } + + /* If an R_U_THERE has been sent or received recently, or if a + * companion Phase 2 SA has shown eroute activity, + * then we don't need to invoke DPD. + */ + if (tm < st->st_last_dpd + delay) + { + DBG(DBG_CONTROL, + DBG_log("recent DPD activity %u seconds ago, " + "no need to send DPD notification" + , (int)(tm - st->st_last_dpd)) + ) + return; + } + + if (!IS_ISAKMP_SA_ESTABLISHED(st->st_state)) + return; + + if (!st->st_dpd_seqno) + { + /* Get a non-zero random value that has room to grow */ + get_rnd_bytes((u_char *)&st->st_dpd_seqno, sizeof(st->st_dpd_seqno)); + st->st_dpd_seqno &= 0x7fff; + st->st_dpd_seqno++; + } + seqno = htonl(st->st_dpd_seqno); + + if (send_isakmp_notification(st, R_U_THERE, &seqno, sizeof(seqno)) != STF_IGNORE) + { + loglog(RC_LOG_SERIOUS, "DPD: Could not send R_U_THERE"); + return; + } + DBG(DBG_CONTROL, + DBG_log("sent DPD notification R_U_THERE with seqno = %u", st->st_dpd_seqno) + ) + st->st_dpd_expectseqno = st->st_dpd_seqno++; + st->st_last_dpd = tm; + /* Only schedule a new timeout if there isn't one currently, + * or if it would be sooner than the current timeout. */ + if (st->st_dpd_event == NULL + || st->st_dpd_event->ev_time > tm + timeout) + { + delete_dpd_event(st); + event_schedule(EVENT_DPD_TIMEOUT, timeout, st); + } +} + +/* + * DPD in Initiator, out Responder + */ +stf_status +dpd_inI_outR(struct state *st, struct isakmp_notification *const n, pb_stream *pbs) +{ + time_t tm = now(); + u_int32_t seqno; + + if (!IS_ISAKMP_SA_ESTABLISHED(st->st_state)) + { + loglog(RC_LOG_SERIOUS, "DPD: Received R_U_THERE for unestablished ISKAMP SA"); + return STF_IGNORE; + } + if (n->isan_spisize != COOKIE_SIZE * 2 || pbs_left(pbs) < COOKIE_SIZE * 2) + { + loglog(RC_LOG_SERIOUS, "DPD: R_U_THERE has invalid SPI length (%d)", n->isan_spisize); + return STF_FAIL + PAYLOAD_MALFORMED; + } + + if (memcmp(pbs->cur, st->st_icookie, COOKIE_SIZE) != 0) + { +#ifdef APPLY_CRISCO + /* Ignore it, cisco sends odd icookies */ +#else + loglog(RC_LOG_SERIOUS, "DPD: R_U_THERE has invalid icookie (broken Cisco?)"); + return STF_FAIL + INVALID_COOKIE; +#endif + } + pbs->cur += COOKIE_SIZE; + + if (memcmp(pbs->cur, st->st_rcookie, COOKIE_SIZE) != 0) + { + loglog(RC_LOG_SERIOUS, "DPD: R_U_THERE has invalid rcookie (broken Cisco?)"); + return STF_FAIL + INVALID_COOKIE; + } + pbs->cur += COOKIE_SIZE; + + if (pbs_left(pbs) != sizeof(seqno)) + { + loglog(RC_LOG_SERIOUS, "DPD: R_U_THERE has invalid data length (%d)" + , (int) pbs_left(pbs)); + return STF_FAIL + PAYLOAD_MALFORMED; + } + + seqno = ntohl(*(u_int32_t *)pbs->cur); + DBG(DBG_CONTROL, + DBG_log("received DPD notification R_U_THERE with seqno = %u", seqno) + ) + + if (st->st_dpd_peerseqno && seqno <= st->st_dpd_peerseqno) { + loglog(RC_LOG_SERIOUS, "DPD: Received old or duplicate R_U_THERE"); + return STF_IGNORE; + } + + st->st_dpd_peerseqno = seqno; + delete_dpd_event(st); + + if (send_isakmp_notification(st, R_U_THERE_ACK, pbs->cur, pbs_left(pbs)) != STF_IGNORE) + { + loglog(RC_LOG_SERIOUS, "DPD Info: could not send R_U_THERE_ACK"); + return STF_IGNORE; + } + DBG(DBG_CONTROL, + DBG_log("sent DPD notification R_U_THERE_ACK with seqno = %u", seqno) + ) + + st->st_last_dpd = tm; + return STF_IGNORE; +} + +/* + * DPD out Responder + */ +stf_status +dpd_inR(struct state *st, struct isakmp_notification *const n, pb_stream *pbs) +{ + u_int32_t seqno; + + if (!IS_ISAKMP_SA_ESTABLISHED(st->st_state)) + { + loglog(RC_LOG_SERIOUS + , "DPD: Received R_U_THERE_ACK for unestablished ISKAMP SA"); + return STF_FAIL; + } + + if (n->isan_spisize != COOKIE_SIZE * 2 || pbs_left(pbs) < COOKIE_SIZE * 2) + { + loglog(RC_LOG_SERIOUS + , "DPD: R_U_THERE_ACK has invalid SPI length (%d)" + , n->isan_spisize); + return STF_FAIL + PAYLOAD_MALFORMED; + } + + if (memcmp(pbs->cur, st->st_icookie, COOKIE_SIZE) != 0) + { +#ifdef APPLY_CRISCO + /* Ignore it, cisco sends odd icookies */ +#else + loglog(RC_LOG_SERIOUS, "DPD: R_U_THERE_ACK has invalid icookie"); + return STF_FAIL + INVALID_COOKIE; +#endif + } + pbs->cur += COOKIE_SIZE; + + if (memcmp(pbs->cur, st->st_rcookie, COOKIE_SIZE) != 0) + { +#ifdef APPLY_CRISCO + /* Ignore it, cisco sends odd icookies */ +#else + loglog(RC_LOG_SERIOUS, "DPD: R_U_THERE_ACK has invalid rcookie"); + return STF_FAIL + INVALID_COOKIE; +#endif + } + pbs->cur += COOKIE_SIZE; + + if (pbs_left(pbs) != sizeof(seqno)) + { + loglog(RC_LOG_SERIOUS + , " DPD: R_U_THERE_ACK has invalid data length (%d)" + , (int) pbs_left(pbs)); + return STF_FAIL + PAYLOAD_MALFORMED; + } + + seqno = ntohl(*(u_int32_t *)pbs->cur); + DBG(DBG_CONTROL, + DBG_log("received DPD notification R_U_THERE_ACK with seqno = %u" + , seqno) + ) + + if (!st->st_dpd_expectseqno && seqno != st->st_dpd_expectseqno) + { + loglog(RC_LOG_SERIOUS + , "DPD: R_U_THERE_ACK has unexpected sequence number"); + return STF_FAIL + PAYLOAD_MALFORMED; + } + + st->st_dpd_expectseqno = 0; + delete_dpd_event(st); + return STF_IGNORE; +} + +/* + * DPD Timeout Function + * + * This function is called when a timeout DPD_EVENT occurs. We set clear/trap + * both the SA and the eroutes, depending on what the connection definition + * tells us (either 'hold' or 'clear') + */ +void +dpd_timeout(struct state *st) +{ + struct state *newest_phase1_st; + struct connection *c = st->st_connection; + int action = st->st_connection->dpd_action; + + passert(action == DPD_ACTION_HOLD + || action == DPD_ACTION_CLEAR + || DPD_ACTION_RESTART); + + /* is there a newer phase1_state? */ + newest_phase1_st = find_phase1_state(c, ISAKMP_SA_ESTABLISHED_STATES); + if (newest_phase1_st != NULL && newest_phase1_st != st) + { + plog("DPD: Phase1 state #%ld has been superseded by #%ld" + " - timeout ignored" + , st->st_serialno, newest_phase1_st->st_serialno); + return; + } + + loglog(RC_LOG_SERIOUS, "DPD: No response from peer - declaring peer dead"); + + /* delete the state, which is probably in phase 2 */ + set_cur_connection(c); + plog("DPD: Terminating all SAs using this connection"); + delete_states_by_connection(c, TRUE); + reset_cur_connection(); + + switch (action) + { + case DPD_ACTION_HOLD: + /* dpdaction=hold - Wipe the SA's but %trap the eroute so we don't + * leak traffic. Also, being in %trap means new packets will + * force an initiation of the conn again. + */ + loglog(RC_LOG_SERIOUS, "DPD: Putting connection into %%trap"); + break; + case DPD_ACTION_CLEAR: + /* dpdaction=clear - Wipe the SA & eroute - everything */ + loglog(RC_LOG_SERIOUS, "DPD: Clearing connection"); + unroute_connection(c); + break; + case DPD_ACTION_RESTART: + /* dpdaction=restart - Restart connection, + * except if roadwarrior connection + */ + loglog(RC_LOG_SERIOUS, "DPD: Restarting connection"); + unroute_connection(c); + initiate_connection(c->name, NULL_FD); + break; + default: + loglog(RC_LOG_SERIOUS, "DPD: unknown action"); + } +} + diff --git a/src/pluto/ipsec_doi.h b/src/pluto/ipsec_doi.h new file mode 100644 index 000000000..80b12c31d --- /dev/null +++ b/src/pluto/ipsec_doi.h @@ -0,0 +1,104 @@ +/* IPsec DOI and Oakley resolution routines + * Copyright (C) 1998-2002 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: ipsec_doi.h,v 1.3 2005/01/06 22:10:44 as Exp $ + */ + +extern void echo_hdr(struct msg_digest *md, bool enc, u_int8_t np); + +extern void ipsecdoi_initiate(int whack_sock, struct connection *c + , lset_t policy, unsigned long try, so_serial_t replacing); + +extern void ipsecdoi_replace(struct state *st, unsigned long try); + +extern void init_phase2_iv(struct state *st, const msgid_t *msgid); + +extern stf_status quick_outI1(int whack_sock + , struct state *isakmp_sa + , struct connection *c + , lset_t policy + , unsigned long try + , so_serial_t replacing); + +extern state_transition_fn + main_inI1_outR1, + main_inR1_outI2, + main_inI2_outR2, + main_inR2_outI3, + main_inI3_outR3, + main_inR3, + quick_inI1_outR1, + quick_inR1_outI2, + quick_inI2; + +extern void send_delete(struct state *st); +extern void accept_delete(struct state *st, struct msg_digest *md + , struct payload_digest *p); +extern void close_message(pb_stream *pbs); +extern bool encrypt_message(pb_stream *pbs, struct state *st); + + +extern void send_notification_from_state(struct state *st, + enum state_kind state, u_int16_t type); +extern void send_notification_from_md(struct msg_digest *md, u_int16_t type); + +extern const char *init_pluto_vendorid(void); + +extern void dpd_outI(struct state *st); +extern stf_status dpd_inI_outR(struct state *st + , struct isakmp_notification *const n, pb_stream *n_pbs); +extern stf_status dpd_inR(struct state *st + , struct isakmp_notification *const n, pb_stream *n_pbs); +extern void dpd_timeout(struct state *st); + +/* START_HASH_PAYLOAD + * + * Emit a to-be-filled-in hash payload, noting the field start (r_hashval) + * and the start of the part of the message to be hashed (r_hash_start). + * This macro is magic. + * - it can cause the caller to return + * - it references variables local to the caller (r_hashval, r_hash_start, st) + */ +#define START_HASH_PAYLOAD(rbody, np) { \ + pb_stream hash_pbs; \ + if (!out_generic(np, &isakmp_hash_desc, &(rbody), &hash_pbs)) \ + return STF_INTERNAL_ERROR; \ + r_hashval = hash_pbs.cur; /* remember where to plant value */ \ + if (!out_zero(st->st_oakley.hasher->hash_digest_size, &hash_pbs, "HASH")) \ + return STF_INTERNAL_ERROR; \ + close_output_pbs(&hash_pbs); \ + r_hash_start = (rbody).cur; /* hash from after HASH payload */ \ +} + +/* CHECK_QUICK_HASH + * + * This macro is magic -- it cannot be expressed as a function. + * - it causes the caller to return! + * - it declares local variables and expects the "do_hash" argument + * expression to reference them (hash_val, hash_pbs) + */ +#define CHECK_QUICK_HASH(md, do_hash, hash_name, msg_name) { \ + pb_stream *const hash_pbs = &md->chain[ISAKMP_NEXT_HASH]->pbs; \ + u_char hash_val[MAX_DIGEST_LEN]; \ + size_t hash_len = do_hash; \ + if (pbs_left(hash_pbs) != hash_len \ + || memcmp(hash_pbs->cur, hash_val, hash_len) != 0) \ + { \ + DBG_cond_dump(DBG_CRYPT, "received " hash_name ":", hash_pbs->cur, pbs_left(hash_pbs)); \ + loglog(RC_LOG_SERIOUS, "received " hash_name " does not match computed value in " msg_name); \ + /* XXX Could send notification back */ \ + return STF_FAIL + INVALID_HASH_INFORMATION; \ + } \ + } + + diff --git a/src/pluto/kameipsec.h b/src/pluto/kameipsec.h new file mode 100644 index 000000000..5f08c7d38 --- /dev/null +++ b/src/pluto/kameipsec.h @@ -0,0 +1,47 @@ +#ifndef __IPSEC_H +#define __IPSEC_H 1 + +/* The definitions, required to talk to KAME racoon IKE. */ + +#define IPSEC_PORT_ANY 0 +#define IPSEC_ULPROTO_ANY 255 +#define IPSEC_PROTO_ANY 255 + +enum { + IPSEC_MODE_ANY = 0, /* We do not support this for SA */ + IPSEC_MODE_TRANSPORT = 1, + IPSEC_MODE_TUNNEL = 2 +}; + +enum { + IPSEC_DIR_ANY = 0, + IPSEC_DIR_INBOUND = 1, + IPSEC_DIR_OUTBOUND = 2, + IPSEC_DIR_FWD = 3, /* It is our own */ + IPSEC_DIR_MAX = 4, + IPSEC_DIR_INVALID = 5 +}; + +enum { + IPSEC_POLICY_DISCARD = 0, + IPSEC_POLICY_NONE = 1, + IPSEC_POLICY_IPSEC = 2, + IPSEC_POLICY_ENTRUST = 3, + IPSEC_POLICY_BYPASS = 4 +}; + +enum { + IPSEC_LEVEL_DEFAULT = 0, + IPSEC_LEVEL_USE = 1, + IPSEC_LEVEL_REQUIRE = 2, + IPSEC_LEVEL_UNIQUE = 3 +}; + +#define IPSEC_MANUAL_REQID_MAX 0x3fff + +#define IPSEC_REPLAYWSIZE 32 + +#define IP_IPSEC_POLICY 16 +#define IPV6_IPSEC_POLICY 34 + +#endif /* __IPSEC_H */ diff --git a/src/pluto/kernel.c b/src/pluto/kernel.c new file mode 100644 index 000000000..663fa7230 --- /dev/null +++ b/src/pluto/kernel.c @@ -0,0 +1,2995 @@ +/* routines that interface with the kernel's IPsec mechanism + * Copyright (C) 1997 Angelos D. Keromytis. + * Copyright (C) 1998-2002 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: kernel.c,v 1.26 2006/04/29 18:16:02 as Exp $ + */ + +#include <stddef.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <wait.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/queue.h> + +#include <sys/stat.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <freeswan.h> +#include <ipsec_policy.h> + +#ifdef KLIPS +#include <signal.h> +#include <sys/time.h> /* for select(2) */ +#include <sys/types.h> /* for select(2) */ +#include <pfkeyv2.h> +#include <pfkey.h> +#include "kameipsec.h" +#endif /* KLIPS */ + +#include "constants.h" +#include "defs.h" +#include "rnd.h" +#include "id.h" +#include "connections.h" +#include "state.h" +#include "timer.h" +#include "kernel.h" +#include "kernel_netlink.h" +#include "kernel_pfkey.h" +#include "kernel_noklips.h" +#include "log.h" +#include "ca.h" +#include "server.h" +#include "whack.h" /* for RC_LOG_SERIOUS */ +#include "keys.h" +#include "nat_traversal.h" +#include "alg_info.h" +#include "kernel_alg.h" + + +bool can_do_IPcomp = TRUE; /* can system actually perform IPCOMP? */ + +/* How far can IPsec messages arrive out of order before the anti-replay + * logic loses track and swats them? 64 is the best KLIPS can do. + * And 32 is the best XFRM can do... + */ +#define REPLAY_WINDOW 64 +#define REPLAY_WINDOW_XFRM 32 + +/* test if the routes required for two different connections agree + * It is assumed that the destination subnets agree; we are only + * testing that the interfaces and nexthops match. + */ +#define routes_agree(c, d) ((c)->interface == (d)->interface \ + && sameaddr(&(c)->spd.this.host_nexthop, &(d)->spd.this.host_nexthop)) + +#ifndef KLIPS + +bool no_klips = TRUE; /* don't actually use KLIPS */ + +#else /* !KLIPS */ + +/* bare (connectionless) shunt (eroute) table + * + * Bare shunts are those that don't "belong" to a connection. + * This happens because some %trapped traffic hasn't yet or cannot be + * assigned to a connection. The usual reason is that we cannot discover + * the peer SG. Another is that even when the peer has been discovered, + * it may be that no connection matches all the particulars. + * We record them so that, with scanning, we can discover + * which %holds are news and which others should expire. + */ + +#define SHUNT_SCAN_INTERVAL (60 * 2) /* time between scans of eroutes */ + +/* SHUNT_PATIENCE only has resolution down to a multiple of the sample rate, + * SHUNT_SCAN_INTERVAL. + * By making SHUNT_PATIENCE an odd multiple of half of SHUNT_SCAN_INTERVAL, + * we minimize the effects of jitter. + */ +#define SHUNT_PATIENCE (SHUNT_SCAN_INTERVAL * 15 / 2) /* inactivity timeout */ + +struct bare_shunt { + policy_prio_t policy_prio; + ip_subnet ours; + ip_subnet his; + ip_said said; + int transport_proto; + unsigned long count; + time_t last_activity; + char *why; + struct bare_shunt *next; +}; + +static struct bare_shunt *bare_shunts = NULL; + +#ifdef DEBUG +static void +DBG_bare_shunt(const char *op, const struct bare_shunt *bs) +{ + DBG(DBG_KLIPS, + { + int ourport = ntohs(portof(&(bs)->ours.addr)); + int hisport = ntohs(portof(&(bs)->his.addr)); + char ourst[SUBNETTOT_BUF]; + char hist[SUBNETTOT_BUF]; + char sat[SATOT_BUF]; + char prio[POLICY_PRIO_BUF]; + + subnettot(&(bs)->ours, 0, ourst, sizeof(ourst)); + subnettot(&(bs)->his, 0, hist, sizeof(hist)); + satot(&(bs)->said, 0, sat, sizeof(sat)); + fmt_policy_prio(bs->policy_prio, prio); + DBG_log("%s bare shunt %p %s:%d -> %s:%d => %s:%d %s %s" + , op, (const void *)(bs), ourst, ourport, hist, hisport + , sat, (bs)->transport_proto, prio, (bs)->why); + }); +} +#else /* !DEBUG */ +#define DBG_bare_shunt(op, bs) {} +#endif /* !DEBUG */ + +/* The orphaned_holds table records %holds for which we + * scan_proc_shunts found no representation of in any connection. + * The corresponding ACQUIRE message might have been lost. + */ +struct eroute_info *orphaned_holds = NULL; + +/* forward declaration */ +static bool shunt_eroute(struct connection *c + , struct spd_route *sr + , enum routing_t rt_kind + , unsigned int op, const char *opname); +static void set_text_said(char *text_said + , const ip_address *dst + , ipsec_spi_t spi + , int proto); + +bool no_klips = FALSE; /* don't actually use KLIPS */ + +static const struct pfkey_proto_info null_proto_info[2] = { + { + proto: IPPROTO_ESP, + encapsulation: ENCAPSULATION_MODE_TRANSPORT, + reqid: 0 + }, + { + proto: 0, + encapsulation: 0, + reqid: 0 + } +}; + +void +record_and_initiate_opportunistic(const ip_subnet *ours + , const ip_subnet *his + , int transport_proto + , const char *why) +{ + passert(samesubnettype(ours, his)); + + /* Add to bare shunt list. + * We need to do this because the shunt was installed by KLIPS + * which can't do this itself. + */ + { + struct bare_shunt *bs = alloc_thing(struct bare_shunt, "bare shunt"); + + bs->why = clone_str(why, "story for bare shunt"); + bs->ours = *ours; + bs->his = *his; + bs->transport_proto = transport_proto; + bs->policy_prio = BOTTOM_PRIO; + + bs->said.proto = SA_INT; + bs->said.spi = htonl(SPI_HOLD); + bs->said.dst = *aftoinfo(subnettypeof(ours))->any; + + bs->count = 0; + bs->last_activity = now(); + + bs->next = bare_shunts; + bare_shunts = bs; + DBG_bare_shunt("add", bs); + } + + /* actually initiate opportunism */ + { + ip_address src, dst; + + networkof(ours, &src); + networkof(his, &dst); + initiate_opportunistic(&src, &dst, transport_proto, TRUE, NULL_FD); + } + + /* if present, remove from orphaned_holds list. + * NOTE: we do this last in case ours or his is a pointer into a member. + */ + { + struct eroute_info **pp, *p; + + for (pp = &orphaned_holds; (p = *pp) != NULL; pp = &p->next) + { + if (samesubnet(ours, &p->ours) + && samesubnet(his, &p->his) + && transport_proto == p->transport_proto + && portof(&ours->addr) == portof(&p->ours.addr) + && portof(&his->addr) == portof(&p->his.addr)) + { + *pp = p->next; + pfree(p); + break; + } + } + } +} + +#endif /* KLIPS */ + +static unsigned get_proto_reqid(unsigned base, int proto) +{ + switch (proto) + { + default: + case IPPROTO_COMP: + base++; + /* fall through */ + case IPPROTO_ESP: + base++; + /* fall through */ + case IPPROTO_AH: + break; + } + + return base; +} + +/* Generate Unique SPI numbers. + * + * The specs say that the number must not be less than IPSEC_DOI_SPI_MIN. + * Pluto generates numbers not less than IPSEC_DOI_SPI_OUR_MIN, + * reserving numbers in between for manual keying (but we cannot so + * restrict numbers generated by our peer). + * XXX This should be replaced by a call to the kernel when + * XXX we get an API. + * The returned SPI is in network byte order. + * We use a random number as the initial SPI so that there is + * a good chance that different Pluto instances will choose + * different SPIs. This is good for two reasons. + * - the keying material for the initiator and responder only + * differs if the SPIs differ. + * - if Pluto is restarted, it would otherwise recycle the SPI + * numbers and confuse everything. When the kernel generates + * SPIs, this will no longer matter. + * We then allocate numbers sequentially. Thus we don't have to + * check if the number was previously used (assuming that no + * SPI lives longer than 4G of its successors). + */ +ipsec_spi_t +get_ipsec_spi(ipsec_spi_t avoid, int proto, struct spd_route *sr, bool tunnel) +{ + static ipsec_spi_t spi = 0; /* host order, so not returned directly! */ + char text_said[SATOT_BUF]; + + set_text_said(text_said, &sr->this.host_addr, 0, proto); + + if (kernel_ops->get_spi) + return kernel_ops->get_spi(&sr->that.host_addr + , &sr->this.host_addr, proto, tunnel + , get_proto_reqid(sr->reqid, proto) + , IPSEC_DOI_SPI_OUR_MIN, 0xffffffff + , text_said); + + spi++; + while (spi < IPSEC_DOI_SPI_OUR_MIN || spi == ntohl(avoid)) + get_rnd_bytes((u_char *)&spi, sizeof(spi)); + + DBG(DBG_CONTROL, + { + ipsec_spi_t spi_net = htonl(spi); + + DBG_dump("generate SPI:", (u_char *)&spi_net, sizeof(spi_net)); + }); + + return htonl(spi); +} + +/* Generate Unique CPI numbers. + * The result is returned as an SPI (4 bytes) in network order! + * The real bits are in the nework-low-order 2 bytes. + * Modelled on get_ipsec_spi, but range is more limited: + * 256-61439. + * If we can't find one easily, return 0 (a bad SPI, + * no matter what order) indicating failure. + */ +ipsec_spi_t +get_my_cpi(struct spd_route *sr, bool tunnel) +{ + static cpi_t + first_busy_cpi = 0, + latest_cpi; + char text_said[SATOT_BUF]; + + set_text_said(text_said, &sr->this.host_addr, 0, IPPROTO_COMP); + + if (kernel_ops->get_spi) + return kernel_ops->get_spi(&sr->that.host_addr + , &sr->this.host_addr, IPPROTO_COMP, tunnel + , get_proto_reqid(sr->reqid, IPPROTO_COMP) + , IPCOMP_FIRST_NEGOTIATED, IPCOMP_LAST_NEGOTIATED + , text_said); + + while (!(IPCOMP_FIRST_NEGOTIATED <= first_busy_cpi && first_busy_cpi < IPCOMP_LAST_NEGOTIATED)) + { + get_rnd_bytes((u_char *)&first_busy_cpi, sizeof(first_busy_cpi)); + latest_cpi = first_busy_cpi; + } + + latest_cpi++; + + if (latest_cpi == first_busy_cpi) + find_my_cpi_gap(&latest_cpi, &first_busy_cpi); + + if (latest_cpi > IPCOMP_LAST_NEGOTIATED) + latest_cpi = IPCOMP_FIRST_NEGOTIATED; + + return htonl((ipsec_spi_t)latest_cpi); +} + +/* invoke the updown script to do the routing and firewall commands required + * + * The user-specified updown script is run. Parameters are fed to it in + * the form of environment variables. All such environment variables + * have names starting with "PLUTO_". + * + * The operation to be performed is specified by PLUTO_VERB. This + * verb has a suffix "-host" if the client on this end is just the + * host; otherwise the suffix is "-client". If the address family + * of the host is IPv6, an extra suffix of "-v6" is added. + * + * "prepare-host" and "prepare-client" are used to delete a route + * that may exist (due to forces outside of Pluto). It is used to + * prepare for pluto creating a route. + * + * "route-host" and "route-client" are used to install a route. + * Since routing is based only on destination, the PLUTO_MY_CLIENT_* + * values are probably of no use (using them may signify a bug). + * + * "unroute-host" and "unroute-client" are used to delete a route. + * Since routing is based only on destination, the PLUTO_MY_CLIENT_* + * values are probably of no use (using them may signify a bug). + * + * "up-host" and "up-client" are run when an eroute is added (not replaced). + * They are useful for adjusting a firewall: usually for adding a rule + * to let processed packets flow between clients. Note that only + * one eroute may exist for a pair of client subnets but inbound + * IPsec SAs may persist without an eroute. + * + * "down-host" and "down-client" are run when an eroute is deleted. + * They are useful for adjusting a firewall. + */ + +#ifndef DEFAULT_UPDOWN +# define DEFAULT_UPDOWN "ipsec _updown" +#endif + +static bool +do_command(struct connection *c, struct spd_route *sr, const char *verb) +{ + char cmd[1536]; /* arbitrary limit on shell command length */ + const char *verb_suffix; + + /* figure out which verb suffix applies */ + { + const char *hs, *cs; + + switch (addrtypeof(&sr->this.host_addr)) + { + case AF_INET: + hs = "-host"; + cs = "-client"; + break; + case AF_INET6: + hs = "-host-v6"; + cs = "-client-v6"; + break; + default: + loglog(RC_LOG_SERIOUS, "unknown address family"); + return FALSE; + } + verb_suffix = subnetisaddr(&sr->this.client, &sr->this.host_addr) + ? hs : cs; + } + + /* form the command string */ + { + char + nexthop_str[sizeof("PLUTO_NEXT_HOP='' ") +ADDRTOT_BUF] = "", + srcip_str[sizeof("PLUTO_MY_SOURCEIP='' ")+ADDRTOT_BUF] = "", + me_str[ADDRTOT_BUF], + myid_str[BUF_LEN], + myclient_str[SUBNETTOT_BUF], + myclientnet_str[ADDRTOT_BUF], + myclientmask_str[ADDRTOT_BUF], + peer_str[ADDRTOT_BUF], + peerid_str[BUF_LEN], + peerclient_str[SUBNETTOT_BUF], + peerclientnet_str[ADDRTOT_BUF], + peerclientmask_str[ADDRTOT_BUF], + peerca_str[BUF_LEN], + secure_myid_str[BUF_LEN] = "", + secure_peerid_str[BUF_LEN] = "", + secure_peerca_str[BUF_LEN] = ""; + ip_address ta; + pubkey_list_t *p; + + if (addrbytesptr(&sr->this.host_nexthop, NULL) + && !isanyaddr(&sr->this.host_nexthop)) + { + char *n; + + strcpy(nexthop_str, "PLUTO_NEXT_HOP='"); + n = nexthop_str + strlen(nexthop_str); + + addrtot(&sr->this.host_nexthop, 0 + ,n , sizeof(nexthop_str)-strlen(nexthop_str)); + strncat(nexthop_str, "' ", sizeof(nexthop_str)); + } + + if (addrbytesptr(&sr->this.host_srcip, NULL) + && !isanyaddr(&sr->this.host_srcip)) + { + char *n; + + strcpy(srcip_str, "PLUTO_MY_SOURCEIP='"); + n = srcip_str + strlen(srcip_str); + + addrtot(&sr->this.host_srcip, 0 + ,n , sizeof(srcip_str)-strlen(srcip_str)); + strncat(srcip_str, "' ", sizeof(srcip_str)); + } + + addrtot(&sr->this.host_addr, 0, me_str, sizeof(me_str)); + idtoa(&sr->this.id, myid_str, sizeof(myid_str)); + escape_metachar(myid_str, secure_myid_str, sizeof(secure_myid_str)); + subnettot(&sr->this.client, 0, myclient_str, sizeof(myclientnet_str)); + networkof(&sr->this.client, &ta); + addrtot(&ta, 0, myclientnet_str, sizeof(myclientnet_str)); + maskof(&sr->this.client, &ta); + addrtot(&ta, 0, myclientmask_str, sizeof(myclientmask_str)); + + addrtot(&sr->that.host_addr, 0, peer_str, sizeof(peer_str)); + idtoa(&sr->that.id, peerid_str, sizeof(peerid_str)); + escape_metachar(peerid_str, secure_peerid_str, sizeof(secure_peerid_str)); + subnettot(&sr->that.client, 0, peerclient_str, sizeof(peerclientnet_str)); + networkof(&sr->that.client, &ta); + addrtot(&ta, 0, peerclientnet_str, sizeof(peerclientnet_str)); + maskof(&sr->that.client, &ta); + addrtot(&ta, 0, peerclientmask_str, sizeof(peerclientmask_str)); + + for (p = pubkeys; p != NULL; p = p->next) + { + pubkey_t *key = p->key; + int pathlen; + + if (key->alg == PUBKEY_ALG_RSA && same_id(&sr->that.id, &key->id) + && trusted_ca(key->issuer, sr->that.ca, &pathlen)) + { + dntoa_or_null(peerca_str, BUF_LEN, key->issuer, ""); + escape_metachar(peerca_str, secure_peerca_str, sizeof(secure_peerca_str)); + break; + } + } + + if (-1 == snprintf(cmd, sizeof(cmd) + , "2>&1 " /* capture stderr along with stdout */ + "PLUTO_VERSION='1.1' " /* change VERSION when interface spec changes */ + "PLUTO_VERB='%s%s' " + "PLUTO_CONNECTION='%s' " + "%s" /* optional PLUTO_NEXT_HOP */ + "PLUTO_INTERFACE='%s' " + "%s" /* optional PLUTO_HOST_ACCESS */ + "PLUTO_REQID='%u' " + "PLUTO_ME='%s' " + "PLUTO_MY_ID='%s' " + "PLUTO_MY_CLIENT='%s' " + "PLUTO_MY_CLIENT_NET='%s' " + "PLUTO_MY_CLIENT_MASK='%s' " + "PLUTO_MY_PORT='%u' " + "PLUTO_MY_PROTOCOL='%u' " + "PLUTO_PEER='%s' " + "PLUTO_PEER_ID='%s' " + "PLUTO_PEER_CLIENT='%s' " + "PLUTO_PEER_CLIENT_NET='%s' " + "PLUTO_PEER_CLIENT_MASK='%s' " + "PLUTO_PEER_PORT='%u' " + "PLUTO_PEER_PROTOCOL='%u' " + "PLUTO_PEER_CA='%s' " + "%s" /* optional PLUTO_MY_SRCIP */ + "%s" /* actual script */ + , verb, verb_suffix + , c->name + , nexthop_str + , c->interface->vname + , sr->this.hostaccess? "PLUTO_HOST_ACCESS='1' " : "" + , sr->reqid + 1 /* ESP requid */ + , me_str + , secure_myid_str + , myclient_str + , myclientnet_str + , myclientmask_str + , sr->this.port + , sr->this.protocol + , peer_str + , secure_peerid_str + , peerclient_str + , peerclientnet_str + , peerclientmask_str + , sr->that.port + , sr->that.protocol + , secure_peerca_str + , srcip_str + , sr->this.updown == NULL? DEFAULT_UPDOWN : sr->this.updown)) + { + loglog(RC_LOG_SERIOUS, "%s%s command too long!", verb, verb_suffix); + return FALSE; + } + } + + DBG(DBG_CONTROL, DBG_log("executing %s%s: %s" + , verb, verb_suffix, cmd)); + +#ifdef KLIPS + if (!no_klips) + { + /* invoke the script, catching stderr and stdout + * It may be of concern that some file descriptors will + * be inherited. For the ones under our control, we + * have done fcntl(fd, F_SETFD, FD_CLOEXEC) to prevent this. + * Any used by library routines (perhaps the resolver or syslog) + * will remain. + */ + FILE *f = popen(cmd, "r"); + + if (f == NULL) + { + loglog(RC_LOG_SERIOUS, "unable to popen %s%s command", verb, verb_suffix); + return FALSE; + } + + /* log any output */ + for (;;) + { + /* if response doesn't fit in this buffer, it will be folded */ + char resp[256]; + + if (fgets(resp, sizeof(resp), f) == NULL) + { + if (ferror(f)) + { + log_errno((e, "fgets failed on output of %s%s command" + , verb, verb_suffix)); + return FALSE; + } + else + { + passert(feof(f)); + break; + } + } + else + { + char *e = resp + strlen(resp); + + if (e > resp && e[-1] == '\n') + e[-1] = '\0'; /* trim trailing '\n' */ + plog("%s%s output: %s", verb, verb_suffix, resp); + } + } + + /* report on and react to return code */ + { + int r = pclose(f); + + if (r == -1) + { + log_errno((e, "pclose failed for %s%s command" + , verb, verb_suffix)); + return FALSE; + } + else if (WIFEXITED(r)) + { + if (WEXITSTATUS(r) != 0) + { + loglog(RC_LOG_SERIOUS, "%s%s command exited with status %d" + , verb, verb_suffix, WEXITSTATUS(r)); + return FALSE; + } + } + else if (WIFSIGNALED(r)) + { + loglog(RC_LOG_SERIOUS, "%s%s command exited with signal %d" + , verb, verb_suffix, WTERMSIG(r)); + return FALSE; + } + else + { + loglog(RC_LOG_SERIOUS, "%s%s command exited with unknown status %d" + , verb, verb_suffix, r); + return FALSE; + } + } + } +#endif /* KLIPS */ + return TRUE; +} + +/* Check that we can route (and eroute). Diagnose if we cannot. */ + +enum routability { + route_impossible = 0, + route_easy = 1, + route_nearconflict = 2, + route_farconflict = 3 +}; + +static enum routability +could_route(struct connection *c) +{ + struct spd_route *esr, *rosr; + struct connection *ero /* who, if anyone, owns our eroute? */ + , *ro = route_owner(c, &rosr, &ero, &esr); /* who owns our route? */ + + /* it makes no sense to route a connection that is ISAKMP-only */ + if (!NEVER_NEGOTIATE(c->policy) && !HAS_IPSEC_POLICY(c->policy)) + { + loglog(RC_ROUTE, "cannot route an ISAKMP-only connection"); + return route_impossible; + } + + /* if this is a Road Warrior template, we cannot route. + * Opportunistic template is OK. + */ + if (c->kind == CK_TEMPLATE && !(c->policy & POLICY_OPPO)) + { + loglog(RC_ROUTE, "cannot route Road Warrior template"); + return route_impossible; + } + + /* if we don't know nexthop, we cannot route */ + if (isanyaddr(&c->spd.this.host_nexthop)) + { + loglog(RC_ROUTE, "cannot route connection without knowing our nexthop"); + return route_impossible; + } + + /* if routing would affect IKE messages, reject */ + if (!no_klips + && c->spd.this.host_port != NAT_T_IKE_FLOAT_PORT + && c->spd.this.host_port != IKE_UDP_PORT + && addrinsubnet(&c->spd.that.host_addr, &c->spd.that.client)) + { + loglog(RC_LOG_SERIOUS, "cannot install route: peer is within its client"); + return route_impossible; + } + + /* If there is already a route for peer's client subnet + * and it disagrees about interface or nexthop, we cannot steal it. + * Note: if this connection is already routed (perhaps for another + * state object), the route will agree. + * This is as it should be -- it will arise during rekeying. + */ + if (ro != NULL && !routes_agree(ro, c)) + { + loglog(RC_LOG_SERIOUS, "cannot route -- route already in use for \"%s\"" + , ro->name); + return route_impossible; /* another connection already + using the eroute */ + } + +#ifdef KLIPS + /* if there is an eroute for another connection, there is a problem */ + if (ero != NULL && ero != c) + { + struct connection *ero2, *ero_top; + struct connection *inside, *outside; + + /* + * note, wavesec (PERMANENT) goes *outside* and + * OE goes *inside* (TEMPLATE) + */ + inside = NULL; + outside= NULL; + if (ero->kind == CK_PERMANENT + && c->kind == CK_TEMPLATE) + { + outside = ero; + inside = c; + } + else if (c->kind == CK_PERMANENT + && ero->kind == CK_TEMPLATE) + { + outside = c; + inside = ero; + } + + /* okay, check again, with correct order */ + if (outside && outside->kind == CK_PERMANENT + && inside && inside->kind == CK_TEMPLATE) + { + char inst[CONN_INST_BUF]; + + /* this is a co-terminal attempt of the "near" kind. */ + /* when chaining, we chain from inside to outside */ + + /* XXX permit multiple deep connections? */ + passert(inside->policy_next == NULL); + + inside->policy_next = outside; + + /* since we are going to steal the eroute from the secondary + * policy, we need to make sure that it no longer thinks that + * it owns the eroute. + */ + outside->spd.eroute_owner = SOS_NOBODY; + outside->spd.routing = RT_UNROUTED_KEYED; + + /* set the priority of the new eroute owner to be higher + * than that of the current eroute owner + */ + inside->prio = outside->prio + 1; + + fmt_conn_instance(inside, inst); + + loglog(RC_LOG_SERIOUS + , "conflict on eroute (%s), switching eroute to %s and linking %s" + , inst, inside->name, outside->name); + + return route_nearconflict; + } + + /* look along the chain of policies for one with the same name */ + ero_top = ero; + + for (ero2 = ero; ero2 != NULL; ero2 = ero->policy_next) + { + if (ero2->kind == CK_TEMPLATE + && streq(ero2->name, c->name)) + break; + } + + /* If we fell of the end of the list, then we found no TEMPLATE + * so there must be a conflict that we can't resolve. + * As the names are not equal, then we aren't replacing/rekeying. + */ + if (ero2 == NULL) + { + char inst[CONN_INST_BUF]; + + fmt_conn_instance(ero, inst); + + loglog(RC_LOG_SERIOUS + , "cannot install eroute -- it is in use for \"%s\"%s #%lu" + , ero->name, inst, esr->eroute_owner); + return FALSE; /* another connection already using the eroute */ + } + } +#endif /* KLIPS */ + return route_easy; +} + +bool +trap_connection(struct connection *c) +{ + switch (could_route(c)) + { + case route_impossible: + return FALSE; + + case route_nearconflict: + case route_easy: + /* RT_ROUTED_TUNNEL is treated specially: we don't override + * because we don't want to lose track of the IPSEC_SAs etc. + */ + if (c->spd.routing < RT_ROUTED_TUNNEL) + { + return route_and_eroute(c, &c->spd, NULL); + } + return TRUE; + + case route_farconflict: + return FALSE; + } + + return FALSE; +} + +/* delete any eroute for a connection and unroute it if route isn't shared */ +void +unroute_connection(struct connection *c) +{ + struct spd_route *sr; + enum routing_t cr; + + for (sr = &c->spd; sr; sr = sr->next) + { + cr = sr->routing; + + if (erouted(cr)) + { + /* cannot handle a live one */ + passert(sr->routing != RT_ROUTED_TUNNEL); +#ifdef KLIPS + shunt_eroute(c, sr, RT_UNROUTED, ERO_DELETE, "delete"); +#endif + } + + sr->routing = RT_UNROUTED; /* do now so route_owner won't find us */ + + /* only unroute if no other connection shares it */ + if (routed(cr) && route_owner(c, NULL, NULL, NULL) == NULL) + (void) do_command(c, sr, "unroute"); + } +} + + +#ifdef KLIPS + +static void +set_text_said(char *text_said, const ip_address *dst, ipsec_spi_t spi, int proto) +{ + ip_said said; + + initsaid(dst, spi, proto, &said); + satot(&said, 0, text_said, SATOT_BUF); +} + +/* find an entry in the bare_shunt table. + * Trick: return a pointer to the pointer to the entry; + * this allows the entry to be deleted. + */ +static struct bare_shunt ** +bare_shunt_ptr(const ip_subnet *ours, const ip_subnet *his, int transport_proto) +{ + struct bare_shunt *p, **pp; + + for (pp = &bare_shunts; (p = *pp) != NULL; pp = &p->next) + { + if (samesubnet(ours, &p->ours) + && samesubnet(his, &p->his) + && transport_proto == p->transport_proto + && portof(&ours->addr) == portof(&p->ours.addr) + && portof(&his->addr) == portof(&p->his.addr)) + return pp; + } + return NULL; +} + +/* free a bare_shunt entry, given a pointer to the pointer */ +static void +free_bare_shunt(struct bare_shunt **pp) +{ + if (pp == NULL) + { + DBG(DBG_CONTROL, + DBG_log("delete bare shunt: null pointer") + ) + } + else + { + struct bare_shunt *p = *pp; + + *pp = p->next; + DBG_bare_shunt("delete", p); + pfree(p->why); + pfree(p); + } +} + +void +show_shunt_status(void) +{ + struct bare_shunt *bs; + + for (bs = bare_shunts; bs != NULL; bs = bs->next) + { + /* Print interesting fields. Ignore count and last_active. */ + + int ourport = ntohs(portof(&bs->ours.addr)); + int hisport = ntohs(portof(&bs->his.addr)); + char ourst[SUBNETTOT_BUF]; + char hist[SUBNETTOT_BUF]; + char sat[SATOT_BUF]; + char prio[POLICY_PRIO_BUF]; + + subnettot(&(bs)->ours, 0, ourst, sizeof(ourst)); + subnettot(&(bs)->his, 0, hist, sizeof(hist)); + satot(&(bs)->said, 0, sat, sizeof(sat)); + fmt_policy_prio(bs->policy_prio, prio); + + whack_log(RC_COMMENT, "%s:%d -> %s:%d => %s:%d %s %s" + , ourst, ourport, hist, hisport, sat, bs->transport_proto + , prio, bs->why); + } + if (bare_shunts != NULL) + whack_log(RC_COMMENT, BLANK_FORMAT); /* spacer */ +} + +/* Setup an IPsec route entry. + * op is one of the ERO_* operators. + */ + +static bool +raw_eroute(const ip_address *this_host + , const ip_subnet *this_client + , const ip_address *that_host + , const ip_subnet *that_client + , ipsec_spi_t spi + , unsigned int proto + , unsigned int satype + , unsigned int transport_proto + , const struct pfkey_proto_info *proto_info + , time_t use_lifetime + , unsigned int op + , const char *opname USED_BY_DEBUG) +{ + char text_said[SATOT_BUF]; + + set_text_said(text_said, that_host, spi, proto); + + DBG(DBG_CONTROL | DBG_KLIPS, + { + int sport = ntohs(portof(&this_client->addr)); + int dport = ntohs(portof(&that_client->addr)); + char mybuf[SUBNETTOT_BUF]; + char peerbuf[SUBNETTOT_BUF]; + + subnettot(this_client, 0, mybuf, sizeof(mybuf)); + subnettot(that_client, 0, peerbuf, sizeof(peerbuf)); + DBG_log("%s eroute %s:%d -> %s:%d => %s:%d" + , opname, mybuf, sport, peerbuf, dport + , text_said, transport_proto); + }); + + return kernel_ops->raw_eroute(this_host, this_client + , that_host, that_client, spi, satype, transport_proto, proto_info + , use_lifetime, op, text_said); +} + +/* test to see if %hold remains */ +bool +has_bare_hold(const ip_address *src, const ip_address *dst, int transport_proto) +{ + ip_subnet this_client, that_client; + struct bare_shunt **bspp; + + passert(addrtypeof(src) == addrtypeof(dst)); + happy(addrtosubnet(src, &this_client)); + happy(addrtosubnet(dst, &that_client)); + bspp = bare_shunt_ptr(&this_client, &that_client, transport_proto); + return bspp != NULL + && (*bspp)->said.proto == SA_INT && (*bspp)->said.spi == htonl(SPI_HOLD); +} + + +/* Replace (or delete) a shunt that is in the bare_shunts table. + * Issues the PF_KEY commands and updates the bare_shunts table. + */ +bool +replace_bare_shunt(const ip_address *src, const ip_address *dst + , policy_prio_t policy_prio + , ipsec_spi_t shunt_spi /* in host order! */ + , bool repl /* if TRUE, replace; if FALSE, delete */ + , unsigned int transport_proto + , const char *why) +{ + ip_subnet this_client, that_client; + ip_subnet this_broad_client, that_broad_client; + const ip_address *null_host = aftoinfo(addrtypeof(src))->any; + + passert(addrtypeof(src) == addrtypeof(dst)); + happy(addrtosubnet(src, &this_client)); + happy(addrtosubnet(dst, &that_client)); + this_broad_client = this_client; + that_broad_client = that_client; + setportof(0, &this_broad_client.addr); + setportof(0, &that_broad_client.addr); + + if (repl) + { + struct bare_shunt **bs_pp = bare_shunt_ptr(&this_broad_client + , &that_broad_client, 0); + + /* is there already a broad host-to-host bare shunt? */ + if (bs_pp == NULL) + { + if (raw_eroute(null_host, &this_broad_client, null_host, &that_broad_client + , htonl(shunt_spi), SA_INT, SADB_X_SATYPE_INT + , 0, null_proto_info + , SHUNT_PATIENCE, ERO_ADD, why)) + { + struct bare_shunt *bs = alloc_thing(struct bare_shunt, "bare shunt"); + + bs->ours = this_broad_client; + bs->his = that_broad_client; + bs->transport_proto = 0; + bs->said.proto = SA_INT; + bs->why = clone_str(why, "bare shunt story"); + bs->policy_prio = policy_prio; + bs->said.spi = htonl(shunt_spi); + bs->said.dst = *null_host; + bs->count = 0; + bs->last_activity = now(); + bs->next = bare_shunts; + bare_shunts = bs; + DBG_bare_shunt("add", bs); + } + } + shunt_spi = SPI_HOLD; + } + + if (raw_eroute(null_host, &this_client, null_host, &that_client + , htonl(shunt_spi), SA_INT, SADB_X_SATYPE_INT + , transport_proto, null_proto_info + , SHUNT_PATIENCE, ERO_DELETE, why)) + { + struct bare_shunt **bs_pp = bare_shunt_ptr(&this_client, &that_client + , transport_proto); + + /* delete bare eroute */ + free_bare_shunt(bs_pp); + return TRUE; + } + else + { + return FALSE; + } +} + +static bool +eroute_connection(struct spd_route *sr +, ipsec_spi_t spi, unsigned int proto, unsigned int satype +, const struct pfkey_proto_info *proto_info +, unsigned int op, const char *opname) +{ + const ip_address *peer = &sr->that.host_addr; + char buf2[256]; + + snprintf(buf2, sizeof(buf2) + , "eroute_connection %s", opname); + + if (proto == SA_INT) + peer = aftoinfo(addrtypeof(peer))->any; + + return raw_eroute(&sr->this.host_addr, &sr->this.client + , peer + , &sr->that.client + , spi, proto, satype + , sr->this.protocol, proto_info, 0, op, buf2); +} + +/* assign a bare hold to a connection */ + +bool +assign_hold(struct connection *c USED_BY_DEBUG + , struct spd_route *sr + , int transport_proto + , const ip_address *src, const ip_address *dst) +{ + /* either the automatically installed %hold eroute is broad enough + * or we try to add a broader one and delete the automatic one. + * Beware: this %hold might be already handled, but still squeak + * through because of a race. + */ + enum routing_t ro = sr->routing /* routing, old */ + , rn = ro; /* routing, new */ + + passert(LHAS(LELEM(CK_PERMANENT) | LELEM(CK_INSTANCE), c->kind)); + /* figure out what routing should become */ + switch (ro) + { + case RT_UNROUTED: + rn = RT_UNROUTED_HOLD; + break; + case RT_ROUTED_PROSPECTIVE: + rn = RT_ROUTED_HOLD; + break; + default: + /* no change: this %hold is old news and should just be deleted */ + break; + } + + /* we need a broad %hold, not the narrow one. + * First we ensure that there is a broad %hold. + * There may already be one (race condition): no need to create one. + * There may already be a %trap: replace it. + * There may not be any broad eroute: add %hold. + * Once the broad %hold is in place, delete the narrow one. + */ + if (rn != ro) + { + if (erouted(ro) + ? !eroute_connection(sr, htonl(SPI_HOLD), SA_INT, SADB_X_SATYPE_INT + , null_proto_info + , ERO_REPLACE, "replace %trap with broad %hold") + : !eroute_connection(sr, htonl(SPI_HOLD), SA_INT, SADB_X_SATYPE_INT + , null_proto_info + , ERO_ADD, "add broad %hold")) + { + return FALSE; + } + } + if (!replace_bare_shunt(src, dst, BOTTOM_PRIO, SPI_HOLD, FALSE + , transport_proto, "delete narrow %hold")) + { + return FALSE; + } + sr->routing = rn; + return TRUE; +} + +/* install or remove eroute for SA Group */ +static bool +sag_eroute(struct state *st, struct spd_route *sr + , unsigned op, const char *opname) +{ + u_int inner_proto = 0; + u_int inner_satype = 0; + ipsec_spi_t inner_spi = 0; + struct pfkey_proto_info proto_info[4]; + int i; + bool tunnel; + + /* figure out the SPI and protocol (in two forms) + * for the innermost transformation. + */ + + i = sizeof(proto_info) / sizeof(proto_info[0]) - 1; + proto_info[i].proto = 0; + tunnel = FALSE; + + if (st->st_ah.present) + { + inner_spi = st->st_ah.attrs.spi; + inner_proto = SA_AH; + inner_satype = SADB_SATYPE_AH; + + i--; + proto_info[i].proto = IPPROTO_AH; + proto_info[i].encapsulation = st->st_ah.attrs.encapsulation; + tunnel |= proto_info[i].encapsulation == ENCAPSULATION_MODE_TUNNEL; + proto_info[i].reqid = sr->reqid; + } + + if (st->st_esp.present) + { + inner_spi = st->st_esp.attrs.spi; + inner_proto = SA_ESP; + inner_satype = SADB_SATYPE_ESP; + + i--; + proto_info[i].proto = IPPROTO_ESP; + proto_info[i].encapsulation = st->st_esp.attrs.encapsulation; + tunnel |= proto_info[i].encapsulation == ENCAPSULATION_MODE_TUNNEL; + proto_info[i].reqid = sr->reqid + 1; + } + + if (st->st_ipcomp.present) + { + inner_spi = st->st_ipcomp.attrs.spi; + inner_proto = SA_COMP; + inner_satype = SADB_X_SATYPE_COMP; + + i--; + proto_info[i].proto = IPPROTO_COMP; + proto_info[i].encapsulation = st->st_ipcomp.attrs.encapsulation; + tunnel |= proto_info[i].encapsulation == ENCAPSULATION_MODE_TUNNEL; + proto_info[i].reqid = sr->reqid + 2; + } + + if (i == sizeof(proto_info) / sizeof(proto_info[0]) - 1) + { + impossible(); /* no transform at all! */ + } + + if (tunnel) + { + int j; + + inner_spi = st->st_tunnel_out_spi; + inner_proto = SA_IPIP; + inner_satype = SADB_X_SATYPE_IPIP; + + proto_info[i].encapsulation = ENCAPSULATION_MODE_TUNNEL; + for (j = i + 1; proto_info[j].proto; j++) + { + proto_info[j].encapsulation = ENCAPSULATION_MODE_TRANSPORT; + } + } + + return eroute_connection(sr + , inner_spi, inner_proto, inner_satype, proto_info + i + , op, opname); +} + +/* compute a (host-order!) SPI to implement the policy in connection c */ +ipsec_spi_t +shunt_policy_spi(struct connection *c, bool prospective) +{ + /* note: these are in host order :-( */ + static const ipsec_spi_t shunt_spi[] = + { + SPI_TRAP, /* --initiateontraffic */ + SPI_PASS, /* --pass */ + SPI_DROP, /* --drop */ + SPI_REJECT, /* --reject */ + }; + + static const ipsec_spi_t fail_spi[] = + { + 0, /* --none*/ + SPI_PASS, /* --failpass */ + SPI_DROP, /* --faildrop */ + SPI_REJECT, /* --failreject */ + }; + + return prospective + ? shunt_spi[(c->policy & POLICY_SHUNT_MASK) >> POLICY_SHUNT_SHIFT] + : fail_spi[(c->policy & POLICY_FAIL_MASK) >> POLICY_FAIL_SHIFT]; +} + +/* Add/replace/delete a shunt eroute. + * Such an eroute determines the fate of packets without the use + * of any SAs. These are defaults, in effect. + * If a negotiation has not been attempted, use %trap. + * If negotiation has failed, the choice between %trap/%pass/%drop/%reject + * is specified in the policy of connection c. + */ +static bool +shunt_eroute(struct connection *c +, struct spd_route *sr +, enum routing_t rt_kind +, unsigned int op, const char *opname) +{ + /* We are constructing a special SAID for the eroute. + * The destination doesn't seem to matter, but the family does. + * The protocol is SA_INT -- mark this as shunt. + * The satype has no meaning, but is required for PF_KEY header! + * The SPI signifies the kind of shunt. + */ + ipsec_spi_t spi = shunt_policy_spi(c, rt_kind == RT_ROUTED_PROSPECTIVE); + bool ok; + + if (spi == 0) + { + /* we're supposed to end up with no eroute: rejig op and opname */ + switch (op) + { + case ERO_REPLACE: + /* replace with nothing == delete */ + op = ERO_DELETE; + opname = "delete"; + break; + case ERO_ADD: + /* add nothing == do nothing */ + return TRUE; + case ERO_DELETE: + /* delete remains delete */ + break; + default: + bad_case(op); + } + } + if (sr->routing == RT_ROUTED_ECLIPSED && c->kind == CK_TEMPLATE) + { + /* We think that we have an eroute, but we don't. + * Adjust the request and account for eclipses. + */ + passert(eclipsable(sr)); + switch (op) + { + case ERO_REPLACE: + /* really an add */ + op = ERO_ADD; + opname = "replace eclipsed"; + eclipse_count--; + break; + case ERO_DELETE: + /* delete unnecessary: we don't actually have an eroute */ + eclipse_count--; + return TRUE; + case ERO_ADD: + default: + bad_case(op); + } + } + else if (eclipse_count > 0 && op == ERO_DELETE && eclipsable(sr)) + { + /* maybe we are uneclipsing something */ + struct spd_route *esr; + struct connection *ue = eclipsed(c, &esr); + + if (ue != NULL) + { + esr->routing = RT_ROUTED_PROSPECTIVE; + return shunt_eroute(ue, esr + , RT_ROUTED_PROSPECTIVE, ERO_REPLACE, "restoring eclipsed"); + } + } + + ok = TRUE; + if (kernel_ops->inbound_eroute) + { + ok = raw_eroute(&c->spd.that.host_addr, &c->spd.that.client + , &c->spd.this.host_addr, &c->spd.this.client + , htonl(spi), SA_INT, SADB_X_SATYPE_INT + , 0, null_proto_info, 0 + , op | (SADB_X_SAFLAGS_INFLOW << ERO_FLAG_SHIFT), opname); + } + return eroute_connection(sr, htonl(spi), SA_INT, SADB_X_SATYPE_INT + , null_proto_info, op, opname) && ok; +} + + +/* + * This is only called when s is a likely SAID with trailing protocol i.e. + * it has the form :- + * + * %<keyword>:p + * <ip-proto><spi>@a.b.c.d:p + * + * The task here is to remove the ":p" part so that the rest can be read + * by another routine. + */ +static const char * +read_proto(const char * s, size_t * len, int * transport_proto) +{ + const char * p; + const char * ugh; + unsigned long proto; + size_t l; + + l = *len; + p = memchr(s, ':', l); + if (p == 0) { + *transport_proto = 0; + return 0; + } + ugh = ttoul(p+1, l-((p-s)+1), 10, &proto); + if (ugh != 0) + return ugh; + if (proto > 65535) + return "protocol number is too large, legal range is 0-65535"; + *len = p-s; + *transport_proto = proto; + return 0; +} + + +/* scan /proc/net/ipsec_eroute every once in a while, looking for: + * + * - %hold shunts of which Pluto isn't aware. This situation could + * be caused by lost ACQUIRE messages. When found, they will + * added to orphan_holds. This in turn will lead to Opportunistic + * initiation. + * + * - other kinds of shunts that haven't been used recently. These will be + * deleted. They represent OE failures. + * + * - recording recent uses of tunnel eroutes so that rekeying decisions + * can be made for OE connections. + * + * Here are some sample lines: + * 10 10.3.2.1.0/24 -> 0.0.0.0/0 => %trap + * 259 10.3.2.1.115/32 -> 10.19.75.161/32 => tun0x1002@10.19.75.145 + * 71 10.44.73.97/32 -> 0.0.0.0/0 => %trap + * 4119 10.44.73.97/32 -> 10.114.121.41/32 => %pass + * Newer versions of KLIPS start each line with a 32-bit packet count. + * If available, the count is used to detect whether a %pass shunt is in use. + * + * NOTE: execution time is quadratic in the number of eroutes since the + * searching for each is sequential. If this becomes a problem, faster + * searches could be implemented (hash or radix tree, for example). + */ +void +scan_proc_shunts(void) +{ + static const char procname[] = "/proc/net/ipsec_eroute"; + FILE *f; + time_t nw = now(); + int lino; + struct eroute_info *expired = NULL; + + event_schedule(EVENT_SHUNT_SCAN, SHUNT_SCAN_INTERVAL, NULL); + + DBG(DBG_CONTROL, + DBG_log("scanning for shunt eroutes") + ) + + /* free any leftover entries: they will be refreshed if still current */ + while (orphaned_holds != NULL) + { + struct eroute_info *p = orphaned_holds; + + orphaned_holds = p->next; + pfree(orphaned_holds); + } + + /* decode the /proc file. Don't do anything strenuous to it + * (certainly no PF_KEY stuff) to minimize the chance that it + * might change underfoot. + */ + + f = fopen(procname, "r"); + if (f == NULL) + return; + + /* for each line... */ + for (lino = 1; ; lino++) + { + unsigned char buf[1024]; /* should be big enough */ + chunk_t field[10]; /* 10 is loose upper bound */ + chunk_t *ff = NULL; /* fixed fields (excluding optional count) */ + int fi; + struct eroute_info eri; + char *cp; + err_t context = "" + , ugh = NULL; + + cp = fgets(buf, sizeof(buf), f); + if (cp == NULL) + break; + + /* break out each field + * Note: if there are too many fields, just stop; + * it will be diagnosed a little later. + */ + for (fi = 0; fi < (int)elemsof(field); fi++) + { + static const char sep[] = " \t\n"; /* field-separating whitespace */ + size_t w; + + cp += strspn(cp, sep); /* find start of field */ + w = strcspn(cp, sep); /* find width of field */ + setchunk(field[fi], cp, w); + cp += w; + if (w == 0) + break; + } + + /* This odd do-hickey is to share error reporting code. + * A break will get to that common code. The setting + * of "ugh" and "context" parameterize it. + */ + do { + /* Old entries have no packet count; new ones do. + * check if things are as they should be. + */ + if (fi == 5) + ff = &field[0]; /* old form, with no count */ + else if (fi == 6) + ff = &field[1]; /* new form, with count */ + else + { + ugh = "has wrong number of fields"; + break; + } + + if (ff[1].len != 2 + || strncmp(ff[1].ptr, "->", 2) != 0 + || ff[3].len != 2 + || strncmp(ff[3].ptr, "=>", 2) != 0) + { + ugh = "is missing -> or =>"; + break; + } + + /* actually digest fields of interest */ + + /* packet count */ + + eri.count = 0; + if (ff != field) + { + context = "count field is malformed: "; + ugh = ttoul(field[0].ptr, field[0].len, 10, &eri.count); + if (ugh != NULL) + break; + } + + /* our client */ + + context = "source subnet field malformed: "; + ugh = ttosubnet(ff[0].ptr, ff[0].len, AF_INET, &eri.ours); + if (ugh != NULL) + break; + + /* his client */ + + context = "destination subnet field malformed: "; + ugh = ttosubnet(ff[2].ptr, ff[2].len, AF_INET, &eri.his); + if (ugh != NULL) + break; + + /* SAID */ + + context = "SA ID field malformed: "; + ugh = read_proto(ff[4].ptr, &ff[4].len, &eri.transport_proto); + if (ugh != NULL) + break; + ugh = ttosa(ff[4].ptr, ff[4].len, &eri.said); + } while (FALSE); + + if (ugh != NULL) + { + plog("INTERNAL ERROR: %s line %d %s%s" + , procname, lino, context, ugh); + continue; /* ignore rest of line */ + } + + /* Now we have decoded eroute, let's consider it. + * For shunt eroutes: + * + * %hold: if not known, add to orphaned_holds list for initiation + * because ACQUIRE might have been lost. + * + * %pass, %drop, %reject: determine if idle; if so, blast it away. + * Can occur bare (if DNS provided insufficient information) + * or with a connection (failure context). + * Could even be installed by ipsec manual. + * + * %trap: always welcome. + * + * For other eroutes: find state and record count change + */ + if (eri.said.proto == SA_INT) + { + /* shunt eroute */ + switch (ntohl(eri.said.spi)) + { + case SPI_HOLD: + if (bare_shunt_ptr(&eri.ours, &eri.his, eri.transport_proto) == NULL + && shunt_owner(&eri.ours, &eri.his) == NULL) + { + int ourport = ntohs(portof(&eri.ours.addr)); + int hisport = ntohs(portof(&eri.his.addr)); + char ourst[SUBNETTOT_BUF]; + char hist[SUBNETTOT_BUF]; + char sat[SATOT_BUF]; + + subnettot(&eri.ours, 0, ourst, sizeof(ourst)); + subnettot(&eri.his, 0, hist, sizeof(hist)); + satot(&eri.said, 0, sat, sizeof(sat)); + + DBG(DBG_CONTROL, + DBG_log("add orphaned shunt %s:%d -> %s:%d => %s:%d" + , ourst, ourport, hist, hisport, sat, eri.transport_proto) + ) + eri.next = orphaned_holds; + orphaned_holds = clone_thing(eri, "orphaned %hold"); + } + break; + + case SPI_PASS: + case SPI_DROP: + case SPI_REJECT: + /* nothing sensible to do if we don't have counts */ + if (ff != field) + { + struct bare_shunt **bs_pp + = bare_shunt_ptr(&eri.ours, &eri.his, eri.transport_proto); + + if (bs_pp != NULL) + { + struct bare_shunt *bs = *bs_pp; + + if (eri.count != bs->count) + { + bs->count = eri.count; + bs->last_activity = nw; + } + else if (nw - bs->last_activity > SHUNT_PATIENCE) + { + eri.next = expired; + expired = clone_thing(eri, "expired %pass"); + } + } + } + break; + + case SPI_TRAP: + break; + + default: + bad_case(ntohl(eri.said.spi)); + } + } + else + { + /* regular (non-shunt) eroute */ + state_eroute_usage(&eri.ours, &eri.his, eri.count, nw); + } + } /* for each line */ + fclose(f); + + /* Now that we've finished processing the /proc file, + * it is safe to delete the expired %pass shunts. + */ + while (expired != NULL) + { + struct eroute_info *p = expired; + ip_address src, dst; + + networkof(&p->ours, &src); + networkof(&p->his, &dst); + (void) replace_bare_shunt(&src, &dst + , BOTTOM_PRIO /* not used because we are deleting. This value is a filler */ + , SPI_PASS /* not used because we are deleting. This value is a filler */ + , FALSE, p->transport_proto, "delete expired bare shunts"); + expired = p->next; + pfree(p); + } +} + +static bool +del_spi(ipsec_spi_t spi, int proto +, const ip_address *src, const ip_address *dest) +{ + char text_said[SATOT_BUF]; + struct kernel_sa sa; + + set_text_said(text_said, dest, spi, proto); + + DBG(DBG_KLIPS, DBG_log("delete %s", text_said)); + + memset(&sa, 0, sizeof(sa)); + sa.spi = spi; + sa.proto = proto; + sa.src = src; + sa.dst = dest; + sa.text_said = text_said; + + return kernel_ops->del_sa(&sa); +} + +/* Setup a pair of SAs. Code taken from setsa.c and spigrp.c, in + * ipsec-0.5. + */ + +static bool +setup_half_ipsec_sa(struct state *st, bool inbound) +{ + /* Build an inbound or outbound SA */ + + struct connection *c = st->st_connection; + ip_subnet src, dst; + ip_subnet src_client, dst_client; + ipsec_spi_t inner_spi = 0; + u_int proto = 0; + u_int satype = SADB_SATYPE_UNSPEC; + bool replace; + + /* SPIs, saved for spigrouping or undoing, if necessary */ + struct kernel_sa + said[EM_MAXRELSPIS], + *said_next = said; + + char text_said[SATOT_BUF]; + int encapsulation; + + replace = inbound && (kernel_ops->get_spi != NULL); + + src.maskbits = 0; + dst.maskbits = 0; + + if (inbound) + { + src.addr = c->spd.that.host_addr; + dst.addr = c->spd.this.host_addr; + src_client = c->spd.that.client; + dst_client = c->spd.this.client; + } + else + { + src.addr = c->spd.this.host_addr, + dst.addr = c->spd.that.host_addr; + src_client = c->spd.this.client; + dst_client = c->spd.that.client; + } + + encapsulation = ENCAPSULATION_MODE_TRANSPORT; + if (st->st_ah.attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL + || st->st_esp.attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL + || st->st_ipcomp.attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL) + { + encapsulation = ENCAPSULATION_MODE_TUNNEL; + } + + memset(said, 0, sizeof(said)); + + /* If we are tunnelling, set up IP in IP pseudo SA */ + + if (kernel_ops->inbound_eroute) + { + inner_spi = 256; + proto = SA_IPIP; + satype = SADB_SATYPE_UNSPEC; + } + else if (encapsulation == ENCAPSULATION_MODE_TUNNEL) + { + /* XXX hack alert -- we SHOULD NOT HAVE TO HAVE A DIFFERENT SPI + * XXX FOR IP-in-IP ENCAPSULATION! + */ + + ipsec_spi_t ipip_spi; + + /* Allocate an SPI for the tunnel. + * Since our peer will never see this, + * and it comes from its own number space, + * it is purely a local implementation wart. + */ + { + static ipsec_spi_t last_tunnel_spi = IPSEC_DOI_SPI_OUR_MIN; + + ipip_spi = htonl(++last_tunnel_spi); + if (inbound) + st->st_tunnel_in_spi = ipip_spi; + else + st->st_tunnel_out_spi = ipip_spi; + } + + set_text_said(text_said + , &c->spd.that.host_addr, ipip_spi, SA_IPIP); + + said_next->src = &src.addr; + said_next->dst = &dst.addr; + said_next->src_client = &src_client; + said_next->dst_client = &dst_client; + said_next->spi = ipip_spi; + said_next->satype = SADB_X_SATYPE_IPIP; + said_next->text_said = text_said; + + if (!kernel_ops->add_sa(said_next, replace)) + goto fail; + + said_next++; + + inner_spi = ipip_spi; + proto = SA_IPIP; + satype = SADB_X_SATYPE_IPIP; + } + + /* set up IPCOMP SA, if any */ + + if (st->st_ipcomp.present) + { + ipsec_spi_t ipcomp_spi = inbound? st->st_ipcomp.our_spi : st->st_ipcomp.attrs.spi; + unsigned compalg; + + switch (st->st_ipcomp.attrs.transid) + { + case IPCOMP_DEFLATE: + compalg = SADB_X_CALG_DEFLATE; + break; + + default: + loglog(RC_LOG_SERIOUS, "IPCOMP transform %s not implemented" + , enum_name(&ipcomp_transformid_names, st->st_ipcomp.attrs.transid)); + goto fail; + } + + set_text_said(text_said, &dst.addr, ipcomp_spi, SA_COMP); + + said_next->src = &src.addr; + said_next->dst = &dst.addr; + said_next->src_client = &src_client; + said_next->dst_client = &dst_client; + said_next->spi = ipcomp_spi; + said_next->satype = SADB_X_SATYPE_COMP; + said_next->compalg = compalg; + said_next->encapsulation = encapsulation; + said_next->reqid = c->spd.reqid + 2; + said_next->text_said = text_said; + + if (!kernel_ops->add_sa(said_next, replace)) + goto fail; + + said_next++; + + encapsulation = ENCAPSULATION_MODE_TRANSPORT; + } + + /* set up ESP SA, if any */ + + if (st->st_esp.present) + { + ipsec_spi_t esp_spi = inbound? st->st_esp.our_spi : st->st_esp.attrs.spi; + u_char *esp_dst_keymat = inbound? st->st_esp.our_keymat : st->st_esp.peer_keymat; + const struct esp_info *ei; + u_int16_t key_len; + + static const struct esp_info esp_info[] = { + { ESP_NULL, AUTH_ALGORITHM_HMAC_MD5, + 0, HMAC_MD5_KEY_LEN, + SADB_EALG_NULL, SADB_AALG_MD5_HMAC }, + { ESP_NULL, AUTH_ALGORITHM_HMAC_SHA1, + 0, HMAC_SHA1_KEY_LEN, + SADB_EALG_NULL, SADB_AALG_SHA1_HMAC }, + + { ESP_DES, AUTH_ALGORITHM_NONE, + DES_CBC_BLOCK_SIZE, 0, + SADB_EALG_DES_CBC, SADB_AALG_NONE }, + { ESP_DES, AUTH_ALGORITHM_HMAC_MD5, + DES_CBC_BLOCK_SIZE, HMAC_MD5_KEY_LEN, + SADB_EALG_DES_CBC, SADB_AALG_MD5_HMAC }, + { ESP_DES, AUTH_ALGORITHM_HMAC_SHA1, + DES_CBC_BLOCK_SIZE, + HMAC_SHA1_KEY_LEN, SADB_EALG_DES_CBC, SADB_AALG_SHA1_HMAC }, + + { ESP_3DES, AUTH_ALGORITHM_NONE, + DES_CBC_BLOCK_SIZE * 3, 0, + SADB_EALG_3DES_CBC, SADB_AALG_NONE }, + { ESP_3DES, AUTH_ALGORITHM_HMAC_MD5, + DES_CBC_BLOCK_SIZE * 3, HMAC_MD5_KEY_LEN, + SADB_EALG_3DES_CBC, SADB_AALG_MD5_HMAC }, + { ESP_3DES, AUTH_ALGORITHM_HMAC_SHA1, + DES_CBC_BLOCK_SIZE * 3, HMAC_SHA1_KEY_LEN, + SADB_EALG_3DES_CBC, SADB_AALG_SHA1_HMAC }, + }; + + u_int8_t natt_type = 0; + u_int16_t natt_sport = 0; + u_int16_t natt_dport = 0; + ip_address natt_oa; + + if (st->nat_traversal & NAT_T_DETECTED) + { + natt_type = (st->nat_traversal & NAT_T_WITH_PORT_FLOATING) ? + ESPINUDP_WITH_NON_ESP : ESPINUDP_WITH_NON_IKE; + natt_sport = inbound? c->spd.that.host_port : c->spd.this.host_port; + natt_dport = inbound? c->spd.this.host_port : c->spd.that.host_port; + natt_oa = st->nat_oa; + } + + for (ei = esp_info; ; ei++) + { + if (ei == &esp_info[elemsof(esp_info)]) + { + /* Check for additional kernel alg */ +#ifndef NO_KERNEL_ALG + if ((ei=kernel_alg_esp_info(st->st_esp.attrs.transid, + st->st_esp.attrs.auth))!=NULL) { + break; + } +#endif + + /* note: enum_show may use a static buffer, so two + * calls in one printf would be a mistake. + * enum_name does the same job, without a static buffer, + * assuming the name will be found. + */ + loglog(RC_LOG_SERIOUS, "ESP transform %s / auth %s not implemented yet" + , enum_name(&esp_transformid_names, st->st_esp.attrs.transid) + , enum_name(&auth_alg_names, st->st_esp.attrs.auth)); + goto fail; + } + + if (st->st_esp.attrs.transid == ei->transid + && st->st_esp.attrs.auth == ei->auth) + break; + } + + key_len = st->st_esp.attrs.key_len/8; + if (key_len) + { + /* XXX: must change to check valid _range_ key_len */ + if (key_len > ei->enckeylen) + { + loglog(RC_LOG_SERIOUS, "ESP transform %s passed key_len=%d > %d", + enum_name(&esp_transformid_names, st->st_esp.attrs.transid), + (int)key_len, (int)ei->enckeylen); + goto fail; + } + } + else + { + key_len = ei->enckeylen; + } + /* Grrrrr.... f*cking 7 bits jurassic algos */ + + /* 168 bits in kernel, need 192 bits for keymat_len */ + if (ei->transid == ESP_3DES && key_len == 21) + key_len = 24; + + /* 56 bits in kernel, need 64 bits for keymat_len */ + if (ei->transid == ESP_DES && key_len == 7) + key_len = 8; + + /* divide up keying material */ + /* passert(st->st_esp.keymat_len == ei->enckeylen + ei->authkeylen); */ + DBG(DBG_KLIPS|DBG_CONTROL|DBG_PARSING, + if(st->st_esp.keymat_len != key_len + ei->authkeylen) + DBG_log("keymat_len=%d key_len=%d authkeylen=%d", + st->st_esp.keymat_len, (int)key_len, (int)ei->authkeylen); + ) + passert(st->st_esp.keymat_len == key_len + ei->authkeylen); + + set_text_said(text_said, &dst.addr, esp_spi, SA_ESP); + + said_next->src = &src.addr; + said_next->dst = &dst.addr; + said_next->src_client = &src_client; + said_next->dst_client = &dst_client; + said_next->spi = esp_spi; + said_next->satype = SADB_SATYPE_ESP; + said_next->replay_window = (kernel_ops->type == KERNEL_TYPE_KLIPS) ? REPLAY_WINDOW : REPLAY_WINDOW_XFRM; + said_next->authalg = ei->authalg; + said_next->authkeylen = ei->authkeylen; + /* said_next->authkey = esp_dst_keymat + ei->enckeylen; */ + said_next->authkey = esp_dst_keymat + key_len; + said_next->encalg = ei->encryptalg; + /* said_next->enckeylen = ei->enckeylen; */ + said_next->enckeylen = key_len; + said_next->enckey = esp_dst_keymat; + said_next->encapsulation = encapsulation; + said_next->reqid = c->spd.reqid + 1; + said_next->natt_sport = natt_sport; + said_next->natt_dport = natt_dport; + said_next->transid = st->st_esp.attrs.transid; + said_next->natt_type = natt_type; + said_next->natt_oa = &natt_oa; + said_next->text_said = text_said; + + if (!kernel_ops->add_sa(said_next, replace)) + goto fail; + + said_next++; + + encapsulation = ENCAPSULATION_MODE_TRANSPORT; + } + + /* set up AH SA, if any */ + + if (st->st_ah.present) + { + ipsec_spi_t ah_spi = inbound? st->st_ah.our_spi : st->st_ah.attrs.spi; + u_char *ah_dst_keymat = inbound? st->st_ah.our_keymat : st->st_ah.peer_keymat; + + unsigned char authalg; + + switch (st->st_ah.attrs.auth) + { + case AUTH_ALGORITHM_HMAC_MD5: + authalg = SADB_AALG_MD5_HMAC; + break; + + case AUTH_ALGORITHM_HMAC_SHA1: + authalg = SADB_AALG_SHA1_HMAC; + break; + + default: + loglog(RC_LOG_SERIOUS, "%s not implemented yet" + , enum_show(&auth_alg_names, st->st_ah.attrs.auth)); + goto fail; + } + + set_text_said(text_said, &dst.addr, ah_spi, SA_AH); + + said_next->src = &src.addr; + said_next->dst = &dst.addr; + said_next->src_client = &src_client; + said_next->dst_client = &dst_client; + said_next->spi = ah_spi; + said_next->satype = SADB_SATYPE_AH; + said_next->replay_window = (kernel_ops->type == KERNEL_TYPE_KLIPS) ? REPLAY_WINDOW : REPLAY_WINDOW_XFRM; + said_next->authalg = authalg; + said_next->authkeylen = st->st_ah.keymat_len; + said_next->authkey = ah_dst_keymat; + said_next->encapsulation = encapsulation; + said_next->reqid = c->spd.reqid; + said_next->text_said = text_said; + + if (!kernel_ops->add_sa(said_next, replace)) + goto fail; + + said_next++; + + encapsulation = ENCAPSULATION_MODE_TRANSPORT; + } + + if (st->st_ah.attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL + || st->st_esp.attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL + || st->st_ipcomp.attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL) + { + encapsulation = ENCAPSULATION_MODE_TUNNEL; + } + + if (kernel_ops->inbound_eroute ? c->spd.eroute_owner == SOS_NOBODY + : encapsulation == ENCAPSULATION_MODE_TUNNEL) + { + /* If inbound, and policy does not specifie DISABLEARRIVALCHECK, + * tell KLIPS to enforce the IP addresses appropriate for this tunnel. + * Note reversed ends. + * Not much to be done on failure. + */ + if (inbound && (c->policy & POLICY_DISABLEARRIVALCHECK) == 0) + { + struct pfkey_proto_info proto_info[4]; + int i = 0; + + if (st->st_ipcomp.present) + { + proto_info[i].proto = IPPROTO_COMP; + proto_info[i].encapsulation = st->st_ipcomp.attrs.encapsulation; + proto_info[i].reqid = c->spd.reqid + 2; + i++; + } + + if (st->st_esp.present) + { + proto_info[i].proto = IPPROTO_ESP; + proto_info[i].encapsulation = st->st_esp.attrs.encapsulation; + proto_info[i].reqid = c->spd.reqid + 1; + i++; + } + + if (st->st_ah.present) + { + proto_info[i].proto = IPPROTO_AH; + proto_info[i].encapsulation = st->st_ah.attrs.encapsulation; + proto_info[i].reqid = c->spd.reqid; + i++; + } + + proto_info[i].proto = 0; + + if (kernel_ops->inbound_eroute + && encapsulation == ENCAPSULATION_MODE_TUNNEL) + { + proto_info[0].encapsulation = ENCAPSULATION_MODE_TUNNEL; + for (i = 1; proto_info[i].proto; i++) + { + proto_info[i].encapsulation = ENCAPSULATION_MODE_TRANSPORT; + } + } + + /* MCR - should be passed a spd_eroute structure here */ + (void) raw_eroute(&c->spd.that.host_addr, &c->spd.that.client + , &c->spd.this.host_addr, &c->spd.this.client + , inner_spi, proto, satype, c->spd.this.protocol + , proto_info, 0 + , ERO_ADD_INBOUND, "add inbound"); + } + } + + /* If there are multiple SPIs, group them. */ + + if (kernel_ops->grp_sa && said_next > &said[1]) + { + struct kernel_sa *s; + + /* group SAs, two at a time, inner to outer (backwards in said[]) + * The grouping is by pairs. So if said[] contains ah esp ipip, + * the grouping would be ipip:esp, esp:ah. + */ + for (s = said; s < said_next-1; s++) + { + char + text_said0[SATOT_BUF], + text_said1[SATOT_BUF]; + + /* group s[1] and s[0], in that order */ + + set_text_said(text_said0, s[0].dst, s[0].spi, s[0].proto); + set_text_said(text_said1, s[1].dst, s[1].spi, s[1].proto); + + DBG(DBG_KLIPS, DBG_log("grouping %s and %s", text_said1, text_said0)); + + s[0].text_said = text_said0; + s[1].text_said = text_said1; + + if (!kernel_ops->grp_sa(s + 1, s)) + goto fail; + } + /* could update said, but it will not be used */ + } + + return TRUE; + +fail: + { + /* undo the done SPIs */ + while (said_next-- != said) + (void) del_spi(said_next->spi, said_next->proto + , &src.addr, said_next->dst); + return FALSE; + } +} + +/* teardown_ipsec_sa is a canibalized version of setup_ipsec_sa */ + +static bool +teardown_half_ipsec_sa(struct state *st, bool inbound) +{ + /* We need to delete AH, ESP, and IP in IP SPIs. + * But if there is more than one, they have been grouped + * so deleting any one will do. So we just delete the + * first one found. It may or may not be the only one. + */ + struct connection *c = st->st_connection; + struct { + unsigned proto; + struct ipsec_proto_info *info; + } protos[4]; + int i; + bool result; + + i = 0; + if (kernel_ops->inbound_eroute && inbound + && c->spd.eroute_owner == SOS_NOBODY) + { + (void) raw_eroute(&c->spd.that.host_addr, &c->spd.that.client + , &c->spd.this.host_addr, &c->spd.this.client + , 256, IPSEC_PROTO_ANY, SADB_SATYPE_UNSPEC, c->spd.this.protocol + , null_proto_info, 0 + , ERO_DEL_INBOUND, "delete inbound"); + } + + if (!kernel_ops->grp_sa) + { + if (st->st_ah.present) + { + protos[i].info = &st->st_ah; + protos[i].proto = SA_AH; + i++; + } + + if (st->st_esp.present) + { + protos[i].info = &st->st_esp; + protos[i].proto = SA_ESP; + i++; + } + + if (st->st_ipcomp.present) + { + protos[i].info = &st->st_ipcomp; + protos[i].proto = SA_COMP; + i++; + } + } + else if (st->st_ah.present) + { + protos[i].info = &st->st_ah; + protos[i].proto = SA_AH; + i++; + } + else if (st->st_esp.present) + { + protos[i].info = &st->st_esp; + protos[i].proto = SA_ESP; + i++; + } + else + { + impossible(); /* neither AH nor ESP in outbound SA bundle! */ + } + protos[i].proto = 0; + + result = TRUE; + for (i = 0; protos[i].proto; i++) + { + unsigned proto = protos[i].proto; + ipsec_spi_t spi; + const ip_address *src, *dst; + + if (inbound) + { + spi = protos[i].info->our_spi; + src = &c->spd.that.host_addr; + dst = &c->spd.this.host_addr; + } + else + { + spi = protos[i].info->attrs.spi; + src = &c->spd.this.host_addr; + dst = &c->spd.that.host_addr; + } + + result &= del_spi(spi, proto, src, dst); + } + return result; +} + +/* + * get information about a given sa + */ +bool +get_sa_info(struct state *st, bool inbound, u_int *bytes, time_t *use_time) +{ + char text_said[SATOT_BUF]; + struct kernel_sa sa; + struct connection *c = st->st_connection; + + *use_time = UNDEFINED_TIME; + + if (kernel_ops->get_sa == NULL || !st->st_esp.present) + return FALSE; + + memset(&sa, 0, sizeof(sa)); + sa.proto = SA_ESP; + + if (inbound) + { + sa.src = &c->spd.that.host_addr; + sa.dst = &c->spd.this.host_addr; + sa.spi = st->st_esp.our_spi; + } + else + { + sa.src = &c->spd.this.host_addr; + sa.dst = &c->spd.that.host_addr; + sa.spi = st->st_esp.attrs.spi; + } + set_text_said(text_said, sa.dst, sa.spi, sa.proto); + + sa.text_said = text_said; + + DBG(DBG_KLIPS, + DBG_log("get %s", text_said) + ) + if (!kernel_ops->get_sa(&sa, bytes)) + return FALSE; + DBG(DBG_KLIPS, + DBG_log(" current: %d bytes", *bytes) + ) + + if (st->st_serialno == c->spd.eroute_owner) + { + DBG(DBG_KLIPS, + DBG_log("get %sbound policy with reqid %u" + , inbound? "in":"out", (u_int)c->spd.reqid + 1) + ) + sa.transport_proto = c->spd.this.protocol; + sa.encapsulation = st->st_esp.attrs.encapsulation; + + if (inbound) + { + sa.src_client = &c->spd.that.client; + sa.dst_client = &c->spd.this.client; + } + else + { + sa.src_client = &c->spd.this.client; + sa.dst_client = &c->spd.that.client; + } + if (!kernel_ops->get_policy(&sa, inbound, use_time)) + return FALSE; + DBG(DBG_KLIPS, + DBG_log(" use_time: %s", timetoa(use_time, FALSE)) + ) + } + return TRUE; +} + +const struct kernel_ops *kernel_ops; + +#endif /* KLIPS */ + +void +init_kernel(void) +{ +#ifdef KLIPS + + if (no_klips) + { + kernel_ops = &noklips_kernel_ops; + return; + } + + init_pfkey(); + + kernel_ops = &klips_kernel_ops; + +#if defined(linux) && defined(KERNEL26_SUPPORT) + { + bool linux_ipsec = 0; + struct stat buf; + + linux_ipsec = (stat("/proc/net/pfkey", &buf) == 0); + if (linux_ipsec) + { + plog("Using Linux 2.6 IPsec interface code"); + kernel_ops = &linux_kernel_ops; + } + else + { + plog("Using KLIPS IPsec interface code"); + } + } +#endif + + if (kernel_ops->init) + { + kernel_ops->init(); + } + + /* register SA types that we can negotiate */ + can_do_IPcomp = FALSE; /* until we get a response from KLIPS */ + kernel_ops->pfkey_register(); + + if (!kernel_ops->policy_lifetime) + { + event_schedule(EVENT_SHUNT_SCAN, SHUNT_SCAN_INTERVAL, NULL); + } +#endif +} + +/* Note: install_inbound_ipsec_sa is only used by the Responder. + * The Responder will subsequently use install_ipsec_sa for the outbound. + * The Initiator uses install_ipsec_sa to install both at once. + */ +bool +install_inbound_ipsec_sa(struct state *st) +{ + struct connection *const c = st->st_connection; + + /* If our peer has a fixed-address client, check if we already + * have a route for that client that conflicts. We will take this + * as proof that that route and the connections using it are + * obsolete and should be eliminated. Interestingly, this is + * the only case in which we can tell that a connection is obsolete. + */ + passert(c->kind == CK_PERMANENT || c->kind == CK_INSTANCE); + if (c->spd.that.has_client) + { + for (;;) + { + struct spd_route *esr; + struct connection *o = route_owner(c, &esr, NULL, NULL); + + if (o == NULL) + break; /* nobody has a route */ + + /* note: we ignore the client addresses at this end */ + if (sameaddr(&o->spd.that.host_addr, &c->spd.that.host_addr) + && o->interface == c->interface) + break; /* existing route is compatible */ + + if (o->kind == CK_TEMPLATE && streq(o->name, c->name)) + break; /* ??? is this good enough?? */ + + loglog(RC_LOG_SERIOUS, "route to peer's client conflicts with \"%s\" %s; releasing old connection to free the route" + , o->name, ip_str(&o->spd.that.host_addr)); + release_connection(o, FALSE); + } + } + + DBG(DBG_CONTROL, DBG_log("install_inbound_ipsec_sa() checking if we can route")); + /* check that we will be able to route and eroute */ + switch (could_route(c)) + { + case route_easy: + case route_nearconflict: + break; + + default: + return FALSE; + } + +#ifdef KLIPS + /* (attempt to) actually set up the SAs */ + return setup_half_ipsec_sa(st, TRUE); +#else /* !KLIPS */ + DBG(DBG_CONTROL, DBG_log("install_inbound_ipsec_sa()")); + return TRUE; +#endif /* !KLIPS */ +} + +/* Install a route and then a prospective shunt eroute or an SA group eroute. + * Assumption: could_route gave a go-ahead. + * Any SA Group must have already been created. + * On failure, steps will be unwound. + */ +bool +route_and_eroute(struct connection *c USED_BY_KLIPS + , struct spd_route *sr USED_BY_KLIPS + , struct state *st USED_BY_KLIPS) +{ +#ifdef KLIPS + struct spd_route *esr; + struct spd_route *rosr; + struct connection *ero /* who, if anyone, owns our eroute? */ + , *ro = route_owner(c, &rosr, &ero, &esr); + bool eroute_installed = FALSE + , firewall_notified = FALSE + , route_installed = FALSE; + + struct connection *ero_top; + struct bare_shunt **bspp; + + DBG(DBG_CONTROLMORE, + DBG_log("route_and_eroute with c: %s (next: %s) ero:%s esr:{%p} ro:%s rosr:{%p} and state: %lu" + , c->name + , (c->policy_next ? c->policy_next->name : "none") + , ero ? ero->name : "null" + , esr + , ro ? ro->name : "null" + , rosr + , st ? st->st_serialno : 0)); + + /* look along the chain of policies for one with the same name */ + ero_top = ero; + +#if 0 + /* XXX - mcr this made sense before, and likely will make sense + * again, so I'l leaving this to remind me what is up */ + if (ero!= NULL && ero->routing == RT_UNROUTED_KEYED) + ero = NULL; + + for (ero2 = ero; ero2 != NULL; ero2 = ero->policy_next) + if ((ero2->kind == CK_TEMPLATE || ero2->kind==CK_SECONDARY) + && streq(ero2->name, c->name)) + break; +#endif + + bspp = (ero == NULL) + ? bare_shunt_ptr(&sr->this.client, &sr->that.client, sr->this.protocol) + : NULL; + + /* install the eroute */ + + passert(bspp == NULL || ero == NULL); /* only one non-NULL */ + + if (bspp != NULL || ero != NULL) + { + /* We're replacing an eroute */ + + /* if no state provided, then install a shunt for later */ + if (st == NULL) + eroute_installed = shunt_eroute(c, sr, RT_ROUTED_PROSPECTIVE + , ERO_REPLACE, "replace"); + else + eroute_installed = sag_eroute(st, sr, ERO_REPLACE, "replace"); + +#if 0 + /* XXX - MCR. I previously felt that this was a bogus check */ + if (ero != NULL && ero != c && esr != sr) + { + /* By elimination, we must be eclipsing ero. Check. */ + passert(ero->kind == CK_TEMPLATE && streq(ero->name, c->name)); + passert(LHAS(LELEM(RT_ROUTED_PROSPECTIVE) | LELEM(RT_ROUTED_ECLIPSED) + , esr->routing)); + passert(samesubnet(&esr->this.client, &sr->this.client) + && samesubnet(&esr->that.client, &sr->that.client)); + } +#endif + /* remember to free bspp iff we make it out of here alive */ + } + else + { + /* we're adding an eroute */ + + /* if no state provided, then install a shunt for later */ + if (st == NULL) + eroute_installed = shunt_eroute(c, sr, RT_ROUTED_PROSPECTIVE + , ERO_ADD, "add"); + else + eroute_installed = sag_eroute(st, sr, ERO_ADD, "add"); + } + + /* notify the firewall of a new tunnel */ + + if (eroute_installed) + { + /* do we have to notify the firewall? Yes, if we are installing + * a tunnel eroute and the firewall wasn't notified + * for a previous tunnel with the same clients. Any Previous + * tunnel would have to be for our connection, so the actual + * test is simple. + */ + firewall_notified = st == NULL /* not a tunnel eroute */ + || sr->eroute_owner != SOS_NOBODY /* already notified */ + || do_command(c, sr, "up"); /* go ahead and notify */ + } + + /* install the route */ + + DBG(DBG_CONTROL, + DBG_log("route_and_eroute: firewall_notified: %s" + , firewall_notified ? "true" : "false")); + if (!firewall_notified) + { + /* we're in trouble -- don't do routing */ + } + else if (ro == NULL) + { + /* a new route: no deletion required, but preparation is */ + (void) do_command(c, sr, "prepare"); /* just in case; ignore failure */ + route_installed = do_command(c, sr, "route"); + } + else if (routed(sr->routing) + || routes_agree(ro, c)) + { + route_installed = TRUE; /* nothing to be done */ + } + else + { + /* Some other connection must own the route + * and the route must disagree. But since could_route + * must have allowed our stealing it, we'll do so. + * + * A feature of LINUX allows us to install the new route + * before deleting the old if the nexthops differ. + * This reduces the "window of vulnerability" when packets + * might flow in the clear. + */ + if (sameaddr(&sr->this.host_nexthop, &esr->this.host_nexthop)) + { + (void) do_command(ro, sr, "unroute"); + route_installed = do_command(c, sr, "route"); + } + else + { + route_installed = do_command(c, sr, "route"); + (void) do_command(ro, sr, "unroute"); + } + + /* record unrouting */ + if (route_installed) + { + do { + passert(!erouted(rosr->routing)); + rosr->routing = RT_UNROUTED; + + /* no need to keep old value */ + ro = route_owner(c, &rosr, NULL, NULL); + } while (ro != NULL); + } + } + + /* all done -- clean up */ + if (route_installed) + { + /* Success! */ + + if (bspp != NULL) + { + free_bare_shunt(bspp); + } + else if (ero != NULL && ero != c) + { + /* check if ero is an ancestor of c. */ + struct connection *ero2; + + for (ero2 = c; ero2 != NULL && ero2 != c; ero2 = ero2->policy_next) + ; + + if (ero2 == NULL) + { + /* By elimination, we must be eclipsing ero. Checked above. */ + if (ero->spd.routing != RT_ROUTED_ECLIPSED) + { + ero->spd.routing = RT_ROUTED_ECLIPSED; + eclipse_count++; + } + } + } + + if (st == NULL) + { + passert(sr->eroute_owner == SOS_NOBODY); + sr->routing = RT_ROUTED_PROSPECTIVE; + } + else + { + char cib[CONN_INST_BUF]; + sr->routing = RT_ROUTED_TUNNEL; + + DBG(DBG_CONTROL, + DBG_log("route_and_eroute: instance \"%s\"%s, setting eroute_owner {spd=%p,sr=%p} to #%ld (was #%ld) (newest_ipsec_sa=#%ld)" + , st->st_connection->name + , (fmt_conn_instance(st->st_connection, cib), cib) + , &st->st_connection->spd, sr + , st->st_serialno + , sr->eroute_owner + , st->st_connection->newest_ipsec_sa)); + sr->eroute_owner = st->st_serialno; + } + + return TRUE; + } + else + { + /* Failure! Unwind our work. */ + if (firewall_notified && sr->eroute_owner == SOS_NOBODY) + (void) do_command(c, sr, "down"); + + if (eroute_installed) + { + /* Restore original eroute, if we can. + * Since there is nothing much to be done if the restoration + * fails, ignore success or failure. + */ + if (bspp != NULL) + { + /* Restore old bare_shunt. + * I don't think that this case is very likely. + * Normally a bare shunt would have been assigned + * to a connection before we've gotten this far. + */ + struct bare_shunt *bs = *bspp; + + (void) raw_eroute(&bs->said.dst /* should be useless */ + , &bs->ours + , &bs->said.dst /* should be useless */ + , &bs->his + , bs->said.spi /* network order */ + , SA_INT + , SADB_X_SATYPE_INT + , 0 + , null_proto_info + , SHUNT_PATIENCE + , ERO_REPLACE, "restore"); + } + else if (ero != NULL) + { + /* restore ero's former glory */ + if (esr->eroute_owner == SOS_NOBODY) + { + /* note: normal or eclipse case */ + (void) shunt_eroute(ero, esr + , esr->routing, ERO_REPLACE, "restore"); + } + else + { + /* Try to find state that owned eroute. + * Don't do anything if it cannot be found. + * This case isn't likely since we don't run + * the updown script when replacing a SA group + * with its successor (for the same conn). + */ + struct state *ost = state_with_serialno(esr->eroute_owner); + + if (ost != NULL) + (void) sag_eroute(ost, esr, ERO_REPLACE, "restore"); + } + } + else + { + /* there was no previous eroute: delete whatever we installed */ + if (st == NULL) + (void) shunt_eroute(c, sr + , sr->routing, ERO_DELETE, "delete"); + else + (void) sag_eroute(st, sr + , ERO_DELETE, "delete"); + } + } + + return FALSE; + } +#else /* !KLIPS */ + return TRUE; +#endif /* !KLIPS */ +} + +bool +install_ipsec_sa(struct state *st, bool inbound_also USED_BY_KLIPS) +{ +#ifdef KLIPS + struct spd_route *sr; + + DBG(DBG_CONTROL, DBG_log("install_ipsec_sa() for #%ld: %s" + , st->st_serialno + , inbound_also? + "inbound and outbound" : "outbound only")); + + switch (could_route(st->st_connection)) + { + case route_easy: + case route_nearconflict: + break; + + default: + return FALSE; + } + + /* (attempt to) actually set up the SA group */ + if ((inbound_also && !setup_half_ipsec_sa(st, TRUE)) + || !setup_half_ipsec_sa(st, FALSE)) + return FALSE; + + for (sr = &st->st_connection->spd; sr != NULL; sr = sr->next) + { + DBG(DBG_CONTROL, DBG_log("sr for #%ld: %s" + , st->st_serialno + , enum_name(&routing_story, sr->routing))); + + /* + * if the eroute owner is not us, then make it us. + * See test co-terminal-02, pluto-rekey-01, pluto-unit-02/oppo-twice + */ + pexpect(sr->eroute_owner == SOS_NOBODY + || sr->routing >= RT_ROUTED_TUNNEL); + + if (sr->eroute_owner != st->st_serialno + && sr->routing != RT_UNROUTED_KEYED) + { + if (!route_and_eroute(st->st_connection, sr, st)) + { + delete_ipsec_sa(st, FALSE); + /* XXX go and unroute any SRs that were successfully + * routed already. + */ + return FALSE; + } + } + } +#else /* !KLIPS */ + DBG(DBG_CONTROL, DBG_log("install_ipsec_sa() %s" + , inbound_also? "inbound and oubound" : "outbound only")); + + switch (could_route(st->st_connection)) + { + case route_easy: + case route_nearconflict: + break; + + default: + return FALSE; + } + + +#endif /* !KLIPS */ + + return TRUE; +} + +/* delete an IPSEC SA. + * we may not succeed, but we bull ahead anyway because + * we cannot do anything better by recognizing failure + */ +void +delete_ipsec_sa(struct state *st USED_BY_KLIPS, bool inbound_only USED_BY_KLIPS) +{ +#ifdef KLIPS + if (!inbound_only) + { + /* If the state is the eroute owner, we must adjust + * the routing for the connection. + */ + struct connection *c = st->st_connection; + struct spd_route *sr; + + passert(st->st_connection); + + for (sr = &c->spd; sr; sr = sr->next) + { + if (sr->eroute_owner == st->st_serialno + && sr->routing == RT_ROUTED_TUNNEL) + { + sr->eroute_owner = SOS_NOBODY; + + /* Routing should become RT_ROUTED_FAILURE, + * but if POLICY_FAIL_NONE, then we just go + * right back to RT_ROUTED_PROSPECTIVE as if no + * failure happened. + */ + sr->routing = (c->policy & POLICY_FAIL_MASK) == POLICY_FAIL_NONE + ? RT_ROUTED_PROSPECTIVE : RT_ROUTED_FAILURE; + + (void) do_command(c, sr, "down"); + if ((c->policy & POLICY_DONT_REKEY) + && c->kind == CK_INSTANCE) + { + /* in this special case, even if the connection + * is still alive (due to an ISAKMP SA), + * we get rid of routing. + * Even though there is still an eroute, the c->routing + * setting will convince unroute_connection to delete it. + * unroute_connection would be upset if c->routing == RT_ROUTED_TUNNEL + */ + unroute_connection(c); + } + else + { + (void) shunt_eroute(c, sr, sr->routing, ERO_REPLACE, "replace with shunt"); + } + } + } + (void) teardown_half_ipsec_sa(st, FALSE); + } + (void) teardown_half_ipsec_sa(st, TRUE); +#else /* !KLIPS */ + DBG(DBG_CONTROL, DBG_log("if I knew how, I'd eroute() and teardown_ipsec_sa()")); +#endif /* !KLIPS */ +} + +#ifdef KLIPS +static bool update_nat_t_ipsec_esp_sa (struct state *st, bool inbound) +{ + struct connection *c = st->st_connection; + char text_said[SATOT_BUF]; + struct kernel_sa sa; + ip_address + src = inbound? c->spd.that.host_addr : c->spd.this.host_addr, + dst = inbound? c->spd.this.host_addr : c->spd.that.host_addr; + + ipsec_spi_t esp_spi = inbound? st->st_esp.our_spi : st->st_esp.attrs.spi; + + u_int16_t + natt_sport = inbound? c->spd.that.host_port : c->spd.this.host_port, + natt_dport = inbound? c->spd.this.host_port : c->spd.that.host_port; + + set_text_said(text_said, &dst, esp_spi, SA_ESP); + + memset(&sa, 0, sizeof(sa)); + sa.spi = esp_spi; + sa.src = &src; + sa.dst = &dst; + sa.text_said = text_said; + sa.authalg = alg_info_esp_aa2sadb(st->st_esp.attrs.auth); + sa.natt_sport = natt_sport; + sa.natt_dport = natt_dport; + sa.transid = st->st_esp.attrs.transid; + + return kernel_ops->add_sa(&sa, TRUE); +} +#endif + +bool update_ipsec_sa (struct state *st USED_BY_KLIPS) +{ +#ifdef KLIPS + if (IS_IPSEC_SA_ESTABLISHED(st->st_state)) + { + if (st->st_esp.present && ( + (!update_nat_t_ipsec_esp_sa (st, TRUE)) || + (!update_nat_t_ipsec_esp_sa (st, FALSE)))) + { + return FALSE; + } + } + else if (IS_ONLY_INBOUND_IPSEC_SA_ESTABLISHED(st->st_state)) + { + if (st->st_esp.present && !update_nat_t_ipsec_esp_sa (st, FALSE)) + { + return FALSE; + } + } + else + { + DBG_log("assert failed at %s:%d st_state=%d", __FILE__, __LINE__, st->st_state); + return FALSE; + } + return TRUE; +#else /* !KLIPS */ + DBG(DBG_CONTROL, DBG_log("if I knew how, I'd update_ipsec_sa()")); + return TRUE; +#endif /* !KLIPS */ +} + +/* Check if there was traffic on given SA during the last idle_max + * seconds. If TRUE, the SA was idle and DPD exchange should be performed. + * If FALSE, DPD is not necessary. We also return TRUE for errors, as they + * could mean that the SA is broken and needs to be replace anyway. + */ +bool +was_eroute_idle(struct state *st, time_t idle_max, time_t *idle_time) +{ + static const char procname[] = "/proc/net/ipsec_spi"; + FILE *f; + char buf[1024]; + u_int bytes; + int ret = TRUE; + + passert(st != NULL); + + f = fopen(procname, "r"); + if (f == NULL) + { + /* Can't open the file, perhaps were are on 26sec? */ + time_t use_time; + + if (get_sa_info(st, TRUE, &bytes, &use_time) + && use_time != UNDEFINED_TIME) + { + *idle_time = time(NULL) - use_time; + ret = *idle_time >= idle_max; + } + } + else + { + while (f != NULL) + { + char *line; + char text_said[SATOT_BUF]; + u_int8_t proto = 0; + ip_address dst; + ip_said said; + ipsec_spi_t spi = 0; + static const char idle[] = "idle="; + + dst = st->st_connection->spd.this.host_addr; /* inbound SA */ + if (st->st_ah.present) + { + proto = SA_AH; + spi = st->st_ah.our_spi; + } + if (st->st_esp.present) + { + proto = SA_ESP; + spi = st->st_esp.our_spi; + } + + if (proto == 0 && spi == 0) + { + ret = TRUE; + break; + } + + initsaid(&dst, spi, proto, &said); + satot(&said, 'x', text_said, SATOT_BUF); + + line = fgets(buf, sizeof(buf), f); + if (line == NULL) + { + /* Reached end of list */ + ret = TRUE; + break; + } + + if (strncmp(line, text_said, strlen(text_said)) == 0) + { + /* we found a match, now try to find idle= */ + char *p = strstr(line, idle); + + if (p == NULL) + { + /* SAs which haven't been used yet don't have it */ + ret = TRUE; /* it didn't have traffic */ + break; + } + p += sizeof(idle)-1; + if (*p == '\0') + { + ret = TRUE; /* be paranoid */ + break; + } + if (sscanf(p, "%d", (int *) idle_time) <= 0) + { + ret = TRUE; + break; + } + if (*idle_time >= idle_max) + { + ret = TRUE; + break; + } + else + { + ret = FALSE; + break; + } + } + } + fclose(f); + } + return ret; +} diff --git a/src/pluto/kernel.h b/src/pluto/kernel.h new file mode 100644 index 000000000..e7ff08c7b --- /dev/null +++ b/src/pluto/kernel.h @@ -0,0 +1,198 @@ +/* declarations of routines that interface with the kernel's IPsec mechanism + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: kernel.h,v 1.10 2006/03/08 22:12:37 as Exp $ + */ + +#include "connections.h" + +extern bool no_klips; /* don't actually use KLIPS */ +extern bool can_do_IPcomp; /* can system actually perform IPCOMP? */ + +#ifdef KLIPS +/* Declare eroute things early enough for uses. + * + * Flags are encoded above the low-order byte of verbs. + * "real" eroutes are only outbound. Inbound eroutes don't exist, + * but an addflow with an INBOUND flag allows IPIP tunnels to be + * limited to appropriate source and destination addresses. + */ + +#define ERO_MASK 0xFF +#define ERO_FLAG_SHIFT 8 + +#define ERO_DELETE SADB_X_DELFLOW +#define ERO_ADD SADB_X_ADDFLOW +#define ERO_REPLACE (SADB_X_ADDFLOW | (SADB_X_SAFLAGS_REPLACEFLOW << ERO_FLAG_SHIFT)) +#define ERO_ADD_INBOUND (SADB_X_ADDFLOW | (SADB_X_SAFLAGS_INFLOW << ERO_FLAG_SHIFT)) +#define ERO_DEL_INBOUND (SADB_X_DELFLOW | (SADB_X_SAFLAGS_INFLOW << ERO_FLAG_SHIFT)) + +struct pfkey_proto_info { + int proto; + int encapsulation; + unsigned reqid; +}; +struct sadb_msg; + +struct kernel_sa { + const ip_address *src; + const ip_address *dst; + + const ip_subnet *src_client; + const ip_subnet *dst_client; + + ipsec_spi_t spi; + unsigned proto; + unsigned satype; + unsigned transport_proto; + unsigned replay_window; + unsigned reqid; + + unsigned authalg; + unsigned authkeylen; + char *authkey; + + unsigned encalg; + unsigned enckeylen; + char *enckey; + + unsigned compalg; + + int encapsulation; + + u_int16_t natt_sport, natt_dport; + u_int8_t transid, natt_type; + ip_address *natt_oa; + + const char *text_said; +}; + +struct kernel_ops { + enum { + KERNEL_TYPE_NONE, + KERNEL_TYPE_KLIPS, + KERNEL_TYPE_LINUX, + } type; + bool inbound_eroute; + bool policy_lifetime; + int *async_fdp; + + void (*init)(void); + void (*pfkey_register)(void); + void (*pfkey_register_response)(const struct sadb_msg *msg); + void (*process_queue)(void); + void (*process_msg)(void); + bool (*raw_eroute)(const ip_address *this_host, + const ip_subnet *this_client, + const ip_address *that_host, + const ip_subnet *that_client, + ipsec_spi_t spi, + unsigned int satype, + unsigned int transport_proto, + const struct pfkey_proto_info *proto_info, + time_t use_lifetime, + unsigned int op, + const char *text_said); + bool (*get_policy)(const struct kernel_sa *sa, bool inbound, + time_t *use_time); + bool (*add_sa)(const struct kernel_sa *sa, bool replace); + bool (*grp_sa)(const struct kernel_sa *sa_outer, + const struct kernel_sa *sa_inner); + bool (*del_sa)(const struct kernel_sa *sa); + bool (*get_sa)(const struct kernel_sa *sa, u_int *bytes); + ipsec_spi_t (*get_spi)(const ip_address *src, + const ip_address *dst, + int proto, + bool tunnel_mode, + unsigned reqid, + ipsec_spi_t min, + ipsec_spi_t max, + const char *text_said); +}; + + +extern const struct kernel_ops *kernel_ops; + +/* information from /proc/net/ipsec_eroute */ + +struct eroute_info { + unsigned long count; + ip_subnet ours; + ip_subnet his; + ip_address dst; + ip_said said; + int transport_proto; + struct eroute_info *next; +}; + +extern struct eroute_info *orphaned_holds; + +extern void show_shunt_status(void); +#endif + +/* A netlink header defines EM_MAXRELSPIS, the max number of SAs in a group. + * Is there a PF_KEY equivalent? + */ +#ifndef EM_MAXRELSPIS +# define EM_MAXRELSPIS 4 /* AH ESP IPCOMP IPIP */ +#endif + +extern void record_and_initiate_opportunistic(const ip_subnet * + , const ip_subnet * + , int transport_proto + , const char *why); + +extern void init_kernel(void); + +extern void scan_proc_shunts(void); + +extern bool trap_connection(struct connection *c); +extern void unroute_connection(struct connection *c); + +extern bool has_bare_hold(const ip_address *src, const ip_address *dst + , int transport_proto); + +extern bool replace_bare_shunt(const ip_address *src, const ip_address *dst + , policy_prio_t policy_prio + , ipsec_spi_t shunt_spi /* in host order! */ + , bool repl + , unsigned int transport_proto + , const char *why); + +extern bool assign_hold(struct connection *c + , struct spd_route *sr + , int transport_proto + , const ip_address *src, const ip_address *dst); + +extern ipsec_spi_t shunt_policy_spi(struct connection *c, bool prospective); + + +struct state; /* forward declaration of tag */ +extern ipsec_spi_t get_ipsec_spi(ipsec_spi_t avoid + , int proto + , struct spd_route *sr + , bool tunnel_mode); +extern ipsec_spi_t get_my_cpi(struct spd_route *sr, bool tunnel_mode); + +extern bool install_inbound_ipsec_sa(struct state *st); +extern bool install_ipsec_sa(struct state *st, bool inbound_also); +extern void delete_ipsec_sa(struct state *st, bool inbound_only); +extern bool route_and_eroute(struct connection *c + , struct spd_route *sr + , struct state *st); +extern bool was_eroute_idle(struct state *st, time_t idle_max + , time_t *idle_time); +extern bool get_sa_info(struct state *st, bool inbound, u_int *bytes + , time_t *use_time); + +extern bool update_ipsec_sa(struct state *st); diff --git a/src/pluto/kernel_alg.c b/src/pluto/kernel_alg.c new file mode 100644 index 000000000..91dfaff59 --- /dev/null +++ b/src/pluto/kernel_alg.c @@ -0,0 +1,775 @@ +/* Kernel runtime algorithm handling interface + * Author: JuanJo Ciarlante <jjo-ipsec@mendoza.gov.ar> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: kernel_alg.c,v 1.9 2005/08/17 16:31:24 as Exp $ + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <sys/queue.h> + +#include <pfkeyv2.h> +#include <pfkey.h> + +#include <freeswan.h> +#include <ipsec_policy.h> + +#include "constants.h" +#include "defs.h" +#include "connections.h" +#include "state.h" +#include "packet.h" +#include "spdb.h" +#include "kernel.h" +#include "kernel_alg.h" +#include "alg_info.h" + +#ifndef NO_PLUTO +#include "log.h" +#include "whack.h" +#include "db_ops.h" +#else +/* + * macros/functions for compilation without pluto (eg: spi for manual conns) + */ +extern int debug; +#include <assert.h> +#define passert(x) assert(x) +#define DBG(cond, action) { if (debug) { action ; } } +#define DBG_log(x, args...) fprintf(stderr, x "\n" , ##args); +#define plog(x, args...) fprintf(stderr, x "\n" , ##args); +#endif /* NO_PLUTO */ +/* ALG storage */ +static struct sadb_alg esp_aalg[SADB_AALG_MAX+1]; +static struct sadb_alg esp_ealg[SADB_EALG_MAX+1]; +static int esp_ealg_num = 0; +static int esp_aalg_num = 0; + +#define ESP_EALG_PRESENT(algo) (((algo)<=SADB_EALG_MAX)&&(esp_ealg[(algo)].sadb_alg_id==(algo))) +#define ESP_EALG_FOR_EACH_UPDOWN(algo) \ + for (algo=SADB_EALG_MAX; algo >0 ; algo--) \ + if (ESP_EALG_PRESENT(algo)) +#define ESP_AALG_PRESENT(algo) ((algo<=SADB_AALG_MAX)&&(esp_aalg[(algo)].sadb_alg_id==(algo))) +#define ESP_AALG_FOR_EACH_UPDOWN(algo) \ + for (algo=SADB_AALG_MAX; algo >0 ; algo--) \ + if (ESP_AALG_PRESENT(algo)) + +static struct sadb_alg* +sadb_alg_ptr (int satype, int exttype, int alg_id, int rw) +{ + struct sadb_alg *alg_p = NULL; + + switch (exttype) + { + case SADB_EXT_SUPPORTED_AUTH: + if (alg_id > SADB_AALG_MAX) + return NULL; + break; + case SADB_EXT_SUPPORTED_ENCRYPT: + if (alg_id > SADB_EALG_MAX) + return NULL; + break; + default: + return NULL; + } + + switch (satype) + { + case SADB_SATYPE_ESP: + alg_p = (exttype == SADB_EXT_SUPPORTED_ENCRYPT)? + &esp_ealg[alg_id] : &esp_aalg[alg_id]; + /* get for write: increment elem count */ + if (rw) + { + (exttype == SADB_EXT_SUPPORTED_ENCRYPT)? + esp_ealg_num++ : esp_aalg_num++; + } + break; + case SADB_SATYPE_AH: + default: + return NULL; + } + + return alg_p; +} + +const struct sadb_alg * +kernel_alg_sadb_alg_get(int satype, int exttype, int alg_id) +{ + return sadb_alg_ptr(satype, exttype, alg_id, 0); +} + +/* + * Forget previous registration + */ +static void +kernel_alg_init(void) +{ + DBG(DBG_KLIPS, + DBG_log("alg_init(): memset(%p, 0, %d) memset(%p, 0, %d)", + &esp_aalg, (int)sizeof (esp_aalg), + &esp_ealg, (int)sizeof (esp_ealg)) + ) + memset (&esp_aalg, 0, sizeof (esp_aalg)); + memset (&esp_ealg, 0, sizeof (esp_ealg)); + esp_ealg_num=esp_aalg_num = 0; +} + +static int +kernel_alg_add(int satype, int exttype, const struct sadb_alg *sadb_alg) +{ + struct sadb_alg *alg_p = NULL; + int alg_id = sadb_alg->sadb_alg_id; + + DBG(DBG_KLIPS, + DBG_log("kernel_alg_add(): satype=%d, exttype=%d, alg_id=%d", + satype, exttype, sadb_alg->sadb_alg_id) + ) + if (!(alg_p = sadb_alg_ptr(satype, exttype, alg_id, 1))) + return -1; + + /* This logic "mimics" KLIPS: first algo implementation will be used */ + if (alg_p->sadb_alg_id) + { + DBG(DBG_KLIPS, + DBG_log("kernel_alg_add(): discarding already setup " + "satype=%d, exttype=%d, alg_id=%d", + satype, exttype, sadb_alg->sadb_alg_id) + ) + return 0; + } + *alg_p = *sadb_alg; + return 1; +} + +bool +kernel_alg_esp_enc_ok(u_int alg_id, u_int key_len, + struct alg_info_esp *alg_info __attribute__((unused))) +{ + struct sadb_alg *alg_p = NULL; + + /* + * test #1: encrypt algo must be present + */ + int ret = ESP_EALG_PRESENT(alg_id); + if (!ret) goto out; + + alg_p = &esp_ealg[alg_id]; + + /* + * test #2: if key_len specified, it must be in range + */ + if (key_len + && (key_len < alg_p->sadb_alg_minbits || key_len > alg_p->sadb_alg_maxbits)) + { + plog("kernel_alg_db_add() key_len not in range: alg_id=%d, " + "key_len=%d, alg_minbits=%d, alg_maxbits=%d" + , alg_id, key_len + , alg_p->sadb_alg_minbits + , alg_p->sadb_alg_maxbits); + ret = FALSE; + } + +out: + if (ret) + { + DBG(DBG_KLIPS, + DBG_log("kernel_alg_esp_enc_ok(%d,%d): " + "alg_id=%d, " + "alg_ivlen=%d, alg_minbits=%d, alg_maxbits=%d, " + "res=%d, ret=%d" + , alg_id, key_len + , alg_p->sadb_alg_id + , alg_p->sadb_alg_ivlen + , alg_p->sadb_alg_minbits + , alg_p->sadb_alg_maxbits + , alg_p->sadb_alg_reserved + , ret); + ) + } + else + { + DBG(DBG_KLIPS, + DBG_log("kernel_alg_esp_enc_ok(%d,%d): NO", alg_id, key_len); + ) + } + return ret; +} + +/* + * ML: make F_STRICT logic consider enc,auth algorithms + */ +#ifndef NO_PLUTO +bool +kernel_alg_esp_ok_final(u_int ealg, u_int key_len, u_int aalg, struct alg_info_esp *alg_info) +{ + int ealg_insecure; + + /* + * key_len passed comes from esp_attrs read from peer + * For many older algoritms (eg 3DES) this key_len is fixed + * and get passed as 0. + * ... then get default key_len + */ + if (key_len == 0) + key_len = kernel_alg_esp_enc_keylen(ealg) * BITS_PER_BYTE; + + /* + * simple test to toss low key_len, will accept it only + * if specified in "esp" string + */ + ealg_insecure = (key_len < 128) ; + + if (ealg_insecure + || (alg_info && alg_info->alg_info_flags & ALG_INFO_F_STRICT)) + { + int i; + struct esp_info *esp_info; + + if (alg_info) + { + ALG_INFO_ESP_FOREACH(alg_info, esp_info, i) + { + if (esp_info->esp_ealg_id == ealg + && (esp_info->esp_ealg_keylen == 0 || key_len == 0 + || esp_info->esp_ealg_keylen == key_len) + && esp_info->esp_aalg_id == aalg) + { + if (ealg_insecure) + { + loglog(RC_LOG_SERIOUS + , "You should NOT use insecure ESP algorithms [%s (%d)]!" + , enum_name(&esp_transformid_names, ealg), key_len); + } + return TRUE; + } + } + } + plog("IPSec Transform [%s (%d), %s] refused due to %s", + enum_name(&esp_transformid_names, ealg), key_len, + enum_name(&auth_alg_names, aalg), + ealg_insecure ? "insecure key_len and enc. alg. not listed in \"esp\" string" : "strict flag"); + return FALSE; + } + return TRUE; +} +#endif /* NO_PLUTO */ + +/* + * Load kernel_alg arrays from /proc + * used in manual mode from klips/utils/spi.c + */ +int +kernel_alg_proc_read(void) +{ + int satype; + int supp_exttype; + int alg_id, ivlen, minbits, maxbits; + struct sadb_alg sadb_alg; + int ret; + char buf[128]; + + FILE *fp=fopen("/proc/net/pf_key_supported", "r"); + + if (!fp) + return -1; + + kernel_alg_init(); + + while (fgets(buf, sizeof(buf), fp)) + { + if (buf[0] != ' ') /* skip titles */ + continue; + + sscanf(buf, "%d %d %d %d %d %d" + ,&satype, &supp_exttype + , &alg_id, &ivlen + , &minbits, &maxbits); + + switch (satype) + { + case SADB_SATYPE_ESP: + switch(supp_exttype) + { + case SADB_EXT_SUPPORTED_AUTH: + case SADB_EXT_SUPPORTED_ENCRYPT: + sadb_alg.sadb_alg_id = alg_id; + sadb_alg.sadb_alg_ivlen = ivlen; + sadb_alg.sadb_alg_minbits = minbits; + sadb_alg.sadb_alg_maxbits = maxbits; + ret = kernel_alg_add(satype, supp_exttype, &sadb_alg); + DBG(DBG_CRYPT, + DBG_log("kernel_alg_proc_read() alg_id=%d, " + "alg_ivlen=%d, alg_minbits=%d, alg_maxbits=%d, " + "ret=%d" + , sadb_alg.sadb_alg_id + , sadb_alg.sadb_alg_ivlen + , sadb_alg.sadb_alg_minbits + , sadb_alg.sadb_alg_maxbits + , ret) + ) + } + default: + continue; + } + } + fclose(fp); + return 0; +} + +/* + * Load kernel_alg arrays pluto's SADB_REGISTER + * user by pluto/kernel.c + */ + +void +kernel_alg_register_pfkey(const struct sadb_msg *msg_buf, int buflen) +{ + /* Trick: one 'type-mangle-able' pointer to ease offset/assign */ + union { + const struct sadb_msg *msg; + const struct sadb_supported *supported; + const struct sadb_ext *ext; + const struct sadb_alg *alg; + const char *ch; + } sadb; + + int satype; + int msglen; + int i = 0; + + /* Initialize alg arrays */ + kernel_alg_init(); + satype = msg_buf->sadb_msg_satype; + sadb.msg = msg_buf; + msglen = sadb.msg->sadb_msg_len*IPSEC_PFKEYv2_ALIGN; + msglen -= sizeof(struct sadb_msg); + buflen -= sizeof(struct sadb_msg); + passert(buflen > 0); + + sadb.msg++; + + while(msglen) + { + int supp_exttype = sadb.supported->sadb_supported_exttype; + int supp_len = sadb.supported->sadb_supported_len*IPSEC_PFKEYv2_ALIGN; + + DBG(DBG_KLIPS, + DBG_log("kernel_alg_register_pfkey(): SADB_SATYPE_%s: " + "sadb_msg_len=%d sadb_supported_len=%d" + , satype==SADB_SATYPE_ESP? "ESP" : "AH" + , msg_buf->sadb_msg_len, supp_len) + ) + sadb.supported++; + msglen -= supp_len; + buflen -= supp_len; + passert(buflen >= 0); + + for (supp_len -= sizeof(struct sadb_supported); + supp_len; + supp_len -= sizeof(struct sadb_alg), sadb.alg++,i++) + { + int ret = kernel_alg_add(satype, supp_exttype, sadb.alg); + + DBG(DBG_KLIPS, + DBG_log("kernel_alg_register_pfkey(): SADB_SATYPE_%s: " + "alg[%d], exttype=%d, satype=%d, alg_id=%d, " + "alg_ivlen=%d, alg_minbits=%d, alg_maxbits=%d, " + "res=%d, ret=%d" + , satype==SADB_SATYPE_ESP? "ESP" : "AH" + , i + , supp_exttype + , satype + , sadb.alg->sadb_alg_id + , sadb.alg->sadb_alg_ivlen + , sadb.alg->sadb_alg_minbits + , sadb.alg->sadb_alg_maxbits + , sadb.alg->sadb_alg_reserved + , ret) + ) + } + } +} + +u_int +kernel_alg_esp_enc_keylen(u_int alg_id) +{ + u_int keylen = 0; + + if (!ESP_EALG_PRESENT(alg_id)) + goto none; + + keylen = esp_ealg[alg_id].sadb_alg_maxbits/BITS_PER_BYTE; + + switch (alg_id) + { + /* + * this is veryUgly[TM] + * Peer should have sent KEY_LENGTH attribute for ESP_AES + * but if not do force it to 128 instead of using sadb_alg_maxbits + * from kernel. + */ + case ESP_AES: + keylen = 128/BITS_PER_BYTE; + break; + } + +none: + DBG(DBG_KLIPS, + DBG_log("kernel_alg_esp_enc_keylen():" + "alg_id=%d, keylen=%d", + alg_id, keylen) + ) + return keylen; +} + +struct sadb_alg * +kernel_alg_esp_sadb_alg(u_int alg_id) +{ + struct sadb_alg *sadb_alg = (ESP_EALG_PRESENT(alg_id)) + ? &esp_ealg[alg_id] : NULL; + + DBG(DBG_KLIPS, + DBG_log("kernel_alg_esp_sadb_alg(): alg_id=%d, sadb_alg=%p" + , alg_id, sadb_alg) + ) + return sadb_alg; +} + +#ifndef NO_PLUTO +void kernel_alg_list(void) +{ + u_int sadb_id; + + whack_log(RC_COMMENT, " "); + whack_log(RC_COMMENT, "List of registered ESP Encryption Algorithms:"); + whack_log(RC_COMMENT, " "); + + for (sadb_id = 1; sadb_id <= SADB_EALG_MAX; sadb_id++) + { + if (ESP_EALG_PRESENT(sadb_id)) + { + struct sadb_alg *alg_p = &esp_ealg[sadb_id]; + + whack_log(RC_COMMENT, "#%-5d %s, blocksize: %d, keylen: %d-%d" + , sadb_id + , enum_name(&esp_transformid_names, sadb_id) + , alg_p->sadb_alg_ivlen + , alg_p->sadb_alg_minbits + , alg_p->sadb_alg_maxbits + ); + } + } + + whack_log(RC_COMMENT, " "); + whack_log(RC_COMMENT, "List of registered ESP Authentication Algorithms:"); + whack_log(RC_COMMENT, " "); + + for (sadb_id = 1; sadb_id <= SADB_AALG_MAX; sadb_id++) + { + if (ESP_AALG_PRESENT(sadb_id)) + { + u_int aaid = alg_info_esp_sadb2aa(sadb_id); + struct sadb_alg *alg_p = &esp_aalg[sadb_id]; + + whack_log(RC_COMMENT, "#%-5d %s, keylen: %d-%d" + , aaid + , enum_name(&auth_alg_names, aaid) + , alg_p->sadb_alg_minbits + , alg_p->sadb_alg_maxbits + ); + } + } +} + +void +kernel_alg_show_connection(struct connection *c, const char *instance) +{ + char buf[256]; + struct state *st; + + if (c->alg_info_esp) + { + alg_info_snprint(buf, sizeof(buf), (struct alg_info *)c->alg_info_esp); + whack_log(RC_COMMENT + , "\"%s\"%s: ESP algorithms wanted: %s" + , c->name + , instance + , buf); + } + if (c->alg_info_esp) + { + alg_info_snprint_esp(buf, sizeof(buf), c->alg_info_esp); + whack_log(RC_COMMENT + , "\"%s\"%s: ESP algorithms loaded: %s" + , c->name + , instance + , buf); + } + st = state_with_serialno(c->newest_ipsec_sa); + if (st && st->st_esp.present) + whack_log(RC_COMMENT + , "\"%s\"%s: ESP algorithm newest: %s_%d-%s; pfsgroup=%s" + , c->name + , instance + , enum_show(&esp_transformid_names, st->st_esp.attrs.transid) + +4 /* strlen("ESP_") */ + , st->st_esp.attrs.key_len + , enum_show(&auth_alg_names, st->st_esp.attrs.auth)+ + +15 /* strlen("AUTH_ALGORITHM_") */ + , c->policy & POLICY_PFS ? + c->alg_info_esp->esp_pfsgroup ? + enum_show(&oakley_group_names, + c->alg_info_esp->esp_pfsgroup) + +13 /*strlen("OAKLEY_GROUP_")*/ + : "<Phase1>" + : "<N/A>" + ); +} +#endif /* NO_PLUTO */ + +bool +kernel_alg_esp_auth_ok(u_int auth, + struct alg_info_esp *alg_info __attribute__((unused))) +{ + return ESP_AALG_PRESENT(alg_info_esp_aa2sadb(auth)); +} + +u_int +kernel_alg_esp_auth_keylen(u_int auth) +{ + u_int sadb_aalg = alg_info_esp_aa2sadb(auth); + + u_int a_keylen = (sadb_aalg) + ? esp_aalg[sadb_aalg].sadb_alg_maxbits/BITS_PER_BYTE + : 0; + + DBG(DBG_CONTROL | DBG_CRYPT | DBG_PARSING, + DBG_log("kernel_alg_esp_auth_keylen(auth=%d, sadb_aalg=%d): " + "a_keylen=%d", auth, sadb_aalg, a_keylen) + ) + return a_keylen; +} + +struct esp_info * +kernel_alg_esp_info(int transid, int auth) +{ + int sadb_aalg, sadb_ealg; + static struct esp_info ei_buf; + + sadb_ealg = transid; + sadb_aalg = alg_info_esp_aa2sadb(auth); + + if (!ESP_EALG_PRESENT(sadb_ealg)) + goto none; + if (!ESP_AALG_PRESENT(sadb_aalg)) + goto none; + + memset(&ei_buf, 0, sizeof (ei_buf)); + ei_buf.transid = transid; + ei_buf.auth = auth; + + /* don't return "default" keylen because this value is used from + * setup_half_ipsec_sa() to "validate" keylen + * In effect, enckeylen will be used as "max" value + */ + ei_buf.enckeylen = esp_ealg[sadb_ealg].sadb_alg_maxbits/BITS_PER_BYTE; + ei_buf.authkeylen = esp_aalg[sadb_aalg].sadb_alg_maxbits/BITS_PER_BYTE; + ei_buf.encryptalg = sadb_ealg; + ei_buf.authalg = sadb_aalg; + + DBG(DBG_PARSING, + DBG_log("kernel_alg_esp_info():" + "transid=%d, auth=%d, ei=%p, " + "enckeylen=%d, authkeylen=%d, encryptalg=%d, authalg=%d", + transid, auth, &ei_buf, + (int)ei_buf.enckeylen, (int)ei_buf.authkeylen, + ei_buf.encryptalg, ei_buf.authalg) + ) + return &ei_buf; + +none: + DBG(DBG_PARSING, + DBG_log("kernel_alg_esp_info():" + "transid=%d, auth=%d, ei=NULL", + transid, auth) + ) + return NULL; +} + +#ifndef NO_PLUTO +static void +kernel_alg_policy_algorithms(struct esp_info *esp_info) +{ + u_int ealg_id = esp_info->esp_ealg_id; + + switch(ealg_id) + { + case 0: + case ESP_DES: + case ESP_3DES: + case ESP_NULL: + case ESP_CAST: + break; + default: + if (!esp_info->esp_ealg_keylen) + { + /* algos that need KEY_LENGTH + * + * Note: this is a very dirty hack ;-) + * Idea: Add a key_length_needed attribute to + * esp_ealg ?? + */ + esp_info->esp_ealg_keylen = esp_ealg[ealg_id].sadb_alg_maxbits; + } + } +} + +static bool +kernel_alg_db_add(struct db_context *db_ctx, struct esp_info *esp_info, lset_t policy) +{ + u_int ealg_id, aalg_id; + + ealg_id = esp_info->esp_ealg_id; + + if (!ESP_EALG_PRESENT(ealg_id)) + { + DBG_log("kernel_alg_db_add() kernel enc ealg_id=%d not present", ealg_id); + return FALSE; + } + + if (!(policy & POLICY_AUTHENTICATE)) /* skip ESP auth attrs for AH */ + { + aalg_id = alg_info_esp_aa2sadb(esp_info->esp_aalg_id); + + if (!ESP_AALG_PRESENT(aalg_id)) + { + DBG_log("kernel_alg_db_add() kernel auth " + "aalg_id=%d not present", aalg_id); + return FALSE; + } + } + + /* do algo policy */ + kernel_alg_policy_algorithms(esp_info); + + /* open new transformation */ + db_trans_add(db_ctx, ealg_id); + + /* add ESP auth attr */ + if (!(policy & POLICY_AUTHENTICATE)) + db_attr_add_values(db_ctx, AUTH_ALGORITHM, esp_info->esp_aalg_id); + + /* add keylegth if specified in esp= string */ + if (esp_info->esp_ealg_keylen) + db_attr_add_values(db_ctx, KEY_LENGTH, esp_info->esp_ealg_keylen); + + return TRUE; +} + +/* + * Create proposal with runtime kernel algos, merging + * with passed proposal if not NULL + * + * for now this function does free() previous returned + * malloced pointer (this quirk allows easier spdb.c change) + */ +struct db_context * +kernel_alg_db_new(struct alg_info_esp *alg_info, lset_t policy ) +{ + const struct esp_info *esp_info; + struct esp_info tmp_esp_info; + struct db_context *ctx_new=NULL; + struct db_trans *t; + struct db_prop *prop; + u_int trans_cnt; + int tn = 0; + + if (!(policy & POLICY_ENCRYPT)) /* not possible, I think */ + return NULL; + + trans_cnt = esp_ealg_num * esp_aalg_num; + DBG(DBG_EMITTING, + DBG_log("kernel_alg_db_prop_new() initial trans_cnt=%d" + , trans_cnt) + ) + + /* pass aprox. number of transforms and attributes */ + ctx_new = db_prop_new(PROTO_IPSEC_ESP, trans_cnt, trans_cnt * 2); + + /* + * Loop: for each element (struct esp_info) of alg_info, + * if kernel support is present then build the transform (and attrs) + * if NULL alg_info, propose everything ... + */ + + if (alg_info) + { + int i; + + ALG_INFO_ESP_FOREACH(alg_info, esp_info, i) + { + tmp_esp_info = *esp_info; + kernel_alg_db_add(ctx_new, &tmp_esp_info, policy); + } + } + else + { + u_int ealg_id; + + ESP_EALG_FOR_EACH_UPDOWN(ealg_id) + { + u_int aalg_id; + + tmp_esp_info.esp_ealg_id = ealg_id; + tmp_esp_info.esp_ealg_keylen = 0; + + for (aalg_id = 1; aalg_id <= SADB_AALG_MAX; aalg_id++) + { + if (ESP_AALG_PRESENT(aalg_id)) + { + tmp_esp_info.esp_aalg_id = alg_info_esp_sadb2aa(aalg_id); + tmp_esp_info.esp_aalg_keylen = 0; + kernel_alg_db_add(ctx_new, &tmp_esp_info, policy); + } + } + } + } + + prop = db_prop_get(ctx_new); + + DBG(DBG_CONTROL|DBG_EMITTING, + DBG_log("kernel_alg_db_prop_new() " + "will return p_new->protoid=%d, p_new->trans_cnt=%d" + , prop->protoid, prop->trans_cnt) + ) + + for (t = prop->trans, tn = 0; tn < prop->trans_cnt; tn++) + { + DBG(DBG_CONTROL|DBG_EMITTING, + DBG_log("kernel_alg_db_prop_new() " + " trans[%d]: transid=%d, attr_cnt=%d, " + "attrs[0].type=%d, attrs[0].val=%d" + , tn + , t[tn].transid, t[tn].attr_cnt + , t[tn].attrs[0].type, t[tn].attrs[0].val) + ) + } + return ctx_new; +} +#endif /* NO_PLUTO */ diff --git a/src/pluto/kernel_alg.h b/src/pluto/kernel_alg.h new file mode 100644 index 000000000..483e97da1 --- /dev/null +++ b/src/pluto/kernel_alg.h @@ -0,0 +1,46 @@ +/* Kernel runtime algorithm handling interface definitions + * Author: JuanJo Ciarlante <jjo-ipsec@mendoza.gov.ar> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: kernel_alg.h,v 1.5 2005/08/17 16:31:24 as Exp $ + */ + +#ifndef _KERNEL_ALG_H +#define _KERNEL_ALG_H + +#include "alg_info.h" +#include "spdb.h" + +/* status info */ +extern void kernel_alg_show_status(void); +void kernel_alg_show_connection(struct connection *c, const char *instance); + +/* Registration messages from pluto */ +extern void kernel_alg_register_pfkey(const struct sadb_msg *msg, int buflen); + +/* ESP interface */ +extern struct sadb_alg *kernel_alg_esp_sadb_alg(u_int alg_id); +extern u_int kernel_alg_esp_ivlen(u_int alg_id); +extern bool kernel_alg_esp_enc_ok(u_int alg_id, u_int key_len, struct alg_info_esp *nfo); +extern bool kernel_alg_esp_ok_final(u_int ealg, u_int key_len, u_int aalg, struct alg_info_esp *alg_info); +extern u_int kernel_alg_esp_enc_keylen(u_int alg_id); +extern bool kernel_alg_esp_auth_ok(u_int auth, struct alg_info_esp *nfo); +extern u_int kernel_alg_esp_auth_keylen(u_int auth); +extern int kernel_alg_proc_read(void); +extern void kernel_alg_list(void); + +/* get sadb_alg for passed args */ +extern const struct sadb_alg * kernel_alg_sadb_alg_get(int satype, int exttype, int alg_id); + +extern struct db_context * kernel_alg_db_new(struct alg_info_esp *ai, lset_t policy); +struct esp_info * kernel_alg_esp_info(int esp_id, int auth_id); +#endif /* _KERNEL_ALG_H */ diff --git a/src/pluto/kernel_netlink.c b/src/pluto/kernel_netlink.c new file mode 100644 index 000000000..1947ddbac --- /dev/null +++ b/src/pluto/kernel_netlink.c @@ -0,0 +1,1219 @@ +/* netlink interface to the kernel's IPsec mechanism + * Copyright (C) 2003 Herbert Xu. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: kernel_netlink.c,v 1.24 2006/03/10 14:49:43 as Exp $ + */ + +#if defined(linux) && defined(KERNEL26_SUPPORT) + +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/queue.h> +#include <unistd.h> + +#include "kameipsec.h" +#include "linux26/rtnetlink.h" +#include "linux26/xfrm.h" + +#include <freeswan.h> +#include <pfkeyv2.h> +#include <pfkey.h> + +#include "constants.h" +#include "defs.h" +#include "kernel.h" +#include "kernel_netlink.h" +#include "kernel_pfkey.h" +#include "log.h" +#include "whack.h" /* for RC_LOG_SERIOUS */ +#include "kernel_alg.h" + +/* Minimum priority number in SPD used by pluto. */ +#define MIN_SPD_PRIORITY 1024 + +static int netlinkfd = NULL_FD; +static int netlink_bcast_fd = NULL_FD; + +#define NE(x) { x, #x } /* Name Entry -- shorthand for sparse_names */ + +static sparse_names xfrm_type_names = { + NE(NLMSG_NOOP), + NE(NLMSG_ERROR), + NE(NLMSG_DONE), + NE(NLMSG_OVERRUN), + + NE(XFRM_MSG_NEWSA), + NE(XFRM_MSG_DELSA), + NE(XFRM_MSG_GETSA), + + NE(XFRM_MSG_NEWPOLICY), + NE(XFRM_MSG_DELPOLICY), + NE(XFRM_MSG_GETPOLICY), + + NE(XFRM_MSG_ALLOCSPI), + NE(XFRM_MSG_ACQUIRE), + NE(XFRM_MSG_EXPIRE), + + NE(XFRM_MSG_UPDPOLICY), + NE(XFRM_MSG_UPDSA), + + NE(XFRM_MSG_POLEXPIRE), + + NE(XFRM_MSG_MAX), + + { 0, sparse_end } +}; + +#undef NE + +/* Authentication algorithms */ +static sparse_names aalg_list = { + { SADB_X_AALG_NULL, "digest_null" }, + { SADB_AALG_MD5_HMAC, "md5" }, + { SADB_AALG_SHA1_HMAC, "sha1" }, + { SADB_AALG_SHA2_256_HMAC, "sha256" }, + { SADB_AALG_SHA2_384_HMAC, "sha384" }, + { SADB_AALG_SHA2_512_HMAC, "sha512" }, + { SADB_AALG_RIPEMD_160_HMAC, "ripemd160" }, + { SADB_X_AALG_NULL, "null" }, + { 0, sparse_end } +}; + +/* Encryption algorithms */ +static sparse_names ealg_list = { + { SADB_EALG_NULL, "cipher_null" }, + { SADB_EALG_DES_CBC, "des" }, + { SADB_EALG_3DES_CBC, "des3_ede" }, + { SADB_EALG_IDEA_CBC, "idea" }, + { SADB_EALG_CAST_CBC, "cast128" }, + { SADB_EALG_BLOWFISH_CBC, "blowfish" }, + { SADB_EALG_AES_CBC, "aes" }, + { SADB_X_EALG_SERPENT_CBC, "serpent" }, + { SADB_X_EALG_TWOFISH_CBC, "twofish" }, + { 0, sparse_end } +}; + +/* Compression algorithms */ +static sparse_names calg_list = { + { SADB_X_CALG_DEFLATE, "deflate" }, + { SADB_X_CALG_LZS, "lzs" }, + { SADB_X_CALG_LZJH, "lzjh" }, + { 0, sparse_end } +}; + +/** ip2xfrm - Take an IP address and convert to an xfrm. + * + * @param addr ip_address + * @param xaddr xfrm_address_t - IPv[46] Address from addr is copied here. + */ +static void +ip2xfrm(const ip_address *addr, xfrm_address_t *xaddr) +{ + if (addr->u.v4.sin_family == AF_INET) + { + xaddr->a4 = addr->u.v4.sin_addr.s_addr; + } + else + { + memcpy(xaddr->a6, &addr->u.v6.sin6_addr, sizeof(xaddr->a6)); + } +} + +/** init_netlink - Initialize the netlink inferface. Opens the sockets and + * then binds to the broadcast socket. + */ +static void +init_netlink(void) +{ + struct sockaddr_nl addr; + + netlinkfd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_XFRM); + + if (netlinkfd < 0) + exit_log_errno((e, "socket() in init_netlink()")); + + if (fcntl(netlinkfd, F_SETFD, FD_CLOEXEC) != 0) + exit_log_errno((e, "fcntl(FD_CLOEXEC) in init_netlink()")); + + netlink_bcast_fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_XFRM); + + if (netlink_bcast_fd < 0) + exit_log_errno((e, "socket() for bcast in init_netlink()")); + + if (fcntl(netlink_bcast_fd, F_SETFD, FD_CLOEXEC) != 0) + exit_log_errno((e, "fcntl(FD_CLOEXEC) for bcast in init_netlink()")); + + if (fcntl(netlink_bcast_fd, F_SETFL, O_NONBLOCK) != 0) + exit_log_errno((e, "fcntl(O_NONBLOCK) for bcast in init_netlink()")); + + addr.nl_family = AF_NETLINK; + addr.nl_pid = getpid(); + addr.nl_groups = XFRMGRP_ACQUIRE | XFRMGRP_EXPIRE; + if (bind(netlink_bcast_fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) + exit_log_errno((e, "Failed to bind bcast socket in init_netlink()")); +} + +/** send_netlink_msg + * + * @param hdr - Data to be sent. + * @param rbuf - Return Buffer - contains data returned from the send. + * @param rbuf_len - Length of rbuf + * @param description - String - user friendly description of what is + * being attempted. Used for diagnostics + * @param text_said - String + * @return bool True if the message was succesfully sent. + */ +static bool +send_netlink_msg(struct nlmsghdr *hdr, struct nlmsghdr *rbuf, size_t rbuf_len +, const char *description, const char *text_said) +{ + struct { + struct nlmsghdr n; + struct nlmsgerr e; + char data[1024]; + } rsp; + + size_t len; + ssize_t r; + struct sockaddr_nl addr; + static uint32_t seq; + + if (no_klips) + { + return TRUE; + } + + hdr->nlmsg_seq = ++seq; + len = hdr->nlmsg_len; + do { + r = write(netlinkfd, hdr, len); + } while (r < 0 && errno == EINTR); + if (r < 0) + { + log_errno((e + , "netlink write() of %s message" + " for %s %s failed" + , sparse_val_show(xfrm_type_names, hdr->nlmsg_type) + , description, text_said)); + return FALSE; + } + else if ((size_t)r != len) + { + loglog(RC_LOG_SERIOUS + , "ERROR: netlink write() of %s message" + " for %s %s truncated: %ld instead of %lu" + , sparse_val_show(xfrm_type_names, hdr->nlmsg_type) + , description, text_said + , (long)r, (unsigned long)len); + return FALSE; + } + + for (;;) { + socklen_t alen; + + alen = sizeof(addr); + r = recvfrom(netlinkfd, &rsp, sizeof(rsp), 0 + , (struct sockaddr *)&addr, &alen); + if (r < 0) + { + if (errno == EINTR) + { + continue; + } + log_errno((e + , "netlink recvfrom() of response to our %s message" + " for %s %s failed" + , sparse_val_show(xfrm_type_names, hdr->nlmsg_type) + , description, text_said)); + return FALSE; + } + else if ((size_t) r < sizeof(rsp.n)) + { + plog("netlink read truncated message: %ld bytes; ignore message" + , (long) r); + continue; + } + else if (addr.nl_pid != 0) + { + /* not for us: ignore */ + DBG(DBG_KLIPS, + DBG_log("netlink: ignoring %s message from process %u" + , sparse_val_show(xfrm_type_names, rsp.n.nlmsg_type) + , addr.nl_pid)); + continue; + } + else if (rsp.n.nlmsg_seq != seq) + { + DBG(DBG_KLIPS, + DBG_log("netlink: ignoring out of sequence (%u/%u) message %s" + , rsp.n.nlmsg_seq, seq + , sparse_val_show(xfrm_type_names, rsp.n.nlmsg_type))); + continue; + } + break; + } + + if (rsp.n.nlmsg_len > (size_t) r) + { + loglog(RC_LOG_SERIOUS + , "netlink recvfrom() of response to our %s message" + " for %s %s was truncated: %ld instead of %lu" + , sparse_val_show(xfrm_type_names, hdr->nlmsg_type) + , description, text_said + , (long) len, (unsigned long) rsp.n.nlmsg_len); + return FALSE; + } + else if (rsp.n.nlmsg_type != NLMSG_ERROR + && (rbuf && rsp.n.nlmsg_type != rbuf->nlmsg_type)) + { + loglog(RC_LOG_SERIOUS + , "netlink recvfrom() of response to our %s message" + " for %s %s was of wrong type (%s)" + , sparse_val_show(xfrm_type_names, hdr->nlmsg_type) + , description, text_said + , sparse_val_show(xfrm_type_names, rsp.n.nlmsg_type)); + return FALSE; + } + else if (rbuf) + { + if ((size_t) r > rbuf_len) + { + loglog(RC_LOG_SERIOUS + , "netlink recvfrom() of response to our %s message" + " for %s %s was too long: %ld > %lu" + , sparse_val_show(xfrm_type_names, hdr->nlmsg_type) + , description, text_said + , (long)r, (unsigned long)rbuf_len); + return FALSE; + } + memcpy(rbuf, &rsp, r); + return TRUE; + } + else if (rsp.n.nlmsg_type == NLMSG_ERROR && rsp.e.error) + { + loglog(RC_LOG_SERIOUS + , "ERROR: netlink response for %s %s included errno %d: %s" + , description, text_said + , -rsp.e.error + , strerror(-rsp.e.error)); + return FALSE; + } + + return TRUE; +} + +/** netlink_policy - + * + * @param hdr - Data to check + * @param enoent_ok - Boolean - OK or not OK. + * @param text_said - String + * @return boolean + */ +static bool +netlink_policy(struct nlmsghdr *hdr, bool enoent_ok, const char *text_said) +{ + struct { + struct nlmsghdr n; + struct nlmsgerr e; + } rsp; + int error; + + rsp.n.nlmsg_type = NLMSG_ERROR; + if (!send_netlink_msg(hdr, &rsp.n, sizeof(rsp), "policy", text_said)) + { + return FALSE; + } + + error = -rsp.e.error; + if (!error) + { + return TRUE; + } + + if (error == ENOENT && enoent_ok) + { + return TRUE; + } + + loglog(RC_LOG_SERIOUS + , "ERROR: netlink %s response for flow %s included errno %d: %s" + , sparse_val_show(xfrm_type_names, hdr->nlmsg_type) + , text_said + , error + , strerror(error)); + return FALSE; +} + +/** netlink_raw_eroute + * + * @param this_host ip_address + * @param this_client ip_subnet + * @param that_host ip_address + * @param that_client ip_subnet + * @param spi + * @param proto int (Currently unused) Contains protocol (u=tcp, 17=udp, etc...) + * @param transport_proto int (Currently unused) 0=tunnel, 1=transport + * @param satype int + * @param proto_info + * @param lifetime (Currently unused) + * @param ip int + * @return boolean True if successful + */ +static bool +netlink_raw_eroute(const ip_address *this_host + , const ip_subnet *this_client + , const ip_address *that_host + , const ip_subnet *that_client + , ipsec_spi_t spi + , unsigned int satype + , unsigned int transport_proto + , const struct pfkey_proto_info *proto_info + , time_t use_lifetime UNUSED + , unsigned int op + , const char *text_said) +{ + struct { + struct nlmsghdr n; + union { + struct xfrm_userpolicy_info p; + struct xfrm_userpolicy_id id; + } u; + char data[1024]; + } req; + int shift; + int dir; + int family; + int policy; + bool ok; + bool enoent_ok; + + policy = IPSEC_POLICY_IPSEC; + + if (satype == SADB_X_SATYPE_INT) + { + /* shunt route */ + switch (ntohl(spi)) + { + case SPI_PASS: + policy = IPSEC_POLICY_NONE; + break; + case SPI_DROP: + case SPI_REJECT: + default: + policy = IPSEC_POLICY_DISCARD; + break; + case SPI_TRAP: + case SPI_TRAPSUBNET: + case SPI_HOLD: + if (op & (SADB_X_SAFLAGS_INFLOW << ERO_FLAG_SHIFT)) + { + return TRUE; + } + break; + } + } + + memset(&req, 0, sizeof(req)); + req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + + family = that_client->addr.u.v4.sin_family; + shift = (family == AF_INET) ? 5 : 7; + + req.u.p.sel.sport = portof(&this_client->addr); + req.u.p.sel.dport = portof(&that_client->addr); + req.u.p.sel.sport_mask = (req.u.p.sel.sport) ? ~0:0; + req.u.p.sel.dport_mask = (req.u.p.sel.dport) ? ~0:0; + ip2xfrm(&this_client->addr, &req.u.p.sel.saddr); + ip2xfrm(&that_client->addr, &req.u.p.sel.daddr); + req.u.p.sel.prefixlen_s = this_client->maskbits; + req.u.p.sel.prefixlen_d = that_client->maskbits; + req.u.p.sel.proto = transport_proto; + req.u.p.sel.family = family; + + dir = XFRM_POLICY_OUT; + if (op & (SADB_X_SAFLAGS_INFLOW << ERO_FLAG_SHIFT)) + { + dir = XFRM_POLICY_IN; + } + + if ((op & ERO_MASK) == ERO_DELETE) + { + req.u.id.dir = dir; + req.n.nlmsg_type = XFRM_MSG_DELPOLICY; + req.n.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req.u.id))); + } + else + { + int src, dst; + + req.u.p.dir = dir; + + src = req.u.p.sel.prefixlen_s; + dst = req.u.p.sel.prefixlen_d; + if (dir != XFRM_POLICY_OUT) { + src = req.u.p.sel.prefixlen_d; + dst = req.u.p.sel.prefixlen_s; + } + req.u.p.priority = MIN_SPD_PRIORITY + + (((2 << shift) - src) << shift) + + (2 << shift) - dst; + + req.u.p.action = XFRM_POLICY_ALLOW; + if (policy == IPSEC_POLICY_DISCARD) + { + req.u.p.action = XFRM_POLICY_BLOCK; + } + req.u.p.lft.soft_use_expires_seconds = use_lifetime; + req.u.p.lft.soft_byte_limit = XFRM_INF; + req.u.p.lft.soft_packet_limit = XFRM_INF; + req.u.p.lft.hard_byte_limit = XFRM_INF; + req.u.p.lft.hard_packet_limit = XFRM_INF; + + req.n.nlmsg_type = XFRM_MSG_NEWPOLICY; + if (op & (SADB_X_SAFLAGS_REPLACEFLOW << ERO_FLAG_SHIFT)) + { + req.n.nlmsg_type = XFRM_MSG_UPDPOLICY; + } + req.n.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req.u.p))); + } + + if (policy == IPSEC_POLICY_IPSEC && (op & ERO_MASK) != ERO_DELETE) + { + struct rtattr *attr; + struct xfrm_user_tmpl tmpl[4]; + int i; + + memset(tmpl, 0, sizeof(tmpl)); + for (i = 0; proto_info[i].proto; i++) + { + tmpl[i].reqid = proto_info[i].reqid; + tmpl[i].id.proto = proto_info[i].proto; + tmpl[i].optional = + proto_info[i].proto == IPPROTO_COMP && dir != XFRM_POLICY_OUT; + tmpl[i].aalgos = tmpl[i].ealgos = tmpl[i].calgos = ~0; + tmpl[i].mode = + proto_info[i].encapsulation == ENCAPSULATION_MODE_TUNNEL; + + if (!tmpl[i].mode) + { + continue; + } + + ip2xfrm(this_host, &tmpl[i].saddr); + ip2xfrm(that_host, &tmpl[i].id.daddr); + } + + attr = (struct rtattr *)((char *)&req + req.n.nlmsg_len); + attr->rta_type = XFRMA_TMPL; + attr->rta_len = i * sizeof(tmpl[0]); + memcpy(RTA_DATA(attr), tmpl, attr->rta_len); + attr->rta_len = RTA_LENGTH(attr->rta_len); + req.n.nlmsg_len += attr->rta_len; + } + + enoent_ok = FALSE; + if (op == ERO_DEL_INBOUND) + { + enoent_ok = TRUE; + } + else if (op == ERO_DELETE && ntohl(spi) == SPI_HOLD) + { + enoent_ok = TRUE; + } + + ok = netlink_policy(&req.n, enoent_ok, text_said); + switch (dir) + { + case XFRM_POLICY_IN: + if (req.n.nlmsg_type == XFRM_MSG_DELPOLICY) + { + req.u.id.dir = XFRM_POLICY_FWD; + } + else if (!ok) + { + break; + } + else if (proto_info[0].encapsulation != ENCAPSULATION_MODE_TUNNEL + && satype != SADB_X_SATYPE_INT) + { + break; + } + else + { + req.u.p.dir = XFRM_POLICY_FWD; + } + ok &= netlink_policy(&req.n, enoent_ok, text_said); + break; + } + + return ok; +} + +/** netlink_add_sa - Add an SA into the kernel SPDB via netlink + * + * @param sa Kernel SA to add/modify + * @param replace boolean - true if this replaces an existing SA + * @return bool True if successfull + */ +static bool +netlink_add_sa(const struct kernel_sa *sa, bool replace) +{ + struct { + struct nlmsghdr n; + struct xfrm_usersa_info p; + char data[1024]; + } req; + struct rtattr *attr; + + memset(&req, 0, sizeof(req)); + req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.n.nlmsg_type = replace ? XFRM_MSG_UPDSA : XFRM_MSG_NEWSA; + + ip2xfrm(sa->src, &req.p.saddr); + ip2xfrm(sa->dst, &req.p.id.daddr); + + req.p.id.spi = sa->spi; + req.p.id.proto = satype2proto(sa->satype); + req.p.family = sa->src->u.v4.sin_family; + req.p.mode = (sa->encapsulation == ENCAPSULATION_MODE_TUNNEL); + req.p.replay_window = sa->replay_window; + req.p.reqid = sa->reqid; + req.p.lft.soft_byte_limit = XFRM_INF; + req.p.lft.soft_packet_limit = XFRM_INF; + req.p.lft.hard_byte_limit = XFRM_INF; + req.p.lft.hard_packet_limit = XFRM_INF; + + req.n.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req.p))); + + attr = (struct rtattr *)((char *)&req + req.n.nlmsg_len); + + if (sa->authalg) + { + struct xfrm_algo algo; + const char *name; + + name = sparse_name(aalg_list, sa->authalg); + if (!name) { + loglog(RC_LOG_SERIOUS, "unknown authentication algorithm: %u" + , sa->authalg); + return FALSE; + } + + strcpy(algo.alg_name, name); + algo.alg_key_len = sa->authkeylen * BITS_PER_BYTE; + + attr->rta_type = XFRMA_ALG_AUTH; + attr->rta_len = RTA_LENGTH(sizeof(algo) + sa->authkeylen); + + memcpy(RTA_DATA(attr), &algo, sizeof(algo)); + memcpy((char *)RTA_DATA(attr) + sizeof(algo), sa->authkey + , sa->authkeylen); + + req.n.nlmsg_len += attr->rta_len; + attr = (struct rtattr *)((char *)attr + attr->rta_len); + } + + if (sa->encalg) + { + struct xfrm_algo algo; + const char *name; + + name = sparse_name(ealg_list, sa->encalg); + if (!name) { + loglog(RC_LOG_SERIOUS, "unknown encryption algorithm: %u" + , sa->encalg); + return FALSE; + } + + strcpy(algo.alg_name, name); + algo.alg_key_len = sa->enckeylen * BITS_PER_BYTE; + + attr->rta_type = XFRMA_ALG_CRYPT; + attr->rta_len = RTA_LENGTH(sizeof(algo) + sa->enckeylen); + + memcpy(RTA_DATA(attr), &algo, sizeof(algo)); + memcpy((char *)RTA_DATA(attr) + sizeof(algo), sa->enckey + , sa->enckeylen); + + req.n.nlmsg_len += attr->rta_len; + attr = (struct rtattr *)((char *)attr + attr->rta_len); + } + + if (sa->compalg) + { + struct xfrm_algo algo; + const char *name; + + name = sparse_name(calg_list, sa->compalg); + if (!name) { + loglog(RC_LOG_SERIOUS, "unknown compression algorithm: %u" + , sa->compalg); + return FALSE; + } + + strcpy(algo.alg_name, name); + algo.alg_key_len = 0; + + attr->rta_type = XFRMA_ALG_COMP; + attr->rta_len = RTA_LENGTH(sizeof(algo)); + + memcpy(RTA_DATA(attr), &algo, sizeof(algo)); + + req.n.nlmsg_len += attr->rta_len; + attr = (struct rtattr *)((char *)attr + attr->rta_len); + } + + if (sa->natt_type) + { + struct xfrm_encap_tmpl natt; + + natt.encap_type = sa->natt_type; + natt.encap_sport = ntohs(sa->natt_sport); + natt.encap_dport = ntohs(sa->natt_dport); + memset (&natt.encap_oa, 0, sizeof (natt.encap_oa)); + + attr->rta_type = XFRMA_ENCAP; + attr->rta_len = RTA_LENGTH(sizeof(natt)); + + memcpy(RTA_DATA(attr), &natt, sizeof(natt)); + + req.n.nlmsg_len += attr->rta_len; + attr = (struct rtattr *)((char *)attr + attr->rta_len); + } + + return send_netlink_msg(&req.n, NULL, 0, "Add SA", sa->text_said); +} + +/** netlink_del_sa - Delete an SA from the Kernel + * + * @param sa Kernel SA to be deleted + * @return bool True if successfull + */ +static bool +netlink_del_sa(const struct kernel_sa *sa) +{ + struct { + struct nlmsghdr n; + struct xfrm_usersa_id id; + char data[1024]; + } req; + + memset(&req, 0, sizeof(req)); + req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.n.nlmsg_type = XFRM_MSG_DELSA; + + ip2xfrm(sa->dst, &req.id.daddr); + + req.id.spi = sa->spi; + req.id.family = sa->src->u.v4.sin_family; + req.id.proto = sa->proto; + + req.n.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req.id))); + + return send_netlink_msg(&req.n, NULL, 0, "Del SA", sa->text_said); +} + +static bool +netlink_error(const char *req_type, const struct nlmsghdr *n +, const struct nlmsgerr *e, int rsp_size) +{ + if (n->nlmsg_type == NLMSG_ERROR) + { + DBG(DBG_KLIPS, + DBG_log("%s returned with errno %d: %s" + , req_type + , -e->error + , strerror(-e->error)) + ) + return TRUE; + } + if (n->nlmsg_len < NLMSG_LENGTH(rsp_size)) + { + plog("%s returned message with length %lu < %lu bytes" + , req_type + , (unsigned long) n->nlmsg_len + , (unsigned long) rsp_size); + return TRUE; + } + return FALSE; +} + +static bool +netlink_get_policy(const struct kernel_sa *sa, bool inbound, time_t *use_time) +{ + struct { + struct nlmsghdr n; + struct xfrm_userpolicy_id id; + } req; + + struct { + struct nlmsghdr n; + union { + struct nlmsgerr e; + struct xfrm_userpolicy_info info; + } u; + char data[1024]; + } rsp; + + memset(&req, 0, sizeof(req)); + req.n.nlmsg_flags = NLM_F_REQUEST; + req.n.nlmsg_type = XFRM_MSG_GETPOLICY; + + req.id.sel.sport = portof(&sa->src_client->addr); + req.id.sel.dport = portof(&sa->dst_client->addr); + req.id.sel.sport_mask = (req.id.sel.sport) ? ~0:0; + req.id.sel.dport_mask = (req.id.sel.dport) ? ~0:0; + ip2xfrm(&sa->src_client->addr, &req.id.sel.saddr); + ip2xfrm(&sa->dst_client->addr, &req.id.sel.daddr); + req.id.sel.prefixlen_s = sa->src_client->maskbits; + req.id.sel.prefixlen_d = sa->dst_client->maskbits; + req.id.sel.proto = sa->transport_proto; + req.id.sel.family = sa->dst_client->addr.u.v4.sin_family; + + req.n.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req.id))); + rsp.n.nlmsg_type = XFRM_MSG_NEWPOLICY; + + req.id.dir = (inbound)? XFRM_POLICY_IN:XFRM_POLICY_OUT; + + if (!send_netlink_msg(&req.n, &rsp.n, sizeof(rsp), "Get policy", "?")) + return FALSE; + + if (netlink_error("XFRM_MSG_GETPOLICY", &rsp.n, &rsp.u.e, sizeof(rsp.u.info))) + return FALSE; + + *use_time = (time_t)rsp.u.info.curlft.use_time; + + if (inbound && sa->encapsulation == ENCAPSULATION_MODE_TUNNEL) + { + time_t use_time_fwd; + + req.id.dir = XFRM_POLICY_FWD; + + if (!send_netlink_msg(&req.n, &rsp.n, sizeof(rsp), "Get policy", "?")) + return FALSE; + + if (netlink_error("XFRM_MSG_GETPOLICY", &rsp.n, &rsp.u.e, sizeof(rsp.u.info))) + return FALSE; + + use_time_fwd = (time_t)rsp.u.info.curlft.use_time; + *use_time = (*use_time > use_time_fwd)? *use_time : use_time_fwd; + } + return TRUE; +} + + +/** netlink_get_sa - Get information about an SA from the Kernel + * + * @param sa Kernel SA to be queried + * @return bool True if successfull + */ +static bool +netlink_get_sa(const struct kernel_sa *sa, u_int *bytes) +{ + struct { + struct nlmsghdr n; + struct xfrm_usersa_id id; + } req; + + struct { + struct nlmsghdr n; + union { + struct nlmsgerr e; + struct xfrm_usersa_info info; + } u; + char data[1024]; + } rsp; + + memset(&req, 0, sizeof(req)); + req.n.nlmsg_flags = NLM_F_REQUEST; + req.n.nlmsg_type = XFRM_MSG_GETSA; + + ip2xfrm(sa->dst, &req.id.daddr); + + req.id.spi = sa->spi; + req.id.family = sa->src->u.v4.sin_family; + req.id.proto = sa->proto; + + req.n.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req.id))); + rsp.n.nlmsg_type = XFRM_MSG_NEWSA; + + if (!send_netlink_msg(&req.n, &rsp.n, sizeof(rsp), "Get SA", sa->text_said)) + return FALSE; + + if (netlink_error("XFRM_MSG_GETSA", &rsp.n, &rsp.u.e, sizeof(rsp.u.info))) + return FALSE; + + *bytes = (u_int) rsp.u.info.curlft.bytes; + + return TRUE; +} + +static void +linux_pfkey_register_response(const struct sadb_msg *msg) +{ + switch (msg->sadb_msg_satype) + { + case SADB_SATYPE_ESP: +#ifndef NO_KERNEL_ALG + kernel_alg_register_pfkey(msg, msg->sadb_msg_len * IPSEC_PFKEYv2_ALIGN); +#endif + break; + case SADB_X_SATYPE_IPCOMP: + can_do_IPcomp = TRUE; + break; + default: + break; + } +} + +/** linux_pfkey_register - Register via PFKEY our capabilities + * + */ +static void +linux_pfkey_register(void) +{ + pfkey_register_proto(SADB_SATYPE_AH, "AH"); + pfkey_register_proto(SADB_SATYPE_ESP, "ESP"); + pfkey_register_proto(SADB_X_SATYPE_IPCOMP, "IPCOMP"); + pfkey_close(); +} + +/** Create ip_address out of xfrm_address_t. + * + * @param family + * @param src xfrm formatted IP address + * @param dst ip_address formatted destination + * @return err_t NULL if okay, otherwise an error + */ +static err_t +xfrm_to_ip_address(unsigned family, const xfrm_address_t *src, ip_address *dst) +{ + switch (family) + { + case AF_INET: /* IPv4 */ + case AF_UNSPEC: /* Unspecified, we assume IPv4 */ + initaddr((const void *) &src->a4, sizeof(src->a4), AF_INET, dst); + return NULL; + case AF_INET6: /* IPv6 */ + initaddr((const void *) &src->a6, sizeof(src->a6), AF_INET6, dst); + return NULL; + default: + return "unknown address family"; + } +} + +/* Create a pair of ip_address's out of xfrm_sel. + * + * @param sel xfrm selector + * @param src ip_address formatted source + * @param dst ip_address formatted destination + * @return err_t NULL if okay, otherwise an error + */ +static err_t +xfrm_sel_to_ip_pair(const struct xfrm_selector *sel + , ip_address *src + , ip_address *dst) +{ + int family; + err_t ugh; + + family = sel->family; + + if ((ugh = xfrm_to_ip_address(family, &sel->saddr, src)) + || (ugh = xfrm_to_ip_address(family, &sel->daddr, dst))) + return ugh; + + /* family has been verified in xfrm_to_ip_address. */ + if (family == AF_INET) + { + src->u.v4.sin_port = sel->sport; + dst->u.v4.sin_port = sel->dport; + } + else + { + src->u.v6.sin6_port = sel->sport; + dst->u.v6.sin6_port = sel->dport; + } + + return NULL; +} + +static void +netlink_acquire(struct nlmsghdr *n) +{ + struct xfrm_user_acquire *acquire; + ip_address src, dst; + ip_subnet ours, his; + unsigned transport_proto; + err_t ugh = NULL; + + if (n->nlmsg_len < NLMSG_LENGTH(sizeof(*acquire))) + { + plog("netlink_acquire got message with length %lu < %lu bytes; ignore message" + , (unsigned long) n->nlmsg_len + , (unsigned long) sizeof(*acquire)); + return; + } + + acquire = NLMSG_DATA(n); + transport_proto = acquire->sel.proto; + + /* XXX also the type of src/dst should be checked to make sure + * that they aren't v4 to v6 or something goofy + */ + + if (!(ugh = xfrm_sel_to_ip_pair(&acquire->sel, &src, &dst)) + && !(ugh = addrtosubnet(&src, &ours)) + && !(ugh = addrtosubnet(&dst, &his))) + record_and_initiate_opportunistic(&ours, &his, transport_proto + , "%acquire-netlink"); + + if (ugh != NULL) + plog("XFRM_MSG_ACQUIRE message from kernel malformed: %s", ugh); +} + +static void +netlink_shunt_expire(struct xfrm_userpolicy_info *pol) +{ + ip_address src, dst; + unsigned transport_proto; + err_t ugh = NULL; + + transport_proto = pol->sel.proto; + + if (!(ugh = xfrm_sel_to_ip_pair(&pol->sel, &src, &dst))) + { + plog("XFRM_MSG_POLEXPIRE message from kernel malformed: %s", ugh); + return; + } + + replace_bare_shunt(&src, &dst, BOTTOM_PRIO, SPI_PASS, FALSE, transport_proto + , "delete expired bare shunt"); +} + +static void +netlink_policy_expire(struct nlmsghdr *n) +{ + struct xfrm_user_polexpire *upe; + struct { + struct nlmsghdr n; + struct xfrm_userpolicy_id id; + } req; + + struct { + struct nlmsghdr n; + union { + struct nlmsgerr e; + struct xfrm_userpolicy_info pol; + } u; + char data[1024]; + } rsp; + + if (n->nlmsg_len < NLMSG_LENGTH(sizeof(*upe))) + { + plog("netlink_policy_expire got message with length %lu < %lu bytes; ignore message" + , (unsigned long) n->nlmsg_len + , (unsigned long) sizeof(*upe)); + return; + } + + upe = NLMSG_DATA(n); + req.id.dir = upe->pol.dir; + req.id.index = upe->pol.index; + req.n.nlmsg_flags = NLM_F_REQUEST; + req.n.nlmsg_type = XFRM_MSG_GETPOLICY; + req.n.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req.id))); + + rsp.n.nlmsg_type = XFRM_MSG_NEWPOLICY; + + if (!send_netlink_msg(&req.n, &rsp.n, sizeof(rsp), "Get policy", "?")) + return; + + if (netlink_error("XFRM_MSG_GETPOLICY", &rsp.n, &rsp.u.e, sizeof(rsp.u.pol))) + return; + + if (req.id.index != rsp.u.pol.index) + { + DBG(DBG_KLIPS, + DBG_log("netlink_policy_expire: policy was replaced: " + "dir=%d, oldindex=%d, newindex=%d" + , req.id.dir, req.id.index, rsp.u.pol.index)); + return; + } + + if (upe->pol.curlft.add_time != rsp.u.pol.curlft.add_time) + { + DBG(DBG_KLIPS, + DBG_log("netlink_policy_expire: policy was replaced " + " and you have won the lottery: " + "dir=%d, index=%d" + , req.id.dir, req.id.index)); + return; + } + + switch (upe->pol.dir) + { + case XFRM_POLICY_OUT: + netlink_shunt_expire(&rsp.u.pol); + break; + } +} + +static bool +netlink_get(void) +{ + struct { + struct nlmsghdr n; + char data[1024]; + } rsp; + ssize_t r; + struct sockaddr_nl addr; + socklen_t alen; + + alen = sizeof(addr); + r = recvfrom(netlink_bcast_fd, &rsp, sizeof(rsp), 0 + , (struct sockaddr *)&addr, &alen); + if (r < 0) + { + if (errno == EAGAIN) + return FALSE; + if (errno != EINTR) + log_errno((e, "recvfrom() failed in netlink_get")); + return TRUE; + } + else if ((size_t) r < sizeof(rsp.n)) + { + plog("netlink_get read truncated message: %ld bytes; ignore message" + , (long) r); + return TRUE; + } + else if (addr.nl_pid != 0) + { + /* not for us: ignore */ + DBG(DBG_KLIPS, + DBG_log("netlink_get: ignoring %s message from process %u" + , sparse_val_show(xfrm_type_names, rsp.n.nlmsg_type) + , addr.nl_pid)); + return TRUE; + } + else if ((size_t) r != rsp.n.nlmsg_len) + { + plog("netlink_get read message with length %ld that doesn't equal nlmsg_len %lu bytes; ignore message" + , (long) r + , (unsigned long) rsp.n.nlmsg_len); + return TRUE; + } + + DBG(DBG_KLIPS, + DBG_log("netlink_get: %s message" + , sparse_val_show(xfrm_type_names, rsp.n.nlmsg_type))); + + switch (rsp.n.nlmsg_type) + { + case XFRM_MSG_ACQUIRE: + netlink_acquire(&rsp.n); + break; + case XFRM_MSG_POLEXPIRE: + netlink_policy_expire(&rsp.n); + break; + default: + /* ignored */ + break; + } + + return TRUE; +} + +static void +netlink_process_msg(void) +{ + while (netlink_get()) + ; +} + +static ipsec_spi_t +netlink_get_spi(const ip_address *src +, const ip_address *dst +, int proto +, bool tunnel_mode +, unsigned reqid +, ipsec_spi_t min +, ipsec_spi_t max +, const char *text_said) +{ + struct { + struct nlmsghdr n; + struct xfrm_userspi_info spi; + } req; + + struct { + struct nlmsghdr n; + union { + struct nlmsgerr e; + struct xfrm_usersa_info sa; + } u; + char data[1024]; + } rsp; + + memset(&req, 0, sizeof(req)); + req.n.nlmsg_flags = NLM_F_REQUEST; + req.n.nlmsg_type = XFRM_MSG_ALLOCSPI; + + ip2xfrm(src, &req.spi.info.saddr); + ip2xfrm(dst, &req.spi.info.id.daddr); + req.spi.info.mode = tunnel_mode; + req.spi.info.reqid = reqid; + req.spi.info.id.proto = proto; + req.spi.info.family = src->u.v4.sin_family; + req.spi.min = min; + req.spi.max = max; + + req.n.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req.spi))); + rsp.n.nlmsg_type = XFRM_MSG_NEWSA; + + if (!send_netlink_msg(&req.n, &rsp.n, sizeof(rsp), "Get SPI", text_said)) + return 0; + + if (netlink_error("XFRM_MSG_ALLOCSPI", &rsp.n, &rsp.u.e, sizeof(rsp.u.sa))) + return 0; + + DBG(DBG_KLIPS, + DBG_log("netlink_get_spi: allocated 0x%x for %s" + , ntohl(rsp.u.sa.id.spi), text_said)); + return rsp.u.sa.id.spi; +} + +const struct kernel_ops linux_kernel_ops = { + type: KERNEL_TYPE_LINUX, + inbound_eroute: 1, + policy_lifetime: 1, + async_fdp: &netlink_bcast_fd, + + init: init_netlink, + pfkey_register: linux_pfkey_register, + pfkey_register_response: linux_pfkey_register_response, + process_msg: netlink_process_msg, + raw_eroute: netlink_raw_eroute, + get_policy: netlink_get_policy, + add_sa: netlink_add_sa, + del_sa: netlink_del_sa, + get_sa: netlink_get_sa, + process_queue: NULL, + grp_sa: NULL, + get_spi: netlink_get_spi, +}; +#endif /* linux && KLIPS */ diff --git a/src/pluto/kernel_netlink.h b/src/pluto/kernel_netlink.h new file mode 100644 index 000000000..1b5f42e48 --- /dev/null +++ b/src/pluto/kernel_netlink.h @@ -0,0 +1,20 @@ +/* declarations of routines that interface with the kernel's pfkey mechanism + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * Copyright (C) 2003 Herbert Xu + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: kernel_netlink.h,v 1.1 2004/03/15 20:35:28 as Exp $ + */ + +#if defined(KLIPS) && defined(linux) +extern const struct kernel_ops linux_kernel_ops; +#endif diff --git a/src/pluto/kernel_noklips.c b/src/pluto/kernel_noklips.c new file mode 100644 index 000000000..570bb0470 --- /dev/null +++ b/src/pluto/kernel_noklips.c @@ -0,0 +1,126 @@ +/* interface to fake kernel interface, used for testing pluto in-vitro. + * Copyright (C) 1997 Angelos D. Keromytis. + * Copyright (C) 1998-2002 D. Hugh Redelmeier. + * Copyright (C) 2003 Michael Richardson <mcr@freeswan.org> + * Copyright (C) 2003 Herbert Xu. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: kernel_noklips.c,v 1.5 2006/02/04 00:01:22 as Exp $ + */ + +#include <errno.h> +#include <fcntl.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <sys/select.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/queue.h> + +#include <freeswan.h> +#include <pfkeyv2.h> +#include <pfkey.h> + +#include "constants.h" +#include "defs.h" +#include "kernel.h" +#include "kernel_noklips.h" +#include "log.h" +#include "whack.h" /* for RC_LOG_SERIOUS */ + +void +init_noklips(void) +{ + return; +} + +/* asynchronous messages from our queue */ +static void +noklips_dequeue(void) +{ +} + +/* asynchronous messages directly from PF_KEY socket */ +static void +noklips_event(void) +{ +} + +static void +noklips_register_response(const struct sadb_msg *msg UNUSED) +{ +} + +static void +noklips_register(void) +{ +} + +static bool +noklips_raw_eroute(const ip_address *this_host UNUSED + , const ip_subnet *this_client UNUSED + , const ip_address *that_host UNUSED + , const ip_subnet *that_client UNUSED + , ipsec_spi_t spi UNUSED + , unsigned int satype UNUSED + , unsigned int transport_proto UNUSED + , const struct pfkey_proto_info *proto_info UNUSED + , time_t use_lifetime UNUSED + , unsigned int op UNUSED + , const char *text_said UNUSED) +{ + return TRUE; +} + +static bool +noklips_add_sa(const struct kernel_sa *sa UNUSED + , bool replace UNUSED) +{ + return TRUE; +} + +static bool +noklips_grp_sa(const struct kernel_sa *sa0 UNUSED + , const struct kernel_sa *sa1 UNUSED) +{ + return TRUE; +} + +static bool +noklips_del_sa(const struct kernel_sa *sa UNUSED) +{ + return TRUE; +} + + +const struct kernel_ops noklips_kernel_ops = { + type: KERNEL_TYPE_NONE, + async_fdp: NULL, + + init: init_noklips, + pfkey_register: noklips_register, + pfkey_register_response: noklips_register_response, + process_queue: noklips_dequeue, + process_msg: noklips_event, + raw_eroute: noklips_raw_eroute, + add_sa: noklips_add_sa, + grp_sa: noklips_grp_sa, + del_sa: noklips_del_sa, + get_sa: NULL, + get_spi: NULL, + inbound_eroute: FALSE, + policy_lifetime: FALSE +}; diff --git a/src/pluto/kernel_noklips.h b/src/pluto/kernel_noklips.h new file mode 100644 index 000000000..fe4e77ec4 --- /dev/null +++ b/src/pluto/kernel_noklips.h @@ -0,0 +1,19 @@ +/* declarations of routines that interface with the kernel's pfkey mechanism + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * Copyright (C) 2003 Herbert Xu + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: kernel_noklips.h,v 1.1 2004/03/15 20:35:28 as Exp $ + */ + +extern void init_noklips(void); +extern const struct kernel_ops noklips_kernel_ops; diff --git a/src/pluto/kernel_pfkey.c b/src/pluto/kernel_pfkey.c new file mode 100644 index 000000000..ced7a1453 --- /dev/null +++ b/src/pluto/kernel_pfkey.c @@ -0,0 +1,926 @@ +/* pfkey interface to the kernel's IPsec mechanism + * Copyright (C) 1997 Angelos D. Keromytis. + * Copyright (C) 1998-2002 D. Hugh Redelmeier. + * Copyright (C) 2003 Herbert Xu. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: kernel_pfkey.c,v 1.8 2006/02/04 00:01:22 as Exp $ + */ + +#ifdef KLIPS + +#include <errno.h> +#include <fcntl.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <sys/select.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/queue.h> + +#include <freeswan.h> +#include <pfkeyv2.h> +#include <pfkey.h> + +#include "constants.h" +#include "defs.h" +#include "kernel.h" +#include "kernel_pfkey.h" +#include "log.h" +#include "whack.h" /* for RC_LOG_SERIOUS */ +#include "demux.h" +#include "nat_traversal.h" +#include "alg_info.h" +#include "kernel_alg.h" + + +static int pfkeyfd = NULL_FD; + +typedef u_int32_t pfkey_seq_t; +static pfkey_seq_t pfkey_seq = 0; /* sequence number for our PF_KEY messages */ + +static pid_t pid; + +#define NE(x) { x, #x } /* Name Entry -- shorthand for sparse_names */ + +static sparse_names pfkey_type_names = { + NE(SADB_RESERVED), + NE(SADB_GETSPI), + NE(SADB_UPDATE), + NE(SADB_ADD), + NE(SADB_DELETE), + NE(SADB_GET), + NE(SADB_ACQUIRE), + NE(SADB_REGISTER), + NE(SADB_EXPIRE), + NE(SADB_FLUSH), + NE(SADB_DUMP), + NE(SADB_X_PROMISC), + NE(SADB_X_PCHANGE), + NE(SADB_X_GRPSA), + NE(SADB_X_ADDFLOW), + NE(SADB_X_DELFLOW), + NE(SADB_X_DEBUG), + NE(SADB_X_NAT_T_NEW_MAPPING), + NE(SADB_MAX), + { 0, sparse_end } +}; + +#ifdef NEVER /* not needed yet */ +static sparse_names pfkey_ext_names = { + NE(SADB_EXT_RESERVED), + NE(SADB_EXT_SA), + NE(SADB_EXT_LIFETIME_CURRENT), + NE(SADB_EXT_LIFETIME_HARD), + NE(SADB_EXT_LIFETIME_SOFT), + NE(SADB_EXT_ADDRESS_SRC), + NE(SADB_EXT_ADDRESS_DST), + NE(SADB_EXT_ADDRESS_PROXY), + NE(SADB_EXT_KEY_AUTH), + NE(SADB_EXT_KEY_ENCRYPT), + NE(SADB_EXT_IDENTITY_SRC), + NE(SADB_EXT_IDENTITY_DST), + NE(SADB_EXT_SENSITIVITY), + NE(SADB_EXT_PROPOSAL), + NE(SADB_EXT_SUPPORTED_AUTH), + NE(SADB_EXT_SUPPORTED_ENCRYPT), + NE(SADB_EXT_SPIRANGE), + NE(SADB_X_EXT_KMPRIVATE), + NE(SADB_X_EXT_SATYPE2), + NE(SADB_X_EXT_SA2), + NE(SADB_X_EXT_ADDRESS_DST2), + NE(SADB_X_EXT_ADDRESS_SRC_FLOW), + NE(SADB_X_EXT_ADDRESS_DST_FLOW), + NE(SADB_X_EXT_ADDRESS_SRC_MASK), + NE(SADB_X_EXT_ADDRESS_DST_MASK), + NE(SADB_X_EXT_DEBUG), + { 0, sparse_end } +}; +#endif /* NEVER */ + +#undef NE + +void +init_pfkey(void) +{ + pid = getpid(); + + /* open PF_KEY socket */ + + pfkeyfd = socket(PF_KEY, SOCK_RAW, PF_KEY_V2); + + if (pfkeyfd == -1) + exit_log_errno((e, "socket() in init_pfkeyfd()")); + +#ifdef NEVER /* apparently unsupported! */ + if (fcntl(pfkeyfd, F_SETFL, O_NONBLOCK) != 0) + exit_log_errno((e, "fcntl(O_NONBLOCK) in init_pfkeyfd()")); +#endif + if (fcntl(pfkeyfd, F_SETFD, FD_CLOEXEC) != 0) + exit_log_errno((e, "fcntl(FD_CLOEXEC) in init_pfkeyfd()")); + + DBG(DBG_KLIPS, + DBG_log("process %u listening for PF_KEY_V2 on file descriptor %d", (unsigned)pid, pfkeyfd)); +} + +/* Kinds of PF_KEY message from the kernel: + * - response to a request from us + * + ACK/NAK + * + Register: indicates transforms supported by kernel + * + SPI requested by getspi + * - Acquire, requesting us to deal with trapped clear packet + * - expiration of of one of our SAs + * - messages to other processes + * + * To minimize the effect on the event-driven structure of Pluto, + * responses are dealt with synchronously. We hope that the Kernel + * produces them synchronously. We must "read ahead" in the PF_KEY + * stream, saving Acquire and Expiry messages that are encountered. + * We ignore messages to other processes. + */ + +typedef union { + unsigned char bytes[PFKEYv2_MAX_MSGSIZE]; + struct sadb_msg msg; + } pfkey_buf; + +/* queue of unprocessed PF_KEY messages input from kernel + * Note that the pfkey_buf may be partly allocated, reflecting + * the variable length nature of the messages. So the link field + * must come first. + */ +typedef struct pfkey_item { + struct pfkey_item *next; + pfkey_buf buf; + } pfkey_item; + +static pfkey_item *pfkey_iq_head = NULL; /* oldest */ +static pfkey_item *pfkey_iq_tail; /* youngest */ + +static bool +pfkey_input_ready(void) +{ + fd_set readfds; + int ndes; + struct timeval tm; + + tm.tv_sec = 0; /* don't wait at all */ + tm.tv_usec = 0; + + FD_ZERO(&readfds); /* we only care about pfkeyfd */ + FD_SET(pfkeyfd, &readfds); + + do { + ndes = select(pfkeyfd + 1, &readfds, NULL, NULL, &tm); + } while (ndes == -1 && errno == EINTR); + + if (ndes < 0) + { + log_errno((e, "select() failed in pfkey_get()")); + return FALSE; + } + + if (ndes == 0) + return FALSE; /* nothing to read */ + + passert(ndes == 1 && FD_ISSET(pfkeyfd, &readfds)); + return TRUE; +} + +/* get a PF_KEY message from kernel. + * Returns TRUE is message found, FALSE if no message pending, + * and aborts or keeps trying when an error is encountered. + * The only validation of the message is that the message length + * received matches that in the message header, and that the message + * is for this process. + */ +static bool +pfkey_get(pfkey_buf *buf) +{ + for (;;) + { + /* len must be less than PFKEYv2_MAX_MSGSIZE, + * so it should fit in an int. We use this fact when printing it. + */ + ssize_t len; + + if (!pfkey_input_ready()) + return FALSE; + + len = read(pfkeyfd, buf->bytes, sizeof(buf->bytes)); + + if (len < 0) + { + if (errno == EAGAIN) + return FALSE; + + log_errno((e, "read() failed in pfkey_get()")); + return FALSE; + } + else if ((size_t) len < sizeof(buf->msg)) + { + plog("pfkey_get read truncated PF_KEY message: %d bytes; ignoring message" + , (int) len); + } + else if ((size_t) len != buf->msg.sadb_msg_len * IPSEC_PFKEYv2_ALIGN) + { + plog("pfkey_get read PF_KEY message with length %d that doesn't equal sadb_msg_len %u * %u; ignoring message" + , (int) len + , (unsigned) buf->msg.sadb_msg_len + , (unsigned) IPSEC_PFKEYv2_ALIGN); + } + else if (!(buf->msg.sadb_msg_pid == (unsigned)pid + || (buf->msg.sadb_msg_pid == 0 && buf->msg.sadb_msg_type == SADB_ACQUIRE) + || (buf->msg.sadb_msg_type == SADB_REGISTER) + || (buf->msg.sadb_msg_pid == 0 && buf->msg.sadb_msg_type == SADB_X_NAT_T_NEW_MAPPING))) + { + /* not for us: ignore */ + DBG(DBG_KLIPS, + DBG_log("pfkey_get: ignoring PF_KEY %s message %u for process %u" + , sparse_val_show(pfkey_type_names, buf->msg.sadb_msg_type) + , buf->msg.sadb_msg_seq + , buf->msg.sadb_msg_pid)); + } + else + { + DBG(DBG_KLIPS, + DBG_log("pfkey_get: %s message %u" + , sparse_val_show(pfkey_type_names, buf->msg.sadb_msg_type) + , buf->msg.sadb_msg_seq)); + return TRUE; + } + } +} + +/* get a response to a specific message */ +static bool +pfkey_get_response(pfkey_buf *buf, pfkey_seq_t seq) +{ + while (pfkey_get(buf)) + { + if (buf->msg.sadb_msg_pid == (unsigned)pid + && buf->msg.sadb_msg_seq == seq) + { + return TRUE; + } + else + { + /* Not for us: queue it. */ + size_t bl = buf->msg.sadb_msg_len * IPSEC_PFKEYv2_ALIGN; + pfkey_item *it = alloc_bytes(offsetof(pfkey_item, buf) + bl, "pfkey_item"); + + memcpy(&it->buf, buf, bl); + + it->next = NULL; + if (pfkey_iq_head == NULL) + { + pfkey_iq_head = it; + } + else + { + pfkey_iq_tail->next = it; + } + pfkey_iq_tail = it; + } + } + return FALSE; +} + +/* Process a SADB_REGISTER message from the kernel. + * This will be a response to one of ours, but it may be asynchronous + * (if kernel modules are loaded and unloaded). + * Some sanity checking has already been performed. + */ +static void +klips_pfkey_register_response(const struct sadb_msg *msg) +{ + /* Find out what the kernel can support. + * In fact, the only question at the moment + * is whether it can support IPcomp. + * So we ignore the rest. + * ??? we really should pay attention to what transforms are supported. + */ + switch (msg->sadb_msg_satype) + { + case SADB_SATYPE_AH: + break; + case SADB_SATYPE_ESP: +#ifndef NO_KERNEL_ALG + kernel_alg_register_pfkey(msg, sizeof (pfkey_buf)); +#endif + break; + case SADB_X_SATYPE_COMP: + /* ??? There ought to be an extension to list the + * supported algorithms, but RFC 2367 doesn't + * list one for IPcomp. KLIPS uses SADB_X_CALG_DEFLATE. + * Since we only implement deflate, we'll assume this. + */ + can_do_IPcomp = TRUE; + break; + case SADB_X_SATYPE_IPIP: + break; + default: + break; + } +} + +/* Processs a SADB_ACQUIRE message from KLIPS. + * Try to build an opportunistic connection! + * See RFC 2367 "PF_KEY Key Management API, Version 2" 3.1.6 + * <base, address(SD), (address(P)), (identity(SD),) (sensitivity,) proposal> + * - extensions for source and data IP addresses + * - optional extensions for identity [not useful for us?] + * - optional extension for sensitivity [not useful for us?] + * - expension for proposal [not useful for us?] + * + * ??? We must use the sequence number in creating an SA. + * We actually need to create up to 4 SAs each way. Which one? + * I guess it depends on the protocol present in the sadb_msg_satype. + * For now, we'll ignore this requirement. + * + * ??? We need some mechanism to make sure that multiple ACQUIRE messages + * don't cause a whole bunch of redundant negotiations. + */ +static void +process_pfkey_acquire(pfkey_buf *buf, struct sadb_ext *extensions[SADB_EXT_MAX + 1]) +{ + struct sadb_address *srcx = (void *) extensions[SADB_EXT_ADDRESS_SRC]; + struct sadb_address *dstx = (void *) extensions[SADB_EXT_ADDRESS_DST]; + int src_proto = srcx->sadb_address_proto; + int dst_proto = dstx->sadb_address_proto; + ip_address *src = (ip_address*)&srcx[1]; + ip_address *dst = (ip_address*)&dstx[1]; + ip_subnet ours, his; + err_t ugh = NULL; + + /* assumption: we're only catching our own outgoing packets + * so source is our end and destination is the other end. + * Verifying this is not actually convenient. + * + * This stylized control structure yields a complaint or + * desired results. For compactness, a pointer value is + * treated as a boolean. Logically, the structure is: + * keep going as long as things are OK. + */ + if (buf->msg.sadb_msg_pid == 0 /* we only wish to hear from kernel */ + && !(ugh = src_proto == dst_proto? NULL : "src and dst protocols differ") + && !(ugh = addrtypeof(src) == addrtypeof(dst)? NULL : "conflicting address types") + && !(ugh = addrtosubnet(src, &ours)) + && !(ugh = addrtosubnet(dst, &his))) + record_and_initiate_opportunistic(&ours, &his, src_proto, "%acquire"); + + if (ugh != NULL) + plog("SADB_ACQUIRE message from KLIPS malformed: %s", ugh); + +} + +/* Handle PF_KEY messages from the kernel that are not dealt with + * synchronously. In other words, all but responses to PF_KEY messages + * that we sent. + */ +static void +pfkey_async(pfkey_buf *buf) +{ + struct sadb_ext *extensions[SADB_EXT_MAX + 1]; + + if (pfkey_msg_parse(&buf->msg, NULL, extensions, EXT_BITS_OUT)) + { + plog("pfkey_async:" + " unparseable PF_KEY message:" + " %s len=%d, errno=%d, seq=%d, pid=%d; message ignored" + , sparse_val_show(pfkey_type_names, buf->msg.sadb_msg_type) + , buf->msg.sadb_msg_len + , buf->msg.sadb_msg_errno + , buf->msg.sadb_msg_seq + , buf->msg.sadb_msg_pid); + } + else + { + DBG(DBG_CONTROL | DBG_KLIPS, DBG_log("pfkey_async:" + " %s len=%u, errno=%u, satype=%u, seq=%u, pid=%u" + , sparse_val_show(pfkey_type_names, buf->msg.sadb_msg_type) + , buf->msg.sadb_msg_len + , buf->msg.sadb_msg_errno + , buf->msg.sadb_msg_satype + , buf->msg.sadb_msg_seq + , buf->msg.sadb_msg_pid)); + + switch (buf->msg.sadb_msg_type) + { + case SADB_REGISTER: + kernel_ops->pfkey_register_response(&buf->msg); + break; + case SADB_ACQUIRE: + /* to simulate loss of ACQUIRE, delete this call */ + process_pfkey_acquire(buf, extensions); + break; + case SADB_X_NAT_T_NEW_MAPPING: + process_pfkey_nat_t_new_mapping(&(buf->msg), extensions); + break; + default: + /* ignored */ + break; + } + } +} + +/* asynchronous messages from our queue */ +static void +pfkey_dequeue(void) +{ + while (pfkey_iq_head != NULL) + { + pfkey_item *it = pfkey_iq_head; + + pfkey_async(&it->buf); + pfkey_iq_head = it->next; + pfree(it); + } + + /* Handle any orphaned holds, but only if no pfkey input is pending. + * For each, we initiate Opportunistic. + * note: we don't need to advance the pointer because + * record_and_initiate_opportunistic will remove the current + * record each time we call it. + */ + while (orphaned_holds != NULL && !pfkey_input_ready()) + record_and_initiate_opportunistic(&orphaned_holds->ours + , &orphaned_holds->his + , orphaned_holds->transport_proto + , "%hold found-pfkey"); + +} + +/* asynchronous messages directly from PF_KEY socket */ +static void +pfkey_event(void) +{ + pfkey_buf buf; + + if (pfkey_get(&buf)) + pfkey_async(&buf); +} + +static bool +pfkey_build(int error +, const char *description +, const char *text_said +, struct sadb_ext *extensions[SADB_EXT_MAX + 1]) +{ + if (error == 0) + { + return TRUE; + } + else + { + loglog(RC_LOG_SERIOUS, "building of %s %s failed, code %d" + , description, text_said, error); + pfkey_extensions_free(extensions); + return FALSE; + } +} + +/* pfkey_extensions_init + pfkey_build + pfkey_msg_hdr_build */ +static bool +pfkey_msg_start(u_int8_t msg_type +, u_int8_t satype +, const char *description +, const char *text_said +, struct sadb_ext *extensions[SADB_EXT_MAX + 1]) +{ + pfkey_extensions_init(extensions); + return pfkey_build(pfkey_msg_hdr_build(&extensions[0], msg_type + , satype, 0, ++pfkey_seq, pid) + , description, text_said, extensions); +} + +/* pfkey_build + pfkey_address_build */ +static bool +pfkeyext_address(u_int16_t exttype +, const ip_address *address +, const char *description +, const char *text_said +, struct sadb_ext *extensions[SADB_EXT_MAX + 1]) +{ + /* the following variable is only needed to silence + * a warning caused by the fact that the argument + * to sockaddrof is NOT pointer to const! + */ + ip_address t = *address; + + return pfkey_build(pfkey_address_build(extensions + exttype + , exttype, 0, 0, sockaddrof(&t)) + , description, text_said, extensions); +} + +/* pfkey_build + pfkey_x_protocol_build */ +static bool +pfkeyext_protocol(int transport_proto +, const char *description +, const char *text_said +, struct sadb_ext *extensions[SADB_EXT_MAX + 1]) +{ + return (transport_proto == 0)? TRUE + : pfkey_build( + pfkey_x_protocol_build(extensions + SADB_X_EXT_PROTOCOL, transport_proto) + , description, text_said, extensions); +} + + +/* Finish (building, sending, accepting response for) PF_KEY message. + * If response isn't NULL, the response from the kernel will be + * placed there (and its errno field will not be examined). + * Returns TRUE iff all appears well. + */ +static bool +finish_pfkey_msg(struct sadb_ext *extensions[SADB_EXT_MAX + 1] +, const char *description +, const char *text_said +, pfkey_buf *response) +{ + struct sadb_msg *pfkey_msg; + bool success = TRUE; + int error; + + error = pfkey_msg_build(&pfkey_msg, extensions, EXT_BITS_IN); + + if (error != 0) + { + loglog(RC_LOG_SERIOUS, "pfkey_msg_build of %s %s failed, code %d" + , description, text_said, error); + success = FALSE; + } + else + { + size_t len = pfkey_msg->sadb_msg_len * IPSEC_PFKEYv2_ALIGN; + + DBG(DBG_KLIPS, + DBG_log("finish_pfkey_msg: %s message %u for %s %s" + , sparse_val_show(pfkey_type_names, pfkey_msg->sadb_msg_type) + , pfkey_msg->sadb_msg_seq + , description, text_said); + DBG_dump(NULL, (void *) pfkey_msg, len)); + + if (!no_klips) + { + ssize_t r = write(pfkeyfd, pfkey_msg, len); + + if (r != (ssize_t)len) + { + if (r < 0) + { + log_errno((e + , "pfkey write() of %s message %u" + " for %s %s failed" + , sparse_val_show(pfkey_type_names + , pfkey_msg->sadb_msg_type) + , pfkey_msg->sadb_msg_seq + , description, text_said)); + } + else + { + loglog(RC_LOG_SERIOUS + , "ERROR: pfkey write() of %s message %u" + " for %s %s truncated: %ld instead of %ld" + , sparse_val_show(pfkey_type_names + , pfkey_msg->sadb_msg_type) + , pfkey_msg->sadb_msg_seq + , description, text_said + , (long)r, (long)len); + } + success = FALSE; + + /* if we were compiled with debugging, but we haven't already + * dumped the KLIPS command, do so. + */ +#ifdef DEBUG + if ((cur_debugging & DBG_KLIPS) == 0) + DBG_dump(NULL, (void *) pfkey_msg, len); +#endif + } + else + { + /* Check response from KLIPS. + * It ought to be an echo, perhaps with additional info. + * If the caller wants it, response will point to space. + */ + pfkey_buf b; + pfkey_buf *bp = response != NULL? response : &b; + + if (!pfkey_get_response(bp, ((struct sadb_msg *) extensions[0])->sadb_msg_seq)) + { + loglog(RC_LOG_SERIOUS + , "ERROR: no response to our PF_KEY %s message for %s %s" + , sparse_val_show(pfkey_type_names, pfkey_msg->sadb_msg_type) + , description, text_said); + success = FALSE; + } + else if (pfkey_msg->sadb_msg_type != bp->msg.sadb_msg_type) + { + loglog(RC_LOG_SERIOUS + , "FreeS/WAN ERROR: response to our PF_KEY %s message for %s %s was of wrong type (%s)" + , sparse_name(pfkey_type_names, pfkey_msg->sadb_msg_type) + , description, text_said + , sparse_val_show(pfkey_type_names, bp->msg.sadb_msg_type)); + success = FALSE; + } + else if (response == NULL && bp->msg.sadb_msg_errno != 0) + { + /* KLIPS is signalling a problem */ + loglog(RC_LOG_SERIOUS + , "ERROR: PF_KEY %s response for %s %s included errno %u: %s" + , sparse_val_show(pfkey_type_names, pfkey_msg->sadb_msg_type) + , description, text_said + , (unsigned) bp->msg.sadb_msg_errno + , strerror(bp->msg.sadb_msg_errno)); + success = FALSE; + } + } + } + } + + /* all paths must exit this way to free resources */ + pfkey_extensions_free(extensions); + pfkey_msg_free(&pfkey_msg); + return success; +} + +/* register SA types that can be negotiated */ +void +pfkey_register_proto(unsigned satype, const char *satypename) +{ + struct sadb_ext *extensions[SADB_EXT_MAX + 1]; + pfkey_buf pfb; + + if (!(pfkey_msg_start(SADB_REGISTER + , satype + , satypename, NULL, extensions) + && finish_pfkey_msg(extensions, satypename, "", &pfb))) + { + /* ??? should this be loglog */ + plog("no KLIPS support for %s", satypename); + } + else + { + kernel_ops->pfkey_register_response(&pfb.msg); + DBG(DBG_KLIPS, + DBG_log("%s registered with kernel.", satypename)); + } +} + +static void +klips_pfkey_register(void) +{ + pfkey_register_proto(SADB_SATYPE_AH, "AH"); + pfkey_register_proto(SADB_SATYPE_ESP, "ESP"); + can_do_IPcomp = FALSE; /* until we get a response from KLIPS */ + pfkey_register_proto(SADB_X_SATYPE_COMP, "IPCOMP"); + pfkey_register_proto(SADB_X_SATYPE_IPIP, "IPIP"); +} + +static bool +pfkey_raw_eroute(const ip_address *this_host + , const ip_subnet *this_client + , const ip_address *that_host + , const ip_subnet *that_client + , ipsec_spi_t spi + , unsigned int satype + , unsigned int transport_proto + , const struct pfkey_proto_info *proto_info UNUSED + , time_t use_lifetime UNUSED + , unsigned int op + , const char *text_said) +{ + struct sadb_ext *extensions[SADB_EXT_MAX + 1]; + ip_address + sflow_ska, + dflow_ska, + smask_ska, + dmask_ska; + int sport = ntohs(portof(&this_client->addr)); + int dport = ntohs(portof(&that_client->addr)); + + networkof(this_client, &sflow_ska); + maskof(this_client, &smask_ska); + setportof(sport ? ~0:0, &smask_ska); + + networkof(that_client, &dflow_ska); + maskof(that_client, &dmask_ska); + setportof(dport ? ~0:0, &dmask_ska); + + if (!pfkey_msg_start(op & ERO_MASK, satype + , "pfkey_msg_hdr flow", text_said, extensions)) + { + return FALSE; + } + + if (op != ERO_DELETE) + { + if (!(pfkey_build(pfkey_sa_build(&extensions[SADB_EXT_SA] + , SADB_EXT_SA + , spi /* in network order */ + , 0, 0, 0, 0, op >> ERO_FLAG_SHIFT) + , "pfkey_sa add flow", text_said, extensions) + + && pfkeyext_address(SADB_EXT_ADDRESS_SRC, this_host + , "pfkey_addr_s add flow", text_said, extensions) + + && pfkeyext_address(SADB_EXT_ADDRESS_DST, that_host + , "pfkey_addr_d add flow", text_said + , extensions))) + { + return FALSE; + } + } + + if (!pfkeyext_address(SADB_X_EXT_ADDRESS_SRC_FLOW, &sflow_ska + , "pfkey_addr_sflow", text_said, extensions)) + { + return FALSE; + } + + if (!pfkeyext_address(SADB_X_EXT_ADDRESS_DST_FLOW, &dflow_ska + , "pfkey_addr_dflow", text_said, extensions)) + { + return FALSE; + } + + if (!pfkeyext_address(SADB_X_EXT_ADDRESS_SRC_MASK, &smask_ska + , "pfkey_addr_smask", text_said, extensions)) + { + return FALSE; + } + + if (!pfkeyext_address(SADB_X_EXT_ADDRESS_DST_MASK, &dmask_ska + , "pfkey_addr_dmask", text_said, extensions)) + { + return FALSE; + } + + if (!pfkeyext_protocol(transport_proto + , "pfkey_x_protocol", text_said, extensions)) + { + return FALSE; + } + + return finish_pfkey_msg(extensions, "flow", text_said, NULL); +} + +static bool +pfkey_add_sa(const struct kernel_sa *sa, bool replace) +{ + struct sadb_ext *extensions[SADB_EXT_MAX + 1]; + + return pfkey_msg_start(replace ? SADB_UPDATE : SADB_ADD, sa->satype + , "pfkey_msg_hdr Add SA", sa->text_said, extensions) + + && pfkey_build(pfkey_sa_build(&extensions[SADB_EXT_SA] + , SADB_EXT_SA + , sa->spi /* in network order */ + , sa->replay_window, SADB_SASTATE_MATURE + , sa->authalg, sa->encalg ? sa->encalg: sa->compalg, 0) + , "pfkey_sa Add SA", sa->text_said, extensions) + + && pfkeyext_address(SADB_EXT_ADDRESS_SRC, sa->src + , "pfkey_addr_s Add SA", sa->text_said, extensions) + + && pfkeyext_address(SADB_EXT_ADDRESS_DST, sa->dst + , "pfkey_addr_d Add SA", sa->text_said, extensions) + + && (sa->authkeylen == 0 + || pfkey_build(pfkey_key_build(&extensions[SADB_EXT_KEY_AUTH] + , SADB_EXT_KEY_AUTH, sa->authkeylen * BITS_PER_BYTE + , sa->authkey) + , "pfkey_key_a Add SA", sa->text_said, extensions)) + + && (sa->enckeylen == 0 + || pfkey_build(pfkey_key_build(&extensions[SADB_EXT_KEY_ENCRYPT] + , SADB_EXT_KEY_ENCRYPT, sa->enckeylen * BITS_PER_BYTE + , sa->enckey) + , "pfkey_key_e Add SA", sa->text_said, extensions)) + + && (sa->natt_type == 0 + || pfkey_build(pfkey_x_nat_t_type_build( + &extensions[SADB_X_EXT_NAT_T_TYPE], sa->natt_type), + "pfkey_nat_t_type Add ESP SA", sa->text_said, extensions)) + && (sa->natt_sport == 0 + || pfkey_build(pfkey_x_nat_t_port_build( + &extensions[SADB_X_EXT_NAT_T_SPORT], SADB_X_EXT_NAT_T_SPORT, + sa->natt_sport), "pfkey_nat_t_sport Add ESP SA", sa->text_said, + extensions)) + && (sa->natt_dport == 0 + || pfkey_build(pfkey_x_nat_t_port_build( + &extensions[SADB_X_EXT_NAT_T_DPORT], SADB_X_EXT_NAT_T_DPORT, + sa->natt_dport), "pfkey_nat_t_dport Add ESP SA", sa->text_said, + extensions)) + && (sa->natt_type == 0 || isanyaddr(sa->natt_oa) + || pfkeyext_address(SADB_X_EXT_NAT_T_OA, sa->natt_oa + , "pfkey_nat_t_oa Add ESP SA", sa->text_said, extensions)) + + && finish_pfkey_msg(extensions, "Add SA", sa->text_said, NULL); + +} + +static bool +pfkey_grp_sa(const struct kernel_sa *sa0, const struct kernel_sa *sa1) +{ + struct sadb_ext *extensions[SADB_EXT_MAX + 1]; + + return pfkey_msg_start(SADB_X_GRPSA, sa1->satype + , "pfkey_msg_hdr group", sa1->text_said, extensions) + + && pfkey_build(pfkey_sa_build(&extensions[SADB_EXT_SA] + , SADB_EXT_SA + , sa1->spi /* in network order */ + , 0, 0, 0, 0, 0) + , "pfkey_sa group", sa1->text_said, extensions) + + && pfkeyext_address(SADB_EXT_ADDRESS_DST, sa1->dst + , "pfkey_addr_d group", sa1->text_said, extensions) + + && pfkey_build(pfkey_x_satype_build(&extensions[SADB_X_EXT_SATYPE2] + , sa0->satype) + , "pfkey_satype group", sa0->text_said, extensions) + + && pfkey_build(pfkey_sa_build(&extensions[SADB_X_EXT_SA2] + , SADB_X_EXT_SA2 + , sa0->spi /* in network order */ + , 0, 0, 0, 0, 0) + , "pfkey_sa2 group", sa0->text_said, extensions) + + && pfkeyext_address(SADB_X_EXT_ADDRESS_DST2, sa0->dst + , "pfkey_addr_d2 group", sa0->text_said, extensions) + + && finish_pfkey_msg(extensions, "group", sa1->text_said, NULL); +} + +static bool +pfkey_del_sa(const struct kernel_sa *sa) +{ + struct sadb_ext *extensions[SADB_EXT_MAX + 1]; + + return pfkey_msg_start(SADB_DELETE, proto2satype(sa->proto) + , "pfkey_msg_hdr delete SA", sa->text_said, extensions) + + && pfkey_build(pfkey_sa_build(&extensions[SADB_EXT_SA] + , SADB_EXT_SA + , sa->spi /* in host order */ + , 0, SADB_SASTATE_MATURE, 0, 0, 0) + , "pfkey_sa delete SA", sa->text_said, extensions) + + && pfkeyext_address(SADB_EXT_ADDRESS_SRC, sa->src + , "pfkey_addr_s delete SA", sa->text_said, extensions) + + && pfkeyext_address(SADB_EXT_ADDRESS_DST, sa->dst + , "pfkey_addr_d delete SA", sa->text_said, extensions) + + && finish_pfkey_msg(extensions, "Delete SA", sa->text_said, NULL); +} + +void +pfkey_close(void) +{ + while (pfkey_iq_head != NULL) + { + pfkey_item *it = pfkey_iq_head; + + pfkey_iq_head = it->next; + pfree(it); + } + + close(pfkeyfd); + pfkeyfd = NULL_FD; +} + +const struct kernel_ops klips_kernel_ops = { + type: KERNEL_TYPE_KLIPS, + async_fdp: &pfkeyfd, + + pfkey_register: klips_pfkey_register, + pfkey_register_response: klips_pfkey_register_response, + process_queue: pfkey_dequeue, + process_msg: pfkey_event, + raw_eroute: pfkey_raw_eroute, + add_sa: pfkey_add_sa, + grp_sa: pfkey_grp_sa, + del_sa: pfkey_del_sa, + get_sa: NULL, + get_spi: NULL, + inbound_eroute: FALSE, + policy_lifetime: FALSE, + init: NULL +}; +#endif /* KLIPS */ diff --git a/src/pluto/kernel_pfkey.h b/src/pluto/kernel_pfkey.h new file mode 100644 index 000000000..9dbcdd341 --- /dev/null +++ b/src/pluto/kernel_pfkey.h @@ -0,0 +1,23 @@ +/* declarations of routines that interface with the kernel's pfkey mechanism + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * Copyright (C) 2003 Herbert Xu + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: kernel_pfkey.h,v 1.1 2004/03/15 20:35:28 as Exp $ + */ + +#ifdef KLIPS +extern void init_pfkey(void); +extern void pfkey_register_proto(unsigned satype, const char *satypename); +extern void pfkey_close(void); +extern const struct kernel_ops klips_kernel_ops; +#endif diff --git a/src/pluto/keys.c b/src/pluto/keys.c new file mode 100644 index 000000000..eed81230f --- /dev/null +++ b/src/pluto/keys.c @@ -0,0 +1,1514 @@ +/* mechanisms for preshared keys (public, private, and preshared secrets) + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: keys.c,v 1.24 2006/01/27 08:59:40 as Exp $ + */ + +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <resolv.h> +#include <arpa/nameser.h> /* missing from <resolv.h> on old systems */ +#include <sys/queue.h> + +#include <glob.h> +#ifndef GLOB_ABORTED +# define GLOB_ABORTED GLOB_ABEND /* fix for old versions */ +#endif + +#include <freeswan.h> +#include <ipsec_policy.h> + +#include "constants.h" +#include "defs.h" +#include "mp_defs.h" +#include "id.h" +#include "x509.h" +#include "pgp.h" +#include "certs.h" +#include "smartcard.h" +#include "connections.h" +#include "state.h" +#include "lex.h" +#include "keys.h" +#include "adns.h" /* needs <resolv.h> */ +#include "dnskey.h" /* needs keys.h and adns.h */ +#include "log.h" +#include "whack.h" /* for RC_LOG_SERIOUS */ +#include "timer.h" +#include "fetch.h" +#include "xauth.h" + +const char *shared_secrets_file = SHARED_SECRETS_FILE; + +typedef struct id_list id_list_t; + +struct id_list { + struct id id; + id_list_t *next; +}; + +typedef struct secret secret_t; + +struct secret { + id_list_t *ids; + enum PrivateKeyKind kind; + union { + chunk_t preshared_secret; + RSA_private_key_t RSA_private_key; + xauth_t xauth_secret; + smartcard_t *smartcard; + } u; + secret_t *next; +}; + +static pubkey_t* +allocate_RSA_public_key(const cert_t cert) +{ + pubkey_t *pk = alloc_thing(pubkey_t, "pubkey"); + chunk_t e, n; + + switch (cert.type) + { + case CERT_PGP: + e = cert.u.pgp->publicExponent; + n = cert.u.pgp->modulus; + break; + case CERT_X509_SIGNATURE: + e = cert.u.x509->publicExponent; + n = cert.u.x509->modulus; + break; + default: + plog("RSA public key allocation error"); + } + + init_RSA_public_key(&pk->u.rsa, e, n); + DBG(DBG_RAW, + RSA_show_public_key(&pk->u.rsa) + ) + + pk->alg = PUBKEY_ALG_RSA; + pk->id = empty_id; + pk->issuer = empty_chunk; + pk->serial = empty_chunk; + + return pk; +} + +/* + * free a public key struct + */ +static void +free_public_key(pubkey_t *pk) +{ + free_id_content(&pk->id); + freeanychunk(pk->issuer); + freeanychunk(pk->serial); + + /* algorithm-specific freeing */ + switch (pk->alg) + { + case PUBKEY_ALG_RSA: + free_RSA_public_content(&pk->u.rsa); + break; + default: + bad_case(pk->alg); + } + pfree(pk); +} + +secret_t *secrets = NULL; + +/* find the struct secret associated with the combination of + * me and the peer. We match the Id (if none, the IP address). + * Failure is indicated by a NULL. + */ +static const secret_t * +get_secret(const struct connection *c, enum PrivateKeyKind kind, bool asym) +{ + enum { /* bits */ + match_default = 01, + match_him = 02, + match_me = 04 + }; + + unsigned int best_match = 0; + secret_t *best = NULL; + secret_t *s; + const struct id *my_id = &c->spd.this.id + , *his_id = &c->spd.that.id; + struct id rw_id; + + /* is there a certificate assigned to this connection? */ + if (kind == PPK_RSA && c->spd.this.cert.type != CERT_NONE) + { + pubkey_t *my_public_key = allocate_RSA_public_key(c->spd.this.cert); + + for (s = secrets; s != NULL; s = s->next) + { + if (s->kind == kind && + same_RSA_public_key(&s->u.RSA_private_key.pub, &my_public_key->u.rsa)) + { + best = s; + break; /* we have found the private key - no sense in searching further */ + } + } + free_public_key(my_public_key); + return best; + } + + if (his_id_was_instantiated(c)) + { + /* roadwarrior: replace him with 0.0.0.0 */ + rw_id.kind = c->spd.that.id.kind; + rw_id.name = empty_chunk; + happy(anyaddr(addrtypeof(&c->spd.that.host_addr), &rw_id.ip_addr)); + his_id = &rw_id; + } + else if (kind == PPK_PSK + && (c->policy & (POLICY_PSK | POLICY_XAUTH_PSK)) + && ((c->kind == CK_TEMPLATE && c->spd.that.id.kind == ID_NONE) || + (c->kind == CK_INSTANCE && id_is_ipaddr(&c->spd.that.id)))) + { + /* roadwarrior: replace him with 0.0.0.0 */ + rw_id.kind = ID_IPV4_ADDR; + happy(anyaddr(addrtypeof(&c->spd.that.host_addr), &rw_id.ip_addr)); + his_id = &rw_id; + } + + for (s = secrets; s != NULL; s = s->next) + { + if (s->kind == kind) + { + unsigned int match = 0; + + if (s->ids == NULL) + { + /* a default (signified by lack of ids): + * accept if no more specific match found + */ + match = match_default; + } + else + { + /* check if both ends match ids */ + id_list_t *i; + + for (i = s->ids; i != NULL; i = i->next) + { + if (same_id(my_id, &i->id)) + match |= match_me; + + if (same_id(his_id, &i->id)) + match |= match_him; + } + + /* If our end matched the only id in the list, + * default to matching any peer. + * A more specific match will trump this. + */ + if (match == match_me + && s->ids->next == NULL) + match |= match_default; + } + + switch (match) + { + case match_me: + /* if this is an asymmetric (eg. public key) system, + * allow this-side-only match to count, even if + * there are other ids in the list. + */ + if (!asym) + break; + /* FALLTHROUGH */ + case match_default: /* default all */ + case match_me | match_default: /* default peer */ + case match_me | match_him: /* explicit */ + if (match == best_match) + { + /* two good matches are equally good: + * do they agree? + */ + bool same = FALSE; + + switch (kind) + { + case PPK_PSK: + same = s->u.preshared_secret.len == best->u.preshared_secret.len + && memcmp(s->u.preshared_secret.ptr, best->u.preshared_secret.ptr, s->u.preshared_secret.len) == 0; + break; + case PPK_RSA: + /* Dirty trick: since we have code to compare + * RSA public keys, but not private keys, we + * make the assumption that equal public keys + * mean equal private keys. This ought to work. + */ + same = same_RSA_public_key(&s->u.RSA_private_key.pub + , &best->u.RSA_private_key.pub); + break; + default: + bad_case(kind); + } + if (!same) + { + loglog(RC_LOG_SERIOUS, "multiple ipsec.secrets entries with distinct secrets match endpoints:" + " first secret used"); + best = s; /* list is backwards: take latest in list */ + } + } + else if (match > best_match) + { + /* this is the best match so far */ + best_match = match; + best = s; + } + } + } + } + return best; +} + +/* find the appropriate preshared key (see get_secret). + * Failure is indicated by a NULL pointer. + * Note: the result is not to be freed by the caller. + */ +const chunk_t * +get_preshared_secret(const struct connection *c) +{ + const secret_t *s = get_secret(c, PPK_PSK, FALSE); + + DBG(DBG_PRIVATE, + if (s == NULL) + DBG_log("no Preshared Key Found"); + else + DBG_dump_chunk("Preshared Key", s->u.preshared_secret); + ) + return s == NULL? NULL : &s->u.preshared_secret; +} + +/* check the existence of an RSA private key matching an RSA public + * key contained in an X.509 or OpenPGP certificate + */ +bool +has_private_key(cert_t cert) +{ + secret_t *s; + bool has_key = FALSE; + pubkey_t *pubkey = allocate_RSA_public_key(cert); + + for (s = secrets; s != NULL; s = s->next) + { + if (s->kind == PPK_RSA && + same_RSA_public_key(&s->u.RSA_private_key.pub, &pubkey->u.rsa)) + { + has_key = TRUE; + break; + } + } + free_public_key(pubkey); + return has_key; +} + +/* + * get the matching RSA private key belonging to a given X.509 certificate + */ +const RSA_private_key_t* +get_x509_private_key(const x509cert_t *cert) +{ + secret_t *s; + const RSA_private_key_t *pri = NULL; + const cert_t c = {CERT_X509_SIGNATURE, {cert}}; + + pubkey_t *pubkey = allocate_RSA_public_key(c); + + for (s = secrets; s != NULL; s = s->next) + { + if (s->kind == PPK_RSA && + same_RSA_public_key(&s->u.RSA_private_key.pub, &pubkey->u.rsa)) + { + pri = &s->u.RSA_private_key; + break; + } + } + free_public_key(pubkey); + return pri; +} + +/* find the appropriate RSA private key (see get_secret). + * Failure is indicated by a NULL pointer. + */ +const RSA_private_key_t * +get_RSA_private_key(const struct connection *c) +{ + const secret_t *s = get_secret(c, PPK_RSA, TRUE); + + return s == NULL? NULL : &s->u.RSA_private_key; +} + +/* digest a secrets file + * + * The file is a sequence of records. A record is a maximal sequence of + * tokens such that the first, and only the first, is in the first column + * of a line. + * + * Tokens are generally separated by whitespace and are key words, ids, + * strings, or data suitable for ttodata(3). As a nod to convention, + * a trailing ":" on what would otherwise be a token is taken as a + * separate token. If preceded by whitespace, a "#" is taken as starting + * a comment: it and the rest of the line are ignored. + * + * One kind of record is an include directive. It starts with "include". + * The filename is the only other token in the record. + * If the filename does not start with /, it is taken to + * be relative to the directory containing the current file. + * + * The other kind of record describes a key. It starts with a + * sequence of ids and ends with key information. Each id + * is an IP address, a Fully Qualified Domain Name (which will immediately + * be resolved), or @FQDN which will be left as a name. + * + * The key part can be in several forms. + * + * The old form of the key is still supported: a simple + * quoted strings (with no escapes) is taken as a preshred key. + * + * The new form starts the key part with a ":". + * + * For Preshared Key, use the "PSK" keyword, and follow it by a string + * or a data token suitable for ttodata(3). + * + * For RSA Private Key, use the "RSA" keyword, followed by a + * brace-enclosed list of key field keywords and data values. + * The data values are large integers to be decoded by ttodata(3). + * The fields are a subset of those used by BIND 8.2 and have the + * same names. + */ + +/* parse PSK from file */ +static err_t +process_psk_secret(chunk_t *psk) +{ + err_t ugh = NULL; + + if (*tok == '"' || *tok == '\'') + { + clonetochunk(*psk, tok+1, flp->cur - tok - 2, "PSK"); + (void) shift(); + } + else + { + char buf[BUF_LEN]; /* limit on size of binary representation of key */ + size_t sz; + + ugh = ttodatav(tok, flp->cur - tok, 0, buf, sizeof(buf), &sz + , diag_space, sizeof(diag_space), TTODATAV_SPACECOUNTS); + if (ugh != NULL) + { + /* ttodata didn't like PSK data */ + ugh = builddiag("PSK data malformed (%s): %s", ugh, tok); + } + else + { + clonetochunk(*psk, buf, sz, "PSK"); + (void) shift(); + } + } + return ugh; +} + +/* Parse fields of RSA private key. + * A braced list of keyword and value pairs. + * At the moment, each field is required, in order. + * The fields come from BIND 8.2's representation + */ +static err_t +process_rsa_secret(RSA_private_key_t *rsak) +{ + char buf[RSA_MAX_ENCODING_BYTES]; /* limit on size of binary representation of key */ + const struct fld *p; + + /* save bytes of Modulus and PublicExponent for keyid calculation */ + unsigned char ebytes[sizeof(buf)]; + unsigned char *eb_next = ebytes; + chunk_t pub_bytes[2]; + chunk_t *pb_next = &pub_bytes[0]; + + for (p = RSA_private_field; p < &RSA_private_field[RSA_PRIVATE_FIELD_ELEMENTS]; p++) + { + size_t sz; + err_t ugh; + + if (!shift()) + { + return "premature end of RSA key"; + } + else if (!tokeqword(p->name)) + { + return builddiag("%s keyword not found where expected in RSA key" + , p->name); + } + else if (!(shift() + && (!tokeq(":") || shift()))) /* ignore optional ":" */ + { + return "premature end of RSA key"; + } + else if (NULL != (ugh = ttodatav(tok, flp->cur - tok + , 0, buf, sizeof(buf), &sz, diag_space, sizeof(diag_space) + , TTODATAV_SPACECOUNTS))) + { + /* in RSA key, ttodata didn't like */ + return builddiag("RSA data malformed (%s): %s", ugh, tok); + } + else + { + MP_INT *n = (MP_INT *) ((char *)rsak + p->offset); + + n_to_mpz(n, buf, sz); + if (pb_next < &pub_bytes[elemsof(pub_bytes)]) + { + if (eb_next - ebytes + sz > sizeof(ebytes)) + return "public key takes too many bytes"; + + setchunk(*pb_next, eb_next, sz); + memcpy(eb_next, buf, sz); + eb_next += sz; + pb_next++; + } +#if 0 /* debugging info that compromises security */ + { + size_t sz = mpz_sizeinbase(n, 16); + char buf[RSA_MAX_OCTETS * 2 + 2]; /* ought to be big enough */ + + passert(sz <= sizeof(buf)); + mpz_get_str(buf, 16, n); + + loglog(RC_LOG_SERIOUS, "%s: %s", p->name, buf); + } +#endif + } + } + + /* We require an (indented) '}' and the end of the record. + * We break down the test so that the diagnostic will be + * more helpful. Some people don't seem to wish to indent + * the brace! + */ + if (!shift() || !tokeq("}")) + { + return "malformed end of RSA private key -- indented '}' required"; + } + else if (shift()) + { + return "malformed end of RSA private key -- unexpected token after '}'"; + } + else + { + unsigned bits = mpz_sizeinbase(&rsak->pub.n, 2); + + rsak->pub.k = (bits + BITS_PER_BYTE - 1) / BITS_PER_BYTE; + rsak->pub.keyid[0] = '\0'; /* in case of splitkeytoid failure */ + splitkeytoid(pub_bytes[1].ptr, pub_bytes[1].len + , pub_bytes[0].ptr, pub_bytes[0].len + , rsak->pub.keyid, sizeof(rsak->pub.keyid)); + return RSA_private_key_sanity(rsak); + } +} + +/* process rsa key file protected with optional passphrase which can either be + * read from ipsec.secrets or prompted for by using whack + */ +static err_t +process_rsa_keyfile(RSA_private_key_t *rsak, int whackfd) +{ + char filename[BUF_LEN]; + prompt_pass_t pass; + + memset(filename,'\0', BUF_LEN); + memset(pass.secret,'\0', sizeof(pass.secret)); + pass.prompt = FALSE; + pass.fd = whackfd; + + /* we expect the filename of a PKCS#1 private key file */ + + if (*tok == '"' || *tok == '\'') /* quoted filename */ + memcpy(filename, tok+1, flp->cur - tok - 2); + else + memcpy(filename, tok, flp->cur - tok); + + if (shift()) + { + /* we expect an appended passphrase or passphrase prompt*/ + if (tokeqword("%prompt")) + { + if (pass.fd == NULL_FD) + return "RSA private key file -- enter passphrase using 'ipsec secrets'"; + pass.prompt = TRUE; + } + else + { + char *passphrase = tok; + size_t len = flp->cur - passphrase; + + if (*tok == '"' || *tok == '\'') /* quoted passphrase */ + { + passphrase++; + len -= 2; + } + if (len > PROMPT_PASS_LEN) + return "RSA private key file -- passphrase exceeds 64 characters"; + + memcpy(pass.secret, passphrase, len); + } + if (shift()) + return "RSA private key file -- unexpected token after passphrase"; + } + return load_rsa_private_key(filename, &pass, rsak); +} + +/* + * process xauth secret read from ipsec.secrets + */ +static err_t +process_xauth(secret_t *s) +{ + chunk_t user_name; + + s->kind = PPK_XAUTH; + + if (!shift()) + return "missing xauth user name"; + if (*tok == '"' || *tok == '\'') /* quoted user name */ + { + user_name.ptr = tok + 1; + user_name.len = flp->cur - tok - 2; + } + else + { + user_name.ptr = tok; + user_name.len = flp->cur - tok; + } + plog(" loaded xauth credentials of user '%.*s'" + , user_name.len + , user_name.ptr); + clonetochunk(s->u.xauth_secret.user_name + , user_name.ptr, user_name.len, "xauth user name"); + + if (!shift()) + return "missing xauth user password"; + return process_psk_secret(&s->u.xauth_secret.user_password); +} + +/* get XAUTH secret from chained secrets lists + * only one entry is currently supported + */ +static bool +xauth_get_secret(xauth_t *xauth_secret) +{ + secret_t *s; + bool found = FALSE; + + for (s = secrets; s != NULL; s = s->next) + { + if (s->kind == PPK_XAUTH) + { + if (found) + { + plog("found multiple xauth secrets - first selected"); + } + else + { + found = TRUE; + *xauth_secret = s->u.xauth_secret; + } + } + } + return found; +} + +/* + * find a matching secret + */ +static bool +xauth_verify_secret(const xauth_t *xauth_secret) +{ + bool found = FALSE; + secret_t *s; + + for (s = secrets; s != NULL; s = s->next) + { + if (s->kind == PPK_XAUTH) + { + if (!same_chunk(xauth_secret->user_name, s->u.xauth_secret.user_name)) + continue; + found = TRUE; + if (same_chunk(xauth_secret->user_password, s->u.xauth_secret.user_password)) + return TRUE; + } + } + plog("xauth user '%.*s' %s" + , xauth_secret->user_name.len, xauth_secret->user_name.ptr + , found? "sent wrong password":"not found"); + return FALSE; +} + +/* + * the global xauth_module struct is defined here + */ +xauth_module_t xauth_module; + +/* + * assign the default xauth functions to any null function pointers + */ +void +xauth_defaults(void) +{ + if (xauth_module.get_secret == NULL) + { + DBG(DBG_CONTROL, + DBG_log("xauth module: using default get_secret() function") + ) + xauth_module.get_secret = xauth_get_secret; + } + if (xauth_module.verify_secret == NULL) + { + DBG(DBG_CONTROL, + DBG_log("xauth module: using default verify_secret() function") + ) + xauth_module.verify_secret = xauth_verify_secret; + } +}; + +/* + * process pin read from ipsec.secrets or prompted for it using whack + */ +static err_t +process_pin(secret_t *s, int whackfd) +{ + smartcard_t *sc; + const char *pin_status = "no pin"; + + s->kind = PPK_PIN; + + /* looking for the smartcard keyword */ + if (!shift() || strncmp(tok, SCX_TOKEN, strlen(SCX_TOKEN)) != 0) + return "PIN keyword must be followed by %smartcard<reader>:<id>"; + + sc = scx_add(scx_parse_number_slot_id(tok + strlen(SCX_TOKEN))); + s->u.smartcard = sc; + scx_share(sc); + if (sc->pin.ptr != NULL) + { + scx_release_context(sc); + scx_free_pin(&sc->pin); + } + sc->valid = FALSE; + + if (!shift()) + return "PIN statement must be terminated either by <pin code>, %pinpad or %prompt"; + + if (tokeqword("%prompt")) + { + shift(); + /* if whackfd exists, whack will be used to prompt for a pin */ + if (whackfd != NULL_FD) + pin_status = scx_get_pin(sc, whackfd) ? "valid pin" : "invalid pin"; + else + pin_status = "pin entry via prompt"; + } + else if (tokeqword("%pinpad")) + { + shift(); + /* pin will be entered via pin pad during verification */ + clonetochunk(sc->pin, "", 0, "empty pin"); + sc->pinpad = TRUE; + sc->valid = TRUE; + pin_status = "pin entry via pad"; + if (pkcs11_keep_state) + scx_verify_pin(sc); + } + else + { + /* we read the pin directly from ipsec.secrets */ + err_t ugh = process_psk_secret(&sc->pin); + if (ugh != NULL) + return ugh; + /* verify the pin */ + pin_status = scx_verify_pin(sc) ? "valid PIN" : "invalid PIN"; + } +#ifdef SMARTCARD + { + char buf[BUF_LEN]; + + if (sc->any_slot) + snprintf(buf, BUF_LEN, "any slot"); + else + snprintf(buf, BUF_LEN, "slot: %lu", sc->slot); + + plog(" %s for #%d (%s, id: %s)" + , pin_status, sc->number, scx_print_slot(sc, ""), sc->id); + } +#else + plog(" warning: SMARTCARD support is deactivated in pluto/Makefile!"); +#endif + return NULL; +} + +static void +process_secret(secret_t *s, int whackfd) +{ + err_t ugh = NULL; + + s->kind = PPK_PSK; /* default */ + if (*tok == '"' || *tok == '\'') + { + /* old PSK format: just a string */ + ugh = process_psk_secret(&s->u.preshared_secret); + } + else if (tokeqword("psk")) + { + /* preshared key: quoted string or ttodata format */ + ugh = !shift()? "unexpected end of record in PSK" + : process_psk_secret(&s->u.preshared_secret); + } + else if (tokeqword("rsa")) + { + /* RSA key: the fun begins. + * A braced list of keyword and value pairs. + */ + s->kind = PPK_RSA; + if (!shift()) + { + ugh = "bad RSA key syntax"; + } + else if (tokeq("{")) + { + ugh = process_rsa_secret(&s->u.RSA_private_key); + } + else + { + ugh = process_rsa_keyfile(&s->u.RSA_private_key, whackfd); + } + } + else if (tokeqword("xauth")) + { + ugh = process_xauth(s); + } + else if (tokeqword("pin")) + { + ugh = process_pin(s, whackfd); + } + else + { + ugh = builddiag("unrecognized key format: %s", tok); + } + + if (ugh != NULL) + { + loglog(RC_LOG_SERIOUS, "\"%s\" line %d: %s" + , flp->filename, flp->lino, ugh); + pfree(s); + } + else if (flushline("expected record boundary in key")) + { + /* gauntlet has been run: install new secret */ + lock_certs_and_keys("process_secret"); + s->next = secrets; + secrets = s; + unlock_certs_and_keys("process_secrets"); + } +} + +static void process_secrets_file(const char *file_pat, int whackfd); /* forward declaration */ + +static void +process_secret_records(int whackfd) +{ + /* read records from ipsec.secrets and load them into our table */ + for (;;) + { + (void)flushline(NULL); /* silently ditch leftovers, if any */ + if (flp->bdry == B_file) + break; + + flp->bdry = B_none; /* eat the Record Boundary */ + (void)shift(); /* get real first token */ + + if (tokeqword("include")) + { + /* an include directive */ + char fn[MAX_TOK_LEN]; /* space for filename (I hope) */ + char *p = fn; + char *end_prefix = strrchr(flp->filename, '/'); + + if (!shift()) + { + loglog(RC_LOG_SERIOUS, "\"%s\" line %d: unexpected end of include directive" + , flp->filename, flp->lino); + continue; /* abandon this record */ + } + + /* if path is relative and including file's pathname has + * a non-empty dirname, prefix this path with that dirname. + */ + if (tok[0] != '/' && end_prefix != NULL) + { + size_t pl = end_prefix - flp->filename + 1; + + /* "clamp" length to prevent problems now; + * will be rediscovered and reported later. + */ + if (pl > sizeof(fn)) + pl = sizeof(fn); + memcpy(fn, flp->filename, pl); + p += pl; + } + if (flp->cur - tok >= &fn[sizeof(fn)] - p) + { + loglog(RC_LOG_SERIOUS, "\"%s\" line %d: include pathname too long" + , flp->filename, flp->lino); + continue; /* abandon this record */ + } + strcpy(p, tok); + (void) shift(); /* move to Record Boundary, we hope */ + if (flushline("ignoring malformed INCLUDE -- expected Record Boundary after filename")) + { + process_secrets_file(fn, whackfd); + tok = NULL; /* correct, but probably redundant */ + } + } + else + { + /* expecting a list of indices and then the key info */ + secret_t *s = alloc_thing(secret_t, "secret"); + + s->ids = NULL; + s->kind = PPK_PSK; /* default */ + setchunk(s->u.preshared_secret, NULL, 0); + s->next = NULL; + + for (;;) + { + if (tok[0] == '"' || tok[0] == '\'') + { + /* found key part */ + process_secret(s, whackfd); + break; + } + else if (tokeq(":")) + { + /* found key part */ + shift(); /* discard explicit separator */ + process_secret(s, whackfd); + break; + } + else + { + /* an id + * See RFC2407 IPsec Domain of Interpretation 4.6.2 + */ + struct id id; + err_t ugh; + + if (tokeq("%any")) + { + id = empty_id; + id.kind = ID_IPV4_ADDR; + ugh = anyaddr(AF_INET, &id.ip_addr); + } + else if (tokeq("%any6")) + { + id = empty_id; + id.kind = ID_IPV6_ADDR; + ugh = anyaddr(AF_INET6, &id.ip_addr); + } + else + { + ugh = atoid(tok, &id, FALSE); + } + + if (ugh != NULL) + { + loglog(RC_LOG_SERIOUS + , "ERROR \"%s\" line %d: index \"%s\" %s" + , flp->filename, flp->lino, tok, ugh); + } + else + { + id_list_t *i = alloc_thing(id_list_t + , "id_list"); + + i->id = id; + unshare_id_content(&i->id); + i->next = s->ids; + s->ids = i; + /* DBG_log("id type %d: %s %.*s", i->kind, ip_str(&i->ip_addr), (int)i->name.len, i->name.ptr); */ + } + if (!shift()) + { + /* unexpected Record Boundary or EOF */ + loglog(RC_LOG_SERIOUS, "\"%s\" line %d: unexpected end of id list" + , flp->filename, flp->lino); + break; + } + } + } + } + } +} + +static int +globugh(const char *epath, int eerrno) +{ + log_errno_routine(eerrno, "problem with secrets file \"%s\"", epath); + return 1; /* stop glob */ +} + +static void +process_secrets_file(const char *file_pat, int whackfd) +{ + struct file_lex_position pos; + char **fnp; + glob_t globbuf; + + pos.depth = flp == NULL? 0 : flp->depth + 1; + + if (pos.depth > 10) + { + loglog(RC_LOG_SERIOUS, "preshared secrets file \"%s\" nested too deeply", file_pat); + return; + } + + /* do globbing */ + { + int r = glob(file_pat, GLOB_ERR, globugh, &globbuf); + + if (r != 0) + { + switch (r) + { + case GLOB_NOSPACE: + loglog(RC_LOG_SERIOUS, "out of space processing secrets filename \"%s\"", file_pat); + break; + case GLOB_ABORTED: + break; /* already logged */ + case GLOB_NOMATCH: + loglog(RC_LOG_SERIOUS, "no secrets filename matched \"%s\"", file_pat); + break; + default: + loglog(RC_LOG_SERIOUS, "unknown glob error %d", r); + break; + } + globfree(&globbuf); + return; + } + } + + /* for each file... */ + for (fnp = globbuf.gl_pathv; *fnp != NULL; fnp++) + { + if (lexopen(&pos, *fnp, FALSE)) + { + plog("loading secrets from \"%s\"", *fnp); + (void) flushline("file starts with indentation (continuation notation)"); + process_secret_records(whackfd); + lexclose(); + } + } + + globfree(&globbuf); +} + +void +free_preshared_secrets(void) +{ + lock_certs_and_keys("free_preshared_secrets"); + + if (secrets != NULL) + { + secret_t *s, *ns; + + plog("forgetting secrets"); + + for (s = secrets; s != NULL; s = ns) + { + id_list_t *i, *ni; + + ns = s->next; /* grab before freeing s */ + for (i = s->ids; i != NULL; i = ni) + { + ni = i->next; /* grab before freeing i */ + free_id_content(&i->id); + pfree(i); + } + switch (s->kind) + { + case PPK_PSK: + pfree(s->u.preshared_secret.ptr); + break; + case PPK_RSA: + free_RSA_private_content(&s->u.RSA_private_key); + break; + case PPK_XAUTH: + pfree(s->u.xauth_secret.user_name.ptr); + pfree(s->u.xauth_secret.user_password.ptr); + break; + case PPK_PIN: + scx_release(s->u.smartcard); + break; + default: + bad_case(s->kind); + } + pfree(s); + } + secrets = NULL; + } + + unlock_certs_and_keys("free_preshard_secrets"); +} + +void +load_preshared_secrets(int whackfd) +{ + free_preshared_secrets(); + (void) process_secrets_file(shared_secrets_file, whackfd); +} + +/* public key machinery + * Note: caller must set dns_auth_level. + */ + +pubkey_t * +public_key_from_rsa(const RSA_public_key_t *k) +{ + pubkey_t *p = alloc_thing(pubkey_t, "pubkey"); + + p->id = empty_id; /* don't know, doesn't matter */ + p->issuer = empty_chunk; + p->serial = empty_chunk; + p->alg = PUBKEY_ALG_RSA; + + memcpy(p->u.rsa.keyid, k->keyid, sizeof(p->u.rsa.keyid)); + p->u.rsa.k = k->k; + mpz_init_set(&p->u.rsa.e, &k->e); + mpz_init_set(&p->u.rsa.n, &k->n); + + /* note that we return a 1 reference count upon creation: + * invariant: recount > 0. + */ + p->refcnt = 1; + time(&p->installed_time); + return p; +} + +/* Free a public key record. + * As a convenience, this returns a pointer to next. + */ +pubkey_list_t * +free_public_keyentry(pubkey_list_t *p) +{ + pubkey_list_t *nxt = p->next; + + if (p->key != NULL) + unreference_key(&p->key); + pfree(p); + return nxt; +} + +void +free_public_keys(pubkey_list_t **keys) +{ + while (*keys != NULL) + *keys = free_public_keyentry(*keys); +} + +/* root of chained public key list */ + +pubkey_list_t *pubkeys = NULL; /* keys from ipsec.conf */ + +void +free_remembered_public_keys(void) +{ + free_public_keys(&pubkeys); +} + +/* transfer public keys from *keys list to front of pubkeys list */ +void +transfer_to_public_keys(struct gw_info *gateways_from_dns +#ifdef USE_KEYRR +, pubkey_list_t **keys +#endif /* USE_KEYRR */ +) +{ + { + struct gw_info *gwp; + + for (gwp = gateways_from_dns; gwp != NULL; gwp = gwp->next) + { + pubkey_list_t *pl = alloc_thing(pubkey_list_t, "from TXT"); + + pl->key = gwp->key; /* note: this is a transfer */ + gwp->key = NULL; /* really, it is! */ + pl->next = pubkeys; + pubkeys = pl; + } + } + +#ifdef USE_KEYRR + { + pubkey_list_t **pp = keys; + + while (*pp != NULL) + pp = &(*pp)->next; + *pp = pubkeys; + pubkeys = *keys; + *keys = NULL; + } +#endif /* USE_KEYRR */ +} + +/* decode of RSA pubkey chunk + * - format specified in RFC 2537 RSA/MD5 Keys and SIGs in the DNS + * - exponent length in bytes (1 or 3 octets) + * + 1 byte if in [1, 255] + * + otherwise 0x00 followed by 2 bytes of length + * - exponent + * - modulus + */ +err_t +unpack_RSA_public_key(RSA_public_key_t *rsa, const chunk_t *pubkey) +{ + chunk_t exp; + chunk_t mod; + + if (pubkey->len < 3) + return "RSA public key blob way to short"; /* not even room for length! */ + + if (pubkey->ptr[0] != 0x00) + { + setchunk(exp, pubkey->ptr + 1, pubkey->ptr[0]); + } + else + { + setchunk(exp, pubkey->ptr + 3 + , (pubkey->ptr[1] << BITS_PER_BYTE) + pubkey->ptr[2]); + } + + if (pubkey->len - (exp.ptr - pubkey->ptr) < exp.len + RSA_MIN_OCTETS_RFC) + return "RSA public key blob too short"; + + mod.ptr = exp.ptr + exp.len; + mod.len = &pubkey->ptr[pubkey->len] - mod.ptr; + + if (mod.len < RSA_MIN_OCTETS) + return RSA_MIN_OCTETS_UGH; + + if (mod.len > RSA_MAX_OCTETS) + return RSA_MAX_OCTETS_UGH; + + init_RSA_public_key(rsa, exp, mod); + rsa->k = mpz_sizeinbase(&rsa->n, 2); /* size in bits, for a start */ + rsa->k = (rsa->k + BITS_PER_BYTE - 1) / BITS_PER_BYTE; /* now octets */ + DBG(DBG_RAW, + RSA_show_public_key(rsa) + ) + + if (rsa->k != mod.len) + { + mpz_clear(&rsa->e); + mpz_clear(&rsa->n); + return "RSA modulus shorter than specified"; + } + + return NULL; +} + +static void +install_public_key(pubkey_t *pk, pubkey_list_t **head) +{ + pubkey_list_t *p = alloc_thing(pubkey_list_t, "pubkey entry"); + + unshare_id_content(&pk->id); + + /* copy issuer dn */ + if (pk->issuer.ptr != NULL) + pk->issuer.ptr = clone_bytes(pk->issuer.ptr, pk->issuer.len, "issuer dn"); + + /* copy serial number */ + if (pk->serial.ptr != NULL) + pk->serial.ptr = clone_bytes(pk->serial.ptr, pk->serial.len, "serialNumber"); + + /* store the time the public key was installed */ + time(&pk->installed_time); + + /* install new key at front */ + p->key = reference_key(pk); + p->next = *head; + *head = p; +} + + +void +delete_public_keys(const struct id *id, enum pubkey_alg alg +, chunk_t issuer, chunk_t serial) +{ + pubkey_list_t **pp, *p; + pubkey_t *pk; + + for (pp = &pubkeys; (p = *pp) != NULL; ) + { + pk = p->key; + + if (same_id(id, &pk->id) && pk->alg == alg + && (issuer.ptr == NULL || pk->issuer.ptr == NULL + || same_dn(issuer, pk->issuer)) + && same_serial(serial, pk->serial)) + *pp = free_public_keyentry(p); + else + pp = &p->next; + } +} + +pubkey_t * +reference_key(pubkey_t *pk) +{ + pk->refcnt++; + return pk; +} + +void +unreference_key(pubkey_t **pkp) +{ + pubkey_t *pk = *pkp; + char b[BUF_LEN]; + + if (pk == NULL) + return; + + /* print stuff */ + DBG(DBG_CONTROLMORE, + idtoa(&pk->id, b, sizeof(b)); + DBG_log("unreference key: %p %s cnt %d--", pk, b, pk->refcnt) + ) + + /* cancel out the pointer */ + *pkp = NULL; + + passert(pk->refcnt != 0); + pk->refcnt--; + if (pk->refcnt == 0) + free_public_key(pk); +} + +err_t +add_public_key(const struct id *id +, enum dns_auth_level dns_auth_level +, enum pubkey_alg alg +, const chunk_t *key +, pubkey_list_t **head) +{ + pubkey_t *pk = alloc_thing(pubkey_t, "pubkey"); + + /* first: algorithm-specific decoding of key chunk */ + switch (alg) + { + case PUBKEY_ALG_RSA: + { + err_t ugh = unpack_RSA_public_key(&pk->u.rsa, key); + + if (ugh != NULL) + { + pfree(pk); + return ugh; + } + } + break; + default: + bad_case(alg); + } + + pk->id = *id; + pk->dns_auth_level = dns_auth_level; + pk->alg = alg; + pk->until_time = UNDEFINED_TIME; + pk->issuer = empty_chunk; + pk->serial = empty_chunk; + + install_public_key(pk, head); + return NULL; +} + +/* extract id and public key from x.509 certificate and + * insert it into a pubkeyrec + */ +void +add_x509_public_key(x509cert_t *cert , time_t until + , enum dns_auth_level dns_auth_level) +{ + generalName_t *gn; + pubkey_t *pk; + cert_t c = { CERT_X509_SIGNATURE, {cert} }; + + /* we support RSA only */ + if (cert->subjectPublicKeyAlgorithm != PUBKEY_ALG_RSA) + return; + + /* ID type: ID_DER_ASN1_DN (X.509 subject field) */ + pk = allocate_RSA_public_key(c); + pk->id.kind = ID_DER_ASN1_DN; + pk->id.name = cert->subject; + pk->dns_auth_level = dns_auth_level; + pk->until_time = until; + pk->issuer = cert->issuer; + pk->serial = cert->serialNumber; + delete_public_keys(&pk->id, pk->alg, pk->issuer, pk->serial); + install_public_key(pk, &pubkeys); + + gn = cert->subjectAltName; + + while (gn != NULL) /* insert all subjectAltNames */ + { + struct id id = empty_id; + + gntoid(&id, gn); + if (id.kind != ID_NONE) + { + pk = allocate_RSA_public_key(c); + pk->id = id; + pk->dns_auth_level = dns_auth_level; + pk->until_time = until; + pk->issuer = cert->issuer; + pk->serial = cert->serialNumber; + delete_public_keys(&pk->id, pk->alg, pk->issuer, pk->serial); + install_public_key(pk, &pubkeys); + } + gn = gn->next; + } +} + +/* extract id and public key from OpenPGP certificate and + * insert it into a pubkeyrec + */ +void +add_pgp_public_key(pgpcert_t *cert , time_t until + , enum dns_auth_level dns_auth_level) +{ + pubkey_t *pk; + cert_t c; + + c.type = CERT_PGP; + c.u.pgp = cert; + + /* we support RSA only */ + if (cert->pubkeyAlg != PUBKEY_ALG_RSA) + { + plog(" RSA public keys supported only"); + return; + } + + pk = allocate_RSA_public_key(c); + pk->id.kind = ID_KEY_ID; + pk->id.name.ptr = cert->fingerprint; + pk->id.name.len = PGP_FINGERPRINT_SIZE; + pk->dns_auth_level = dns_auth_level; + pk->until_time = until; + delete_public_keys(&pk->id, pk->alg, empty_chunk, empty_chunk); + install_public_key(pk, &pubkeys); +} + +/* when a X.509 certificate gets revoked, all instances of + * the corresponding public key must be removed + */ +void +remove_x509_public_key(const x509cert_t *cert) +{ + const cert_t c = {CERT_X509_SIGNATURE, {cert}}; + pubkey_list_t *p, **pp; + pubkey_t *revoked_pk; + + revoked_pk = allocate_RSA_public_key(c); + p = pubkeys; + pp = &pubkeys; + + while(p != NULL) + { + if (same_RSA_public_key(&p->key->u.rsa, &revoked_pk->u.rsa)) + { + /* remove p from list and free memory */ + *pp = free_public_keyentry(p); + loglog(RC_LOG_SERIOUS, + "invalid RSA public key deleted"); + } + else + { + pp = &p->next; + } + p =*pp; + } + free_public_key(revoked_pk); +} + +/* + * list all public keys in the chained list + */ +void list_public_keys(bool utc) +{ + pubkey_list_t *p = pubkeys; + + if (p != NULL) + { + whack_log(RC_COMMENT, " "); + whack_log(RC_COMMENT, "List of Public Keys:"); + whack_log(RC_COMMENT, " "); + } + + while (p != NULL) + { + pubkey_t *key = p->key; + + if (key->alg == PUBKEY_ALG_RSA) + { + char buf[BUF_LEN]; + char expires_buf[TIMETOA_BUF]; + + idtoa(&key->id, buf, BUF_LEN); + strcpy(expires_buf, timetoa(&key->until_time, utc)); + whack_log(RC_COMMENT, "%s, %4d RSA Key %s, until %s %s", + + timetoa(&key->installed_time, utc), 8*key->u.rsa.k, key->u.rsa.keyid, + expires_buf, + check_expiry(key->until_time, PUBKEY_WARNING_INTERVAL, TRUE)); + whack_log(RC_COMMENT," %s '%s'", + enum_show(&ident_names, key->id.kind), buf); + if (key->issuer.len > 0) + { + dntoa(buf, BUF_LEN, key->issuer); + whack_log(RC_COMMENT," issuer: '%s'", buf); + } + if (key->serial.len > 0) + { + datatot(key->serial.ptr, key->serial.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT," serial: %s", buf); + } + } + p = p->next; + } +} diff --git a/src/pluto/keys.h b/src/pluto/keys.h new file mode 100644 index 000000000..415bdc3c1 --- /dev/null +++ b/src/pluto/keys.h @@ -0,0 +1,113 @@ +/* mechanisms for preshared keys (public, private, and preshared secrets) + * Copyright (C) 1998-2002 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: keys.h,v 1.7 2006/01/26 20:10:34 as Exp $ + */ + +#ifndef _KEYS_H +#define _KEYS_H + +#include <gmp.h> /* GNU Multi-Precision library */ + +#include "pkcs1.h" +#include "certs.h" + +#ifndef SHARED_SECRETS_FILE +# define SHARED_SECRETS_FILE IPSEC_CONFDIR "/ipsec.secrets" +#endif + +const char *shared_secrets_file; + +extern void load_preshared_secrets(int whackfd); +extern void free_preshared_secrets(void); + +enum PrivateKeyKind { + PPK_PSK, + /* PPK_DSS, */ /* not implemented */ + PPK_RSA, + PPK_XAUTH, + PPK_PIN +}; + +extern void xauth_defaults(void); + +/* forward declaration */ +struct connection; + +extern const chunk_t *get_preshared_secret(const struct connection *c); +extern err_t unpack_RSA_public_key(RSA_public_key_t *rsa, const chunk_t *pubkey); +extern const RSA_private_key_t *get_RSA_private_key(const struct connection *c); +extern const RSA_private_key_t *get_x509_private_key(const x509cert_t *cert); + +/* public key machinery */ + +typedef struct pubkey pubkey_t; + +struct pubkey { + struct id id; + unsigned refcnt; /* reference counted! */ + enum dns_auth_level dns_auth_level; + char *dns_sig; + time_t installed_time + , last_tried_time + , last_worked_time + , until_time; + chunk_t issuer; + chunk_t serial; + enum pubkey_alg alg; + union { + RSA_public_key_t rsa; + } u; +}; + +typedef struct pubkey_list pubkey_list_t; + +struct pubkey_list { + pubkey_t *key; + pubkey_list_t *next; +}; + +extern pubkey_list_t *pubkeys; /* keys from ipsec.conf or from certs */ + +extern pubkey_t *public_key_from_rsa(const RSA_public_key_t *k); +extern pubkey_list_t *free_public_keyentry(pubkey_list_t *p); +extern void free_public_keys(pubkey_list_t **keys); +extern void free_remembered_public_keys(void); +extern void delete_public_keys(const struct id *id, enum pubkey_alg alg + , chunk_t issuer, chunk_t serial); + +extern pubkey_t *reference_key(pubkey_t *pk); +extern void unreference_key(pubkey_t **pkp); + +extern err_t add_public_key(const struct id *id + , enum dns_auth_level dns_auth_level + , enum pubkey_alg alg + , const chunk_t *key + , pubkey_list_t **head); + +extern bool has_private_key(cert_t cert); +extern void add_x509_public_key(x509cert_t *cert, time_t until + , enum dns_auth_level dns_auth_level); +extern void add_pgp_public_key(pgpcert_t *cert, time_t until + , enum dns_auth_level dns_auth_level); +extern void remove_x509_public_key(const x509cert_t *cert); +extern void list_public_keys(bool utc); + +struct gw_info; /* forward declaration of tag (defined in dnskey.h) */ +extern void transfer_to_public_keys(struct gw_info *gateways_from_dns +#ifdef USE_KEYRR + , pubkey_list_t **keys +#endif /* USE_KEYRR */ + ); + +#endif /* _KEYS_H */ diff --git a/src/pluto/lex.c b/src/pluto/lex.c new file mode 100644 index 000000000..5c811725a --- /dev/null +++ b/src/pluto/lex.c @@ -0,0 +1,213 @@ +/* lexer (lexical analyzer) for control files + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: lex.c,v 1.1 2004/03/15 20:35:28 as Exp $ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <errno.h> + +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "whack.h" /* for RC_LOG_SERIOUS */ +#include "lex.h" + +struct file_lex_position *flp = NULL; + +/* Open a file for lexical processing. + * new_flp and name must point into storage with will live + * at least until the file is closed. + */ +bool +lexopen(struct file_lex_position *new_flp, const char *name, bool optional) +{ + FILE *f = fopen(name, "r"); + + if (f == NULL) + { + if (!optional || errno != ENOENT) + log_errno((e, "could not open \"%s\"", name)); + return FALSE; + } + else + { + new_flp->previous = flp; + flp = new_flp; + flp->filename = name; + flp->fp = f; + flp->lino = 0; + flp->bdry = B_none; + + flp->cur = flp->buffer; /* nothing loaded yet */ + flp->under = *flp->cur = '\0'; + + (void) shift(); /* prime tok */ + return TRUE; + } +} + +void +lexclose(void) +{ + fclose(flp->fp); + flp = flp->previous; +} + +/* Token decoding: shift() loads the next token into tok. + * Iff a token starts at the left margin, it is considered + * to be the first in a record. We create a special condition, + * Record Boundary (analogous to EOF), just before such a token. + * We are unwilling to shift through a record boundary: + * it must be overridden first. + * Returns FALSE iff Record Boundary or EOF (i.e. no token); + * tok will then be NULL. + */ + +char *tok; +#define tokeq(s) (streq(tok, (s))) +#define tokeqword(s) (strcasecmp(tok, (s)) == 0) + +bool +shift(void) +{ + char *p = flp->cur; + char *sor = NULL; /* start of record for any new lines */ + + passert(flp->bdry == B_none); + + *p = flp->under; + flp->under = '\0'; + + for (;;) + { + switch (*p) + { + case '\0': /* end of line */ + case '#': /* comment to end of line: treat as end of line */ + /* get the next line */ + if (fgets(flp->buffer, sizeof(flp->buffer)-1, flp->fp) == NULL) + { + flp->bdry = B_file; + tok = flp->cur = NULL; + return FALSE; + } + else + { + /* strip trailing whitespace, including \n */ + + for (p = flp->buffer+strlen(flp->buffer)-1 + ; p>flp->buffer && isspace(p[-1]); p--) + ; + *p = '\0'; + + flp->lino++; + sor = p = flp->buffer; + } + break; /* try again for a token */ + + case ' ': /* whitespace */ + case '\t': + p++; + break; /* try again for a token */ + + case '"': /* quoted token */ + case '\'': + if (p != sor) + { + /* we have a quoted token: note and advance to its end */ + tok = p; + p = strchr(p+1, *p); + if (p == NULL) + { + loglog(RC_LOG_SERIOUS, "\"%s\" line %d: unterminated string" + , flp->filename, flp->lino); + p = tok + strlen(tok); + } + else + { + p++; /* include delimiter in token */ + } + + /* remember token delimiter and replace with '\0' */ + flp->under = *p; + *p = '\0'; + flp->cur = p; + return TRUE; + } + /* FALL THROUGH */ + default: + if (p != sor) + { + /* we seem to have a token: note and advance to its end */ + tok = p; + + if (p[0] == '0' && p[1] == 't') + { + /* 0t... token goes to end of line */ + p += strlen(p); + } + else + { + /* "ordinary" token: up to whitespace or end of line */ + do { + p++; + } while (*p != '\0' && !isspace(*p)) + ; + + /* fudge to separate ':' from a preceding adjacent token */ + if (p-1 > tok && p[-1] == ':') + p--; + } + + /* remember token delimiter and replace with '\0' */ + flp->under = *p; + *p = '\0'; + flp->cur = p; + return TRUE; + } + + /* we have a start-of-record: return it, deferring "real" token */ + flp->bdry = B_record; + tok = NULL; + flp->under = *p; + flp->cur = p; + return FALSE; + } + } +} + +/* ensures we are at a Record (or File) boundary, optionally warning if not */ + +bool +flushline(const char *m) +{ + if (flp->bdry != B_none) + { + return TRUE; + } + else + { + if (m != NULL) + loglog(RC_LOG_SERIOUS, "\"%s\" line %d: %s", flp->filename, flp->lino, m); + do ; while (shift()); + return FALSE; + } +} diff --git a/src/pluto/lex.h b/src/pluto/lex.h new file mode 100644 index 000000000..fb6c15236 --- /dev/null +++ b/src/pluto/lex.h @@ -0,0 +1,52 @@ +/* lexer (lexical analyzer) for control files + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: lex.h,v 1.1 2004/03/15 20:35:28 as Exp $ + */ + +#define MAX_TOK_LEN 2048 /* includes terminal '\0' */ +struct file_lex_position +{ + int depth; /* how deeply we are nested */ + const char *filename; + FILE *fp; + enum { B_none, B_record, B_file } bdry; /* current boundary */ + int lino; /* line number in file */ + char buffer[MAX_TOK_LEN + 1]; /* note: one extra char for our use (jamming '"') */ + char *cur; /* cursor */ + char under; /* except in shift(): character orignally at *cur */ + struct file_lex_position *previous; +}; + +extern struct file_lex_position *flp; + +extern bool lexopen(struct file_lex_position *new_flp, const char *name, bool optional); +extern void lexclose(void); + + +/* Token decoding: shift() loads the next token into tok. + * Iff a token starts at the left margin, it is considered + * to be the first in a record. We create a special condition, + * Record Boundary (analogous to EOF), just before such a token. + * We are unwilling to shift through a record boundary: + * it must be overridden first. + * Returns FALSE iff Record Boundary or EOF (i.e. no token); + * tok will then be NULL. + */ + +extern char *tok; +#define tokeq(s) (streq(tok, (s))) +#define tokeqword(s) (strcasecmp(tok, (s)) == 0) + +extern bool shift(void); +extern bool flushline(const char *m); diff --git a/src/pluto/linux26/netlink.h b/src/pluto/linux26/netlink.h new file mode 100644 index 000000000..6b0896da6 --- /dev/null +++ b/src/pluto/linux26/netlink.h @@ -0,0 +1,90 @@ +#ifndef __LINUX_NETLINK_H +#define __LINUX_NETLINK_H + +#include <stdint.h> +#include <sys/socket.h> /* for sa_family_t */ + +#define NETLINK_ROUTE 0 /* Routing/device hook */ +#define NETLINK_SKIP 1 /* Reserved for ENskip */ +#define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */ +#define NETLINK_FIREWALL 3 /* Firewalling hook */ +#define NETLINK_TCPDIAG 4 /* TCP socket monitoring */ +#define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */ +#define NETLINK_XFRM 6 /* ipsec */ +#define NETLINK_ARPD 8 +#define NETLINK_ROUTE6 11 /* af_inet6 route comm channel */ +#define NETLINK_IP6_FW 13 +#define NETLINK_DNRTMSG 14 /* DECnet routing messages */ +#define NETLINK_TAPBASE 16 /* 16 to 31 are ethertap */ + +#define MAX_LINKS 32 + +struct sockaddr_nl +{ + sa_family_t nl_family; /* AF_NETLINK */ + unsigned short nl_pad; /* zero */ + uint32_t nl_pid; /* process pid */ + uint32_t nl_groups; /* multicast groups mask */ +}; + +struct nlmsghdr +{ + uint32_t nlmsg_len; /* Length of message including header */ + uint16_t nlmsg_type; /* Message content */ + uint16_t nlmsg_flags; /* Additional flags */ + uint32_t nlmsg_seq; /* Sequence number */ + uint32_t nlmsg_pid; /* Sending process PID */ +}; + +/* Flags values */ + +#define NLM_F_REQUEST 1 /* It is request message. */ +#define NLM_F_MULTI 2 /* Multipart message, terminated by NLMSG_DONE */ +#define NLM_F_ACK 4 /* Reply with ack, with zero or error code */ +#define NLM_F_ECHO 8 /* Echo this request */ + +/* Modifiers to GET request */ +#define NLM_F_ROOT 0x100 /* specify tree root */ +#define NLM_F_MATCH 0x200 /* return all matching */ +#define NLM_F_ATOMIC 0x400 /* atomic GET */ +#define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH) + +/* Modifiers to NEW request */ +#define NLM_F_REPLACE 0x100 /* Override existing */ +#define NLM_F_EXCL 0x200 /* Do not touch, if it exists */ +#define NLM_F_CREATE 0x400 /* Create, if it does not exist */ +#define NLM_F_APPEND 0x800 /* Add to end of list */ + +/* + 4.4BSD ADD NLM_F_CREATE|NLM_F_EXCL + 4.4BSD CHANGE NLM_F_REPLACE + + True CHANGE NLM_F_CREATE|NLM_F_REPLACE + Append NLM_F_CREATE + Check NLM_F_EXCL + */ + +#define NLMSG_ALIGNTO 4 +#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) ) +#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr))) +#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len)) +#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0))) +#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \ + (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len))) +#define NLMSG_OK(nlh,len) ((len) > 0 && (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \ + (nlh)->nlmsg_len <= (len)) +#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len))) + +#define NLMSG_NOOP 0x1 /* Nothing. */ +#define NLMSG_ERROR 0x2 /* Error */ +#define NLMSG_DONE 0x3 /* End of a dump */ +#define NLMSG_OVERRUN 0x4 /* Data lost */ + +struct nlmsgerr +{ + int error; + struct nlmsghdr msg; +}; + +#define NET_MAJOR 36 /* Major 36 is reserved for networking */ +#endif /* __LINUX_NETLINK_H */ diff --git a/src/pluto/linux26/rtnetlink.h b/src/pluto/linux26/rtnetlink.h new file mode 100644 index 000000000..341bc1f86 --- /dev/null +++ b/src/pluto/linux26/rtnetlink.h @@ -0,0 +1,562 @@ +#ifndef __LINUX_RTNETLINK_H +#define __LINUX_RTNETLINK_H + +#include "netlink.h" +#include <stdint.h> + +#define RTNL_DEBUG 1 + + +/**** + * Routing/neighbour discovery messages. + ****/ + +/* Types of messages */ + +#define RTM_BASE 0x10 + +#define RTM_NEWLINK (RTM_BASE+0) +#define RTM_DELLINK (RTM_BASE+1) +#define RTM_GETLINK (RTM_BASE+2) +#define RTM_SETLINK (RTM_BASE+3) + +#define RTM_NEWADDR (RTM_BASE+4) +#define RTM_DELADDR (RTM_BASE+5) +#define RTM_GETADDR (RTM_BASE+6) + +#define RTM_NEWROUTE (RTM_BASE+8) +#define RTM_DELROUTE (RTM_BASE+9) +#define RTM_GETROUTE (RTM_BASE+10) + +#define RTM_NEWNEIGH (RTM_BASE+12) +#define RTM_DELNEIGH (RTM_BASE+13) +#define RTM_GETNEIGH (RTM_BASE+14) + +#define RTM_NEWRULE (RTM_BASE+16) +#define RTM_DELRULE (RTM_BASE+17) +#define RTM_GETRULE (RTM_BASE+18) + +#define RTM_NEWQDISC (RTM_BASE+20) +#define RTM_DELQDISC (RTM_BASE+21) +#define RTM_GETQDISC (RTM_BASE+22) + +#define RTM_NEWTCLASS (RTM_BASE+24) +#define RTM_DELTCLASS (RTM_BASE+25) +#define RTM_GETTCLASS (RTM_BASE+26) + +#define RTM_NEWTFILTER (RTM_BASE+28) +#define RTM_DELTFILTER (RTM_BASE+29) +#define RTM_GETTFILTER (RTM_BASE+30) + +#define RTM_MAX (RTM_BASE+31) + +/* + Generic structure for encapsulation optional route information. + It is reminiscent of sockaddr, but with sa_family replaced + with attribute type. + */ + +struct rtattr +{ + unsigned short rta_len; + unsigned short rta_type; +}; + +/* Macros to handle rtattributes */ + +#define RTA_ALIGNTO 4 +#define RTA_ALIGN(len) ( ((len)+RTA_ALIGNTO-1) & ~(RTA_ALIGNTO-1) ) +#define RTA_OK(rta,len) ((len) > 0 && (rta)->rta_len >= sizeof(struct rtattr) && \ + (rta)->rta_len <= (len)) +#define RTA_NEXT(rta,attrlen) ((attrlen) -= RTA_ALIGN((rta)->rta_len), \ + (struct rtattr*)(((char*)(rta)) + RTA_ALIGN((rta)->rta_len))) +#define RTA_LENGTH(len) (RTA_ALIGN(sizeof(struct rtattr)) + (len)) +#define RTA_SPACE(len) RTA_ALIGN(RTA_LENGTH(len)) +#define RTA_DATA(rta) ((void*)(((char*)(rta)) + RTA_LENGTH(0))) +#define RTA_PAYLOAD(rta) ((int)((rta)->rta_len) - RTA_LENGTH(0)) + + + + +/****************************************************************************** + * Definitions used in routing table administation. + ****/ + +struct rtmsg +{ + unsigned char rtm_family; + unsigned char rtm_dst_len; + unsigned char rtm_src_len; + unsigned char rtm_tos; + + unsigned char rtm_table; /* Routing table id */ + unsigned char rtm_protocol; /* Routing protocol; see below */ + unsigned char rtm_scope; /* See below */ + unsigned char rtm_type; /* See below */ + + unsigned rtm_flags; +}; + +/* rtm_type */ + +enum +{ + RTN_UNSPEC, + RTN_UNICAST, /* Gateway or direct route */ + RTN_LOCAL, /* Accept locally */ + RTN_BROADCAST, /* Accept locally as broadcast, + send as broadcast */ + RTN_ANYCAST, /* Accept locally as broadcast, + but send as unicast */ + RTN_MULTICAST, /* Multicast route */ + RTN_BLACKHOLE, /* Drop */ + RTN_UNREACHABLE, /* Destination is unreachable */ + RTN_PROHIBIT, /* Administratively prohibited */ + RTN_THROW, /* Not in this table */ + RTN_NAT, /* Translate this address */ + RTN_XRESOLVE, /* Use external resolver */ +}; + +#define RTN_MAX RTN_XRESOLVE + + +/* rtm_protocol */ + +#define RTPROT_UNSPEC 0 +#define RTPROT_REDIRECT 1 /* Route installed by ICMP redirects; + not used by current IPv4 */ +#define RTPROT_KERNEL 2 /* Route installed by kernel */ +#define RTPROT_BOOT 3 /* Route installed during boot */ +#define RTPROT_STATIC 4 /* Route installed by administrator */ + +/* Values of protocol >= RTPROT_STATIC are not interpreted by kernel; + they just passed from user and back as is. + It will be used by hypothetical multiple routing daemons. + Note that protocol values should be standardized in order to + avoid conflicts. + */ + +#define RTPROT_GATED 8 /* Apparently, GateD */ +#define RTPROT_RA 9 /* RDISC/ND router advertisments */ +#define RTPROT_MRT 10 /* Merit MRT */ +#define RTPROT_ZEBRA 11 /* Zebra */ +#define RTPROT_BIRD 12 /* BIRD */ +#define RTPROT_DNROUTED 13 /* DECnet routing daemon */ + +/* rtm_scope + + Really it is not scope, but sort of distance to the destination. + NOWHERE are reserved for not existing destinations, HOST is our + local addresses, LINK are destinations, located on directly attached + link and UNIVERSE is everywhere in the Universe. + + Intermediate values are also possible f.e. interior routes + could be assigned a value between UNIVERSE and LINK. +*/ + +enum rt_scope_t +{ + RT_SCOPE_UNIVERSE=0, +/* User defined values */ + RT_SCOPE_SITE=200, + RT_SCOPE_LINK=253, + RT_SCOPE_HOST=254, + RT_SCOPE_NOWHERE=255 +}; + +/* rtm_flags */ + +#define RTM_F_NOTIFY 0x100 /* Notify user of route change */ +#define RTM_F_CLONED 0x200 /* This route is cloned */ +#define RTM_F_EQUALIZE 0x400 /* Multipath equalizer: NI */ + +/* Reserved table identifiers */ + +enum rt_class_t +{ + RT_TABLE_UNSPEC=0, +/* User defined values */ + RT_TABLE_DEFAULT=253, + RT_TABLE_MAIN=254, + RT_TABLE_LOCAL=255 +}; +#define RT_TABLE_MAX RT_TABLE_LOCAL + + + +/* Routing message attributes */ + +enum rtattr_type_t +{ + RTA_UNSPEC, + RTA_DST, + RTA_SRC, + RTA_IIF, + RTA_OIF, + RTA_GATEWAY, + RTA_PRIORITY, + RTA_PREFSRC, + RTA_METRICS, + RTA_MULTIPATH, + RTA_PROTOINFO, + RTA_FLOW, + RTA_CACHEINFO, + RTA_SESSION, +}; + +#define RTA_MAX RTA_SESSION + +#define RTM_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg)))) +#define RTM_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct rtmsg)) + +/* RTM_MULTIPATH --- array of struct rtnexthop. + * + * "struct rtnexthop" describres all necessary nexthop information, + * i.e. parameters of path to a destination via this nextop. + * + * At the moment it is impossible to set different prefsrc, mtu, window + * and rtt for different paths from multipath. + */ + +struct rtnexthop +{ + unsigned short rtnh_len; + unsigned char rtnh_flags; + unsigned char rtnh_hops; + int rtnh_ifindex; +}; + +/* rtnh_flags */ + +#define RTNH_F_DEAD 1 /* Nexthop is dead (used by multipath) */ +#define RTNH_F_PERVASIVE 2 /* Do recursive gateway lookup */ +#define RTNH_F_ONLINK 4 /* Gateway is forced on link */ + +/* Macros to handle hexthops */ + +#define RTNH_ALIGNTO 4 +#define RTNH_ALIGN(len) ( ((len)+RTNH_ALIGNTO-1) & ~(RTNH_ALIGNTO-1) ) +#define RTNH_OK(rtnh,len) ((rtnh)->rtnh_len >= sizeof(struct rtnexthop) && \ + ((int)(rtnh)->rtnh_len) <= (len)) +#define RTNH_NEXT(rtnh) ((struct rtnexthop*)(((char*)(rtnh)) + RTNH_ALIGN((rtnh)->rtnh_len))) +#define RTNH_LENGTH(len) (RTNH_ALIGN(sizeof(struct rtnexthop)) + (len)) +#define RTNH_SPACE(len) RTNH_ALIGN(RTNH_LENGTH(len)) +#define RTNH_DATA(rtnh) ((struct rtattr*)(((char*)(rtnh)) + RTNH_LENGTH(0))) + +/* RTM_CACHEINFO */ + +struct rta_cacheinfo +{ + uint32_t rta_clntref; + uint32_t rta_lastuse; + int32_t rta_expires; + uint32_t rta_error; + uint32_t rta_used; + +#define RTNETLINK_HAVE_PEERINFO 1 + uint32_t rta_id; + uint32_t rta_ts; + uint32_t rta_tsage; +}; + +/* RTM_METRICS --- array of struct rtattr with types of RTAX_* */ + +enum +{ + RTAX_UNSPEC, +#define RTAX_UNSPEC RTAX_UNSPEC + RTAX_LOCK, +#define RTAX_LOCK RTAX_LOCK + RTAX_MTU, +#define RTAX_MTU RTAX_MTU + RTAX_WINDOW, +#define RTAX_WINDOW RTAX_WINDOW + RTAX_RTT, +#define RTAX_RTT RTAX_RTT + RTAX_RTTVAR, +#define RTAX_RTTVAR RTAX_RTTVAR + RTAX_SSTHRESH, +#define RTAX_SSTHRESH RTAX_SSTHRESH + RTAX_CWND, +#define RTAX_CWND RTAX_CWND + RTAX_ADVMSS, +#define RTAX_ADVMSS RTAX_ADVMSS + RTAX_REORDERING, +#define RTAX_REORDERING RTAX_REORDERING +}; + +#define RTAX_MAX RTAX_REORDERING + +struct rta_session +{ + uint8_t proto; + + union { + struct { + uint16_t sport; + uint16_t dport; + } ports; + + struct { + uint8_t type; + uint8_t code; + uint16_t ident; + } icmpt; + + uint32_t spi; + } u; +}; + + +/********************************************************* + * Interface address. + ****/ + +struct ifaddrmsg +{ + unsigned char ifa_family; + unsigned char ifa_prefixlen; /* The prefix length */ + unsigned char ifa_flags; /* Flags */ + unsigned char ifa_scope; /* See above */ + int ifa_index; /* Link index */ +}; + +enum +{ + IFA_UNSPEC, + IFA_ADDRESS, + IFA_LOCAL, + IFA_LABEL, + IFA_BROADCAST, + IFA_ANYCAST, + IFA_CACHEINFO +}; + +#define IFA_MAX IFA_CACHEINFO + +/* ifa_flags */ + +#define IFA_F_SECONDARY 0x01 +#define IFA_F_TEMPORARY IFA_F_SECONDARY + +#define IFA_F_DEPRECATED 0x20 +#define IFA_F_TENTATIVE 0x40 +#define IFA_F_PERMANENT 0x80 + +struct ifa_cacheinfo +{ + int32_t ifa_prefered; + int32_t ifa_valid; +}; + + +#define IFA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg)))) +#define IFA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifaddrmsg)) + +/* + Important comment: + IFA_ADDRESS is prefix address, rather than local interface address. + It makes no difference for normally configured broadcast interfaces, + but for point-to-point IFA_ADDRESS is DESTINATION address, + local address is supplied in IFA_LOCAL attribute. + */ + +/************************************************************** + * Neighbour discovery. + ****/ + +struct ndmsg +{ + unsigned char ndm_family; + unsigned char ndm_pad1; + unsigned short ndm_pad2; + int ndm_ifindex; /* Link index */ + uint16_t ndm_state; + uint8_t ndm_flags; + uint8_t ndm_type; +}; + +enum +{ + NDA_UNSPEC, + NDA_DST, + NDA_LLADDR, + NDA_CACHEINFO +}; + +#define NDA_MAX NDA_CACHEINFO + +#define NDA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg)))) +#define NDA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ndmsg)) + +/* + * Neighbor Cache Entry Flags + */ + +#define NTF_PROXY 0x08 /* == ATF_PUBL */ +#define NTF_ROUTER 0x80 + +/* + * Neighbor Cache Entry States. + */ + +#define NUD_INCOMPLETE 0x01 +#define NUD_REACHABLE 0x02 +#define NUD_STALE 0x04 +#define NUD_DELAY 0x08 +#define NUD_PROBE 0x10 +#define NUD_FAILED 0x20 + +/* Dummy states */ +#define NUD_NOARP 0x40 +#define NUD_PERMANENT 0x80 +#define NUD_NONE 0x00 + + +struct nda_cacheinfo +{ + uint32_t ndm_confirmed; + uint32_t ndm_used; + uint32_t ndm_updated; + uint32_t ndm_refcnt; +}; + +/**** + * General form of address family dependent message. + ****/ + +struct rtgenmsg +{ + unsigned char rtgen_family; +}; + +/***************************************************************** + * Link layer specific messages. + ****/ + +/* struct ifinfomsg + * passes link level specific information, not dependent + * on network protocol. + */ + +struct ifinfomsg +{ + unsigned char ifi_family; + unsigned char __ifi_pad; + unsigned short ifi_type; /* ARPHRD_* */ + int ifi_index; /* Link index */ + unsigned ifi_flags; /* IFF_* flags */ + unsigned ifi_change; /* IFF_* change mask */ +}; + +enum +{ + IFLA_UNSPEC, + IFLA_ADDRESS, + IFLA_BROADCAST, + IFLA_IFNAME, + IFLA_MTU, + IFLA_LINK, + IFLA_QDISC, + IFLA_STATS, + IFLA_COST, +#define IFLA_COST IFLA_COST + IFLA_PRIORITY, +#define IFLA_PRIORITY IFLA_PRIORITY + IFLA_MASTER, +#define IFLA_MASTER IFLA_MASTER + IFLA_WIRELESS, /* Wireless Extension event - see wireless.h */ +#define IFLA_WIRELESS IFLA_WIRELESS +}; + + +#define IFLA_MAX IFLA_WIRELESS + +#define IFLA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg)))) +#define IFLA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifinfomsg)) + +/* ifi_flags. + + IFF_* flags. + + The only change is: + IFF_LOOPBACK, IFF_BROADCAST and IFF_POINTOPOINT are + more not changeable by user. They describe link media + characteristics and set by device driver. + + Comments: + - Combination IFF_BROADCAST|IFF_POINTOPOINT is invalid + - If neiher of these three flags are set; + the interface is NBMA. + + - IFF_MULTICAST does not mean anything special: + multicasts can be used on all not-NBMA links. + IFF_MULTICAST means that this media uses special encapsulation + for multicast frames. Apparently, all IFF_POINTOPOINT and + IFF_BROADCAST devices are able to use multicasts too. + */ + +/* IFLA_LINK. + For usual devices it is equal ifi_index. + If it is a "virtual interface" (f.e. tunnel), ifi_link + can point to real physical interface (f.e. for bandwidth calculations), + or maybe 0, what means, that real media is unknown (usual + for IPIP tunnels, when route to endpoint is allowed to change) + */ + +/***************************************************************** + * Traffic control messages. + ****/ + +struct tcmsg +{ + unsigned char tcm_family; + unsigned char tcm__pad1; + unsigned short tcm__pad2; + int tcm_ifindex; + uint32_t tcm_handle; + uint32_t tcm_parent; + uint32_t tcm_info; +}; + +enum +{ + TCA_UNSPEC, + TCA_KIND, + TCA_OPTIONS, + TCA_STATS, + TCA_XSTATS, + TCA_RATE, +}; + +#define TCA_MAX TCA_RATE + +#define TCA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct tcmsg)))) +#define TCA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct tcmsg)) + + +/* SUMMARY: maximal rtattr understood by kernel */ + +#define RTATTR_MAX RTA_MAX + +/* RTnetlink multicast groups */ + +#define RTMGRP_LINK 1 +#define RTMGRP_NOTIFY 2 +#define RTMGRP_NEIGH 4 +#define RTMGRP_TC 8 + +#define RTMGRP_IPV4_IFADDR 0x10 +#define RTMGRP_IPV4_MROUTE 0x20 +#define RTMGRP_IPV4_ROUTE 0x40 + +#define RTMGRP_IPV6_IFADDR 0x100 +#define RTMGRP_IPV6_MROUTE 0x200 +#define RTMGRP_IPV6_ROUTE 0x400 + +#define RTMGRP_DECnet_IFADDR 0x1000 +#define RTMGRP_DECnet_ROUTE 0x4000 + +/* End of information exported to user level */ + +#endif /* __LINUX_RTNETLINK_H */ diff --git a/src/pluto/linux26/xfrm.h b/src/pluto/linux26/xfrm.h new file mode 100644 index 000000000..4269ae29b --- /dev/null +++ b/src/pluto/linux26/xfrm.h @@ -0,0 +1,233 @@ +#ifndef _LINUX_XFRM_H +#define _LINUX_XFRM_H + +#include <stdint.h> + +/* All of the structures in this file may not change size as they are + * passed into the kernel from userspace via netlink sockets. + */ + +/* Structure to encapsulate addresses. I do not want to use + * "standard" structure. My apologies. + */ +typedef union +{ + uint32_t a4; + uint32_t a6[4]; +} xfrm_address_t; + +/* Ident of a specific xfrm_state. It is used on input to lookup + * the state by (spi,daddr,ah/esp) or to store information about + * spi, protocol and tunnel address on output. + */ +struct xfrm_id +{ + xfrm_address_t daddr; + uint32_t spi; + uint8_t proto; +}; + +/* Selector, used as selector both on policy rules (SPD) and SAs. */ + +struct xfrm_selector +{ + xfrm_address_t daddr; + xfrm_address_t saddr; + uint16_t dport; + uint16_t dport_mask; + uint16_t sport; + uint16_t sport_mask; + uint16_t family; + uint8_t prefixlen_d; + uint8_t prefixlen_s; + uint8_t proto; + int ifindex; + uid_t user; +}; + +#define XFRM_INF (~(uint64_t)0) + +struct xfrm_lifetime_cfg +{ + uint64_t soft_byte_limit; + uint64_t hard_byte_limit; + uint64_t soft_packet_limit; + uint64_t hard_packet_limit; + uint64_t soft_add_expires_seconds; + uint64_t hard_add_expires_seconds; + uint64_t soft_use_expires_seconds; + uint64_t hard_use_expires_seconds; +}; + +struct xfrm_lifetime_cur +{ + uint64_t bytes; + uint64_t packets; + uint64_t add_time; + uint64_t use_time; +}; + +struct xfrm_replay_state +{ + uint32_t oseq; + uint32_t seq; + uint32_t bitmap; +}; + +struct xfrm_algo { + char alg_name[64]; + int alg_key_len; /* in bits */ + char alg_key[0]; +}; + +struct xfrm_stats { + uint32_t replay_window; + uint32_t replay; + uint32_t integrity_failed; +}; + +enum +{ + XFRM_POLICY_IN = 0, + XFRM_POLICY_OUT = 1, + XFRM_POLICY_FWD = 2, + XFRM_POLICY_MAX = 3 +}; + +enum +{ + XFRM_SHARE_ANY, /* No limitations */ + XFRM_SHARE_SESSION, /* For this session only */ + XFRM_SHARE_USER, /* For this user only */ + XFRM_SHARE_UNIQUE /* Use once */ +}; + +/* Netlink configuration messages. */ +#define XFRM_MSG_BASE 0x10 + +#define XFRM_MSG_NEWSA (XFRM_MSG_BASE + 0) +#define XFRM_MSG_DELSA (XFRM_MSG_BASE + 1) +#define XFRM_MSG_GETSA (XFRM_MSG_BASE + 2) + +#define XFRM_MSG_NEWPOLICY (XFRM_MSG_BASE + 3) +#define XFRM_MSG_DELPOLICY (XFRM_MSG_BASE + 4) +#define XFRM_MSG_GETPOLICY (XFRM_MSG_BASE + 5) + +#define XFRM_MSG_ALLOCSPI (XFRM_MSG_BASE + 6) +#define XFRM_MSG_ACQUIRE (XFRM_MSG_BASE + 7) +#define XFRM_MSG_EXPIRE (XFRM_MSG_BASE + 8) + +#define XFRM_MSG_UPDPOLICY (XFRM_MSG_BASE + 9) +#define XFRM_MSG_UPDSA (XFRM_MSG_BASE + 10) + +#define XFRM_MSG_POLEXPIRE (XFRM_MSG_BASE + 11) + +#define XFRM_MSG_MAX (XFRM_MSG_POLEXPIRE+1) + +struct xfrm_user_tmpl { + struct xfrm_id id; + uint16_t family; + xfrm_address_t saddr; + uint32_t reqid; + uint8_t mode; + uint8_t share; + uint8_t optional; + uint32_t aalgos; + uint32_t ealgos; + uint32_t calgos; +}; + +struct xfrm_encap_tmpl { + uint16_t encap_type; + uint16_t encap_sport; + uint16_t encap_dport; + xfrm_address_t encap_oa; +}; + +/* Netlink message attributes. */ +enum xfrm_attr_type_t { + XFRMA_UNSPEC, + XFRMA_ALG_AUTH, /* struct xfrm_algo */ + XFRMA_ALG_CRYPT, /* struct xfrm_algo */ + XFRMA_ALG_COMP, /* struct xfrm_algo */ + XFRMA_ENCAP, /* struct xfrm_algo + struct xfrm_encap_tmpl */ + XFRMA_TMPL, /* 1 or more struct xfrm_user_tmpl */ + +#define XFRMA_MAX XFRMA_TMPL +}; + +struct xfrm_usersa_info { + struct xfrm_selector sel; + struct xfrm_id id; + xfrm_address_t saddr; + struct xfrm_lifetime_cfg lft; + struct xfrm_lifetime_cur curlft; + struct xfrm_stats stats; + uint32_t seq; + uint32_t reqid; + uint16_t family; + uint8_t mode; /* 0=transport,1=tunnel */ + uint8_t replay_window; + uint8_t flags; +#define XFRM_STATE_NOECN 1 +}; + +struct xfrm_usersa_id { + xfrm_address_t daddr; + uint32_t spi; + uint16_t family; + uint8_t proto; +}; + +struct xfrm_userspi_info { + struct xfrm_usersa_info info; + uint32_t min; + uint32_t max; +}; + +struct xfrm_userpolicy_info { + struct xfrm_selector sel; + struct xfrm_lifetime_cfg lft; + struct xfrm_lifetime_cur curlft; + uint32_t priority; + uint32_t index; + uint8_t dir; + uint8_t action; +#define XFRM_POLICY_ALLOW 0 +#define XFRM_POLICY_BLOCK 1 + uint8_t flags; +#define XFRM_POLICY_LOCALOK 1 /* Allow user to override global policy */ + uint8_t share; +}; + +struct xfrm_userpolicy_id { + struct xfrm_selector sel; + uint32_t index; + uint8_t dir; +}; + +struct xfrm_user_acquire { + struct xfrm_id id; + xfrm_address_t saddr; + struct xfrm_selector sel; + struct xfrm_userpolicy_info policy; + uint32_t aalgos; + uint32_t ealgos; + uint32_t calgos; + uint32_t seq; +}; + +struct xfrm_user_expire { + struct xfrm_usersa_info state; + uint8_t hard; +}; + +struct xfrm_user_polexpire { + struct xfrm_userpolicy_info pol; + uint8_t hard; +}; + +#define XFRMGRP_ACQUIRE 1 +#define XFRMGRP_EXPIRE 2 + +#endif /* _LINUX_XFRM_H */ diff --git a/src/pluto/log.c b/src/pluto/log.c new file mode 100644 index 000000000..36997122c --- /dev/null +++ b/src/pluto/log.c @@ -0,0 +1,841 @@ +/* error logging functions + * Copyright (C) 1997 Angelos D. Keromytis. + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: log.c,v 1.8 2006/04/29 18:16:02 as Exp $ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <stdarg.h> +#include <syslog.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> /* used only if MSG_NOSIGNAL not defined */ +#include <sys/queue.h> +#include <libgen.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "server.h" +#include "state.h" +#include "connections.h" +#include "kernel.h" +#include "whack.h" /* needs connections.h */ +#include "timer.h" + +/* close one per-peer log */ +static void perpeer_logclose(struct connection *c); /* forward */ + + +bool + log_to_stderr = TRUE, /* should log go to stderr? */ + log_to_syslog = TRUE, /* should log go to syslog? */ + log_to_perpeer= FALSE; /* should log go to per-IP file? */ + +bool + logged_txt_warning = FALSE; /* should we complain about finding KEY? */ + +/* should we complain when we find no local id */ +bool + logged_myid_fqdn_txt_warning = FALSE, + logged_myid_ip_txt_warning = FALSE, + logged_myid_fqdn_key_warning = FALSE, + logged_myid_ip_key_warning = FALSE; + +/* may include trailing / */ +const char *base_perpeer_logdir = PERPEERLOGDIR; +static int perpeer_count = 0; + +/* from sys/queue.h */ +static CIRCLEQ_HEAD(,connection) perpeer_list; + + +/* Context for logging. + * + * Global variables: must be carefully adjusted at transaction boundaries! + * If the context provides a whack file descriptor, messages + * should be copied to it -- see whack_log() + */ +int whack_log_fd = NULL_FD; /* only set during whack_handle() */ +struct state *cur_state = NULL; /* current state, for diagnostics */ +struct connection *cur_connection = NULL; /* current connection, for diagnostics */ +const ip_address *cur_from = NULL; /* source of current current message */ +u_int16_t cur_from_port; /* host order */ + +void +init_log(const char *program) +{ + if (log_to_stderr) + setbuf(stderr, NULL); + if (log_to_syslog) + openlog(program, LOG_CONS | LOG_NDELAY | LOG_PID, LOG_AUTHPRIV); + + CIRCLEQ_INIT(&perpeer_list); +} + +void +close_peerlog(void) +{ + /* end of circular queue is given by pointer to "HEAD" + * BUT if the queue is not initialized, this won't be true + * so we must guard by test perpeer_list.cqh_first != NULL + */ + if (perpeer_list.cqh_first != NULL) + while (perpeer_list.cqh_first != (void *)&perpeer_list) + perpeer_logclose(perpeer_list.cqh_first); +} + +void +close_log(void) +{ + if (log_to_syslog) + closelog(); + + close_peerlog(); +} + +/* Sanitize character string in situ: turns dangerous characters into \OOO. + * With a bit of work, we could use simpler reps for \\, \r, etc., + * but this is only to protect against something that shouldn't be used. + * Truncate resulting string to what fits in buffer. + */ +static size_t +sanitize(char *buf, size_t size) +{ +# define UGLY_WIDTH 4 /* width for ugly character: \OOO */ + size_t len; + size_t added = 0; + char *p; + + passert(size >= UGLY_WIDTH); /* need room to swing cat */ + + /* find right side of string to be sanitized and count + * number of columns to be added. Stop on end of string + * or lack of room for more result. + */ + for (p = buf; *p != '\0' && &p[added] < &buf[size - UGLY_WIDTH]; ) + { + unsigned char c = *p++; + + if (c == '\\' || !isprint(c)) + added += UGLY_WIDTH - 1; + } + + /* at this point, p points after last original character to be + * included. added is how many characters are added to sanitize. + * so p[added] will point after last sanitized character. + */ + + p[added] = '\0'; + len = &p[added] - buf; + + /* scan backwards, copying characters to their new home + * and inserting the expansions for ugly characters. + * It is finished when no more shifting is required. + * This is a predecrement loop. + */ + while (added != 0) + { + char fmtd[UGLY_WIDTH + 1]; + unsigned char c; + + while ((c = *--p) != '\\' && isprint(c)) + p[added] = c; + added -= UGLY_WIDTH - 1; + snprintf(fmtd, sizeof(fmtd), "\\%03o", c); + memcpy(p + added, fmtd, UGLY_WIDTH); + } + return len; +# undef UGLY_WIDTH +} + +/* format a string for the log, with suitable prefixes. + * A format starting with ~ indicates that this is a reprocessing + * of the message, so prefixing and quoting is suppressed. + */ +static void +fmt_log(char *buf, size_t buf_len, const char *fmt, va_list ap) +{ + bool reproc = *fmt == '~'; + size_t ps; + struct connection *c = cur_state != NULL ? cur_state->st_connection + : cur_connection; + + buf[0] = '\0'; + if (reproc) + fmt++; /* ~ at start of format suppresses this prefix */ + else if (c != NULL) + { + /* start with name of connection */ + char *const be = buf + buf_len; + char *bp = buf; + + snprintf(bp, be - bp, "\"%s\"", c->name); + bp += strlen(bp); + + /* if it fits, put in any connection instance information */ + if (be - bp > CONN_INST_BUF) + { + fmt_conn_instance(c, bp); + bp += strlen(bp); + } + + if (cur_state != NULL) + { + /* state number */ + snprintf(bp, be - bp, " #%lu", cur_state->st_serialno); + bp += strlen(bp); + } + snprintf(bp, be - bp, ": "); + } + else if (cur_from != NULL) + { + /* peer's IP address */ + /* Note: must not use ip_str() because our caller might! */ + char ab[ADDRTOT_BUF]; + + (void) addrtot(cur_from, 0, ab, sizeof(ab)); + snprintf(buf, buf_len, "packet from %s:%u: " + , ab, (unsigned)cur_from_port); + } + + ps = strlen(buf); + vsnprintf(buf + ps, buf_len - ps, fmt, ap); + if (!reproc) + (void)sanitize(buf, buf_len); +} + +static void +perpeer_logclose(struct connection *c) +{ + /* only free/close things if we had used them! */ + if (c->log_file != NULL) + { + passert(perpeer_count > 0); + + CIRCLEQ_REMOVE(&perpeer_list, c, log_link); + perpeer_count--; + fclose(c->log_file); + c->log_file=NULL; + } +} + +void +perpeer_logfree(struct connection *c) +{ + perpeer_logclose(c); + if (c->log_file_name != NULL) + { + pfree(c->log_file_name); + c->log_file_name = NULL; + c->log_file_err = FALSE; + } +} + +/* open the per-peer log */ +static void +open_peerlog(struct connection *c) +{ + syslog(LOG_INFO, "opening log file for conn %s", c->name); + + if (c->log_file_name == NULL) + { + char peername[ADDRTOT_BUF], dname[ADDRTOT_BUF]; + int peernamelen, lf_len; + + addrtot(&c->spd.that.host_addr, 'Q', peername, sizeof(peername)); + peernamelen = strlen(peername); + + /* copy IP address, turning : and . into / */ + { + char c, *p, *q; + + p = peername; + q = dname; + do { + c = *p++; + if (c == '.' || c == ':') + c = '/'; + *q++ = c; + } while (c != '\0'); + } + + lf_len = peernamelen * 2 + + strlen(base_perpeer_logdir) + + sizeof("//.log") + + 1; + c->log_file_name = alloc_bytes(lf_len, "per-peer log file name"); + + fprintf(stderr, "base dir |%s| dname |%s| peername |%s|" + , base_perpeer_logdir, dname, peername); + snprintf(c->log_file_name, lf_len, "%s/%s/%s.log" + , base_perpeer_logdir, dname, peername); + + syslog(LOG_DEBUG, "conn %s logfile is %s", c->name, c->log_file_name); + } + + /* now open the file, creating directories if necessary */ + + { /* create the directory */ + char *dname; + int bpl_len = strlen(base_perpeer_logdir); + char *slashloc; + + dname = clone_str(c->log_file_name, "temp copy of file name"); + dname = dirname(dname); + + if (access(dname, W_OK) != 0) + { + if (errno != ENOENT) + { + if (c->log_file_err) + { + syslog(LOG_CRIT, "can not write to %s: %s" + , dname, strerror(errno)); + c->log_file_err = TRUE; + pfree(dname); + return; + } + } + + /* directory does not exist, walk path creating dirs */ + /* start at base_perpeer_logdir */ + slashloc = dname + bpl_len; + slashloc++; /* since, by construction there is a slash + right there */ + + while (*slashloc != '\0') + { + char saveslash; + + /* look for next slash */ + while (*slashloc != '\0' && *slashloc != '/') slashloc++; + + saveslash = *slashloc; + + *slashloc = '\0'; + + if (mkdir(dname, 0750) != 0 && errno != EEXIST) + { + syslog(LOG_CRIT, "can not create dir %s: %s" + , dname, strerror(errno)); + c->log_file_err = TRUE; + pfree(dname); + return; + } + syslog(LOG_DEBUG, "created new directory %s", dname); + *slashloc = saveslash; + slashloc++; + } + } + + pfree(dname); + } + + c->log_file = fopen(c->log_file_name, "a"); + if (c->log_file == NULL) + { + if (c->log_file_err) + { + syslog(LOG_CRIT, "logging system can not open %s: %s" + , c->log_file_name, strerror(errno)); + c->log_file_err = TRUE; + } + return; + } + + /* look for a connection to close! */ + while (perpeer_count >= MAX_PEERLOG_COUNT) + { + /* can not be NULL because perpeer_count > 0 */ + passert(perpeer_list.cqh_last != (void *)&perpeer_list); + + perpeer_logclose(perpeer_list.cqh_last); + } + + /* insert this into the list */ + CIRCLEQ_INSERT_HEAD(&perpeer_list, c, log_link); + passert(c->log_file != NULL); + perpeer_count++; +} + +/* log a line to cur_connection's log */ +static void +peerlog(const char *prefix, const char *m) +{ + if (cur_connection == NULL) + { + /* we can not log it in this case. Oh well. */ + return; + } + + if (cur_connection->log_file == NULL) + { + open_peerlog(cur_connection); + } + + /* despite our attempts above, we may not be able to open the file. */ + if (cur_connection->log_file != NULL) + { + char datebuf[32]; + time_t n; + struct tm *t; + + time(&n); + t = localtime(&n); + + strftime(datebuf, sizeof(datebuf), "%Y-%m-%d %T", t); + fprintf(cur_connection->log_file, "%s %s%s\n", datebuf, prefix, m); + + /* now move it to the front of the list */ + CIRCLEQ_REMOVE(&perpeer_list, cur_connection, log_link); + CIRCLEQ_INSERT_HEAD(&perpeer_list, cur_connection, log_link); + } +} + +void +plog(const char *message, ...) +{ + va_list args; + char m[LOG_WIDTH]; /* longer messages will be truncated */ + + va_start(args, message); + fmt_log(m, sizeof(m), message, args); + va_end(args); + + if (log_to_stderr) + fprintf(stderr, "%s\n", m); + if (log_to_syslog) + syslog(LOG_WARNING, "%s", m); + if (log_to_perpeer) + peerlog("", m); + + whack_log(RC_LOG, "~%s", m); +} + +void +loglog(int mess_no, const char *message, ...) +{ + va_list args; + char m[LOG_WIDTH]; /* longer messages will be truncated */ + + va_start(args, message); + fmt_log(m, sizeof(m), message, args); + va_end(args); + + if (log_to_stderr) + fprintf(stderr, "%s\n", m); + if (log_to_syslog) + syslog(LOG_WARNING, "%s", m); + if (log_to_perpeer) + peerlog("", m); + + whack_log(mess_no, "~%s", m); +} + +void +log_errno_routine(int e, const char *message, ...) +{ + va_list args; + char m[LOG_WIDTH]; /* longer messages will be truncated */ + + va_start(args, message); + fmt_log(m, sizeof(m), message, args); + va_end(args); + + if (log_to_stderr) + fprintf(stderr, "ERROR: %s. Errno %d: %s\n", m, e, strerror(e)); + if (log_to_syslog) + syslog(LOG_ERR, "ERROR: %s. Errno %d: %s", m, e, strerror(e)); + if (log_to_perpeer) + { + peerlog(strerror(e), m); + } + + whack_log(RC_LOG_SERIOUS + , "~ERROR: %s. Errno %d: %s", m, e, strerror(e)); +} + +void +exit_log(const char *message, ...) +{ + va_list args; + char m[LOG_WIDTH]; /* longer messages will be truncated */ + + va_start(args, message); + fmt_log(m, sizeof(m), message, args); + va_end(args); + + if (log_to_stderr) + fprintf(stderr, "FATAL ERROR: %s\n", m); + if (log_to_syslog) + syslog(LOG_ERR, "FATAL ERROR: %s", m); + if (log_to_perpeer) + peerlog("FATAL ERROR: ", m); + + whack_log(RC_LOG_SERIOUS, "~FATAL ERROR: %s", m); + + exit_pluto(1); +} + +void +exit_log_errno_routine(int e, const char *message, ...) +{ + va_list args; + char m[LOG_WIDTH]; /* longer messages will be truncated */ + + va_start(args, message); + fmt_log(m, sizeof(m), message, args); + va_end(args); + + if (log_to_stderr) + fprintf(stderr, "FATAL ERROR: %s. Errno %d: %s\n", m, e, strerror(e)); + if (log_to_syslog) + syslog(LOG_ERR, "FATAL ERROR: %s. Errno %d: %s", m, e, strerror(e)); + if (log_to_perpeer) + peerlog(strerror(e), m); + + whack_log(RC_LOG_SERIOUS + , "~FATAL ERROR: %s. Errno %d: %s", m, e, strerror(e)); + + exit_pluto(1); +} + +/* emit message to whack. + * form is "ddd statename text" where + * - ddd is a decimal status code (RC_*) as described in whack.h + * - text is a human-readable annotation + */ +#ifdef DEBUG +static volatile sig_atomic_t dying_breath = FALSE; +#endif + +void +whack_log(int mess_no, const char *message, ...) +{ + int wfd = whack_log_fd != NULL_FD ? whack_log_fd + : cur_state != NULL ? cur_state->st_whack_sock + : NULL_FD; + + if (wfd != NULL_FD +#ifdef DEBUG + || dying_breath +#endif + ) + { + va_list args; + char m[LOG_WIDTH]; /* longer messages will be truncated */ + int prelen = snprintf(m, sizeof(m), "%03d ", mess_no); + + passert(prelen >= 0); + + va_start(args, message); + fmt_log(m+prelen, sizeof(m)-prelen, message, args); + va_end(args); + +#if DEBUG + if (dying_breath) + { + /* status output copied to log */ + if (log_to_stderr) + fprintf(stderr, "%s\n", m + prelen); + if (log_to_syslog) + syslog(LOG_WARNING, "%s", m + prelen); + if (log_to_perpeer) + peerlog("", m); + } +#endif + + if (wfd != NULL_FD) + { + /* write to whack socket, but suppress possible SIGPIPE */ + size_t len = strlen(m); +#ifdef MSG_NOSIGNAL /* depends on version of glibc??? */ + m[len] = '\n'; /* don't need NUL, do need NL */ + (void) send(wfd, m, len + 1, MSG_NOSIGNAL); +#else /* !MSG_NOSIGNAL */ + int r; + struct sigaction act + , oldact; + + m[len] = '\n'; /* don't need NUL, do need NL */ + act.sa_handler = SIG_IGN; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; /* no nothing */ + r = sigaction(SIGPIPE, &act, &oldact); + passert(r == 0); + + (void) write(wfd, m, len + 1); + + r = sigaction(SIGPIPE, &oldact, NULL); + passert(r == 0); +#endif /* !MSG_NOSIGNAL */ + } + } +} + +/* Build up a diagnostic in a static buffer. + * Although this would be a generally useful function, it is very + * hard to come up with a discipline that prevents different uses + * from interfering. It is intended that by limiting it to building + * diagnostics, we will avoid this problem. + * Juggling is performed to allow an argument to be a previous + * result: the new string may safely depend on the old one. This + * restriction is not checked in any way: violators will produce + * confusing results (without crashing!). + */ +char diag_space[sizeof(diag_space)]; + +err_t +builddiag(const char *fmt, ...) +{ + static char diag_space[LOG_WIDTH]; /* longer messages will be truncated */ + char t[sizeof(diag_space)]; /* build result here first */ + va_list args; + + va_start(args, fmt); + t[0] = '\0'; /* in case nothing terminates string */ + vsnprintf(t, sizeof(t), fmt, args); + va_end(args); + strcpy(diag_space, t); + return diag_space; +} + +/* Debugging message support */ + +#ifdef DEBUG + +void +switch_fail(int n, const char *file_str, unsigned long line_no) +{ + char buf[30]; + + snprintf(buf, sizeof(buf), "case %d unexpected", n); + passert_fail(buf, file_str, line_no); +} + +void +passert_fail(const char *pred_str, const char *file_str, unsigned long line_no) +{ + /* we will get a possibly unplanned prefix. Hope it works */ + loglog(RC_LOG_SERIOUS, "ASSERTION FAILED at %s:%lu: %s", file_str, line_no, pred_str); + if (!dying_breath) + { + dying_breath = TRUE; + show_status(TRUE, NULL); + } + abort(); /* exiting correctly doesn't always work */ +} + +void +pexpect_log(const char *pred_str, const char *file_str, unsigned long line_no) +{ + /* we will get a possibly unplanned prefix. Hope it works */ + loglog(RC_LOG_SERIOUS, "EXPECTATION FAILED at %s:%lu: %s", file_str, line_no, pred_str); +} + +lset_t + base_debugging = DBG_NONE, /* default to reporting nothing */ + cur_debugging = DBG_NONE; + +void +extra_debugging(const struct connection *c) +{ + if(c == NULL) + { + reset_debugging(); + return; + } + + if (c!= NULL && c->extra_debugging != 0) + { + plog("enabling for connection: %s" + , bitnamesof(debug_bit_names, c->extra_debugging & ~cur_debugging)); + cur_debugging |= c->extra_debugging; + } +} + +/* log a debugging message (prefixed by "| ") */ + +void +DBG_log(const char *message, ...) +{ + va_list args; + char m[LOG_WIDTH]; /* longer messages will be truncated */ + + va_start(args, message); + vsnprintf(m, sizeof(m), message, args); + va_end(args); + + (void)sanitize(m, sizeof(m)); + + if (log_to_stderr) + fprintf(stderr, "| %s\n", m); + if (log_to_syslog) + syslog(LOG_DEBUG, "| %s", m); + if (log_to_perpeer) + peerlog("| ", m); +} + +/* dump raw bytes in hex to stderr (for lack of any better destination) */ + +void +DBG_dump(const char *label, const void *p, size_t len) +{ +# define DUMP_LABEL_WIDTH 20 /* arbitrary modest boundary */ +# define DUMP_WIDTH (4 * (1 + 4 * 3) + 1) + char buf[DUMP_LABEL_WIDTH + DUMP_WIDTH]; + char *bp; + const unsigned char *cp = p; + + bp = buf; + + if (label != NULL && label[0] != '\0') + { + /* Handle the label. Care must be taken to avoid buffer overrun. */ + size_t llen = strlen(label); + + if (llen + 1 > sizeof(buf)) + { + DBG_log("%s", label); + } + else + { + strcpy(buf, label); + if (buf[llen-1] == '\n') + { + buf[llen-1] = '\0'; /* get rid of newline */ + DBG_log("%s", buf); + } + else if (llen < DUMP_LABEL_WIDTH) + { + bp = buf + llen; + } + else + { + DBG_log("%s", buf); + } + } + } + + do { + int i, j; + + for (i = 0; len!=0 && i!=4; i++) + { + *bp++ = ' '; + for (j = 0; len!=0 && j!=4; len--, j++) + { + static const char hexdig[] = "0123456789abcdef"; + + *bp++ = ' '; + *bp++ = hexdig[(*cp >> 4) & 0xF]; + *bp++ = hexdig[*cp & 0xF]; + cp++; + } + } + *bp = '\0'; + DBG_log("%s", buf); + bp = buf; + } while (len != 0); +# undef DUMP_LABEL_WIDTH +# undef DUMP_WIDTH +} + +#endif /* DEBUG */ + +void +show_status(bool all, const char *name) +{ + if (all) + { + show_ifaces_status(); + show_myid_status(); + show_debug_status(); + whack_log(RC_COMMENT, BLANK_FORMAT); /* spacer */ + } + show_connections_status(all, name); + show_states_status(all, name); +#ifdef KLIPS + show_shunt_status(); +#endif +} + +/* ip_str: a simple to use variant of addrtot. + * It stores its result in a static buffer. + * This means that newer calls overwrite the storage of older calls. + * Note: this is not used in any of the logging functions, so their + * callers may use it. + */ +const char * +ip_str(const ip_address *src) +{ + static char buf[ADDRTOT_BUF]; + + addrtot(src, 0, buf, sizeof(buf)); + return buf; +} + +/* + * a routine that attempts to schedule itself daily. + * + */ + +void +daily_log_reset(void) +{ + /* now perform actions */ + logged_txt_warning = FALSE; + + logged_myid_fqdn_txt_warning = FALSE; + logged_myid_ip_txt_warning = FALSE; + logged_myid_fqdn_key_warning = FALSE; + logged_myid_ip_key_warning = FALSE; +} + +void +daily_log_event(void) +{ + struct tm *ltime; + time_t n, interval; + + /* attempt to schedule oneself to midnight, local time + * do this by getting seconds in the day, and delaying + * by 86400 - hour*3600+minutes*60+seconds. + */ + time(&n); + ltime = localtime(&n); + interval = (24 * 60 * 60) + - (ltime->tm_sec + + ltime->tm_min * 60 + + ltime->tm_hour * 3600); + + event_schedule(EVENT_LOG_DAILY, interval, NULL); + + daily_log_reset(); +} + +/* + * Local Variables: + * c-basic-offset:4 + * c-style: pluto + * End: + */ diff --git a/src/pluto/log.h b/src/pluto/log.h new file mode 100644 index 000000000..a4eae9d1c --- /dev/null +++ b/src/pluto/log.h @@ -0,0 +1,236 @@ +/* logging definitions + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: log.h,v 1.4 2005/07/11 18:33:45 as Exp $ + */ + +#include <freeswan.h> + +#define LOG_WIDTH 1024 /* roof of number of chars in log line */ + +#ifndef PERPEERLOGDIR +#define PERPEERLOGDIR "/var/log/pluto/peer" +#endif + +/* our versions of assert: log result */ + +#ifdef DEBUG + +extern void passert_fail(const char *pred_str + , const char *file_str, unsigned long line_no) NEVER_RETURNS; + +extern void pexpect_log(const char *pred_str + , const char *file_str, unsigned long line_no); + +# define impossible() passert_fail("impossible", __FILE__, __LINE__) + +extern void switch_fail(int n + , const char *file_str, unsigned long line_no) NEVER_RETURNS; + +# define bad_case(n) switch_fail((int) n, __FILE__, __LINE__) + +# define passert(pred) { \ + if (!(pred)) \ + passert_fail(#pred, __FILE__, __LINE__); \ + } + +# define pexpect(pred) { \ + if (!(pred)) \ + pexpect_log(#pred, __FILE__, __LINE__); \ + } + +/* assert that an err_t is NULL; evaluate exactly once */ +# define happy(x) { \ + err_t ugh = x; \ + if (ugh != NULL) \ + passert_fail(ugh, __FILE__, __LINE__); \ + } + +#else /*!DEBUG*/ + +# define impossible() abort() +# define bad_case(n) abort() +# define passert(pred) { } /* do nothing */ +# define happy(x) { (void) x; } /* evaluate non-judgementally */ + +#endif /*!DEBUG*/ + + +extern bool + log_to_stderr, /* should log go to stderr? */ + log_to_syslog, /* should log go to syslog? */ + log_to_perpeer; /* should log go to per-IP file? */ + +extern const char *base_perpeer_logdir; + +/* maximum number of files to keep open for per-peer log files */ +#define MAX_PEERLOG_COUNT 16 + +/* Context for logging. + * + * Global variables: must be carefully adjusted at transaction boundaries! + * All are to be left in RESET condition and will be checked. + * There are several pairs of routines to set and reset them. + * If the context provides a whack file descriptor, messages + * should be copied to it -- see whack_log() + */ +extern int whack_log_fd; /* only set during whack_handle() */ +extern struct state *cur_state; /* current state, for diagnostics */ +extern struct connection *cur_connection; /* current connection, for diagnostics */ +extern const ip_address *cur_from; /* source of current current message */ +extern u_int16_t cur_from_port; /* host order */ + +#ifdef DEBUG + + extern lset_t cur_debugging; /* current debugging level */ + + extern void extra_debugging(const struct connection *c); + +# define reset_debugging() { cur_debugging = base_debugging; } + +# define GLOBALS_ARE_RESET() (whack_log_fd == NULL_FD \ + && cur_state == NULL \ + && cur_connection == NULL \ + && cur_from == NULL \ + && cur_debugging == base_debugging) + +#else /*!DEBUG*/ + +# define extra_debugging(c) { } + +# define reset_debugging() { } + +# define GLOBALS_ARE_RESET() (whack_log_fd == NULL_FD \ + && cur_state == NULL \ + && cur_connection == NULL \ + && cur_from == NULL) + +#endif /*!DEBUG*/ + +#define reset_globals() { \ + whack_log_fd = NULL_FD; \ + cur_state = NULL; \ + cur_from = NULL; \ + reset_cur_connection(); \ + } + + +#define set_cur_connection(c) { \ + cur_connection = (c); \ + extra_debugging(c); \ + } + +#define reset_cur_connection() { \ + cur_connection = NULL; \ + reset_debugging(); \ + } + + +#define set_cur_state(s) { \ + cur_state = (s); \ + extra_debugging((s)->st_connection); \ + } + +#define reset_cur_state() { \ + cur_state = NULL; \ + reset_debugging(); \ + } + +extern void init_log(const char *program); +extern void close_log(void); +extern void plog(const char *message, ...) PRINTF_LIKE(1); +extern void exit_log(const char *message, ...) PRINTF_LIKE(1) NEVER_RETURNS; + +/* close of all per-peer logging */ +extern void close_peerlog(void); + +/* free all per-peer log resources */ +extern void perpeer_logfree(struct connection *c); + + + +/* the following routines do a dance to capture errno before it is changed + * A call must doubly parenthesize the argument list (no varargs macros). + * The first argument must be "e", the local variable that captures errno. + */ +#define log_errno(a) { int e = errno; log_errno_routine a; } +extern void log_errno_routine(int e, const char *message, ...) PRINTF_LIKE(2); +#define exit_log_errno(a) { int e = errno; exit_log_errno_routine a; } +extern void exit_log_errno_routine(int e, const char *message, ...) PRINTF_LIKE(2) NEVER_RETURNS NEVER_RETURNS; + +extern void whack_log(int mess_no, const char *message, ...) PRINTF_LIKE(2); + +/* Log to both main log and whack log + * Much like log, actually, except for specifying mess_no. + */ +extern void loglog(int mess_no, const char *message, ...) PRINTF_LIKE(2); + +/* show status, usually on whack log */ +extern void show_status(bool all, const char *name); + +/* Build up a diagnostic in a static buffer. + * Although this would be a generally useful function, it is very + * hard to come up with a discipline that prevents different uses + * from interfering. It is intended that by limiting it to building + * diagnostics, we will avoid this problem. + * Juggling is performed to allow an argument to be a previous + * result: the new string may safely depend on the old one. This + * restriction is not checked in any way: violators will produce + * confusing results (without crashing!). + */ +extern char diag_space[LOG_WIDTH]; /* output buffer, but can be occupied at call */ +extern err_t builddiag(const char *fmt, ...) PRINTF_LIKE(1); + +#ifdef DEBUG + +extern lset_t base_debugging; /* bits selecting what to report */ + +#define DBGP(cond) (cur_debugging & (cond)) +#define DBG(cond, action) { if (DBGP(cond)) { action ; } } + +extern void DBG_log(const char *message, ...) PRINTF_LIKE(1); +extern void DBG_dump(const char *label, const void *p, size_t len); +#define DBG_dump_chunk(label, ch) DBG_dump(label, (ch).ptr, (ch).len) + +#else /*!DEBUG*/ + +#define DBG(cond, action) { } /* do nothing */ + +#endif /*!DEBUG*/ + +#define DBG_cond_dump(cond, label, p, len) DBG(cond, DBG_dump(label, p, len)) +#define DBG_cond_dump_chunk(cond, label, ch) DBG(cond, DBG_dump_chunk(label, ch)) + + +/* ip_str: a simple to use variant of addrtot. + * It stores its result in a static buffer. + * This means that newer calls overwrite the storage of older calls. + * Note: this is not used in any of the logging functions, so their + * callers may use it. + */ +extern const char *ip_str(const ip_address *src); + +/* + * call this routine to reset daily items. + */ +extern void daily_log_reset(void); +extern void daily_log_event(void); + +/* + * some events are to be logged only occasionally. + */ +extern bool logged_txt_warning; +extern bool logged_myid_ip_txt_warning; +extern bool logged_myid_ip_key_warning; +extern bool logged_myid_fqdn_txt_warning; +extern bool logged_myid_fqdn_key_warning; diff --git a/src/pluto/md2.c b/src/pluto/md2.c new file mode 100644 index 000000000..d6465477d --- /dev/null +++ b/src/pluto/md2.c @@ -0,0 +1,237 @@ +/* MD2C.C - RSA Data Security, Inc., MD2 message-digest algorithm + */ + +/* Copyright (C) 1990-2, RSA Data Security, Inc. Created 1990. All + rights reserved. + + License to copy and use this software is granted for + non-commercial Internet Privacy-Enhanced Mail provided that it is + identified as the "RSA Data Security, Inc. MD2 Message Digest + Algorithm" in all material mentioning or referencing this software + or this function. + + RSA Data Security, Inc. makes no representations concerning either + the merchantability of this software or the suitability of this + software for any particular purpose. It is provided "as is" + without express or implied warranty of any kind. + + These notices must be retained in any copies of any part of this + documentation and/or software. + */ + +#include "md2.h" + +#define HAVEMEMCOPY 1 /* use ISO C's memcpy and memset */ + +static void MD2Transform PROTO_LIST + ((unsigned char [16], unsigned char [16], const unsigned char [16])); + +#ifdef HAVEMEMCOPY +#include <memory.h> +#define MD2_memcpy memcpy +#define MD2_memset memset +#else +#ifdef HAVEBCOPY +#define MD2_memcpy(_a,_b,_c) memcpy((_a), (_b),(_c)) +#define MD2_memset(_a,_b,_c) memset((_a), '\0',(_c)) +#else +static void MD2_memcpy PROTO_LIST ((POINTER, CONST_POINTER, unsigned int)); +static void MD2_memset PROTO_LIST ((POINTER, int, unsigned int)); +#endif +#endif + +/* Permutation of 0..255 constructed from the digits of pi. It gives a + "random" nonlinear byte substitution operation. + */ +static unsigned char PI_SUBST[256] = { + 41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6, + 19, 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188, + 76, 130, 202, 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24, + 138, 23, 229, 18, 190, 78, 196, 214, 218, 158, 222, 73, 160, 251, + 245, 142, 187, 47, 238, 122, 169, 104, 121, 145, 21, 178, 7, 63, + 148, 194, 16, 137, 11, 34, 95, 33, 128, 127, 93, 154, 90, 144, 50, + 39, 53, 62, 204, 231, 191, 247, 151, 3, 255, 25, 48, 179, 72, 165, + 181, 209, 215, 94, 146, 42, 172, 86, 170, 198, 79, 184, 56, 210, + 150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241, 69, 157, + 112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2, 27, + 96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15, + 85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197, + 234, 38, 44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65, + 129, 77, 82, 106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123, + 8, 12, 189, 177, 74, 120, 136, 149, 139, 227, 99, 232, 109, 233, + 203, 213, 254, 59, 0, 29, 57, 242, 239, 183, 14, 102, 88, 208, 228, + 166, 119, 114, 248, 235, 117, 75, 10, 49, 68, 80, 180, 143, 237, + 31, 26, 219, 153, 141, 51, 159, 17, 131, 20 +}; + +static const unsigned char *PADDING[] = { + (const unsigned char *)"", + (const unsigned char *)"\001", + (const unsigned char *)"\002\002", + (const unsigned char *)"\003\003\003", + (const unsigned char *)"\004\004\004\004", + (const unsigned char *)"\005\005\005\005\005", + (const unsigned char *)"\006\006\006\006\006\006", + (const unsigned char *)"\007\007\007\007\007\007\007", + (const unsigned char *)"\010\010\010\010\010\010\010\010", + (const unsigned char *)"\011\011\011\011\011\011\011\011\011", + (const unsigned char *)"\012\012\012\012\012\012\012\012\012\012", + (const unsigned char *)"\013\013\013\013\013\013\013\013\013\013\013", + (const unsigned char *)"\014\014\014\014\014\014\014\014\014\014\014\014", + (const unsigned char *) + "\015\015\015\015\015\015\015\015\015\015\015\015\015", + (const unsigned char *) + "\016\016\016\016\016\016\016\016\016\016\016\016\016\016", + (const unsigned char *) + "\017\017\017\017\017\017\017\017\017\017\017\017\017\017\017", + (const unsigned char *) + "\020\020\020\020\020\020\020\020\020\020\020\020\020\020\020\020" +}; + +/* MD2 initialization. Begins an MD2 operation, writing a new context. + */ +void MD2Init (context) +MD2_CTX *context; /* context */ +{ + context->count = 0; + MD2_memset ((POINTER)context->state, 0, sizeof (context->state)); + MD2_memset + ((POINTER)context->checksum, 0, sizeof (context->checksum)); +} + +/* MD2 block update operation. Continues an MD2 message-digest + operation, processing another message block, and updating the + context. + */ +void MD2Update (context, input, inputLen) +MD2_CTX *context; /* context */ +const unsigned char *input; /* input block */ +unsigned int inputLen; /* length of input block */ +{ + unsigned int i, index, partLen; + + /* Update number of bytes mod 16 */ + index = context->count; + context->count = (index + inputLen) & 0xf; + + partLen = 16 - index; + + /* Transform as many times as possible. + */ + if (inputLen >= partLen) { + MD2_memcpy + ((POINTER)&context->buffer[index], (CONST_POINTER)input, partLen); + MD2Transform (context->state, context->checksum, context->buffer); + + for (i = partLen; i + 15 < inputLen; i += 16) + MD2Transform (context->state, context->checksum, &input[i]); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + MD2_memcpy + ((POINTER)&context->buffer[index], (CONST_POINTER)&input[i], + inputLen-i); +} + +/* MD2 finalization. Ends an MD2 message-digest operation, writing the + message digest and zeroizing the context. + */ +void MD2Final (digest, context) + +unsigned char digest[16]; /* message digest */ +MD2_CTX *context; /* context */ +{ + unsigned int index, padLen; + + /* Pad out to multiple of 16. + */ + index = context->count; + padLen = 16 - index; + MD2Update (context, PADDING[padLen], padLen); + + /* Extend with checksum */ + MD2Update (context, context->checksum, 16); + + /* Store state in digest */ + MD2_memcpy ((POINTER)digest, (POINTER)context->state, 16); + + /* Zeroize sensitive information. + */ + MD2_memset ((POINTER)context, 0, sizeof (*context)); +} + +/* MD2 basic transformation. Transforms state and updates checksum + based on block. + */ +static void MD2Transform (state, checksum, block) +unsigned char state[16]; +unsigned char checksum[16]; +const unsigned char block[16]; +{ + unsigned int i, j, t; + unsigned char x[48]; + + /* Form encryption block from state, block, state ^ block. + */ + MD2_memcpy ((POINTER)x, (CONST_POINTER)state, 16); + MD2_memcpy ((POINTER)x+16, (CONST_POINTER)block, 16); + for (i = 0; i < 16; i++) + x[i+32] = state[i] ^ block[i]; + + /* Encrypt block (18 rounds). + */ + t = 0; + for (i = 0; i < 18; i++) { + for (j = 0; j < 48; j++) + t = x[j] ^= PI_SUBST[t]; + t = (t + i) & 0xff; + } + + /* Save new state */ + MD2_memcpy ((POINTER)state, (POINTER)x, 16); + + /* Update checksum. + */ + t = checksum[15]; + for (i = 0; i < 16; i++) + t = checksum[i] ^= PI_SUBST[block[i] ^ t]; + + /* Zeroize sensitive information. + */ + MD2_memset ((POINTER)x, 0, sizeof (x)); +} + +#ifndef HAVEMEMCOPY +#ifndef HAVEBCOPY +/* Note: Replace "for loop" with standard memcpy if possible. + */ +static void MD2_memcpy (output, input, len) +POINTER output; +POINTER input; +unsigned int len; +{ + unsigned int i; + + for (i = 0; i < len; i++) + output[i] = input[i]; +} + +/* Note: Replace "for loop" with standard memset if possible. + */ +static void MD2_memset (output, value, len) +POINTER output; +int value; +unsigned int len; +{ + unsigned int i; + + for (i = 0; i < len; i++) + ((char *)output)[i] = (char)value; +} +#endif +#endif + diff --git a/src/pluto/md2.h b/src/pluto/md2.h new file mode 100644 index 000000000..b3b48dd92 --- /dev/null +++ b/src/pluto/md2.h @@ -0,0 +1,72 @@ +#ifndef _GLOBAL_H_ +#define _GLOBAL_H_ +/* GLOBAL.H - RSAREF types and constants + */ + +/* PROTOTYPES should be set to one if and only if the compiler supports + function argument prototyping. + The following makes PROTOTYPES default to 0 if it has not already + been defined with C compiler flags. + */ +#ifndef PROTOTYPES +#define PROTOTYPES 1 +#endif + +/* POINTER defines a generic pointer type */ +typedef unsigned char *POINTER; +typedef const unsigned char *CONST_POINTER; + +/* UINT2 defines a two byte word */ +typedef unsigned short int UINT2; + +/* UINT4 defines a four byte word */ +typedef unsigned long int UINT4; + +/* PROTO_LIST is defined depending on how PROTOTYPES is defined above. + If using PROTOTYPES, then PROTO_LIST returns the list, otherwise it + returns an empty list. + */ + +#if PROTOTYPES +#define PROTO_LIST(list) list +#else +#define PROTO_LIST(list) () +#endif + +#endif + +/* MD2.H - header file for MD2C.C + */ + +/* Copyright (C) 1990-2, RSA Data Security, Inc. Created 1990. All + rights reserved. + + License to copy and use this software is granted for + non-commercial Internet Privacy-Enhanced Mail provided that it is + identified as the "RSA Data Security, Inc. MD2 Message Digest + Algorithm" in all material mentioning or referencing this software + or this function. + + RSA Data Security, Inc. makes no representations concerning either + the merchantability of this software or the suitability of this + software for any particular purpose. It is provided "as is" + without express or implied warranty of any kind. + + These notices must be retained in any copies of any part of this + documentation and/or software. + */ + +/* MD2 context. */ +typedef struct { + unsigned char state[16]; /* state */ + unsigned char checksum[16]; /* checksum */ + unsigned int count; /* number of bytes, modulo 16 */ + unsigned char buffer[16]; /* input buffer */ +} MD2_CTX; + +void MD2Init PROTO_LIST ((MD2_CTX *)); +void MD2Update PROTO_LIST + ((MD2_CTX *, const unsigned char *, unsigned int)); +void MD2Final PROTO_LIST ((unsigned char [16], MD2_CTX *)); + +#define _MD2_H_ diff --git a/src/pluto/md5.c b/src/pluto/md5.c new file mode 100644 index 000000000..5d75e38a4 --- /dev/null +++ b/src/pluto/md5.c @@ -0,0 +1,385 @@ +/* + * The rest of the code is derived from MD5C.C by RSADSI. Minor cosmetic + * changes to accomodate it in the kernel by ji. + * Minor changes to make 64 bit clean by Peter Onion (i.e. using u_int*_t). + */ + +/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm + */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +/* + * Additions by JI + * + * HAVEMEMCOPY is defined if mem* routines are available + * + * HAVEHTON is defined if htons() and htonl() can be used + * for big/little endian conversions + * + */ + +#include <stddef.h> +#include <string.h> +#include <sys/types.h> /* for u_int*_t */ +#include <endian.h> /* sets BYTE_ORDER, LITTLE_ENDIAN, and BIG_ENDIAN */ + +#include "md5.h" + +#define HAVEMEMCOPY 1 /* use ISO C's memcpy and memset */ + +/* Constants for MD5Transform routine. + */ + +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +#define MD5Transform _MD5Transform + +static void MD5Transform PROTO_LIST ((UINT4 [4], const unsigned char [64])); + +#if BYTE_ORDER == LITTLE_ENDIAN +#define Encode MD5_memcpy +#define Decode MD5_memcpy +#else +static void Encode PROTO_LIST + ((unsigned char *, UINT4 *, unsigned int)); +static void Decode PROTO_LIST + ((UINT4 *, unsigned char *, unsigned int)); +#endif + +#ifdef HAVEMEMCOPY +#include <memory.h> +#define MD5_memcpy memcpy +#define MD5_memset memset +#else +#ifdef HAVEBCOPY +#define MD5_memcpy(_a,_b,_c) memcpy((_a), (_b),(_c)) +#define MD5_memset(_a,_b,_c) memset((_a), '\0',(_c)) +#else +static void MD5_memcpy PROTO_LIST ((POINTER, POINTER, unsigned int)); +static void MD5_memset PROTO_LIST ((POINTER, int, unsigned int)); +#endif +#endif +static unsigned char PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G, H and I are basic MD5 functions. + */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +/* ROTATE_LEFT rotates x left n bits. + */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +Rotation is separate from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s, ac) { \ + (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) { \ + (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) { \ + (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) { \ + (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +/* MD5 initialization. Begins an MD5 operation, writing a new context. + */ +void MD5Init (context) +MD5_CTX *context; /* context */ +{ + context->count[0] = context->count[1] = 0; + /* Load magic initialization constants. +*/ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* MD5 block update operation. Continues an MD5 message-digest + operation, processing another message block, and updating the + context. + */ +void MD5Update (context, input, inputLen) +MD5_CTX *context; /* context */ +const unsigned char *input; /* input block */ +UINT4 inputLen; /* length of input block */ +{ + UINT4 i; + unsigned int index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += (inputLen << 3)) < (inputLen << 3)) + context->count[1]++; + context->count[1] += (inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible. */ + if (inputLen >= partLen) { + MD5_memcpy((POINTER)&context->buffer[index], (CONSTPOINTER)input, partLen); + MD5Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD5Transform (context->state, &input[i]); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + MD5_memcpy((POINTER)&context->buffer[index], (CONSTPOINTER)&input[i], inputLen-i); +} + +/* MD5 finalization. Ends an MD5 message-digest operation, writing the + the message digest and zeroizing the context. + */ +void MD5Final (digest, context) +unsigned char digest[16]; /* message digest */ +MD5_CTX *context; /* context */ +{ + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64. +*/ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD5Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD5Update (context, bits, 8); + + if (digest != NULL) /* Bill Simpson's padding */ + { + /* store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information. + */ + MD5_memset ((POINTER)context, 0, sizeof (*context)); + } +} + +/* MD5 basic transformation. Transforms state based on block. + */ +static void MD5Transform (state, block) +UINT4 state[4]; +const unsigned char block[64]; +{ + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information. +*/ + MD5_memset ((POINTER)x, 0, sizeof (x)); +} + +#if BYTE_ORDER != LITTLE_ENDIAN + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is + a multiple of 4. + */ +static void Encode (output, input, len) +unsigned char *output; +UINT4 *input; +unsigned int len; +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is + a multiple of 4. + */ +static void Decode (output, input, len) +UINT4 *output; +unsigned char *input; +unsigned int len; +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | + (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); +} + +#endif + +#ifndef HAVEMEMCOPY +#ifndef HAVEBCOPY +/* Note: Replace "for loop" with standard memcpy if possible. + */ + +static void MD5_memcpy (output, input, len) +POINTER output; +POINTER input; +unsigned int len; +{ + unsigned int i; + + for (i = 0; i < len; i++) + + output[i] = input[i]; +} + +/* Note: Replace "for loop" with standard memset if possible. + */ +static void MD5_memset (output, value, len) +POINTER output; +int value; +unsigned int len; +{ + unsigned int i; + + for (i = 0; i < len; i++) + ((char *)output)[i] = (char)value; +} +#endif +#endif + diff --git a/src/pluto/md5.h b/src/pluto/md5.h new file mode 100644 index 000000000..9b29bc46e --- /dev/null +++ b/src/pluto/md5.h @@ -0,0 +1,75 @@ +#ifndef _GLOBAL_H_ +#define _GLOBAL_H_ +/* GLOBAL.H - RSAREF types and constants + */ + +/* PROTOTYPES should be set to one if and only if the compiler supports + function argument prototyping. + The following makes PROTOTYPES default to 0 if it has not already + been defined with C compiler flags. + */ +#ifndef PROTOTYPES +#define PROTOTYPES 1 +#endif + +/* POINTER defines a generic pointer type */ +typedef unsigned char *POINTER; +typedef const unsigned char *CONSTPOINTER; + +/* UINT2 defines a two byte word */ +typedef u_int16_t UINT2; + +/* UINT4 defines a four byte word */ +typedef u_int32_t UINT4; + +/* PROTO_LIST is defined depending on how PROTOTYPES is defined above. + If using PROTOTYPES, then PROTO_LIST returns the list, otherwise it + returns an empty list. + */ + +#if PROTOTYPES +#define PROTO_LIST(list) list +#else +#define PROTO_LIST(list) () +#endif + +#endif + +/* MD5.H - header file for MD5C.C + */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +/* MD5 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD5_CTX; + +void MD5Init PROTO_LIST ((MD5_CTX *)); +void MD5Update PROTO_LIST + ((MD5_CTX *, const unsigned char *, UINT4)); +void MD5Final PROTO_LIST ((unsigned char [16], MD5_CTX *)); + +#define _MD5_H_ diff --git a/src/pluto/modecfg.c b/src/pluto/modecfg.c new file mode 100644 index 000000000..ab44a113e --- /dev/null +++ b/src/pluto/modecfg.c @@ -0,0 +1,1078 @@ +/* Mode config related functions + * Copyright (C) 2001-2002 Colubris Networks + * Copyright (C) 2003 Sean Mathews - Nu Tech Software Solutions, inc. + * Copyright (C) 2003-2004 Xelerance Corporation + * Copyright (C) 2006-2007 Andreas Steffen - Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: modecfg.c,v 1.6 2006/04/24 20:44:57 as Exp $ + * + * This code originally written by Colubris Networks, Inc. + * Extraction of patch and porting to 1.99 codebases by Xelerance Corporation + * Porting to 2.x by Sean Mathews + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "state.h" +#include "demux.h" +#include "timer.h" +#include "ipsec_doi.h" +#include "log.h" +#include "md5.h" +#include "sha1.h" +#include "crypto.h" +#include "modecfg.h" +#include "whack.h" +#include "xauth.h" + +#define MAX_XAUTH_TRIES 3 + +#define SUPPORTED_ATTR_SET ( LELEM(INTERNAL_IP4_ADDRESS) \ + | LELEM(INTERNAL_IP4_NETMASK) \ + | LELEM(INTERNAL_IP4_DNS) \ + | LELEM(INTERNAL_IP4_NBNS) \ + | LELEM(APPLICATION_VERSION) \ + ) + +#define SUPPORTED_UNITY_ATTR_SET ( LELEM(UNITY_BANNER - UNITY_BASE) ) + +#define UNITY_BANNER_STR "Welcome to strongSwan - the Linux VPN Solution!\n" + +/* + * Addresses assigned (usually via ModeCfg) to the Initiator + */ +typedef struct internal_addr internal_addr_t; + +struct internal_addr +{ + lset_t attr_set; + lset_t xauth_attr_set; + lset_t unity_attr_set; + + /* ModeCfg variables */ + ip_address ipaddr; + ip_address dns[2]; + ip_address wins[2]; + + char *unity_banner; + + /* XAUTH variables */ + u_int16_t xauth_type; + xauth_t xauth_secret; + bool xauth_status; +}; + +/* + * Initialize an internal_addr struct + */ +static void +init_internal_addr(internal_addr_t *ia) +{ + ia->attr_set = LEMPTY; + ia->xauth_attr_set = LEMPTY; + ia->xauth_secret.user_name = empty_chunk; + ia->xauth_secret.user_password = empty_chunk; + ia->xauth_type = XAUTH_TYPE_GENERIC; + ia->xauth_status = XAUTH_STATUS_FAIL; + ia->unity_attr_set = LEMPTY; + ia->unity_banner = NULL; + + anyaddr(AF_INET, &ia->ipaddr); + anyaddr(AF_INET, &ia->dns[0]); + anyaddr(AF_INET, &ia->dns[1]); + anyaddr(AF_INET, &ia->wins[0]); + anyaddr(AF_INET, &ia->wins[1]); +} + +/* + * get internal IP address for a connection + */ +static void +get_internal_addr(struct connection *c, internal_addr_t *ia) +{ + if (isanyaddr(&c->spd.that.host_srcip)) + { + /* not defined in connection - fetch it from LDAP */ + } + else + { + char srcip[ADDRTOT_BUF]; + + ia->ipaddr = c->spd.that.host_srcip; + + addrtot(&ia->ipaddr, 0, srcip, sizeof(srcip)); + plog("assigning virtual IP source address %s", srcip); + } + + if (!isanyaddr(&ia->ipaddr)) /* We got an IP address, send it */ + { + c->spd.that.client.addr = ia->ipaddr; + c->spd.that.client.maskbits = 32; + c->spd.that.has_client = TRUE; + + ia->attr_set = LELEM(INTERNAL_IP4_ADDRESS) + | LELEM(INTERNAL_IP4_NETMASK); + } + + if (!isanyaddr(&ia->dns[0])) /* We got DNS addresses, send them */ + ia->attr_set |= LELEM(INTERNAL_IP4_DNS); + + if (!isanyaddr(&ia->wins[0])) /* We got WINS addresses, send them */ + ia->attr_set |= LELEM(INTERNAL_IP4_NBNS); +} + +/* + * Set srcip and client subnet to internal IP address + */ +static bool +set_internal_addr(struct connection *c, internal_addr_t *ia) +{ + if (ia->attr_set & LELEM(INTERNAL_IP4_ADDRESS) + && !isanyaddr(&ia->ipaddr)) + { + if (addrbytesptr(&c->spd.this.host_srcip, NULL) == 0 + || isanyaddr(&c->spd.this.host_srcip) + || sameaddr(&c->spd.this.host_srcip, &ia->ipaddr)) + { + char srcip[ADDRTOT_BUF]; + + addrtot(&ia->ipaddr, 0, srcip, sizeof(srcip)); + plog("setting virtual IP source address to %s", srcip); + } + else + { + char old_srcip[ADDRTOT_BUF]; + char new_srcip[ADDRTOT_BUF]; + + addrtot(&c->spd.this.host_srcip, 0, old_srcip, sizeof(old_srcip)); + addrtot(&ia->ipaddr, 0, new_srcip, sizeof(new_srcip)); + plog("replacing virtual IP source address %s by %s" + , old_srcip, new_srcip); + } + + /* setting srcip */ + c->spd.this.host_srcip = ia->ipaddr; + + /* setting client subnet to srcip/32 */ + addrtosubnet(&ia->ipaddr, &c->spd.this.client); + setportof(0, &c->spd.this.client.addr); + c->spd.this.has_client = TRUE; + return TRUE; + } + return FALSE; +} + +/* + * Compute HASH of Mode Config. + */ +static size_t +modecfg_hash(u_char *dest, const u_char *start, const u_char *roof + , const struct state *st) +{ + struct hmac_ctx ctx; + + hmac_init_chunk(&ctx, st->st_oakley.hasher, st->st_skeyid_a); + hmac_update(&ctx, (const u_char *) &st->st_msgid, sizeof(st->st_msgid)); + hmac_update(&ctx, start, roof-start); + hmac_final(dest, &ctx); + + DBG(DBG_CRYPT, + DBG_log("ModeCfg HASH computed:"); + DBG_dump("", dest, ctx.hmac_digest_size) + ) + return ctx.hmac_digest_size; +} + + +/* + * Generate an IKE message containing ModeCfg information (eg: IP, DNS, WINS) + */ +static stf_status +modecfg_build_msg(struct state *st, pb_stream *rbody + , u_int16_t msg_type + , internal_addr_t *ia + , u_int16_t ap_id) +{ + u_char *r_hash_start, *r_hashval; + + START_HASH_PAYLOAD(*rbody, ISAKMP_NEXT_ATTR); + + /* ATTR out */ + { + struct isakmp_mode_attr attrh; + struct isakmp_attribute attr; + pb_stream strattr,attrval; + int attr_type; + int dns_idx, wins_idx; + bool dont_advance; + bool is_xauth_attr_set = ia->xauth_attr_set != LEMPTY; + bool is_unity_attr_set = ia->unity_attr_set != LEMPTY; + lset_t attr_set = ia->attr_set; + + attrh.isama_np = ISAKMP_NEXT_NONE; + attrh.isama_type = msg_type; + attrh.isama_identifier = ap_id; + + if (!out_struct(&attrh, &isakmp_attr_desc, rbody, &strattr)) + return STF_INTERNAL_ERROR; + + attr_type = 0; + dns_idx = 0; + wins_idx = 0; + + while (attr_set != LEMPTY || is_xauth_attr_set || is_unity_attr_set) + { + if (attr_set == LEMPTY) + { + if (is_xauth_attr_set) + { + attr_set = ia->xauth_attr_set; + attr_type = XAUTH_BASE; + is_xauth_attr_set = FALSE; + } + else + { + attr_set = ia->unity_attr_set; + attr_type = UNITY_BASE; + is_unity_attr_set = FALSE; + } + } + + dont_advance = FALSE; + + if (attr_set & 1) + { + const u_char *byte_ptr; + u_int len; + + /* ISAKMP attr out */ + if (attr_type == XAUTH_TYPE) + { + attr.isaat_af_type = attr_type | ISAKMP_ATTR_AF_TV; + attr.isaat_lv = ia->xauth_type; + } + else if (attr_type == XAUTH_STATUS) + { + attr.isaat_af_type = attr_type | ISAKMP_ATTR_AF_TV; + attr.isaat_lv = ia->xauth_status; + } + else + { + attr.isaat_af_type = attr_type | ISAKMP_ATTR_AF_TLV; + } + out_struct(&attr, &isakmp_modecfg_attribute_desc, &strattr, &attrval); + + switch (attr_type) + { + case INTERNAL_IP4_ADDRESS: + if (!isanyaddr(&ia->ipaddr)) + { + len = addrbytesptr(&ia->ipaddr, &byte_ptr); + out_raw(byte_ptr, len, &attrval, "IP4_addr"); + } + break; + case INTERNAL_IP4_NETMASK: + { + u_int mask; +#if 0 + char mask[4],bits[8]={0x00,0x80,0xc0,0xe0,0xf0,0xf8,0xfc,0xfe}; + int t,m=st->st_connection->that.host_addr.maskbit; + for (t=0; t<4; t++) + { + if (m < 8) + mask[t] = bits[m]; + else + mask[t] = 0xff; + m -= 8; + } +#endif + if (st->st_connection->spd.this.client.maskbits == 0) + mask = 0; + else + mask = 0xffffffff * 1; + out_raw(&mask, 4, &attrval, "IP4_mask"); + } + break; + case INTERNAL_IP4_SUBNET: + { + char mask[4]; + char bits[8] = {0x00,0x80,0xc0,0xe0,0xf0,0xf8,0xfc,0xfe}; + int t; + int m = st->st_connection->spd.this.client.maskbits; + + for (t = 0; t < 4; t++) + { + if (m < 8) + mask[t] = bits[m]; + else + mask[t] = 0xff; + m -= 8; + if (m < 0) + m = 0; + } + len = addrbytesptr(&st->st_connection->spd.this.client.addr, &byte_ptr); + out_raw(byte_ptr, len, &attrval, "IP4_subnet"); + out_raw(mask, sizeof(mask), &attrval, "IP4_submsk"); + } + break; + case INTERNAL_IP4_DNS: + if (!isanyaddr(&ia->dns[dns_idx])) + { + len = addrbytesptr(&ia->dns[dns_idx++], &byte_ptr); + out_raw(byte_ptr, len, &attrval, "IP4_dns"); + } + if (dns_idx < 2 && !isanyaddr(&ia->dns[dns_idx])) + { + dont_advance = TRUE; + } + break; + case INTERNAL_IP4_NBNS: + if (!isanyaddr(&ia->wins[wins_idx])) + { + len = addrbytesptr(&ia->wins[wins_idx++], &byte_ptr); + out_raw(byte_ptr, len, &attrval, "IP4_wins"); + } + if (wins_idx < 2 && !isanyaddr(&ia->wins[wins_idx])) + { + dont_advance = TRUE; + } + break; + case XAUTH_TYPE: + break; + case XAUTH_USER_NAME: + if (ia->xauth_secret.user_name.ptr != NULL) + { + out_raw(ia->xauth_secret.user_name.ptr + , ia->xauth_secret.user_name.len + , &attrval, "xauth_user_name"); + } + break; + case XAUTH_USER_PASSWORD: + if (ia->xauth_secret.user_password.ptr != NULL) + { + out_raw(ia->xauth_secret.user_password.ptr + , ia->xauth_secret.user_password.len + , &attrval, "xauth_user_password"); + } + break; + case XAUTH_STATUS: + break; + case UNITY_BANNER: + if (ia->unity_banner != NULL) + { + out_raw(ia->unity_banner + , strlen(ia->unity_banner) + , &attrval, "UNITY_BANNER"); + } + break; + default: + plog("attempt to send unsupported mode cfg attribute %s." + , enum_show(&modecfg_attr_names, attr_type)); + break; + } + close_output_pbs(&attrval); + } + if (!dont_advance) + { + attr_type++; + attr_set >>= 1; + } + } + close_message(&strattr); + } + + modecfg_hash(r_hashval, r_hash_start, rbody->cur, st); + close_message(rbody); + encrypt_message(rbody, st); + return STF_OK; +} + +/* + * Send ModeCfg message + */ +static stf_status +modecfg_send_msg(struct state *st, int isama_type, internal_addr_t *ia) +{ + pb_stream msg; + pb_stream rbody; + char buf[BUF_LEN]; + + /* set up attr */ + init_pbs(&msg, buf, sizeof(buf), "ModeCfg msg buffer"); + + /* this is the beginning of a new exchange */ + st->st_msgid = generate_msgid(st); + init_phase2_iv(st, &st->st_msgid); + + /* HDR out */ + { + struct isakmp_hdr hdr; + + zero(&hdr); /* default to 0 */ + hdr.isa_version = ISAKMP_MAJOR_VERSION << ISA_MAJ_SHIFT | ISAKMP_MINOR_VERSION; + hdr.isa_np = ISAKMP_NEXT_HASH; + hdr.isa_xchg = ISAKMP_XCHG_MODE_CFG; + hdr.isa_flags = ISAKMP_FLAG_ENCRYPTION; + memcpy(hdr.isa_icookie, st->st_icookie, COOKIE_SIZE); + memcpy(hdr.isa_rcookie, st->st_rcookie, COOKIE_SIZE); + hdr.isa_msgid = st->st_msgid; + + if (!out_struct(&hdr, &isakmp_hdr_desc, &msg, &rbody)) + { + return STF_INTERNAL_ERROR; + } + } + + /* ATTR out */ + modecfg_build_msg(st, &rbody + , isama_type + , ia + , 0 /* XXX isama_id */ + ); + + freeanychunk(st->st_tpacket); + clonetochunk(st->st_tpacket, msg.start, pbs_offset(&msg), "ModeCfg msg"); + + /* Transmit */ + send_packet(st, "ModeCfg msg"); + + if (st->st_event->ev_type != EVENT_RETRANSMIT) + { + delete_event(st); + event_schedule(EVENT_RETRANSMIT, EVENT_RETRANSMIT_DELAY_0, st); + } + return STF_OK; +} + +/* + * Parse a ModeCfg attribute payload + */ +static stf_status +modecfg_parse_attributes(pb_stream *attrs, internal_addr_t *ia) +{ + struct isakmp_attribute attr; + pb_stream strattr; + + while (pbs_left(attrs) >= sizeof(struct isakmp_attribute)) + { + u_int16_t attr_type; + u_int16_t attr_len; + + if (!in_struct(&attr, &isakmp_modecfg_attribute_desc, attrs, &strattr)) + { + return STF_FAIL; + } + attr_type = attr.isaat_af_type & ISAKMP_ATTR_RTYPE_MASK; + attr_len = attr.isaat_lv; + + switch (attr_type) + { + case INTERNAL_IP4_ADDRESS: + if (attr_len == 4) + { + initaddr((char *)(strattr.cur), 4, AF_INET, &ia->ipaddr); + } + /* fall through to set attribute flag */ + case INTERNAL_IP4_NETMASK: + case INTERNAL_IP4_DNS: + case INTERNAL_IP4_SUBNET: + case INTERNAL_IP4_NBNS: + case INTERNAL_ADDRESS_EXPIRY: + case INTERNAL_IP4_DHCP: + case INTERNAL_IP6_ADDRESS: + case INTERNAL_IP6_NETMASK: + case INTERNAL_IP6_DNS: + case INTERNAL_IP6_NBNS: + case INTERNAL_IP6_DHCP: + case SUPPORTED_ATTRIBUTES: + case INTERNAL_IP6_SUBNET: + ia->attr_set |= LELEM(attr_type); + break; + case APPLICATION_VERSION: + if (attr_len > 0) + { + DBG(DBG_PARSING, + DBG_log(" '%.*s'", attr_len, strattr.cur) + ) + } + ia->attr_set |= LELEM(attr_type); + break; + case XAUTH_TYPE: + ia->xauth_type = attr.isaat_lv; + ia->xauth_attr_set |= LELEM(attr_type - XAUTH_BASE); + break; + case XAUTH_USER_NAME: + setchunk(ia->xauth_secret.user_name, strattr.cur, attr_len); + ia->xauth_attr_set |= LELEM(attr_type - XAUTH_BASE); + break; + case XAUTH_USER_PASSWORD: + setchunk(ia->xauth_secret.user_password, strattr.cur, attr_len); + ia->xauth_attr_set |= LELEM(attr_type - XAUTH_BASE); + break; + case XAUTH_STATUS: + ia->xauth_status = attr.isaat_lv; + ia->xauth_attr_set |= LELEM(attr_type - XAUTH_BASE); + break; + case XAUTH_MESSAGE: + if (attr_len > 0) + { + DBG(DBG_PARSING, + DBG_log(" '%.*s'", attr_len, strattr.cur) + ) + } + /* fall through to set attribute flag */ + case XAUTH_PASSCODE: + case XAUTH_CHALLENGE: + case XAUTH_DOMAIN: + case XAUTH_NEXT_PIN: + case XAUTH_ANSWER: + ia->xauth_attr_set |= LELEM(attr_type - XAUTH_BASE); + break; + case UNITY_DDNS_HOSTNAME: + if (attr_len > 0) + { + DBG(DBG_PARSING, + DBG_log(" '%.*s'", attr_len, strattr.cur) + ) + } + /* fall through to set attribute flag */ + case UNITY_BANNER: + case UNITY_SAVE_PASSWD: + case UNITY_DEF_DOMAIN: + case UNITY_SPLITDNS_NAME: + case UNITY_SPLIT_INCLUDE: + case UNITY_NATT_PORT: + case UNITY_LOCAL_LAN: + case UNITY_PFS: + case UNITY_FW_TYPE: + case UNITY_BACKUP_SERVERS: + ia->unity_attr_set |= LELEM(attr_type - UNITY_BASE); + break; + default: + plog("unsupported ModeCfg attribute %s received." + , enum_show(&modecfg_attr_names, attr_type)); + break; + } + } + return STF_OK; +} + +/* + * Parse a ModeCfg message + */ +static stf_status +modecfg_parse_msg(struct msg_digest *md, int isama_type, u_int16_t *isama_id + , internal_addr_t *ia) +{ + struct state *const st = md->st; + struct payload_digest *p; + stf_status stat; + + st->st_msgid = md->hdr.isa_msgid; + + CHECK_QUICK_HASH(md, modecfg_hash(hash_val + , hash_pbs->roof + , md->message_pbs.roof, st) + , "MODECFG-HASH", "ISAKMP_CFG_MSG"); + + /* process the ModeCfg payloads received */ + for (p = md->chain[ISAKMP_NEXT_ATTR]; p != NULL; p = p->next) + { + internal_addr_t ia_candidate; + + init_internal_addr(&ia_candidate); + + if (p->payload.attribute.isama_type == isama_type) + { + *isama_id = p->payload.attribute.isama_identifier; + + stat = modecfg_parse_attributes(&p->pbs, &ia_candidate); + if (stat == STF_OK) + { + /* return with a valid set of attributes */ + *ia = ia_candidate; + return STF_OK; + } + } + else + { + plog("expected %s, got %s instead (ignored)" + , enum_name(&attr_msg_type_names, isama_type) + , enum_name(&attr_msg_type_names, p->payload.attribute.isama_type)); + + stat = modecfg_parse_attributes(&p->pbs, &ia_candidate); + } + if (stat != STF_OK) + return stat; + } + return STF_IGNORE; +} + +/* + * Send ModeCfg request message from client to server in pull mode + */ +stf_status +modecfg_send_request(struct state *st) +{ + stf_status stat; + internal_addr_t ia; + + init_internal_addr(&ia); + + ia.attr_set = LELEM(INTERNAL_IP4_ADDRESS) + | LELEM(INTERNAL_IP4_NETMASK); + + plog("sending ModeCfg request"); + st->st_state = STATE_MODE_CFG_I1; + stat = modecfg_send_msg(st, ISAKMP_CFG_REQUEST, &ia); + if (stat == STF_OK) + st->st_modecfg.started = TRUE; + return stat; +} + +/* STATE_MODE_CFG_R0: + * HDR*, HASH, ATTR(REQ=IP) --> HDR*, HASH, ATTR(REPLY=IP) + * + * used in ModeCfg pull mode, on the server (responder) + */ +stf_status +modecfg_inR0(struct msg_digest *md) +{ + struct state *const st = md->st; + u_int16_t isama_id; + internal_addr_t ia; + bool want_unity_banner; + stf_status stat, stat_build; + + stat = modecfg_parse_msg(md, ISAKMP_CFG_REQUEST, &isama_id, &ia); + if (stat != STF_OK) + return stat; + + want_unity_banner = (ia.unity_attr_set & LELEM(UNITY_BANNER - UNITY_BASE)) != LEMPTY; + + init_internal_addr(&ia); + get_internal_addr(st->st_connection, &ia); + + if (want_unity_banner) + { + ia.unity_banner = UNITY_BANNER_STR; + ia.unity_attr_set |= LELEM(UNITY_BANNER - UNITY_BASE); + } + + plog("sending ModeCfg reply"); + + stat_build = modecfg_build_msg(st, &md->rbody + , ISAKMP_CFG_REPLY + , &ia + , isama_id); + if (stat_build != STF_OK) + return stat_build; + + st->st_msgid = 0; + return STF_OK; +} + +/* STATE_MODE_CFG_I1: + * HDR*, HASH, ATTR(REPLY=IP) + * + * used in ModeCfg pull mode, on the client (initiator) + */ +stf_status +modecfg_inI1(struct msg_digest *md) +{ + struct state *const st = md->st; + u_int16_t isama_id; + internal_addr_t ia; + stf_status stat; + + plog("parsing ModeCfg reply"); + + stat = modecfg_parse_msg(md, ISAKMP_CFG_REPLY, &isama_id, &ia); + if (stat != STF_OK) + return stat; + + st->st_modecfg.vars_set = set_internal_addr(st->st_connection, &ia); + st->st_msgid = 0; + return STF_OK; +} + + +/* + * Send ModeCfg set message from server to client in push mode + */ +stf_status +modecfg_send_set(struct state *st) +{ + stf_status stat; + internal_addr_t ia; + + init_internal_addr(&ia); + get_internal_addr(st->st_connection, &ia); + +#ifdef CISCO_QUIRKS + ia.unity_banner = UNITY_BANNER_STR; + ia.unity_attr_set |= LELEM(UNITY_BANNER - UNITY_BASE); +#endif + + plog("sending ModeCfg set"); + st->st_state = STATE_MODE_CFG_R3; + stat = modecfg_send_msg(st, ISAKMP_CFG_SET, &ia); + if (stat == STF_OK) + st->st_modecfg.started = TRUE; + return stat; +} + +/* STATE_MODE_CFG_I0: + * HDR*, HASH, ATTR(SET=IP) --> HDR*, HASH, ATTR(ACK,OK) + * + * used in ModeCfg push mode, on the client (initiator). + */ +stf_status +modecfg_inI0(struct msg_digest *md) +{ + struct state *const st = md->st; + u_int16_t isama_id; + internal_addr_t ia; + lset_t attr_set, unity_attr_set; + stf_status stat, stat_build; + + plog("parsing ModeCfg set"); + + stat = modecfg_parse_msg(md, ISAKMP_CFG_SET, &isama_id, &ia); + if (stat != STF_OK) + return stat; + + st->st_modecfg.vars_set = set_internal_addr(st->st_connection, &ia); + + /* prepare ModeCfg ack which sends zero length attributes */ + attr_set = ia.attr_set; + unity_attr_set = ia.unity_attr_set; + init_internal_addr(&ia); + ia.attr_set = attr_set & SUPPORTED_ATTR_SET; + ia.unity_attr_set = unity_attr_set & SUPPORTED_UNITY_ATTR_SET; + + plog("sending ModeCfg ack"); + + stat_build = modecfg_build_msg(st, &md->rbody + , ISAKMP_CFG_ACK + , &ia + , isama_id); + if (stat_build != STF_OK) + return stat_build; + + st->st_msgid = 0; + return STF_OK; +} + +/* STATE_MODE_CFG_R3: + * HDR*, HASH, ATTR(ACK,OK) + * + * used in ModeCfg push mode, on the server (responder) + */ +stf_status +modecfg_inR3(struct msg_digest *md) +{ + struct state *const st = md->st; + u_int16_t isama_id; + internal_addr_t ia; + stf_status stat; + + plog("parsing ModeCfg ack"); + + stat = modecfg_parse_msg(md, ISAKMP_CFG_ACK, &isama_id, &ia); + if (stat != STF_OK) + return stat; + + st->st_msgid = 0; + return STF_OK; +} + +/* + * Send XAUTH credentials request (username + password) + */ +stf_status +xauth_send_request(struct state *st) +{ + stf_status stat; + internal_addr_t ia; + + init_internal_addr(&ia); + ia.xauth_attr_set = LELEM(XAUTH_USER_NAME - XAUTH_BASE) + | LELEM(XAUTH_USER_PASSWORD - XAUTH_BASE); + + plog("sending XAUTH request"); + st->st_state = STATE_XAUTH_R1; + stat = modecfg_send_msg(st, ISAKMP_CFG_REQUEST, &ia); + if (stat == STF_OK) + st->st_xauth.started = TRUE; + return stat; +} + +/* STATE_XAUTH_I0: + * HDR*, HASH, ATTR(REQ) --> HDR*, HASH, ATTR(REPLY=USERNAME/PASSWORD) + * + * used on the XAUTH client (initiator) + */ +stf_status +xauth_inI0(struct msg_digest *md) +{ + struct state *const st = md->st; + u_int16_t isama_id; + internal_addr_t ia; + stf_status stat, stat_build; + bool xauth_type_present; + + plog("parsing XAUTH request"); + + stat = modecfg_parse_msg(md, ISAKMP_CFG_REQUEST, &isama_id, &ia); + if (stat != STF_OK) + return stat; + + /* check XAUTH attributes */ + xauth_type_present = (ia.xauth_attr_set & LELEM(XAUTH_TYPE - XAUTH_BASE)) != LEMPTY; + + if (xauth_type_present && ia.xauth_type != XAUTH_TYPE_GENERIC) + { + plog("xauth type %s is not supported", enum_name(&xauth_type_names, ia.xauth_type)); + stat = STF_FAIL; + } + else if ((ia.xauth_attr_set & LELEM(XAUTH_USER_NAME - XAUTH_BASE)) == LEMPTY) + { + plog("user name attribute is missing in XAUTH request"); + stat = STF_FAIL; + } + else if ((ia.xauth_attr_set & LELEM(XAUTH_USER_PASSWORD - XAUTH_BASE)) == LEMPTY) + { + plog("user password attribute is missing in XAUTH request"); + stat = STF_FAIL; + } + + /* prepare XAUTH reply */ + init_internal_addr(&ia); + + if (stat == STF_OK) + { + /* get user credentials using a plugin function */ + if (!xauth_module.get_secret(&ia.xauth_secret)) + { + plog("xauth user credentials not found"); + stat = STF_FAIL; + } + } + if (stat == STF_OK) + { + DBG(DBG_CONTROL, + DBG_log("my xauth user name is '%.*s'" + , ia.xauth_secret.user_name.len + , ia.xauth_secret.user_name.ptr) + ) + DBG(DBG_PRIVATE, + DBG_log("my xauth user password is '%.*s'" + , ia.xauth_secret.user_password.len + , ia.xauth_secret.user_password.ptr) + ) + ia.xauth_attr_set = LELEM(XAUTH_USER_NAME - XAUTH_BASE) + | LELEM(XAUTH_USER_PASSWORD - XAUTH_BASE); + if (xauth_type_present) + ia.xauth_attr_set |= LELEM(XAUTH_TYPE - XAUTH_BASE); + } + else + { + ia.xauth_attr_set = LELEM(XAUTH_STATUS - XAUTH_BASE); + ia.xauth_status = XAUTH_STATUS_FAIL; + } + + plog("sending XAUTH reply"); + + stat_build = modecfg_build_msg(st, &md->rbody + , ISAKMP_CFG_REPLY + , &ia + , isama_id); + if (stat_build != STF_OK) + return stat_build; + + if (stat == STF_OK) + { + st->st_xauth.started = TRUE; + st->st_msgid = 0; + return STF_OK; + } + else + { + /* send XAUTH reply msg and then delete ISAKMP SA */ + freeanychunk(st->st_tpacket); + clonetochunk(st->st_tpacket, md->reply.start + , pbs_offset(&md->reply), "XAUTH reply msg"); + send_packet(st, "XAUTH reply msg"); + delete_state(st); + return STF_IGNORE; + } +} + +/* STATE_XAUTH_R1: + * HDR*, HASH, ATTR(REPLY=USERNAME/PASSWORD) --> HDR*, HASH, ATTR(STATUS) + * + * used on the XAUTH server (responder) + */ +stf_status +xauth_inR1(struct msg_digest *md) +{ + struct state *const st = md->st; + u_int16_t isama_id; + internal_addr_t ia; + stf_status stat, stat_build; + + plog("parsing XAUTH reply"); + + stat = modecfg_parse_msg(md, ISAKMP_CFG_REPLY, &isama_id, &ia); + if (stat != STF_OK) + return stat; + + /* did the client return an XAUTH FAIL status? */ + if ((ia.xauth_attr_set & LELEM(XAUTH_STATUS - XAUTH_BASE)) != LEMPTY) + { + plog("received FAIL status in XAUTH reply"); + + /* client is not able to do XAUTH, delete ISAKMP SA */ + delete_state(st); + return STF_IGNORE; + } + + /* check XAUTH reply */ + if ((ia.xauth_attr_set & LELEM(XAUTH_USER_NAME - XAUTH_BASE)) == LEMPTY) + { + plog("user name attribute is missing in XAUTH reply"); + st->st_xauth.status = FALSE; + } + else if ((ia.xauth_attr_set & LELEM(XAUTH_USER_PASSWORD - XAUTH_BASE)) == LEMPTY) + { + plog("user password attribute is missing in XAUTH reply"); + st->st_xauth.status = FALSE; + } + else + { + DBG(DBG_CONTROL, + DBG_log("peer xauth user name is '%.*s'" + , ia.xauth_secret.user_name.len + , ia.xauth_secret.user_name.ptr) + ) + DBG(DBG_PRIVATE, + DBG_log("peer xauth user password is '%.*s'" + , ia.xauth_secret.user_password.len + , ia.xauth_secret.user_password.ptr) + ) + /* verify the user credentials using a plugn function */ + st->st_xauth.status = xauth_module.verify_secret(&ia.xauth_secret); + plog("extended authentication %s", st->st_xauth.status? "was successful":"failed"); + } + + /* prepare XAUTH set which sends the authentication status */ + init_internal_addr(&ia); + ia.xauth_attr_set = LELEM(XAUTH_STATUS - XAUTH_BASE); + ia.xauth_status = (st->st_xauth.status)? XAUTH_STATUS_OK : XAUTH_STATUS_FAIL; + + plog("sending XAUTH status:"); + + stat_build = modecfg_send_msg(st, ISAKMP_CFG_SET, &ia); + if (stat_build != STF_OK) + return stat_build; + return STF_OK; +} + +/* STATE_XAUTH_I1: + * HDR*, HASH, ATTR(STATUS) --> HDR*, HASH, ATTR(ACK) + * + * used on the XAUTH client (initiator) + */ +stf_status +xauth_inI1(struct msg_digest *md) +{ + struct state *const st = md->st; + u_int16_t isama_id; + internal_addr_t ia; + stf_status stat, stat_build; + + plog("parsing XAUTH status"); + stat = modecfg_parse_msg(md, ISAKMP_CFG_SET, &isama_id, &ia); + if (stat != STF_OK) + { + /* notification payload - not exactly the right choice, but okay */ + md->note = ATTRIBUTES_NOT_SUPPORTED; + return stat; + } + + st->st_xauth.status = ia.xauth_status; + plog("extended authentication %s", st->st_xauth.status? "was successful":"failed"); + + plog("sending XAUTH ack"); + init_internal_addr(&ia); + stat_build = modecfg_build_msg(st, &md->rbody + , ISAKMP_CFG_ACK + , &ia + , isama_id); + if (stat_build != STF_OK) + return stat_build; + + if (st->st_xauth.status) + { + st->st_msgid = 0; + return STF_OK; + } + else + { + /* send XAUTH ack msg and then delete ISAKMP SA */ + freeanychunk(st->st_tpacket); + clonetochunk(st->st_tpacket, md->reply.start + , pbs_offset(&md->reply), "XAUTH ack msg"); + send_packet(st, "XAUTH ack msg"); + delete_state(st); + return STF_IGNORE; + } +} + +/* STATE_XAUTH_R2: + * HDR*, ATTR(STATUS), HASH --> Done + * + * used on the XAUTH server (responder) + */ +stf_status +xauth_inR2(struct msg_digest *md) +{ + struct state *const st = md->st; + u_int16_t isama_id; + internal_addr_t ia; + stf_status stat; + + plog("parsing XAUTH ack"); + + stat = modecfg_parse_msg(md, ISAKMP_CFG_ACK, &isama_id, &ia); + if (stat != STF_OK) + return stat; + + st->st_msgid = 0; + if (st->st_xauth.status) + { + return STF_OK; + } + else + { + delete_state(st); + return STF_IGNORE; + } +} diff --git a/src/pluto/modecfg.h b/src/pluto/modecfg.h new file mode 100644 index 000000000..68b7ef446 --- /dev/null +++ b/src/pluto/modecfg.h @@ -0,0 +1,47 @@ +/* Mode Config related functions + * Copyright (C) 2001-2002 Colubris Networks + * Copyright (C) 2003-2004 Xelerance Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: modecfg.h,v 1.1 2005/01/06 22:10:15 as Exp $ + */ + +#ifndef _MODECFG_H +#define _MODECFG_H + +struct state; +struct msg_digest; + +/* ModeConfig pull mode start function */ +extern stf_status modecfg_send_request(struct state *st); + +/* ModeConfig pull mode state transition functions */ +extern stf_status modecfg_inR0(struct msg_digest *md); +extern stf_status modecfg_inI1(struct msg_digest *md); + +/* ModeConfig push mode start function */ +extern stf_status modecfg_send_set(struct state *st); + +/* ModeConfig push mode state transition functions */ +extern stf_status modecfg_inI0(struct msg_digest *md); +extern stf_status modecfg_inR3(struct msg_digest *md); + +/* XAUTH start function */ +extern stf_status xauth_send_request(struct state *st); + +/* XAUTH state transition funcgtions */ +extern stf_status xauth_inI0(struct msg_digest *md); +extern stf_status xauth_inR1(struct msg_digest *md); +extern stf_status xauth_inI1(struct msg_digest *md); +extern stf_status xauth_inR2(struct msg_digest *md); + +#endif /* _MODECFG_H */ diff --git a/src/pluto/mp_defs.c b/src/pluto/mp_defs.c new file mode 100644 index 000000000..7ad896751 --- /dev/null +++ b/src/pluto/mp_defs.c @@ -0,0 +1,70 @@ +/* some multiprecision utilities + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: mp_defs.c,v 1.1 2006/01/05 12:37:11 as Exp $ + */ + +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "mp_defs.h" +#include "log.h" + +/* Convert MP_INT to network form (binary octets, big-endian). + * We do the malloc; caller must eventually do free. + */ +chunk_t +mpz_to_n(const MP_INT *mp, size_t bytes) +{ + chunk_t r; + MP_INT temp1, temp2; + int i; + + r.len = bytes; + r.ptr = alloc_bytes(r.len, "host representation of large integer"); + + mpz_init(&temp1); + mpz_init(&temp2); + + mpz_set(&temp1, mp); + + for (i = r.len-1; i >= 0; i--) + { + r.ptr[i] = mpz_mdivmod_ui(&temp2, NULL, &temp1, 1 << BITS_PER_BYTE); + mpz_set(&temp1, &temp2); + } + + passert(mpz_sgn(&temp1) == 0); /* we must have done all the bits */ + mpz_clear(&temp1); + mpz_clear(&temp2); + + return r; +} + +/* Convert network form (binary bytes, big-endian) to MP_INT. + * The *mp must not be previously mpz_inited. + */ +void +n_to_mpz(MP_INT *mp, const u_char *nbytes, size_t nlen) +{ + size_t i; + + mpz_init_set_ui(mp, 0); + + for (i = 0; i != nlen; i++) + { + mpz_mul_ui(mp, mp, 1 << BITS_PER_BYTE); + mpz_add_ui(mp, mp, nbytes[i]); + } +} diff --git a/src/pluto/mp_defs.h b/src/pluto/mp_defs.h new file mode 100644 index 000000000..744a028d1 --- /dev/null +++ b/src/pluto/mp_defs.h @@ -0,0 +1,36 @@ +/* some multiprecision utilities + * Copyright (C) 1997 Angelos D. Keromytis. + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: mp_defs.h,v 1.2 2006/01/06 11:40:45 as Exp $ + */ + +#ifndef _MP_DEFS_H +#define _MP_DEFS_H + +#include <gmp.h> + +#include "defs.h" + +extern void n_to_mpz(MP_INT *mp, const u_char *nbytes, size_t nlen); +extern chunk_t mpz_to_n(const MP_INT *mp, size_t bytes); + +/* var := mod(base ** exp, mod), ensuring var is mpz_inited */ +#define mpz_init_powm(flag, var, base, exp, mod) { \ + if (!(flag)) \ + mpz_init(&(var)); \ + (flag) = TRUE; \ + mpz_powm(&(var), &(base), &(exp), (mod)); \ + } + +#endif /* _MP_DEFS_H */ diff --git a/src/pluto/nat_traversal.c b/src/pluto/nat_traversal.c new file mode 100644 index 000000000..4a52cc107 --- /dev/null +++ b/src/pluto/nat_traversal.c @@ -0,0 +1,866 @@ +/* FreeS/WAN NAT-Traversal + * Copyright (C) 2002-2005 Mathieu Lafon - Arkoon Network Security + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: nat_traversal.c,v 1.8 2005/01/06 22:36:58 as Exp $ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <stdarg.h> +#include <syslog.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> /* used only if MSG_NOSIGNAL not defined */ +#include <sys/queue.h> + +#include <freeswan.h> +#include <ipsec_policy.h> +#include <pfkeyv2.h> +#include <pfkey.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "server.h" +#include "state.h" +#include "connections.h" +#include "packet.h" +#include "demux.h" +#include "kernel.h" +#include "whack.h" +#include "timer.h" +#include "cookie.h" +#include "sha1.h" +#include "md5.h" +#include "crypto.h" +#include "vendor.h" +#include "ike_alg.h" +#include "nat_traversal.h" + +/* #define FORCE_NAT_TRAVERSAL */ +#define NAT_D_DEBUG +#define NAT_T_SUPPORT_LAST_DRAFTS + +#ifndef SOL_UDP +#define SOL_UDP 17 +#endif + +#ifndef UDP_ESPINUDP +#define UDP_ESPINUDP 100 +#endif + +#define DEFAULT_KEEP_ALIVE_PERIOD 20 + +#ifdef _IKE_ALG_H +/* Alg patch: hash_digest_len -> hash_digest_size */ +#define hash_digest_len hash_digest_size +#endif + +bool nat_traversal_enabled = FALSE; +bool nat_traversal_support_non_ike = FALSE; +bool nat_traversal_support_port_floating = FALSE; + +static unsigned int _kap = 0; +static unsigned int _ka_evt = 0; +static bool _force_ka = 0; + +static const char *natt_version = "0.6c"; + +void init_nat_traversal (bool activate, unsigned int keep_alive_period, + bool fka, bool spf) +{ + nat_traversal_enabled = activate; + nat_traversal_support_non_ike = activate; +#ifdef NAT_T_SUPPORT_LAST_DRAFTS + nat_traversal_support_port_floating = activate ? spf : FALSE; +#endif + _force_ka = fka; + _kap = keep_alive_period ? keep_alive_period : DEFAULT_KEEP_ALIVE_PERIOD; + plog(" including NAT-Traversal patch (Version %s)%s%s%s" + , natt_version, activate ? "" : " [disabled]" + , activate & fka ? " [Force KeepAlive]" : "" + , activate & !spf ? " [Port Floating disabled]" : ""); +} + +static void disable_nat_traversal (int type) +{ + if (type == ESPINUDP_WITH_NON_IKE) + nat_traversal_support_non_ike = FALSE; + else + nat_traversal_support_port_floating = FALSE; + + if (!nat_traversal_support_non_ike && + !nat_traversal_support_port_floating) + nat_traversal_enabled = FALSE; +} + +static void _natd_hash(const struct hash_desc *hasher, char *hash, + u_int8_t *icookie, u_int8_t *rcookie, + const ip_address *ip, u_int16_t port) +{ + union hash_ctx ctx; + + if (is_zero_cookie(icookie)) + DBG_log("_natd_hash: Warning, icookie is zero !!"); + if (is_zero_cookie(rcookie)) + DBG_log("_natd_hash: Warning, rcookie is zero !!"); + + /** + * draft-ietf-ipsec-nat-t-ike-01.txt + * + * HASH = HASH(CKY-I | CKY-R | IP | Port) + * + * All values in network order + */ + hasher->hash_init(&ctx); + hasher->hash_update(&ctx, icookie, COOKIE_SIZE); + hasher->hash_update(&ctx, rcookie, COOKIE_SIZE); + switch (addrtypeof(ip)) { + case AF_INET: + hasher->hash_update(&ctx, (const u_char *)&ip->u.v4.sin_addr.s_addr + , sizeof(ip->u.v4.sin_addr.s_addr)); + break; + case AF_INET6: + hasher->hash_update(&ctx, (const u_char *)&ip->u.v6.sin6_addr.s6_addr + , sizeof(ip->u.v6.sin6_addr.s6_addr)); + break; + } + hasher->hash_update(&ctx, (const u_char *)&port, sizeof(u_int16_t)); + hasher->hash_final(hash, &ctx); +#ifdef NAT_D_DEBUG + DBG(DBG_NATT, + DBG_log("_natd_hash: hasher=%p(%d)", hasher, (int)hasher->hash_digest_len); + DBG_dump("_natd_hash: icookie=", icookie, COOKIE_SIZE); + DBG_dump("_natd_hash: rcookie=", rcookie, COOKIE_SIZE); + switch (addrtypeof(ip)) { + case AF_INET: + DBG_dump("_natd_hash: ip=", &ip->u.v4.sin_addr.s_addr + , sizeof(ip->u.v4.sin_addr.s_addr)); + break; + } + DBG_log("_natd_hash: port=%d", port); + DBG_dump("_natd_hash: hash=", hash, hasher->hash_digest_len); + ); +#endif +} + +/* Add NAT-Traversal VIDs (supported ones) + * used when we are Initiator + */ +bool nat_traversal_add_vid(u_int8_t np, pb_stream *outs) +{ + bool r = TRUE; + + if (nat_traversal_support_port_floating) + { + u_int8_t last_np = nat_traversal_support_non_ike ? + ISAKMP_NEXT_VID : np; + + if (r) + r = out_vendorid(ISAKMP_NEXT_VID, outs, VID_NATT_RFC); + if (r) + r = out_vendorid(ISAKMP_NEXT_VID, outs, VID_NATT_IETF_03); + if (r) + r = out_vendorid(ISAKMP_NEXT_VID, outs, VID_NATT_IETF_02); + if (r) + r = out_vendorid(last_np, outs, VID_NATT_IETF_02_N); + } + if (nat_traversal_support_non_ike) + { + if (r) + r = out_vendorid(np, outs, VID_NATT_IETF_00); + } + return r; +} + +u_int32_t nat_traversal_vid_to_method(unsigned short nat_t_vid) +{ + switch (nat_t_vid) + { + case VID_NATT_IETF_00: + return LELEM(NAT_TRAVERSAL_IETF_00_01); + case VID_NATT_IETF_02: + case VID_NATT_IETF_02_N: + case VID_NATT_IETF_03: + return LELEM(NAT_TRAVERSAL_IETF_02_03); + case VID_NATT_RFC: + return LELEM(NAT_TRAVERSAL_RFC); + } + return 0; +} + +void nat_traversal_natd_lookup(struct msg_digest *md) +{ + char hash[MAX_DIGEST_LEN]; + struct payload_digest *p; + struct state *st = md->st; + int i; + + if (!st || !md->iface || !st->st_oakley.hasher) + { + loglog(RC_LOG_SERIOUS, "NAT-Traversal: assert failed %s:%d" + , __FILE__, __LINE__); + return; + } + + /** Count NAT-D **/ + for (p = md->chain[ISAKMP_NEXT_NATD_RFC], i=0; p != NULL; p = p->next, i++); + + /* + * We need at least 2 NAT-D (1 for us, many for peer) + */ + if (i < 2) + { + loglog(RC_LOG_SERIOUS, + "NAT-Traversal: Only %d NAT-D - Aborting NAT-Traversal negociation", i); + st->nat_traversal = 0; + return; + } + + /* + * First one with my IP & port + */ + p = md->chain[ISAKMP_NEXT_NATD_RFC]; + _natd_hash(st->st_oakley.hasher, hash, st->st_icookie, st->st_rcookie, + &(md->iface->addr), ntohs(st->st_connection->spd.this.host_port)); + + if (!(pbs_left(&p->pbs) == st->st_oakley.hasher->hash_digest_len && + memcmp(p->pbs.cur, hash, st->st_oakley.hasher->hash_digest_len) == 0)) + { +#ifdef NAT_D_DEBUG + DBG(DBG_NATT, + DBG_log("NAT_TRAVERSAL_NAT_BHND_ME"); + DBG_dump("expected NAT-D:", hash + , st->st_oakley.hasher->hash_digest_len); + DBG_dump("received NAT-D:", p->pbs.cur, pbs_left(&p->pbs)); + ) +#endif + st->nat_traversal |= LELEM(NAT_TRAVERSAL_NAT_BHND_ME); + } + + /* + * The others with sender IP & port + */ + _natd_hash(st->st_oakley.hasher, hash, st->st_icookie, st->st_rcookie, + &(md->sender), ntohs(md->sender_port)); + for (p = p->next, i=0 ; p != NULL; p = p->next) + { + if (pbs_left(&p->pbs) == st->st_oakley.hasher->hash_digest_len && + memcmp(p->pbs.cur, hash, st->st_oakley.hasher->hash_digest_len) == 0) + { + i++; + } + } + if (!i) + { +#ifdef NAT_D_DEBUG + DBG(DBG_NATT, + DBG_log("NAT_TRAVERSAL_NAT_BHND_PEER"); + DBG_dump("expected NAT-D:", hash + , st->st_oakley.hasher->hash_digest_len); + p = md->chain[ISAKMP_NEXT_NATD_RFC]; + for (p = p->next, i=0 ; p != NULL; p = p->next) + { + DBG_dump("received NAT-D:", p->pbs.cur, pbs_left(&p->pbs)); + } + ) +#endif + st->nat_traversal |= LELEM(NAT_TRAVERSAL_NAT_BHND_PEER); + } +#ifdef FORCE_NAT_TRAVERSAL + st->nat_traversal |= LELEM(NAT_TRAVERSAL_NAT_BHND_PEER); + st->nat_traversal |= LELEM(NAT_TRAVERSAL_NAT_BHND_ME); +#endif +} + +bool nat_traversal_add_natd(u_int8_t np, pb_stream *outs, + struct msg_digest *md) +{ + char hash[MAX_DIGEST_LEN]; + struct state *st = md->st; + + if (!st || !st->st_oakley.hasher) + { + loglog(RC_LOG_SERIOUS, "NAT-Traversal: assert failed %s:%d" + , __FILE__, __LINE__); + return FALSE; + } + + DBG(DBG_EMITTING, + DBG_log("sending NATD payloads") + ) + + /* + * First one with sender IP & port + */ + _natd_hash(st->st_oakley.hasher, hash, st->st_icookie, + is_zero_cookie(st->st_rcookie) ? md->hdr.isa_rcookie : st->st_rcookie, + &(md->sender), +#ifdef FORCE_NAT_TRAVERSAL + 0 +#else + ntohs(md->sender_port) +#endif + ); + if (!out_generic_raw((st->nat_traversal & NAT_T_WITH_RFC_VALUES + ? ISAKMP_NEXT_NATD_RFC : ISAKMP_NEXT_NATD_DRAFTS), &isakmp_nat_d, outs, + hash, st->st_oakley.hasher->hash_digest_len, "NAT-D")) + { + return FALSE; + } + + /* + * Second one with my IP & port + */ + _natd_hash(st->st_oakley.hasher, hash, st->st_icookie, + is_zero_cookie(st->st_rcookie) ? md->hdr.isa_rcookie : st->st_rcookie, + &(md->iface->addr), +#ifdef FORCE_NAT_TRAVERSAL + 0 +#else + ntohs(st->st_connection->spd.this.host_port) +#endif + ); + return (out_generic_raw(np, &isakmp_nat_d, outs, + hash, st->st_oakley.hasher->hash_digest_len, "NAT-D")); +} + +/* + * nat_traversal_natoa_lookup() + * + * Look for NAT-OA in message + */ +void nat_traversal_natoa_lookup(struct msg_digest *md) +{ + struct payload_digest *p; + struct state *st = md->st; + int i; + ip_address ip; + + if (!st || !md->iface) + { + loglog(RC_LOG_SERIOUS, "NAT-Traversal: assert failed %s:%d" + , __FILE__, __LINE__); + return; + } + + /* Initialize NAT-OA */ + anyaddr(AF_INET, &st->nat_oa); + + /* Count NAT-OA **/ + for (p = md->chain[ISAKMP_NEXT_NATOA_RFC], i=0; p != NULL; p = p->next, i++); + + DBG(DBG_NATT, + DBG_log("NAT-Traversal: received %d NAT-OA.", i) + ) + + if (i == 0) + return; + + if (!(st->nat_traversal & LELEM(NAT_TRAVERSAL_NAT_BHND_PEER))) + { + loglog(RC_LOG_SERIOUS, "NAT-Traversal: received %d NAT-OA. " + "ignored because peer is not NATed", i); + return; + } + + if (i > 1) + { + loglog(RC_LOG_SERIOUS, "NAT-Traversal: received %d NAT-OA. " + "using first, ignoring others", i); + } + + /* Take first */ + p = md->chain[ISAKMP_NEXT_NATOA_RFC]; + + DBG(DBG_PARSING, + DBG_dump("NAT-OA:", p->pbs.start, pbs_room(&p->pbs)); + ); + + switch (p->payload.nat_oa.isanoa_idtype) + { + case ID_IPV4_ADDR: + if (pbs_left(&p->pbs) == sizeof(struct in_addr)) + { + initaddr(p->pbs.cur, pbs_left(&p->pbs), AF_INET, &ip); + } + else + { + loglog(RC_LOG_SERIOUS, "NAT-Traversal: received IPv4 NAT-OA " + "with invalid IP size (%d)", (int)pbs_left(&p->pbs)); + return; + } + break; + case ID_IPV6_ADDR: + if (pbs_left(&p->pbs) == sizeof(struct in6_addr)) + { + initaddr(p->pbs.cur, pbs_left(&p->pbs), AF_INET6, &ip); + } + else + { + loglog(RC_LOG_SERIOUS, "NAT-Traversal: received IPv6 NAT-OA " + "with invalid IP size (%d)", (int)pbs_left(&p->pbs)); + return; + } + break; + default: + loglog(RC_LOG_SERIOUS, "NAT-Traversal: " + "invalid ID Type (%d) in NAT-OA - ignored", + p->payload.nat_oa.isanoa_idtype); + return; + } + + DBG(DBG_NATT, + { + char ip_t[ADDRTOT_BUF]; + addrtot(&ip, 0, ip_t, sizeof(ip_t)); + + DBG_log("received NAT-OA: %s", ip_t); + } + ) + + if (isanyaddr(&ip)) + loglog(RC_LOG_SERIOUS, "NAT-Traversal: received %%any NAT-OA..."); + else + st->nat_oa = ip; +} + +bool nat_traversal_add_natoa(u_int8_t np, pb_stream *outs, + struct state *st) +{ + struct isakmp_nat_oa natoa; + pb_stream pbs; + unsigned char ip_val[sizeof(struct in6_addr)]; + size_t ip_len = 0; + ip_address *ip; + + if ((!st) || (!st->st_connection)) + { + loglog(RC_LOG_SERIOUS, "NAT-Traversal: assert failed %s:%d" + , __FILE__, __LINE__); + return FALSE; + } + ip = &(st->st_connection->spd.this.host_addr); + + memset(&natoa, 0, sizeof(natoa)); + natoa.isanoa_np = np; + + switch (addrtypeof(ip)) + { + case AF_INET: + ip_len = sizeof(ip->u.v4.sin_addr.s_addr); + memcpy(ip_val, &ip->u.v4.sin_addr.s_addr, ip_len); + natoa.isanoa_idtype = ID_IPV4_ADDR; + break; + case AF_INET6: + ip_len = sizeof(ip->u.v6.sin6_addr.s6_addr); + memcpy(ip_val, &ip->u.v6.sin6_addr.s6_addr, ip_len); + natoa.isanoa_idtype = ID_IPV6_ADDR; + break; + default: + loglog(RC_LOG_SERIOUS, "NAT-Traversal: " + "invalid addrtypeof()=%d", addrtypeof(ip)); + return FALSE; + } + + if (!out_struct(&natoa, &isakmp_nat_oa, outs, &pbs)) + return FALSE; + + if (!out_raw(ip_val, ip_len, &pbs, "NAT-OA")) + return FALSE; + + DBG(DBG_NATT, + DBG_dump("NAT-OA (S):", ip_val, ip_len) + ) + + close_output_pbs(&pbs); + return TRUE; +} + +void nat_traversal_show_result (u_int32_t nt, u_int16_t sport) +{ + const char *mth = NULL, *rslt = NULL; + + switch (nt & NAT_TRAVERSAL_METHOD) + { + case LELEM(NAT_TRAVERSAL_IETF_00_01): + mth = natt_type_bitnames[0]; + break; + case LELEM(NAT_TRAVERSAL_IETF_02_03): + mth = natt_type_bitnames[1]; + break; + case LELEM(NAT_TRAVERSAL_RFC): + mth = natt_type_bitnames[2]; + break; + } + + switch (nt & NAT_T_DETECTED) + { + case 0: + rslt = "no NAT detected"; + break; + case LELEM(NAT_TRAVERSAL_NAT_BHND_ME): + rslt = "i am NATed"; + break; + case LELEM(NAT_TRAVERSAL_NAT_BHND_PEER): + rslt = "peer is NATed"; + break; + case LELEM(NAT_TRAVERSAL_NAT_BHND_ME) | LELEM(NAT_TRAVERSAL_NAT_BHND_PEER): + rslt = "both are NATed"; + break; + } + + loglog(RC_LOG_SERIOUS, + "NAT-Traversal: Result using %s: %s", + mth ? mth : "unknown method", + rslt ? rslt : "unknown result" + ); + + if ((nt & LELEM(NAT_TRAVERSAL_NAT_BHND_PEER)) + && (sport == IKE_UDP_PORT) + && ((nt & NAT_T_WITH_PORT_FLOATING)==0)) + { + loglog(RC_LOG_SERIOUS, + "Warning: peer is NATed but source port is still udp/%d. " + "Ipsec-passthrough NAT device suspected -- NAT-T may not work.", + IKE_UDP_PORT + ); + } +} + +int nat_traversal_espinudp_socket (int sk, u_int32_t type) +{ + int r = setsockopt(sk, SOL_UDP, UDP_ESPINUDP, &type, sizeof(type)); + + if (r < 0 && errno == ENOPROTOOPT) + { + loglog(RC_LOG_SERIOUS, + "NAT-Traversal: ESPINUDP(%d) not supported by kernel -- " + "NAT-T disabled", type); + disable_nat_traversal(type); + } + return r; +} + +void nat_traversal_new_ka_event (void) +{ + if (_ka_evt) + return; /* event already scheduled */ + + event_schedule(EVENT_NAT_T_KEEPALIVE, _kap, NULL); + _ka_evt = 1; +} + +static void nat_traversal_send_ka (struct state *st) +{ + static unsigned char ka_payload = 0xff; + chunk_t sav; + + DBG(DBG_NATT, + DBG_log("ka_event: send NAT-KA to %s:%d", + ip_str(&st->st_connection->spd.that.host_addr), + st->st_connection->spd.that.host_port); + ) + + /* save state chunk */ + setchunk(sav, st->st_tpacket.ptr, st->st_tpacket.len); + + /* send keep alive */ + setchunk(st->st_tpacket, &ka_payload, 1); + send_packet(st, "NAT-T Keep Alive"); + + /* restore state chunk */ + setchunk(st->st_tpacket, sav.ptr, sav.len); +} + +/** + * Find ISAKMP States with NAT-T and send keep-alive + */ +static void nat_traversal_ka_event_state (struct state *st, void *data) +{ + unsigned int *_kap_st = (unsigned int *)data; + const struct connection *c = st->st_connection; + + if (!c) + return; + + if ((st->st_state == STATE_MAIN_R3 || st->st_state == STATE_MAIN_I4) + && (st->nat_traversal & NAT_T_DETECTED) + && ((st->nat_traversal & LELEM(NAT_TRAVERSAL_NAT_BHND_ME)) || _force_ka)) + { + /* + * - ISAKMP established + * - NAT-Traversal detected + * - NAT-KeepAlive needed (we are NATed) + */ + if (c->newest_isakmp_sa != st->st_serialno) + { + /* + * if newest is also valid, ignore this one, we will only use + * newest. + */ + struct state *st_newest; + + st_newest = state_with_serialno(c->newest_isakmp_sa); + if (st_newest + && (st_newest->st_state == STATE_MAIN_R3 || st_newest->st_state == STATE_MAIN_I4) + && (st_newest->nat_traversal & NAT_T_DETECTED) + && ((st_newest->nat_traversal & LELEM(NAT_TRAVERSAL_NAT_BHND_ME)) || _force_ka)) + { + return; + } + } + set_cur_state(st); + nat_traversal_send_ka(st); + reset_cur_state(); + (*_kap_st)++; + } +} + +void nat_traversal_ka_event (void) +{ + unsigned int _kap_st = 0; + + _ka_evt = 0; /* ready to be reschedule */ + + for_each_state((void *)nat_traversal_ka_event_state, &_kap_st); + + /* if there are still states who needs Keep-Alive, schedule new event */ + if (_kap_st) + nat_traversal_new_ka_event(); +} + +struct _new_mapp_nfo { + ip_address addr; + u_int16_t sport, dport; +}; + +static void nat_traversal_find_new_mapp_state (struct state *st, void *data) +{ + struct connection *c = st->st_connection; + struct _new_mapp_nfo *nfo = (struct _new_mapp_nfo *)data; + + if (c != NULL + && sameaddr(&c->spd.that.host_addr, &(nfo->addr)) + && c->spd.that.host_port == nfo->sport) + { + + /* change host port */ + c->spd.that.host_port = nfo->dport; + + if (IS_IPSEC_SA_ESTABLISHED(st->st_state) + || IS_ONLY_INBOUND_IPSEC_SA_ESTABLISHED(st->st_state)) + { + if (!update_ipsec_sa(st)) + { + /* + * If ipsec update failed, restore old port or we'll + * not be able to update anymore. + */ + c->spd.that.host_port = nfo->sport; + } + } + } +} + +static int nat_traversal_new_mapping(const ip_address *src, u_int16_t sport, + const ip_address *dst, u_int16_t dport) +{ + char srca[ADDRTOT_BUF], dsta[ADDRTOT_BUF]; + struct _new_mapp_nfo nfo; + + addrtot(src, 0, srca, ADDRTOT_BUF); + addrtot(dst, 0, dsta, ADDRTOT_BUF); + + if (!sameaddr(src, dst)) + { + loglog(RC_LOG_SERIOUS, "nat_traversal_new_mapping: " + "address change currently not supported [%s:%d,%s:%d]", + srca, sport, dsta, dport); + return -1; + } + + if (sport == dport) + { + /* no change */ + return 0; + } + + DBG_log("NAT-T: new mapping %s:%d/%d)", srca, sport, dport); + + nfo.addr = *src; + nfo.sport = sport; + nfo.dport = dport; + + for_each_state((void *)nat_traversal_find_new_mapp_state, &nfo); + + return 0; +} + +void nat_traversal_change_port_lookup(struct msg_digest *md, struct state *st) +{ + struct connection *c = st ? st->st_connection : NULL; + struct iface *i = NULL; + + if ((st == NULL) || (c == NULL)) + return; + + if (md) + { + /* + * If source port has changed, update (including other states and + * established kernel SA) + */ + if (c->spd.that.host_port != md->sender_port) + { + nat_traversal_new_mapping(&c->spd.that.host_addr, c->spd.that.host_port, + &c->spd.that.host_addr, md->sender_port); + } + + /* + * If interface type has changed, update local port (500/4500) + */ + if ((c->spd.this.host_port == NAT_T_IKE_FLOAT_PORT && !md->iface->ike_float) + || (c->spd.this.host_port != NAT_T_IKE_FLOAT_PORT && md->iface->ike_float)) + { + c->spd.this.host_port = (md->iface->ike_float) + ? NAT_T_IKE_FLOAT_PORT : pluto_port; + + DBG(DBG_NATT, + DBG_log("NAT-T: updating local port to %d", c->spd.this.host_port); + ); + } + } + + /* + * If we're initiator and NAT-T (with port floating) is detected, we + * need to change port (MAIN_I3 or QUICK_I1) + */ + if ((st->st_state == STATE_MAIN_I3 || st->st_state == STATE_QUICK_I1) + && (st->nat_traversal & NAT_T_WITH_PORT_FLOATING) + && (st->nat_traversal & NAT_T_DETECTED) + && (c->spd.this.host_port != NAT_T_IKE_FLOAT_PORT)) + { + DBG(DBG_NATT, + DBG_log("NAT-T: floating to port %d", NAT_T_IKE_FLOAT_PORT); + ) + c->spd.this.host_port = NAT_T_IKE_FLOAT_PORT; + c->spd.that.host_port = NAT_T_IKE_FLOAT_PORT; + /* + * Also update pending connections or they will be deleted if uniqueids + * option is set. + */ + update_pending(st, st); + } + + /* + * Find valid interface according to local port (500/4500) + */ + if ((c->spd.this.host_port == NAT_T_IKE_FLOAT_PORT && !c->interface->ike_float) + || (c->spd.this.host_port != NAT_T_IKE_FLOAT_PORT && c->interface->ike_float)) + { + for (i = interfaces; i != NULL; i = i->next) + { + if (sameaddr(&c->interface->addr, &i->addr) + && i->ike_float != c->interface->ike_float) + { + DBG(DBG_NATT, + DBG_log("NAT-T: using interface %s:%d", i->rname, + i->ike_float ? NAT_T_IKE_FLOAT_PORT : pluto_port); + ) + c->interface = i; + break; + } + } + } +} + +struct _new_klips_mapp_nfo { + struct sadb_sa *sa; + ip_address src, dst; + u_int16_t sport, dport; +}; + +static void nat_t_new_klips_mapp (struct state *st, void *data) +{ + struct connection *c = st->st_connection; + struct _new_klips_mapp_nfo *nfo = (struct _new_klips_mapp_nfo *)data; + + if (c != NULL && st->st_esp.present + && sameaddr(&c->spd.that.host_addr, &(nfo->src)) + && st->st_esp.our_spi == nfo->sa->sadb_sa_spi) + { + nat_traversal_new_mapping(&c->spd.that.host_addr, c->spd.that.host_port, + &(nfo->dst), nfo->dport); + } +} + +void process_pfkey_nat_t_new_mapping( + struct sadb_msg *msg __attribute__ ((unused)), + struct sadb_ext *extensions[SADB_EXT_MAX + 1]) +{ + struct _new_klips_mapp_nfo nfo; + struct sadb_address *srcx = (void *) extensions[SADB_EXT_ADDRESS_SRC]; + struct sadb_address *dstx = (void *) extensions[SADB_EXT_ADDRESS_DST]; + struct sockaddr *srca, *dsta; + err_t ugh = NULL; + + nfo.sa = (void *) extensions[SADB_EXT_SA]; + + if (!nfo.sa || !srcx || !dstx) + { + plog("SADB_X_NAT_T_NEW_MAPPING message from KLIPS malformed: " + "got NULL params"); + return; + } + + srca = ((struct sockaddr *)(void *)&srcx[1]); + dsta = ((struct sockaddr *)(void *)&dstx[1]); + + if (srca->sa_family != AF_INET || dsta->sa_family != AF_INET) + { + ugh = "only AF_INET supported"; + } + else + { + char text_said[SATOT_BUF]; + char _srca[ADDRTOT_BUF], _dsta[ADDRTOT_BUF]; + ip_said said; + + initaddr((const void *) &((const struct sockaddr_in *)srca)->sin_addr, + sizeof(((const struct sockaddr_in *)srca)->sin_addr), + srca->sa_family, &(nfo.src)); + nfo.sport = ntohs(((const struct sockaddr_in *)srca)->sin_port); + initaddr((const void *) &((const struct sockaddr_in *)dsta)->sin_addr, + sizeof(((const struct sockaddr_in *)dsta)->sin_addr), + dsta->sa_family, &(nfo.dst)); + nfo.dport = ntohs(((const struct sockaddr_in *)dsta)->sin_port); + + DBG(DBG_NATT, + initsaid(&nfo.src, nfo.sa->sadb_sa_spi, SA_ESP, &said); + satot(&said, 0, text_said, SATOT_BUF); + addrtot(&nfo.src, 0, _srca, ADDRTOT_BUF); + addrtot(&nfo.dst, 0, _dsta, ADDRTOT_BUF); + DBG_log("new klips mapping %s %s:%d %s:%d", + text_said, _srca, nfo.sport, _dsta, nfo.dport); + ) + + for_each_state((void *)nat_t_new_klips_mapp, &nfo); + } + + if (ugh != NULL) + plog("SADB_X_NAT_T_NEW_MAPPING message from KLIPS malformed: %s", ugh); +} + diff --git a/src/pluto/nat_traversal.h b/src/pluto/nat_traversal.h new file mode 100644 index 000000000..71222c54c --- /dev/null +++ b/src/pluto/nat_traversal.h @@ -0,0 +1,154 @@ +/* FreeS/WAN NAT-Traversal + * Copyright (C) 2002-2003 Mathieu Lafon - Arkoon Network Security + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: nat_traversal.h,v 1.4 2004/07/27 21:11:30 as Exp $ + */ + +#ifndef _NAT_TRAVERSAL_H +#define _NAT_TRAVERSAL_H + +#include "packet.h" + +#define NAT_TRAVERSAL_IETF_00_01 1 +#define NAT_TRAVERSAL_IETF_02_03 2 +#define NAT_TRAVERSAL_RFC 3 + +#define NAT_TRAVERSAL_NAT_BHND_ME 30 +#define NAT_TRAVERSAL_NAT_BHND_PEER 31 + +#define NAT_TRAVERSAL_METHOD (0xffffffff - LELEM(30) - LELEM(31)) + +/** + * NAT-Traversal methods which need NAT-D + */ +#define NAT_T_WITH_NATD \ + ( LELEM(NAT_TRAVERSAL_IETF_00_01) | LELEM(NAT_TRAVERSAL_IETF_02_03) | \ + LELEM(NAT_TRAVERSAL_RFC) ) +/** + * NAT-Traversal methods which need NAT-OA + */ +#define NAT_T_WITH_NATOA \ + ( LELEM(NAT_TRAVERSAL_IETF_00_01) | LELEM(NAT_TRAVERSAL_IETF_02_03) | \ + LELEM(NAT_TRAVERSAL_RFC) ) +/** + * NAT-Traversal methods which use NAT-KeepAlive + */ +#define NAT_T_WITH_KA \ + ( LELEM(NAT_TRAVERSAL_IETF_00_01) | LELEM(NAT_TRAVERSAL_IETF_02_03) | \ + LELEM(NAT_TRAVERSAL_RFC) ) +/** + * NAT-Traversal methods which use floating port + */ +#define NAT_T_WITH_PORT_FLOATING \ + ( LELEM(NAT_TRAVERSAL_IETF_02_03) | LELEM(NAT_TRAVERSAL_RFC) ) + +/** + * NAT-Traversal methods which use officials values (RFC) + */ +#define NAT_T_WITH_RFC_VALUES \ + ( LELEM(NAT_TRAVERSAL_RFC) ) + +/** + * NAT-Traversal detected + */ +#define NAT_T_DETECTED \ + ( LELEM(NAT_TRAVERSAL_NAT_BHND_ME) | LELEM(NAT_TRAVERSAL_NAT_BHND_PEER) ) + +/** + * NAT-T Port Floating + */ +#define NAT_T_IKE_FLOAT_PORT 4500 + +void init_nat_traversal (bool activate, unsigned int keep_alive_period, + bool fka, bool spf); + +extern bool nat_traversal_enabled; +extern bool nat_traversal_support_non_ike; +extern bool nat_traversal_support_port_floating; + +/** + * NAT-D + */ +void nat_traversal_natd_lookup(struct msg_digest *md); +#ifndef PB_STREAM_UNDEFINED +bool nat_traversal_add_natd(u_int8_t np, pb_stream *outs, + struct msg_digest *md); +#endif + +/** + * NAT-OA + */ +void nat_traversal_natoa_lookup(struct msg_digest *md); +#ifndef PB_STREAM_UNDEFINED +bool nat_traversal_add_natoa(u_int8_t np, pb_stream *outs, + struct state *st); +#endif + +/** + * NAT-keep_alive + */ +void nat_traversal_new_ka_event (void); +void nat_traversal_ka_event (void); + +void nat_traversal_show_result (u_int32_t nt, u_int16_t sport); + +int nat_traversal_espinudp_socket (int sk, u_int32_t type); + +/** + * Vendor ID + */ +#ifndef PB_STREAM_UNDEFINED +bool nat_traversal_add_vid(u_int8_t np, pb_stream *outs); +#endif +u_int32_t nat_traversal_vid_to_method(unsigned short nat_t_vid); + +void nat_traversal_change_port_lookup(struct msg_digest *md, struct state *st); + +/** + * New NAT mapping + */ +#ifdef __PFKEY_V2_H +void process_pfkey_nat_t_new_mapping( + struct sadb_msg *, + struct sadb_ext *[SADB_EXT_MAX + 1]); +#endif + +/** + * IKE port floating + */ +bool +nat_traversal_port_float(struct state *st, struct msg_digest *md, bool in); + +/** + * Encapsulation mode macro (see demux.c) + */ +#define NAT_T_ENCAPSULATION_MODE(st,nat_t_policy) ( \ + ((st)->nat_traversal & NAT_T_DETECTED) \ + ? ( ((nat_t_policy) & POLICY_TUNNEL) \ + ? ( ((st)->nat_traversal & NAT_T_WITH_RFC_VALUES) \ + ? (ENCAPSULATION_MODE_UDP_TUNNEL_RFC) \ + : (ENCAPSULATION_MODE_UDP_TUNNEL_DRAFTS) \ + ) \ + : ( ((st)->nat_traversal & NAT_T_WITH_RFC_VALUES) \ + ? (ENCAPSULATION_MODE_UDP_TRANSPORT_RFC) \ + : (ENCAPSULATION_MODE_UDP_TRANSPORT_DRAFTS) \ + ) \ + ) \ + : ( ((st)->st_policy & POLICY_TUNNEL) \ + ? (ENCAPSULATION_MODE_TUNNEL) \ + : (ENCAPSULATION_MODE_TRANSPORT) \ + ) \ + ) + +#endif /* _NAT_TRAVERSAL_H */ + diff --git a/src/pluto/ocsp.c b/src/pluto/ocsp.c new file mode 100644 index 000000000..a338be446 --- /dev/null +++ b/src/pluto/ocsp.c @@ -0,0 +1,1568 @@ +/* Support of the Online Certificate Status Protocol (OCSP) + * Copyright (C) 2003 Christoph Gysin, Simon Zwahlen + * Zuercher Hochschule Winterthur + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + */ + +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <freeswan.h> +#include <ipsec_policy.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "x509.h" +#include "crl.h" +#include "ca.h" +#include "rnd.h" +#include "asn1.h" +#include "certs.h" +#include "smartcard.h" +#include "oid.h" +#include "whack.h" +#include "pkcs1.h" +#include "keys.h" +#include "fetch.h" +#include "ocsp.h" + +#define NONCE_LENGTH 16 + +static const char *const cert_status_names[] = { + "good", + "revoked", + "unknown", + "undefined" +}; + + +static const char *const response_status_names[] = { + "successful", + "malformed request", + "internal error", + "try later", + "signature required", + "unauthorized" +}; + +/* response container */ +typedef struct response response_t; + +struct response { + chunk_t tbs; + chunk_t responder_id_name; + chunk_t responder_id_key; + time_t produced_at; + chunk_t responses; + chunk_t nonce; + int algorithm; + chunk_t signature; +}; + +const response_t empty_response = { + { NULL, 0 } , /* tbs */ + { NULL, 0 } , /* responder_id_name */ + { NULL, 0 } , /* responder_id_key */ + UNDEFINED_TIME, /* produced_at */ + { NULL, 0 } , /* single_response */ + { NULL, 0 } , /* nonce */ + OID_UNKNOWN , /* signature_algorithm */ + { NULL, 0 } /* signature */ +}; + +/* single response container */ +typedef struct single_response single_response_t; + +struct single_response { + single_response_t *next; + int hash_algorithm; + chunk_t issuer_name_hash; + chunk_t issuer_key_hash; + chunk_t serialNumber; + cert_status_t status; + time_t revocationTime; + crl_reason_t revocationReason; + time_t thisUpdate; + time_t nextUpdate; +}; + +const single_response_t empty_single_response = { + NULL , /* *next */ + OID_UNKNOWN , /* hash_algorithm */ + { NULL, 0 } , /* issuer_name_hash */ + { NULL, 0 } , /* issuer_key_hash */ + { NULL, 0 } , /* serial_number */ + CERT_UNDEFINED , /* status */ + UNDEFINED_TIME , /* revocationTime */ + REASON_UNSPECIFIED, /* revocationReason */ + UNDEFINED_TIME , /* this_update */ + UNDEFINED_TIME /* next_update */ +}; + + +/* list of single requests */ +typedef struct request_list request_list_t; +struct request_list { + chunk_t request; + request_list_t *next; +}; + +/* some OCSP specific prefabricated ASN.1 constants */ + +static u_char ASN1_nonce_oid_str[] = { + 0x06, 0x09, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x02 +}; + +static const chunk_t ASN1_nonce_oid = strchunk(ASN1_nonce_oid_str); + +static u_char ASN1_response_oid_str[] = { + 0x06, 0x09, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x04 +}; + +static const chunk_t ASN1_response_oid = strchunk(ASN1_response_oid_str); + +static u_char ASN1_response_content_str[] = { + 0x04, 0x0D, + 0x30, 0x0B, + 0x06, 0x09, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01 +}; + +static const chunk_t ASN1_response_content = strchunk(ASN1_response_content_str); + +/* default OCSP uri */ +static chunk_t ocsp_default_uri; + +/* ocsp cache: pointer to first element */ +static ocsp_location_t *ocsp_cache = NULL; + +/* static temporary storage for ocsp requestor information */ +static x509cert_t *ocsp_requestor_cert = NULL; + +static smartcard_t *ocsp_requestor_sc = NULL; + +static const struct RSA_private_key *ocsp_requestor_pri = NULL; + +/* asn.1 definitions for parsing */ + +static const asn1Object_t ocspResponseObjects[] = { + { 0, "OCSPResponse", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */ + { 1, "responseStatus", ASN1_ENUMERATED, ASN1_BODY }, /* 1 */ + { 1, "responseBytesContext", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 2 */ + { 2, "responseBytes", ASN1_SEQUENCE, ASN1_NONE }, /* 3 */ + { 3, "responseType", ASN1_OID, ASN1_BODY }, /* 4 */ + { 3, "response", ASN1_OCTET_STRING, ASN1_BODY }, /* 5 */ + { 1, "end opt", ASN1_EOC, ASN1_END } /* 6 */ +}; + +#define OCSP_RESPONSE_STATUS 1 +#define OCSP_RESPONSE_TYPE 4 +#define OCSP_RESPONSE 5 +#define OCSP_RESPONSE_ROOF 7 + +static const asn1Object_t basicResponseObjects[] = { + { 0, "BasicOCSPResponse", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */ + { 1, "tbsResponseData", ASN1_SEQUENCE, ASN1_OBJ }, /* 1 */ + { 2, "versionContext", ASN1_CONTEXT_C_0, ASN1_NONE | + ASN1_DEF }, /* 2 */ + { 3, "version", ASN1_INTEGER, ASN1_BODY }, /* 3 */ + { 2, "responderIdContext", ASN1_CONTEXT_C_1, ASN1_OPT }, /* 4 */ + { 3, "responderIdByName", ASN1_SEQUENCE, ASN1_OBJ }, /* 5 */ + { 2, "end choice", ASN1_EOC, ASN1_END }, /* 6 */ + { 2, "responderIdContext", ASN1_CONTEXT_C_2, ASN1_OPT }, /* 7 */ + { 3, "responderIdByKey", ASN1_OCTET_STRING, ASN1_BODY }, /* 8 */ + { 2, "end choice", ASN1_EOC, ASN1_END }, /* 9 */ + { 2, "producedAt", ASN1_GENERALIZEDTIME, ASN1_BODY }, /* 10 */ + { 2, "responses", ASN1_SEQUENCE, ASN1_OBJ }, /* 11 */ + { 2, "responseExtensionsContext", ASN1_CONTEXT_C_1, ASN1_OPT }, /* 12 */ + { 3, "responseExtensions", ASN1_SEQUENCE, ASN1_LOOP }, /* 13 */ + { 4, "extension", ASN1_SEQUENCE, ASN1_NONE }, /* 14 */ + { 5, "extnID", ASN1_OID, ASN1_BODY }, /* 15 */ + { 5, "critical", ASN1_BOOLEAN, ASN1_BODY | + ASN1_DEF }, /* 16 */ + { 5, "extnValue", ASN1_OCTET_STRING, ASN1_BODY }, /* 17 */ + { 4, "end loop", ASN1_EOC, ASN1_END }, /* 18 */ + { 2, "end opt", ASN1_EOC, ASN1_END }, /* 19 */ + { 1, "signatureAlgorithm", ASN1_EOC, ASN1_RAW }, /* 20 */ + { 1, "signature", ASN1_BIT_STRING, ASN1_BODY }, /* 21 */ + { 1, "certsContext", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 22 */ + { 2, "certs", ASN1_SEQUENCE, ASN1_LOOP }, /* 23 */ + { 3, "certificate", ASN1_SEQUENCE, ASN1_OBJ }, /* 24 */ + { 2, "end loop", ASN1_EOC, ASN1_END }, /* 25 */ + { 1, "end opt", ASN1_EOC, ASN1_END } /* 26 */ +}; + +#define BASIC_RESPONSE_TBS_DATA 1 +#define BASIC_RESPONSE_VERSION 3 +#define BASIC_RESPONSE_ID_BY_NAME 5 +#define BASIC_RESPONSE_ID_BY_KEY 8 +#define BASIC_RESPONSE_PRODUCED_AT 10 +#define BASIC_RESPONSE_RESPONSES 11 +#define BASIC_RESPONSE_EXT_ID 15 +#define BASIC_RESPONSE_CRITICAL 16 +#define BASIC_RESPONSE_EXT_VALUE 17 +#define BASIC_RESPONSE_ALGORITHM 20 +#define BASIC_RESPONSE_SIGNATURE 21 +#define BASIC_RESPONSE_CERTIFICATE 24 +#define BASIC_RESPONSE_ROOF 27 + +static const asn1Object_t responsesObjects[] = { + { 0, "responses", ASN1_SEQUENCE, ASN1_LOOP }, /* 0 */ + { 1, "singleResponse", ASN1_EOC, ASN1_RAW }, /* 1 */ + { 0, "end loop", ASN1_EOC, ASN1_END } /* 2 */ +}; + +#define RESPONSES_SINGLE_RESPONSE 1 +#define RESPONSES_ROOF 3 + +static const asn1Object_t singleResponseObjects[] = { + { 0, "singleResponse", ASN1_SEQUENCE, ASN1_BODY }, /* 0 */ + { 1, "certID", ASN1_SEQUENCE, ASN1_NONE }, /* 1 */ + { 2, "algorithm", ASN1_EOC, ASN1_RAW }, /* 2 */ + { 2, "issuerNameHash", ASN1_OCTET_STRING, ASN1_BODY }, /* 3 */ + { 2, "issuerKeyHash", ASN1_OCTET_STRING, ASN1_BODY }, /* 4 */ + { 2, "serialNumber", ASN1_INTEGER, ASN1_BODY }, /* 5 */ + { 1, "certStatusGood", ASN1_CONTEXT_S_0, ASN1_OPT }, /* 6 */ + { 1, "end opt", ASN1_EOC, ASN1_END }, /* 7 */ + { 1, "certStatusRevoked", ASN1_CONTEXT_C_1, ASN1_OPT }, /* 8 */ + { 2, "revocationTime", ASN1_GENERALIZEDTIME, ASN1_BODY }, /* 9 */ + { 2, "revocationReason", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 10 */ + { 3, "crlReason", ASN1_ENUMERATED, ASN1_BODY }, /* 11 */ + { 2, "end opt", ASN1_EOC, ASN1_END }, /* 12 */ + { 1, "end opt", ASN1_EOC, ASN1_END }, /* 13 */ + { 1, "certStatusUnknown", ASN1_CONTEXT_S_2, ASN1_OPT }, /* 14 */ + { 1, "end opt", ASN1_EOC, ASN1_END }, /* 15 */ + { 1, "thisUpdate", ASN1_GENERALIZEDTIME, ASN1_BODY }, /* 16 */ + { 1, "nextUpdateContext", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 17 */ + { 2, "nextUpdate", ASN1_GENERALIZEDTIME, ASN1_BODY }, /* 18 */ + { 1, "end opt", ASN1_EOC, ASN1_END }, /* 19 */ + { 1, "singleExtensionsContext", ASN1_CONTEXT_C_1, ASN1_OPT }, /* 20 */ + { 2, "singleExtensions", ASN1_SEQUENCE, ASN1_LOOP }, /* 21 */ + { 3, "extension", ASN1_SEQUENCE, ASN1_NONE }, /* 22 */ + { 4, "extnID", ASN1_OID, ASN1_BODY }, /* 23 */ + { 4, "critical", ASN1_BOOLEAN, ASN1_BODY | + ASN1_DEF }, /* 24 */ + { 4, "extnValue", ASN1_OCTET_STRING, ASN1_BODY }, /* 25 */ + { 2, "end loop", ASN1_EOC, ASN1_END }, /* 26 */ + { 1, "end opt", ASN1_EOC, ASN1_END } /* 27 */ +}; + +#define SINGLE_RESPONSE_ALGORITHM 2 +#define SINGLE_RESPONSE_ISSUER_NAME_HASH 3 +#define SINGLE_RESPONSE_ISSUER_KEY_HASH 4 +#define SINGLE_RESPONSE_SERIAL_NUMBER 5 +#define SINGLE_RESPONSE_CERT_STATUS_GOOD 6 +#define SINGLE_RESPONSE_CERT_STATUS_REVOKED 8 +#define SINGLE_RESPONSE_CERT_STATUS_REVOCATION_TIME 9 +#define SINGLE_RESPONSE_CERT_STATUS_CRL_REASON 11 +#define SINGLE_RESPONSE_CERT_STATUS_UNKNOWN 14 +#define SINGLE_RESPONSE_THIS_UPDATE 16 +#define SINGLE_RESPONSE_NEXT_UPDATE 18 +#define SINGLE_RESPONSE_EXT_ID 23 +#define SINGLE_RESPONSE_CRITICAL 24 +#define SINGLE_RESPONSE_EXT_VALUE 25 +#define SINGLE_RESPONSE_ROOF 28 + +/* build an ocsp location from certificate information + * without unsharing its contents + */ +static bool +build_ocsp_location(const x509cert_t *cert, ocsp_location_t *location) +{ + static u_char digest[SHA1_DIGEST_SIZE]; /* temporary storage */ + + location->uri = cert->accessLocation; + + if (location->uri.ptr == NULL) + { + ca_info_t *ca = get_ca_info(cert->issuer, cert->authKeySerialNumber + , cert->authKeyID); + if (ca != NULL && ca->ocspuri != NULL) + setchunk(location->uri, ca->ocspuri, strlen(ca->ocspuri)) + else + /* abort if no ocsp location uri is defined */ + return FALSE; + } + + setchunk(location->authNameID, digest, SHA1_DIGEST_SIZE); + compute_digest(cert->issuer, OID_SHA1, &location->authNameID); + + location->next = NULL; + location->issuer = cert->issuer; + location->authKeyID = cert->authKeyID; + location->authKeySerialNumber = cert->authKeySerialNumber; + + if (cert->authKeyID.ptr == NULL) + { + x509cert_t *authcert = get_authcert(cert->issuer + , cert->authKeySerialNumber, cert->authKeyID, AUTH_CA); + + if (authcert != NULL) + { + location->authKeyID = authcert->subjectKeyID; + location->authKeySerialNumber = authcert->serialNumber; + } + } + + location->nonce = empty_chunk; + location->certinfo = NULL; + + return TRUE; +} + +/* + * compare two ocsp locations for equality + */ +static bool +same_ocsp_location(const ocsp_location_t *a, const ocsp_location_t *b) +{ + return ((a->authKeyID.ptr != NULL) + ? same_keyid(a->authKeyID, b->authKeyID) + : (same_dn(a->issuer, b->issuer) + && same_serial(a->authKeySerialNumber, b->authKeySerialNumber))) + && same_chunk(a->uri, b->uri); +} + +/* + * find an existing ocsp location in a chained list + */ +ocsp_location_t* +get_ocsp_location(const ocsp_location_t * loc, ocsp_location_t *chain) +{ + + while (chain != NULL) + { + if (same_ocsp_location(loc, chain)) + return chain; + chain = chain->next; + } + return NULL; +} + +/* retrieves the status of a cert from the ocsp cache + * returns CERT_UNDEFINED if no status is found + */ +static cert_status_t +get_ocsp_status(const ocsp_location_t *loc, chunk_t serialNumber + ,time_t *nextUpdate, time_t *revocationTime, crl_reason_t *revocationReason) +{ + ocsp_certinfo_t *certinfo, **certinfop; + int cmp = -1; + + /* find location */ + ocsp_location_t *location = get_ocsp_location(loc, ocsp_cache); + + if (location == NULL) + return CERT_UNDEFINED; + + /* traverse list of certinfos in increasing order */ + certinfop = &location->certinfo; + certinfo = *certinfop; + + while (certinfo != NULL) + { + cmp = cmp_chunk(serialNumber, certinfo->serialNumber); + if (cmp <= 0) + break; + certinfop = &certinfo->next; + certinfo = *certinfop; + } + + if (cmp == 0) + { + *nextUpdate = certinfo->nextUpdate; + *revocationTime = certinfo->revocationTime; + *revocationReason = certinfo->revocationReason; + return certinfo->status; + } + + return CERT_UNDEFINED; +} + +/* + * verify the ocsp status of a certificate + */ +cert_status_t +verify_by_ocsp(const x509cert_t *cert, time_t *until +, time_t *revocationDate, crl_reason_t *revocationReason) +{ + cert_status_t status; + ocsp_location_t location; + time_t nextUpdate = 0; + + *revocationDate = UNDEFINED_TIME; + *revocationReason = REASON_UNSPECIFIED; + + /* is an ocsp location defined? */ + if (!build_ocsp_location(cert, &location)) + return CERT_UNDEFINED; + + lock_ocsp_cache("verify_by_ocsp"); + status = get_ocsp_status(&location, cert->serialNumber, &nextUpdate + , revocationDate, revocationReason); + unlock_ocsp_cache("verify_by_ocsp"); + + if (status == CERT_UNDEFINED || nextUpdate < time(NULL)) + { + plog("ocsp status is stale or not in cache"); + add_ocsp_fetch_request(&location, cert->serialNumber); + + /* inititate fetching of ocsp status */ + wake_fetch_thread("verify_by_ocsp"); + } + *until = nextUpdate; + return status; +} + +/* + * check if an ocsp status is about to expire + */ +void +check_ocsp(void) +{ + ocsp_location_t *location; + + lock_ocsp_cache("check_ocsp"); + location = ocsp_cache; + + while (location != NULL) + { + char buf[BUF_LEN]; + bool first = TRUE; + ocsp_certinfo_t *certinfo = location->certinfo; + + while (certinfo != NULL) + { + if (!certinfo->once) + { + time_t time_left = certinfo->nextUpdate - time(NULL); + + DBG(DBG_CONTROL, + if (first) + { + dntoa(buf, BUF_LEN, location->issuer); + DBG_log("issuer: '%s'", buf); + if (location->authKeyID.ptr != NULL) + { + datatot(location->authKeyID.ptr, location->authKeyID.len + , ':', buf, BUF_LEN); + DBG_log("authkey: %s", buf); + } + first = FALSE; + } + datatot(certinfo->serialNumber.ptr, certinfo->serialNumber.len + , ':', buf, BUF_LEN); + DBG_log("serial: %s, %ld seconds left", buf, time_left) + ) + + if (time_left < 2*crl_check_interval) + add_ocsp_fetch_request(location, certinfo->serialNumber); + } + certinfo = certinfo->next; + } + location = location->next; + } + unlock_ocsp_cache("check_ocsp"); +} + +/* + * frees the allocated memory of a certinfo struct + */ +static void +free_certinfo(ocsp_certinfo_t *certinfo) +{ + freeanychunk(certinfo->serialNumber); + pfree(certinfo); +} + +/* + * frees all certinfos in a chained list + */ +static void +free_certinfos(ocsp_certinfo_t *chain) +{ + ocsp_certinfo_t *certinfo; + + while (chain != NULL) + { + certinfo = chain; + chain = chain->next; + free_certinfo(certinfo); + } +} + +/* + * frees the memory allocated to an ocsp location including all certinfos + */ +static void +free_ocsp_location(ocsp_location_t* location) +{ + freeanychunk(location->issuer); + freeanychunk(location->authNameID); + freeanychunk(location->authKeyID); + freeanychunk(location->authKeySerialNumber); + freeanychunk(location->uri); + free_certinfos(location->certinfo); + pfree(location); +} + +/* + * free a chained list of ocsp locations + */ +void +free_ocsp_locations(ocsp_location_t **chain) +{ + while (*chain != NULL) + { + ocsp_location_t *location = *chain; + *chain = location->next; + free_ocsp_location(location); + } +} + +/* + * free the ocsp cache + */ +void +free_ocsp_cache(void) +{ + lock_ocsp_cache("free_ocsp_cache"); + free_ocsp_locations(&ocsp_cache); + unlock_ocsp_cache("free_ocsp_cache"); +} + +/* + * frees the ocsp cache and global variables + */ +void +free_ocsp(void) +{ + pfreeany(ocsp_default_uri.ptr); + free_ocsp_cache(); +} + +/* + * list a chained list of ocsp_locations + */ +void +list_ocsp_locations(ocsp_location_t *location, bool requests, bool utc +, bool strict) +{ + bool first = TRUE; + + while (location != NULL) + { + ocsp_certinfo_t *certinfo = location->certinfo; + + if (certinfo != NULL) + { + u_char buf[BUF_LEN]; + + if (first) + { + whack_log(RC_COMMENT, " "); + whack_log(RC_COMMENT, "List of OCSP %s:", requests? + "fetch requests":"responses"); + first = FALSE; + } + whack_log(RC_COMMENT, " "); + if (location->issuer.ptr != NULL) + { + dntoa(buf, BUF_LEN, location->issuer); + whack_log(RC_COMMENT, " issuer: '%s'", buf); + } + whack_log(RC_COMMENT, " uri: '%.*s'", (int)location->uri.len + , location->uri.ptr); + if (location->authNameID.ptr != NULL) + { + datatot(location->authNameID.ptr, location->authNameID.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT, " authname: %s", buf); + } + if (location->authKeyID.ptr != NULL) + { + datatot(location->authKeyID.ptr, location->authKeyID.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT, " authkey: %s", buf); + } + if (location->authKeySerialNumber.ptr != NULL) + { + datatot(location->authKeySerialNumber.ptr + , location->authKeySerialNumber.len, ':', buf, BUF_LEN); + whack_log(RC_COMMENT, " aserial: %s", buf); + } + while (certinfo != NULL) + { + char thisUpdate[TIMETOA_BUF]; + + strcpy(thisUpdate, timetoa(&certinfo->thisUpdate, utc)); + + if (requests) + { + whack_log(RC_COMMENT, "%s, trials: %d", thisUpdate + , certinfo->trials); + } + else if (certinfo->once) + { + whack_log(RC_COMMENT, "%s, onetime use%s", thisUpdate + , (certinfo->nextUpdate < time(NULL))? " (expired)": ""); + } + else + { + whack_log(RC_COMMENT, "%s, until %s %s", thisUpdate + , timetoa(&certinfo->nextUpdate, utc) + , check_expiry(certinfo->nextUpdate, OCSP_WARNING_INTERVAL, strict)); + } + datatot(certinfo->serialNumber.ptr, certinfo->serialNumber.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT, " serial: %s, %s", buf + , cert_status_names[certinfo->status]); + certinfo = certinfo->next; + } + } + location = location->next; + } +} + +/* + * list the ocsp cache + */ +void +list_ocsp_cache(bool utc, bool strict) +{ + lock_ocsp_cache("list_ocsp_cache"); + list_ocsp_locations(ocsp_cache, FALSE, utc, strict); + unlock_ocsp_cache("list_ocsp_cache"); +} + +static bool +get_ocsp_requestor_cert(ocsp_location_t *location) +{ + x509cert_t *cert = NULL; + + /* initialize temporary static storage */ + ocsp_requestor_cert = NULL; + ocsp_requestor_sc = NULL; + ocsp_requestor_pri = NULL; + + for (;;) + { + char buf[BUF_LEN]; + + /* looking for a certificate from the same issuer */ + cert = get_x509cert(location->issuer, location->authKeySerialNumber + ,location->authKeyID, cert); + if (cert == NULL) + break; + + DBG(DBG_CONTROL, + dntoa(buf, BUF_LEN, cert->subject); + DBG_log("candidate: '%s'", buf); + ) + + if (cert->smartcard) + { + /* look for a matching private key on a smartcard */ + smartcard_t *sc = scx_get(cert); + + if (sc != NULL) + { + DBG(DBG_CONTROL, + DBG_log("matching smartcard found") + ) + if (sc->valid) + { + ocsp_requestor_cert = cert; + ocsp_requestor_sc = sc; + return TRUE; + } + plog("unable to sign ocsp request without PIN"); + } + } + else + { + /* look for a matching private key in the chained list */ + const struct RSA_private_key *pri = get_x509_private_key(cert); + + if (pri != NULL) + { + DBG(DBG_CONTROL, + DBG_log("matching private key found") + ) + ocsp_requestor_cert = cert; + ocsp_requestor_pri = pri; + return TRUE; + } + } + } + return FALSE; +} + +static chunk_t +generate_signature(chunk_t digest, smartcard_t *sc + , const RSA_private_key_t *pri) +{ + chunk_t sigdata; + u_char *pos; + size_t siglen = 0; + + if (sc != NULL) + { + /* RSA signature is done on smartcard */ + + if (!scx_establish_context(sc) || !scx_login(sc)) + { + scx_release_context(sc); + return empty_chunk; + } + + siglen = scx_get_keylength(sc); + + if (siglen == 0) + { + plog("failed to get keylength from smartcard"); + scx_release_context(sc); + return empty_chunk; + } + + DBG(DBG_CONTROL | DBG_CRYPT, + DBG_log("signing hash with RSA key from smartcard (slot: %d, id: %s)" + , (int)sc->slot, sc->id) + ) + + pos = build_asn1_object(&sigdata, ASN1_BIT_STRING, 1 + siglen); + *pos++ = 0x00; + scx_sign_hash(sc, digest.ptr, digest.len, pos, siglen); + if (!pkcs11_keep_state) + scx_release_context(sc); + } + else + { + /* RSA signature is done in software */ + siglen = pri->pub.k; + pos = build_asn1_object(&sigdata, ASN1_BIT_STRING, 1 + siglen); + *pos++ = 0x00; + sign_hash(pri, digest.ptr, digest.len, pos, siglen); + } + return sigdata; +} + +/* + * build signature into ocsp request + * gets built only if a request cert with + * a corresponding private key is found + */ +static chunk_t +build_signature(chunk_t tbsRequest) +{ + chunk_t sigdata, certs; + chunk_t digest_info; + + u_char digest_buf[MAX_DIGEST_LEN]; + chunk_t digest_raw = { digest_buf, MAX_DIGEST_LEN }; + + if (!compute_digest(tbsRequest, OID_SHA1, &digest_raw)) + return empty_chunk; + + /* according to PKCS#1 v2.1 digest must be packaged into + * an ASN.1 structure for encryption + */ + digest_info = asn1_wrap(ASN1_SEQUENCE, "cm" + , ASN1_sha1_id + , asn1_simple_object(ASN1_OCTET_STRING, digest_raw)); + + /* generate the RSA signature */ + sigdata = generate_signature(digest_info + , ocsp_requestor_sc + , ocsp_requestor_pri); + freeanychunk(digest_info); + + /* has the RSA signature generation been successful? */ + if (sigdata.ptr == NULL) + return empty_chunk; + + /* include our certificate */ + certs = asn1_wrap(ASN1_CONTEXT_C_0, "m" + , asn1_simple_object(ASN1_SEQUENCE + , ocsp_requestor_cert->certificate + ) + ); + + /* build signature comprising algorithm, signature and cert */ + return asn1_wrap(ASN1_CONTEXT_C_0, "m" + , asn1_wrap(ASN1_SEQUENCE, "cmm" + , ASN1_sha1WithRSA_id + , sigdata + , certs + ) + ); +} + +/* build request (into requestList) + * no singleRequestExtensions used + */ +static chunk_t +build_request(ocsp_location_t *location, ocsp_certinfo_t *certinfo) +{ + chunk_t reqCert = asn1_wrap(ASN1_SEQUENCE, "cmmm" + , ASN1_sha1_id + , asn1_simple_object(ASN1_OCTET_STRING, location->authNameID) + , asn1_simple_object(ASN1_OCTET_STRING, location->authKeyID) + , asn1_simple_object(ASN1_INTEGER, certinfo->serialNumber)); + + return asn1_wrap(ASN1_SEQUENCE, "m", reqCert); +} + +/* + * build requestList (into TBSRequest) + */ +static chunk_t +build_request_list(ocsp_location_t *location) +{ + chunk_t requestList; + request_list_t *reqs = NULL; + ocsp_certinfo_t *certinfo = location->certinfo; + u_char *pos; + + size_t datalen = 0; + + /* build content */ + while (certinfo != NULL) + { + /* build request for every certificate in list + * and store them in a chained list + */ + request_list_t *req = alloc_thing(request_list_t, "ocsp request"); + + req->request = build_request(location, certinfo); + req->next = reqs; + reqs = req; + + datalen += req->request.len; + certinfo = certinfo->next; + } + + pos = build_asn1_object(&requestList, ASN1_SEQUENCE + , datalen); + + /* copy all in chained list, free list afterwards */ + while (reqs != NULL) + { + request_list_t *req = reqs; + + mv_chunk(&pos, req->request); + reqs = reqs->next; + pfree(req); + } + + return requestList; +} + +/* + * build requestorName (into TBSRequest) + */ +static chunk_t +build_requestor_name(void) +{ + return asn1_wrap(ASN1_CONTEXT_C_1, "m" + , asn1_simple_object(ASN1_CONTEXT_C_4 + , ocsp_requestor_cert->subject)); +} + +/* + * build nonce extension (into requestExtensions) + */ +static chunk_t +build_nonce_extension(ocsp_location_t *location) +{ + /* generate a random nonce */ + location->nonce.ptr = alloc_bytes(NONCE_LENGTH, "ocsp nonce"), + location->nonce.len = NONCE_LENGTH; + get_rnd_bytes(location->nonce.ptr, NONCE_LENGTH); + + return asn1_wrap(ASN1_SEQUENCE, "cm" + , ASN1_nonce_oid + , asn1_simple_object(ASN1_OCTET_STRING, location->nonce)); +} + +/* + * build requestExtensions (into TBSRequest) + */ +static chunk_t +build_request_ext(ocsp_location_t *location) +{ + return asn1_wrap(ASN1_CONTEXT_C_2, "m" + , asn1_wrap(ASN1_SEQUENCE, "mm" + , build_nonce_extension(location) + , asn1_wrap(ASN1_SEQUENCE, "cc" + , ASN1_response_oid + , ASN1_response_content + ) + ) + ); +} + +/* + * build TBSRequest (into OCSPRequest) + */ +static chunk_t +build_tbs_request(ocsp_location_t *location, bool has_requestor_cert) +{ + /* version is skipped since the default is ok */ + return asn1_wrap(ASN1_SEQUENCE, "mmm" + , (has_requestor_cert) + ? build_requestor_name() + : empty_chunk + , build_request_list(location) + , build_request_ext(location)); +} + +/* assembles an ocsp request to given location + * and sets nonce field in location to the sent nonce + */ +chunk_t +build_ocsp_request(ocsp_location_t *location) +{ + bool has_requestor_cert; + chunk_t tbsRequest, signature; + char buf[BUF_LEN]; + + DBG(DBG_CONTROL, + DBG_log("assembling ocsp request"); + dntoa(buf, BUF_LEN, location->issuer); + DBG_log("issuer: '%s'", buf); + if (location->authKeyID.ptr != NULL) + { + datatot(location->authKeyID.ptr, location->authKeyID.len, ':' + , buf, BUF_LEN); + DBG_log("authkey: %s", buf); + } + ) + lock_certs_and_keys("build_ocsp_request"); + + /* looks for requestor cert and matching private key */ + has_requestor_cert = get_ocsp_requestor_cert(location); + + /* build content */ + tbsRequest = build_tbs_request(location, has_requestor_cert); + + /* sign tbsReuqest */ + signature = (has_requestor_cert)? build_signature(tbsRequest) + : empty_chunk; + + unlock_certs_and_keys("build_ocsp_request"); + + return asn1_wrap(ASN1_SEQUENCE, "mm" + , tbsRequest + , signature); +} + +/* + * check if the OCSP response has a valid signature + */ +static bool +valid_ocsp_response(response_t *res) +{ + int pathlen; + x509cert_t *authcert; + + lock_authcert_list("valid_ocsp_response"); + + authcert = get_authcert(res->responder_id_name, empty_chunk + , res->responder_id_key, AUTH_OCSP | AUTH_CA); + + if (authcert == NULL) + { + plog("no matching ocsp signer cert found"); + unlock_authcert_list("valid_ocsp_response"); + return FALSE; + } + DBG(DBG_CONTROL, + DBG_log("ocsp signer cert found") + ) + + if (!check_signature(res->tbs, res->signature, res->algorithm + , res->algorithm, authcert)) + { + plog("signature of ocsp response is invalid"); + unlock_authcert_list("valid_ocsp_response"); + return FALSE; + } + DBG(DBG_CONTROL, + DBG_log("signature of ocsp response is valid") + ) + + + for (pathlen = 0; pathlen < MAX_CA_PATH_LEN; pathlen++) + { + u_char buf[BUF_LEN]; + err_t ugh = NULL; + time_t until; + + x509cert_t *cert = authcert; + + DBG(DBG_CONTROL, + dntoa(buf, BUF_LEN, cert->subject); + DBG_log("subject: '%s'",buf); + dntoa(buf, BUF_LEN, cert->issuer); + DBG_log("issuer: '%s'",buf); + if (cert->authKeyID.ptr != NULL) + { + datatot(cert->authKeyID.ptr, cert->authKeyID.len, ':' + , buf, BUF_LEN); + DBG_log("authkey: %s", buf); + } + ) + + ugh = check_validity(authcert, &until); + + if (ugh != NULL) + { + plog("%s", ugh); + unlock_authcert_list("valid_ocsp_response"); + return FALSE; + } + + DBG(DBG_CONTROL, + DBG_log("certificate is valid") + ) + + authcert = get_authcert(cert->issuer, cert->authKeySerialNumber + , cert->authKeyID, AUTH_CA); + + if (authcert == NULL) + { + plog("issuer cacert not found"); + unlock_authcert_list("valid_ocsp_response"); + return FALSE; + } + DBG(DBG_CONTROL, + DBG_log("issuer cacert found") + ) + + if (!check_signature(cert->tbsCertificate, cert->signature + , cert->algorithm, cert->algorithm, authcert)) + { + plog("certificate signature is invalid"); + unlock_authcert_list("valid_ocsp_response"); + return FALSE; + } + DBG(DBG_CONTROL, + DBG_log("certificate signature is valid") + ) + + /* check if cert is self-signed */ + if (same_dn(cert->issuer, cert->subject)) + { + DBG(DBG_CONTROL, + DBG_log("reached self-signed root ca") + ) + unlock_authcert_list("valid_ocsp_response"); + return TRUE; + } + } + plog("maximum ca path length of %d levels exceeded", MAX_CA_PATH_LEN); + unlock_authcert_list("valid_ocsp_response"); + return FALSE; +} + +/* + * parse a basic OCSP response + */ +static bool +parse_basic_ocsp_response(chunk_t blob, int level0, response_t *res) +{ + u_int level, version; + u_int extn_oid = OID_UNKNOWN; + u_char buf[BUF_LEN]; + asn1_ctx_t ctx; + bool critical; + chunk_t object; + int objectID = 0; + + asn1_init(&ctx, blob, level0, FALSE, DBG_RAW); + + while (objectID < BASIC_RESPONSE_ROOF) + { + if (!extract_object(basicResponseObjects, &objectID, &object, &level, &ctx)) + return FALSE; + + switch (objectID) + { + case BASIC_RESPONSE_TBS_DATA: + res->tbs = object; + break; + case BASIC_RESPONSE_VERSION: + version = (object.len)? (1 + (u_int)*object.ptr) : 1; + if (version != OCSP_BASIC_RESPONSE_VERSION) + { + plog("wrong ocsp basic response version (version= %i)", version); + return FALSE; + } + break; + case BASIC_RESPONSE_ID_BY_NAME: + res->responder_id_name = object; + DBG(DBG_PARSING, + dntoa(buf, BUF_LEN, object); + DBG_log(" '%s'",buf) + ) + break; + case BASIC_RESPONSE_ID_BY_KEY: + res->responder_id_key = object; + break; + case BASIC_RESPONSE_PRODUCED_AT: + res->produced_at = asn1totime(&object, ASN1_GENERALIZEDTIME); + break; + case BASIC_RESPONSE_RESPONSES: + res->responses = object; + break; + case BASIC_RESPONSE_EXT_ID: + extn_oid = known_oid(object); + break; + case BASIC_RESPONSE_CRITICAL: + critical = object.len && *object.ptr; + DBG(DBG_PARSING, + DBG_log(" %s",(critical)?"TRUE":"FALSE"); + ) + break; + case BASIC_RESPONSE_EXT_VALUE: + if (extn_oid == OID_NONCE) + res->nonce = object; + break; + case BASIC_RESPONSE_ALGORITHM: + res->algorithm = parse_algorithmIdentifier(object, level+1, NULL); + break; + case BASIC_RESPONSE_SIGNATURE: + res->signature = object; + break; + case BASIC_RESPONSE_CERTIFICATE: + { + chunk_t blob; + x509cert_t *cert = alloc_thing(x509cert_t, "ocspcert"); + + clonetochunk(blob, object.ptr, object.len, "ocspcert blob"); + *cert = empty_x509cert; + + if (parse_x509cert(blob, level+1, cert) + && cert->isOcspSigner + && trust_authcert_candidate(cert, NULL)) + { + add_authcert(cert, AUTH_OCSP); + } + else + { + DBG(DBG_CONTROL | DBG_PARSING, + DBG_log("embedded ocsp certificate rejected") + ) + free_x509cert(cert); + } + } + break; + } + objectID++; + } + return TRUE; +} + + +/* + * parse an ocsp response and return the result as a response_t struct + */ +static response_status +parse_ocsp_response(chunk_t blob, response_t * res) +{ + asn1_ctx_t ctx; + chunk_t object; + u_int level; + int objectID = 0; + + response_status rStatus = STATUS_INTERNALERROR; + u_int ocspResponseType = OID_UNKNOWN; + + asn1_init(&ctx, blob, 0, FALSE, DBG_RAW); + + while (objectID < OCSP_RESPONSE_ROOF) + { + if (!extract_object(ocspResponseObjects, &objectID, &object, &level, &ctx)) + return STATUS_INTERNALERROR; + + switch (objectID) { + case OCSP_RESPONSE_STATUS: + rStatus = (response_status) *object.ptr; + + switch (rStatus) + { + case STATUS_SUCCESSFUL: + break; + case STATUS_MALFORMEDREQUEST: + case STATUS_INTERNALERROR: + case STATUS_TRYLATER: + case STATUS_SIGREQUIRED: + case STATUS_UNAUTHORIZED: + plog("ocsp response: server said '%s'" + , response_status_names[rStatus]); + return rStatus; + default: + return STATUS_INTERNALERROR; + } + break; + case OCSP_RESPONSE_TYPE: + ocspResponseType = known_oid(object); + break; + case OCSP_RESPONSE: + { + switch (ocspResponseType) { + case OID_BASIC: + if (!parse_basic_ocsp_response(object, level+1, res)) + return STATUS_INTERNALERROR; + break; + default: + DBG(DBG_CONTROL, + DBG_log("ocsp response is not of type BASIC"); + DBG_dump_chunk("ocsp response OID: ", object); + ) + return STATUS_INTERNALERROR; + } + } + break; + } + objectID++; + } + return rStatus; +} + +/* + * parse a basic OCSP response + */ +static bool +parse_ocsp_single_response(chunk_t blob, int level0, single_response_t *sres) +{ + u_int level, extn_oid; + asn1_ctx_t ctx; + bool critical; + chunk_t object; + int objectID = 0; + + asn1_init(&ctx, blob, level0, FALSE, DBG_RAW); + + while (objectID < SINGLE_RESPONSE_ROOF) + { + if (!extract_object(singleResponseObjects, &objectID, &object, &level, &ctx)) + return FALSE; + + switch (objectID) + { + case SINGLE_RESPONSE_ALGORITHM: + sres->hash_algorithm = parse_algorithmIdentifier(object, level+1, NULL); + break; + case SINGLE_RESPONSE_ISSUER_NAME_HASH: + sres->issuer_name_hash = object; + break; + case SINGLE_RESPONSE_ISSUER_KEY_HASH: + sres->issuer_key_hash = object; + break; + case SINGLE_RESPONSE_SERIAL_NUMBER: + sres->serialNumber = object; + break; + case SINGLE_RESPONSE_CERT_STATUS_GOOD: + sres->status = CERT_GOOD; + break; + case SINGLE_RESPONSE_CERT_STATUS_REVOKED: + sres->status = CERT_REVOKED; + break; + case SINGLE_RESPONSE_CERT_STATUS_REVOCATION_TIME: + sres->revocationTime = asn1totime(&object, ASN1_GENERALIZEDTIME); + break; + case SINGLE_RESPONSE_CERT_STATUS_CRL_REASON: + sres->revocationReason = (object.len == 1) + ? *object.ptr : REASON_UNSPECIFIED; + break; + case SINGLE_RESPONSE_CERT_STATUS_UNKNOWN: + sres->status = CERT_UNKNOWN; + break; + case SINGLE_RESPONSE_THIS_UPDATE: + sres->thisUpdate = asn1totime(&object, ASN1_GENERALIZEDTIME); + break; + case SINGLE_RESPONSE_NEXT_UPDATE: + sres->nextUpdate = asn1totime(&object, ASN1_GENERALIZEDTIME); + break; + case SINGLE_RESPONSE_EXT_ID: + extn_oid = known_oid(object); + break; + case SINGLE_RESPONSE_CRITICAL: + critical = object.len && *object.ptr; + DBG(DBG_PARSING, + DBG_log(" %s",(critical)?"TRUE":"FALSE"); + ) + case SINGLE_RESPONSE_EXT_VALUE: + break; + } + objectID++; + } + return TRUE; +} + +/* + * add an ocsp location to a chained list + */ +ocsp_location_t* +add_ocsp_location(const ocsp_location_t *loc, ocsp_location_t **chain) +{ + ocsp_location_t *location = alloc_thing(ocsp_location_t, "ocsp location"); + + /* unshare location fields */ + clonetochunk(location->issuer + , loc->issuer.ptr, loc->issuer.len + , "ocsp issuer"); + + clonetochunk(location->authNameID + , loc->authNameID.ptr, loc->authNameID.len + , "ocsp authNameID"); + + if (loc->authKeyID.ptr == NULL) + location->authKeyID = empty_chunk; + else + clonetochunk(location->authKeyID + , loc->authKeyID.ptr, loc->authKeyID.len + , "ocsp authKeyID"); + + if (loc->authKeySerialNumber.ptr == NULL) + location->authKeySerialNumber = empty_chunk; + else + clonetochunk(location->authKeySerialNumber + , loc->authKeySerialNumber.ptr, loc->authKeySerialNumber.len + , "ocsp authKeySerialNumber"); + + clonetochunk(location->uri + , loc->uri.ptr, loc->uri.len + , "ocsp uri"); + + location->certinfo = NULL; + + /* insert new ocsp location in front of chain */ + location->next = *chain; + *chain = location; + + DBG(DBG_CONTROL, + DBG_log("new ocsp location added") + ) + + return location; +} + +/* + * add a certinfo struct to a chained list + */ +void +add_certinfo(ocsp_location_t *loc, ocsp_certinfo_t *info, ocsp_location_t **chain + , bool request) +{ + ocsp_location_t *location; + ocsp_certinfo_t *certinfo, **certinfop; + char buf[BUF_LEN]; + time_t now; + int cmp = -1; + + location = get_ocsp_location(loc, *chain); + if (location == NULL) + location = add_ocsp_location(loc, chain); + + /* traverse list of certinfos in increasing order */ + certinfop = &location->certinfo; + certinfo = *certinfop; + + while (certinfo != NULL) + { + cmp = cmp_chunk(info->serialNumber, certinfo->serialNumber); + if (cmp <= 0) + break; + certinfop = &certinfo->next; + certinfo = *certinfop; + } + + if (cmp != 0) + { + /* add a new certinfo entry */ + ocsp_certinfo_t *cnew = alloc_thing(ocsp_certinfo_t, "ocsp certinfo"); + clonetochunk(cnew->serialNumber, info->serialNumber.ptr + , info->serialNumber.len, "serialNumber"); + cnew->next = certinfo; + *certinfop = cnew; + certinfo = cnew; + } + + DBG(DBG_CONTROL, + datatot(info->serialNumber.ptr, info->serialNumber.len, ':' + , buf, BUF_LEN); + DBG_log("ocsp %s for serial %s %s" + , request?"fetch request":"certinfo" + , buf + , (cmp == 0)? (request?"already exists":"updated"):"added") + ) + + time(&now); + + if (request) + { + certinfo->status = CERT_UNDEFINED; + + if (cmp != 0) + certinfo->thisUpdate = now; + + certinfo->nextUpdate = UNDEFINED_TIME; + } + else + { + certinfo->status = info->status; + certinfo->revocationTime = info->revocationTime; + certinfo->revocationReason = info->revocationReason; + + certinfo->thisUpdate = (info->thisUpdate != UNDEFINED_TIME)? + info->thisUpdate : now; + + certinfo->once = (info->nextUpdate == UNDEFINED_TIME); + + certinfo->nextUpdate = (certinfo->once)? + (now + OCSP_DEFAULT_VALID_TIME) : info->nextUpdate; + } +} + +/* + * process received ocsp single response and add it to ocsp cache + */ +static void +process_single_response(ocsp_location_t *location, single_response_t *sres) +{ + ocsp_certinfo_t *certinfo, **certinfop; + int cmp = -1; + + if (sres->hash_algorithm != OID_SHA1) + { + plog("only SHA-1 hash supported in OCSP single response"); + return; + } + if (!(same_chunk(sres->issuer_name_hash, location->authNameID) + && same_chunk(sres->issuer_key_hash, location->authKeyID))) + { + plog("ocsp single response has wrong issuer"); + return; + } + + /* traverse list of certinfos in increasing order */ + certinfop = &location->certinfo; + certinfo = *certinfop; + + while (certinfo != NULL) + { + cmp = cmp_chunk(sres->serialNumber, certinfo->serialNumber); + if (cmp <= 0) + break; + certinfop = &certinfo->next; + certinfo = *certinfop; + } + + if (cmp != 0) + { + plog("received unrequested cert status from ocsp server"); + return; + } + + /* unlink cert from ocsp fetch request list */ + *certinfop = certinfo->next; + + /* update certinfo using the single response information */ + certinfo->thisUpdate = sres->thisUpdate; + certinfo->nextUpdate = sres->nextUpdate; + certinfo->status = sres->status; + certinfo->revocationTime = sres->revocationTime; + certinfo->revocationReason = sres->revocationReason; + + /* add or update certinfo in ocsp cache */ + lock_ocsp_cache("process_single_response"); + add_certinfo(location, certinfo, &ocsp_cache, FALSE); + unlock_ocsp_cache("process_single_response"); + + /* free certinfo unlinked from ocsp fetch request list */ + free_certinfo(certinfo); + +} + +/* + * parse and verify ocsp response and update the ocsp cache + */ +void +parse_ocsp(ocsp_location_t *location, chunk_t blob) +{ + response_t res = empty_response; + + /* parse the ocsp response without looking at the single responses yet */ + response_status status = parse_ocsp_response(blob, &res); + + if (status != STATUS_SUCCESSFUL) + { + plog("error in ocsp response"); + return; + } + /* check if there was a nonce in the request */ + if (location->nonce.ptr != NULL && res.nonce.ptr == NULL) + { + plog("ocsp response contains no nonce, replay attack possible"); + } + /* check if the nonce is identical */ + if (res.nonce.ptr != NULL && !same_chunk(res.nonce, location->nonce)) + { + plog("invalid nonce in ocsp response"); + return; + } + /* check if the response is signed by a trusted key */ + if (!valid_ocsp_response(&res)) + { + plog("invalid ocsp response"); + return; + } + DBG(DBG_CONTROL, + DBG_log("valid ocsp response") + ) + + /* now parse the single responses one at a time */ + { + u_int level; + asn1_ctx_t ctx; + chunk_t object; + int objectID = 0; + + asn1_init(&ctx, res.responses, 0, FALSE, DBG_RAW); + + while (objectID < RESPONSES_ROOF) + { + if (!extract_object(responsesObjects, &objectID, &object, &level, &ctx)) + return; + + if (objectID == RESPONSES_SINGLE_RESPONSE) + { + single_response_t sres = empty_single_response; + + if (parse_ocsp_single_response(object, level+1, &sres)) + { + process_single_response(location, &sres); + } + } + objectID++; + } + } +} diff --git a/src/pluto/ocsp.h b/src/pluto/ocsp.h new file mode 100644 index 000000000..49e1026ec --- /dev/null +++ b/src/pluto/ocsp.h @@ -0,0 +1,85 @@ +/* Support of the Online Certificate Status Protocol (OCSP) Support + * Copyright (C) 2003 Christoph Gysin, Simon Zwahlen + * Zuercher Hochschule Winterthur + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + */ + +#include "constants.h" + +/* constants */ + +#define OCSP_BASIC_RESPONSE_VERSION 1 +#define OCSP_DEFAULT_VALID_TIME 120 /* validity of one-time response in seconds */ +#define OCSP_WARNING_INTERVAL 2 /* days */ + +/* OCSP response status */ + +typedef enum { + STATUS_SUCCESSFUL = 0, + STATUS_MALFORMEDREQUEST = 1, + STATUS_INTERNALERROR = 2, + STATUS_TRYLATER = 3, + STATUS_SIGREQUIRED = 5, + STATUS_UNAUTHORIZED= 6 +} response_status; + +/* OCSP access structures */ + +typedef struct ocsp_certinfo ocsp_certinfo_t; + +struct ocsp_certinfo { + ocsp_certinfo_t *next; + int trials; + chunk_t serialNumber; + cert_status_t status; + bool once; + crl_reason_t revocationReason; + time_t revocationTime; + time_t thisUpdate; + time_t nextUpdate; +}; + +typedef struct ocsp_location ocsp_location_t; + +struct ocsp_location { + ocsp_location_t *next; + chunk_t issuer; + chunk_t authNameID; + chunk_t authKeyID; + chunk_t authKeySerialNumber; + chunk_t uri; + chunk_t nonce; + ocsp_certinfo_t *certinfo; +}; + +extern ocsp_location_t* get_ocsp_location(const ocsp_location_t *loc + , ocsp_location_t *chain); +extern ocsp_location_t* add_ocsp_location(const ocsp_location_t *loc + , ocsp_location_t **chain); +extern void add_certinfo(ocsp_location_t *loc, ocsp_certinfo_t *info + , ocsp_location_t **chain, bool request); +extern void check_ocsp(void); +extern cert_status_t verify_by_ocsp(const x509cert_t *cert, time_t *until + , time_t *revocationTime, crl_reason_t *revocationReason); +extern bool ocsp_set_request_cert(char* path); +extern void ocsp_set_default_uri(char* uri); +extern void ocsp_cache_add_cert(const x509cert_t* cert); +extern chunk_t build_ocsp_request(ocsp_location_t* location); +extern void parse_ocsp(ocsp_location_t* location, chunk_t blob); +extern void list_ocsp_locations(ocsp_location_t *location, bool requests + , bool utc, bool strict); +extern void list_ocsp_cache(bool utc, bool strict); +extern void free_ocsp_locations(ocsp_location_t **chain); +extern void free_ocsp_cache(void); +extern void free_ocsp(void); +extern void ocsp_purge_cache(void); diff --git a/src/pluto/oid.c b/src/pluto/oid.c new file mode 100644 index 000000000..4b0632de2 --- /dev/null +++ b/src/pluto/oid.c @@ -0,0 +1,197 @@ +/* List of some useful object identifiers (OIDs) + * Copyright (C) 2003-2004 Andreas Steffen, Zuercher Hochschule Winterthur + * + * This file has been automatically generated by the script oid.pl + * Do not edit manually! + */ + +#include <stdlib.h> + +#include "oid.h" + +const oid_t oid_names[] = { + {0x02, 7, 1, "ITU-T Administration" }, /* 0 */ + { 0x82, 0, 1, "" }, /* 1 */ + { 0x06, 0, 1, "Germany ITU-T member" }, /* 2 */ + { 0x01, 0, 1, "Deutsche Telekom AG" }, /* 3 */ + { 0x0A, 0, 1, "" }, /* 4 */ + { 0x07, 0, 1, "" }, /* 5 */ + { 0x14, 0, 0, "ND" }, /* 6 */ + {0x09, 18, 1, "data" }, /* 7 */ + { 0x92, 0, 1, "" }, /* 8 */ + { 0x26, 0, 1, "" }, /* 9 */ + { 0x89, 0, 1, "" }, /* 10 */ + { 0x93, 0, 1, "" }, /* 11 */ + { 0xF2, 0, 1, "" }, /* 12 */ + { 0x2C, 0, 1, "" }, /* 13 */ + { 0x64, 0, 1, "pilot" }, /* 14 */ + { 0x01, 0, 1, "pilotAttributeType" }, /* 15 */ + { 0x01, 17, 0, "UID" }, /* 16 */ + { 0x19, 0, 0, "DC" }, /* 17 */ + {0x55, 51, 1, "X.500" }, /* 18 */ + { 0x04, 36, 1, "X.509" }, /* 19 */ + { 0x03, 21, 0, "CN" }, /* 20 */ + { 0x04, 22, 0, "S" }, /* 21 */ + { 0x05, 23, 0, "SN" }, /* 22 */ + { 0x06, 24, 0, "C" }, /* 23 */ + { 0x07, 25, 0, "L" }, /* 24 */ + { 0x08, 26, 0, "ST" }, /* 25 */ + { 0x0A, 27, 0, "O" }, /* 26 */ + { 0x0B, 28, 0, "OU" }, /* 27 */ + { 0x0C, 29, 0, "T" }, /* 28 */ + { 0x0D, 30, 0, "D" }, /* 29 */ + { 0x24, 31, 0, "userCertificate" }, /* 30 */ + { 0x29, 32, 0, "N" }, /* 31 */ + { 0x2A, 33, 0, "G" }, /* 32 */ + { 0x2B, 34, 0, "I" }, /* 33 */ + { 0x2D, 35, 0, "ID" }, /* 34 */ + { 0x48, 0, 0, "role" }, /* 35 */ + { 0x1D, 0, 1, "id-ce" }, /* 36 */ + { 0x09, 38, 0, "subjectDirectoryAttrs" }, /* 37 */ + { 0x0E, 39, 0, "subjectKeyIdentifier" }, /* 38 */ + { 0x0F, 40, 0, "keyUsage" }, /* 39 */ + { 0x10, 41, 0, "privateKeyUsagePeriod" }, /* 40 */ + { 0x11, 42, 0, "subjectAltName" }, /* 41 */ + { 0x12, 43, 0, "issuerAltName" }, /* 42 */ + { 0x13, 44, 0, "basicConstraints" }, /* 43 */ + { 0x15, 45, 0, "reasonCode" }, /* 44 */ + { 0x1F, 46, 0, "crlDistributionPoints" }, /* 45 */ + { 0x20, 47, 0, "certificatePolicies" }, /* 46 */ + { 0x23, 48, 0, "authorityKeyIdentifier" }, /* 47 */ + { 0x25, 49, 0, "extendedKeyUsage" }, /* 48 */ + { 0x37, 50, 0, "targetInformation" }, /* 49 */ + { 0x38, 0, 0, "noRevAvail" }, /* 50 */ + {0x2A, 88, 1, "" }, /* 51 */ + { 0x86, 0, 1, "" }, /* 52 */ + { 0x48, 0, 1, "" }, /* 53 */ + { 0x86, 0, 1, "" }, /* 54 */ + { 0xF7, 0, 1, "" }, /* 55 */ + { 0x0D, 0, 1, "RSADSI" }, /* 56 */ + { 0x01, 83, 1, "PKCS" }, /* 57 */ + { 0x01, 66, 1, "PKCS-1" }, /* 58 */ + { 0x01, 60, 0, "rsaEncryption" }, /* 59 */ + { 0x02, 61, 0, "md2WithRSAEncryption" }, /* 60 */ + { 0x04, 62, 0, "md5WithRSAEncryption" }, /* 61 */ + { 0x05, 63, 0, "sha-1WithRSAEncryption" }, /* 62 */ + { 0x0B, 64, 0, "sha256WithRSAEncryption"}, /* 63 */ + { 0x0C, 65, 0, "sha384WithRSAEncryption"}, /* 64 */ + { 0x0D, 0, 0, "sha512WithRSAEncryption"}, /* 65 */ + { 0x07, 73, 1, "PKCS-7" }, /* 66 */ + { 0x01, 68, 0, "data" }, /* 67 */ + { 0x02, 69, 0, "signedData" }, /* 68 */ + { 0x03, 70, 0, "envelopedData" }, /* 69 */ + { 0x04, 71, 0, "signedAndEnvelopedData" }, /* 70 */ + { 0x05, 72, 0, "digestedData" }, /* 71 */ + { 0x06, 0, 0, "encryptedData" }, /* 72 */ + { 0x09, 0, 1, "PKCS-9" }, /* 73 */ + { 0x01, 75, 0, "E" }, /* 74 */ + { 0x02, 76, 0, "unstructuredName" }, /* 75 */ + { 0x03, 77, 0, "contentType" }, /* 76 */ + { 0x04, 78, 0, "messageDigest" }, /* 77 */ + { 0x05, 79, 0, "signingTime" }, /* 78 */ + { 0x06, 80, 0, "counterSignature" }, /* 79 */ + { 0x07, 81, 0, "challengePassword" }, /* 80 */ + { 0x08, 82, 0, "unstructuredAddress" }, /* 81 */ + { 0x0E, 0, 0, "extensionRequest" }, /* 82 */ + { 0x02, 86, 1, "digestAlgorithm" }, /* 83 */ + { 0x02, 85, 0, "md2" }, /* 84 */ + { 0x05, 0, 0, "md5" }, /* 85 */ + { 0x03, 0, 1, "encryptionAlgorithm" }, /* 86 */ + { 0x07, 0, 0, "3des-ede-cbc" }, /* 87 */ + {0x2B, 149, 1, "" }, /* 88 */ + { 0x06, 136, 1, "dod" }, /* 89 */ + { 0x01, 0, 1, "internet" }, /* 90 */ + { 0x04, 105, 1, "private" }, /* 91 */ + { 0x01, 0, 1, "enterprise" }, /* 92 */ + { 0x82, 98, 1, "" }, /* 93 */ + { 0x37, 0, 1, "Microsoft" }, /* 94 */ + { 0x0A, 0, 1, "" }, /* 95 */ + { 0x03, 0, 1, "" }, /* 96 */ + { 0x03, 0, 0, "msSGC" }, /* 97 */ + { 0x89, 0, 1, "" }, /* 98 */ + { 0x31, 0, 1, "" }, /* 99 */ + { 0x01, 0, 1, "" }, /* 100 */ + { 0x01, 0, 1, "" }, /* 101 */ + { 0x02, 0, 1, "" }, /* 102 */ + { 0x02, 104, 0, "" }, /* 103 */ + { 0x4B, 0, 0, "TCGID" }, /* 104 */ + { 0x05, 0, 1, "security" }, /* 105 */ + { 0x05, 0, 1, "mechanisms" }, /* 106 */ + { 0x07, 0, 1, "id-pkix" }, /* 107 */ + { 0x01, 110, 1, "id-pe" }, /* 108 */ + { 0x01, 0, 0, "authorityInfoAccess" }, /* 109 */ + { 0x03, 120, 1, "id-kp" }, /* 110 */ + { 0x01, 112, 0, "serverAuth" }, /* 111 */ + { 0x02, 113, 0, "clientAuth" }, /* 112 */ + { 0x03, 114, 0, "codeSigning" }, /* 113 */ + { 0x04, 115, 0, "emailProtection" }, /* 114 */ + { 0x05, 116, 0, "ipsecEndSystem" }, /* 115 */ + { 0x06, 117, 0, "ipsecTunnel" }, /* 116 */ + { 0x07, 118, 0, "ipsecUser" }, /* 117 */ + { 0x08, 119, 0, "timeStamping" }, /* 118 */ + { 0x09, 0, 0, "ocspSigning" }, /* 119 */ + { 0x08, 122, 1, "id-otherNames" }, /* 120 */ + { 0x05, 0, 0, "xmppAddr" }, /* 121 */ + { 0x0A, 127, 1, "id-aca" }, /* 122 */ + { 0x01, 124, 0, "authenticationInfo" }, /* 123 */ + { 0x02, 125, 0, "accessIdentity" }, /* 124 */ + { 0x03, 126, 0, "chargingIdentity" }, /* 125 */ + { 0x04, 0, 0, "group" }, /* 126 */ + { 0x30, 0, 1, "id-ad" }, /* 127 */ + { 0x01, 0, 1, "ocsp" }, /* 128 */ + { 0x01, 130, 0, "basic" }, /* 129 */ + { 0x02, 131, 0, "nonce" }, /* 130 */ + { 0x03, 132, 0, "crl" }, /* 131 */ + { 0x04, 133, 0, "response" }, /* 132 */ + { 0x05, 134, 0, "noCheck" }, /* 133 */ + { 0x06, 135, 0, "archiveCutoff" }, /* 134 */ + { 0x07, 0, 0, "serviceLocator" }, /* 135 */ + { 0x0E, 142, 1, "oiw" }, /* 136 */ + { 0x03, 0, 1, "secsig" }, /* 137 */ + { 0x02, 0, 1, "algorithms" }, /* 138 */ + { 0x07, 140, 0, "des-cbc" }, /* 139 */ + { 0x1A, 141, 0, "sha-1" }, /* 140 */ + { 0x1D, 0, 0, "sha-1WithRSASignature" }, /* 141 */ + { 0x24, 0, 1, "TeleTrusT" }, /* 142 */ + { 0x03, 0, 1, "algorithm" }, /* 143 */ + { 0x03, 0, 1, "signatureAlgorithm" }, /* 144 */ + { 0x01, 0, 1, "rsaSignature" }, /* 145 */ + { 0x02, 147, 0, "rsaSigWithripemd160" }, /* 146 */ + { 0x03, 148, 0, "rsaSigWithripemd128" }, /* 147 */ + { 0x04, 0, 0, "rsaSigWithripemd256" }, /* 148 */ + {0x60, 0, 1, "" }, /* 149 */ + { 0x86, 0, 1, "" }, /* 150 */ + { 0x48, 0, 1, "" }, /* 151 */ + { 0x01, 0, 1, "organization" }, /* 152 */ + { 0x65, 160, 1, "gov" }, /* 153 */ + { 0x03, 0, 1, "csor" }, /* 154 */ + { 0x04, 0, 1, "nistalgorithm" }, /* 155 */ + { 0x02, 0, 1, "hashalgs" }, /* 156 */ + { 0x01, 158, 0, "id-SHA-256" }, /* 157 */ + { 0x02, 159, 0, "id-SHA-384" }, /* 158 */ + { 0x03, 0, 0, "id-SHA-512" }, /* 159 */ + { 0x86, 0, 1, "" }, /* 160 */ + { 0xf8, 0, 1, "" }, /* 161 */ + { 0x42, 174, 1, "netscape" }, /* 162 */ + { 0x01, 169, 1, "" }, /* 163 */ + { 0x01, 165, 0, "nsCertType" }, /* 164 */ + { 0x03, 166, 0, "nsRevocationUrl" }, /* 165 */ + { 0x04, 167, 0, "nsCaRevocationUrl" }, /* 166 */ + { 0x08, 168, 0, "nsCaPolicyUrl" }, /* 167 */ + { 0x0d, 0, 0, "nsComment" }, /* 168 */ + { 0x03, 172, 1, "directory" }, /* 169 */ + { 0x01, 0, 1, "" }, /* 170 */ + { 0x03, 0, 0, "employeeNumber" }, /* 171 */ + { 0x04, 0, 1, "policy" }, /* 172 */ + { 0x01, 0, 0, "nsSGC" }, /* 173 */ + { 0x45, 0, 1, "verisign" }, /* 174 */ + { 0x01, 0, 1, "pki" }, /* 175 */ + { 0x09, 0, 1, "attributes" }, /* 176 */ + { 0x02, 178, 0, "messageType" }, /* 177 */ + { 0x03, 179, 0, "pkiStatus" }, /* 178 */ + { 0x04, 180, 0, "failInfo" }, /* 179 */ + { 0x05, 181, 0, "senderNonce" }, /* 180 */ + { 0x06, 182, 0, "recipientNonce" }, /* 181 */ + { 0x07, 183, 0, "transID" }, /* 182 */ + { 0x08, 0, 0, "extensionReq" } /* 183 */ +}; diff --git a/src/pluto/oid.h b/src/pluto/oid.h new file mode 100644 index 000000000..ccdfb2954 --- /dev/null +++ b/src/pluto/oid.h @@ -0,0 +1,78 @@ +/* Object identifiers (OIDs) used by FreeS/WAN + * Copyright (C) 2003-2004 Andreas Steffen, Zuercher Hochschule Winterthur + * + * This file has been automatically generated by the script oid.pl + * Do not edit manually! + */ + +typedef struct { + u_char octet; + u_int next; + u_int down; + const u_char *name; +} oid_t; + +extern const oid_t oid_names[]; + +#define OID_UNKNOWN -1 +#define OID_ROLE 35 +#define OID_SUBJECT_KEY_ID 38 +#define OID_SUBJECT_ALT_NAME 41 +#define OID_BASIC_CONSTRAINTS 43 +#define OID_CRL_REASON_CODE 44 +#define OID_CRL_DISTRIBUTION_POINTS 45 +#define OID_AUTHORITY_KEY_ID 47 +#define OID_EXTENDED_KEY_USAGE 48 +#define OID_TARGET_INFORMATION 49 +#define OID_NO_REV_AVAIL 50 +#define OID_RSA_ENCRYPTION 59 +#define OID_MD2_WITH_RSA 60 +#define OID_MD5_WITH_RSA 61 +#define OID_SHA1_WITH_RSA 62 +#define OID_SHA256_WITH_RSA 63 +#define OID_SHA384_WITH_RSA 64 +#define OID_SHA512_WITH_RSA 65 +#define OID_PKCS7_DATA 67 +#define OID_PKCS7_SIGNED_DATA 68 +#define OID_PKCS7_ENVELOPED_DATA 69 +#define OID_PKCS7_SIGNED_ENVELOPED_DATA 70 +#define OID_PKCS7_DIGESTED_DATA 71 +#define OID_PKCS7_ENCRYPTED_DATA 72 +#define OID_PKCS9_EMAIL 74 +#define OID_PKCS9_CONTENT_TYPE 76 +#define OID_PKCS9_MESSAGE_DIGEST 77 +#define OID_PKCS9_SIGNING_TIME 78 +#define OID_MD2 84 +#define OID_MD5 85 +#define OID_3DES_EDE_CBC 87 +#define OID_AUTHORITY_INFO_ACCESS 109 +#define OID_OCSP_SIGNING 119 +#define OID_XMPP_ADDR 121 +#define OID_AUTHENTICATION_INFO 123 +#define OID_ACCESS_IDENTITY 124 +#define OID_CHARGING_IDENTITY 125 +#define OID_GROUP 126 +#define OID_OCSP 128 +#define OID_BASIC 129 +#define OID_NONCE 130 +#define OID_CRL 131 +#define OID_RESPONSE 132 +#define OID_NO_CHECK 133 +#define OID_ARCHIVE_CUTOFF 134 +#define OID_SERVICE_LOCATOR 135 +#define OID_DES_CBC 139 +#define OID_SHA1 140 +#define OID_SHA1_WITH_RSA_OIW 141 +#define OID_SHA256 157 +#define OID_SHA384 158 +#define OID_SHA512 159 +#define OID_NS_REVOCATION_URL 165 +#define OID_NS_CA_REVOCATION_URL 166 +#define OID_NS_CA_POLICY_URL 167 +#define OID_NS_COMMENT 168 +#define OID_PKI_MESSAGE_TYPE 177 +#define OID_PKI_STATUS 178 +#define OID_PKI_FAIL_INFO 179 +#define OID_PKI_SENDER_NONCE 180 +#define OID_PKI_RECIPIENT_NONCE 181 +#define OID_PKI_TRANS_ID 182 diff --git a/src/pluto/oid.pl b/src/pluto/oid.pl new file mode 100644 index 000000000..52ac8eae0 --- /dev/null +++ b/src/pluto/oid.pl @@ -0,0 +1,123 @@ +#!/usr/bin/perl +# Generates oid.h and oid.c out of oid.txt +# Copyright (C) 2003-2004 Andreas Steffen, Zuercher Hochschule Winterthur +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# + +$copyright="Copyright (C) 2003-2004 Andreas Steffen, Zuercher Hochschule Winterthur"; +$automatic="This file has been automatically generated by the script oid.pl"; +$warning="Do not edit manually!"; + +print "oid.pl generating oid.h and oid.c\n"; + +# Generate oid.h + +open(OID_H, ">oid.h") + or die "could not open 'oid.h': $!"; + +print OID_H "/* Object identifiers (OIDs) used by FreeS/WAN\n", + " * ", $copyright, "\n", + " * \n", + " * ", $automatic, "\n", + " * ", $warning, "\n", + " */\n\n", + "typedef struct {\n", + " u_char octet;\n", + " u_int next;\n", + " u_int down;\n", + " const u_char *name;\n", + "} oid_t;\n", + "\n", + "extern const oid_t oid_names[];\n", + "\n", + "#define OID_UNKNOWN -1\n"; + +# parse oid.txt + +open(SRC, "<oid.txt") + or die "could not open 'oid.txt': $!"; + +$counter = 0; +$max_name = 0; +$max_order = 0; + +while ($line = <SRC>) +{ + $line =~ m/( *?)(0x\w{2})\s+(".*?")[ \t]*?([\w_]*?)\Z/; + + @order[$counter] = length($1); + @octet[$counter] = $2; + @name[$counter] = $3; + + if (length($1) > $max_order) + { + $max_order = length($1); + } + if (length($3) > $max_name) + { + $max_name = length($3); + } + if (length($4) > 0) + { + printf OID_H "#define %s%s%d\n", $4, "\t" x ((39-length($4))/8), $counter; + } + $counter++; +} + +close SRC; +close OID_H; + +# Generate oid.c + +open(OID_C, ">oid.c") + or die "could not open 'oid.c': $!"; + +print OID_C "/* List of some useful object identifiers (OIDs)\n", + " * ", $copyright, "\n", + " * \n", + " * ", $automatic, "\n", + " * ", $warning, "\n", + " */\n", + "\n", + "#include <stdlib.h>\n", + "\n", + "#include \"oid.h\"\n", + "\n", + "const oid_t oid_names[] = {\n"; + +for ($c = 0; $c < $counter; $c++) +{ + $next = 0; + + for ($d = $c+1; $d < $counter && @order[$d] >= @order[$c]; $d++) + { + if (@order[$d] == @order[$c]) + { + @next[$c] = $d; + last; + } + } + + printf OID_C " {%s%s,%s%3d, %d, %s%s}%s /* %3d */\n" + ,' ' x @order[$c] + , @octet[$c] + , ' ' x (1 + $max_order - @order[$c]) + , @next[$c] + , @order[$c+1] > @order[$c] + , @name[$c] + , ' ' x ($max_name - length(@name[$c])) + , $c != $counter-1 ? "," : " " + , $c; +} + +print OID_C "};\n" ; +close OID_C; diff --git a/src/pluto/oid.txt b/src/pluto/oid.txt new file mode 100644 index 000000000..e8750024e --- /dev/null +++ b/src/pluto/oid.txt @@ -0,0 +1,184 @@ +0x02 "ITU-T Administration" + 0x82 "" + 0x06 "Germany ITU-T member" + 0x01 "Deutsche Telekom AG" + 0x0A "" + 0x07 "" + 0x14 "ND" +0x09 "data" + 0x92 "" + 0x26 "" + 0x89 "" + 0x93 "" + 0xF2 "" + 0x2C "" + 0x64 "pilot" + 0x01 "pilotAttributeType" + 0x01 "UID" + 0x19 "DC" +0x55 "X.500" + 0x04 "X.509" + 0x03 "CN" + 0x04 "S" + 0x05 "SN" + 0x06 "C" + 0x07 "L" + 0x08 "ST" + 0x0A "O" + 0x0B "OU" + 0x0C "T" + 0x0D "D" + 0x24 "userCertificate" + 0x29 "N" + 0x2A "G" + 0x2B "I" + 0x2D "ID" + 0x48 "role" OID_ROLE + 0x1D "id-ce" + 0x09 "subjectDirectoryAttrs" + 0x0E "subjectKeyIdentifier" OID_SUBJECT_KEY_ID + 0x0F "keyUsage" + 0x10 "privateKeyUsagePeriod" + 0x11 "subjectAltName" OID_SUBJECT_ALT_NAME + 0x12 "issuerAltName" + 0x13 "basicConstraints" OID_BASIC_CONSTRAINTS + 0x15 "reasonCode" OID_CRL_REASON_CODE + 0x1F "crlDistributionPoints" OID_CRL_DISTRIBUTION_POINTS + 0x20 "certificatePolicies" + 0x23 "authorityKeyIdentifier" OID_AUTHORITY_KEY_ID + 0x25 "extendedKeyUsage" OID_EXTENDED_KEY_USAGE + 0x37 "targetInformation" OID_TARGET_INFORMATION + 0x38 "noRevAvail" OID_NO_REV_AVAIL +0x2A "" + 0x86 "" + 0x48 "" + 0x86 "" + 0xF7 "" + 0x0D "RSADSI" + 0x01 "PKCS" + 0x01 "PKCS-1" + 0x01 "rsaEncryption" OID_RSA_ENCRYPTION + 0x02 "md2WithRSAEncryption" OID_MD2_WITH_RSA + 0x04 "md5WithRSAEncryption" OID_MD5_WITH_RSA + 0x05 "sha-1WithRSAEncryption" OID_SHA1_WITH_RSA + 0x0B "sha256WithRSAEncryption" OID_SHA256_WITH_RSA + 0x0C "sha384WithRSAEncryption" OID_SHA384_WITH_RSA + 0x0D "sha512WithRSAEncryption" OID_SHA512_WITH_RSA + 0x07 "PKCS-7" + 0x01 "data" OID_PKCS7_DATA + 0x02 "signedData" OID_PKCS7_SIGNED_DATA + 0x03 "envelopedData" OID_PKCS7_ENVELOPED_DATA + 0x04 "signedAndEnvelopedData" OID_PKCS7_SIGNED_ENVELOPED_DATA + 0x05 "digestedData" OID_PKCS7_DIGESTED_DATA + 0x06 "encryptedData" OID_PKCS7_ENCRYPTED_DATA + 0x09 "PKCS-9" + 0x01 "E" OID_PKCS9_EMAIL + 0x02 "unstructuredName" + 0x03 "contentType" OID_PKCS9_CONTENT_TYPE + 0x04 "messageDigest" OID_PKCS9_MESSAGE_DIGEST + 0x05 "signingTime" OID_PKCS9_SIGNING_TIME + 0x06 "counterSignature" + 0x07 "challengePassword" + 0x08 "unstructuredAddress" + 0x0E "extensionRequest" + 0x02 "digestAlgorithm" + 0x02 "md2" OID_MD2 + 0x05 "md5" OID_MD5 + 0x03 "encryptionAlgorithm" + 0x07 "3des-ede-cbc" OID_3DES_EDE_CBC +0x2B "" + 0x06 "dod" + 0x01 "internet" + 0x04 "private" + 0x01 "enterprise" + 0x82 "" + 0x37 "Microsoft" + 0x0A "" + 0x03 "" + 0x03 "msSGC" + 0x89 "" + 0x31 "" + 0x01 "" + 0x01 "" + 0x02 "" + 0x02 "" + 0x4B "TCGID" + 0x05 "security" + 0x05 "mechanisms" + 0x07 "id-pkix" + 0x01 "id-pe" + 0x01 "authorityInfoAccess" OID_AUTHORITY_INFO_ACCESS + 0x03 "id-kp" + 0x01 "serverAuth" + 0x02 "clientAuth" + 0x03 "codeSigning" + 0x04 "emailProtection" + 0x05 "ipsecEndSystem" + 0x06 "ipsecTunnel" + 0x07 "ipsecUser" + 0x08 "timeStamping" + 0x09 "ocspSigning" OID_OCSP_SIGNING + 0x08 "id-otherNames" + 0x05 "xmppAddr" OID_XMPP_ADDR + 0x0A "id-aca" + 0x01 "authenticationInfo" OID_AUTHENTICATION_INFO + 0x02 "accessIdentity" OID_ACCESS_IDENTITY + 0x03 "chargingIdentity" OID_CHARGING_IDENTITY + 0x04 "group" OID_GROUP + 0x30 "id-ad" + 0x01 "ocsp" OID_OCSP + 0x01 "basic" OID_BASIC + 0x02 "nonce" OID_NONCE + 0x03 "crl" OID_CRL + 0x04 "response" OID_RESPONSE + 0x05 "noCheck" OID_NO_CHECK + 0x06 "archiveCutoff" OID_ARCHIVE_CUTOFF + 0x07 "serviceLocator" OID_SERVICE_LOCATOR + 0x0E "oiw" + 0x03 "secsig" + 0x02 "algorithms" + 0x07 "des-cbc" OID_DES_CBC + 0x1A "sha-1" OID_SHA1 + 0x1D "sha-1WithRSASignature" OID_SHA1_WITH_RSA_OIW + 0x24 "TeleTrusT" + 0x03 "algorithm" + 0x03 "signatureAlgorithm" + 0x01 "rsaSignature" + 0x02 "rsaSigWithripemd160" + 0x03 "rsaSigWithripemd128" + 0x04 "rsaSigWithripemd256" +0x60 "" + 0x86 "" + 0x48 "" + 0x01 "organization" + 0x65 "gov" + 0x03 "csor" + 0x04 "nistalgorithm" + 0x02 "hashalgs" + 0x01 "id-SHA-256" OID_SHA256 + 0x02 "id-SHA-384" OID_SHA384 + 0x03 "id-SHA-512" OID_SHA512 + 0x86 "" + 0xf8 "" + 0x42 "netscape" + 0x01 "" + 0x01 "nsCertType" + 0x03 "nsRevocationUrl" OID_NS_REVOCATION_URL + 0x04 "nsCaRevocationUrl" OID_NS_CA_REVOCATION_URL + 0x08 "nsCaPolicyUrl" OID_NS_CA_POLICY_URL + 0x0d "nsComment" OID_NS_COMMENT + 0x03 "directory" + 0x01 "" + 0x03 "employeeNumber" + 0x04 "policy" + 0x01 "nsSGC" + 0x45 "verisign" + 0x01 "pki" + 0x09 "attributes" + 0x02 "messageType" OID_PKI_MESSAGE_TYPE + 0x03 "pkiStatus" OID_PKI_STATUS + 0x04 "failInfo" OID_PKI_FAIL_INFO + 0x05 "senderNonce" OID_PKI_SENDER_NONCE + 0x06 "recipientNonce" OID_PKI_RECIPIENT_NONCE + 0x07 "transID" OID_PKI_TRANS_ID + 0x08 "extensionReq" diff --git a/src/pluto/packet.c b/src/pluto/packet.c new file mode 100644 index 000000000..9f04c8bb2 --- /dev/null +++ b/src/pluto/packet.c @@ -0,0 +1,1244 @@ +/* parsing packets: formats and tools + * Copyright (C) 1997 Angelos D. Keromytis. + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: packet.c,v 1.7 2005/01/06 22:39:04 as Exp $ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <netinet/in.h> +#include <string.h> + +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "packet.h" +#include "whack.h" /* for RC_LOG_SERIOUS */ + +/* ISAKMP Header: for all messages + * layout from RFC 2408 "ISAKMP" section 3.1 + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Initiator ! + * ! Cookie ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Responder ! + * ! Cookie ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! MjVer ! MnVer ! Exchange Type ! Flags ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Message ID ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +static field_desc isa_fields[] = { + { ft_raw, COOKIE_SIZE, "initiator cookie", NULL }, + { ft_raw, COOKIE_SIZE, "responder cookie", NULL }, + { ft_enum, 8/BITS_PER_BYTE, "next payload type", &payload_names }, + { ft_enum, 8/BITS_PER_BYTE, "ISAKMP version", &version_names }, + { ft_enum, 8/BITS_PER_BYTE, "exchange type", &exchange_names }, + { ft_set, 8/BITS_PER_BYTE, "flags", flag_bit_names }, + { ft_raw, 32/BITS_PER_BYTE, "message ID", NULL }, + { ft_len, 32/BITS_PER_BYTE, "length", NULL }, + { ft_end, 0, NULL, NULL } +}; + +struct_desc isakmp_hdr_desc = { "ISAKMP Message", isa_fields, sizeof(struct isakmp_hdr) }; + +/* Generic portion of all ISAKMP payloads. + * layout from RFC 2408 "ISAKMP" section 3.2 + * This describes the first 32-bit chunk of all payloads. + * The previous next payload depends on the actual payload type. + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +static field_desc isag_fields[] = { + { ft_enum, 8/BITS_PER_BYTE, "next payload type", &payload_names }, + { ft_mbz, 8/BITS_PER_BYTE, NULL, NULL }, + { ft_len, 16/BITS_PER_BYTE, "length", NULL }, + { ft_end, 0, NULL, NULL } +}; + +struct_desc isakmp_generic_desc = { "ISAKMP Generic Payload", isag_fields, sizeof(struct isakmp_generic) }; + + +/* ISAKMP Data Attribute (generic representation within payloads) + * layout from RFC 2408 "ISAKMP" section 3.3 + * This is not a payload type. + * In TLV format, this is followed by a value field. + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * !A! Attribute Type ! AF=0 Attribute Length ! + * !F! ! AF=1 Attribute Value ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * . AF=0 Attribute Value . + * . AF=1 Not Transmitted . + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +/* Oakley Attributes */ +static field_desc isaat_fields_oakley[] = { + { ft_af_enum, 16/BITS_PER_BYTE, "af+type", &oakley_attr_names }, + { ft_lv, 16/BITS_PER_BYTE, "length/value", NULL }, + { ft_end, 0, NULL, NULL } +}; + +struct_desc isakmp_oakley_attribute_desc = { + "ISAKMP Oakley attribute", + isaat_fields_oakley, sizeof(struct isakmp_attribute) }; + +/* IPsec DOI Attributes */ +static field_desc isaat_fields_ipsec[] = { + { ft_af_enum, 16/BITS_PER_BYTE, "af+type", &ipsec_attr_names }, + { ft_lv, 16/BITS_PER_BYTE, "length/value", NULL }, + { ft_end, 0, NULL, NULL } +}; + +struct_desc isakmp_ipsec_attribute_desc = { + "ISAKMP IPsec DOI attribute", + isaat_fields_ipsec, sizeof(struct isakmp_attribute) }; + +/* Mode Config Attributes */ +static field_desc isaat_fields_modecfg[] = { + { ft_af_loose_enum, 16/BITS_PER_BYTE, "ModeCfg attr type", &modecfg_attr_names }, + { ft_lv, 16/BITS_PER_BYTE, "length/value", NULL }, + { ft_end, 0, NULL, NULL } +}; + +struct_desc isakmp_modecfg_attribute_desc = { + "ISAKMP ModeCfg attribute", + isaat_fields_modecfg, sizeof(struct isakmp_attribute) }; + +/* ISAKMP Security Association Payload + * layout from RFC 2408 "ISAKMP" section 3.4 + * A variable length Situation follows. + * Previous next payload: ISAKMP_NEXT_SA + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Domain of Interpretation (DOI) ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Situation ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +static field_desc isasa_fields[] = { + { ft_enum, 8/BITS_PER_BYTE, "next payload type", &payload_names }, + { ft_mbz, 8/BITS_PER_BYTE, NULL, NULL }, + { ft_len, 16/BITS_PER_BYTE, "length", NULL }, + { ft_enum, 32/BITS_PER_BYTE, "DOI", &doi_names }, + { ft_end, 0, NULL, NULL } +}; + +struct_desc isakmp_sa_desc = { "ISAKMP Security Association Payload", isasa_fields, sizeof(struct isakmp_sa) }; + +static field_desc ipsec_sit_field[] = { + { ft_set, 32/BITS_PER_BYTE, "IPsec DOI SIT", &sit_bit_names }, + { ft_end, 0, NULL, NULL } +}; + +struct_desc ipsec_sit_desc = { "IPsec DOI SIT", ipsec_sit_field, sizeof(u_int32_t) }; + +/* ISAKMP Proposal Payload + * layout from RFC 2408 "ISAKMP" section 3.5 + * A variable length SPI follows. + * Previous next payload: ISAKMP_NEXT_P + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Proposal # ! Protocol-Id ! SPI Size !# of Transforms! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! SPI (variable) ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +static field_desc isap_fields[] = { + { ft_enum, 8/BITS_PER_BYTE, "next payload type", &payload_names }, + { ft_mbz, 8/BITS_PER_BYTE, NULL, NULL }, + { ft_len, 16/BITS_PER_BYTE, "length", NULL }, + { ft_nat, 8/BITS_PER_BYTE, "proposal number", NULL }, + { ft_enum, 8/BITS_PER_BYTE, "protocol ID", &protocol_names }, + { ft_nat, 8/BITS_PER_BYTE, "SPI size", NULL }, + { ft_nat, 8/BITS_PER_BYTE, "number of transforms", NULL }, + { ft_end, 0, NULL, NULL } +}; + +struct_desc isakmp_proposal_desc = { "ISAKMP Proposal Payload", isap_fields, sizeof(struct isakmp_proposal) }; + +/* ISAKMP Transform Payload + * layout from RFC 2408 "ISAKMP" section 3.6 + * Variable length SA Attributes follow. + * Previous next payload: ISAKMP_NEXT_T + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Transform # ! Transform-Id ! RESERVED2 ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ SA Attributes ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +/* PROTO_ISAKMP */ +static field_desc isat_fields_isakmp[] = { + { ft_enum, 8/BITS_PER_BYTE, "next payload type", &payload_names }, + { ft_mbz, 8/BITS_PER_BYTE, NULL, NULL }, + { ft_len, 16/BITS_PER_BYTE, "length", NULL }, + { ft_nat, 8/BITS_PER_BYTE, "transform number", NULL }, + { ft_enum, 8/BITS_PER_BYTE, "transform ID", &isakmp_transformid_names }, + { ft_mbz, 16/BITS_PER_BYTE, NULL, NULL }, + { ft_end, 0, NULL, NULL } +}; + +struct_desc isakmp_isakmp_transform_desc = { + "ISAKMP Transform Payload (ISAKMP)", + isat_fields_isakmp, sizeof(struct isakmp_transform) }; + +/* PROTO_IPSEC_AH */ +static field_desc isat_fields_ah[] = { + { ft_enum, 8/BITS_PER_BYTE, "next payload type", &payload_names }, + { ft_mbz, 8/BITS_PER_BYTE, NULL, NULL }, + { ft_len, 16/BITS_PER_BYTE, "length", NULL }, + { ft_nat, 8/BITS_PER_BYTE, "transform number", NULL }, + { ft_enum, 8/BITS_PER_BYTE, "transform ID", &ah_transformid_names }, + { ft_mbz, 16/BITS_PER_BYTE, NULL, NULL }, + { ft_end, 0, NULL, NULL } +}; + +struct_desc isakmp_ah_transform_desc = { + "ISAKMP Transform Payload (AH)", + isat_fields_ah, sizeof(struct isakmp_transform) }; + +/* PROTO_IPSEC_ESP */ +static field_desc isat_fields_esp[] = { + { ft_enum, 8/BITS_PER_BYTE, "next payload type", &payload_names }, + { ft_mbz, 8/BITS_PER_BYTE, NULL, NULL }, + { ft_len, 16/BITS_PER_BYTE, "length", NULL }, + { ft_nat, 8/BITS_PER_BYTE, "transform number", NULL }, + { ft_enum, 8/BITS_PER_BYTE, "transform ID", &esp_transformid_names }, + { ft_mbz, 16/BITS_PER_BYTE, NULL, NULL }, + { ft_end, 0, NULL, NULL } +}; + +struct_desc isakmp_esp_transform_desc = { + "ISAKMP Transform Payload (ESP)", + isat_fields_esp, sizeof(struct isakmp_transform) }; + +/* PROTO_IPCOMP */ +static field_desc isat_fields_ipcomp[] = { + { ft_enum, 8/BITS_PER_BYTE, "next payload type", &payload_names }, + { ft_mbz, 8/BITS_PER_BYTE, NULL, NULL }, + { ft_len, 16/BITS_PER_BYTE, "length", NULL }, + { ft_nat, 8/BITS_PER_BYTE, "transform number", NULL }, + { ft_enum, 8/BITS_PER_BYTE, "transform ID", &ipcomp_transformid_names }, + { ft_mbz, 16/BITS_PER_BYTE, NULL, NULL }, + { ft_end, 0, NULL, NULL } +}; + +struct_desc isakmp_ipcomp_transform_desc = { + "ISAKMP Transform Payload (COMP)", + isat_fields_ipcomp, sizeof(struct isakmp_transform) }; + + +/* ISAKMP Key Exchange Payload: no fixed fields beyond the generic ones. + * layout from RFC 2408 "ISAKMP" section 3.7 + * Variable Key Exchange Data follow the generic fields. + * Previous next payload: ISAKMP_NEXT_KE + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Key Exchange Data ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +struct_desc isakmp_keyex_desc = { "ISAKMP Key Exchange Payload", isag_fields, sizeof(struct isakmp_generic) }; + +/* ISAKMP Identification Payload + * layout from RFC 2408 "ISAKMP" section 3.8 + * See "struct identity" declared later. + * Variable length Identification Data follow. + * Previous next payload: ISAKMP_NEXT_ID + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ID Type ! DOI Specific ID Data ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Identification Data ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +static field_desc isaid_fields[] = { + { ft_enum, 8/BITS_PER_BYTE, "next payload type", &payload_names }, + { ft_mbz, 8/BITS_PER_BYTE, NULL, NULL }, + { ft_len, 16/BITS_PER_BYTE, "length", NULL }, + { ft_enum, 8/BITS_PER_BYTE, "ID type", &ident_names }, /* ??? depends on DOI? */ + { ft_nat, 8/BITS_PER_BYTE, "DOI specific A", NULL }, /* ??? depends on DOI? */ + { ft_nat, 16/BITS_PER_BYTE, "DOI specific B", NULL }, /* ??? depends on DOI? */ + { ft_end, 0, NULL, NULL } +}; + +struct_desc isakmp_identification_desc = { "ISAKMP Identification Payload", isaid_fields, sizeof(struct isakmp_id) }; + +/* IPSEC Identification Payload Content + * layout from RFC 2407 "IPsec DOI" section 4.6.2 + * See struct isakmp_id declared earlier. + * Note: Hashing skips the ISAKMP generic payload header + * Variable length Identification Data follow. + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ID Type ! Protocol ID ! Port ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ~ Identification Data ~ + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +static field_desc isaiid_fields[] = { + { ft_enum, 8/BITS_PER_BYTE, "next payload type", &payload_names }, + { ft_mbz, 8/BITS_PER_BYTE, NULL, NULL }, + { ft_len, 16/BITS_PER_BYTE, "length", NULL }, + { ft_enum, 8/BITS_PER_BYTE, "ID type", &ident_names }, + { ft_nat, 8/BITS_PER_BYTE, "Protocol ID", NULL }, /* ??? UDP/TCP or 0? */ + { ft_nat, 16/BITS_PER_BYTE, "port", NULL }, + { ft_end, 0, NULL, NULL } +}; + +struct_desc isakmp_ipsec_identification_desc = { "ISAKMP Identification Payload (IPsec DOI)", isaiid_fields, sizeof(struct isakmp_ipsec_id) }; + +/* ISAKMP Certificate Payload: oddball fixed field beyond the generic ones. + * layout from RFC 2408 "ISAKMP" section 3.9 + * Variable length Certificate Data follow the generic fields. + * Previous next payload: ISAKMP_NEXT_CERT. + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Cert Encoding ! ! + * +-+-+-+-+-+-+-+-+ ! + * ~ Certificate Data ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +static field_desc isacert_fields[] = { + { ft_enum, 8/BITS_PER_BYTE, "next payload type", &payload_names }, + { ft_mbz, 8/BITS_PER_BYTE, NULL, NULL }, + { ft_len, 16/BITS_PER_BYTE, "length", NULL }, + { ft_enum, 8/BITS_PER_BYTE, "cert encoding", &cert_type_names }, + { ft_end, 0, NULL, NULL } +}; + +/* Note: the size field of isakmp_ipsec_certificate_desc cannot be + * sizeof(struct isakmp_cert) because that will rounded up for padding. + */ + struct_desc isakmp_ipsec_certificate_desc = { "ISAKMP Certificate Payload", isacert_fields, ISAKMP_CERT_SIZE }; + +/* ISAKMP Certificate Request Payload: oddball field beyond the generic ones. + * layout from RFC 2408 "ISAKMP" section 3.10 + * Variable length Certificate Types and Certificate Authorities follow. + * Previous next payload: ISAKMP_NEXT_CR. + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Cert. Type ! ! + * +-+-+-+-+-+-+-+-+ ! + * ~ Certificate Authority ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +static field_desc isacr_fields[] = { + { ft_enum, 8/BITS_PER_BYTE, "next payload type", &payload_names }, + { ft_mbz, 8/BITS_PER_BYTE, NULL, NULL }, + { ft_len, 16/BITS_PER_BYTE, "length", NULL }, + { ft_enum, 8/BITS_PER_BYTE, "cert type", &cert_type_names }, + { ft_end, 0, NULL, NULL } +}; + +/* Note: the size field of isakmp_ipsec_cert_req_desc cannot be + * sizeof(struct isakmp_cr) because that will rounded up for padding. + */ +struct_desc isakmp_ipsec_cert_req_desc = { "ISAKMP Certificate RequestPayload", isacr_fields, ISAKMP_CR_SIZE }; + +/* ISAKMP Hash Payload: no fixed fields beyond the generic ones. + * layout from RFC 2408 "ISAKMP" section 3.11 + * Variable length Hash Data follow. + * Previous next payload: ISAKMP_NEXT_HASH. + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Hash Data ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +struct_desc isakmp_hash_desc = { "ISAKMP Hash Payload", isag_fields, sizeof(struct isakmp_generic) }; + +/* ISAKMP Signature Payload: no fixed fields beyond the generic ones. + * layout from RFC 2408 "ISAKMP" section 3.12 + * Variable length Signature Data follow. + * Previous next payload: ISAKMP_NEXT_SIG. + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Signature Data ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +struct_desc isakmp_signature_desc = { "ISAKMP Signature Payload", isag_fields, sizeof(struct isakmp_generic) }; + +/* ISAKMP Nonce Payload: no fixed fields beyond the generic ones. + * layout from RFC 2408 "ISAKMP" section 3.13 + * Variable length Nonce Data follow. + * Previous next payload: ISAKMP_NEXT_NONCE. + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Nonce Data ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +struct_desc isakmp_nonce_desc = { "ISAKMP Nonce Payload", isag_fields, sizeof(struct isakmp_generic) }; + +/* ISAKMP Notification Payload + * layout from RFC 2408 "ISAKMP" section 3.14 + * This is followed by a variable length SPI + * and then possibly by variable length Notification Data. + * Previous next payload: ISAKMP_NEXT_N + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Domain of Interpretation (DOI) ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Protocol-ID ! SPI Size ! Notify Message Type ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Security Parameter Index (SPI) ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Notification Data ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +static field_desc isan_fields[] = { + { ft_enum, 8/BITS_PER_BYTE, "next payload type", &payload_names }, + { ft_mbz, 8/BITS_PER_BYTE, NULL, NULL }, + { ft_len, 16/BITS_PER_BYTE, "length", NULL }, + { ft_enum, 32/BITS_PER_BYTE, "DOI", &doi_names }, + { ft_nat, 8/BITS_PER_BYTE, "protocol ID", NULL }, /* ??? really enum: ISAKMP, IPSEC, ESP, ... */ + { ft_nat, 8/BITS_PER_BYTE, "SPI size", NULL }, + { ft_enum, 16/BITS_PER_BYTE, "Notify Message Type", ¬ification_names }, + { ft_end, 0, NULL, NULL } +}; + +struct_desc isakmp_notification_desc = { "ISAKMP Notification Payload", isan_fields, sizeof(struct isakmp_notification) }; + +/* ISAKMP Delete Payload + * layout from RFC 2408 "ISAKMP" section 3.15 + * This is followed by a variable length SPI. + * Previous next payload: ISAKMP_NEXT_D + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Domain of Interpretation (DOI) ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Protocol-Id ! SPI Size ! # of SPIs ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Security Parameter Index(es) (SPI) ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +static field_desc isad_fields[] = { + { ft_enum, 8/BITS_PER_BYTE, "next payload type", &payload_names }, + { ft_mbz, 8/BITS_PER_BYTE, NULL, NULL }, + { ft_len, 16/BITS_PER_BYTE, "length", NULL }, + { ft_enum, 32/BITS_PER_BYTE, "DOI", &doi_names }, + { ft_nat, 8/BITS_PER_BYTE, "protocol ID", NULL }, /* ??? really enum: ISAKMP, IPSEC */ + { ft_nat, 8/BITS_PER_BYTE, "SPI size", NULL }, + { ft_nat, 16/BITS_PER_BYTE, "number of SPIs", NULL }, + { ft_end, 0, NULL, NULL } +}; + +struct_desc isakmp_delete_desc = { "ISAKMP Delete Payload", isad_fields, sizeof(struct isakmp_delete) }; + +/* ISAKMP Vendor ID Payload + * layout from RFC 2408 "ISAKMP" section 3.15 + * This is followed by a variable length VID. + * Previous next payload: ISAKMP_NEXT_VID + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Vendor ID (VID) ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +struct_desc isakmp_vendor_id_desc = { "ISAKMP Vendor ID Payload", isag_fields, sizeof(struct isakmp_generic) }; + +/* MODECFG */ +/* + * From draft-dukes-ike-mode-cfg +3.2. Attribute Payload + 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ! Next Payload ! RESERVED ! Payload Length ! + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ! Type ! RESERVED ! Identifier ! + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ! ! + ~ Attributes ~ + ! ! + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static field_desc isaattr_fields[] = { + { ft_enum, 8/BITS_PER_BYTE, "next payload type", &payload_names }, + { ft_mbz, 8/BITS_PER_BYTE, NULL, NULL }, + { ft_len, 16/BITS_PER_BYTE, "length", NULL }, + { ft_enum, 8/BITS_PER_BYTE, "Attr Msg Type", &attr_msg_type_names }, + { ft_mbz, 8/BITS_PER_BYTE, NULL, NULL }, + { ft_nat, 16/BITS_PER_BYTE, "Identifier", NULL }, + { ft_end, 0, NULL, NULL } +}; + +struct_desc isakmp_attr_desc = { "ISAKMP Mode Attribute", isaattr_fields, sizeof(struct isakmp_mode_attr) }; + +/* ISAKMP NAT-Traversal NAT-D + * layout from draft-ietf-ipsec-nat-t-ike-01.txt section 3.2 + * + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! HASH of the address and port ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +struct_desc isakmp_nat_d = { "ISAKMP NAT-D Payload", isag_fields, sizeof(struct isakmp_generic) }; + +/* ISAKMP NAT-Traversal NAT-OA + * layout from draft-ietf-ipsec-nat-t-ike-01.txt section 4.2 + * + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ID Type ! RESERVED ! RESERVED ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! IPv4 (4 octets) or IPv6 address (16 octets) ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +static field_desc isanat_oa_fields[] = { + { ft_enum, 8/BITS_PER_BYTE, "next payload type", &payload_names }, + { ft_mbz, 8/BITS_PER_BYTE, NULL, NULL }, + { ft_len, 16/BITS_PER_BYTE, "length", NULL }, + { ft_enum, 8/BITS_PER_BYTE, "ID type", &ident_names }, + { ft_mbz, 24/BITS_PER_BYTE, NULL, NULL }, + { ft_end, 0, NULL, NULL } +}; + +struct_desc isakmp_nat_oa = { "ISAKMP NAT-OA Payload", isanat_oa_fields, sizeof(struct isakmp_nat_oa) }; + +/* descriptor for each payload type + * + * There is a slight problem in that some payloads differ, depending + * on the mode. Since this is table only used for top-level payloads, + * Proposal and Transform payloads need not be handled. + * That leaves only Identification payloads as a problem. + * We make all these entries NULL + */ +struct_desc *const payload_descs[ISAKMP_NEXT_ROOF] = { + NULL, /* 0 ISAKMP_NEXT_NONE (No other payload following) */ + &isakmp_sa_desc, /* 1 ISAKMP_NEXT_SA (Security Association) */ + NULL, /* 2 ISAKMP_NEXT_P (Proposal) */ + NULL, /* 3 ISAKMP_NEXT_T (Transform) */ + &isakmp_keyex_desc, /* 4 ISAKMP_NEXT_KE (Key Exchange) */ + NULL, /* 5 ISAKMP_NEXT_ID (Identification) */ + &isakmp_ipsec_certificate_desc, /* 6 ISAKMP_NEXT_CERT (Certificate) */ + &isakmp_ipsec_cert_req_desc, /* 7 ISAKMP_NEXT_CR (Certificate Request) */ + &isakmp_hash_desc, /* 8 ISAKMP_NEXT_HASH (Hash) */ + &isakmp_signature_desc, /* 9 ISAKMP_NEXT_SIG (Signature) */ + &isakmp_nonce_desc, /* 10 ISAKMP_NEXT_NONCE (Nonce) */ + &isakmp_notification_desc, /* 11 ISAKMP_NEXT_N (Notification) */ + &isakmp_delete_desc, /* 12 ISAKMP_NEXT_D (Delete) */ + &isakmp_vendor_id_desc, /* 13 ISAKMP_NEXT_VID (Vendor ID) */ + &isakmp_attr_desc, /* 14 ISAKMP_NEXT_ATTR (Mode Config) */ + NULL, /* 15 */ + NULL, /* 16 */ + NULL, /* 17 */ + NULL, /* 18 */ + NULL, /* 19 */ + &isakmp_nat_d, /* 20=130 ISAKMP_NEXT_NATD (NAT-D) */ + &isakmp_nat_oa, /* 20=131 ISAKMP_NEXT_NATOA (NAT-OA) */ +}; + +void +init_pbs(pb_stream *pbs, u_int8_t *start, size_t len, const char *name) +{ + pbs->container = NULL; + pbs->desc = NULL; + pbs->name = name; + pbs->start = pbs->cur = start; + pbs->roof = start + len; + pbs->lenfld = NULL; + pbs->lenfld_desc = NULL; +} + +#ifdef DEBUG + +/* print a host struct + * + * This code assumes that the network and host structure + * members have the same alignment and size! This requires + * that all padding be explicit. + */ +void +DBG_print_struct(const char *label, const void *struct_ptr +, struct_desc *sd, bool len_meaningful) +{ + bool immediate = FALSE; + const u_int8_t *inp = struct_ptr; + field_desc *fp; + + DBG_log("%s%s:", label, sd->name); + + for (fp = sd->fields; fp->field_type != ft_end; fp++) + { + int i = fp->size; + u_int32_t n = 0; + + switch (fp->field_type) + { + case ft_mbz: /* must be zero */ + inp += i; + break; + case ft_nat: /* natural number (may be 0) */ + case ft_len: /* length of this struct and any following crud */ + case ft_lv: /* length/value field of attribute */ + case ft_enum: /* value from an enumeration */ + case ft_loose_enum: /* value from an enumeration with only some names known */ + case ft_af_enum: /* Attribute Format + value from an enumeration */ + case ft_af_loose_enum: /* Attribute Format + value from an enumeration */ + case ft_set: /* bits representing set */ + switch (i) + { + case 8/BITS_PER_BYTE: + n = *(const u_int8_t *)inp; + break; + case 16/BITS_PER_BYTE: + n = *(const u_int16_t *)inp; + break; + case 32/BITS_PER_BYTE: + n = *(const u_int32_t *)inp; + break; + default: + bad_case(i); + } + switch (fp->field_type) + { + case ft_len: /* length of this struct and any following crud */ + case ft_lv: /* length/value field of attribute */ + if (!immediate && !len_meaningful) + break; + /* FALL THROUGH */ + case ft_nat: /* natural number (may be 0) */ + DBG_log(" %s: %lu", fp->name, (unsigned long)n); + break; + case ft_af_enum: /* Attribute Format + value from an enumeration */ + case ft_af_loose_enum: /* Attribute Format + value from an enumeration */ + if ((n & ISAKMP_ATTR_AF_MASK) == ISAKMP_ATTR_AF_TV) + immediate = TRUE; + /* FALL THROUGH */ + case ft_enum: /* value from an enumeration */ + case ft_loose_enum: /* value from an enumeration with only some names known */ + DBG_log(" %s: %s", fp->name, enum_show(fp->desc, n)); + break; + case ft_set: /* bits representing set */ + DBG_log(" %s: %s", fp->name, bitnamesof(fp->desc, n)); + break; + default: + bad_case(fp->field_type); + } + inp += i; + break; + + case ft_raw: /* bytes to be left in network-order */ + { + char m[50]; /* arbitrary limit on name width in log */ + + snprintf(m, sizeof(m), " %s:", fp->name); + DBG_dump(m, inp, i); + inp += i; + } + break; + default: + bad_case(fp->field_type); + } + } +} + +static void +DBG_prefix_print_struct(const pb_stream *pbs +, const char *label, const void *struct_ptr +, struct_desc *sd, bool len_meaningful) +{ + /* print out a title, with a prefix of asterisks to show + * the nesting level. + */ + char space[40]; /* arbitrary limit on label+flock-of-* */ + size_t len = strlen(label); + + if (sizeof(space) <= len) + { + DBG_print_struct(label, struct_ptr, sd, len_meaningful); + } + else + { + const pb_stream *p = pbs; + char *pre = &space[sizeof(space) - (len + 1)]; + + strcpy(pre, label); + + /* put at least one * out */ + for (;;) + { + if (pre <= space) + break; + *--pre = '*'; + if (p == NULL) + break; + p = p->container; + } + DBG_print_struct(pre, struct_ptr, sd, len_meaningful); + } +} + +#endif + +/* "parse" a network struct into a host struct. + * + * This code assumes that the network and host structure + * members have the same alignment and size! This requires + * that all padding be explicit. + * + * If obj_pbs is supplied, a new pb_stream is created for the + * variable part of the structure (this depends on their + * being one length field in the structure). The cursor of this + * new PBS is set to after the parsed part of the struct. + * + * This routine returns TRUE iff it succeeds. + */ + +bool +in_struct(void *struct_ptr, struct_desc *sd +, pb_stream *ins, pb_stream *obj_pbs) +{ + err_t ugh = NULL; + u_int8_t *cur = ins->cur; + + if (ins->roof - cur < (ptrdiff_t)sd->size) + { + ugh = builddiag("not enough room in input packet for %s", sd->name); + } + else + { + u_int8_t *roof = cur + sd->size; /* may be changed by a length field */ + u_int8_t *outp = struct_ptr; + bool immediate = FALSE; + field_desc *fp; + + for (fp = sd->fields; ugh == NULL; fp++) + { + size_t i = fp->size; + + passert(ins->roof - cur >= (ptrdiff_t)i); + passert(cur - ins->cur <= (ptrdiff_t)(sd->size - i)); + passert(outp - (cur - ins->cur) == struct_ptr); + +#if 0 + DBG(DBG_PARSING, DBG_log("%d %s" + , (int) (cur - ins->cur), fp->name == NULL? "" : fp->name)); +#endif + switch (fp->field_type) + { + case ft_mbz: /* must be zero */ + for (; i != 0; i--) + { + if (*cur++ != 0) + { + ugh = builddiag("byte %d of %s must be zero, but is not" + , (int) (cur - ins->cur), sd->name); + break; + } + *outp++ = '\0'; /* probably redundant */ + } + break; + + case ft_nat: /* natural number (may be 0) */ + case ft_len: /* length of this struct and any following crud */ + case ft_lv: /* length/value field of attribute */ + case ft_enum: /* value from an enumeration */ + case ft_loose_enum: /* value from an enumeration with only some names known */ + case ft_af_enum: /* Attribute Format + value from an enumeration */ + case ft_af_loose_enum: /* Attribute Format + value from an enumeration */ + case ft_set: /* bits representing set */ + { + u_int32_t n = 0; + + for (; i != 0; i--) + n = (n << BITS_PER_BYTE) | *cur++; + + switch (fp->field_type) + { + case ft_len: /* length of this struct and any following crud */ + case ft_lv: /* length/value field of attribute */ + { + u_int32_t len = fp->field_type == ft_len? n + : immediate? sd->size : n + sd->size; + + if (len < sd->size) + { + ugh = builddiag("%s of %s is smaller than minimum" + , fp->name, sd->name); + } + else if (pbs_left(ins) < len) + { + ugh = builddiag("%s of %s is larger than can fit" + , fp->name, sd->name); + } + else + { + roof = ins->cur + len; + } + break; + } + case ft_af_loose_enum: /* Attribute Format + value from an enumeration */ + if ((n & ISAKMP_ATTR_AF_MASK) == ISAKMP_ATTR_AF_TV) + immediate = TRUE; + break; + case ft_af_enum: /* Attribute Format + value from an enumeration */ + if ((n & ISAKMP_ATTR_AF_MASK) == ISAKMP_ATTR_AF_TV) + immediate = TRUE; + /* FALL THROUGH */ + case ft_enum: /* value from an enumeration */ + if (enum_name(fp->desc, n) == NULL) + { + ugh = builddiag("%s of %s has an unknown value: %lu" + , fp->name, sd->name, (unsigned long)n); + } + /* FALL THROUGH */ + case ft_loose_enum: /* value from an enumeration with only some names known */ + break; + case ft_set: /* bits representing set */ + if (!testset(fp->desc, n)) + { + ugh = builddiag("bitset %s of %s has unknown member(s): %s" + , fp->name, sd->name, bitnamesof(fp->desc, n)); + } + break; + default: + break; + } + i = fp->size; + switch (i) + { + case 8/BITS_PER_BYTE: + *(u_int8_t *)outp = n; + break; + case 16/BITS_PER_BYTE: + *(u_int16_t *)outp = n; + break; + case 32/BITS_PER_BYTE: + *(u_int32_t *)outp = n; + break; + default: + bad_case(i); + } + outp += i; + break; + } + + case ft_raw: /* bytes to be left in network-order */ + for (; i != 0; i--) + { + *outp++ = *cur++; + } + break; + + case ft_end: /* end of field list */ + passert(cur == ins->cur + sd->size); + if (obj_pbs != NULL) + { + init_pbs(obj_pbs, ins->cur, roof - ins->cur, sd->name); + obj_pbs->container = ins; + obj_pbs->desc = sd; + obj_pbs->cur = cur; + } + ins->cur = roof; + DBG(DBG_PARSING + , DBG_prefix_print_struct(ins, "parse ", struct_ptr, sd, TRUE)); + return TRUE; + + default: + bad_case(fp->field_type); + } + } + } + + /* some failure got us here: report it */ + loglog(RC_LOG_SERIOUS, ugh); + return FALSE; +} + +bool +in_raw(void *bytes, size_t len, pb_stream *ins, const char *name) +{ + if (pbs_left(ins) < len) + { + loglog(RC_LOG_SERIOUS, "not enough bytes left to get %s from %s", name, ins->name); + return FALSE; + } + else + { + if (bytes == NULL) + { + DBG(DBG_PARSING + , DBG_log("skipping %u raw bytes of %s (%s)" + , (unsigned) len, ins->name, name); + DBG_dump(name, ins->cur, len)); + } + else + { + memcpy(bytes, ins->cur, len); + DBG(DBG_PARSING + , DBG_log("parsing %u raw bytes of %s into %s" + , (unsigned) len, ins->name, name); + DBG_dump(name, bytes, len)); + } + ins->cur += len; + return TRUE; + } +} + +/* "emit" a host struct into a network packet. + * + * This code assumes that the network and host structure + * members have the same alignment and size! This requires + * that all padding be explicit. + * + * If obj_pbs is non-NULL, its pbs describes a new output stream set up + * to contain the object. The cursor will be left at the variable part. + * This new stream must subsequently be finalized by close_output_pbs(). + * + * The value of any field of type ft_len is computed, not taken + * from the input struct. The length is actually filled in when + * the object's output stream is finalized. If obj_pbs is NULL, + * finalization is done by out_struct before it returns. + * + * This routine returns TRUE iff it succeeds. + */ + +bool +out_struct(const void *struct_ptr, struct_desc *sd +, pb_stream *outs, pb_stream *obj_pbs) +{ + err_t ugh = NULL; + const u_int8_t *inp = struct_ptr; + u_int8_t *cur = outs->cur; + + DBG(DBG_EMITTING + , DBG_prefix_print_struct(outs, "emit ", struct_ptr, sd, obj_pbs==NULL)); + + if (outs->roof - cur < (ptrdiff_t)sd->size) + { + ugh = builddiag("not enough room left in output packet to place %s" + , sd->name); + } + else + { + bool immediate = FALSE; + pb_stream obj; + field_desc *fp; + + obj.lenfld = NULL; /* until a length field is discovered */ + obj.lenfld_desc = NULL; + + for (fp = sd->fields; ugh == NULL; fp++) + { + size_t i = fp->size; + + passert(outs->roof - cur >= (ptrdiff_t)i); + passert(cur - outs->cur <= (ptrdiff_t)(sd->size - i)); + passert(inp - (cur - outs->cur) == struct_ptr); + +#if 0 + DBG(DBG_EMITTING, DBG_log("%d %s" + , (int) (cur - outs->cur), fp->name == NULL? "" : fp->name); +#endif + switch (fp->field_type) + { + case ft_mbz: /* must be zero */ + inp += i; + for (; i != 0; i--) + *cur++ = '\0'; + break; + case ft_nat: /* natural number (may be 0) */ + case ft_len: /* length of this struct and any following crud */ + case ft_lv: /* length/value field of attribute */ + case ft_enum: /* value from an enumeration */ + case ft_loose_enum: /* value from an enumeration with only some names known */ + case ft_af_enum: /* Attribute Format + value from an enumeration */ + case ft_af_loose_enum: /* Attribute Format + value from an enumeration */ + case ft_set: /* bits representing set */ + { + u_int32_t n = 0; + + switch (i) + { + case 8/BITS_PER_BYTE: + n = *(const u_int8_t *)inp; + break; + case 16/BITS_PER_BYTE: + n = *(const u_int16_t *)inp; + break; + case 32/BITS_PER_BYTE: + n = *(const u_int32_t *)inp; + break; + default: + bad_case(i); + } + + switch (fp->field_type) + { + case ft_len: /* length of this struct and any following crud */ + case ft_lv: /* length/value field of attribute */ + if (immediate) + break; /* not a length */ + /* We can't check the length because it will likely + * be filled in after variable part is supplied. + * We do record where this is so that it can be + * filled in by a subsequent close_output_pbs(). + */ + passert(obj.lenfld == NULL); /* only one ft_len allowed */ + obj.lenfld = cur; + obj.lenfld_desc = fp; + break; + case ft_af_loose_enum: /* Attribute Format + value from an enumeration */ + if ((n & ISAKMP_ATTR_AF_MASK) == ISAKMP_ATTR_AF_TV) + immediate = TRUE; + break; + case ft_af_enum: /* Attribute Format + value from an enumeration */ + if ((n & ISAKMP_ATTR_AF_MASK) == ISAKMP_ATTR_AF_TV) + immediate = TRUE; + /* FALL THROUGH */ + case ft_enum: /* value from an enumeration */ + if (enum_name(fp->desc, n) == NULL) + { + ugh = builddiag("%s of %s has an unknown value: %lu" + , fp->name, sd->name, (unsigned long)n); + } + /* FALL THROUGH */ + case ft_loose_enum: /* value from an enumeration with only some names known */ + break; + case ft_set: /* bits representing set */ + if (!testset(fp->desc, n)) + { + ugh = builddiag("bitset %s of %s has unknown member(s): %s" + , fp->name, sd->name, bitnamesof(fp->desc, n)); + } + break; + default: + break; + } + + while (i-- != 0) + { + cur[i] = (u_int8_t)n; + n >>= BITS_PER_BYTE; + } + inp += fp->size; + cur += fp->size; + break; + } + case ft_raw: /* bytes to be left in network-order */ + for (; i != 0; i--) + *cur++ = *inp++; + break; + case ft_end: /* end of field list */ + passert(cur == outs->cur + sd->size); + + obj.container = outs; + obj.desc = sd; + obj.name = sd->name; + obj.start = outs->cur; + obj.cur = cur; + obj.roof = outs->roof; /* limit of possible */ + /* obj.lenfld and obj.lenfld_desc already set */ + + if (obj_pbs == NULL) + { + close_output_pbs(&obj); /* fill in length field, if any */ + } + else + { + /* We set outs->cur to outs->roof so that + * any attempt to output something into outs + * before obj is closed will trigger an error. + */ + outs->cur = outs->roof; + + *obj_pbs = obj; + } + return TRUE; + + default: + bad_case(fp->field_type); + } + } + } + + /* some failure got us here: report it */ + loglog(RC_LOG_SERIOUS, ugh); /* ??? serious, but errno not relevant */ + return FALSE; +} + +bool +out_generic(u_int8_t np, struct_desc *sd +, pb_stream *outs, pb_stream *obj_pbs) +{ + struct isakmp_generic gen; + + passert(sd->fields == isakmp_generic_desc.fields); + gen.isag_np = np; + return out_struct(&gen, sd, outs, obj_pbs); +} + +bool +out_generic_raw(u_int8_t np, struct_desc *sd +, pb_stream *outs, const void *bytes, size_t len, const char *name) +{ + pb_stream pbs; + + if (!out_generic(np, sd, outs, &pbs) + || !out_raw(bytes, len, &pbs, name)) + return FALSE; + close_output_pbs(&pbs); + return TRUE; +} + +bool +out_raw(const void *bytes, size_t len, pb_stream *outs, const char *name) +{ + if (pbs_left(outs) < len) + { + loglog(RC_LOG_SERIOUS, "not enough room left to place %lu bytes of %s in %s" + , (unsigned long) len, name, outs->name); + return FALSE; + } + else + { + DBG(DBG_EMITTING + , DBG_log("emitting %u raw bytes of %s into %s" + , (unsigned) len, name, outs->name); + DBG_dump(name, bytes, len)); + memcpy(outs->cur, bytes, len); + outs->cur += len; + return TRUE; + } +} + +bool +out_zero(size_t len, pb_stream *outs, const char *name) +{ + if (pbs_left(outs) < len) + { + loglog(RC_LOG_SERIOUS, "not enough room left to place %s in %s", name, outs->name); + return FALSE; + } + else + { + DBG(DBG_EMITTING, DBG_log("emitting %u zero bytes of %s into %s" + , (unsigned) len, name, outs->name)); + memset(outs->cur, 0x00, len); + outs->cur += len; + return TRUE; + } +} + +/* Record current length. + * Note: currently, this may be repeated any number of times; + * the last one wins. + */ +void +close_output_pbs(pb_stream *pbs) +{ + if (pbs->lenfld != NULL) + { + u_int32_t len = pbs_offset(pbs); + int i = pbs->lenfld_desc->size; + + if (pbs->lenfld_desc->field_type == ft_lv) + len -= sizeof(struct isakmp_attribute); + DBG(DBG_EMITTING, DBG_log("emitting length of %s: %lu" + , pbs->name, (unsigned long) len)); + while (i-- != 0) + { + pbs->lenfld[i] = (u_int8_t)len; + len >>= BITS_PER_BYTE; + } + } + if (pbs->container != NULL) + pbs->container->cur = pbs->cur; /* pass space utilization up */ +} diff --git a/src/pluto/packet.h b/src/pluto/packet.h new file mode 100644 index 000000000..676a5e6cd --- /dev/null +++ b/src/pluto/packet.h @@ -0,0 +1,655 @@ +/* parsing packets: formats and tools + * Copyright (C) 1997 Angelos D. Keromytis. + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: packet.h,v 1.5 2005/01/06 22:10:15 as Exp $ + */ + +#ifndef _PACKET_H +#define _PACKET_H + +/* a struct_desc describes a structure for the struct I/O routines. + * This requires arrays of field_desc values to describe struct fields. + */ + +typedef const struct struct_desc { + const char *name; + const struct field_desc *fields; + size_t size; +} struct_desc; + +/* Note: if an ft_af_enum field has the ISAKMP_ATTR_AF_TV bit set, + * the subsequent ft_lv field will be interpreted as an immediate value. + * This matches how attributes are encoded. + * See RFC 2408 "ISAKMP" 3.3 + */ + +enum field_type { + ft_mbz, /* must be zero */ + ft_nat, /* natural number (may be 0) */ + ft_len, /* length of this struct and any following crud */ + ft_lv, /* length/value field of attribute */ + ft_enum, /* value from an enumeration */ + ft_loose_enum, /* value from an enumeration with only some names known */ + ft_af_loose_enum, /* Attribute Format + enumeration, some names known */ + ft_af_enum, /* Attribute Format + value from an enumeration */ + ft_set, /* bits representing set */ + ft_raw, /* bytes to be left in network-order */ + ft_end, /* end of field list */ +}; + +typedef const struct field_desc { + enum field_type field_type; + int size; /* size, in bytes, of field */ + const char *name; + const void *desc; /* enum_names for enum or char *[] for bits */ +} field_desc; + +/* The formatting of input and output of packets is done + * through packet_byte_stream objects. + * These describe a stream of bytes in memory. + * Several routines are provided to manipulate these objects + * Actual packet transfer is done elsewhere. + */ +typedef struct packet_byte_stream { + struct packet_byte_stream *container; /* PBS of which we are part */ + struct_desc *desc; + const char *name; /* what does this PBS represent? */ + u_int8_t + *start, + *cur, /* current position in stream */ + *roof; /* byte after last in PBS (actually just a limit on output) */ + /* For an output PBS, the length field will be filled in later so + * we need to record its particulars. Note: it may not be aligned. + */ + u_int8_t *lenfld; + field_desc *lenfld_desc; +} pb_stream; + +/* For an input PBS, pbs_offset is amount of stream processed. + * For an output PBS, pbs_offset is current size of stream. + * For an input PBS, pbs_room is size of stream. + * For an output PBS, pbs_room is maximum size allowed. + */ +#define pbs_offset(pbs) ((size_t)((pbs)->cur - (pbs)->start)) +#define pbs_room(pbs) ((size_t)((pbs)->roof - (pbs)->start)) +#define pbs_left(pbs) ((size_t)((pbs)->roof - (pbs)->cur)) + +extern void init_pbs(pb_stream *pbs, u_int8_t *start, size_t len, const char *name); + +extern bool in_struct(void *struct_ptr, struct_desc *sd, + pb_stream *ins, pb_stream *obj_pbs); +extern bool in_raw(void *bytes, size_t len, pb_stream *ins, const char *name); + +extern bool out_struct(const void *struct_ptr, struct_desc *sd, + pb_stream *outs, pb_stream *obj_pbs); +extern bool out_generic(u_int8_t np, struct_desc *sd, + pb_stream *outs, pb_stream *obj_pbs); +extern bool out_generic_raw(u_int8_t np, struct_desc *sd, + pb_stream *outs, const void *bytes, size_t len, const char *name); +#define out_generic_chunk(np, sd, outs, ch, name) \ + out_generic_raw(np, sd, outs, (ch).ptr, (ch).len, name) +extern bool out_zero(size_t len, pb_stream *outs, const char *name); +extern bool out_raw(const void *bytes, size_t len, pb_stream *outs, const char *name); +#define out_chunk(ch, outs, name) out_raw((ch).ptr, (ch).len, (outs), (name)) +extern void close_output_pbs(pb_stream *pbs); + +#ifdef DEBUG +extern void DBG_print_struct(const char *label, const void *struct_ptr, + struct_desc *sd, bool len_meaningful); +#endif + +/* ISAKMP Header: for all messages + * layout from RFC 2408 "ISAKMP" section 3.1 + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Initiator ! + * ! Cookie ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Responder ! + * ! Cookie ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! MjVer ! MnVer ! Exchange Type ! Flags ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Message ID ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * Although the drafts are a little unclear, there are a few + * places that specify that messages should be padded with 0x00 + * octets (bytes) to make the length a multiple of something. + * + * RFC 2408 "ISAKMP" 3.6 specifies that all messages will be + * padded to be a multiple of 4 octets in length. + * ??? This looks vestigial, and we ignore this requirement. + * + * RFC 2409 "IKE" Appedix B specifies: + * Each message should be padded up to the nearest block size + * using bytes containing 0x00. + * ??? This does not appear to be limited to encrypted messages, + * but it surely must be: the block size is meant to be the encryption + * block size, and that is meaningless for a non-encrypted message. + * + * RFC 2409 "IKE" 5.3 specifies: + * Encrypted payloads are padded up to the nearest block size. + * All padding bytes, except for the last one, contain 0x00. The + * last byte of the padding contains the number of the padding + * bytes used, excluding the last one. Note that this means there + * will always be padding. + * ??? This is nuts since payloads are not padded, messages are. + * It also contradicts Appendix B. So we ignore it. + * + * Summary: we pad encrypted output messages with 0x00 to bring them + * up to a multiple of the encryption block size. On input, we require + * that any encrypted portion of a message be a multiple of the encryption + * block size. After any decryption, we ignore padding (any bytes after + * the first payload that specifies a next payload of none; we don't + * require them to be zero). + */ + +struct isakmp_hdr +{ + u_int8_t isa_icookie[COOKIE_SIZE]; + u_int8_t isa_rcookie[COOKIE_SIZE]; + u_int8_t isa_np; /* Next payload */ + u_int8_t isa_version; /* high-order 4 bits: Major; low order 4: Minor */ +#define ISA_MAJ_SHIFT 4 +#define ISA_MIN_MASK (~((~0u) << ISA_MAJ_SHIFT)) + u_int8_t isa_xchg; /* Exchange type */ + u_int8_t isa_flags; + u_int32_t isa_msgid; /* Message ID (RAW) */ + u_int32_t isa_length; /* Length of message */ +}; + +extern struct_desc isakmp_hdr_desc; + +/* Generic portion of all ISAKMP payloads. + * layout from RFC 2408 "ISAKMP" section 3.2 + * This describes the first 32-bit chunk of all payloads. + * The previous next payload depends on the actual payload type. + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +struct isakmp_generic +{ + u_int8_t isag_np; + u_int8_t isag_reserved; + u_int16_t isag_length; +}; + +extern struct_desc isakmp_generic_desc; + +/* ISAKMP Data Attribute (generic representation within payloads) + * layout from RFC 2408 "ISAKMP" section 3.3 + * This is not a payload type. + * In TLV format, this is followed by a value field. + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * !A! Attribute Type ! AF=0 Attribute Length ! + * !F! ! AF=1 Attribute Value ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * . AF=0 Attribute Value . + * . AF=1 Not Transmitted . + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +struct isakmp_attribute +{ + /* The high order bit of isaat_af_type is the Attribute Format + * If it is off, the format is TLV: lv is the length of the following + * attribute value. + * If it is on, the format is TV: lv is the value of the attribute. + * ISAKMP_ATTR_AF_MASK is the mask in host form. + * + * The low order 15 bits of isaat_af_type is the Attribute Type. + * ISAKMP_ATTR_RTYPE_MASK is the mask in host form. + */ + u_int16_t isaat_af_type; /* high order bit: AF; lower 15: rtype */ + u_int16_t isaat_lv; /* Length or value */ +}; + +#define ISAKMP_ATTR_AF_MASK 0x8000 +#define ISAKMP_ATTR_AF_TV ISAKMP_ATTR_AF_MASK /* value in lv */ +#define ISAKMP_ATTR_AF_TLV 0 /* length in lv; value follows */ + +#define ISAKMP_ATTR_RTYPE_MASK 0x7FFF + +extern struct_desc + isakmp_oakley_attribute_desc, + isakmp_ipsec_attribute_desc; + +/* ISAKMP Security Association Payload + * layout from RFC 2408 "ISAKMP" section 3.4 + * A variable length Situation follows. + * Previous next payload: ISAKMP_NEXT_SA + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Domain of Interpretation (DOI) ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Situation ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +struct isakmp_sa +{ + u_int8_t isasa_np; /* Next payload */ + u_int8_t isasa_reserved; + u_int16_t isasa_length; /* Payload length */ + u_int32_t isasa_doi; /* DOI */ +}; + +extern struct_desc isakmp_sa_desc; + +extern struct_desc ipsec_sit_desc; + +/* ISAKMP Proposal Payload + * layout from RFC 2408 "ISAKMP" section 3.5 + * A variable length SPI follows. + * Previous next payload: ISAKMP_NEXT_P + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Proposal # ! Protocol-Id ! SPI Size !# of Transforms! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! SPI (variable) ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +struct isakmp_proposal +{ + u_int8_t isap_np; + u_int8_t isap_reserved; + u_int16_t isap_length; + u_int8_t isap_proposal; + u_int8_t isap_protoid; + u_int8_t isap_spisize; + u_int8_t isap_notrans; /* Number of transforms */ +}; + +extern struct_desc isakmp_proposal_desc; + +/* ISAKMP Transform Payload + * layout from RFC 2408 "ISAKMP" section 3.6 + * Variable length SA Attributes follow. + * Previous next payload: ISAKMP_NEXT_T + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Transform # ! Transform-Id ! RESERVED2 ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ SA Attributes ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +struct isakmp_transform +{ + u_int8_t isat_np; + u_int8_t isat_reserved; + u_int16_t isat_length; + u_int8_t isat_transnum; /* Number of the transform */ + u_int8_t isat_transid; + u_int16_t isat_reserved2; +}; + +extern struct_desc + isakmp_isakmp_transform_desc, + isakmp_ah_transform_desc, + isakmp_esp_transform_desc, + isakmp_ipcomp_transform_desc; + +/* ISAKMP Key Exchange Payload: no fixed fields beyond the generic ones. + * layout from RFC 2408 "ISAKMP" section 3.7 + * Variable Key Exchange Data follow the generic fields. + * Previous next payload: ISAKMP_NEXT_KE + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Key Exchange Data ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +extern struct_desc isakmp_keyex_desc; + +/* ISAKMP Identification Payload + * layout from RFC 2408 "ISAKMP" section 3.8 + * See "struct identity" declared later. + * Variable length Identification Data follow. + * Previous next payload: ISAKMP_NEXT_ID + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ID Type ! DOI Specific ID Data ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Identification Data ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +struct isakmp_id +{ + u_int8_t isaid_np; + u_int8_t isaid_reserved; + u_int16_t isaid_length; + u_int8_t isaid_idtype; + u_int8_t isaid_doi_specific_a; + u_int16_t isaid_doi_specific_b; +}; + +extern struct_desc isakmp_identification_desc; + +/* IPSEC Identification Payload Content + * layout from RFC 2407 "IPsec DOI" section 4.6.2 + * See struct isakmp_id declared earlier. + * Note: Hashing skips the ISAKMP generic payload header + * Variable length Identification Data follow. + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ID Type ! Protocol ID ! Port ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ~ Identification Data ~ + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +struct isakmp_ipsec_id +{ + u_int8_t isaiid_np; + u_int8_t isaiid_reserved; + u_int16_t isaiid_length; + u_int8_t isaiid_idtype; + u_int8_t isaiid_protoid; + u_int16_t isaiid_port; +}; + +extern struct_desc isakmp_ipsec_identification_desc; + +/* ISAKMP Certificate Payload: no fixed fields beyond the generic ones. + * layout from RFC 2408 "ISAKMP" section 3.9 + * Variable length Certificate Data follow the generic fields. + * Previous next payload: ISAKMP_NEXT_CERT. + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Cert Encoding ! ! + * +-+-+-+-+-+-+-+-+ ! + * ~ Certificate Data ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +struct isakmp_cert +{ + u_int8_t isacert_np; + u_int8_t isacert_reserved; + u_int16_t isacert_length; + u_int8_t isacert_type; +}; + +/* NOTE: this packet type has a fixed portion that is not a + * multiple of 4 octets. This means that sizeof(struct isakmp_cert) + * yields the wrong value for the length. + */ +#define ISAKMP_CERT_SIZE 5 + +extern struct_desc isakmp_ipsec_certificate_desc; + +/* ISAKMP Certificate Request Payload: no fixed fields beyond the generic ones. + * layout from RFC 2408 "ISAKMP" section 3.10 + * Variable length Certificate Types and Certificate Authorities follow. + * Previous next payload: ISAKMP_NEXT_CR. + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Cert. Type ! ! + * +-+-+-+-+-+-+-+-+ ! + * ~ Certificate Authority ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +struct isakmp_cr +{ + u_int8_t isacr_np; + u_int8_t isacr_reserved; + u_int16_t isacr_length; + u_int8_t isacr_type; +}; + +/* NOTE: this packet type has a fixed portion that is not a + * multiple of 4 octets. This means that sizeof(struct isakmp_cr) + * yields the wrong value for the length. + */ +#define ISAKMP_CR_SIZE 5 + +extern struct_desc isakmp_ipsec_cert_req_desc; + +/* ISAKMP Hash Payload: no fixed fields beyond the generic ones. + * layout from RFC 2408 "ISAKMP" section 3.11 + * Variable length Hash Data follow. + * Previous next payload: ISAKMP_NEXT_HASH. + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Hash Data ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +extern struct_desc isakmp_hash_desc; + +/* ISAKMP Signature Payload: no fixed fields beyond the generic ones. + * layout from RFC 2408 "ISAKMP" section 3.12 + * Variable length Signature Data follow. + * Previous next payload: ISAKMP_NEXT_SIG. + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Signature Data ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +extern struct_desc isakmp_signature_desc; + +/* ISAKMP Nonce Payload: no fixed fields beyond the generic ones. + * layout from RFC 2408 "ISAKMP" section 3.13 + * Variable length Nonce Data follow. + * Previous next payload: ISAKMP_NEXT_NONCE. + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Nonce Data ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +extern struct_desc isakmp_nonce_desc; + +/* ISAKMP Notification Payload + * layout from RFC 2408 "ISAKMP" section 3.14 + * This is followed by a variable length SPI + * and then possibly by variable length Notification Data. + * Previous next payload: ISAKMP_NEXT_N + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Domain of Interpretation (DOI) ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Protocol-ID ! SPI Size ! Notify Message Type ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Security Parameter Index (SPI) ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Notification Data ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +struct isakmp_notification +{ + u_int8_t isan_np; + u_int8_t isan_reserved; + u_int16_t isan_length; + u_int32_t isan_doi; + u_int8_t isan_protoid; + u_int8_t isan_spisize; + u_int16_t isan_type; +}; + +extern struct_desc isakmp_notification_desc; + +/* ISAKMP Delete Payload + * layout from RFC 2408 "ISAKMP" section 3.15 + * This is followed by a variable length SPI. + * Previous next payload: ISAKMP_NEXT_D + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Domain of Interpretation (DOI) ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Protocol-Id ! SPI Size ! # of SPIs ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Security Parameter Index(es) (SPI) ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +struct isakmp_delete +{ + u_int8_t isad_np; + u_int8_t isad_reserved; + u_int16_t isad_length; + u_int32_t isad_doi; + u_int8_t isad_protoid; + u_int8_t isad_spisize; + u_int16_t isad_nospi; +}; + +extern struct_desc isakmp_delete_desc; + +/* From draft-dukes-ike-mode-cfg +3.2. Attribute Payload + 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ! Next Payload ! RESERVED ! Payload Length ! + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ! Type ! RESERVED ! Identifier ! + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ! ! + ! ! + ~ Attributes ~ + ! ! + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +struct isakmp_mode_attr +{ + u_int8_t isama_np; + u_int8_t isama_reserved; + u_int16_t isama_length; + u_int8_t isama_type; + u_int8_t isama_reserved2; + u_int16_t isama_identifier; +}; + +extern struct_desc isakmp_attr_desc; +extern struct_desc isakmp_modecfg_attribute_desc; + +/* ISAKMP Vendor ID Payload + * layout from RFC 2408 "ISAKMP" section 3.15 + * This is followed by a variable length VID. + * Previous next payload: ISAKMP_NEXT_VID + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! Next Payload ! RESERVED ! Payload Length ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! ! + * ~ Vendor ID (VID) ~ + * ! ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +extern struct_desc isakmp_vendor_id_desc; + +struct isakmp_nat_oa +{ + u_int8_t isanoa_np; + u_int8_t isanoa_reserved_1; + u_int16_t isanoa_length; + u_int8_t isanoa_idtype; + u_int8_t isanoa_reserved_2; + u_int16_t isanoa_reserved_3; +}; + +extern struct_desc isakmp_nat_d; +extern struct_desc isakmp_nat_oa; + +/* union of all payloads */ + +union payload { + struct isakmp_generic generic; + struct isakmp_sa sa; + struct isakmp_proposal proposal; + struct isakmp_transform transform; + struct isakmp_id id; /* Main Mode */ + struct isakmp_cert cert; + struct isakmp_cr cr; + struct isakmp_ipsec_id ipsec_id; /* Quick Mode */ + struct isakmp_notification notification; + struct isakmp_delete delete; + struct isakmp_nat_oa nat_oa; + struct isakmp_mode_attr attribute; +}; + +/* descriptor for each payload type + * + * There is a slight problem in that some payloads differ, depending + * on the mode. Since this is table only used for top-level payloads, + * Proposal and Transform payloads need not be handled. + * That leaves only Identification payloads as a problem. + * We make all these entries NULL + */ +extern struct_desc *const payload_descs[ISAKMP_NEXT_ROOF]; + +#endif /* _PACKET_H */ diff --git a/src/pluto/pem.c b/src/pluto/pem.c new file mode 100644 index 000000000..db6d0d7e3 --- /dev/null +++ b/src/pluto/pem.c @@ -0,0 +1,463 @@ +/* Loading of PEM encoded files with optional encryption + * Copyright (C) 2001-2004 Andreas Steffen, Zuercher Hochschule Winterthur + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: pem.c,v 1.4 2005/08/17 16:31:24 as Exp $ + */ + +/* decrypt a PEM encoded data block using DES-EDE3-CBC + * see RFC 1423 PEM: Algorithms, Modes and Identifiers + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <stddef.h> +#include <sys/types.h> + +#include <freeswan.h> +#define HEADER_DES_LOCL_H /* stupid trick to force prototype decl in <des.h> */ +#include <libdes/des.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "md5.h" +#include "whack.h" +#include "pem.h" + +/* + * check the presence of a pattern in a character string + */ +static bool +present(const char* pattern, chunk_t* ch) +{ + u_int pattern_len = strlen(pattern); + + if (ch->len >= pattern_len && strncmp(ch->ptr, pattern, pattern_len) == 0) + { + ch->ptr += pattern_len; + ch->len -= pattern_len; + return TRUE; + } + return FALSE; +} + +/* + * compare string with chunk + */ +static bool +match(const char *pattern, const chunk_t *ch) +{ + return ch->len == strlen(pattern) && + strncmp(pattern, ch->ptr, ch->len) == 0; +} + +/* + * find a boundary of the form -----tag name----- + */ +static bool +find_boundary(const char* tag, chunk_t *line) +{ + chunk_t name = empty_chunk; + + if (!present("-----", line)) + return FALSE; + if (!present(tag, line)) + return FALSE; + if (*line->ptr != ' ') + return FALSE; + line->ptr++; line->len--; + + /* extract name */ + name.ptr = line->ptr; + while (line->len > 0) + { + if (present("-----", line)) + { + DBG(DBG_PARSING, + DBG_log(" -----%s %.*s-----", + tag, (int)name.len, name.ptr); + ) + return TRUE; + } + line->ptr++; line->len--; name.len++; + } + return FALSE; +} + +/* + * eat whitespace + */ +static void +eat_whitespace(chunk_t *src) +{ + while (src->len > 0 && (*src->ptr == ' ' || *src->ptr == '\t')) + { + src->ptr++; src->len--; + } +} + +/* + * extracts a token ending with a given termination symbol + */ +static bool +extract_token(chunk_t *token, char termination, chunk_t *src) +{ + u_char *eot = memchr(src->ptr, termination, src->len); + + /* initialize empty token */ + *token = empty_chunk; + + if (eot == NULL) /* termination symbol not found */ + return FALSE; + + /* extract token */ + token->ptr = src->ptr; + token->len = (u_int)(eot - src->ptr); + + /* advance src pointer after termination symbol */ + src->ptr = eot + 1; + src->len -= (token->len + 1); + + return TRUE; +} + +/* + * extracts a name: value pair from the PEM header + */ +static bool +extract_parameter(chunk_t *name, chunk_t *value, chunk_t *line) +{ + DBG(DBG_PARSING, + DBG_log(" %.*s", (int)line->len, line->ptr); + ) + + /* extract name */ + if (!extract_token(name,':', line)) + return FALSE; + + eat_whitespace(line); + + /* extract value */ + *value = *line; + return TRUE; +} + +/* + * fetches a new line terminated by \n or \r\n + */ +static bool +fetchline(chunk_t *src, chunk_t *line) +{ + if (src->len == 0) /* end of src reached */ + return FALSE; + + if (extract_token(line, '\n', src)) + { + if (line->len > 0 && *(line->ptr + line->len -1) == '\r') + line->len--; /* remove optional \r */ + } + else /*last line ends without newline */ + { + *line = *src; + src->ptr += src->len; + src->len = 0; + } + return TRUE; +} + +/* + * decrypts a DES-EDE-CBC encrypted data block + */ +static bool +pem_decrypt_3des(chunk_t *blob, chunk_t *iv, const char *passphrase) +{ + MD5_CTX context; + u_char digest[MD5_DIGEST_SIZE]; + u_char des_iv[DES_CBC_BLOCK_SIZE]; + u_char key[24]; + des_cblock *deskey = (des_cblock *)key; + des_key_schedule ks[3]; + u_char padding, *last_padding_pos, *first_padding_pos; + + /* Convert passphrase to 3des key */ + MD5Init(&context); + MD5Update(&context, passphrase, strlen(passphrase)); + MD5Update(&context, iv->ptr, iv->len); + MD5Final(digest, &context); + + memcpy(key, digest, MD5_DIGEST_SIZE); + + MD5Init(&context); + MD5Update(&context, digest, MD5_DIGEST_SIZE); + MD5Update(&context, passphrase, strlen(passphrase)); + MD5Update(&context, iv->ptr, iv->len); + MD5Final(digest, &context); + + memcpy(key + MD5_DIGEST_SIZE, digest, 24 - MD5_DIGEST_SIZE); + + (void) des_set_key(&deskey[0], ks[0]); + (void) des_set_key(&deskey[1], ks[1]); + (void) des_set_key(&deskey[2], ks[2]); + + /* decrypt data block */ + memcpy(des_iv, iv->ptr, DES_CBC_BLOCK_SIZE); + des_ede3_cbc_encrypt((des_cblock *)blob->ptr, (des_cblock *)blob->ptr, + blob->len, ks[0], ks[1], ks[2], (des_cblock *)des_iv, FALSE); + + /* determine amount of padding */ + last_padding_pos = blob->ptr + blob->len - 1; + padding = *last_padding_pos; + first_padding_pos = (padding > blob->len)? + blob->ptr : last_padding_pos - padding; + + /* check the padding pattern */ + while (--last_padding_pos > first_padding_pos) + { + if (*last_padding_pos != padding) + return FALSE; + } + + /* remove padding */ + blob->len -= padding; + return TRUE; +} + +/* + * optionally prompts for a passphrase before decryption + * currently we support DES-EDE3-CBC, only + */ +static err_t +pem_decrypt(chunk_t *blob, chunk_t *iv, prompt_pass_t *pass, const char* label) +{ + DBG(DBG_CRYPT, + DBG_log(" decrypting file using 'DES-EDE3-CBC'"); + ) + if (iv->len != DES_CBC_BLOCK_SIZE) + return "size of DES-EDE3-CBC IV is not 8 bytes"; + + if (pass == NULL) + return "no passphrase available"; + + /* do we prompt for the passphrase? */ + if (pass->prompt && pass->fd != NULL_FD) + { + int i; + chunk_t blob_copy; + err_t ugh = "invalid passphrase, too many trials"; + + whack_log(RC_ENTERSECRET, "need passphrase for '%s'", label); + + for (i = 0; i < MAX_PROMPT_PASS_TRIALS; i++) + { + int n; + + if (i > 0) + whack_log(RC_ENTERSECRET, "invalid passphrase, please try again"); + + n = read(pass->fd, pass->secret, PROMPT_PASS_LEN); + + if (n == -1) + { + err_t ugh = "read(whackfd) failed"; + + whack_log(RC_LOG_SERIOUS,ugh); + return ugh; + } + + pass->secret[n-1] = '\0'; + + if (strlen(pass->secret) == 0) + { + err_t ugh = "no passphrase entered, aborted"; + + whack_log(RC_LOG_SERIOUS, ugh); + return ugh; + } + + clonetochunk(blob_copy, blob->ptr, blob->len, "blob copy"); + + if (pem_decrypt_3des(blob, iv, pass->secret)) + { + whack_log(RC_SUCCESS, "valid passphrase"); + pfree(blob_copy.ptr); + return NULL; + } + + /* blob is useless after wrong decryption, restore the original */ + pfree(blob->ptr); + *blob = blob_copy; + } + whack_log(RC_LOG_SERIOUS, ugh); + return ugh; + } + else + { + if (pem_decrypt_3des(blob, iv, pass->secret)) + return NULL; + else + return "invalid passphrase"; + } +} + +/* Converts a PEM encoded file into its binary form + * + * RFC 1421 Privacy Enhancement for Electronic Mail, February 1993 + * RFC 934 Message Encapsulation, January 1985 + */ +err_t +pemtobin(chunk_t *blob, prompt_pass_t *pass, const char* label, bool *pgp) +{ + typedef enum { + PEM_PRE = 0, + PEM_MSG = 1, + PEM_HEADER = 2, + PEM_BODY = 3, + PEM_POST = 4, + PEM_ABORT = 5 + } state_t; + + bool encrypted = FALSE; + + state_t state = PEM_PRE; + + chunk_t src = *blob; + chunk_t dst = *blob; + chunk_t line = empty_chunk; + chunk_t iv = empty_chunk; + + u_char iv_buf[MAX_DIGEST_LEN]; + + /* zero size of converted blob */ + dst.len = 0; + + /* zero size of IV */ + iv.ptr = iv_buf; + iv.len = 0; + + while (fetchline(&src, &line)) + { + if (state == PEM_PRE) + { + if (find_boundary("BEGIN", &line)) + { + *pgp = FALSE; + state = PEM_MSG; + } + continue; + } + else + { + if (find_boundary("END", &line)) + { + state = PEM_POST; + break; + } + if (state == PEM_MSG) + { + state = (memchr(line.ptr, ':', line.len) == NULL)? + PEM_BODY : PEM_HEADER; + } + if (state == PEM_HEADER) + { + chunk_t name = empty_chunk; + chunk_t value = empty_chunk; + + /* an empty line separates HEADER and BODY */ + if (line.len == 0) + { + state = PEM_BODY; + continue; + } + + /* we are looking for a name: value pair */ + if (!extract_parameter(&name, &value, &line)) + continue; + + if (match("Proc-Type", &name) && *value.ptr == '4') + encrypted = TRUE; + else if (match("DEK-Info", &name)) + { + const char *ugh = NULL; + size_t len = 0; + chunk_t dek; + + if (!extract_token(&dek, ',', &value)) + dek = value; + + /* we support DES-EDE3-CBC encrypted files, only */ + if (!match("DES-EDE3-CBC", &dek)) + return "we support DES-EDE3-CBC encrypted files, only"; + + eat_whitespace(&value); + ugh = ttodata(value.ptr, value.len, 16, + iv.ptr, MAX_DIGEST_LEN, &len); + if (ugh) + return "error in IV"; + + iv.len = len; + } + } + else /* state is PEM_BODY */ + { + const char *ugh = NULL; + size_t len = 0; + chunk_t data; + + /* remove any trailing whitespace */ + if (!extract_token(&data ,' ', &line)) + data = line; + + /* check for PGP armor checksum */ + if (*data.ptr == '=') + { + *pgp = TRUE; + data.ptr++; + data.len--; + DBG(DBG_PARSING, + DBG_log(" Armor checksum: %.*s", (int)data.len, data.ptr); + ) + continue; + } + + ugh = ttodata(data.ptr, data.len, 64, + dst.ptr, blob->len - dst.len, &len); + if (ugh) + { + DBG(DBG_PARSING, + DBG_log(" %s", ugh); + ) + state = PEM_ABORT; + break; + } + else + { + dst.ptr += len; + dst.len += len; + } + } + } + } + /* set length to size of binary blob */ + blob->len = dst.len; + + if (state != PEM_POST) + return "file coded in unknown format, discarded"; + + if (encrypted) + return pem_decrypt(blob, &iv, pass, label); + else + return NULL; +} diff --git a/src/pluto/pem.h b/src/pluto/pem.h new file mode 100644 index 000000000..815b5d85b --- /dev/null +++ b/src/pluto/pem.h @@ -0,0 +1,18 @@ +/* Loading of PEM encoded files with optional encryption + * Copyright (C) 2001-2004 Andreas Steffen, Zuercher Hochschule Winterthur + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: pem.h,v 1.1 2004/03/15 20:35:28 as Exp $ + */ + +extern err_t pemtobin(chunk_t *blob, prompt_pass_t *pass, const char* label + , bool *pgp); diff --git a/src/pluto/pgp.c b/src/pluto/pgp.c new file mode 100644 index 000000000..307303f6b --- /dev/null +++ b/src/pluto/pgp.c @@ -0,0 +1,647 @@ +/* Support of OpenPGP certificates + * Copyright (C) 2002-2004 Andreas Steffen, Zuercher Hochschule Winterthur + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: pgp.c,v 1.7 2006/01/04 21:00:43 as Exp $ + */ + +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include <freeswan.h> +#include <ipsec_policy.h> + +#include "constants.h" +#include "defs.h" +#include "mp_defs.h" +#include "log.h" +#include "id.h" +#include "pgp.h" +#include "certs.h" +#include "md5.h" +#include "whack.h" +#include "pkcs1.h" +#include "keys.h" + +/* + * chained list of OpenPGP end certificates + */ +static pgpcert_t *pgpcerts = NULL; + +/* + * OpenPGP packet tags defined in section 4.3 of RFC 2440 + */ +#define PGP_PKT_RESERVED 0 +#define PGP_PKT_PUBKEY_ENC_SESSION_KEY 1 +#define PGP_PKT_SIGNATURE 2 +#define PGP_PKT_SYMKEY_ENC_SESSION_KEY 3 +#define PGP_PKT_ONE_PASS_SIGNATURE_PKT 4 +#define PGP_PKT_SECRET_KEY 5 +#define PGP_PKT_PUBLIC_KEY 6 +#define PGP_PKT_SECRET_SUBKEY 7 +#define PGP_PKT_COMPRESSED_DATA 8 +#define PGP_PKT_SYMKEY_ENC_DATA 9 +#define PGP_PKT_MARKER 10 +#define PGP_PKT_LITERAL_DATA 11 +#define PGP_PKT_TRUST 12 +#define PGP_PKT_USER_ID 13 +#define PGP_PKT_PUBLIC_SUBKEY 14 +#define PGP_PKT_ROOF 15 + +static const char *const pgp_packet_type_name[] = { + "Reserved", + "Public-Key Encrypted Session Key Packet", + "Signature Packet", + "Symmetric-Key Encrypted Session Key Packet", + "One-Pass Signature Packet", + "Secret Key Packet", + "Public Key Packet", + "Secret Subkey Packet", + "Compressed Data Packet", + "Symmetrically Encrypted Data Packet", + "Marker Packet", + "Literal Data Packet", + "Trust Packet", + "User ID Packet", + "Public Subkey Packet" +}; + +/* + * OpenPGP public key algorithms defined in section 9.1 of RFC 2440 + */ +#define PGP_PUBKEY_ALG_RSA 1 +#define PGP_PUBKEY_ALG_RSA_ENC_ONLY 2 +#define PGP_PUBKEY_ALG_RSA_SIGN_ONLY 3 +#define PGP_PUBKEY_ALG_ELGAMAL_ENC_ONLY 16 +#define PGP_PUBKEY_ALG_DSA 17 +#define PGP_PUBKEY_ALG_ECC 18 +#define PGP_PUBKEY_ALG_ECDSA 19 +#define PGP_PUBKEY_ALG_ELGAMAL 20 + +/* + * OpenPGP symmetric key algorithms defined in section 9.2 of RFC 2440 + */ +#define PGP_SYM_ALG_PLAIN 0 +#define PGP_SYM_ALG_IDEA 1 +#define PGP_SYM_ALG_3DES 2 +#define PGP_SYM_ALG_CAST5 3 +#define PGP_SYM_ALG_BLOWFISH 4 +#define PGP_SYM_ALG_SAFER 5 +#define PGP_SYM_ALG_DES 6 +#define PGP_SYM_ALG_AES 7 +#define PGP_SYM_ALG_AES_192 8 +#define PGP_SYM_ALG_AES_256 9 +#define PGP_SYM_ALG_TWOFISH 10 +#define PGP_SYM_ALG_ROOF 11 + +static const char *const pgp_sym_alg_name[] = { + "Plaintext", + "IDEA", + "3DES", + "CAST5", + "Blowfish", + "SAFER", + "DES", + "AES", + "AES-192", + "AES-256", + "Twofish" +}; + +/* + * Size of PGP Key ID + */ +#define PGP_KEYID_SIZE 8 + +const pgpcert_t empty_pgpcert = { + NULL , /* *next */ + 0 , /* installed */ + 0 , /* count */ + { NULL, 0 }, /* certificate */ + 0 , /* created */ + 0 , /* until */ + 0 , /* pubkeyAlgorithm */ + { NULL, 0 }, /* modulus */ + { NULL, 0 }, /* publicExponent */ + "" /* fingerprint */ +}; + +static size_t +pgp_size(chunk_t *blob, int len) +{ + size_t size = 0; + + blob->len -= len; + while (len-- > 0) + size = 256*size + *blob->ptr++; + return size; +} + +/* + * extracts the length of a PGP packet + */ +static size_t +pgp_old_packet_length(chunk_t *blob) +{ + /* bits 0 and 1 define the packet length type */ + int len_type = 0x03 & *blob->ptr++; + + blob->len--; + + /* len_type: 0 -> 1 byte, 1 -> 2 bytes, 2 -> 4 bytes */ + return pgp_size(blob, (len_type == 0)? 1: len_type << 1); +} + +/* + * extracts PGP packet version (V3 or V4) + */ +static u_char +pgp_version(chunk_t *blob) +{ + u_char version = *blob->ptr++; + blob->len--; + DBG(DBG_PARSING, + DBG_log("L3 - version:"); + DBG_log(" V%d", version) + ) + return version; +} + +/* + * Parse OpenPGP public key packet defined in section 5.5.2 of RFC 2440 + */ +static bool +parse_pgp_pubkey_packet(chunk_t *packet, pgpcert_t *cert) +{ + u_char version = pgp_version(packet); + + if (version < 3 || version > 4) + { + plog("PGP packet version V%d not supported", version); + return FALSE; + } + + /* creation date - 4 bytes */ + cert->created = (time_t)pgp_size(packet, 4); + DBG(DBG_PARSING, + DBG_log("L3 - created:"); + DBG_log(" %s", timetoa(&cert->created, TRUE)) + ) + + if (version == 3) + { + /* validity in days - 2 bytes */ + cert->until = (time_t)pgp_size(packet, 2); + + /* validity of 0 days means that the key never expires */ + if (cert->until > 0) + cert->until = cert->created + 24*3600*cert->until; + + DBG(DBG_PARSING, + DBG_log("L3 - until:"); + DBG_log(" %s", timetoa(&cert->until, TRUE)); + ) + } + + /* public key algorithm - 1 byte */ + DBG(DBG_PARSING, + DBG_log("L3 - public key algorithm:") + ) + + switch (pgp_size(packet, 1)) + { + case PGP_PUBKEY_ALG_RSA: + case PGP_PUBKEY_ALG_RSA_SIGN_ONLY: + cert->pubkeyAlg = PUBKEY_ALG_RSA; + DBG(DBG_PARSING, + DBG_log(" RSA") + ) + /* modulus n */ + cert->modulus.len = (pgp_size(packet, 2)+7) / BITS_PER_BYTE; + cert->modulus.ptr = packet->ptr; + packet->ptr += cert->modulus.len; + packet->len -= cert->modulus.len; + DBG(DBG_PARSING, + DBG_log("L3 - modulus:") + ) + DBG_cond_dump_chunk(DBG_RAW, "", cert->modulus); + + /* public exponent e */ + cert->publicExponent.len = (pgp_size(packet, 2)+7) / BITS_PER_BYTE; + cert->publicExponent.ptr = packet->ptr; + packet->ptr += cert->publicExponent.len; + packet->len -= cert->publicExponent.len; + DBG(DBG_PARSING, + DBG_log("L3 - public exponent:") + ) + DBG_cond_dump_chunk(DBG_RAW, "", cert->publicExponent); + + if (version == 3) + { + /* a V3 fingerprint is the MD5 hash of modulus and public exponent */ + MD5_CTX context; + MD5Init(&context); + MD5Update(&context, cert->modulus.ptr, cert->modulus.len); + MD5Update(&context, cert->publicExponent.ptr, cert->publicExponent.len); + MD5Final(cert->fingerprint, &context); + } + else + { + plog(" computation of V4 key ID not implemented yet"); + } + break; + case PGP_PUBKEY_ALG_DSA: + cert->pubkeyAlg = PUBKEY_ALG_DSA; + DBG(DBG_PARSING, + DBG_log(" DSA") + ) + plog(" DSA public keys not supported"); + return FALSE; + default: + cert->pubkeyAlg = 0; + DBG(DBG_PARSING, + DBG_log(" other") + ) + plog(" exotic not RSA public keys not supported"); + return FALSE; + } + return TRUE; +} + +/* + * Parse OpenPGP secret key packet defined in section 5.5.3 of RFC 2440 + */ +static bool +parse_pgp_secretkey_packet(chunk_t *packet, RSA_private_key_t *key) +{ + int i, s2k; + pgpcert_t cert = empty_pgpcert; + + if (!parse_pgp_pubkey_packet(packet, &cert)) + return FALSE; + + init_RSA_public_key((RSA_public_key_t *)key, cert.publicExponent + , cert.modulus); + + /* string-to-key usage */ + s2k = pgp_size(packet, 1); + + DBG(DBG_PARSING, + DBG_log("L3 - string-to-key: %d", s2k) + ) + + if (s2k == 255) + { + plog(" string-to-key specifiers not supported"); + return FALSE; + } + + if (s2k >= PGP_SYM_ALG_ROOF) + { + plog(" undefined symmetric key algorithm"); + return FALSE; + } + + /* a known symmetric key algorithm is specified*/ + DBG(DBG_PARSING, + DBG_log(" %s", pgp_sym_alg_name[s2k]) + ) + + /* private key is unencrypted */ + if (s2k == PGP_SYM_ALG_PLAIN) + { + for (i = 2; i < RSA_PRIVATE_FIELD_ELEMENTS; i++) + { + mpz_t u; /* auxiliary variable */ + + /* compute offset to private key component i*/ + MP_INT *n = (MP_INT*)((char *)key + RSA_private_field[i].offset); + + switch (i) + { + case 2: + case 3: + case 4: + { + size_t len = (pgp_size(packet, 2)+7) / BITS_PER_BYTE; + + n_to_mpz(n, packet->ptr, len); + DBG(DBG_PARSING, + DBG_log("L3 - %s:", RSA_private_field[i].name) + ) + DBG_cond_dump(DBG_PRIVATE, "", packet->ptr, len); + packet->ptr += len; + packet->len -= len; + } + break; + case 5: /* dP = d mod (p-1) */ + mpz_init(u); + mpz_sub_ui(u, &key->p, 1); + mpz_mod(n, &key->d, u); + mpz_clear(u); + break; + case 6: /* dQ = d mod (q-1) */ + mpz_init(u); + mpz_sub_ui(u, &key->q, 1); + mpz_mod(n, &key->d, u); + mpz_clear(u); + break; + case 7: /* qInv = (q^-1) mod p */ + mpz_invert(n, &key->q, &key->p); + if (mpz_cmp_ui(n, 0) < 0) + mpz_add(n, n, &key->p); + passert(mpz_cmp(n, &key->p) < 0); + break; + } + } + return TRUE; + } + + plog(" %s encryption not supported", pgp_sym_alg_name[s2k]); + return FALSE; +} + +/* + * Parse OpenPGP signature packet defined in section 5.2.2 of RFC 2440 + */ +static bool +parse_pgp_signature_packet(chunk_t *packet, pgpcert_t *cert) +{ + time_t created; + chunk_t keyid; + u_char sig_type; + u_char version = pgp_version(packet); + + /* we parse only V3 signature packets */ + if (version != 3) + return TRUE; + + /* size byte must have the value 5 */ + if (pgp_size(packet, 1) != 5) + { + plog(" size must be 5"); + return FALSE; + } + + /* signature type - 1 byte */ + sig_type = (u_char)pgp_size(packet, 1); + DBG(DBG_PARSING, + DBG_log("L3 - signature type: 0x%2x", sig_type) + ) + + /* creation date - 4 bytes */ + created = (time_t)pgp_size(packet, 4); + DBG(DBG_PARSING, + DBG_log("L3 - created:"); + DBG_log(" %s", timetoa(&cert->created, TRUE)) + ) + + /* key ID of signer - 8 bytes */ + keyid.ptr = packet->ptr; + keyid.len = PGP_KEYID_SIZE; + DBG_cond_dump_chunk(DBG_PARSING, "L3 - key ID of signer", keyid); + + return TRUE; +} + +bool +parse_pgp(chunk_t blob, pgpcert_t *cert, RSA_private_key_t *key) +{ + DBG(DBG_PARSING, + DBG_log("L0 - PGP file:") + ) + DBG_cond_dump_chunk(DBG_RAW, "", blob); + + if (cert != NULL) + { + /* parse a PGP certificate file */ + cert->certificate = blob; + time(&cert->installed); + } + else if (key == NULL) + { + /* should not occur, nothing to parse */ + return FALSE; + } + + while (blob.len > 0) + { + chunk_t packet = empty_chunk; + u_char packet_tag = *blob.ptr; + + DBG(DBG_PARSING, + DBG_log("L1 - PGP packet: tag= 0x%2x", packet_tag) + ) + + /* bit 7 must be set */ + if (!(packet_tag & 0x80)) + { + plog(" incorrect Packet Tag"); + return FALSE; + } + + /* bit 6 set defines new packet format */ + if (packet_tag & 0x40) + { + plog(" new PGP packet format not supported"); + return FALSE; + } + else + { + int packet_type = (packet_tag & 0x3C) >> 2; + + packet.len = pgp_old_packet_length(&blob); + packet.ptr = blob.ptr; + blob.ptr += packet.len; + blob.len -= packet.len; + DBG(DBG_PARSING, + DBG_log(" %s (%d), old format, %d bytes", + (packet_type < PGP_PKT_ROOF) ? + pgp_packet_type_name[packet_type] : + "Undefined Packet Type", packet_type, (int)packet.len); + DBG_log("L2 - body:") + ) + DBG_cond_dump_chunk(DBG_RAW, "", packet); + + if (cert != NULL) + { + /* parse a PGP certificate */ + switch (packet_type) + { + case PGP_PKT_PUBLIC_KEY: + if (!parse_pgp_pubkey_packet(&packet, cert)) + return FALSE; + break; + case PGP_PKT_SIGNATURE: + if (!parse_pgp_signature_packet(&packet, cert)) + return FALSE; + break; + case PGP_PKT_USER_ID: + DBG(DBG_PARSING, + DBG_log("L3 - user ID:"); + DBG_log(" '%.*s'", (int)packet.len, packet.ptr) + ) + break; + default: + break; + } + } + else + { + /* parse a PGP private key file */ + switch (packet_type) + { + case PGP_PKT_SECRET_KEY: + if (!parse_pgp_secretkey_packet(&packet, key)) + return FALSE; + break; + default: + break; + } + } + } + } + return TRUE; +} + +/* + * compare two OpenPGP certificates + */ +static bool +same_pgpcert(pgpcert_t *a, pgpcert_t *b) +{ + return a->certificate.len == b->certificate.len && + memcmp(a->certificate.ptr, b->certificate.ptr, b->certificate.len) == 0; +} + +/* + * for each link pointing to the certificate increase the count by one + */ +void +share_pgpcert(pgpcert_t *cert) +{ + if (cert != NULL) + cert->count++; +} + +/* + * select the OpenPGP keyid as ID + */ +void +select_pgpcert_id(pgpcert_t *cert, struct id *end_id) +{ + end_id->kind = ID_KEY_ID; + end_id->name.len = PGP_FINGERPRINT_SIZE; + end_id->name.ptr = cert->fingerprint; + end_id->name.ptr = temporary_cyclic_buffer(); + memcpy(end_id->name.ptr, cert->fingerprint, PGP_FINGERPRINT_SIZE); +} + +/* + * add an OpenPGP user/host certificate to the chained list + */ +pgpcert_t* +add_pgpcert(pgpcert_t *cert) +{ + pgpcert_t *c = pgpcerts; + + while (c != NULL) + { + if (same_pgpcert(c, cert)) /* already in chain, free cert */ + { + free_pgpcert(cert); + return c; + } + c = c->next; + } + + /* insert new cert at the root of the chain */ + cert->next = pgpcerts; + pgpcerts = cert; + DBG(DBG_CONTROL | DBG_PARSING, + DBG_log(" pgp cert inserted") + ) + return cert; +} + +/* release of a certificate decreases the count by one + " the certificate is freed when the counter reaches zero + */ +void +release_pgpcert(pgpcert_t *cert) +{ + if (cert != NULL && --cert->count == 0) + { + pgpcert_t **pp = &pgpcerts; + while (*pp != cert) + pp = &(*pp)->next; + *pp = cert->next; + free_pgpcert(cert); + } +} + +/* + * free a PGP certificate + */ +void +free_pgpcert(pgpcert_t *cert) +{ + if (cert != NULL) + { + if (cert->certificate.ptr != NULL) + pfree(cert->certificate.ptr); + pfree(cert); + } +} + +/* + * list all PGP end certificates in a chained list + */ +void +list_pgp_end_certs(bool utc) +{ + pgpcert_t *cert = pgpcerts; + time_t now; + + /* determine the current time */ + time(&now); + + if (cert != NULL) + { + whack_log(RC_COMMENT, " "); + whack_log(RC_COMMENT, "List of PGP End certificates:"); + whack_log(RC_COMMENT, " "); + } + + while (cert != NULL) + { + unsigned keysize; + char buf[BUF_LEN]; + cert_t c; + + c.type = CERT_PGP; + c.u.pgp = cert; + + whack_log(RC_COMMENT, "%s, count: %d", timetoa(&cert->installed, utc), cert->count); + datatot(cert->fingerprint, PGP_FINGERPRINT_SIZE, 'x', buf, BUF_LEN); + whack_log(RC_COMMENT, " fingerprint: %s", buf); + form_keyid(cert->publicExponent, cert->modulus, buf, &keysize); + whack_log(RC_COMMENT, " pubkey: %4d RSA Key %s%s", 8*keysize, buf, + (has_private_key(c))? ", has private key" : ""); + whack_log(RC_COMMENT, " created: %s", timetoa(&cert->created, utc)); + whack_log(RC_COMMENT, " until: %s %s", timetoa(&cert->until, utc), + check_expiry(cert->until, CA_CERT_WARNING_INTERVAL, TRUE)); + cert = cert->next; + } +} + diff --git a/src/pluto/pgp.h b/src/pluto/pgp.h new file mode 100644 index 000000000..4f34debc9 --- /dev/null +++ b/src/pluto/pgp.h @@ -0,0 +1,54 @@ +/* Support of OpenPGP certificates + * Copyright (C) 2002-2004 Andreas Steffen, Zuercher Hochschule Winterthur + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: pgp.h,v 1.3 2005/08/07 07:50:09 as Exp $ + */ + +#ifndef _PGP_H +#define _PGP_H + +#include "pkcs1.h" +/* + * Length of PGP V3 fingerprint + */ +#define PGP_FINGERPRINT_SIZE MD5_DIGEST_SIZE + +typedef char fingerprint_t[PGP_FINGERPRINT_SIZE]; + +/* access structure for an OpenPGP certificate */ + +typedef struct pgpcert pgpcert_t; + +struct pgpcert { + pgpcert_t *next; + time_t installed; + int count; + chunk_t certificate; + time_t created; + time_t until; + enum pubkey_alg pubkeyAlg; + chunk_t modulus; + chunk_t publicExponent; + fingerprint_t fingerprint; +}; + +extern const pgpcert_t empty_pgpcert; +extern bool parse_pgp(chunk_t blob, pgpcert_t *cert, RSA_private_key_t *key); +extern void share_pgpcert(pgpcert_t *cert); +extern void select_pgpcert_id(pgpcert_t *cert, struct id *end_id); +extern pgpcert_t* add_pgpcert(pgpcert_t *cert); +extern void list_pgp_end_certs(bool utc); +extern void release_pgpcert(pgpcert_t *cert); +extern void free_pgpcert(pgpcert_t *cert); + +#endif /* _PGP_H */ diff --git a/src/pluto/pkcs1.c b/src/pluto/pkcs1.c new file mode 100644 index 000000000..ade5fdd94 --- /dev/null +++ b/src/pluto/pkcs1.c @@ -0,0 +1,674 @@ +/* Support of PKCS#1 private key data structures + * Copyright (C) 2005 Jan Hutter, Martin Willi + * Copyright (C) 2002-2005 Andreas Steffen + * Hochschule fuer Technik Rapperswil, Switzerland + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: pkcs1.c,v 1.17 2006/01/04 21:00:43 as Exp $ + */ + +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#include <freeswan.h> +#include <libsha2/sha2.h> + +#include "constants.h" +#include "defs.h" +#include "mp_defs.h" +#include "asn1.h" +#include "oid.h" +#include "log.h" +#include "pkcs1.h" +#include "md2.h" +#include "md5.h" +#include "sha1.h" +#include "rnd.h" + +const struct fld RSA_private_field[] = +{ + { "Modulus", offsetof(RSA_private_key_t, pub.n) }, + { "PublicExponent", offsetof(RSA_private_key_t, pub.e) }, + + { "PrivateExponent", offsetof(RSA_private_key_t, d) }, + { "Prime1", offsetof(RSA_private_key_t, p) }, + { "Prime2", offsetof(RSA_private_key_t, q) }, + { "Exponent1", offsetof(RSA_private_key_t, dP) }, + { "Exponent2", offsetof(RSA_private_key_t, dQ) }, + { "Coefficient", offsetof(RSA_private_key_t, qInv) }, +}; + +/* ASN.1 definition of a PKCS#1 RSA private key */ + +static const asn1Object_t privkeyObjects[] = { + { 0, "RSAPrivateKey", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */ + { 1, "version", ASN1_INTEGER, ASN1_BODY }, /* 1 */ + { 1, "modulus", ASN1_INTEGER, ASN1_BODY }, /* 2 */ + { 1, "publicExponent", ASN1_INTEGER, ASN1_BODY }, /* 3 */ + { 1, "privateExponent", ASN1_INTEGER, ASN1_BODY }, /* 4 */ + { 1, "prime1", ASN1_INTEGER, ASN1_BODY }, /* 5 */ + { 1, "prime2", ASN1_INTEGER, ASN1_BODY }, /* 6 */ + { 1, "exponent1", ASN1_INTEGER, ASN1_BODY }, /* 7 */ + { 1, "exponent2", ASN1_INTEGER, ASN1_BODY }, /* 8 */ + { 1, "coefficient", ASN1_INTEGER, ASN1_BODY }, /* 9 */ + { 1, "otherPrimeInfos", ASN1_SEQUENCE, ASN1_OPT | + ASN1_LOOP }, /* 10 */ + { 2, "otherPrimeInfo", ASN1_SEQUENCE, ASN1_NONE }, /* 11 */ + { 3, "prime", ASN1_INTEGER, ASN1_BODY }, /* 12 */ + { 3, "exponent", ASN1_INTEGER, ASN1_BODY }, /* 13 */ + { 3, "coefficient", ASN1_INTEGER, ASN1_BODY }, /* 14 */ + { 1, "end opt or loop", ASN1_EOC, ASN1_END } /* 15 */ +}; + +#define PKCS1_PRIV_KEY_VERSION 1 +#define PKCS1_PRIV_KEY_MODULUS 2 +#define PKCS1_PRIV_KEY_PUB_EXP 3 +#define PKCS1_PRIV_KEY_COEFF 9 +#define PKCS1_PRIV_KEY_ROOF 16 + + +/* + * forms the FreeS/WAN keyid from the public exponent e and modulus n + */ +void +form_keyid(chunk_t e, chunk_t n, char* keyid, unsigned *keysize) +{ + /* eliminate leading zero bytes in modulus from ASN.1 coding */ + while (n.len > 1 && *n.ptr == 0x00) + { + n.ptr++; n.len--; + } + + /* form the FreeS/WAN keyid */ + keyid[0] = '\0'; /* in case of splitkeytoid failure */ + splitkeytoid(e.ptr, e.len, n.ptr, n.len, keyid, KEYID_BUF); + + /* return the RSA modulus size in octets */ + *keysize = n.len; +} + +/* + * initialize an RSA_public_key_t object + */ +void +init_RSA_public_key(RSA_public_key_t *rsa, chunk_t e, chunk_t n) +{ + n_to_mpz(&rsa->e, e.ptr, e.len); + n_to_mpz(&rsa->n, n.ptr, n.len); + + form_keyid(e, n, rsa->keyid, &rsa->k); +} + +#ifdef DEBUG +static void +RSA_show_key_fields(RSA_private_key_t *k, int fieldcnt) +{ + const struct fld *p; + + DBG_log(" keyid: *%s", k->pub.keyid); + + for (p = RSA_private_field; p < &RSA_private_field[fieldcnt]; p++) + { + MP_INT *n = (MP_INT *) ((char *)k + p->offset); + size_t sz = mpz_sizeinbase(n, 16); + char buf[RSA_MAX_OCTETS * 2 + 2]; /* ought to be big enough */ + + passert(sz <= sizeof(buf)); + mpz_get_str(buf, 16, n); + + DBG_log(" %s: 0x%s", p->name, buf); + } +} + +/* debugging info that compromises security! */ +void +RSA_show_private_key(RSA_private_key_t *k) +{ + RSA_show_key_fields(k, elemsof(RSA_private_field)); +} + +void +RSA_show_public_key(RSA_public_key_t *k) +{ + /* Kludge: pretend that it is a private key, but only display the + * first two fields (which are the public key). + */ + passert(offsetof(RSA_private_key_t, pub) == 0); + RSA_show_key_fields((RSA_private_key_t *)k, 2); +} +#endif + +err_t +RSA_private_key_sanity(RSA_private_key_t *k) +{ + /* note that the *last* error found is reported */ + err_t ugh = NULL; + mpz_t t, u, q1; + +#ifdef DEBUG /* debugging info that compromises security */ + DBG(DBG_PRIVATE, RSA_show_private_key(k)); +#endif + + /* PKCS#1 1.5 section 6 requires modulus to have at least 12 octets. + * We actually require more (for security). + */ + if (k->pub.k < RSA_MIN_OCTETS) + return RSA_MIN_OCTETS_UGH; + + /* we picked a max modulus size to simplify buffer allocation */ + if (k->pub.k > RSA_MAX_OCTETS) + return RSA_MAX_OCTETS_UGH; + + mpz_init(t); + mpz_init(u); + mpz_init(q1); + + /* check that n == p * q */ + mpz_mul(u, &k->p, &k->q); + if (mpz_cmp(u, &k->pub.n) != 0) + ugh = "n != p * q"; + + /* check that e divides neither p-1 nor q-1 */ + mpz_sub_ui(t, &k->p, 1); + mpz_mod(t, t, &k->pub.e); + if (mpz_cmp_ui(t, 0) == 0) + ugh = "e divides p-1"; + + mpz_sub_ui(t, &k->q, 1); + mpz_mod(t, t, &k->pub.e); + if (mpz_cmp_ui(t, 0) == 0) + ugh = "e divides q-1"; + + /* check that d is e^-1 (mod lcm(p-1, q-1)) */ + /* see PKCS#1v2, aka RFC 2437, for the "lcm" */ + mpz_sub_ui(q1, &k->q, 1); + mpz_sub_ui(u, &k->p, 1); + mpz_gcd(t, u, q1); /* t := gcd(p-1, q-1) */ + mpz_mul(u, u, q1); /* u := (p-1) * (q-1) */ + mpz_divexact(u, u, t); /* u := lcm(p-1, q-1) */ + + mpz_mul(t, &k->d, &k->pub.e); + mpz_mod(t, t, u); + if (mpz_cmp_ui(t, 1) != 0) + ugh = "(d * e) mod (lcm(p-1, q-1)) != 1"; + + /* check that dP is d mod (p-1) */ + mpz_sub_ui(u, &k->p, 1); + mpz_mod(t, &k->d, u); + if (mpz_cmp(t, &k->dP) != 0) + ugh = "dP is not congruent to d mod (p-1)"; + + /* check that dQ is d mod (q-1) */ + mpz_sub_ui(u, &k->q, 1); + mpz_mod(t, &k->d, u); + if (mpz_cmp(t, &k->dQ) != 0) + ugh = "dQ is not congruent to d mod (q-1)"; + + /* check that qInv is (q^-1) mod p */ + mpz_mul(t, &k->qInv, &k->q); + mpz_mod(t, t, &k->p); + if (mpz_cmp_ui(t, 1) != 0) + ugh = "qInv is not conguent ot (q^-1) mod p"; + + mpz_clear(t); + mpz_clear(u); + mpz_clear(q1); + return ugh; +} + +/* + * Check the equality of two RSA public keys + */ +bool +same_RSA_public_key(const RSA_public_key_t *a, const RSA_public_key_t *b) +{ + return a == b + || (a->k == b->k && mpz_cmp(&a->n, &b->n) == 0 && mpz_cmp(&a->e, &b->e) == 0); +} + +/* + * Parses a PKCS#1 private key + */ +bool +pkcs1_parse_private_key(chunk_t blob, RSA_private_key_t *key) +{ + err_t ugh = NULL; + asn1_ctx_t ctx; + chunk_t object, modulus, exp; + u_int level; + int objectID = 0; + + asn1_init(&ctx, blob, 0, FALSE, DBG_PRIVATE); + + while (objectID < PKCS1_PRIV_KEY_ROOF) { + + if (!extract_object(privkeyObjects, &objectID, &object, &level, &ctx)) + return FALSE; + + if (objectID == PKCS1_PRIV_KEY_VERSION) + { + if (object.len > 0 && *object.ptr != 0) + { + plog(" wrong PKCS#1 private key version"); + return FALSE; + } + } + else if (objectID >= PKCS1_PRIV_KEY_MODULUS && + objectID <= PKCS1_PRIV_KEY_COEFF) + { + MP_INT *u = (MP_INT *) ((char *)key + + RSA_private_field[objectID - PKCS1_PRIV_KEY_MODULUS].offset); + + n_to_mpz(u, object.ptr, object.len); + + if (objectID == PKCS1_PRIV_KEY_MODULUS) + modulus = object; + else if (objectID == PKCS1_PRIV_KEY_PUB_EXP) + exp = object; + } + objectID++; + } + form_keyid(exp, modulus, key->pub.keyid, &key->pub.k); + ugh = RSA_private_key_sanity(key); + return (ugh == NULL); +} + +/* + * compute a digest over a binary blob + */ +bool +compute_digest(chunk_t tbs, int alg, chunk_t *digest) +{ + switch (alg) + { + case OID_MD2: + case OID_MD2_WITH_RSA: + { + MD2_CTX context; + + MD2Init(&context); + MD2Update(&context, tbs.ptr, tbs.len); + MD2Final(digest->ptr, &context); + digest->len = MD2_DIGEST_SIZE; + return TRUE; + } + case OID_MD5: + case OID_MD5_WITH_RSA: + { + MD5_CTX context; + + MD5Init(&context); + MD5Update(&context, tbs.ptr, tbs.len); + MD5Final(digest->ptr, &context); + digest->len = MD5_DIGEST_SIZE; + return TRUE; + } + case OID_SHA1: + case OID_SHA1_WITH_RSA: + case OID_SHA1_WITH_RSA_OIW: + { + SHA1_CTX context; + + SHA1Init(&context); + SHA1Update(&context, tbs.ptr, tbs.len); + SHA1Final(digest->ptr, &context); + digest->len = SHA1_DIGEST_SIZE; + return TRUE; + } + case OID_SHA256: + case OID_SHA256_WITH_RSA: + { + sha256_context context; + + sha256_init(&context); + sha256_write(&context, tbs.ptr, tbs.len); + sha256_final(&context); + memcpy(digest->ptr, context.sha_out, SHA2_256_DIGEST_SIZE); + digest->len = SHA2_256_DIGEST_SIZE; + return TRUE; + } + case OID_SHA384: + case OID_SHA384_WITH_RSA: + { + sha512_context context; + + sha384_init(&context); + sha512_write(&context, tbs.ptr, tbs.len); + sha512_final(&context); + memcpy(digest->ptr, context.sha_out, SHA2_384_DIGEST_SIZE); + digest->len = SHA2_384_DIGEST_SIZE; + return TRUE; + } + case OID_SHA512: + case OID_SHA512_WITH_RSA: + { + sha512_context context; + + sha512_init(&context); + sha512_write(&context, tbs.ptr, tbs.len); + sha512_final(&context); + memcpy(digest->ptr, context.sha_out, SHA2_512_DIGEST_SIZE); + digest->len = SHA2_512_DIGEST_SIZE; + return TRUE; + } + default: + digest->len = 0; + return FALSE; + } +} + +/* + * compute an RSA signature with PKCS#1 padding + */ +void +sign_hash(const RSA_private_key_t *k, const u_char *hash_val, size_t hash_len + , u_char *sig_val, size_t sig_len) +{ + chunk_t ch; + mpz_t t1, t2; + size_t padlen; + u_char *p = sig_val; + + DBG(DBG_CONTROL | DBG_CRYPT, + DBG_log("signing hash with RSA Key *%s", k->pub.keyid) + ) + /* PKCS#1 v1.5 8.1 encryption-block formatting */ + *p++ = 0x00; + *p++ = 0x01; /* BT (block type) 01 */ + padlen = sig_len - 3 - hash_len; + memset(p, 0xFF, padlen); + p += padlen; + *p++ = 0x00; + memcpy(p, hash_val, hash_len); + passert(p + hash_len - sig_val == (ptrdiff_t)sig_len); + + /* PKCS#1 v1.5 8.2 octet-string-to-integer conversion */ + n_to_mpz(t1, sig_val, sig_len); /* (could skip leading 0x00) */ + + /* PKCS#1 v1.5 8.3 RSA computation y = x^c mod n + * Better described in PKCS#1 v2.0 5.1 RSADP. + * There are two methods, depending on the form of the private key. + * We use the one based on the Chinese Remainder Theorem. + */ + mpz_init(t2); + + mpz_powm(t2, t1, &k->dP, &k->p); /* m1 = c^dP mod p */ + + mpz_powm(t1, t1, &k->dQ, &k->q); /* m2 = c^dQ mod Q */ + + mpz_sub(t2, t2, t1); /* h = qInv (m1 - m2) mod p */ + mpz_mod(t2, t2, &k->p); + mpz_mul(t2, t2, &k->qInv); + mpz_mod(t2, t2, &k->p); + + mpz_mul(t2, t2, &k->q); /* m = m2 + h q */ + mpz_add(t1, t1, t2); + + /* PKCS#1 v1.5 8.4 integer-to-octet-string conversion */ + ch = mpz_to_n(t1, sig_len); + memcpy(sig_val, ch.ptr, sig_len); + pfree(ch.ptr); + + mpz_clear(t1); + mpz_clear(t2); +} + +/* + * encrypt data with an RSA public key after padding + */ +chunk_t +RSA_encrypt(const RSA_public_key_t *key, chunk_t in) +{ + u_char padded[RSA_MAX_OCTETS]; + u_char *pos = padded; + int padding = key->k - in.len - 3; + int i; + + if (padding < 8 || key->k > RSA_MAX_OCTETS) + return empty_chunk; + + /* add padding according to PKCS#1 7.2.1 1.+2. */ + *pos++ = 0x00; + *pos++ = 0x02; + + /* pad with pseudo random bytes unequal to zero */ + get_rnd_bytes(pos, padding); + for (i = 0; i < padding; i++) + { + while (!*pos) + get_rnd_bytes(pos, 1); + pos++; + } + + /* append the padding terminator */ + *pos++ = 0x00; + + /* now add the data */ + memcpy(pos, in.ptr, in.len); + DBG(DBG_RAW, + DBG_dump_chunk("data for rsa encryption:\n", in); + DBG_dump("padded data for rsa encryption:\n", padded, key->k) + ) + + /* convert chunk to integer (PKCS#1 7.2.1 3.a) */ + { + chunk_t out; + mpz_t m, c; + + mpz_init(c); + n_to_mpz(m, padded, key->k); + + /* encrypt(PKCS#1 7.2.1 3.b) */ + mpz_powm(c, m, &key->e, &key->n); + + /* convert integer back to a chunk (PKCS#1 7.2.1 3.c) */ + out = mpz_to_n(c, key->k); + mpz_clear(c); + mpz_clear(m); + + DBG(DBG_RAW, + DBG_dump_chunk("rsa encrypted data:\n", out) + ) + return out; + } +} + +/* + * decrypt data with an RSA private key and remove padding + */ +bool +RSA_decrypt(const RSA_private_key_t *key, chunk_t in, chunk_t *out) +{ + chunk_t padded; + u_char *pos; + mpz_t t1, t2; + + n_to_mpz(t1, in.ptr,in.len); + + /* PKCS#1 v1.5 8.3 RSA computation y = x^c mod n + * Better described in PKCS#1 v2.0 5.1 RSADP. + * There are two methods, depending on the form of the private key. + * We use the one based on the Chinese Remainder Theorem. + */ + mpz_init(t2); + + mpz_powm(t2, t1, &key->dP, &key->p); /* m1 = c^dP mod p */ + mpz_powm(t1, t1, &key->dQ, &key->q); /* m2 = c^dQ mod Q */ + + mpz_sub(t2, t2, t1); /* h = qInv (m1 - m2) mod p */ + mpz_mod(t2, t2, &key->p); + mpz_mul(t2, t2, &key->qInv); + mpz_mod(t2, t2, &key->p); + + mpz_mul(t2, t2, &key->q); /* m = m2 + h q */ + mpz_add(t1, t1, t2); + + padded = mpz_to_n(t1, key->pub.k); + mpz_clear(t1); + mpz_clear(t2); + + DBG(DBG_PRIVATE, + DBG_dump_chunk("rsa decrypted data with padding:\n", padded) + ) + pos = padded.ptr; + + /* PKCS#1 v1.5 8.1 encryption-block formatting (EB = 00 || 02 || PS || 00 || D) */ + + /* check for hex pattern 00 02 in decrypted message */ + if ((*pos++ != 0x00) || (*(pos++) != 0x02)) + { + plog("incorrect padding - probably wrong RSA key"); + freeanychunk(padded); + return FALSE; + } + padded.len -= 2; + + /* the plaintext data starts after first 0x00 byte */ + while (padded.len-- > 0 && *pos++ != 0x00) + + if (padded.len == 0) + { + plog("no plaintext data"); + freeanychunk(padded); + return FALSE; + } + + clonetochunk(*out, pos, padded.len, "decrypted data"); + freeanychunk(padded); + return TRUE; +} + +/* + * build signatureValue + */ +chunk_t +pkcs1_build_signature(chunk_t tbs, int hash_alg, const RSA_private_key_t *key +, bool bit_string) +{ + + size_t siglen = key->pub.k; + + u_char digest_buf[MAX_DIGEST_LEN]; + chunk_t digest = { digest_buf, MAX_DIGEST_LEN }; + chunk_t digestInfo, alg_id, signatureValue; + u_char *pos; + + switch (hash_alg) + { + case OID_MD5: + case OID_MD5_WITH_RSA: + alg_id = ASN1_md5_id; + break; + case OID_SHA1: + case OID_SHA1_WITH_RSA: + alg_id = ASN1_sha1_id; + break; + default: + return empty_chunk; + } + compute_digest(tbs, hash_alg, &digest); + + /* according to PKCS#1 v2.1 digest must be packaged into + * an ASN.1 structure for encryption + */ + digestInfo = asn1_wrap(ASN1_SEQUENCE, "cm" + , alg_id + , asn1_simple_object(ASN1_OCTET_STRING, digest)); + + /* generate the RSA signature */ + if (bit_string) + { + pos = build_asn1_object(&signatureValue, ASN1_BIT_STRING, 1 + siglen); + *pos++ = 0x00; + } + else + { + pos = build_asn1_object(&signatureValue, ASN1_OCTET_STRING, siglen); + } + sign_hash(key, digestInfo.ptr, digestInfo.len, pos, siglen); + pfree(digestInfo.ptr); + + return signatureValue; +} + +/* + * build a DER-encoded PKCS#1 private key object + */ +chunk_t +pkcs1_build_private_key(const RSA_private_key_t *key) +{ + chunk_t pkcs1 = asn1_wrap(ASN1_SEQUENCE, "cmmmmmmmm" + , ASN1_INTEGER_0 + , asn1_integer_from_mpz(&key->pub.n) + , asn1_integer_from_mpz(&key->pub.e) + , asn1_integer_from_mpz(&key->d) + , asn1_integer_from_mpz(&key->p) + , asn1_integer_from_mpz(&key->q) + , asn1_integer_from_mpz(&key->dP) + , asn1_integer_from_mpz(&key->dQ) + , asn1_integer_from_mpz(&key->qInv)); + + DBG(DBG_PRIVATE, + DBG_dump_chunk("PKCS#1 encoded private key:", pkcs1) + ) + return pkcs1; +} + +/* + * build a DER-encoded PKCS#1 public key object + */ +chunk_t +pkcs1_build_public_key(const RSA_public_key_t *rsa) +{ + return asn1_wrap(ASN1_SEQUENCE, "mm" + , asn1_integer_from_mpz(&rsa->n) + , asn1_integer_from_mpz(&rsa->e)); +} + +/* + * build a DER-encoded publicKeyInfo object + */ +chunk_t +pkcs1_build_publicKeyInfo(const RSA_public_key_t *rsa) +{ + chunk_t publicKey; + chunk_t rawKey = pkcs1_build_public_key(rsa); + + u_char *pos = build_asn1_object(&publicKey, ASN1_BIT_STRING + , 1 + rawKey.len); + *pos++ = 0x00; + mv_chunk(&pos, rawKey); + + return asn1_wrap(ASN1_SEQUENCE, "cm" + , ASN1_rsaEncryption_id + , publicKey); +} +void +free_RSA_public_content(RSA_public_key_t *rsa) +{ + mpz_clear(&rsa->n); + mpz_clear(&rsa->e); +} + +void +free_RSA_private_content(RSA_private_key_t *rsak) +{ + free_RSA_public_content(&rsak->pub); + mpz_clear(&rsak->d); + mpz_clear(&rsak->p); + mpz_clear(&rsak->q); + mpz_clear(&rsak->dP); + mpz_clear(&rsak->dQ); + mpz_clear(&rsak->qInv); +} + diff --git a/src/pluto/pkcs1.h b/src/pluto/pkcs1.h new file mode 100644 index 000000000..c927db0f8 --- /dev/null +++ b/src/pluto/pkcs1.h @@ -0,0 +1,88 @@ +/* Support of PKCS#1 private key data structures + * Copyright (C) 2005 Jan Hutter, Martin Willi + * Copyright (C) 2002-2005 Andreas Steffen + * Hochschule fuer Technik Rapperswil, Switzerland + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: pkcs1.h,v 1.14 2005/12/06 22:52:12 as Exp $ + */ + +#ifndef _PKCS1_H +#define _PKCS1_H + +#include <gmp.h> /* GNU Multi Precision library */ + +#include "defs.h" + +typedef struct RSA_public_key RSA_public_key_t; + +struct RSA_public_key +{ + char keyid[KEYID_BUF]; /* see ipsec_keyblobtoid(3) */ + + /* length of modulus n in octets: [RSA_MIN_OCTETS, RSA_MAX_OCTETS] */ + unsigned k; + + /* public: */ + MP_INT + n, /* modulus: p * q */ + e; /* exponent: relatively prime to (p-1) * (q-1) [probably small] */ +}; + +typedef struct RSA_private_key RSA_private_key_t; + +struct RSA_private_key { + struct RSA_public_key pub; /* must be at start for RSA_show_public_key */ + + MP_INT + d, /* private exponent: (e^-1) mod ((p-1) * (q-1)) */ + /* help for Chinese Remainder Theorem speedup: */ + p, /* first secret prime */ + q, /* second secret prime */ + dP, /* first factor's exponent: (e^-1) mod (p-1) == d mod (p-1) */ + dQ, /* second factor's exponent: (e^-1) mod (q-1) == d mod (q-1) */ + qInv; /* (q^-1) mod p */ +}; + +struct fld { + const char *name; + size_t offset; +}; + +extern const struct fld RSA_private_field[]; +#define RSA_PRIVATE_FIELD_ELEMENTS 8 + +extern void init_RSA_public_key(RSA_public_key_t *rsa, chunk_t e, chunk_t n); +extern bool pkcs1_parse_private_key(chunk_t blob, RSA_private_key_t *key); +extern chunk_t pkcs1_build_private_key(const RSA_private_key_t *key); +extern chunk_t pkcs1_build_public_key(const RSA_public_key_t *rsa); +extern chunk_t pkcs1_build_publicKeyInfo(const RSA_public_key_t *rsa); +extern chunk_t pkcs1_build_signature(chunk_t tbs, int hash_alg + , const RSA_private_key_t *key, bool bit_string); +extern bool compute_digest(chunk_t tbs, int alg, chunk_t *digest); +extern void sign_hash(const RSA_private_key_t *k, const u_char *hash_val + , size_t hash_len, u_char *sig_val, size_t sig_len); +extern chunk_t RSA_encrypt(const RSA_public_key_t *key, chunk_t in); +extern bool RSA_decrypt(const RSA_private_key_t *key, chunk_t in + , chunk_t *out); +extern bool same_RSA_public_key(const RSA_public_key_t *a + , const RSA_public_key_t *b); +extern void form_keyid(chunk_t e, chunk_t n, char* keyid, unsigned *keysize); +extern err_t RSA_private_key_sanity(RSA_private_key_t *k); +#ifdef DEBUG +extern void RSA_show_public_key(RSA_public_key_t *k); +extern void RSA_show_private_key(RSA_private_key_t *k); +#endif +extern void free_RSA_public_content(RSA_public_key_t *rsa); +extern void free_RSA_private_content(RSA_private_key_t *rsak); + +#endif /* _PKCS1_H */ diff --git a/src/pluto/pkcs7.c b/src/pluto/pkcs7.c new file mode 100644 index 000000000..3068c0c94 --- /dev/null +++ b/src/pluto/pkcs7.c @@ -0,0 +1,862 @@ +/* Support of PKCS#7 data structures + * Copyright (C) 2005 Jan Hutter, Martin Willi + * Copyright (C) 2002-2005 Andreas Steffen + * Hochschule fuer Technik Rapperswil, Switzerland + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: pkcs7.c,v 1.13 2005/12/22 22:11:24 as Exp $ + */ + +#include <stdlib.h> +#include <string.h> +#include <libdes/des.h> + +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "asn1.h" +#include "oid.h" +#include "log.h" +#include "x509.h" +#include "certs.h" +#include "pkcs7.h" +#include "rnd.h" + +const contentInfo_t empty_contentInfo = { + OID_UNKNOWN , /* type */ + { NULL, 0 } /* content */ +}; + +/* ASN.1 definition of the PKCS#7 ContentInfo type */ + +static const asn1Object_t contentInfoObjects[] = { + { 0, "contentInfo", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */ + { 1, "contentType", ASN1_OID, ASN1_BODY }, /* 1 */ + { 1, "content", ASN1_CONTEXT_C_0, ASN1_OPT | + ASN1_BODY }, /* 2 */ + { 1, "end opt", ASN1_EOC, ASN1_END } /* 3 */ +}; + +#define PKCS7_INFO_TYPE 1 +#define PKCS7_INFO_CONTENT 2 +#define PKCS7_INFO_ROOF 4 + +/* ASN.1 definition of the PKCS#7 signedData type */ + +static const asn1Object_t signedDataObjects[] = { + { 0, "signedData", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */ + { 1, "version", ASN1_INTEGER, ASN1_BODY }, /* 1 */ + { 1, "digestAlgorithms", ASN1_SET, ASN1_LOOP }, /* 2 */ + { 2, "algorithm", ASN1_EOC, ASN1_RAW }, /* 3 */ + { 1, "end loop", ASN1_EOC, ASN1_END }, /* 4 */ + { 1, "contentInfo", ASN1_EOC, ASN1_RAW }, /* 5 */ + { 1, "certificates", ASN1_CONTEXT_C_0, ASN1_OPT | + ASN1_LOOP }, /* 6 */ + { 2, "certificate", ASN1_SEQUENCE, ASN1_OBJ }, /* 7 */ + { 1, "end opt or loop", ASN1_EOC, ASN1_END }, /* 8 */ + { 1, "crls", ASN1_CONTEXT_C_1, ASN1_OPT | + ASN1_LOOP }, /* 9 */ + { 2, "crl", ASN1_SEQUENCE, ASN1_OBJ }, /* 10 */ + { 1, "end opt or loop", ASN1_EOC, ASN1_END }, /* 11 */ + { 1, "signerInfos", ASN1_SET, ASN1_LOOP }, /* 12 */ + { 2, "signerInfo", ASN1_SEQUENCE, ASN1_NONE }, /* 13 */ + { 3, "version", ASN1_INTEGER, ASN1_BODY }, /* 14 */ + { 3, "issuerAndSerialNumber", ASN1_SEQUENCE, ASN1_BODY }, /* 15 */ + { 4, "issuer", ASN1_SEQUENCE, ASN1_OBJ }, /* 16 */ + { 4, "serial", ASN1_INTEGER, ASN1_BODY }, /* 17 */ + { 3, "digestAlgorithm", ASN1_EOC, ASN1_RAW }, /* 18 */ + { 3, "authenticatedAttributes", ASN1_CONTEXT_C_0, ASN1_OPT | + ASN1_OBJ }, /* 19 */ + { 3, "end opt", ASN1_EOC, ASN1_END }, /* 20 */ + { 3, "digestEncryptionAlgorithm", ASN1_EOC, ASN1_RAW }, /* 21 */ + { 3, "encryptedDigest", ASN1_OCTET_STRING, ASN1_BODY }, /* 22 */ + { 3, "unauthenticatedAttributes", ASN1_CONTEXT_C_1, ASN1_OPT }, /* 23 */ + { 3, "end opt", ASN1_EOC, ASN1_END }, /* 24 */ + { 1, "end loop", ASN1_EOC, ASN1_END } /* 25 */ +}; + +#define PKCS7_DIGEST_ALG 3 +#define PKCS7_SIGNED_CONTENT_INFO 5 +#define PKCS7_SIGNED_CERT 7 +#define PKCS7_SIGNER_INFO 13 +#define PKCS7_SIGNED_ISSUER 16 +#define PKCS7_SIGNED_SERIAL_NUMBER 17 +#define PKCS7_DIGEST_ALGORITHM 18 +#define PKCS7_AUTH_ATTRIBUTES 19 +#define PKCS7_DIGEST_ENC_ALGORITHM 21 +#define PKCS7_ENCRYPTED_DIGEST 22 +#define PKCS7_SIGNED_ROOF 26 + +/* ASN.1 definition of the PKCS#7 envelopedData type */ + +static const asn1Object_t envelopedDataObjects[] = { + { 0, "envelopedData", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */ + { 1, "version", ASN1_INTEGER, ASN1_BODY }, /* 1 */ + { 1, "recipientInfos", ASN1_SET, ASN1_LOOP }, /* 2 */ + { 2, "recipientInfo", ASN1_SEQUENCE, ASN1_BODY }, /* 3 */ + { 3, "version", ASN1_INTEGER, ASN1_BODY }, /* 4 */ + { 3, "issuerAndSerialNumber", ASN1_SEQUENCE, ASN1_BODY }, /* 5 */ + { 4, "issuer", ASN1_SEQUENCE, ASN1_OBJ }, /* 6 */ + { 4, "serial", ASN1_INTEGER, ASN1_BODY }, /* 7 */ + { 3, "encryptionAlgorithm", ASN1_EOC, ASN1_RAW }, /* 8 */ + { 3, "encryptedKey", ASN1_OCTET_STRING, ASN1_BODY }, /* 9 */ + { 1, "end loop", ASN1_EOC, ASN1_END }, /* 10 */ + { 1, "encryptedContentInfo", ASN1_SEQUENCE, ASN1_OBJ }, /* 11 */ + { 2, "contentType", ASN1_OID, ASN1_BODY }, /* 12 */ + { 2, "contentEncryptionAlgorithm", ASN1_EOC, ASN1_RAW }, /* 13 */ + { 2, "encryptedContent", ASN1_CONTEXT_S_0, ASN1_BODY } /* 14 */ +}; + +#define PKCS7_ENVELOPED_VERSION 1 +#define PKCS7_RECIPIENT_INFO_VERSION 4 +#define PKCS7_ISSUER 6 +#define PKCS7_SERIAL_NUMBER 7 +#define PKCS7_ENCRYPTION_ALG 8 +#define PKCS7_ENCRYPTED_KEY 9 +#define PKCS7_CONTENT_TYPE 12 +#define PKCS7_CONTENT_ENC_ALGORITHM 13 +#define PKCS7_ENCRYPTED_CONTENT 14 +#define PKCS7_ENVELOPED_ROOF 15 + +/* PKCS7 contentInfo OIDs */ + +static u_char ASN1_pkcs7_data_oid_str[] = { + 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01 +}; + +static u_char ASN1_pkcs7_signed_data_oid_str[] = { + 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x02 +}; + +static u_char ASN1_pkcs7_enveloped_data_oid_str[] = { + 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x03 +}; + +static u_char ASN1_pkcs7_signed_enveloped_data_oid_str[] = { + 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x04 +}; + +static u_char ASN1_pkcs7_digested_data_oid_str[] = { + 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x05 +}; + +static char ASN1_pkcs7_encrypted_data_oid_str[] = { + 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x06 +}; + +static const chunk_t ASN1_pkcs7_data_oid = + strchunk(ASN1_pkcs7_data_oid_str); +static const chunk_t ASN1_pkcs7_signed_data_oid = + strchunk(ASN1_pkcs7_signed_data_oid_str); +static const chunk_t ASN1_pkcs7_enveloped_data_oid = + strchunk(ASN1_pkcs7_enveloped_data_oid_str); +static const chunk_t ASN1_pkcs7_signed_enveloped_data_oid = + strchunk(ASN1_pkcs7_signed_enveloped_data_oid_str); +static const chunk_t ASN1_pkcs7_digested_data_oid = + strchunk(ASN1_pkcs7_digested_data_oid_str); +static const chunk_t ASN1_pkcs7_encrypted_data_oid = + strchunk(ASN1_pkcs7_encrypted_data_oid_str); + +/* 3DES and DES encryption OIDs */ + +static u_char ASN1_3des_ede_cbc_oid_str[] = { + 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x03, 0x07 +}; + +static u_char ASN1_des_cbc_oid_str[] = { + 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x07 +}; + +static const chunk_t ASN1_3des_ede_cbc_oid = + strchunk(ASN1_3des_ede_cbc_oid_str); +static const chunk_t ASN1_des_cbc_oid = + strchunk(ASN1_des_cbc_oid_str); + +/* PKCS#7 attribute type OIDs */ + +static u_char ASN1_contentType_oid_str[] = { + 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x03 +}; + +static u_char ASN1_messageDigest_oid_str[] = { + 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x04 +}; + +static const chunk_t ASN1_contentType_oid = + strchunk(ASN1_contentType_oid_str); +static const chunk_t ASN1_messageDigest_oid = + strchunk(ASN1_messageDigest_oid_str); + +/* + * Parse PKCS#7 ContentInfo object + */ +bool +pkcs7_parse_contentInfo(chunk_t blob, u_int level0, contentInfo_t *cInfo) +{ + asn1_ctx_t ctx; + chunk_t object; + u_int level; + int objectID = 0; + + asn1_init(&ctx, blob, level0, FALSE, DBG_RAW); + + while (objectID < PKCS7_INFO_ROOF) + { + if (!extract_object(contentInfoObjects, &objectID, &object, &level, &ctx)) + return FALSE; + + if (objectID == PKCS7_INFO_TYPE) + { + cInfo->type = known_oid(object); + if (cInfo->type < OID_PKCS7_DATA + || cInfo->type > OID_PKCS7_ENCRYPTED_DATA) + { + plog("unknown pkcs7 content type"); + return FALSE; + } + } + else if (objectID == PKCS7_INFO_CONTENT) + { + cInfo->content = object; + } + objectID++; + } + return TRUE; +} + +/* + * Parse a PKCS#7 signedData object + */ +bool +pkcs7_parse_signedData(chunk_t blob, contentInfo_t *data, x509cert_t **cert +, chunk_t *attributes, const x509cert_t *cacert) +{ + u_char buf[BUF_LEN]; + asn1_ctx_t ctx; + chunk_t object; + u_int level; + int digest_alg = OID_UNKNOWN; + int enc_alg = OID_UNKNOWN; + int signerInfos = 0; + int objectID = 0; + + contentInfo_t cInfo = empty_contentInfo; + chunk_t encrypted_digest = empty_chunk; + + if (!pkcs7_parse_contentInfo(blob, 0, &cInfo)) + return FALSE; + + if (cInfo.type != OID_PKCS7_SIGNED_DATA) + { + plog("pkcs7 content type is not signedData"); + return FALSE; + } + + asn1_init(&ctx, cInfo.content, 2, FALSE, DBG_RAW); + + while (objectID < PKCS7_SIGNED_ROOF) + { + if (!extract_object(signedDataObjects, &objectID, &object, &level, &ctx)) + return FALSE; + + switch (objectID) + { + case PKCS7_DIGEST_ALG: + digest_alg = parse_algorithmIdentifier(object, level, NULL); + break; + case PKCS7_SIGNED_CONTENT_INFO: + if (data != NULL) + { + pkcs7_parse_contentInfo(object, level, data); + } + break; + case PKCS7_SIGNED_CERT: + if (cert != NULL) + { + chunk_t cert_blob; + + x509cert_t *newcert = alloc_thing(x509cert_t + , "pkcs7 wrapped x509cert"); + + clonetochunk(cert_blob, object.ptr, object.len + , "pkcs7 cert blob"); + *newcert = empty_x509cert; + + DBG(DBG_CONTROL | DBG_PARSING, + DBG_log("parsing pkcs7-wrapped certificate") + ) + if (parse_x509cert(cert_blob, level+1, newcert)) + { + newcert->next = *cert; + *cert = newcert; + } + else + { + free_x509cert(newcert); + } + } + break; + case PKCS7_SIGNER_INFO: + signerInfos++; + DBG(DBG_PARSING, + DBG_log(" signer #%d", signerInfos) + ) + break; + case PKCS7_SIGNED_ISSUER: + DBG(DBG_PARSING, + dntoa(buf, BUF_LEN, object); + DBG_log(" '%s'",buf) + ) + break; + case PKCS7_AUTH_ATTRIBUTES: + if (attributes != NULL) + { + *attributes = object; + *attributes->ptr = ASN1_SET; + } + break; + case PKCS7_DIGEST_ALGORITHM: + digest_alg = parse_algorithmIdentifier(object, level, NULL); + break; + case PKCS7_DIGEST_ENC_ALGORITHM: + enc_alg = parse_algorithmIdentifier(object, level, NULL); + break; + case PKCS7_ENCRYPTED_DIGEST: + encrypted_digest = object; + } + objectID++; + } + + /* check the signature only if a cacert is available */ + if (cacert != NULL) + { + if (signerInfos == 0) + { + plog("no signerInfo object found"); + return FALSE; + } + else if (signerInfos > 1) + { + plog("more than one signerInfo object found"); + return FALSE; + } + if (attributes->ptr == NULL) + { + plog("no authenticatedAttributes object found"); + return FALSE; + } + if (!check_signature(*attributes, encrypted_digest, digest_alg + , enc_alg, cacert)) + { + plog("invalid signature"); + return FALSE; + } + else + { + DBG(DBG_CONTROL, + DBG_log("signature is valid") + ) + } + } + return TRUE; +} + +/* + * Parse a PKCS#7 envelopedData object + */ +bool +pkcs7_parse_envelopedData(chunk_t blob, chunk_t *data +, chunk_t serialNumber, const RSA_private_key_t *key) +{ + asn1_ctx_t ctx; + chunk_t object; + chunk_t iv = empty_chunk; + chunk_t symmetric_key = empty_chunk; + chunk_t encrypted_content = empty_chunk; + + u_char buf[BUF_LEN]; + u_int level; + u_int total_keys = 3; + int enc_alg = OID_UNKNOWN; + int content_enc_alg = OID_UNKNOWN; + int objectID = 0; + + contentInfo_t cInfo = empty_contentInfo; + *data = empty_chunk; + + if (!pkcs7_parse_contentInfo(blob, 0, &cInfo)) + goto failed; + + if (cInfo.type != OID_PKCS7_ENVELOPED_DATA) + { + plog("pkcs7 content type is not envelopedData"); + return FALSE; + } + + asn1_init(&ctx, cInfo.content, 2, FALSE, DBG_RAW); + + while (objectID < PKCS7_ENVELOPED_ROOF) + { + if (!extract_object(envelopedDataObjects, &objectID, &object, &level, &ctx)) + goto failed; + + switch (objectID) + { + case PKCS7_ENVELOPED_VERSION: + if (*object.ptr != 0) + { + plog("envelopedData version is not 0"); + goto failed; + } + break; + case PKCS7_RECIPIENT_INFO_VERSION: + if (*object.ptr != 0) + { + plog("recipient info version is not 0"); + goto failed; + } + break; + case PKCS7_ISSUER: + DBG(DBG_PARSING, + dntoa(buf, BUF_LEN, object); + DBG_log(" '%s'", buf) + ) + break; + case PKCS7_SERIAL_NUMBER: + if (!same_chunk(serialNumber, object)) + { + plog("serial numbers do not match"); + goto failed; + } + break; + case PKCS7_ENCRYPTION_ALG: + enc_alg = parse_algorithmIdentifier(object, level, NULL); + if (enc_alg != OID_RSA_ENCRYPTION) + { + plog("only rsa encryption supported"); + goto failed; + } + break; + case PKCS7_ENCRYPTED_KEY: + if (!RSA_decrypt(key, object, &symmetric_key)) + { + plog("symmetric key could not be decrypted with rsa"); + goto failed; + } + DBG(DBG_PRIVATE, + DBG_dump_chunk("symmetric key :", symmetric_key) + ) + break; + case PKCS7_CONTENT_TYPE: + if (known_oid(object) != OID_PKCS7_DATA) + { + plog("encrypted content not of type pkcs7 data"); + goto failed; + } + break; + case PKCS7_CONTENT_ENC_ALGORITHM: + content_enc_alg = parse_algorithmIdentifier(object, level, &iv); + + switch (content_enc_alg) + { + case OID_DES_CBC: + total_keys = 1; + break; + case OID_3DES_EDE_CBC: + total_keys = 3; + break; + default: + plog("Only DES and 3DES supported for symmetric encryption"); + goto failed; + } + if (symmetric_key.len != (total_keys * DES_CBC_BLOCK_SIZE)) + { + plog("key length is not %d",(total_keys * DES_CBC_BLOCK_SIZE)); + goto failed; + } + if (!parse_asn1_simple_object(&iv, ASN1_OCTET_STRING, level+1, "IV")) + { + plog("IV could not be parsed"); + goto failed; + } + if (iv.len != DES_CBC_BLOCK_SIZE) + { + plog("IV has wrong length"); + goto failed; + } + break; + case PKCS7_ENCRYPTED_CONTENT: + encrypted_content = object; + break; + } + objectID++; + } + + /* decrypt the content */ + { + u_int i; + des_cblock des_key[3], des_iv; + des_key_schedule key_s[3]; + + memcpy((char *)des_key, symmetric_key.ptr, symmetric_key.len); + memcpy((char *)des_iv, iv.ptr, iv.len); + + for (i = 0; i < total_keys; i++) + { + if (des_set_key(&des_key[i], key_s[i])) + { + plog("des key schedule failed"); + goto failed; + } + } + + data->len = encrypted_content.len; + data->ptr = alloc_bytes(data->len, "decrypted data"); + + switch (content_enc_alg) + { + case OID_DES_CBC: + des_cbc_encrypt((des_cblock*)encrypted_content.ptr + , (des_cblock*)data->ptr, data->len + , key_s[0], &des_iv, DES_DECRYPT); + break; + case OID_3DES_EDE_CBC: + des_ede3_cbc_encrypt( (des_cblock*)encrypted_content.ptr + , (des_cblock*)data->ptr, data->len + , key_s[0], key_s[1], key_s[2] + , &des_iv, DES_DECRYPT); + } + DBG(DBG_PRIVATE, + DBG_dump_chunk("decrypted content with padding:\n", *data) + ) + } + + /* remove the padding */ + { + u_char *pos = data->ptr + data->len - 1; + u_char pattern = *pos; + size_t padding = pattern; + + if (padding > data->len) + { + plog("padding greater than data length"); + goto failed; + } + data->len -= padding; + + while (padding-- > 0) + { + if (*pos-- != pattern) + { + plog("wrong padding pattern"); + goto failed; + } + } + } + freeanychunk(symmetric_key); + return TRUE; + +failed: + freeanychunk(symmetric_key); + pfreeany(data->ptr); + return FALSE; +} + +/** + * @brief Builds a contentType attribute + * + * @return ASN.1 encoded contentType attribute + */ +chunk_t +pkcs7_contentType_attribute(void) +{ + return asn1_wrap(ASN1_SEQUENCE, "cm" + , ASN1_contentType_oid + , asn1_simple_object(ASN1_SET, ASN1_pkcs7_data_oid)); +} + +/** + * @brief Builds a messageDigest attribute + * + * + * @param[in] blob content to create digest of + * @param[in] digest_alg digest algorithm to be used + * @return ASN.1 encoded messageDigest attribute + * + */ +chunk_t +pkcs7_messageDigest_attribute(chunk_t content, int digest_alg) +{ + u_char digest_buf[MAX_DIGEST_LEN]; + chunk_t digest = { digest_buf, MAX_DIGEST_LEN }; + + compute_digest(content, digest_alg, &digest); + + return asn1_wrap(ASN1_SEQUENCE, "cm" + , ASN1_messageDigest_oid + , asn1_wrap(ASN1_SET, "m" + , asn1_simple_object(ASN1_OCTET_STRING, digest) + ) + ); +} +/* + * build a DER-encoded contentInfo object + */ +static chunk_t +pkcs7_build_contentInfo(contentInfo_t *cInfo) +{ + chunk_t content_type; + + /* select DER-encoded OID for pkcs7 contentInfo type */ + switch(cInfo->type) + { + case OID_PKCS7_DATA: + content_type = ASN1_pkcs7_data_oid; + break; + case OID_PKCS7_SIGNED_DATA: + content_type = ASN1_pkcs7_signed_data_oid; + break; + case OID_PKCS7_ENVELOPED_DATA: + content_type = ASN1_pkcs7_enveloped_data_oid; + break; + case OID_PKCS7_SIGNED_ENVELOPED_DATA: + content_type = ASN1_pkcs7_signed_enveloped_data_oid; + break; + case OID_PKCS7_DIGESTED_DATA: + content_type = ASN1_pkcs7_digested_data_oid; + break; + case OID_PKCS7_ENCRYPTED_DATA: + content_type = ASN1_pkcs7_encrypted_data_oid; + break; + case OID_UNKNOWN: + default: + fprintf(stderr, "invalid pkcs7 contentInfo type"); + return empty_chunk; + } + + return (cInfo->content.ptr == NULL) + ? asn1_simple_object(ASN1_SEQUENCE, content_type) + : asn1_wrap(ASN1_SEQUENCE, "cm" + , content_type + , asn1_simple_object(ASN1_CONTEXT_C_0, cInfo->content) + ); +} + +/* + * build issuerAndSerialNumber object + */ +chunk_t +pkcs7_build_issuerAndSerialNumber(const x509cert_t *cert) +{ + return asn1_wrap(ASN1_SEQUENCE, "cm" + , cert->issuer + , asn1_simple_object(ASN1_INTEGER, cert->serialNumber)); +} + +/* + * create a signed pkcs7 contentInfo object + */ +chunk_t +pkcs7_build_signedData(chunk_t data, chunk_t attributes, const x509cert_t *cert +, int digest_alg, const RSA_private_key_t *key) +{ + contentInfo_t pkcs7Data, signedData; + chunk_t authenticatedAttributes, encryptedDigest, signerInfo, cInfo; + + chunk_t digestAlgorithm = asn1_algorithmIdentifier(digest_alg); + + if (attributes.ptr != NULL) + { + encryptedDigest = pkcs1_build_signature(attributes, digest_alg + , key, FALSE); + clonetochunk(authenticatedAttributes, attributes.ptr, attributes.len + , "authenticatedAttributes"); + *authenticatedAttributes.ptr = ASN1_CONTEXT_C_0; + } + else + { + encryptedDigest = (data.ptr == NULL)? empty_chunk + : pkcs1_build_signature(data, digest_alg, key, FALSE); + authenticatedAttributes = empty_chunk; + } + + signerInfo = asn1_wrap(ASN1_SEQUENCE, "cmcmcm" + , ASN1_INTEGER_1 + , pkcs7_build_issuerAndSerialNumber(cert) + , digestAlgorithm + , authenticatedAttributes + , ASN1_rsaEncryption_id + , encryptedDigest); + + pkcs7Data.type = OID_PKCS7_DATA; + pkcs7Data.content = (data.ptr == NULL)? empty_chunk + : asn1_simple_object(ASN1_OCTET_STRING, data); + + signedData.type = OID_PKCS7_SIGNED_DATA; + signedData.content = asn1_wrap(ASN1_SEQUENCE, "cmmmm" + , ASN1_INTEGER_1 + , asn1_simple_object(ASN1_SET, digestAlgorithm) + , pkcs7_build_contentInfo(&pkcs7Data) + , asn1_simple_object(ASN1_CONTEXT_C_0, cert->certificate) + , asn1_wrap(ASN1_SET, "m", signerInfo)); + + cInfo = pkcs7_build_contentInfo(&signedData); + DBG(DBG_RAW, + DBG_dump_chunk("signedData:\n", cInfo) + ) + + freeanychunk(pkcs7Data.content); + freeanychunk(signedData.content); + return cInfo; +} + +/* + * create a symmetrically encrypted pkcs7 contentInfo object + */ +chunk_t +pkcs7_build_envelopedData(chunk_t data, const x509cert_t *cert, int cipher) +{ + bool des_check_key_save; + des_key_schedule ks[3]; + des_cblock key[3], des_iv, des_iv_buf; + + chunk_t iv = { (u_char *)des_iv_buf, DES_CBC_BLOCK_SIZE }; + chunk_t out; + chunk_t cipher_oid; + + u_int total_keys, i; + size_t padding = pad_up(data.len, DES_CBC_BLOCK_SIZE); + + RSA_public_key_t public_key; + + init_RSA_public_key(&public_key, cert->publicExponent + , cert->modulus); + + if (padding == 0) + padding += DES_CBC_BLOCK_SIZE; + + out.len = data.len + padding; + out.ptr = alloc_bytes(out.len, "DES-encrypted output"); + + DBG(DBG_CONTROL, + DBG_log("padding %d bytes of data to multiple DES block size of %d bytes" + , (int)data.len, (int)out.len) + ) + + /* copy data */ + memcpy(out.ptr, data.ptr, data.len); + /* append padding */ + memset(out.ptr + data.len, padding, padding); + + DBG(DBG_RAW, + DBG_dump_chunk("Padded unencrypted data:\n", out) + ) + + /* select OID and keylength for specified cipher */ + switch (cipher) + { + case OID_DES_CBC: + total_keys = 1; + cipher_oid = ASN1_des_cbc_oid; + break; + case OID_3DES_EDE_CBC: + default: + total_keys = 3; + cipher_oid = ASN1_3des_ede_cbc_oid; + } + DBG(DBG_CONTROLMORE, + DBG_log("pkcs7 encryption cipher: %s", oid_names[cipher].name) + ) + + /* generate a strong random key for DES/3DES */ + des_check_key_save = des_check_key; + des_check_key = TRUE; + for (i = 0; i < total_keys;i++) + { + for (;;) + { + get_rnd_bytes((char*)key[i], DES_CBC_BLOCK_SIZE); + des_set_odd_parity(&key[i]); + if (!des_set_key(&key[i], ks[i])) + break; + plog("weak DES key discarded - we try again"); + } + DBG(DBG_PRIVATE, + DBG_dump("DES key:", key[i], 8) + ) + } + des_check_key = des_check_key_save; + + /* generate an iv for DES/3DES CBC */ + get_rnd_bytes(des_iv, DES_CBC_BLOCK_SIZE); + memcpy(iv.ptr, des_iv, DES_CBC_BLOCK_SIZE); + DBG(DBG_RAW, + DBG_dump_chunk("DES IV :", iv) + ) + + /* encryption using specified cipher */ + switch (cipher) + { + case OID_DES_CBC: + des_cbc_encrypt((des_cblock*)out.ptr, (des_cblock*)out.ptr, out.len + , ks[0], &des_iv, DES_ENCRYPT); + break; + case OID_3DES_EDE_CBC: + default: + des_ede3_cbc_encrypt((des_cblock*)out.ptr, (des_cblock*)out.ptr, out.len + , ks[0], ks[1], ks[2], &des_iv, DES_ENCRYPT); + } + DBG(DBG_RAW, + DBG_dump_chunk("Encrypted data:\n", out)); + + /* build pkcs7 enveloped data object */ + { + chunk_t contentEncryptionAlgorithm = asn1_wrap(ASN1_SEQUENCE, "cm" + , cipher_oid + , asn1_simple_object(ASN1_OCTET_STRING, iv)); + + chunk_t encryptedContentInfo = asn1_wrap(ASN1_SEQUENCE, "cmm" + , ASN1_pkcs7_data_oid + , contentEncryptionAlgorithm + , asn1_wrap(ASN1_CONTEXT_S_0, "m", out)); + + chunk_t plainKey = { (u_char *)key, DES_CBC_BLOCK_SIZE * total_keys }; + + chunk_t encryptedKey = asn1_wrap(ASN1_OCTET_STRING, "m" + , RSA_encrypt(&public_key, plainKey)); + + chunk_t recipientInfo = asn1_wrap(ASN1_SEQUENCE, "cmcm" + , ASN1_INTEGER_0 + , pkcs7_build_issuerAndSerialNumber(cert) + , ASN1_rsaEncryption_id + , encryptedKey); + + chunk_t cInfo; + contentInfo_t envelopedData; + + envelopedData.type = OID_PKCS7_ENVELOPED_DATA; + envelopedData.content = asn1_wrap(ASN1_SEQUENCE, "cmm" + , ASN1_INTEGER_0 + , asn1_wrap(ASN1_SET, "m", recipientInfo) + , encryptedContentInfo); + + cInfo = pkcs7_build_contentInfo(&envelopedData); + DBG(DBG_RAW, + DBG_dump_chunk("envelopedData:\n", cInfo) + ) + + free_RSA_public_content(&public_key); + freeanychunk(envelopedData.content); + return cInfo; + } +} diff --git a/src/pluto/pkcs7.h b/src/pluto/pkcs7.h new file mode 100644 index 000000000..38c633f4e --- /dev/null +++ b/src/pluto/pkcs7.h @@ -0,0 +1,51 @@ +/* Support of PKCS#7 data structures + * Copyright (C) 2005 Jan Hutter, Martin Willi + * Copyright (C) 2002-2005 Andreas Steffen + * Hochschule fuer Technik Rapperswil, Switzerland + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: pkcs7.h,v 1.10 2005/12/22 22:11:24 as Exp $ + */ + +#ifndef _PKCS7_H +#define _PKCS7_H + +#include "defs.h" +#include "pkcs1.h" +#include "x509.h" + +/* Access structure for a PKCS#7 ContentInfo object */ + +typedef struct contentInfo contentInfo_t; + +struct contentInfo { + int type; + chunk_t content; +}; + +extern const contentInfo_t empty_contentInfo; + +extern bool pkcs7_parse_contentInfo(chunk_t blob, u_int level0 + , contentInfo_t *cInfo); +extern bool pkcs7_parse_signedData(chunk_t blob, contentInfo_t *data + , x509cert_t **cert, chunk_t *attributes, const x509cert_t *cacert); +extern bool pkcs7_parse_envelopedData(chunk_t blob, chunk_t *data + , chunk_t serialNumber, const RSA_private_key_t *key); +extern chunk_t pkcs7_contentType_attribute(void); +extern chunk_t pkcs7_messageDigest_attribute(chunk_t content, int digest_alg); +extern chunk_t pkcs7_build_issuerAndSerialNumber(const x509cert_t *cert); +extern chunk_t pkcs7_build_signedData(chunk_t data, chunk_t attributes + ,const x509cert_t *cert, int digest_alg, const RSA_private_key_t *key); +extern chunk_t pkcs7_build_envelopedData(chunk_t data, const x509cert_t *cert + , int cipher); + +#endif /* _PKCS7_H */ diff --git a/src/pluto/pluto.8 b/src/pluto/pluto.8 new file mode 100644 index 000000000..b80d13772 --- /dev/null +++ b/src/pluto/pluto.8 @@ -0,0 +1,1649 @@ +.TH IPSEC_PLUTO 8 "28 March 1999" +.SH NAME +ipsec pluto \- IPsec IKE keying daemon +.br +ipsec whack \- control interface for IPSEC keying daemon +.SH SYNOPSIS +.na +.nh +.HP +.ft B +ipsec pluto +[\-\-help] +[\-\-version] +[\-\-optionsfrom\ \c +\fIfilename\fP] +[\-\-nofork] +[\-\-stderrlog] +[\-\-noklips] +[\-\-uniqueids] +[\fB\-\-interface\fP \fIinterfacename\fP] +[\-\-ikeport\ \c +\fIportnumber\fP] +[\-\-ctlbase\ \c +\fIpath\fP] +[\-\-secretsfile\ \c +\fIsecrets\(hyfile\fP] +[\-\-adns \fIpathname\fP] +[\-\-lwdnsq \fIpathname\fP] +[\-\-perpeerlog] +[\-\-perpeerlogbase\ \c +\fIdirname\fP] +[\-\-debug\(hynone] +[\-\-debug\(hyall] +[\-\-debug\(hyraw] +[\-\-debug\(hycrypt] +[\-\-debug\(hyparsing] +[\-\-debug\(hyemitting] +[\-\-debug\(hycontrol] +[\-\-debug\(hylifecycle] +[\-\-debug\(hyklips] +[\-\-debug\(hydns] +[\-\-debug\(hyoppo] +[\-\-debug\(hyprivate] +.HP +.ft B +ipsec whack +[\-\-help] +[\-\-version] +.HP +.ft B +ipsec whack +\-\-name\ \c +\fIconnection-name\fP +.br +[\-\-id\ \c +\fIid\fP] \c +[\-\-host\ \c +\fIip\(hyaddress\fP] +[\-\-ikeport\ \c +\fIport\(hynumber\fP] +[\-\-nexthop\ \c +\fIip\(hyaddress\fP] +[\-\-client\ \c +\fIsubnet\fP] +[\-\-dnskeyondemand] +[\-\-updown\ \c +\fIupdown\fP] +.br +\-\-to +.br +[\-\-id\ \c +\fIid\fP] +[\-\-host\ \c +\fIip\(hyaddress\fP] +[\-\-ikeport\ \c +\fIport\(hynumber\fP] +[\-\-nexthop\ \c +\fIip\(hyaddress\fP] +[\-\-client\ \c +\fIsubnet\fP] +[\-\-dnskeyondemand] +[\-\-updown\ \c +\fIupdown\fP] +.br +[\-\-psk] +[\-\-rsasig] +[\-\-encrypt] +[\-\-authenticate] +[\-\-compress] +[\-\-tunnel] +[\-\-pfs] +[\-\-disablearrivalcheck] +[\-\-ipv4] +[\-\-ipv6] +[\-\-tunnelipv4] +[\-\-tunnelipv6] +[\-\-ikelifetime\ \c +\fIseconds\fP] +[\-\-ipseclifetime\ \c +\fIseconds\fP] +[\-\-rekeymargin\ \c +\fIseconds\fP] +[\-\-rekeyfuzz\ \c +\fIpercentage\fP] +[\-\-keyingtries\ \c +\fIcount\fP] +[\-\-dontrekey] +[\-\-delete] +[\-\-ctlbase\ \c +\fIpath\fP] +[\-\-optionsfrom\ \c +\fIfilename\fP] +[\-\-label\ \c +\fIstring\fP] +.HP +.ft B +ipsec whack +\-\-keyid\ \c +\fIid\fP +[\-\-addkey] +[\-\-pubkeyrsa\ \c +\fIkey\fP] +[\-\-ctlbase\ \c +\fIpath\fP] +[\-\-optionsfrom\ \c +\fIfilename\fP] +[\-\-label\ \c +\fIstring\fP] +.HP +.ft B +ipsec whack +\-\-myid\ \c +\fIid\fP +.HP +.ft B +ipsec whack +\-\-listen|\-\-unlisten +[\-\-ctlbase\ \c +\fIpath\fP] +[\-\-optionsfrom\ \c +\fIfilename\fP] +[\-\-label\ \c +\fIstring\fP] +.HP +.ft B +ipsec whack +\-\-route|\-\-unroute +\-\-name\ \c +\fIconnection-name\fP +[\-\-ctlbase\ \c +\fIpath\fP] +[\-\-optionsfrom\ \c +\fIfilename\fP] +[\-\-label\ \c +\fIstring\fP] +.HP +.ft B +ipsec whack +\-\-initiate|\-\-terminate +\-\-name\ \c +\fIconnection-name\fP +[\-\-asynchronous] +[\-\-ctlbase\ \c +\fIpath\fP] +[\-\-optionsfrom\ \c +\fIfilename\fP] +[\-\-label\ \c +\fIstring\fP] +.HP +.ft B +ipsec whack +[\-\-tunnelipv4] +[\-\-tunnelipv6] +\-\-oppohere \fIip\(hyaddress\fP +\-\-oppothere \fIip\(hyaddress\fP +.HP +.ft B +ipsec whack +\-\-delete +\-\-name\ \c +\fIconnection-name\fP +[\-\-ctlbase\ \c +\fIpath\fP] +[\-\-optionsfrom\ \c +\fIfilename\fP] +[\-\-label\ \c +\fIstring\fP] +.HP +.ft B +ipsec whack +\-\-deletestate\ \c +\fIstate-number\fP +[\-\-ctlbase\ \c +\fIpath\fP] +[\-\-optionsfrom\ \c +\fIfilename\fP] +[\-\-label\ \c +\fIstring\fP] +.HP +.ft B +ipsec whack +[\-\-name\ \c +\fIconnection-name\fP] +[\-\-debug\(hynone] +[\-\-debug\(hyall] +[\-\-debug\(hyraw] +[\-\-debug\(hycrypt] +[\-\-debug\(hyparsing] +[\-\-debug\(hyemitting] +[\-\-debug\(hycontrol] +[\-\-debug\(hylifecycle] +[\-\-debug\(hyklips] +[\-\-debug\(hydns] +[\-\-debug\(hyoppo] +[\-\-debug\(hyprivate] +[\-\-ctlbase\ \c +\fIpath\fP] +[\-\-optionsfrom\ \c +\fIfilename\fP] +[\-\-label\ \c +\fIstring\fP] +.HP +.ft B +ipsec whack +\-\-status +[\-\-ctlbase\ \c +\fIpath\fP] +[\-\-optionsfrom\ \c +\fIfilename\fP] +[\-\-label\ \c +\fIstring\fP] +.HP +.ft B +ipsec whack +\-\-shutdown +[\-\-ctlbase\ \c +\fIpath\fP] +[\-\-optionsfrom\ \c +\fIfilename\fP] +[\-\-label\ \c +\fIstring\fP] +.ft R +.hy +.ad +.SH DESCRIPTION +.BR pluto +is an IKE (``IPsec Key Exchange'') daemon. +.BR whack +is an auxiliary program to allow requests to be made to a running +.BR pluto . +.LP +.BR pluto +is used to automatically build shared ``security associations'' on a +system that has IPsec, the secure IP protocol. +In other words, +.BR pluto +can eliminate much of the work of manual keying. +The actual +secure transmission of packets is the responsibility of other parts of +the system (see +.BR KLIPS , +the companion implementation of IPsec). +\fIipsec_auto\fP(8) provides a more convenient interface to +\fBpluto\fP and \fBwhack\fP. +.SS IKE's Job +.LP +A \fISecurity Association\fP (\fISA\fP) is an agreement between two network nodes on +how to process certain traffic between them. This processing involves +encapsulation, authentication, encryption, or compression. +.LP +IKE can be deployed on a network node to negotiate Security +Associations for that node. These IKE implementations can only +negotiate with other IKE implementations, so IKE must be on each node +that is to be an endpoint of an IKE-negotiated Security Association. +No other nodes need to be running IKE. +.LP +An IKE instance (i.e. an IKE implementation on a particular network +node) communicates with another IKE instance using UDP IP packets, so +there must be a route between the nodes in each direction. +.LP +The negotiation of Security Associations requires a number of choices +that involve tradeoffs between security, convenience, trust, and +efficiency. These are policy issues and are normally specified to the +IKE instance by the system administrator. +.LP +IKE deals with two kinds of Security Associations. The first part of +a negotiation between IKE instances is to build an ISAKMP SA. An +ISAKMP SA is used to protect communication between the two IKEs. +IPsec SAs can then be built by the IKEs \- these are used to carry +protected IP traffic between the systems. +.LP +The negotiation of the ISAKMP SA is known as Phase 1. In theory, +Phase 1 can be accomplished by a couple of different exchange types, +but we only implement one called Main Mode (we don't implement +Aggressive Mode). +.LP +Any negotiation under the protection of an ISAKMP SA, including the +negotiation of IPsec SAs, is part of Phase 2. The exchange type +that we use to negotiate an IPsec SA is called Quick Mode. +.LP +IKE instances must be able to authenticate each other as part of their +negotiation of an ISAKMP SA. This can be done by several mechanisms +described in the draft standards. +.LP +IKE negotiation can be initiated by any instance with any other. If +both can find an agreeable set of characteristics for a Security +Association, and both recognize each others authenticity, they can set +up a Security Association. The standards do not specify what causes +an IKE instance to initiate a negotiation. +.LP +In summary, an IKE instance is prepared to automate the management of +Security Associations in an IPsec environment, but a number of issues +are considered policy and are left in the system administrator's hands. +.SS Pluto +.LP +\fBpluto\fP is an implementation of IKE. It runs as a daemon on a network +node. Currently, this network node must be a LINUX system running the +\fBKLIPS\fP implementation of IPsec. +.LP +\fBpluto\fP only implements a subset of IKE. This is enough for it to +interoperate with other instances of \fBpluto\fP, and many other IKE +implementations. We are working on implementing more of IKE. +.LP +The policy for acceptable characteristics for Security Associations is +mostly hardwired into the code of \fBpluto\fP (spdb.c). Eventually +this will be moved into a security policy database with reasonable +expressive power and more convenience. +.LP +\fBpluto\fP uses shared secrets or RSA signatures to authenticate +peers with whom it is negotiating. +.LP +\fBpluto\fP initiates negotiation of a Security Association when it is +manually prodded: the program \fBwhack\fP is run to trigger this. +It will also initiate a negotiation when \fBKLIPS\fP traps an outbound packet +for Opportunistic Encryption. +.LP +\fBpluto\fP implements ISAKMP SAs itself. After it has negotiated the +characteristics of an IPsec SA, it directs \fBKLIPS\fP to implement it. +It also invokes a script to adjust any firewall and issue \fIroute\fP(8) +commands to direct IP packets through \fBKLIPS\fP. +.LP +When \fBpluto\fP shuts down, it closes all Security Associations. +.SS Before Running Pluto +.LP +\fBpluto\fP runs as a daemon with userid root. Before running it, a few +things must be set up. +.LP +\fBpluto\fP requires \fBKLIPS\fP, the FreeS/WAN implementation of IPsec. +All of the components of \fBKLIPS\fP and \fBpluto\fP should be installed. +.LP +\fBpluto\fP supports multiple public networks (that is, networks +that are considered insecure and thus need to have their traffic +encrypted or authenticated). It discovers the +public interfaces to use by looking at all interfaces that are +configured (the \fB\-\-interface\fP option can be used to limit +the interfaces considered). +It does this only when \fBwhack\fP tells it to \-\-listen, +so the interfaces must be configured by then. Each interface with a name of the form +\fBipsec\fP[\fB0\fP-\fB9\fP] is taken as a \fBKLIPS\fP virtual public interface. +Another network interface with the same IP address (there should be only +one) is taken as the corresponding real public +interface. \fIifconfig\fP(8) with the \fB\-a\fP flag will show +the name and status of each network interface. +.LP +\fBpluto\fP requires a database of preshared secrets and RSA private keys. +This is described in the +.IR ipsec.secrets (5). +\fBpluto\fP is told of RSA public keys via \fBwhack\fP commands. +If the connection is Opportunistic, and no RSA public key is known, +\fBpluto\fP will attempt to fetch RSA keys using the Domain Name System. +.SS Setting up \fBKLIPS\fP for \fBpluto\fP +.LP +The most basic network topology that \fBpluto\fP supports has two security +gateways negotiating on behalf of client subnets. The diagram of RGB's +testbed is a good example (see \fIklips/doc/rgb_setup.txt\fP). +.LP +The file \fIINSTALL\fP in the base directory of this distribution +explains how to start setting up the whole system, including \fBKLIPS\fP. +.LP +Make sure that the security gateways have routes to each other. This +is usually covered by the default route, but may require issuing +.IR route (8) +commands. The route must go through a particular IP +interface (we will assume it is \fIeth0\fP, but it need not be). The +interface that connects the security gateway to its client must be a +different one. +.LP +It is necessary to issue a +.IR ipsec_tncfg (8) +command on each gateway. The required command is: + +\ \ \ ipsec tncfg \-\-attach\ \-\-virtual\ ipsec0 \-\-physical\ eth0 + +A command to set up the ipsec0 virtual interface will also need to be +run. It will have the same parameters as the command used to set up +the physical interface to which it has just been connected using +.IR ipsec_tncfg (8). +.SS ipsec.secrets file +.LP +A \fBpluto\fP daemon and another IKE daemon (for example, another instance +of \fBpluto\fP) must convince each other that they are who they are supposed +to be before any negotiation can succeed. This authentication is +accomplished by using either secrets that have been shared beforehand +(manually) or by using RSA signatures. There are other techniques, +but they have not been implemented in \fBpluto\fP. +.LP +The file \fI/etc/ipsec.secrets\fP is used to keep preshared secret keys +and RSA private keys for +authentication with other IKE daemons. For debugging, there is an +argument to the \fBpluto\fP command to use a different file. +This file is described in +.IR ipsec.secrets (5). +.SS Running Pluto +.LP +To fire up the daemon, just type \fBpluto\fP (be sure to be running as +the superuser). +The default IKE port number is 500, the UDP port assigned by IANA for IKE Daemons. +\fBpluto\fP must be run by the superuser to be able to use the UDP 500 port. +.LP +\fBpluto\fP attempts to create a lockfile with the name +\fI/var/run/pluto.pid\fP. If the lockfile cannot be created, +\fBpluto\fP exits \- this prevents multiple \fBpluto\fPs from +competing Any ``leftover'' lockfile must be removed before +\fBpluto\fP will run. \fBpluto\fP writes its pid into this file so +that scripts can find it. This lock will not function properly if it +is on an NFS volume (but sharing locks on multiple machines doesn't +make sense anyway). +.LP +\fBpluto\fP then forks and the parent exits. This is the conventional +``daemon fork''. It can make debugging awkward, so there is an option +to suppress this fork. +.LP +All logging, including diagnostics, is sent to +.IR syslog (3) +with facility=authpriv; +it decides where to put these messages (possibly in /var/log/secure). +Since this too can make debugging awkward, there is an option to +steer logging to stderr. +.LP +If the \fB\-\-perpeerlog\fP option is given, then pluto will open +a log file per connection. By default, this is in /var/log/pluto/peer, +in a subdirectory formed by turning all dot (.) [IPv4} or colon (:) +[IPv6] into slashes (/). +.LP +The base directory can be changed with the \fB\-\-perpeerlogbase\fP. +.LP +Once \fBpluto\fP is started, it waits for requests from \fBwhack\fP. +.SS Pluto's Internal State +.LP +To understand how to use \fBpluto\fP, it is helpful to understand a little +about its internal state. Furthermore, the terminology is needed to decipher +some of the diagnostic messages. +.LP +The \fI(potential) connection\fP database describes attributes of a +connection. These include the IP addresses of the hosts and client +subnets and the security characteristics desired. \fBpluto\fP +requires this information (simply called a connection) before it can +respond to a request to build an SA. Each connection is given a name +when it is created, and all references are made using this name. +.LP +During the IKE exchange to build an SA, the information about the +negotiation is represented in a \fIstate object\fP. Each state object +reflects how far the negotiation has reached. Once the negotiation is +complete and the SA established, the state object remains to represent +the SA. When the SA is terminated, the state object is discarded. +Each State object is given a serial number and this is used to refer +to the state objects in logged messages. +.LP +Each state object corresponds to a connection and can be thought of +as an instantiation of that connection. +At any particular time, there may be any number of state objects +corresponding to a particular connection. +Often there is one representing an ISAKMP SA and another representing +an IPsec SA. +.LP +\fBKLIPS\fP hooks into the routing code in a LINUX kernel. +Traffic to be processed by an IPsec SA must be directed through +\fBKLIPS\fP by routing commands. Furthermore, the processing to be +done is specified by \fIipsec eroute(8)\fP commands. +\fBpluto\fP takes the responsibility of managing both of these special +kinds of routes. +.LP +Each connection may be routed, and must be while it has an IPsec SA. +The connection specifies the characteristics of the route: the +interface on this machine, the ``gateway'' (the nexthop), +and the peer's client subnet. Two +connections may not be simultaneously routed if they are for the same +peer's client subnet but use different interfaces or gateways +(\fBpluto\fP's logic does not reflect any advanced routing capabilities). +.LP +Each eroute is associated with the state object for an IPsec SA +because it has the particular characteristics of the SA. +Two eroutes conflict if they specify the identical local +and remote clients (unlike for routes, the local clients are +taken into account). +.LP +When \fBpluto\fP needs to install a route for a connection, +it must make sure that no conflicting route is in use. If another +connection has a conflicting route, that route will be taken down, as long +as there is no IPsec SA instantiating that connection. +If there is such an IPsec SA, the attempt to install a route will fail. +.LP +There is an exception. If \fBpluto\fP, as Responder, needs to install +a route to a fixed client subnet for a connection, and there is +already a conflicting route, then the SAs using the route are deleted +to make room for the new SAs. The rationale is that the new +connection is probably more current. The need for this usually is a +product of Road Warrior connections (these are explained later; they +cannot be used to initiate). +.LP +When \fBpluto\fP needs to install an eroute for an IPsec SA (for a +state object), first the state object's connection must be routed (if +this cannot be done, the eroute and SA will not be installed). +If a conflicting eroute is already in place for another connection, +the eroute and SA will not be installed (but note that the routing +exception mentioned above may have already deleted potentially conflicting SAs). +If another IPsec +SA for the same connection already has an eroute, all its outgoing traffic +is taken over by the new eroute. The incoming traffic will still be +processed. This characteristic is exploited during rekeying. +.LP +All of these routing characteristics are expected change when +\fBKLIPS\fP is modified to use the firewall hooks in the LINUX 2.4.x +kernel. +.SS Using Whack +.LP +\fBwhack\fP is used to command a running \fBpluto\fP. +\fBwhack\fP uses a UNIX domain socket to speak to \fBpluto\fP +(by default, \fI/var/pluto.ctl\fP). +.LP +\fBwhack\fP has an intricate argument syntax. +This syntax allows many different functions to be specified. +The help form shows the usage or version information. +The connection form gives \fBpluto\fP a description of a potential connection. +The public key form informs \fBpluto\fP of the RSA public key for a potential peer. +The delete form deletes a connection description and all SAs corresponding +to it. +The listen form tells \fBpluto\fP to start or stop listening on the public interfaces +for IKE requests from peers. +The route form tells \fBpluto\fP to set up routing for a connection; +the unroute form undoes this. +The initiate form tells \fBpluto\fP to negotiate an SA corresponding to a connection. +The terminate form tells \fBpluto\fP to remove all SAs corresponding to a connection, +including those being negotiated. +The status form displays the \fBpluto\fP's internal state. +The debug form tells \fBpluto\fP to change the selection of debugging output +``on the fly''. The shutdown form tells +\fBpluto\fP to shut down, deleting all SAs. +.LP +Most options are specific to one of the forms, and will be described +with that form. There are three options that apply to all forms. +.TP +\fB\-\-ctlbase\fP\ \fIpath\fP +\fIpath\fP.ctl is used as the UNIX domain socket for talking +to \fBpluto\fP. +This option facilitates debugging. +.TP +\fB\-\-optionsfrom\fP\ \fIfilename\fP +adds the contents of the file to the argument list. +.TP +\fB\-\-label\fP\ \fIstring\fP +adds the string to all error messages generated by \fBwhack\fP. +.LP +The help form of \fBwhack\fP is self-explanatory. +.TP +\fB\-\-help\fP +display the usage message. +.TP +\fB\-\-version\fP +display the version of \fBwhack\fP. +.LP +The connection form describes a potential connection to \fBpluto\fP. +\fBpluto\fP needs to know what connections can and should be negotiated. +When \fBpluto\fP is the initiator, it needs to know what to propose. +When \fBpluto\fP is the responder, it needs to know enough to decide whether +is is willing to set up the proposed connection. +.LP +The description of a potential connection can specify a large number +of details. Each connection has a unique name. This name will appear +in a updown shell command, so it should not contain punctuation +that would make the command ill-formed. +.TP +\fB\-\-name\fP\ \fIconnection-name\fP +.LP +The topology of +a connection is symmetric, so to save space here is half a picture: + +\ \ \ client_subnet<\-\->host:ikeport<\-\->nexthop<\-\-\- + +A similar trick is used in the flags. The same flag names are used for +both ends. Those before the \fB\-\-to\fP flag describe the left side +and those afterwards describe the right side. When \fBpluto\fP attempts +to use the connection, it decides whether it is the left side or the right +side of the connection, based on the IP numbers of its interfaces. +.TP +\fB\-\-id\fP\ \fIid\fP +the identity of the end. Currently, this can be an IP address (specified +as dotted quad or as a Fully Qualified Domain Name, which will be resolved +immediately) or as a Fully Qualified Domain Name itself (prefixed by ``@'' +to signify that it should not be resolved), or as user@FQDN, or as the +magic value \fB%myid\fP. +\fBPluto\fP only authenticates the identity, and does not use it for +addressing, so, for example, an IP address need not be the one to which +packets are to be sent. If the option is absent, the +identity defaults to the IP address specified by \fB\-\-host\fP. +\fB%myid\fP allows the identity to be separately specified (by the \fBpluto\fP or \fBwhack\fP option \fB\-\-myid\fP +or by the \fBipsec.conf\fP(5) \fBconfig setup\fP parameter \fPmyid\fP). +Otherwise, \fBpluto\fP tries to guess what \fB%myid\fP should stand for: +the IP address of \fB%defaultroute\fP, if it is supported by a suitable TXT record in the reverse domain for that IP address, +or the system's hostname, if it is supported by a suitable TXT record in its forward domain. +.\" The identity is transmitted in the IKE protocol, and is what is authenticated. +.TP +\fB\-\-host\fP\ \fIip\(hyaddress\fP +.TP +\fB\-\-host\fP\ \fB%any\fP +.TP +\fB\-\-host\fP\ \fB%opportunistic\fP +the IP address of the end (generally the public interface). +If \fBpluto\fP is to act as a responder +for IKE negotiations initiated from unknown IP addresses (the +``Road Warrior'' case), the +IP address should be specified as \fB%any\fP (currently, +the obsolete notation \fB0.0.0.0\fP is also accepted for this). +If \fBpluto\fP is to opportunistically initiate the connection, +use \fB%opportunistic\fP +.TP +\fB\-\-ikeport\fP\ \fIport\(hynumber\fP +the UDP port that IKE listens to on that host. The default is 500. +(\fBpluto\fP on this machine uses the port specified by its own command +line argument, so this only affects where \fBpluto\fP sends messages.) +.TP +\fB\-\-nexthop\fP\ \fIip\(hyaddress\fP +where to route packets for the peer's client (presumably for the peer too, +but it will not be used for this). +When \fBpluto\fP installs an IPsec SA, it issues a route command. +It uses the nexthop as the gateway. +The default is the peer's IP address (this can be explicitly written as +\fB%direct\fP; the obsolete notation \fB0.0.0.0\fP is accepted). +This option is necessary if \fBpluto\fP's host's interface used for sending +packets to the peer is neither point-to-point nor directly connected to the +peer. +.TP +\fB\-\-client\fP\ \fIsubnet\fP +the subnet for which the IPsec traffic will be destined. If not specified, +the host will be the client. +The subnet can be specified in any of the forms supported by \fIipsec_atosubnet\fP(3). +The general form is \fIaddress\fP/\fImask\fP. The \fIaddress\fP can be either +a domain name or four decimal numbers (specifying octets) separated by dots. +The most convenient form of the \fImask\fP is a decimal integer, specifying +the number of leading one bits in the mask. So, for example, 10.0.0.0/8 +would specify the class A network ``Net 10''. +.TP +\fB\-\-dnskeyondemand]\fP +specifies that when an RSA public key is needed to authenticate this +host, and it isn't already known, fetch it from DNS. +.TP +\fB\-\-updown\fP\ \fIupdown\fP +specifies an external shell command to be run whenever \fBpluto\fP +brings up or down a connection. +The script is used to build a shell command, so it may contain positional +parameters, but ought not to have punctuation that would cause the +resulting command to be ill-formed. +The default is \fIipsec _updown\fP. +.TP +\fB\-\-to\fP +separates the specification of the left and right ends of the connection. +.LP +The potential connection description also specifies characteristics of +rekeying and security. +.TP +\fB\-\-psk\fP +Propose and allow preshared secret authentication for IKE peers. This authentication +requires that each side use the same secret. May be combined with \fB\-\-rsasig\fP; +at least one must be specified. +.TP +\fB\-\-rsasig\fP +Propose and allow RSA signatures for authentication of IKE peers. This authentication +requires that each side have have a private key of its own and know the +public key of its peer. May be combined with \fB\-\-psk\fP; +at least one must be specified. +.TP +\fB\-\-encrypt\fP +All proposed or accepted IPsec SAs will include non-null ESP. +The actual choices of transforms are wired into \fBpluto\fP. +.TP +\fB\-\-authenticate\fP +All proposed IPsec SAs will include AH. +All accepted IPsec SAs will include AH or ESP with authentication. +The actual choices of transforms are wired into \fBpluto\fP. +Note that this has nothing to do with IKE authentication. +.TP +\fB\-\-compress\fP +All proposed IPsec SAs will include IPCOMP (compression). +This will be ignored if KLIPS is not configured with IPCOMP support. +.TP +\fB\-\-tunnel\fP +the IPsec SA should use tunneling. Implicit if the SA is for clients. +Must only be used with \fB\-\-authenticate\fP or \fB\-\-encrypt\fP. +.TP +\fB\-\-ipv4\fP +The host addresses will be interpreted as IPv4 addresses. This is the +default. Note that for a connection, all host addresses must be of +the same Address Family (IPv4 and IPv6 use different Address Families). +.TP +\fB\-\-ipv6\fP +The host addresses (including nexthop) will be interpreted as IPv6 addresses. +Note that for a connection, all host addresses must be of +the same Address Family (IPv4 and IPv6 use different Address Families). +.TP +\fB\-\-tunnelipv4\fP +The client addresses will be interpreted as IPv4 addresses. The default is +to match what the host will be. This does not imply \fB\-\-tunnel\fP so the +flag can be safely used when no tunnel is actually specified. +Note that for a connection, all tunnel addresses must be of the same +Address Family. +.TP +\fB\-\-tunnelipv6\fP +The client addresses will be interpreted as IPv6 addresses. The default is +to match what the host will be. This does not imply \fB\-\-tunnel\fP so the +flag can be safely used when no tunnel is actually specified. +Note that for a connection, all tunnel addresses must be of the same +Address Family. +.TP +\fB\-\-pfs\fP +There should be Perfect Forward Secrecy \- new keying material will +be generated for each IPsec SA rather than being derived from the ISAKMP +SA keying material. +Since the group to be used cannot be negotiated (a dubious feature of the +standard), \fBpluto\fP will propose the same group that was used during Phase 1. +We don't implement a stronger form of PFS which would require that the +ISAKMP SA be deleted after the IPSEC SA is negotiated. +.TP +\fB\-\-disablearrivalcheck\fP +If the connection is a tunnel, allow packets arriving through the tunnel +to have any source and destination addresses. +.LP +If none of the \fB\-\-encrypt\fP, \fB\-\-authenticate\fP, \fB\-\-compress\fP, +or \fB\-\-pfs\fP flags is given, the initiating the connection will +only build an ISAKMP SA. For such a connection, client subnets have +no meaning and must not be specified. +.LP +More work is needed to allow for flexible policies. Currently +policy is hardwired in the source file spdb.c. The ISAKMP SAs may use +Oakley groups MODP1024 and MODP1536; 3DES encryption; SHA1-96 +and MD5-96 authentication. The IPsec SAs may use 3DES and +MD5-96 or SHA1-96 for ESP, or just MD5-96 or SHA1-96 for AH. +IPCOMP Compression is always Deflate. +.TP +\fB\-\-ikelifetime\fP\ \fIseconds\fP +how long \fBpluto\fP will propose that an ISAKMP SA be allowed to live. +The default is 10800 (three hours) and the maximum is 86400 (one day). +This option will not affect what is accepted. +\fBpluto\fP will reject proposals that exceed the maximum. +.TP +\fB\-\-ipseclifetime\fP\ \fIseconds\fP +how long \fBpluto\fP will propose that an IPsec SA be allowed to live. +The default is 3600 (one hour) and the maximum is 86400 (one day). +This option will not affect what is accepted. +\fBpluto\fP will reject proposals that exceed the maximum. +.TP +\fB\-\-rekeymargin\fP\ \fIseconds\fP +how long before an SA's expiration should \fBpluto\fP try to negotiate +a replacement SA. This will only happen if \fBpluto\fP was the initiator. +The default is 540 (nine minutes). +.TP +\fB\-\-rekeyfuzz\fP\ \fIpercentage\fP +maximum size of random component to add to rekeymargin, expressed as +a percentage of rekeymargin. \fBpluto\fP will select a delay uniformly +distributed within this range. By default, the percentage will be 100. +If greater determinism is desired, specify 0. It may be appropriate +for the percentage to be much larger than 100. +.TP +\fB\-\-keyingtries\fP\ \fIcount\fP +how many times \fBpluto\fP should try to negotiate an SA, +either for the first time or for rekeying. +A value of 0 is interpreted as a very large number: never give up. +The default is three. +.TP +\fB\-\-dontrekey\fP +A misnomer. +Only rekey a connection if we were the Initiator and there was recent +traffic on the existing connection. +This applies to Phase 1 and Phase 2. +This is currently the only automatic way for a connection to terminate. +It may be useful with Road Warrior or Opportunistic connections. +.br +Since SA lifetime negotiation is take-it-or-leave it, a Responder +normally uses the shorter of the negotiated or the configured lifetime. +This only works because if the lifetime is shorter than negotiated, +the Responder will rekey in time so that everything works. +This interacts badly with \fB\-\-dontrekey\fP. In this case, +the Responder will end up rekeying to rectify a shortfall in an IPsec SA +lifetime; for an ISAKMP SA, the Responder will accept the negotiated +lifetime. +.TP +\fB\-\-delete\fP +when used in the connection form, it causes any previous connection +with this name to be deleted before this one is added. Unlike a +normal delete, no diagnostic is produced if there was no previous +connection to delete. Any routing in place for the connection is undone. +.LP +The delete form deletes a named connection description and any +SAs established or negotiations initiated using this connection. +Any routing in place for the connection is undone. +.TP +\fB\-\-delete\fP +.TP +\fB\-\-name\fP\ \fIconnection-name\fP +.LP +The deletestate form deletes the state object with the specified serial number. +This is useful for selectively deleting instances of connections. +.TP +\fB\-\-deletestate\fP\ \fIstate-number\fP +.LP +The route form of the \fBwhack\fP command tells \fBpluto\fP to set up +routing for a connection. +Although like a traditional route, it uses an ipsec device as a +virtual interface. +Once routing is set up, no packets will be +sent ``in the clear'' to the peer's client specified in the connection. +A TRAP shunt eroute will be installed; if outbound traffic is caught, +Pluto will initiate the connection. +An explicit \fBwhack\fP route is not always needed: if it hasn't been +done when an IPsec SA is being installed, one will be automatically attempted. +.LP +When a routing is attempted for a connection, there must not already +be a routing for a different connection with the same subnet but different +interface or destination, or if +there is, it must not be being used by an IPsec SA. Otherwise the +attempt will fail. +.TP +\fB\-\-route\fP +.TP +\fB\-\-name\fP\ \fIconnection-name\fP +.LP +The unroute form of the \fBwhack\fP command tells \fBpluto\fP to undo +a routing. \fBpluto\fP will refuse if an IPsec SA is using the connection. +If another connection is sharing the same routing, it will be left in place. +Without a routing, packets will be sent without encryption or authentication. +.TP +\fB\-\-unroute\fP +.TP +\fB\-\-name\fP\ \fIconnection-name\fP +.LP +The initiate form tells \fBpluto\fP to initiate a negotiation with another +\fBpluto\fP (or other IKE daemon) according to the named connection. +Initiation requires a route that \fB\-\-route\fP would provide; +if none is in place at the time an IPsec SA is being installed, +\fBpluto\fP attempts to set one up. +.TP +\fB\-\-initiate\fP +.TP +\fB\-\-name\fP\ \fIconnection-name\fP +.TP +\fB\-\-asynchronous +.LP +The initiate form of the \fBwhack\fP command will relay back from +\fBpluto\fP status information via the UNIX domain socket (unless +\-\-asynchronous is specified). The status information is meant to +look a bit like that from \fBFTP\fP. Currently \fBwhack\fP simply +copies this to stderr. When the request is finished (eg. the SAs are +established or \fBpluto\fP gives up), \fBpluto\fP closes the channel, +causing \fBwhack\fP to terminate. +.LP +The opportunistic initiate form is mainly used for debugging. +.TP +\fB\-\-tunnelipv4\fP +.TP +\fB\-\-tunnelipv6\fP +.TP +\fB\-\-oppohere\fP\ \fIip-address\fP +.TP +\fB\-\-oppothere\fP\ \fIip-address\fP +.LP +This will cause \fBpluto\fP to attempt to opportunistically initiate a +connection from here to the there, even if a previous attempt +had been made. +The whack log will show the progress of this attempt. +.LP +The terminate form tells \fBpluto\fP to delete any SAs that use the specified +connection and to stop any negotiations in process. +It does not prevent new negotiations from starting (the delete form +has this effect). +.TP +\fB\-\-terminate\fP +.TP +\fB\-\-name\fP\ \fIconnection-name\fP +.LP +The public key for informs \fBpluto\fP of the RSA public key for a potential peer. +Private keys must be kept secret, so they are kept in +.IR ipsec.secrets (5). +.TP +\fB\-\-keyid\ \fP\fIid\fP +specififies the identity of the peer for which a public key should be used. +Its form is identical to the identity in the connection. +If no public key is specified, \fBpluto\fP attempts to find KEY records +from DNS for the id (if a FQDN) or through reverse lookup (if an IP address). +Note that there several interesting ways in which this is not secure. +.TP +\fB\-\-addkey\fP +specifies that the new key is added to the collection; otherwise the +new key replaces any old ones. +.TP +\fB\-\-pubkeyrsa\ \fP\fIkey\fP +specifies the value of the RSA public key. It is a sequence of bytes +as described in RFC 2537 ``RSA/MD5 KEYs and SIGs in the Domain Name System (DNS)''. +It is denoted in a way suitable for \fIipsec_ttodata\fP(3). +For example, a base 64 numeral starts with 0s. +.LP +The listen form tells \fBpluto\fP to start listening for IKE requests +on its public interfaces. To avoid race conditions, it is normal to +load the appropriate connections into \fBpluto\fP before allowing it +to listen. If \fBpluto\fP isn't listening, it is pointless to +initiate negotiations, so it will refuse requests to do so. Whenever +the listen form is used, \fBpluto\fP looks for public interfaces and +will notice when new ones have been added and when old ones have been +removed. This is also the trigger for \fBpluto\fP to read the +\fIipsec.secrets\fP file. So listen may useful more than once. +.TP +\fB\-\-listen\fP +start listening for IKE traffic on public interfaces. +.TP +\fB\-\-unlisten\fP +stop listening for IKE traffic on public interfaces. +.LP +The status form will display information about the internal state of +\fBpluto\fP: information about each potential connection, about +each state object, and about each shunt that \fBpluto\fP is managing +without an associated connection. +.TP +\fB\-\-status\fP +.LP +The shutdown form is the proper way to shut down \fBpluto\fP. +It will tear down the SAs on this machine that \fBpluto\fP has negotiated. +It does not inform its peers, so the SAs on their machines remain. +.TP +\fB\-\-shutdown\fP +.SS Examples +.LP +It would be normal to start \fBpluto\fP in one of the system initialization +scripts. It needs to be run by the superuser. Generally, no arguments are needed. +To run in manually, the superuser can simply type + +\ \ \ ipsec pluto + +The command will immediately return, but a \fBpluto\fP process will be left +running, waiting for requests from \fBwhack\fP or a peer. +.LP +Using \fBwhack\fP, several potential connections would be described: +.HP +.na +\ \ \ ipsec whack \-\-name\ silly +\-\-host\ 127.0.0.1 \-\-to \-\-host\ 127.0.0.2 +\-\-ikelifetime\ 900 \-\-ipseclifetime\ 800 \-\-keyingtries\ 3 +.ad +.LP +Since this silly connection description specifies neither encryption, +authentication, nor tunneling, it could only be used to establish +an ISAKMP SA. +.HP +.na +\ \ \ ipsec whack \-\-name\ secret \-\-host\ 10.0.0.1 \-\-client\ 10.0.1.0/24 +\-\-to \-\-host\ 10.0.0.2 \-\-client\ 10.0.2.0/24 +\-\-encrypt +.ad +.LP +This is something that must be done on both sides. If the other +side is \fBpluto\fP, the same \fBwhack\fP command could be used on it +(the command syntax is designed to not distinguish which end is ours). +.LP +Now that the connections are specified, \fBpluto\fP is ready to handle +requests and replies via the public interfaces. We must tell it to discover +those interfaces and start accepting messages from peers: + +\ \ \ ipsec whack \-\-listen +.LP +If we don't immediately wish to bring up a secure connection between +the two clients, we might wish to prevent insecure traffic. +The routing form asks \fBpluto\fP to cause the packets sent from +our client to the peer's client to be routed through the ipsec0 +device; if there is no SA, they will be discarded: + +\ \ \ ipsec whack \-\-route secret +.LP +Finally, we are ready to get \fBpluto\fP to initiate negotiation +for an IPsec SA (and implicitly, an ISAKMP SA): + +\ \ \ ipsec whack \-\-initiate\ \-\-name\ secret + +A small log of interesting events will appear on standard output +(other logging is sent to syslog). +.LP +\fBwhack\fP can also be used to terminate \fBpluto\fP cleanly, tearing down +all SAs that it has negotiated. + +\ \ \ ipsec whack \-\-shutdown + +Notification of any IPSEC SA deletion, but not ISAKMP SA deletion +is sent to the peer. Unfortunately, such Notification is not reliable. +Furthermore, \fBpluto\fP itself ignores Notifications. +.SS The updown command +.LP +Whenever \fBpluto\fP brings a connection up or down, it invokes +the updown command. This command is specified using the \fB\-\-updown\fP +option. This allows for customized control over routing and firewall manipulation. +.LP +The updown is invoked for five different operations. Each of +these operations can be for our client subnet or for our host itself. +.TP +\fBprepare-host\fP or \fBprepare-client\fP +is run before bringing up a new connection if no other connection +with the same clients is up. Generally, this is useful for deleting a +route that might have been set up before \fBpluto\fP was run or +perhaps by some agent not known to \fBpluto\fP. +.TP +\fBroute-host\fP or \fBroute-client\fP +is run when bringing up a connection for a new peer client subnet +(even if \fBprepare-host\fP or \fBprepare-client\fP was run). The +command should install a suitable route. Routing decisions are based +only on the destination (peer's client) subnet address, unlike eroutes +which discriminate based on source too. +.TP +\fBunroute-host\fP or \fBunroute-client\fP +is run when bringing down the last connection for a particular peer +client subnet. It should undo what the \fBroute-host\fP or \fBroute-client\fP +did. +.TP +\fBup-host\fP or \fBup-client\fP +is run when bringing up a tunnel eroute with a pair of client subnets +that does not already have a tunnel eroute. +This command should install firewall rules as appropriate. +It is generally a good idea to allow IKE messages (UDP port 500) +travel between the hosts. +.TP +\fBdown-host\fP or \fBdown-client\fP +is run when bringing down the eroute for a pair of client subnets. +This command should delete firewall rules as appropriate. Note that +there may remain some inbound IPsec SAs with these client subnets. +.LP +The script is passed a large number of environment variables to specify +what needs to be done. +.TP +\fBPLUTO_VERSION\fP +indicates what version of this interface is being used. This document +describes version 1.1. This is upwardly compatible with version 1.0. +.TP +\fBPLUTO_VERB\fP +specifies the name of the operation to be performed +(\fBprepare-host\fP,r \fBprepare-client\fP, +\fBup-host\fP, \fBup-client\fP, +\fBdown-host\fP, or \fBdown-client\fP). If the address family for +security gateway to security gateway communications is IPv6, then +a suffix of -v6 is added to the verb. +.TP +\fBPLUTO_CONNECTION\fP +is the name of the connection for which we are routing. +.TP +\fBPLUTO_NEXT_HOP\fP +is the next hop to which packets bound for the peer must be sent. +.TP +\fBPLUTO_INTERFACE\fP +is the name of the ipsec interface to be used. +.TP +\fBPLUTO_ME\fP +is the IP address of our host. +.TP +\fBPLUTO_MY_CLIENT\fP +is the IP address / count of our client subnet. +If the client is just the host, this will be the host's own IP address / max +(where max is 32 for IPv4 and 128 for IPv6). +.TP +\fBPLUTO_MY_CLIENT_NET\fP +is the IP address of our client net. +If the client is just the host, this will be the host's own IP address. +.TP +\fBPLUTO_MY_CLIENT_MASK\fP +is the mask for our client net. +If the client is just the host, this will be 255.255.255.255. +.TP +\fBPLUTO_PEER\fP +is the IP address of our peer. +.TP +\fBPLUTO_PEER_CLIENT\fP +is the IP address / count of the peer's client subnet. +If the client is just the peer, this will be the peer's own IP address / max +(where max is 32 for IPv4 and 128 for IPv6). +.TP +\fBPLUTO_PEER_CLIENT_NET\fP +is the IP address of the peer's client net. +If the client is just the peer, this will be the peer's own IP address. +.TP +\fBPLUTO_PEER_CLIENT_MASK\fP +is the mask for the peer's client net. +If the client is just the peer, this will be 255.255.255.255. +.LP +All output sent by the script to stderr or stdout is logged. The +script should return an exit status of 0 if and only if it succeeds. +.LP +\fBPluto\fP waits for the script to finish and will not do any other +processing while it is waiting. +The script may assume that \fBpluto\fP will not change anything +while the script runs. +The script should avoid doing anything that takes much time and it +should not issue any command that requires processing by \fBpluto\fP. +Either of these activities could be performed by a background +subprocess of the script. +.SS Rekeying +.LP +When an SA that was initiated by \fBpluto\fP has only a bit of +lifetime left, +\fBpluto\fP will initiate the creation of a new SA. This applies to +ISAKMP and IPsec SAs. +The rekeying will be initiated when the SA's remaining lifetime is +less than the rekeymargin plus a random percentage, between 0 and +rekeyfuzz, of the rekeymargin. +.LP +Similarly, when an SA that was initiated by the peer has only a bit of +lifetime left, \fBpluto\fP will try to initiate the creation of a +replacement. +To give preference to the initiator, this rekeying will only be initiated +when the SA's remaining lifetime is half of rekeymargin. +If rekeying is done by the responder, the roles will be reversed: the +responder for the old SA will be the initiator for the replacement. +The former initiator might also initiate rekeying, so there may +be redundant SAs created. +To avoid these complications, make sure that rekeymargin is generous. +.LP +One risk of having the former responder initiate is that perhaps +none of its proposals is acceptable to the former initiator +(they have not been used in a successful negotiation). +To reduce the chances of this happening, and to prevent loss of security, +the policy settings are taken from the old SA (this is the case even if +the former initiator is initiating). +These may be stricter than those of the connection. +.LP +\fBpluto\fP will not rekey an SA if that SA is not the most recent of its +type (IPsec or ISAKMP) for its potential connection. +This avoids creating redundant SAs. +.LP +The random component in the rekeying time (rekeyfuzz) is intended to +make certain pathological patterns of rekeying unstable. If both +sides decide to rekey at the same time, twice as many SAs as necessary +are created. This could become a stable pattern without the +randomness. +.LP +Another more important case occurs when a security gateway has SAs +with many other security gateways. Each of these connections might +need to be rekeyed at the same time. This would cause a high peek +requirement for resources (network bandwidth, CPU time, entropy for +random numbers). The rekeyfuzz can be used to stagger the rekeying +times. +.LP +Once a new set of SAs has been negotiated, \fBpluto\fP will never send +traffic on a superseded one. Traffic will be accepted on an old SA +until it expires. +.SS Selecting a Connection When Responding: Road Warrior Support +.LP +When \fBpluto\fP receives an initial Main Mode message, it needs to +decide which connection this message is for. It picks based solely on +the source and destination IP addresses of the message. There might +be several connections with suitable IP addresses, in which case one +of them is arbitrarily chosen. (The ISAKMP SA proposal contained in +the message could be taken into account, but it is not.) +.LP +The ISAKMP SA is negotiated before the parties pass further +identifying information, so all ISAKMP SA characteristics specified in +the connection description should be the same for every connection +with the same two host IP addresses. At the moment, the only +characteristic that might differ is authentication method. +.LP +Up to this point, +all configuring has presumed that the IP addresses +are known to all parties ahead of time. This will not work +when either end is mobile (or assigned a dynamic IP address for other +reasons). We call this situation ``Road Warrior''. It is fairly tricky +and has some important limitations, most of which are features of +the IKE protocol. +.LP +Only the initiator may be mobile: +the initiator may have an IP number unknown to the responder. When +the responder doesn't recognize the IP address on the first Main Mode +packet, it looks for a connection with itself as one end and \fB%any\fP +as the other. +If it cannot find one, it refuses to negotiate. If it +does find one, it creates a temporary connection that is a duplicate +except with the \fB%any\fP replaced by the source IP address from the +packet; if there was no identity specified for the peer, the new IP +address will be used. +.LP +When \fBpluto\fP is using one of these temporary connections and +needs to find the preshared secret or RSA private key in \fIipsec.secrets\fP, +and and the connection specified no identity for the peer, \fB%any\fP +is used as its identity. After all, the real IP address was apparently +unknown to the configuration, so it is unreasonable to require that +it be used in this table. +.LP +Part way into the Phase 1 (Main Mode) negotiation using one of these +temporary connection descriptions, \fBpluto\fP will be receive an +Identity Payload. At this point, \fBpluto\fP checks for a more +appropriate connection, one with an identity for the peer that matches +the payload but which would use the same keys so-far used for +authentication. If it finds one, it will switch to using this better +connection (or a temporary derived from this, if it has \fB%any\fP +for the peer's IP address). It may even turn out that no connection +matches the newly discovered identity, including the current connection; +if so, \fBpluto\fP terminates negotiation. +.LP +Unfortunately, if preshared secret authentication is being used, the +Identity Payload is encrypted using this secret, so the secret must be +selected by the responder without knowing this payload. This +limits there to being at most one preshared secret for all Road Warrior +systems connecting to a host. RSA Signature authentications does not +require that the responder know how to select the initiator's public key +until after the initiator's Identity Payload is decoded (using the +responder's private key, so that must be preselected). +.LP +When \fBpluto\fP is responding to a Quick Mode negotiation via one of these +temporary connection descriptions, it may well find that the subnets +specified by the initiator don't match those in the temporary +connection description. If so, it will look for a connection with +matching subnets, its own host address, a peer address of \fB%any\fP +and matching identities. +If it finds one, a new temporary connection is derived from this one +and used for the Quick Mode negotiation of IPsec SAs. If it does not +find one, \fBpluto\fP terminates negotiation. +.LP +Be sure to specify an appropriate nexthop for the responder +to send a message to the initiator: \fBpluto\fP has no way of guessing +it (if forwarding isn't required, use an explicit \fB%direct\fP as the nexthop +and the IP address of the initiator will be filled in; the obsolete +notation \fB0.0.0.0\fP is still accepted). +.LP +\fBpluto\fP has no special provision for the initiator side. The current +(possibly dynamic) IP address and nexthop must be used in defining +connections. These must be +properly configured each time the initiator's IP address changes. +\fBpluto\fP has no mechanism to do this automatically. +.LP +Although we call this Road Warrior Support, it could also be used to +support encrypted connections with anonymous initiators. The +responder's organization could announce the preshared secret that would be used +with unrecognized initiators and let anyone connect. Of course the initiator's +identity would not be authenticated. +.LP +If any Road Warrior connections are supported, \fBpluto\fP cannot +reject an exchange initiated by an unknown host until it has +determined that the secret is not shared or the signature is invalid. +This must await the +third Main Mode message from the initiator. If no Road Warrior +connection is supported, the first message from an unknown source +would be rejected. This has implications for ease of debugging +configurations and for denial of service attacks. +.LP +Although a Road Warrior connection must be initiated by the mobile +side, the other side can and will rekey using the temporary connection +it has created. If the Road Warrior wishes to be able to disconnect, +it is probably wise to set \fB\-\-keyingtries\fP to 1 in the +connection on the non-mobile side to prevent it trying to rekey the +connection. Unfortunately, there is no mechanism to unroute the +connection automatically. +.SS Debugging +.LP +\fBpluto\fP accepts several optional arguments, useful mostly for debugging. +Except for \fB\-\-interface\fP, each should appear at most once. +.TP +\fB\-\-interface\fP \fIinterfacename\fP +specifies that the named real public network interface should be considered. +The interface name specified should not be \fBipsec\fP\fIN\fP. +If the option doesn't appear, all interfaces are considered. +To specify several interfaces, use the option once for each. +One use of this option is to specify which interface should be used +when two or more share the same IP address. +.TP +\fB\-\-ikeport\fP \fIport-number\fP +changes the UDP port that \fBpluto\fP will use +(default, specified by IANA: 500) +.TP +\fB\-\-ctlbase\fP \fIpath\fP +basename for control files. +\fIpath\fP.ctl is the socket through which \fBwhack\fP communicates with +\fBpluto\fP. +\fIpath\fP.pid is the lockfile to prevent multiple \fBpluto\fP instances. +The default is \fI/var/run/pluto\fP). +.TP +\fB\-\-secretsfile\fP \fIfile\fP +specifies the file for authentication secrets +(default: \fI/etc/ipsec.secrets\fP). +This name is subject to ``globbing'' as in \fIsh\fP(1), +so every file with a matching name is processed. +Quoting is generally needed to prevent the shell from doing the globbing. +.TP +\fB\-\-adns\fP \fIpathname\fP +.TP +\fB\-\-lwdnsq\fP \fIpathname\fP +specifies where to find \fBpluto\fP's helper program for asynchronous DNS lookup. +\fBpluto\fP can be built to use one of two helper programs: \fB_pluto_adns\fP +or \fBlwdnsq\fP. You must use the program for which it was built. +By default, \fBpluto\fP will look for the program in +\fB$IPSEC_DIR\fP (if that environment variable is defined) or, failing that, +in the same directory as \fBpluto\fP. +.TP +\fB\-\-nofork\fP +disable ``daemon fork'' (default is to fork). In addition, after the +lock file and control socket are created, print the line ``Pluto +initialized'' to standard out. +.TP +\fB\-\-noklips\fP +don't actually implement negotiated IPsec SAs +.TP +\fB\-\-uniqueids\fP +if this option has been selected, whenever a new ISAKMP SA is +established, any connection with the same Peer ID but a different +Peer IP address is unoriented (causing all its SAs to be deleted). +This helps clean up dangling SAs when a connection is lost and +then regained at another IP address. +.TP +\fB\-\-stderrlog\fP +log goes to standard out {default is to use \fIsyslogd\fP(8)) +.LP +For example +.TP +pluto \-\-secretsfile\ ipsec.secrets \-\-ctlbase\ pluto.base \-\-ikeport\ 8500 \-\-nofork \-\-noklips \-\-stderrlog +.LP +lets one test \fBpluto\fP without using the superuser account. +.LP +\fBpluto\fP is willing to produce a prodigious amount of debugging +information. To do so, it must be compiled with \-DDEBUG. There are +several classes of debugging output, and \fBpluto\fP may be directed to +produce a selection of them. All lines of +debugging output are prefixed with ``|\ '' to distinguish them from error +messages. +.LP +When \fBpluto\fP is invoked, it may be given arguments to specify +which classes to output. The current options are: +.TP +\fB\-\-debug-raw\fP +show the raw bytes of messages +.TP +\fB\-\-debug-crypt\fP +show the encryption and decryption of messages +.TP +\fB\-\-debug-parsing\fP +show the structure of input messages +.TP +\fB\-\-debug-emitting\fP +show the structure of output messages +.TP +\fB\-\-debug-control\fP +show \fBpluto\fP's decision making +.TP +\fB\-\-debug-lifecycle\fP +[this option is temporary] log more detail of lifecycle of SAs +.TP +\fB\-\-debug-klips\fP +show \fBpluto\fP's interaction with \fBKLIPS\fP +.TP +\fB\-\-debug-dns\fP +show \fBpluto\fP's interaction with \fBDNS\fP for KEY and TXT records +.TP +\fB\-\-debug-oppo\fP +show why \fBpluto\fP didn't find a suitable DNS TXT record to authorize opportunistic initiation +.TP +\fB\-\-debug-all\fP +all of the above +.TP +\fB\-\-debug-private\fP +allow debugging output with private keys. +.TP +\fB\-\-debug-none\fP +none of the above +.LP +The debug form of the +\fBwhack\fP command will change the selection in a running +\fBpluto\fP. +If a connection name is specified, the flags are added whenever +\fBpluto\fP has identified that it is dealing with that connection. +Unfortunately, this is often part way into the operation being observed. +.LP +For example, to start a \fBpluto\fP with a display of the structure of input +and output: +.IP +pluto \-\-debug-emitting \-\-debug-parsing +.LP +To later change this \fBpluto\fP to only display raw bytes: +.IP +whack \-\-debug-raw +.LP +For testing, SSH's IKE test page is quite useful: +.IP +\fIhttp://isakmp-test.ssh.fi/\fP +.LP +Hint: ISAKMP SAs are often kept alive by IKEs even after the IPsec SA +is established. This allows future IPsec SA's to be negotiated +directly. If one of the IKEs is restarted, the other may try to use +the ISAKMP SA but the new IKE won't know about it. This can lead to +much confusion. \fBpluto\fP is not yet smart enough to get out of such a +mess. +.SS Pluto's Behaviour When Things Go Wrong +.LP +When \fBpluto\fP doesn't understand or accept a message, it just +ignores the message. It is not yet capable of communicating the +problem to the other IKE daemon (in the future it might use +Notifications to accomplish this in many cases). It does log a diagnostic. +.LP +When \fBpluto\fP gets no response from a message, it resends the same +message (a message will be sent at most three times). This is +appropriate: UDP is unreliable. +.LP +When pluto gets a message that it has already seen, there are many +cases when it notices and discards it. This too is appropriate for UDP. +.LP +Combine these three rules, and you can explain many apparently +mysterious behaviours. In a \fBpluto\fP log, retrying isn't usually the +interesting event. The critical thing is either earlier (\fBpluto\fP +got a message which it didn't like and so ignored, so it was still +awaiting an acceptable message and got impatient) or on the other +system (\fBpluto\fP didn't send a reply because it wasn't happy with +the previous message). +.SS Notes +.LP +If \fBpluto\fP is compiled without \-DKLIPS, it negotiates Security +Associations but never ask the kernel to put them in place and never +makes routing changes. This allows \fBpluto\fP to be tested on systems +without \fBKLIPS\fP, but makes it rather useless. +.LP +Each IPsec SA is assigned an SPI, a 32-bit number used to refer to the SA. +The IKE protocol lets the destination of the SA choose the SPI. +The range 0 to 0xFF is reserved for IANA. +\fBPluto\fP also avoids choosing an SPI in the range 0x100 to 0xFFF, +leaving these SPIs free for manual keying. +Remember that the peer, if not \fBpluto\fP, may well chose +SPIs in this range. +.SS Policies +.LP +This catalogue of policies may be of use when trying to configure +\fBPluto\fP and another IKE implementation to interoperate. +.LP +In Phase 1, only Main Mode is supported. We are not sure that +Aggressive Mode is secure. For one thing, it does not support +identity protection. It may allow more severe Denial Of Service +attacks. +.LP +No Informational Exchanges are supported. These are optional and +since their delivery is not assured, they must not matter. +It is the case that some IKE implementations won't interoperate +without Informational Exchanges, but we feel they are broken. +.LP +No Informational Payloads are supported. These are optional, but +useful. It is of concern that these payloads are not authenticated in +Phase 1, nor in those Phase 2 messages authenticated with HASH(3). +.IP \(bu \w'\(bu\ 'u +Diffie Hellman Groups MODP 1024 and MODP 1536 (2 and 5) +are supported. +Group MODP768 (1) is not supported because it is too weak. +.IP \(bu +Host authetication can be done by RSA Signatures or Pre-Shared +Secrets. +.IP \(bu +3DES CBC (Cypher Block Chaining mode) is the only encryption +supported, both for ISAKMP SAs and IPSEC SAs. +.IP \(bu +MD5 and SHA1 hashing are supported for packet authentication in both +kinds of SAs. +.IP \(bu +The ESP, AH, or AH plus ESP are supported. If, and only if, AH and +ESP are combined, the ESP need not have its own authentication +component. The selection is controlled by the \-\-encrypt and +\-\-authenticate flags. +.IP \(bu +Each of these may be combined with IPCOMP Deflate compression, +but only if the potential connection specifies compression and only +if KLIPS is configured with IPCOMP support. +.IP \(bu +The IPSEC SAs may be tunnel or transport mode, where appropriate. +The \-\-tunnel flag controls this when \fBpluto\fP is initiating. +.IP \(bu +When responding to an ISAKMP SA proposal, the maximum acceptable +lifetime is eight hours. The default is one hour. There is no +minimum. The \-\-ikelifetime flag controls this when \fBpluto\fP +is initiating. +.IP \(bu +When responding to an IPSEC SA proposal, the maximum acceptable +lifetime is one day. The default is eight hours. There is no +minimum. The \-\-ipseclifetime flag controls this when \fBpluto\fP +is initiating. +.IP \(bu +PFS is acceptable, and will be proposed if the \-\-pfs flag was +specified. The DH group proposed will be the same as negotiated for +Phase 1. +.SH SIGNALS +.LP +\fBPluto\fP responds to \fBSIGHUP\fP by issuing a suggestion that ``\fBwhack\fP +\-\-listen'' might have been intended. +.LP +\fBPluto\fP exits when it recieves \fBSIGTERM\fP. +.SH EXIT STATUS +.LP +\fBpluto\fP normally forks a daemon process, so the exit status is +normally a very preliminary result. +.TP +0 +means that all is OK so far. +.TP +1 +means that something was wrong. +.TP +10 +means that the lock file already exists. +.LP +If \fBwhack\fP detects a problem, it will return an exit status of 1. +If it received progress messages from \fBpluto\fP, it returns as status +the value of the numeric prefix from the last such message +that was not a message sent to syslog or a comment +(but the prefix for success is treated as 0). +Otherwise, the exit status is 0. +.SH FILES +\fI/var/run/pluto.pid\fP +.br +\fI/var/run/pluto.ctl\fP +.br +\fI/etc/ipsec.secrets\fP +.br +\fI$IPSEC_LIBDIR/_pluto_adns\fP +.br +\fI$IPSEC_EXECDIR/lwdnsq\fP +.br +\fI/dev/urandom\fP +.SH ENVIRONMENT +\fIIPSEC_LIBDIR\fP +.br +\fIIPSEC_EXECDIR\fP +.br +\fIIPSECmyid\fP +.SH SEE ALSO +.LP +The rest of the FreeS/WAN distribution, in particular \fIipsec\fP(8). +.LP +\fIipsec_auto\fP(8) is designed to make using \fBpluto\fP more pleasant. +Use it! +.LP +.IR ipsec.secrets (5) +describes the format of the secrets file. +.LP +\fIipsec_atoaddr\fP(3), part of the FreeS/WAN distribution, describes the +forms that IP addresses may take. +\fIipsec_atosubnet\fP(3), part of the FreeS/WAN distribution, describes the +forms that subnet specifications. +.LP +For more information on IPsec, the mailing list, and the relevant +documents, see: +.IP +.nh +\fIhttp://www.ietf.cnri.reston.va.us/html.charters/ipsec-charter.html\fP +.hy +.LP +At the time of writing, the most relevant IETF RFCs are: +.IP +RFC2409 The Internet Key Exchange (IKE) +.IP +RFC2408 Internet Security Association and Key Management Protocol (ISAKMP) +.IP +RFC2407 The Internet IP Security Domain of Interpretation for ISAKMP +.LP +The FreeS/WAN web site <htp://www.freeswan.org> +and the mailing lists described there. +.SH HISTORY +This code is released under the GPL terms. +See the accompanying file COPYING-2.0 for more details. +The GPL does NOT apply to those pieces of code written by others +which are included in this distribution, except as noted by the +individual authors. +.LP +This software was originally written +for the FreeS/WAN project +<http://www.freeswan.org> +by Angelos D. Keromytis +(angelos@dsl.cis.upenn.edu), in May/June 1997, in Athens, Greece. +Thanks go to John Ioannidis for his help. +.LP +It is currently (2000) +being developed and maintained by D. Hugh Redelmeier +(hugh@mimosa.com), in Canada. The regulations of Greece and Canada +allow us to make the code freely redistributable. +.LP +Kai Martius (admin@imib.med.tu-dresden.de) contributed the initial +version of the code supporting PFS. +.LP +Richard Guy Briggs <rgb@conscoop.ottawa.on.ca> and Peter Onion +<ponion@srd.bt.co.uk> added the PFKEY2 support. +.LP +We gratefully acknowledge that we use parts of Eric Young's \fIlibdes\fP +package; see \fI../libdes/COPYRIGHT\fP. +.SH BUGS +.BR pluto +is a work-in-progress. It currently has many limitations. +For example, it ignores notification messages that it receives, and +it generates only Delete Notifications and those only for IPSEC SAs. +.LP +\fBpluto\fP does not support the Commit Flag. +The Commit Flag is a bad feature of the IKE protocol. +It isn't protected -- neither encrypted nor authenticated. +A man in the middle could turn it on, leading to DoS. +We just ignore it, with a warning. +This should let us interoperate with +implementations that insist on it, with minor damage. +.LP +\fBpluto\fP does not check that the SA returned by the Responder +is actually one that was proposed. It only checks that the SA is +acceptable. The difference is not large, but can show up in attributes +such as SA lifetime. +.LP +There is no good way for a connection to be automatically terminated. +This is a problem for Road Warrior and Opportunistic connections. +The \fB\-\-dontrekey\fP option does prevent the SAs from +being rekeyed on expiry. +Additonally, if a Road Warrior connection has a client subnet with a fixed IP +address, a negotiation with that subnet will cause any other +connection instantiations with that same subnet to be unoriented +(deleted, in effect). +See also the \-\-uniqueids option for an extension of this. +.LP +When \fBpluto\fP sends a message to a peer that has disappeared, +\fBpluto\fP receives incomplete information from the kernel, so it +logs the unsatisfactory message ``some IKE message we sent has been +rejected with ECONNREFUSED (kernel supplied no details)''. John +Denker suggests that this command is useful for tracking down the +source of these problems: +.br + tcpdump -i eth0 icmp[0] != 8 and icmp[0] != 0 +.br +Substitute your public interface for eth0 if it is different. +.LP +The word ``authenticate'' is used for two different features. We must +authenticate each IKE peer to the other. This is an important task of +Phase 1. Each packet must be authenticated, both in IKE and in IPsec, +and the method for IPsec is negotiated as an AH SA or part of an ESP SA. +Unfortunately, the protocol has no mechanism for authenticating the Phase 2 +identities. +.LP +Bugs should be reported to the <users@lists.freeswan.org> mailing list. +Caution: we cannot accept +actual code from US residents, or even US citizens living outside the +US, because that would bring FreeS/WAN under US export law. Some +other countries cause similar problems. In general, we would prefer +that you send detailed problem reports rather than code: we want +FreeS/WAN to be unquestionably freely exportable, which means being +very careful about where the code comes from, and for a small bug fix, +that is often more time-consuming than just reinventing the fix +ourselves. diff --git a/src/pluto/plutomain.c b/src/pluto/plutomain.c new file mode 100644 index 000000000..e235ff765 --- /dev/null +++ b/src/pluto/plutomain.c @@ -0,0 +1,655 @@ +/* Pluto main program + * Copyright (C) 1997 Angelos D. Keromytis. + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: plutomain.c,v 1.16 2005/09/25 21:30:52 as Exp $ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <ctype.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <fcntl.h> +#include <getopt.h> +#include <resolv.h> +#include <arpa/nameser.h> /* missing from <resolv.h> on old systems */ +#include <sys/queue.h> + +#include <freeswan.h> + +#include <pfkeyv2.h> +#include <pfkey.h> + +#include "constants.h" +#include "defs.h" +#include "id.h" +#include "ca.h" +#include "certs.h" +#include "ac.h" +#include "connections.h" +#include "foodgroups.h" +#include "packet.h" +#include "demux.h" /* needs packet.h */ +#include "server.h" +#include "kernel.h" +#include "log.h" +#include "keys.h" +#include "adns.h" /* needs <resolv.h> */ +#include "dnskey.h" /* needs keys.h and adns.h */ +#include "rnd.h" +#include "state.h" +#include "ipsec_doi.h" /* needs demux.h and state.h */ +#include "ocsp.h" +#include "crl.h" +#include "fetch.h" +#include "xauth.h" +#include "sha1.h" +#include "md5.h" +#include "crypto.h" /* requires sha1.h and md5.h */ +#include "nat_traversal.h" +#include "virtual.h" + +static void +usage(const char *mess) +{ + if (mess != NULL && *mess != '\0') + fprintf(stderr, "%s\n", mess); + fprintf(stderr + , "Usage: pluto" + " [--help]" + " [--version]" + " [--optionsfrom <filename>]" + " \\\n\t" + "[--nofork]" + " [--stderrlog]" + " [--noklips]" + " [--nocrsend]" + " \\\n\t" + "[--strictcrlpolicy]" + " [--crlcheckinterval <interval>]" + " [--cachecrls]" + " [--uniqueids]" + " \\\n\t" + "[--interface <ifname>]" + " [--ikeport <port-number>]" + " \\\n\t" + "[--ctlbase <path>]" + " \\\n\t" + "[--perpeerlogbase <path>] [--perpeerlog]" + " \\\n\t" + "[--secretsfile <secrets-file>]" + " [--policygroupsdir <policygroups-dir>]" + " \\\n\t" + "[--adns <pathname>]" + "[--pkcs11module <path>]" + "[--pkcs11keepstate" +#ifdef DEBUG + " \\\n\t" + "[--debug-none]" + " [--debug-all]" + " \\\n\t" + "[--debug-raw]" + " [--debug-crypt]" + " [--debug-parsing]" + " [--debug-emitting]" + " \\\n\t" + "[--debug-control]" + " [--debug-lifecycle]" + " [--debug-klips]" + " [--debug-dns]" + " \\\n\t" + "[--debug-oppo]" + " [--debug-controlmore]" + " [--debug-private]" +#endif + " [ --debug-natt]" + " \\\n\t" + "[--nat_traversal] [--keep_alive <delay_sec>]" + " \\\n\t" + "[--force_keepalive] [--disable_port_floating]" + " \\\n\t" + "[--virtual_private <network_list>]" + "\n" + "strongSwan %s\n" + , ipsec_version_code()); + exit_pluto(mess == NULL? 0 : 1); +} + + +/* lock file support + * - provides convenient way for scripts to find Pluto's pid + * - prevents multiple Plutos competing for the same port + * - same basename as unix domain control socket + * NOTE: will not take account of sharing LOCK_DIR with other systems. + */ + +static char pluto_lock[sizeof(ctl_addr.sun_path)] = DEFAULT_CTLBASE LOCK_SUFFIX; +static bool pluto_lock_created = FALSE; + +/* create lockfile, or die in the attempt */ +static int +create_lock(void) +{ + int fd = open(pluto_lock, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC + , S_IRUSR | S_IRGRP | S_IROTH); + + if (fd < 0) + { + if (errno == EEXIST) + { + fprintf(stderr, "pluto: lock file \"%s\" already exists\n" + , pluto_lock); + exit_pluto(10); + } + else + { + fprintf(stderr + , "pluto: unable to create lock file \"%s\" (%d %s)\n" + , pluto_lock, errno, strerror(errno)); + exit_pluto(1); + } + } + pluto_lock_created = TRUE; + return fd; +} + +static bool +fill_lock(int lockfd, pid_t pid) +{ + char buf[30]; /* holds "<pid>\n" */ + int len = snprintf(buf, sizeof(buf), "%u\n", (unsigned int) pid); + bool ok = len > 0 && write(lockfd, buf, len) == len; + + close(lockfd); + return ok; +} + +static void +delete_lock(void) +{ + if (pluto_lock_created) + { + delete_ctl_socket(); + unlink(pluto_lock); /* is noting failure useful? */ + } +} + +/* by default pluto sends certificate requests to its peers */ +bool no_cr_send = FALSE; + +/* by default the CRL policy is lenient */ +bool strict_crl_policy = FALSE; + +/* by default CRLs are cached locally as files */ +bool cache_crls = FALSE; + +/* by default pluto does not check crls dynamically */ +long crl_check_interval = 0; + +/* path to the PKCS#11 module */ +char *pkcs11_module_path = NULL; + +/* by default pluto logs out after every smartcard use */ +bool pkcs11_keep_state = FALSE; + +/* by default pluto does not allow pkcs11 proxy access via whack */ +bool pkcs11_proxy = FALSE; + +int +main(int argc, char **argv) +{ + bool fork_desired = TRUE; + bool log_to_stderr_desired = FALSE; + bool nat_traversal = FALSE; + bool nat_t_spf = TRUE; /* support port floating */ + unsigned int keep_alive = 0; + bool force_keepalive = FALSE; + char *virtual_private = NULL; + int lockfd; + + /* handle arguments */ + for (;;) + { +# define DBG_OFFSET 256 + static const struct option long_opts[] = { + /* name, has_arg, flag, val */ + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { "optionsfrom", required_argument, NULL, '+' }, + { "nofork", no_argument, NULL, 'd' }, + { "stderrlog", no_argument, NULL, 'e' }, + { "noklips", no_argument, NULL, 'n' }, + { "nocrsend", no_argument, NULL, 'c' }, + { "strictcrlpolicy", no_argument, NULL, 'r' }, + { "crlcheckinterval", required_argument, NULL, 'x'}, + { "cachecrls", no_argument, NULL, 'C' }, + { "uniqueids", no_argument, NULL, 'u' }, + { "interface", required_argument, NULL, 'i' }, + { "ikeport", required_argument, NULL, 'p' }, + { "ctlbase", required_argument, NULL, 'b' }, + { "secretsfile", required_argument, NULL, 's' }, + { "foodgroupsdir", required_argument, NULL, 'f' }, + { "perpeerlogbase", required_argument, NULL, 'P' }, + { "perpeerlog", no_argument, NULL, 'l' }, + { "policygroupsdir", required_argument, NULL, 'f' }, +#ifdef USE_LWRES + { "lwdnsq", required_argument, NULL, 'a' }, +#else /* !USE_LWRES */ + { "adns", required_argument, NULL, 'a' }, +#endif /* !USE_LWRES */ + { "pkcs11module", required_argument, NULL, 'm' }, + { "pkcs11keepstate", no_argument, NULL, 'k' }, + { "pkcs11proxy", no_argument, NULL, 'y' }, + { "nat_traversal", no_argument, NULL, '1' }, + { "keep_alive", required_argument, NULL, '2' }, + { "force_keepalive", no_argument, NULL, '3' }, + { "disable_port_floating", no_argument, NULL, '4' }, + { "debug-natt", no_argument, NULL, '5' }, + { "virtual_private", required_argument, NULL, '6' }, +#ifdef DEBUG + { "debug-none", no_argument, NULL, 'N' }, + { "debug-all", no_argument, NULL, 'A' }, + { "debug-raw", no_argument, NULL, DBG_RAW + DBG_OFFSET }, + { "debug-crypt", no_argument, NULL, DBG_CRYPT + DBG_OFFSET }, + { "debug-parsing", no_argument, NULL, DBG_PARSING + DBG_OFFSET }, + { "debug-emitting", no_argument, NULL, DBG_EMITTING + DBG_OFFSET }, + { "debug-control", no_argument, NULL, DBG_CONTROL + DBG_OFFSET }, + { "debug-lifecycle", no_argument, NULL, DBG_LIFECYCLE + DBG_OFFSET }, + { "debug-klips", no_argument, NULL, DBG_KLIPS + DBG_OFFSET }, + { "debug-dns", no_argument, NULL, DBG_DNS + DBG_OFFSET }, + { "debug-oppo", no_argument, NULL, DBG_OPPO + DBG_OFFSET }, + { "debug-controlmore", no_argument, NULL, DBG_CONTROLMORE + DBG_OFFSET }, + { "debug-private", no_argument, NULL, DBG_PRIVATE + DBG_OFFSET }, + + { "impair-delay-adns-key-answer", no_argument, NULL, IMPAIR_DELAY_ADNS_KEY_ANSWER + DBG_OFFSET }, + { "impair-delay-adns-txt-answer", no_argument, NULL, IMPAIR_DELAY_ADNS_TXT_ANSWER + DBG_OFFSET }, + { "impair-bust-mi2", no_argument, NULL, IMPAIR_BUST_MI2 + DBG_OFFSET }, + { "impair-bust-mr2", no_argument, NULL, IMPAIR_BUST_MR2 + DBG_OFFSET }, +#endif + { 0,0,0,0 } + }; + /* Note: we don't like the way short options get parsed + * by getopt_long, so we simply pass an empty string as + * the list. It could be "hvdenp:l:s:" "NARXPECK". + */ + int c = getopt_long(argc, argv, "", long_opts, NULL); + + /* Note: "breaking" from case terminates loop */ + switch (c) + { + case EOF: /* end of flags */ + break; + + case 0: /* long option already handled */ + continue; + + case ':': /* diagnostic already printed by getopt_long */ + case '?': /* diagnostic already printed by getopt_long */ + usage(""); + break; /* not actually reached */ + + case 'h': /* --help */ + usage(NULL); + break; /* not actually reached */ + + case 'v': /* --version */ + { + const char **sp = ipsec_copyright_notice(); + + printf("%s%s\n", ipsec_version_string(), + compile_time_interop_options); + for (; *sp != NULL; sp++) + puts(*sp); + } + exit_pluto(0); + break; /* not actually reached */ + + case '+': /* --optionsfrom <filename> */ + optionsfrom(optarg, &argc, &argv, optind, stderr); + /* does not return on error */ + continue; + + case 'd': /* --nofork*/ + fork_desired = FALSE; + continue; + + case 'e': /* --stderrlog */ + log_to_stderr_desired = TRUE; + continue; + + case 'n': /* --noklips */ + no_klips = TRUE; + continue; + + case 'c': /* --nocrsend */ + no_cr_send = TRUE; + continue; + + case 'r': /* --strictcrlpolicy */ + strict_crl_policy = TRUE; + continue; + + case 'x': /* --crlcheckinterval <time>*/ + if (optarg == NULL || !isdigit(optarg[0])) + usage("missing interval time"); + + { + char *endptr; + long interval = strtol(optarg, &endptr, 0); + + if (*endptr != '\0' || endptr == optarg + || interval <= 0) + usage("<interval-time> must be a positive number"); + crl_check_interval = interval; + } + continue; + + case 'C': /* --cachecrls */ + cache_crls = TRUE; + continue; + + case 'u': /* --uniqueids */ + uniqueIDs = TRUE; + continue; + + case 'i': /* --interface <ifname> */ + if (!use_interface(optarg)) + usage("too many --interface specifications"); + continue; + + case 'p': /* --port <portnumber> */ + if (optarg == NULL || !isdigit(optarg[0])) + usage("missing port number"); + + { + char *endptr; + long port = strtol(optarg, &endptr, 0); + + if (*endptr != '\0' || endptr == optarg + || port <= 0 || port > 0x10000) + usage("<port-number> must be a number between 1 and 65535"); + pluto_port = port; + } + continue; + + case 'b': /* --ctlbase <path> */ + if (snprintf(ctl_addr.sun_path, sizeof(ctl_addr.sun_path) + , "%s%s", optarg, CTL_SUFFIX) == -1) + usage("<path>" CTL_SUFFIX " too long for sun_path"); + if (snprintf(info_addr.sun_path, sizeof(info_addr.sun_path) + , "%s%s", optarg, INFO_SUFFIX) == -1) + usage("<path>" INFO_SUFFIX " too long for sun_path"); + if (snprintf(pluto_lock, sizeof(pluto_lock) + , "%s%s", optarg, LOCK_SUFFIX) == -1) + usage("<path>" LOCK_SUFFIX " must fit"); + continue; + + case 's': /* --secretsfile <secrets-file> */ + shared_secrets_file = optarg; + continue; + + case 'f': /* --policygroupsdir <policygroups-dir> */ + policygroups_dir = optarg; + continue; + + case 'a': /* --adns <pathname> */ + pluto_adns_option = optarg; + continue; + + case 'm': /* --pkcs11module <pathname> */ + pkcs11_module_path = optarg; + continue; + + case 'k': /* --pkcs11keepstate */ + pkcs11_keep_state = TRUE; + continue; + + case 'y': /* --pkcs11proxy */ + pkcs11_proxy = TRUE; + continue; + +#ifdef DEBUG + case 'N': /* --debug-none */ + base_debugging = DBG_NONE; + continue; + + case 'A': /* --debug-all */ + base_debugging = DBG_ALL; + continue; +#endif + + case 'P': /* --perpeerlogbase */ + base_perpeer_logdir = optarg; + continue; + + case 'l': + log_to_perpeer = TRUE; + continue; + + case '1': /* --nat_traversal */ + nat_traversal = TRUE; + continue; + case '2': /* --keep_alive */ + keep_alive = atoi(optarg); + continue; + case '3': /* --force_keepalive */ + force_keepalive = TRUE; + continue; + case '4': /* --disable_port_floating */ + nat_t_spf = FALSE; + continue; + case '5': /* --debug-nat_t */ + base_debugging |= DBG_NATT; + continue; + case '6': /* --virtual_private */ + virtual_private = optarg; + continue; + + default: +#ifdef DEBUG + if (c >= DBG_OFFSET) + { + base_debugging |= c - DBG_OFFSET; + continue; + } +# undef DBG_OFFSET +#endif + bad_case(c); + } + break; + } + if (optind != argc) + usage("unexpected argument"); + reset_debugging(); + lockfd = create_lock(); + + /* select between logging methods */ + + if (log_to_stderr_desired) + log_to_syslog = FALSE; + else + log_to_stderr = FALSE; + + /* set the logging function of pfkey debugging */ +#ifdef DEBUG + pfkey_debug_func = DBG_log; +#else + pfkey_debug_func = NULL; +#endif + + /* create control socket. + * We must create it before the parent process returns so that + * there will be no race condition in using it. The easiest + * place to do this is before the daemon fork. + */ + { + err_t ugh = init_ctl_socket(); + + if (ugh != NULL) + { + fprintf(stderr, "pluto: %s", ugh); + exit_pluto(1); + } + } + + /* If not suppressed, do daemon fork */ + + if (fork_desired) + { + { + pid_t pid = fork(); + + if (pid < 0) + { + int e = errno; + + fprintf(stderr, "pluto: fork failed (%d %s)\n", + errno, strerror(e)); + exit_pluto(1); + } + + if (pid != 0) + { + /* parent: die, after filling PID into lock file. + * must not use exit_pluto: lock would be removed! + */ + exit(fill_lock(lockfd, pid)? 0 : 1); + } + } + + if (setsid() < 0) + { + int e = errno; + + fprintf(stderr, "setsid() failed in main(). Errno %d: %s\n", + errno, strerror(e)); + exit_pluto(1); + } + } + else + { + /* no daemon fork: we have to fill in lock file */ + (void) fill_lock(lockfd, getpid()); + fprintf(stdout, "Pluto initialized\n"); + fflush(stdout); + } + + /* Close everything but ctl_fd and (if needed) stderr. + * There is some danger that a library that we don't know + * about is using some fd that we don't know about. + * I guess we'll soon find out. + */ + { + int i; + + for (i = getdtablesize() - 1; i >= 0; i--) /* Bad hack */ + { + if ((!log_to_stderr || i != 2) && i != ctl_fd) + close(i); + } + + /* make sure that stdin, stdout, stderr are reserved */ + if (open("/dev/null", O_RDONLY) != 0) + abort(); + if (dup2(0, 1) != 1) + abort(); + if (!log_to_stderr && dup2(0, 2) != 2) + abort(); + } + + init_constants(); + init_log("pluto"); + + /* Note: some scripts may look for this exact message -- don't change + * ipsec barf was one, but it no longer does. + */ + plog("Starting Pluto (strongSwan Version %s%s)" + , ipsec_version_code() + , compile_time_interop_options); + + init_nat_traversal(nat_traversal, keep_alive, force_keepalive, nat_t_spf); + init_virtual_ip(virtual_private); + scx_init(pkcs11_module_path); /* load and initialize PKCS #11 module */ + xauth_init(); /* load and initialize XAUTH module */ + init_rnd_pool(); + init_secret(); + init_states(); + init_crypto(); + init_demux(); + init_kernel(); + init_adns(); + init_id(); + init_fetch(); + + /* loading X.509 CA certificates */ + load_authcerts("CA cert", CA_CERT_PATH, AUTH_CA); + /* loading X.509 AA certificates */ + load_authcerts("AA cert", AA_CERT_PATH, AUTH_AA); + /* loading X.509 OCSP certificates */ + load_authcerts("OCSP cert", OCSP_CERT_PATH, AUTH_OCSP); + /* loading X.509 CRLs */ + load_crls(); + /* loading attribute certificates (experimental) */ + load_acerts(); + + daily_log_event(); + call_server(); + return -1; /* Shouldn't ever reach this */ +} + +/* leave pluto, with status. + * Once child is launched, parent must not exit this way because + * the lock would be released. + * + * 0 OK + * 1 general discomfort + * 10 lock file exists + */ +void +exit_pluto(int status) +{ + reset_globals(); /* needed because we may be called in odd state */ + free_preshared_secrets(); + free_remembered_public_keys(); + delete_every_connection(); + free_crl_fetch(); /* free chain of crl fetch requests */ + free_ocsp_fetch(); /* free chain of ocsp fetch requests */ + free_authcerts(); /* free chain of X.509 authority certificates */ + free_crls(); /* free chain of X.509 CRLs */ + free_acerts(); /* free chain of X.509 attribute certificates */ + free_ca_infos(); /* free chain of X.509 CA information records */ + free_ocsp(); /* free ocsp cache */ + free_ifaces(); + scx_finalize(); /* finalize and unload PKCS #11 module */ + xauth_finalize(); /* finalize and unload XAUTH module */ + stop_adns(); + free_md_pool(); + delete_lock(); +#ifdef LEAK_DETECTIVE + report_leaks(); +#endif /* LEAK_DETECTIVE */ + close_log(); + exit(status); +} + +/* + * Local Variables: + * c-basic-offset:4 + * c-style: pluto + * End: + */ diff --git a/src/pluto/primegen.c b/src/pluto/primegen.c new file mode 100644 index 000000000..159490345 --- /dev/null +++ b/src/pluto/primegen.c @@ -0,0 +1,593 @@ +/* primegen.c - prime number generator + * Copyright (C) 1998 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * *********************************************************************** + * The algorithm used to generate practically save primes is due to + * Lim and Lee as described in the CRYPTO '97 proceedings (ISBN3540633847) + * page 260. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef PLUTO +#include <gmp.h> +#include <freeswan.h> +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "rnd.h" +#include "gcryptfix.h" +#else /*! PLUTO */ +/* #include <assert.h> */ +/* #include <config.h> */ +/* #include "util.h" */ +/* #include "mpi.h" */ +/* #include "cipher.h" */ +#endif /* !PLUTO */ + +static int no_of_small_prime_numbers; +static MPI gen_prime( unsigned nbits, int mode, int randomlevel ); +static int check_prime( MPI prime, MPI val_2 ); +static int is_prime( MPI n, unsigned steps, int *count ); +static void m_out_of_n( char *array, int m, int n ); + + +static void +progress( int c ) +{ + fputc( c, stderr ); +} + + +/**************** + * Generate a prime number (stored in secure memory) + */ +MPI +generate_secret_prime( unsigned nbits ) +{ + MPI prime; + + prime = gen_prime( nbits, 1, 2 ); + progress('\n'); + return prime; +} + +MPI +generate_public_prime( unsigned nbits ) +{ + MPI prime; + + prime = gen_prime( nbits, 0, 2 ); + progress('\n'); + return prime; +} + + +/**************** + * We do not need to use the strongest RNG because we gain no extra + * security from it - The prime number is public and we could also + * offer the factors for those who are willing to check that it is + * indeed a strong prime. + * + * mode 0: Standard + * 1: Make sure that at least one factor is of size qbits. + */ +MPI +generate_elg_prime( int mode, unsigned pbits, unsigned qbits, + MPI g, MPI **ret_factors ) +{ + int n; /* number of factors */ + int m; /* number of primes in pool */ + unsigned fbits; /* length of prime factors */ + MPI *factors; /* current factors */ + MPI *pool; /* pool of primes */ + MPI q; /* first prime factor (variable)*/ + MPI prime; /* prime test value */ + MPI q_factor; /* used for mode 1 */ + byte *perms = NULL; + int i, j; + int count1, count2; + unsigned nprime; + unsigned req_qbits = qbits; /* the requested q bits size */ + MPI val_2 = mpi_alloc_set_ui( 2 ); + + /* find number of needed prime factors */ + for(n=1; (pbits - qbits - 1) / n >= qbits; n++ ) + ; + n--; + if( !n || (mode==1 && n < 2) ) + log_fatal("can't gen prime with pbits=%u qbits=%u\n", pbits, qbits ); + if( mode == 1 ) { + n--; + fbits = (pbits - 2*req_qbits -1) / n; + qbits = pbits - req_qbits - n*fbits; + } + else { + fbits = (pbits - req_qbits -1) / n; + qbits = pbits - n*fbits; + } + if( DBG_CIPHER ) + log_debug("gen prime: pbits=%u qbits=%u fbits=%u/%u n=%d\n", + pbits, req_qbits, qbits, fbits, n ); + prime = mpi_alloc( (pbits + BITS_PER_MPI_LIMB - 1) / BITS_PER_MPI_LIMB ); + q = gen_prime( qbits, 0, 1 ); + q_factor = mode==1? gen_prime( req_qbits, 0, 1 ) : NULL; + + /* allocate an array to hold the factors + 2 for later usage */ +#ifdef PLUTO + m_alloc_ptrs_clear(factors, n+2); +#else + factors = m_alloc_clear( (n+2) * sizeof *factors ); +#endif + + /* make a pool of 3n+5 primes (this is an arbitrary value) */ + m = n*3+5; + if( mode == 1 ) + m += 5; /* need some more for DSA */ + if( m < 25 ) + m = 25; +#ifdef PLUTO + m_alloc_ptrs_clear(pool, m); +#else + pool = m_alloc_clear( m * sizeof *pool ); +#endif + + /* permutate over the pool of primes */ + count1=count2=0; + do { + next_try: + if( !perms ) { + /* allocate new primes */ + for(i=0; i < m; i++ ) { + mpi_free(pool[i]); + pool[i] = NULL; + } + /* init m_out_of_n() */ +#ifdef PLUTO + perms = alloc_bytes( m, "perms" ); +#else + perms = m_alloc_clear( m ); +#endif + for(i=0; i < n; i++ ) { + perms[i] = 1; + pool[i] = gen_prime( fbits, 0, 1 ); + factors[i] = pool[i]; + } + } + else { + m_out_of_n( perms, n, m ); + for(i=j=0; i < m && j < n ; i++ ) + if( perms[i] ) { + if( !pool[i] ) + pool[i] = gen_prime( fbits, 0, 1 ); + factors[j++] = pool[i]; + } + if( i == n ) { + m_free(perms); perms = NULL; + progress('!'); + goto next_try; /* allocate new primes */ + } + } + + mpi_set( prime, q ); + mpi_mul_ui( prime, prime, 2 ); + if( mode == 1 ) + mpi_mul( prime, prime, q_factor ); + for(i=0; i < n; i++ ) + mpi_mul( prime, prime, factors[i] ); + mpi_add_ui( prime, prime, 1 ); + nprime = mpi_get_nbits(prime); + if( nprime < pbits ) { + if( ++count1 > 20 ) { + count1 = 0; + qbits++; + progress('>'); + q = gen_prime( qbits, 0, 1 ); + goto next_try; + } + } + else + count1 = 0; + if( nprime > pbits ) { + if( ++count2 > 20 ) { + count2 = 0; + qbits--; + progress('<'); + q = gen_prime( qbits, 0, 1 ); + goto next_try; + } + } + else + count2 = 0; + } while( !(nprime == pbits && check_prime( prime, val_2 )) ); + + if( DBG_CIPHER ) { + progress('\n'); + log_mpidump( "prime : ", prime ); + log_mpidump( "factor q: ", q ); + if( mode == 1 ) + log_mpidump( "factor q0: ", q_factor ); + for(i=0; i < n; i++ ) + log_mpidump( "factor pi: ", factors[i] ); + log_debug("bit sizes: prime=%u, q=%u", mpi_get_nbits(prime), mpi_get_nbits(q) ); + if( mode == 1 ) + fprintf(stderr, ", q0=%u", mpi_get_nbits(q_factor) ); + for(i=0; i < n; i++ ) + fprintf(stderr, ", p%d=%u", i, mpi_get_nbits(factors[i]) ); + progress('\n'); + } + + if( ret_factors ) { /* caller wants the factors */ +#ifdef PLUTO + m_alloc_ptrs_clear(*ret_factors, n+2); +#else + *ret_factors = m_alloc_clear( (n+2) * sizeof **ret_factors); +#endif + if( mode == 1 ) { + i = 0; + (*ret_factors)[i++] = mpi_copy( q_factor ); + for(; i <= n; i++ ) + (*ret_factors)[i] = mpi_copy( factors[i] ); + } + else { + for(; i < n; i++ ) + (*ret_factors)[i] = mpi_copy( factors[i] ); + } + } + + if( g ) { /* create a generator (start with 3)*/ + MPI tmp = mpi_alloc( mpi_get_nlimbs(prime) ); + MPI b = mpi_alloc( mpi_get_nlimbs(prime) ); + MPI pmin1 = mpi_alloc( mpi_get_nlimbs(prime) ); + + if( mode == 1 ) + BUG(); /* not yet implemented */ + factors[n] = q; + factors[n+1] = mpi_alloc_set_ui(2); + mpi_sub_ui( pmin1, prime, 1 ); + mpi_set_ui(g,2); + do { + mpi_add_ui(g, g, 1); + if( DBG_CIPHER ) { +#ifdef PLUTO + log_mpidump("checking g: ", g); +#else + log_debug("checking g: "); + mpi_print( stderr, g, 1 ); +#endif + } + else + progress('^'); + for(i=0; i < n+2; i++ ) { + /*fputc('~', stderr);*/ + mpi_fdiv_q(tmp, pmin1, factors[i] ); + /* (no mpi_pow(), but it is okay to use this with mod prime) */ + mpi_powm(b, g, tmp, prime ); + if( !mpi_cmp_ui(b, 1) ) + break; + } + if( DBG_CIPHER ) + progress('\n'); + } while( i < n+2 ); + mpi_free(factors[n+1]); + mpi_free(tmp); + mpi_free(b); + mpi_free(pmin1); + } + if( !DBG_CIPHER ) + progress('\n'); + + m_free( factors ); /* (factors are shallow copies) */ + for(i=0; i < m; i++ ) + mpi_free( pool[i] ); + m_free( pool ); + m_free(perms); + mpi_free(val_2); + return prime; +} + + + +static MPI +gen_prime( unsigned nbits, int secret, int randomlevel ) +{ + unsigned nlimbs; + MPI prime, ptest, pminus1, val_2, val_3, result; + int i; + unsigned x, step; + unsigned count1, count2; + int *mods; + + if( 0 && DBG_CIPHER ) + log_debug("generate a prime of %u bits ", nbits ); + + if( !no_of_small_prime_numbers ) { + for(i=0; small_prime_numbers[i]; i++ ) + no_of_small_prime_numbers++; + } + mods = m_alloc( no_of_small_prime_numbers * sizeof *mods ); + /* make nbits fit into MPI implementation */ + nlimbs = (nbits + BITS_PER_MPI_LIMB - 1) / BITS_PER_MPI_LIMB; + val_2 = mpi_alloc_set_ui( 2 ); + val_3 = mpi_alloc_set_ui( 3); + prime = secret? mpi_alloc_secure( nlimbs ): mpi_alloc( nlimbs ); + result = mpi_alloc_like( prime ); + pminus1= mpi_alloc_like( prime ); + ptest = mpi_alloc_like( prime ); + count1 = count2 = 0; + for(;;) { /* try forvever */ + int dotcount=0; + + /* generate a random number */ + { char *p = get_random_bits( nbits, randomlevel, secret ); + mpi_set_buffer( prime, p, (nbits+7)/8, 0 ); + m_free(p); + } + + /* set high order bit to 1, set low order bit to 1 */ + mpi_set_highbit( prime, nbits-1 ); + mpi_set_bit( prime, 0 ); + + /* calculate all remainders */ + for(i=0; (x = small_prime_numbers[i]); i++ ) + mods[i] = mpi_fdiv_r_ui(NULL, prime, x); + + /* now try some primes starting with prime */ + for(step=0; step < 20000; step += 2 ) { + /* check against all the small primes we have in mods */ + count1++; + for(i=0; (x = small_prime_numbers[i]); i++ ) { + while( mods[i] + step >= x ) + mods[i] -= x; + if( !(mods[i] + step) ) + break; + } + if( x ) + continue; /* found a multiple of an already known prime */ + + mpi_add_ui( ptest, prime, step ); + + /* do a faster Fermat test */ + count2++; + mpi_sub_ui( pminus1, ptest, 1); + mpi_powm( result, val_2, pminus1, ptest ); + if( !mpi_cmp_ui( result, 1 ) ) { /* not composite */ + /* perform stronger tests */ + if( is_prime(ptest, 5, &count2 ) ) { + if( !mpi_test_bit( ptest, nbits-1 ) ) { + progress('\n'); + log_debug("overflow in prime generation\n"); + break; /* step loop, continue with a new prime */ + } + + mpi_free(val_2); + mpi_free(val_3); + mpi_free(result); + mpi_free(pminus1); + mpi_free(prime); + m_free(mods); + return ptest; + } + } + if( ++dotcount == 10 ) { + progress('.'); + dotcount = 0; + } + } + progress(':'); /* restart with a new random value */ + } +} + +/**************** + * Returns: true if this may be a prime + */ +static int +check_prime( MPI prime, MPI val_2 ) +{ + int i; + unsigned x; + int count=0; + + /* check against small primes */ + for(i=0; (x = small_prime_numbers[i]); i++ ) { + if( mpi_divisible_ui( prime, x ) ) + return 0; + } + + /* a quick fermat test */ + { + MPI result = mpi_alloc_like( prime ); + MPI pminus1 = mpi_alloc_like( prime ); + mpi_sub_ui( pminus1, prime, 1); + mpi_powm( result, val_2, pminus1, prime ); + mpi_free( pminus1 ); + if( mpi_cmp_ui( result, 1 ) ) { /* if composite */ + mpi_free( result ); + progress('.'); + return 0; + } + mpi_free( result ); + } + + /* perform stronger tests */ + if( is_prime(prime, 5, &count ) ) + return 1; /* is probably a prime */ + progress('.'); + return 0; +} + + +/**************** + * Return true if n is probably a prime + */ +static int +is_prime( MPI n, unsigned steps, int *count ) +{ + MPI x = mpi_alloc( mpi_get_nlimbs( n ) ); + MPI y = mpi_alloc( mpi_get_nlimbs( n ) ); + MPI z = mpi_alloc( mpi_get_nlimbs( n ) ); + MPI nminus1 = mpi_alloc( mpi_get_nlimbs( n ) ); + MPI a2 = mpi_alloc_set_ui( 2 ); + MPI q; + unsigned i, j, k; + int rc = 0; + unsigned nbits = mpi_get_nbits( n ); + + mpi_sub_ui( nminus1, n, 1 ); + + /* find q and k, so that n = 1 + 2^k * q */ + q = mpi_copy( nminus1 ); + k = mpi_trailing_zeros( q ); + mpi_tdiv_q_2exp(q, q, k); + + for(i=0 ; i < steps; i++ ) { + ++*count; + if( !i ) { + mpi_set_ui( x, 2 ); + } + else { + /*mpi_set_bytes( x, nbits-1, get_random_byte, 0 );*/ + { char *p = get_random_bits( nbits, 0, 0 ); + mpi_set_buffer( x, p, (nbits+7)/8, 0 ); + m_free(p); + } + /* make sure that the number is smaller than the prime + * and keep the randomness of the high bit */ + if( mpi_test_bit( x, nbits-2 ) ) { + mpi_set_highbit( x, nbits-2 ); /* clear all higher bits */ + } + else { + mpi_set_highbit( x, nbits-2 ); + mpi_clear_bit( x, nbits-2 ); + } + assert( mpi_cmp( x, nminus1 ) < 0 && mpi_cmp_ui( x, 1 ) > 0 ); + } + mpi_powm( y, x, q, n); + if( mpi_cmp_ui(y, 1) && mpi_cmp( y, nminus1 ) ) { + for( j=1; j < k && mpi_cmp( y, nminus1 ); j++ ) { + mpi_powm(y, y, a2, n); + if( !mpi_cmp_ui( y, 1 ) ) + goto leave; /* not a prime */ + } + if( mpi_cmp( y, nminus1 ) ) + goto leave; /* not a prime */ + } + progress('+'); + } + rc = 1; /* may be a prime */ + + leave: + mpi_free( x ); + mpi_free( y ); + mpi_free( z ); + mpi_free( nminus1 ); + mpi_free( q ); + + return rc; +} + + +static void +m_out_of_n( char *array, int m, int n ) +{ + int i=0, i1=0, j=0, jp=0, j1=0, k1=0, k2=0; + + if( !m || m >= n ) + return; + + if( m == 1 ) { /* special case */ + for(i=0; i < n; i++ ) + if( array[i] ) { + array[i++] = 0; + if( i >= n ) + i = 0; + array[i] = 1; + return; + } + BUG(); + } + + for(j=1; j < n; j++ ) { + if( array[n-1] == array[n-j-1] ) + continue; + j1 = j; + break; + } + + if( m & 1 ) { /* m is odd */ + if( array[n-1] ) { + if( j1 & 1 ) { + k1 = n - j1; + k2 = k1+2; + if( k2 > n ) + k2 = n; + goto leave; + } + goto scan; + } + k2 = n - j1 - 1; + if( k2 == 0 ) { + k1 = i; + k2 = n - j1; + } + else if( array[k2] && array[k2-1] ) + k1 = n; + else + k1 = k2 + 1; + } + else { /* m is even */ + if( !array[n-1] ) { + k1 = n - j1; + k2 = k1 + 1; + goto leave; + } + + if( !(j1 & 1) ) { + k1 = n - j1; + k2 = k1+2; + if( k2 > n ) + k2 = n; + goto leave; + } + scan: + jp = n - j1 - 1; + for(i=1; i <= jp; i++ ) { + i1 = jp + 2 - i; + if( array[i1-1] ) { + if( array[i1-2] ) { + k1 = i1 - 1; + k2 = n - j1; + } + else { + k1 = i1 - 1; + k2 = n + 1 - j1; + } + goto leave; + } + } + k1 = 1; + k2 = n + 1 - m; + } + leave: + array[k1-1] = !array[k1-1]; + array[k2-1] = !array[k2-1]; +} + diff --git a/src/pluto/rcv_whack.c b/src/pluto/rcv_whack.c new file mode 100644 index 000000000..6a39e7c1f --- /dev/null +++ b/src/pluto/rcv_whack.c @@ -0,0 +1,685 @@ +/* whack communicating routines + * Copyright (C) 1997 Angelos D. Keromytis. + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: rcv_whack.c,v 1.18 2006/05/25 11:33:57 as Exp $ + */ + +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <resolv.h> +#include <arpa/nameser.h> /* missing from <resolv.h> on old systems */ +#include <sys/queue.h> +#include <fcntl.h> + +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "id.h" +#include "ca.h" +#include "certs.h" +#include "ac.h" +#include "smartcard.h" +#include "connections.h" +#include "foodgroups.h" +#include "whack.h" /* needs connections.h */ +#include "packet.h" +#include "demux.h" /* needs packet.h */ +#include "state.h" +#include "ipsec_doi.h" /* needs demux.h and state.h */ +#include "kernel.h" +#include "rcv_whack.h" +#include "log.h" +#include "keys.h" +#include "adns.h" /* needs <resolv.h> */ +#include "dnskey.h" /* needs keys.h and adns.h */ +#include "server.h" +#include "fetch.h" +#include "ocsp.h" +#include "crl.h" + +#include "kernel_alg.h" +#include "ike_alg.h" +/* helper variables and function to decode strings from whack message */ + +static char *next_str + , *str_roof; + +static bool +unpack_str(char **p) +{ + char *end = memchr(next_str, '\0', str_roof - next_str); + + if (end == NULL) + { + return FALSE; /* fishy: no end found */ + } + else + { + *p = next_str == end? NULL : next_str; + next_str = end + 1; + return TRUE; + } +} + +/* bits loading keys from asynchronous DNS */ + +enum key_add_attempt { + ka_TXT, +#ifdef USE_KEYRR + ka_KEY, +#endif + ka_roof /* largest value + 1 */ +}; + +struct key_add_common { + int refCount; + char *diag[ka_roof]; + int whack_fd; + bool success; +}; + +struct key_add_continuation { + struct adns_continuation ac; /* common prefix */ + struct key_add_common *common; /* common data */ + enum key_add_attempt lookingfor; +}; + +static void +key_add_ugh(const struct id *keyid, err_t ugh) +{ + char name[BUF_LEN]; /* longer IDs will be truncated in message */ + + (void)idtoa(keyid, name, sizeof(name)); + loglog(RC_NOKEY + , "failure to fetch key for %s from DNS: %s", name, ugh); +} + +/* last one out: turn out the lights */ +static void +key_add_merge(struct key_add_common *oc, const struct id *keyid) +{ + if (oc->refCount == 0) + { + enum key_add_attempt kaa; + + /* if no success, print all diagnostics */ + if (!oc->success) + for (kaa = ka_TXT; kaa != ka_roof; kaa++) + key_add_ugh(keyid, oc->diag[kaa]); + + for (kaa = ka_TXT; kaa != ka_roof; kaa++) + pfreeany(oc->diag[kaa]); + + close(oc->whack_fd); + pfree(oc); + } +} + +static void +key_add_continue(struct adns_continuation *ac, err_t ugh) +{ + struct key_add_continuation *kc = (void *) ac; + struct key_add_common *oc = kc->common; + + passert(whack_log_fd == NULL_FD); + whack_log_fd = oc->whack_fd; + + if (ugh != NULL) + { + oc->diag[kc->lookingfor] = clone_str(ugh, "key add error"); + } + else + { + oc->success = TRUE; + transfer_to_public_keys(kc->ac.gateways_from_dns +#ifdef USE_KEYRR + , &kc->ac.keys_from_dns +#endif /* USE_KEYRR */ + ); + } + + oc->refCount--; + key_add_merge(oc, &ac->id); + whack_log_fd = NULL_FD; +} + +static void +key_add_request(const whack_message_t *msg) +{ + struct id keyid; + err_t ugh = atoid(msg->keyid, &keyid, FALSE); + + if (ugh != NULL) + { + loglog(RC_BADID, "bad --keyid \"%s\": %s", msg->keyid, ugh); + } + else + { + if (!msg->whack_addkey) + delete_public_keys(&keyid, msg->pubkey_alg + , empty_chunk, empty_chunk); + + if (msg->keyval.len == 0) + { + struct key_add_common *oc + = alloc_thing(struct key_add_common + , "key add common things"); + enum key_add_attempt kaa; + + /* initialize state shared by queries */ + oc->refCount = 0; + oc->whack_fd = dup_any(whack_log_fd); + oc->success = FALSE; + + for (kaa = ka_TXT; kaa != ka_roof; kaa++) + { + struct key_add_continuation *kc + = alloc_thing(struct key_add_continuation + , "key add continuation"); + + oc->diag[kaa] = NULL; + oc->refCount++; + kc->common = oc; + kc->lookingfor = kaa; + switch (kaa) + { + case ka_TXT: + ugh = start_adns_query(&keyid + , &keyid /* same */ + , T_TXT + , key_add_continue + , &kc->ac); + break; +#ifdef USE_KEYRR + case ka_KEY: + ugh = start_adns_query(&keyid + , NULL + , T_KEY + , key_add_continue + , &kc->ac); + break; +#endif /* USE_KEYRR */ + default: + bad_case(kaa); /* suppress gcc warning */ + } + if (ugh != NULL) + { + oc->diag[kaa] = clone_str(ugh, "early key add failure"); + oc->refCount--; + } + } + + /* Done launching queries. + * Handle total failure case. + */ + key_add_merge(oc, &keyid); + } + else + { + ugh = add_public_key(&keyid, DAL_LOCAL, msg->pubkey_alg + , &msg->keyval, &pubkeys); + if (ugh != NULL) + loglog(RC_LOG_SERIOUS, "%s", ugh); + } + } +} + +/* Handle a kernel request. Supposedly, there's a message in + * the kernelsock socket. + */ +void +whack_handle(int whackctlfd) +{ + whack_message_t msg; + struct sockaddr_un whackaddr; + int whackaddrlen = sizeof(whackaddr); + int whackfd = accept(whackctlfd, (struct sockaddr *)&whackaddr, &whackaddrlen); + /* Note: actual value in n should fit in int. To print, cast to int. */ + ssize_t n; + + if (whackfd < 0) + { + log_errno((e, "accept() failed in whack_handle()")); + return; + } + if (fcntl(whackfd, F_SETFD, FD_CLOEXEC) < 0) + { + log_errno((e, "failed to set CLOEXEC in whack_handle()")); + close(whackfd); + return; + } + + n = read(whackfd, &msg, sizeof(msg)); + + if (n == -1) + { + log_errno((e, "read() failed in whack_handle()")); + close(whackfd); + return; + } + + whack_log_fd = whackfd; + + /* sanity check message */ + { + err_t ugh = NULL; + + next_str = msg.string; + str_roof = (char *)&msg + n; + + if ((size_t)n < offsetof(whack_message_t, whack_shutdown) + sizeof(msg.whack_shutdown)) + { + ugh = builddiag("ignoring runt message from whack: got %d bytes", (int)n); + } + else if (msg.magic != WHACK_MAGIC) + { + if (msg.magic == WHACK_BASIC_MAGIC) + { + /* Only shutdown command. Simpler inter-version compatability. */ + if (msg.whack_shutdown) + { + plog("shutting down"); + exit_pluto(0); /* delete lock and leave, with 0 status */ + } + ugh = ""; /* bail early, but without complaint */ + } + else + { + ugh = builddiag("ignoring message from whack with bad magic %d; should be %d; probably wrong version" + , msg.magic, WHACK_MAGIC); + } + } + else if (next_str > str_roof) + { + ugh = builddiag("ignoring truncated message from whack: got %d bytes; expected %u" + , (int) n, (unsigned) sizeof(msg)); + } + else if (!unpack_str(&msg.name) /* string 1 */ + || !unpack_str(&msg.left.id) /* string 2 */ + || !unpack_str(&msg.left.cert) /* string 3 */ + || !unpack_str(&msg.left.ca) /* string 4 */ + || !unpack_str(&msg.left.groups) /* string 5 */ + || !unpack_str(&msg.left.updown) /* string 6 */ + || !unpack_str(&msg.left.virt) /* string 7 */ + || !unpack_str(&msg.right.id) /* string 8 */ + || !unpack_str(&msg.right.cert) /* string 9 */ + || !unpack_str(&msg.right.ca) /* string 10 */ + || !unpack_str(&msg.right.groups) /* string 11 */ + || !unpack_str(&msg.right.updown) /* string 12 */ + || !unpack_str(&msg.right.virt) /* string 13 */ + || !unpack_str(&msg.keyid) /* string 14 */ + || !unpack_str(&msg.myid) /* string 15 */ + || !unpack_str(&msg.cacert) /* string 16 */ + || !unpack_str(&msg.ldaphost) /* string 17 */ + || !unpack_str(&msg.ldapbase) /* string 18 */ + || !unpack_str(&msg.crluri) /* string 19 */ + || !unpack_str(&msg.crluri2) /* string 20 */ + || !unpack_str(&msg.ocspuri) /* string 21 */ + || !unpack_str(&msg.ike) /* string 22 */ + || !unpack_str(&msg.esp) /* string 23 */ + || !unpack_str(&msg.sc_data) /* string 24 */ + || str_roof - next_str != (ptrdiff_t)msg.keyval.len) /* check chunk */ + { + ugh = "message from whack contains bad string"; + } + else + { + msg.keyval.ptr = next_str; /* grab chunk */ + } + + if (ugh != NULL) + { + if (*ugh != '\0') + loglog(RC_BADWHACKMESSAGE, "%s", ugh); + whack_log_fd = NULL_FD; + close(whackfd); + return; + } + } + + if (msg.whack_options) + { +#ifdef DEBUG + if (msg.name == NULL) + { + /* we do a two-step so that if either old or new would + * cause the message to print, it will be printed. + */ + cur_debugging |= msg.debugging; + DBG(DBG_CONTROL + , DBG_log("base debugging = %s" + , bitnamesof(debug_bit_names, msg.debugging))); + cur_debugging = base_debugging = msg.debugging; + } + else if (!msg.whack_connection) + { + struct connection *c = con_by_name(msg.name, TRUE); + + if (c != NULL) + { + c->extra_debugging = msg.debugging; + DBG(DBG_CONTROL + , DBG_log("\"%s\" extra_debugging = %s" + , c->name + , bitnamesof(debug_bit_names, c->extra_debugging))); + } + } +#endif + } + + if (msg.whack_myid) + set_myid(MYID_SPECIFIED, msg.myid); + + /* Deleting combined with adding a connection works as replace. + * To make this more useful, in only this combination, + * delete will silently ignore the lack of the connection. + */ + if (msg.whack_delete) + { + if (msg.whack_ca) + find_ca_info_by_name(msg.name, TRUE); + else + delete_connections_by_name(msg.name, !msg.whack_connection); + } + + if (msg.whack_deletestate) + { + struct state *st = state_with_serialno(msg.whack_deletestateno); + + if (st == NULL) + { + loglog(RC_UNKNOWN_NAME, "no state #%lu to delete" + , msg.whack_deletestateno); + } + else + { + delete_state(st); + } + } + + if (msg.whack_crash) + delete_states_by_peer(&msg.whack_crash_peer); + + if (msg.whack_connection) + add_connection(&msg); + + if (msg.whack_ca && msg.cacert != NULL) + add_ca_info(&msg); + + /* process "listen" before any operation that could require it */ + if (msg.whack_listen) + { + close_peerlog(); /* close any open per-peer logs */ + plog("listening for IKE messages"); + listening = TRUE; + daily_log_reset(); + reset_adns_restart_count(); + set_myFQDN(); + find_ifaces(); + load_preshared_secrets(NULL_FD); + load_groups(); + } + if (msg.whack_unlisten) + { + plog("no longer listening for IKE messages"); + listening = FALSE; + } + + if (msg.whack_reread & REREAD_SECRETS) + { + load_preshared_secrets(whackfd); + } + + if (msg.whack_reread & REREAD_CACERTS) + { + load_authcerts("CA cert", CA_CERT_PATH, AUTH_CA); + } + + if (msg.whack_reread & REREAD_AACERTS) + { + load_authcerts("AA cert", AA_CERT_PATH, AUTH_AA); + } + + if (msg.whack_reread & REREAD_OCSPCERTS) + { + load_authcerts("OCSP cert", OCSP_CERT_PATH, AUTH_OCSP); + } + + if (msg.whack_reread & REREAD_ACERTS) + { + load_acerts(); + } + + if (msg.whack_reread & REREAD_CRLS) + { + load_crls(); + } + + if (msg.whack_purgeocsp) + { + free_ocsp_fetch(); + free_ocsp_cache(); + } + + if (msg.whack_list & LIST_ALGS) + { + ike_alg_list(); + kernel_alg_list(); + } + if (msg.whack_list & LIST_PUBKEYS) + { + list_public_keys(msg.whack_utc); + } + + if (msg.whack_list & LIST_CERTS) + { + list_certs(msg.whack_utc); + } + + if (msg.whack_list & LIST_CACERTS) + { + list_authcerts("CA", AUTH_CA, msg.whack_utc); + } + + if (msg.whack_list & LIST_AACERTS) + { + list_authcerts("AA", AUTH_AA, msg.whack_utc); + } + + if (msg.whack_list & LIST_OCSPCERTS) + { + list_authcerts("OCSP", AUTH_OCSP, msg.whack_utc); + } + + if (msg.whack_list & LIST_ACERTS) + { + list_acerts(msg.whack_utc); + } + + if (msg.whack_list & LIST_GROUPS) + { + list_groups(msg.whack_utc); + } + + if (msg.whack_list & LIST_CAINFOS) + { + list_ca_infos(msg.whack_utc); + } + + if (msg.whack_list & LIST_CRLS) + { + list_crls(msg.whack_utc, strict_crl_policy); + list_crl_fetch_requests(msg.whack_utc); + } + + if (msg.whack_list & LIST_OCSP) + { + list_ocsp_cache(msg.whack_utc, strict_crl_policy); + list_ocsp_fetch_requests(msg.whack_utc); + } + + if (msg.whack_list & LIST_CARDS) + { + scx_list(msg.whack_utc); + } + + if (msg.whack_key) + { + /* add a public key */ + key_add_request(&msg); + } + + if (msg.whack_route) + { + if (!listening) + { + whack_log(RC_DEAF, "need --listen before --route"); + } + if (msg.name == NULL) + { + whack_log(RC_UNKNOWN_NAME + , "whack --route requires a connection name"); + } + else + { + struct connection *c = con_by_name(msg.name, TRUE); + + if (c != NULL && c->ikev1) + { + set_cur_connection(c); + if (!oriented(*c)) + whack_log(RC_ORIENT + , "we have no ipsecN interface for either end of this connection"); + else if (c->policy & POLICY_GROUP) + route_group(c); + else if (!trap_connection(c)) + whack_log(RC_ROUTE, "could not route"); + reset_cur_connection(); + } + } + } + + if (msg.whack_unroute) + { + if (msg.name == NULL) + { + whack_log(RC_UNKNOWN_NAME + , "whack --unroute requires a connection name"); + } + else + { + struct connection *c = con_by_name(msg.name, TRUE); + + if (c != NULL && c->ikev1) + { + struct spd_route *sr; + int fail = 0; + + set_cur_connection(c); + + for (sr = &c->spd; sr != NULL; sr = sr->next) + { + if (sr->routing >= RT_ROUTED_TUNNEL) + fail++; + } + if (fail > 0) + whack_log(RC_RTBUSY, "cannot unroute: route busy"); + else if (c->policy & POLICY_GROUP) + unroute_group(c); + else + unroute_connection(c); + reset_cur_connection(); + } + } + } + + if (msg.whack_initiate) + { + if (!listening) + { + whack_log(RC_DEAF, "need --listen before --initiate"); + } + else if (msg.name == NULL) + { + whack_log(RC_UNKNOWN_NAME + , "whack --initiate requires a connection name"); + } + else + { + initiate_connection(msg.name + , msg.whack_async? NULL_FD : dup_any(whackfd)); + } + } + + if (msg.whack_oppo_initiate) + { + if (!listening) + whack_log(RC_DEAF, "need --listen before opportunistic initiation"); + else + initiate_opportunistic(&msg.oppo_my_client, &msg.oppo_peer_client, 0 + , FALSE + , msg.whack_async? NULL_FD : dup_any(whackfd)); + } + + if (msg.whack_terminate) + { + if (msg.name == NULL) + { + whack_log(RC_UNKNOWN_NAME + , "whack --terminate requires a connection name"); + } + else + { + terminate_connection(msg.name); + } + } + + if (msg.whack_status) + show_status(msg.whack_statusall, msg.name); + + if (msg.whack_shutdown) + { + plog("shutting down"); + exit_pluto(0); /* delete lock and leave, with 0 status */ + } + + if (msg.whack_sc_op != SC_OP_NONE) + { + if (pkcs11_proxy) + scx_op_via_whack(msg.sc_data, msg.inbase, msg.outbase + , msg.whack_sc_op, msg.keyid, whackfd); + else + plog("pkcs11 access to smartcard not allowed (set pkcs11proxy=yes)"); + } + + whack_log_fd = NULL_FD; + close(whackfd); +} + +/* + * Local Variables: + * c-basic-offset:4 + * c-style: pluto + * End: + */ diff --git a/src/pluto/rcv_whack.h b/src/pluto/rcv_whack.h new file mode 100644 index 000000000..f42761c51 --- /dev/null +++ b/src/pluto/rcv_whack.h @@ -0,0 +1,17 @@ +/* whack communicating routines + * Copyright (C) 1998, 1999 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: rcv_whack.h,v 1.1 2004/03/15 20:35:29 as Exp $ + */ + +extern void whack_handle(int kernelfd); diff --git a/src/pluto/rnd.c b/src/pluto/rnd.c new file mode 100644 index 000000000..812882c6b --- /dev/null +++ b/src/pluto/rnd.c @@ -0,0 +1,250 @@ +/* randomness machinery + * Copyright (C) 1997 Angelos D. Keromytis. + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: rnd.c,v 1.3 2005/09/08 16:26:30 as Exp $ + */ + +/* A true random number generator (we hope) + * + * Under LINUX ("linux" predefined), use /dev/urandom. + * Under OpenBSD ("__OpenBSD__" predefined), use arc4random(). + * Otherwise use our own random number generator based on clock skew. + * I (ADK) first heard of the idea from John Ioannidis, who heard it + * from Matt Blaze and/or Jack Lacy. + * ??? Why is mixing need for linux but not OpenBSD? + */ + +/* Pluto's uses of randomness: + * + * - Setting up the "secret_of_the_day". This changes every hour! 20 + * bytes a shot. It is used in building responder cookies. + * + * - generating initiator cookies (8 bytes, once per Phase 1 initiation). + * + * - 32 bytes per DH local secret. Once per Main Mode exchange and once + * per Quick Mode Exchange with PFS. (Size is our choice, with + * tradeoffs.) + * + * - 16 bytes per nonce we generate. Once per Main Mode exchange and + * once per Quick Mode exchange. (Again, we choose the size.) + * + * - 4 bytes per SPI number that we generate. We choose the SPIs for all + * inbound SPIs, one to three per IPSEC SA (one for AH (rare, probably) + * one for ESP (almost always), and one for tunnel (very common)). + * I don't actually know how the kernel would generate these numbers -- + * currently Pluto generates them; this isn't the way things will be + * done in the future. + * + * - 4 bytes per Message ID we need to generate. One per Quick Mode + * exchange. Eventually, one per informational exchange. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <unistd.h> +#include <errno.h> +#include <sys/time.h> +#include <fcntl.h> + +#include <freeswan.h> + +#include "sha1.h" +#include "constants.h" +#include "defs.h" +#include "rnd.h" +#include "log.h" +#include "timer.h" + +#ifdef linux +# define USE_DEV_RANDOM 1 +# define RANDOM_PATH DEV_URANDOM +#else +# ifdef __OpenBSD__ +# define USE_ARC4RANDOM +# else +# define USE_CLOCK_SLEW +# endif +#endif + +#ifdef USE_ARC4RANDOM + +#define get_rnd_byte() (arc4random() % 256) + +#else /**** start of large #else ****/ + +#ifdef USE_DEV_RANDOM +static int random_fd = NULL_FD; +#endif + +#define RANDOM_POOL_SIZE SHA1_DIGEST_SIZE +static u_char random_pool[RANDOM_POOL_SIZE]; + +#ifdef USE_DEV_RANDOM + +/* Generate (what we hope is) a true random byte using /dev/urandom */ +static u_char +generate_rnd_byte(void) +{ + u_char c; + + if (read(random_fd, &c, sizeof(c)) == -1) + exit_log_errno((e, "read() failed in get_rnd_byte()")); + + return c; +} + +#else /* !USE_DEV_RANDOM */ + +/* Generate (what we hope is) a true random byte using the clock skew trick. + * Note: this code is not maintained! In particular, LINUX signal(2) + * semantics changed with glibc2 (and not for the better). It isn't clear + * that this code will work. We keep the code because someday it might + * come in handy. + */ +# error "This code is not maintained. Please define USE_DEV_RANDOM." + +static volatile sig_atomic_t i, j, k; + +/* timer signal handler */ +static void +rnd_handler(int ignore_me UNUSED) +{ + k <<= 1; /* Shift left by 1 */ + j++; + k |= (i & 0x1); /* Get lsbit of counter */ + + if (j != 8) + signal(SIGVTALRM, rnd_handler); +} + +static u_char +generate_rnd_byte(void) +{ + struct itimerval tmval, ntmval; + +# ifdef NEVER /* ??? */ +# ifdef linux + int mask = siggetmask(); + + mask |= SIGVTALRM; + sigsetmask(mask); +# endif +# endif + + i = 0; + j = 0; + + ntmval.it_interval.tv_sec = 0; + ntmval.it_interval.tv_usec = 1; + ntmval.it_value.tv_sec = 0; + ntmval.it_value.tv_usec = 1; + signal(SIGVTALRM, rnd_handler); + setitimer(ITIMER_VIRTUAL, &ntmval, &tmval); + + while (j != 8) + i++; + + setitimer(ITIMER_VIRTUAL, &tmval, &ntmval); + signal(SIGVTALRM, SIG_IGN); + +# ifdef NEVER /* ??? */ +# ifdef linux + mask ^= SIGVTALRM; + sigsetmask(mask); +# endif +# endif + + return k; +} + +#endif /* !USE_DEV_RANDOM */ + +static void +mix_pool(void) +{ + SHA1_CTX ctx; + + SHA1Init(&ctx); + SHA1Update(&ctx, random_pool, RANDOM_POOL_SIZE); + SHA1Final(random_pool, &ctx); +} + +/* + * Get a single random byte. + */ +static u_char +get_rnd_byte(void) +{ + random_pool[RANDOM_POOL_SIZE - 1] = generate_rnd_byte(); + random_pool[0] = generate_rnd_byte(); + mix_pool(); + return random_pool[0]; +} + +#endif /* !USE_ARC4RANDOM */ /**** end of large #else ****/ + +void +get_rnd_bytes(u_char *buffer, int length) +{ + int i; + + for (i = 0; i < length; i++) + buffer[i] = get_rnd_byte(); +} + +/* + * Initialize the random pool. + */ +void +init_rnd_pool(void) +{ +#ifndef USE_ARC4RANDOM +# ifdef USE_DEV_RANDOM + DBG(DBG_KLIPS, DBG_log("opening %s", RANDOM_PATH)); + random_fd = open(RANDOM_PATH, O_RDONLY); + if (random_fd == -1) + exit_log_errno((e, "open of %s failed in init_rnd_pool()", RANDOM_PATH)); + fcntl(random_fd, F_SETFD, FD_CLOEXEC); +# endif + + get_rnd_bytes(random_pool, RANDOM_POOL_SIZE); + mix_pool(); +#endif /* !USE_ARC4RANDOM */ + + /* start of rand(3) on the right foot */ + { + unsigned int seed; + + get_rnd_bytes((void *)&seed, sizeof(seed)); + srand(seed); + } +} + +u_char secret_of_the_day[SHA1_DIGEST_SIZE]; + +#ifndef NO_PLUTO + +void +init_secret(void) +{ + /* + * Generate the secret value for responder cookies, and + * schedule an event for refresh. + */ + get_rnd_bytes(secret_of_the_day, sizeof(secret_of_the_day)); + event_schedule(EVENT_REINIT_SECRET, EVENT_REINIT_SECRET_DELAY, NULL); +} + +#endif /* NO_PLUTO */ diff --git a/src/pluto/rnd.h b/src/pluto/rnd.h new file mode 100644 index 000000000..0bd168039 --- /dev/null +++ b/src/pluto/rnd.h @@ -0,0 +1,21 @@ +/* randomness machinery + * Copyright (C) 1998, 1999 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: rnd.h,v 1.1 2004/03/15 20:35:29 as Exp $ + */ + +extern u_char secret_of_the_day[SHA1_DIGEST_SIZE]; + +extern void get_rnd_bytes(u_char *buffer, int length); +extern void init_rnd_pool(void); +extern void init_secret(void); diff --git a/src/pluto/rsaref/pkcs11.h b/src/pluto/rsaref/pkcs11.h new file mode 100644 index 000000000..9261e1e4c --- /dev/null +++ b/src/pluto/rsaref/pkcs11.h @@ -0,0 +1,299 @@ +/* pkcs11.h include file for PKCS #11. */ +/* $Revision: 1.2 $ */ + +/* License to copy and use this software is granted provided that it is + * identified as "RSA Security Inc. PKCS #11 Cryptographic Token Interface + * (Cryptoki)" in all material mentioning or referencing this software. + + * License is also granted to make and use derivative works provided that + * such works are identified as "derived from the RSA Security Inc. PKCS #11 + * Cryptographic Token Interface (Cryptoki)" in all material mentioning or + * referencing the derived work. + + * RSA Security Inc. makes no representations concerning either the + * merchantability of this software or the suitability of this software for + * any particular purpose. It is provided "as is" without express or implied + * warranty of any kind. + */ + +#ifndef _PKCS11_H_ +#define _PKCS11_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* Before including this file (pkcs11.h) (or pkcs11t.h by + * itself), 6 platform-specific macros must be defined. These + * macros are described below, and typical definitions for them + * are also given. Be advised that these definitions can depend + * on both the platform and the compiler used (and possibly also + * on whether a Cryptoki library is linked statically or + * dynamically). + * + * In addition to defining these 6 macros, the packing convention + * for Cryptoki structures should be set. The Cryptoki + * convention on packing is that structures should be 1-byte + * aligned. + * + * If you're using Microsoft Developer Studio 5.0 to produce + * Win32 stuff, this might be done by using the following + * preprocessor directive before including pkcs11.h or pkcs11t.h: + * + * #pragma pack(push, cryptoki, 1) + * + * and using the following preprocessor directive after including + * pkcs11.h or pkcs11t.h: + * + * #pragma pack(pop, cryptoki) + * + * If you're using an earlier version of Microsoft Developer + * Studio to produce Win16 stuff, this might be done by using + * the following preprocessor directive before including + * pkcs11.h or pkcs11t.h: + * + * #pragma pack(1) + * + * In a UNIX environment, you're on your own for this. You might + * not need to do (or be able to do!) anything. + * + * + * Now for the macros: + * + * + * 1. CK_PTR: The indirection string for making a pointer to an + * object. It can be used like this: + * + * typedef CK_BYTE CK_PTR CK_BYTE_PTR; + * + * If you're using Microsoft Developer Studio 5.0 to produce + * Win32 stuff, it might be defined by: + * + * #define CK_PTR * + * + * If you're using an earlier version of Microsoft Developer + * Studio to produce Win16 stuff, it might be defined by: + * + * #define CK_PTR far * + * + * In a typical UNIX environment, it might be defined by: + * + * #define CK_PTR * + * + * + * 2. CK_DEFINE_FUNCTION(returnType, name): A macro which makes + * an exportable Cryptoki library function definition out of a + * return type and a function name. It should be used in the + * following fashion to define the exposed Cryptoki functions in + * a Cryptoki library: + * + * CK_DEFINE_FUNCTION(CK_RV, C_Initialize)( + * CK_VOID_PTR pReserved + * ) + * { + * ... + * } + * + * If you're using Microsoft Developer Studio 5.0 to define a + * function in a Win32 Cryptoki .dll, it might be defined by: + * + * #define CK_DEFINE_FUNCTION(returnType, name) \ + * returnType __declspec(dllexport) name + * + * If you're using an earlier version of Microsoft Developer + * Studio to define a function in a Win16 Cryptoki .dll, it + * might be defined by: + * + * #define CK_DEFINE_FUNCTION(returnType, name) \ + * returnType __export _far _pascal name + * + * In a UNIX environment, it might be defined by: + * + * #define CK_DEFINE_FUNCTION(returnType, name) \ + * returnType name + * + * + * 3. CK_DECLARE_FUNCTION(returnType, name): A macro which makes + * an importable Cryptoki library function declaration out of a + * return type and a function name. It should be used in the + * following fashion: + * + * extern CK_DECLARE_FUNCTION(CK_RV, C_Initialize)( + * CK_VOID_PTR pReserved + * ); + * + * If you're using Microsoft Developer Studio 5.0 to declare a + * function in a Win32 Cryptoki .dll, it might be defined by: + * + * #define CK_DECLARE_FUNCTION(returnType, name) \ + * returnType __declspec(dllimport) name + * + * If you're using an earlier version of Microsoft Developer + * Studio to declare a function in a Win16 Cryptoki .dll, it + * might be defined by: + * + * #define CK_DECLARE_FUNCTION(returnType, name) \ + * returnType __export _far _pascal name + * + * In a UNIX environment, it might be defined by: + * + * #define CK_DECLARE_FUNCTION(returnType, name) \ + * returnType name + * + * + * 4. CK_DECLARE_FUNCTION_POINTER(returnType, name): A macro + * which makes a Cryptoki API function pointer declaration or + * function pointer type declaration out of a return type and a + * function name. It should be used in the following fashion: + * + * // Define funcPtr to be a pointer to a Cryptoki API function + * // taking arguments args and returning CK_RV. + * CK_DECLARE_FUNCTION_POINTER(CK_RV, funcPtr)(args); + * + * or + * + * // Define funcPtrType to be the type of a pointer to a + * // Cryptoki API function taking arguments args and returning + * // CK_RV, and then define funcPtr to be a variable of type + * // funcPtrType. + * typedef CK_DECLARE_FUNCTION_POINTER(CK_RV, funcPtrType)(args); + * funcPtrType funcPtr; + * + * If you're using Microsoft Developer Studio 5.0 to access + * functions in a Win32 Cryptoki .dll, in might be defined by: + * + * #define CK_DECLARE_FUNCTION_POINTER(returnType, name) \ + * returnType __declspec(dllimport) (* name) + * + * If you're using an earlier version of Microsoft Developer + * Studio to access functions in a Win16 Cryptoki .dll, it might + * be defined by: + * + * #define CK_DECLARE_FUNCTION_POINTER(returnType, name) \ + * returnType __export _far _pascal (* name) + * + * In a UNIX environment, it might be defined by: + * + * #define CK_DECLARE_FUNCTION_POINTER(returnType, name) \ + * returnType (* name) + * + * + * 5. CK_CALLBACK_FUNCTION(returnType, name): A macro which makes + * a function pointer type for an application callback out of + * a return type for the callback and a name for the callback. + * It should be used in the following fashion: + * + * CK_CALLBACK_FUNCTION(CK_RV, myCallback)(args); + * + * to declare a function pointer, myCallback, to a callback + * which takes arguments args and returns a CK_RV. It can also + * be used like this: + * + * typedef CK_CALLBACK_FUNCTION(CK_RV, myCallbackType)(args); + * myCallbackType myCallback; + * + * If you're using Microsoft Developer Studio 5.0 to do Win32 + * Cryptoki development, it might be defined by: + * + * #define CK_CALLBACK_FUNCTION(returnType, name) \ + * returnType (* name) + * + * If you're using an earlier version of Microsoft Developer + * Studio to do Win16 development, it might be defined by: + * + * #define CK_CALLBACK_FUNCTION(returnType, name) \ + * returnType _far _pascal (* name) + * + * In a UNIX environment, it might be defined by: + * + * #define CK_CALLBACK_FUNCTION(returnType, name) \ + * returnType (* name) + * + * + * 6. NULL_PTR: This macro is the value of a NULL pointer. + * + * In any ANSI/ISO C environment (and in many others as well), + * this should best be defined by + * + * #ifndef NULL_PTR + * #define NULL_PTR 0 + * #endif + */ + + +/* All the various Cryptoki types and #define'd values are in the + * file pkcs11t.h. */ +#include "pkcs11t.h" + +#define __PASTE(x,y) x##y + + +/* ============================================================== + * Define the "extern" form of all the entry points. + * ============================================================== + */ + +#define CK_NEED_ARG_LIST 1 +#define CK_PKCS11_FUNCTION_INFO(name) \ + extern CK_DECLARE_FUNCTION(CK_RV, name) + +/* pkcs11f.h has all the information about the Cryptoki + * function prototypes. */ +#include "pkcs11f.h" + +#undef CK_NEED_ARG_LIST +#undef CK_PKCS11_FUNCTION_INFO + + +/* ============================================================== + * Define the typedef form of all the entry points. That is, for + * each Cryptoki function C_XXX, define a type CK_C_XXX which is + * a pointer to that kind of function. + * ============================================================== + */ + +#define CK_NEED_ARG_LIST 1 +#define CK_PKCS11_FUNCTION_INFO(name) \ + typedef CK_DECLARE_FUNCTION_POINTER(CK_RV, __PASTE(CK_,name)) + +/* pkcs11f.h has all the information about the Cryptoki + * function prototypes. */ +#include "pkcs11f.h" + +#undef CK_NEED_ARG_LIST +#undef CK_PKCS11_FUNCTION_INFO + + +/* ============================================================== + * Define structed vector of entry points. A CK_FUNCTION_LIST + * contains a CK_VERSION indicating a library's Cryptoki version + * and then a whole slew of function pointers to the routines in + * the library. This type was declared, but not defined, in + * pkcs11t.h. + * ============================================================== + */ + +#define CK_PKCS11_FUNCTION_INFO(name) \ + __PASTE(CK_,name) name; + +struct CK_FUNCTION_LIST { + + CK_VERSION version; /* Cryptoki version */ + +/* Pile all the function pointers into the CK_FUNCTION_LIST. */ +/* pkcs11f.h has all the information about the Cryptoki + * function prototypes. */ +#include "pkcs11f.h" + +}; + +#undef CK_PKCS11_FUNCTION_INFO + + +#undef __PASTE + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/pluto/rsaref/pkcs11f.h b/src/pluto/rsaref/pkcs11f.h new file mode 100644 index 000000000..dec6315dd --- /dev/null +++ b/src/pluto/rsaref/pkcs11f.h @@ -0,0 +1,912 @@ +/* pkcs11f.h include file for PKCS #11. */ +/* $Revision: 1.2 $ */ + +/* License to copy and use this software is granted provided that it is + * identified as "RSA Security Inc. PKCS #11 Cryptographic Token Interface + * (Cryptoki)" in all material mentioning or referencing this software. + + * License is also granted to make and use derivative works provided that + * such works are identified as "derived from the RSA Security Inc. PKCS #11 + * Cryptographic Token Interface (Cryptoki)" in all material mentioning or + * referencing the derived work. + + * RSA Security Inc. makes no representations concerning either the + * merchantability of this software or the suitability of this software for + * any particular purpose. It is provided "as is" without express or implied + * warranty of any kind. + */ + +/* This header file contains pretty much everything about all the */ +/* Cryptoki function prototypes. Because this information is */ +/* used for more than just declaring function prototypes, the */ +/* order of the functions appearing herein is important, and */ +/* should not be altered. */ + +/* General-purpose */ + +/* C_Initialize initializes the Cryptoki library. */ +CK_PKCS11_FUNCTION_INFO(C_Initialize) +#ifdef CK_NEED_ARG_LIST +( + CK_VOID_PTR pInitArgs /* if this is not NULL_PTR, it gets + * cast to CK_C_INITIALIZE_ARGS_PTR + * and dereferenced */ +); +#endif + + +/* C_Finalize indicates that an application is done with the + * Cryptoki library. */ +CK_PKCS11_FUNCTION_INFO(C_Finalize) +#ifdef CK_NEED_ARG_LIST +( + CK_VOID_PTR pReserved /* reserved. Should be NULL_PTR */ +); +#endif + + +/* C_GetInfo returns general information about Cryptoki. */ +CK_PKCS11_FUNCTION_INFO(C_GetInfo) +#ifdef CK_NEED_ARG_LIST +( + CK_INFO_PTR pInfo /* location that receives information */ +); +#endif + + +/* C_GetFunctionList returns the function list. */ +CK_PKCS11_FUNCTION_INFO(C_GetFunctionList) +#ifdef CK_NEED_ARG_LIST +( + CK_FUNCTION_LIST_PTR_PTR ppFunctionList /* receives pointer to + * function list */ +); +#endif + + + +/* Slot and token management */ + +/* C_GetSlotList obtains a list of slots in the system. */ +CK_PKCS11_FUNCTION_INFO(C_GetSlotList) +#ifdef CK_NEED_ARG_LIST +( + CK_BBOOL tokenPresent, /* only slots with tokens? */ + CK_SLOT_ID_PTR pSlotList, /* receives array of slot IDs */ + CK_ULONG_PTR pulCount /* receives number of slots */ +); +#endif + + +/* C_GetSlotInfo obtains information about a particular slot in + * the system. */ +CK_PKCS11_FUNCTION_INFO(C_GetSlotInfo) +#ifdef CK_NEED_ARG_LIST +( + CK_SLOT_ID slotID, /* the ID of the slot */ + CK_SLOT_INFO_PTR pInfo /* receives the slot information */ +); +#endif + + +/* C_GetTokenInfo obtains information about a particular token + * in the system. */ +CK_PKCS11_FUNCTION_INFO(C_GetTokenInfo) +#ifdef CK_NEED_ARG_LIST +( + CK_SLOT_ID slotID, /* ID of the token's slot */ + CK_TOKEN_INFO_PTR pInfo /* receives the token information */ +); +#endif + + +/* C_GetMechanismList obtains a list of mechanism types + * supported by a token. */ +CK_PKCS11_FUNCTION_INFO(C_GetMechanismList) +#ifdef CK_NEED_ARG_LIST +( + CK_SLOT_ID slotID, /* ID of token's slot */ + CK_MECHANISM_TYPE_PTR pMechanismList, /* gets mech. array */ + CK_ULONG_PTR pulCount /* gets # of mechs. */ +); +#endif + + +/* C_GetMechanismInfo obtains information about a particular + * mechanism possibly supported by a token. */ +CK_PKCS11_FUNCTION_INFO(C_GetMechanismInfo) +#ifdef CK_NEED_ARG_LIST +( + CK_SLOT_ID slotID, /* ID of the token's slot */ + CK_MECHANISM_TYPE type, /* type of mechanism */ + CK_MECHANISM_INFO_PTR pInfo /* receives mechanism info */ +); +#endif + + +/* C_InitToken initializes a token. */ +CK_PKCS11_FUNCTION_INFO(C_InitToken) +#ifdef CK_NEED_ARG_LIST +/* pLabel changed from CK_CHAR_PTR to CK_UTF8CHAR_PTR for v2.10 */ +( + CK_SLOT_ID slotID, /* ID of the token's slot */ + CK_UTF8CHAR_PTR pPin, /* the SO's initial PIN */ + CK_ULONG ulPinLen, /* length in bytes of the PIN */ + CK_UTF8CHAR_PTR pLabel /* 32-byte token label (blank padded) */ +); +#endif + + +/* C_InitPIN initializes the normal user's PIN. */ +CK_PKCS11_FUNCTION_INFO(C_InitPIN) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_UTF8CHAR_PTR pPin, /* the normal user's PIN */ + CK_ULONG ulPinLen /* length in bytes of the PIN */ +); +#endif + + +/* C_SetPIN modifies the PIN of the user who is logged in. */ +CK_PKCS11_FUNCTION_INFO(C_SetPIN) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_UTF8CHAR_PTR pOldPin, /* the old PIN */ + CK_ULONG ulOldLen, /* length of the old PIN */ + CK_UTF8CHAR_PTR pNewPin, /* the new PIN */ + CK_ULONG ulNewLen /* length of the new PIN */ +); +#endif + + + +/* Session management */ + +/* C_OpenSession opens a session between an application and a + * token. */ +CK_PKCS11_FUNCTION_INFO(C_OpenSession) +#ifdef CK_NEED_ARG_LIST +( + CK_SLOT_ID slotID, /* the slot's ID */ + CK_FLAGS flags, /* from CK_SESSION_INFO */ + CK_VOID_PTR pApplication, /* passed to callback */ + CK_NOTIFY Notify, /* callback function */ + CK_SESSION_HANDLE_PTR phSession /* gets session handle */ +); +#endif + + +/* C_CloseSession closes a session between an application and a + * token. */ +CK_PKCS11_FUNCTION_INFO(C_CloseSession) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession /* the session's handle */ +); +#endif + + +/* C_CloseAllSessions closes all sessions with a token. */ +CK_PKCS11_FUNCTION_INFO(C_CloseAllSessions) +#ifdef CK_NEED_ARG_LIST +( + CK_SLOT_ID slotID /* the token's slot */ +); +#endif + + +/* C_GetSessionInfo obtains information about the session. */ +CK_PKCS11_FUNCTION_INFO(C_GetSessionInfo) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_SESSION_INFO_PTR pInfo /* receives session info */ +); +#endif + + +/* C_GetOperationState obtains the state of the cryptographic operation + * in a session. */ +CK_PKCS11_FUNCTION_INFO(C_GetOperationState) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pOperationState, /* gets state */ + CK_ULONG_PTR pulOperationStateLen /* gets state length */ +); +#endif + + +/* C_SetOperationState restores the state of the cryptographic + * operation in a session. */ +CK_PKCS11_FUNCTION_INFO(C_SetOperationState) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pOperationState, /* holds state */ + CK_ULONG ulOperationStateLen, /* holds state length */ + CK_OBJECT_HANDLE hEncryptionKey, /* en/decryption key */ + CK_OBJECT_HANDLE hAuthenticationKey /* sign/verify key */ +); +#endif + + +/* C_Login logs a user into a token. */ +CK_PKCS11_FUNCTION_INFO(C_Login) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_USER_TYPE userType, /* the user type */ + CK_UTF8CHAR_PTR pPin, /* the user's PIN */ + CK_ULONG ulPinLen /* the length of the PIN */ +); +#endif + + +/* C_Logout logs a user out from a token. */ +CK_PKCS11_FUNCTION_INFO(C_Logout) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession /* the session's handle */ +); +#endif + + + +/* Object management */ + +/* C_CreateObject creates a new object. */ +CK_PKCS11_FUNCTION_INFO(C_CreateObject) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_ATTRIBUTE_PTR pTemplate, /* the object's template */ + CK_ULONG ulCount, /* attributes in template */ + CK_OBJECT_HANDLE_PTR phObject /* gets new object's handle. */ +); +#endif + + +/* C_CopyObject copies an object, creating a new object for the + * copy. */ +CK_PKCS11_FUNCTION_INFO(C_CopyObject) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_OBJECT_HANDLE hObject, /* the object's handle */ + CK_ATTRIBUTE_PTR pTemplate, /* template for new object */ + CK_ULONG ulCount, /* attributes in template */ + CK_OBJECT_HANDLE_PTR phNewObject /* receives handle of copy */ +); +#endif + + +/* C_DestroyObject destroys an object. */ +CK_PKCS11_FUNCTION_INFO(C_DestroyObject) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_OBJECT_HANDLE hObject /* the object's handle */ +); +#endif + + +/* C_GetObjectSize gets the size of an object in bytes. */ +CK_PKCS11_FUNCTION_INFO(C_GetObjectSize) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_OBJECT_HANDLE hObject, /* the object's handle */ + CK_ULONG_PTR pulSize /* receives size of object */ +); +#endif + + +/* C_GetAttributeValue obtains the value of one or more object + * attributes. */ +CK_PKCS11_FUNCTION_INFO(C_GetAttributeValue) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_OBJECT_HANDLE hObject, /* the object's handle */ + CK_ATTRIBUTE_PTR pTemplate, /* specifies attrs; gets vals */ + CK_ULONG ulCount /* attributes in template */ +); +#endif + + +/* C_SetAttributeValue modifies the value of one or more object + * attributes */ +CK_PKCS11_FUNCTION_INFO(C_SetAttributeValue) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_OBJECT_HANDLE hObject, /* the object's handle */ + CK_ATTRIBUTE_PTR pTemplate, /* specifies attrs and values */ + CK_ULONG ulCount /* attributes in template */ +); +#endif + + +/* C_FindObjectsInit initializes a search for token and session + * objects that match a template. */ +CK_PKCS11_FUNCTION_INFO(C_FindObjectsInit) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_ATTRIBUTE_PTR pTemplate, /* attribute values to match */ + CK_ULONG ulCount /* attrs in search template */ +); +#endif + + +/* C_FindObjects continues a search for token and session + * objects that match a template, obtaining additional object + * handles. */ +CK_PKCS11_FUNCTION_INFO(C_FindObjects) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_OBJECT_HANDLE_PTR phObject, /* gets obj. handles */ + CK_ULONG ulMaxObjectCount, /* max handles to get */ + CK_ULONG_PTR pulObjectCount /* actual # returned */ +); +#endif + + +/* C_FindObjectsFinal finishes a search for token and session + * objects. */ +CK_PKCS11_FUNCTION_INFO(C_FindObjectsFinal) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession /* the session's handle */ +); +#endif + + + +/* Encryption and decryption */ + +/* C_EncryptInit initializes an encryption operation. */ +CK_PKCS11_FUNCTION_INFO(C_EncryptInit) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_MECHANISM_PTR pMechanism, /* the encryption mechanism */ + CK_OBJECT_HANDLE hKey /* handle of encryption key */ +); +#endif + + +/* C_Encrypt encrypts single-part data. */ +CK_PKCS11_FUNCTION_INFO(C_Encrypt) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pData, /* the plaintext data */ + CK_ULONG ulDataLen, /* bytes of plaintext */ + CK_BYTE_PTR pEncryptedData, /* gets ciphertext */ + CK_ULONG_PTR pulEncryptedDataLen /* gets c-text size */ +); +#endif + + +/* C_EncryptUpdate continues a multiple-part encryption + * operation. */ +CK_PKCS11_FUNCTION_INFO(C_EncryptUpdate) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pPart, /* the plaintext data */ + CK_ULONG ulPartLen, /* plaintext data len */ + CK_BYTE_PTR pEncryptedPart, /* gets ciphertext */ + CK_ULONG_PTR pulEncryptedPartLen /* gets c-text size */ +); +#endif + + +/* C_EncryptFinal finishes a multiple-part encryption + * operation. */ +CK_PKCS11_FUNCTION_INFO(C_EncryptFinal) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session handle */ + CK_BYTE_PTR pLastEncryptedPart, /* last c-text */ + CK_ULONG_PTR pulLastEncryptedPartLen /* gets last size */ +); +#endif + + +/* C_DecryptInit initializes a decryption operation. */ +CK_PKCS11_FUNCTION_INFO(C_DecryptInit) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_MECHANISM_PTR pMechanism, /* the decryption mechanism */ + CK_OBJECT_HANDLE hKey /* handle of decryption key */ +); +#endif + + +/* C_Decrypt decrypts encrypted data in a single part. */ +CK_PKCS11_FUNCTION_INFO(C_Decrypt) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pEncryptedData, /* ciphertext */ + CK_ULONG ulEncryptedDataLen, /* ciphertext length */ + CK_BYTE_PTR pData, /* gets plaintext */ + CK_ULONG_PTR pulDataLen /* gets p-text size */ +); +#endif + + +/* C_DecryptUpdate continues a multiple-part decryption + * operation. */ +CK_PKCS11_FUNCTION_INFO(C_DecryptUpdate) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pEncryptedPart, /* encrypted data */ + CK_ULONG ulEncryptedPartLen, /* input length */ + CK_BYTE_PTR pPart, /* gets plaintext */ + CK_ULONG_PTR pulPartLen /* p-text size */ +); +#endif + + +/* C_DecryptFinal finishes a multiple-part decryption + * operation. */ +CK_PKCS11_FUNCTION_INFO(C_DecryptFinal) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pLastPart, /* gets plaintext */ + CK_ULONG_PTR pulLastPartLen /* p-text size */ +); +#endif + + + +/* Message digesting */ + +/* C_DigestInit initializes a message-digesting operation. */ +CK_PKCS11_FUNCTION_INFO(C_DigestInit) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_MECHANISM_PTR pMechanism /* the digesting mechanism */ +); +#endif + + +/* C_Digest digests data in a single part. */ +CK_PKCS11_FUNCTION_INFO(C_Digest) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pData, /* data to be digested */ + CK_ULONG ulDataLen, /* bytes of data to digest */ + CK_BYTE_PTR pDigest, /* gets the message digest */ + CK_ULONG_PTR pulDigestLen /* gets digest length */ +); +#endif + + +/* C_DigestUpdate continues a multiple-part message-digesting + * operation. */ +CK_PKCS11_FUNCTION_INFO(C_DigestUpdate) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pPart, /* data to be digested */ + CK_ULONG ulPartLen /* bytes of data to be digested */ +); +#endif + + +/* C_DigestKey continues a multi-part message-digesting + * operation, by digesting the value of a secret key as part of + * the data already digested. */ +CK_PKCS11_FUNCTION_INFO(C_DigestKey) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_OBJECT_HANDLE hKey /* secret key to digest */ +); +#endif + + +/* C_DigestFinal finishes a multiple-part message-digesting + * operation. */ +CK_PKCS11_FUNCTION_INFO(C_DigestFinal) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pDigest, /* gets the message digest */ + CK_ULONG_PTR pulDigestLen /* gets byte count of digest */ +); +#endif + + + +/* Signing and MACing */ + +/* C_SignInit initializes a signature (private key encryption) + * operation, where the signature is (will be) an appendix to + * the data, and plaintext cannot be recovered from the + *signature. */ +CK_PKCS11_FUNCTION_INFO(C_SignInit) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_MECHANISM_PTR pMechanism, /* the signature mechanism */ + CK_OBJECT_HANDLE hKey /* handle of signature key */ +); +#endif + + +/* C_Sign signs (encrypts with private key) data in a single + * part, where the signature is (will be) an appendix to the + * data, and plaintext cannot be recovered from the signature. */ +CK_PKCS11_FUNCTION_INFO(C_Sign) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pData, /* the data to sign */ + CK_ULONG ulDataLen, /* count of bytes to sign */ + CK_BYTE_PTR pSignature, /* gets the signature */ + CK_ULONG_PTR pulSignatureLen /* gets signature length */ +); +#endif + + +/* C_SignUpdate continues a multiple-part signature operation, + * where the signature is (will be) an appendix to the data, + * and plaintext cannot be recovered from the signature. */ +CK_PKCS11_FUNCTION_INFO(C_SignUpdate) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pPart, /* the data to sign */ + CK_ULONG ulPartLen /* count of bytes to sign */ +); +#endif + + +/* C_SignFinal finishes a multiple-part signature operation, + * returning the signature. */ +CK_PKCS11_FUNCTION_INFO(C_SignFinal) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pSignature, /* gets the signature */ + CK_ULONG_PTR pulSignatureLen /* gets signature length */ +); +#endif + + +/* C_SignRecoverInit initializes a signature operation, where + * the data can be recovered from the signature. */ +CK_PKCS11_FUNCTION_INFO(C_SignRecoverInit) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_MECHANISM_PTR pMechanism, /* the signature mechanism */ + CK_OBJECT_HANDLE hKey /* handle of the signature key */ +); +#endif + + +/* C_SignRecover signs data in a single operation, where the + * data can be recovered from the signature. */ +CK_PKCS11_FUNCTION_INFO(C_SignRecover) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pData, /* the data to sign */ + CK_ULONG ulDataLen, /* count of bytes to sign */ + CK_BYTE_PTR pSignature, /* gets the signature */ + CK_ULONG_PTR pulSignatureLen /* gets signature length */ +); +#endif + + + +/* Verifying signatures and MACs */ + +/* C_VerifyInit initializes a verification operation, where the + * signature is an appendix to the data, and plaintext cannot + * cannot be recovered from the signature (e.g. DSA). */ +CK_PKCS11_FUNCTION_INFO(C_VerifyInit) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_MECHANISM_PTR pMechanism, /* the verification mechanism */ + CK_OBJECT_HANDLE hKey /* verification key */ +); +#endif + + +/* C_Verify verifies a signature in a single-part operation, + * where the signature is an appendix to the data, and plaintext + * cannot be recovered from the signature. */ +CK_PKCS11_FUNCTION_INFO(C_Verify) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pData, /* signed data */ + CK_ULONG ulDataLen, /* length of signed data */ + CK_BYTE_PTR pSignature, /* signature */ + CK_ULONG ulSignatureLen /* signature length*/ +); +#endif + + +/* C_VerifyUpdate continues a multiple-part verification + * operation, where the signature is an appendix to the data, + * and plaintext cannot be recovered from the signature. */ +CK_PKCS11_FUNCTION_INFO(C_VerifyUpdate) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pPart, /* signed data */ + CK_ULONG ulPartLen /* length of signed data */ +); +#endif + + +/* C_VerifyFinal finishes a multiple-part verification + * operation, checking the signature. */ +CK_PKCS11_FUNCTION_INFO(C_VerifyFinal) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pSignature, /* signature to verify */ + CK_ULONG ulSignatureLen /* signature length */ +); +#endif + + +/* C_VerifyRecoverInit initializes a signature verification + * operation, where the data is recovered from the signature. */ +CK_PKCS11_FUNCTION_INFO(C_VerifyRecoverInit) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_MECHANISM_PTR pMechanism, /* the verification mechanism */ + CK_OBJECT_HANDLE hKey /* verification key */ +); +#endif + + +/* C_VerifyRecover verifies a signature in a single-part + * operation, where the data is recovered from the signature. */ +CK_PKCS11_FUNCTION_INFO(C_VerifyRecover) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pSignature, /* signature to verify */ + CK_ULONG ulSignatureLen, /* signature length */ + CK_BYTE_PTR pData, /* gets signed data */ + CK_ULONG_PTR pulDataLen /* gets signed data len */ +); +#endif + + + +/* Dual-function cryptographic operations */ + +/* C_DigestEncryptUpdate continues a multiple-part digesting + * and encryption operation. */ +CK_PKCS11_FUNCTION_INFO(C_DigestEncryptUpdate) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pPart, /* the plaintext data */ + CK_ULONG ulPartLen, /* plaintext length */ + CK_BYTE_PTR pEncryptedPart, /* gets ciphertext */ + CK_ULONG_PTR pulEncryptedPartLen /* gets c-text length */ +); +#endif + + +/* C_DecryptDigestUpdate continues a multiple-part decryption and + * digesting operation. */ +CK_PKCS11_FUNCTION_INFO(C_DecryptDigestUpdate) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pEncryptedPart, /* ciphertext */ + CK_ULONG ulEncryptedPartLen, /* ciphertext length */ + CK_BYTE_PTR pPart, /* gets plaintext */ + CK_ULONG_PTR pulPartLen /* gets plaintext len */ +); +#endif + + +/* C_SignEncryptUpdate continues a multiple-part signing and + * encryption operation. */ +CK_PKCS11_FUNCTION_INFO(C_SignEncryptUpdate) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pPart, /* the plaintext data */ + CK_ULONG ulPartLen, /* plaintext length */ + CK_BYTE_PTR pEncryptedPart, /* gets ciphertext */ + CK_ULONG_PTR pulEncryptedPartLen /* gets c-text length */ +); +#endif + + +/* C_DecryptVerifyUpdate continues a multiple-part decryption and + * verify operation. */ +CK_PKCS11_FUNCTION_INFO(C_DecryptVerifyUpdate) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pEncryptedPart, /* ciphertext */ + CK_ULONG ulEncryptedPartLen, /* ciphertext length */ + CK_BYTE_PTR pPart, /* gets plaintext */ + CK_ULONG_PTR pulPartLen /* gets p-text length */ +); +#endif + + + +/* Key management */ + +/* C_GenerateKey generates a secret key, creating a new key + * object. */ +CK_PKCS11_FUNCTION_INFO(C_GenerateKey) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_MECHANISM_PTR pMechanism, /* key generation mech. */ + CK_ATTRIBUTE_PTR pTemplate, /* template for new key */ + CK_ULONG ulCount, /* # of attrs in template */ + CK_OBJECT_HANDLE_PTR phKey /* gets handle of new key */ +); +#endif + + +/* C_GenerateKeyPair generates a public-key/private-key pair, + * creating new key objects. */ +CK_PKCS11_FUNCTION_INFO(C_GenerateKeyPair) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session + * handle */ + CK_MECHANISM_PTR pMechanism, /* key-gen + * mech. */ + CK_ATTRIBUTE_PTR pPublicKeyTemplate, /* template + * for pub. + * key */ + CK_ULONG ulPublicKeyAttributeCount, /* # pub. + * attrs. */ + CK_ATTRIBUTE_PTR pPrivateKeyTemplate, /* template + * for priv. + * key */ + CK_ULONG ulPrivateKeyAttributeCount, /* # priv. + * attrs. */ + CK_OBJECT_HANDLE_PTR phPublicKey, /* gets pub. + * key + * handle */ + CK_OBJECT_HANDLE_PTR phPrivateKey /* gets + * priv. key + * handle */ +); +#endif + + +/* C_WrapKey wraps (i.e., encrypts) a key. */ +CK_PKCS11_FUNCTION_INFO(C_WrapKey) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_MECHANISM_PTR pMechanism, /* the wrapping mechanism */ + CK_OBJECT_HANDLE hWrappingKey, /* wrapping key */ + CK_OBJECT_HANDLE hKey, /* key to be wrapped */ + CK_BYTE_PTR pWrappedKey, /* gets wrapped key */ + CK_ULONG_PTR pulWrappedKeyLen /* gets wrapped key size */ +); +#endif + + +/* C_UnwrapKey unwraps (decrypts) a wrapped key, creating a new + * key object. */ +CK_PKCS11_FUNCTION_INFO(C_UnwrapKey) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_MECHANISM_PTR pMechanism, /* unwrapping mech. */ + CK_OBJECT_HANDLE hUnwrappingKey, /* unwrapping key */ + CK_BYTE_PTR pWrappedKey, /* the wrapped key */ + CK_ULONG ulWrappedKeyLen, /* wrapped key len */ + CK_ATTRIBUTE_PTR pTemplate, /* new key template */ + CK_ULONG ulAttributeCount, /* template length */ + CK_OBJECT_HANDLE_PTR phKey /* gets new handle */ +); +#endif + + +/* C_DeriveKey derives a key from a base key, creating a new key + * object. */ +CK_PKCS11_FUNCTION_INFO(C_DeriveKey) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_MECHANISM_PTR pMechanism, /* key deriv. mech. */ + CK_OBJECT_HANDLE hBaseKey, /* base key */ + CK_ATTRIBUTE_PTR pTemplate, /* new key template */ + CK_ULONG ulAttributeCount, /* template length */ + CK_OBJECT_HANDLE_PTR phKey /* gets new handle */ +); +#endif + + + +/* Random number generation */ + +/* C_SeedRandom mixes additional seed material into the token's + * random number generator. */ +CK_PKCS11_FUNCTION_INFO(C_SeedRandom) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pSeed, /* the seed material */ + CK_ULONG ulSeedLen /* length of seed material */ +); +#endif + + +/* C_GenerateRandom generates random data. */ +CK_PKCS11_FUNCTION_INFO(C_GenerateRandom) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR RandomData, /* receives the random data */ + CK_ULONG ulRandomLen /* # of bytes to generate */ +); +#endif + + + +/* Parallel function management */ + +/* C_GetFunctionStatus is a legacy function; it obtains an + * updated status of a function running in parallel with an + * application. */ +CK_PKCS11_FUNCTION_INFO(C_GetFunctionStatus) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession /* the session's handle */ +); +#endif + + +/* C_CancelFunction is a legacy function; it cancels a function + * running in parallel. */ +CK_PKCS11_FUNCTION_INFO(C_CancelFunction) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession /* the session's handle */ +); +#endif + + + +/* Functions added in for Cryptoki Version 2.01 or later */ + +/* C_WaitForSlotEvent waits for a slot event (token insertion, + * removal, etc.) to occur. */ +CK_PKCS11_FUNCTION_INFO(C_WaitForSlotEvent) +#ifdef CK_NEED_ARG_LIST +( + CK_FLAGS flags, /* blocking/nonblocking flag */ + CK_SLOT_ID_PTR pSlot, /* location that receives the slot ID */ + CK_VOID_PTR pRserved /* reserved. Should be NULL_PTR */ +); +#endif diff --git a/src/pluto/rsaref/pkcs11t.h b/src/pluto/rsaref/pkcs11t.h new file mode 100644 index 000000000..3da20b215 --- /dev/null +++ b/src/pluto/rsaref/pkcs11t.h @@ -0,0 +1,1685 @@ +/* pkcs11t.h include file for PKCS #11. */ +/* $Revision: 1.2 $ */ + +/* License to copy and use this software is granted provided that it is + * identified as "RSA Security Inc. PKCS #11 Cryptographic Token Interface + * (Cryptoki)" in all material mentioning or referencing this software. + + * License is also granted to make and use derivative works provided that + * such works are identified as "derived from the RSA Security Inc. PKCS #11 + * Cryptographic Token Interface (Cryptoki)" in all material mentioning or + * referencing the derived work. + + * RSA Security Inc. makes no representations concerning either the + * merchantability of this software or the suitability of this software for + * any particular purpose. It is provided "as is" without express or implied + * warranty of any kind. + */ + +/* See top of pkcs11.h for information about the macros that + * must be defined and the structure-packing conventions that + * must be set before including this file. */ + +#ifndef _PKCS11T_H_ +#define _PKCS11T_H_ 1 + +#define CK_TRUE 1 +#define CK_FALSE 0 + +#ifndef CK_DISABLE_TRUE_FALSE +#ifndef FALSE +#define FALSE CK_FALSE +#endif + +#ifndef TRUE +#define TRUE CK_TRUE +#endif +#endif + +/* an unsigned 8-bit value */ +typedef unsigned char CK_BYTE; + +/* an unsigned 8-bit character */ +typedef CK_BYTE CK_CHAR; + +/* an 8-bit UTF-8 character */ +typedef CK_BYTE CK_UTF8CHAR; + +/* a BYTE-sized Boolean flag */ +typedef CK_BYTE CK_BBOOL; + +/* an unsigned value, at least 32 bits long */ +typedef unsigned long int CK_ULONG; + +/* a signed value, the same size as a CK_ULONG */ +/* CK_LONG is new for v2.0 */ +typedef long int CK_LONG; + +/* at least 32 bits; each bit is a Boolean flag */ +typedef CK_ULONG CK_FLAGS; + + +/* some special values for certain CK_ULONG variables */ +#define CK_UNAVAILABLE_INFORMATION (~0UL) +#define CK_EFFECTIVELY_INFINITE 0 + + +typedef CK_BYTE CK_PTR CK_BYTE_PTR; +typedef CK_CHAR CK_PTR CK_CHAR_PTR; +typedef CK_UTF8CHAR CK_PTR CK_UTF8CHAR_PTR; +typedef CK_ULONG CK_PTR CK_ULONG_PTR; +typedef void CK_PTR CK_VOID_PTR; + +/* Pointer to a CK_VOID_PTR-- i.e., pointer to pointer to void */ +typedef CK_VOID_PTR CK_PTR CK_VOID_PTR_PTR; + + +/* The following value is always invalid if used as a session */ +/* handle or object handle */ +#define CK_INVALID_HANDLE 0 + + +typedef struct CK_VERSION { + CK_BYTE major; /* integer portion of version number */ + CK_BYTE minor; /* 1/100ths portion of version number */ +} CK_VERSION; + +typedef CK_VERSION CK_PTR CK_VERSION_PTR; + + +typedef struct CK_INFO { + /* manufacturerID and libraryDecription have been changed from + * CK_CHAR to CK_UTF8CHAR for v2.10 */ + CK_VERSION cryptokiVersion; /* Cryptoki interface ver */ + CK_UTF8CHAR manufacturerID[32]; /* blank padded */ + CK_FLAGS flags; /* must be zero */ + + /* libraryDescription and libraryVersion are new for v2.0 */ + CK_UTF8CHAR libraryDescription[32]; /* blank padded */ + CK_VERSION libraryVersion; /* version of library */ +} CK_INFO; + +typedef CK_INFO CK_PTR CK_INFO_PTR; + + +/* CK_NOTIFICATION enumerates the types of notifications that + * Cryptoki provides to an application */ +/* CK_NOTIFICATION has been changed from an enum to a CK_ULONG + * for v2.0 */ +typedef CK_ULONG CK_NOTIFICATION; +#define CKN_SURRENDER 0 + + +typedef CK_ULONG CK_SLOT_ID; + +typedef CK_SLOT_ID CK_PTR CK_SLOT_ID_PTR; + + +/* CK_SLOT_INFO provides information about a slot */ +typedef struct CK_SLOT_INFO { + /* slotDescription and manufacturerID have been changed from + * CK_CHAR to CK_UTF8CHAR for v2.10 */ + CK_UTF8CHAR slotDescription[64]; /* blank padded */ + CK_UTF8CHAR manufacturerID[32]; /* blank padded */ + CK_FLAGS flags; + + /* hardwareVersion and firmwareVersion are new for v2.0 */ + CK_VERSION hardwareVersion; /* version of hardware */ + CK_VERSION firmwareVersion; /* version of firmware */ +} CK_SLOT_INFO; + +/* flags: bit flags that provide capabilities of the slot + * Bit Flag Mask Meaning + */ +#define CKF_TOKEN_PRESENT 0x00000001 /* a token is there */ +#define CKF_REMOVABLE_DEVICE 0x00000002 /* removable devices*/ +#define CKF_HW_SLOT 0x00000004 /* hardware slot */ + +typedef CK_SLOT_INFO CK_PTR CK_SLOT_INFO_PTR; + + +/* CK_TOKEN_INFO provides information about a token */ +typedef struct CK_TOKEN_INFO { + /* label, manufacturerID, and model have been changed from + * CK_CHAR to CK_UTF8CHAR for v2.10 */ + CK_UTF8CHAR label[32]; /* blank padded */ + CK_UTF8CHAR manufacturerID[32]; /* blank padded */ + CK_UTF8CHAR model[16]; /* blank padded */ + CK_CHAR serialNumber[16]; /* blank padded */ + CK_FLAGS flags; /* see below */ + + /* ulMaxSessionCount, ulSessionCount, ulMaxRwSessionCount, + * ulRwSessionCount, ulMaxPinLen, and ulMinPinLen have all been + * changed from CK_USHORT to CK_ULONG for v2.0 */ + CK_ULONG ulMaxSessionCount; /* max open sessions */ + CK_ULONG ulSessionCount; /* sess. now open */ + CK_ULONG ulMaxRwSessionCount; /* max R/W sessions */ + CK_ULONG ulRwSessionCount; /* R/W sess. now open */ + CK_ULONG ulMaxPinLen; /* in bytes */ + CK_ULONG ulMinPinLen; /* in bytes */ + CK_ULONG ulTotalPublicMemory; /* in bytes */ + CK_ULONG ulFreePublicMemory; /* in bytes */ + CK_ULONG ulTotalPrivateMemory; /* in bytes */ + CK_ULONG ulFreePrivateMemory; /* in bytes */ + + /* hardwareVersion, firmwareVersion, and time are new for + * v2.0 */ + CK_VERSION hardwareVersion; /* version of hardware */ + CK_VERSION firmwareVersion; /* version of firmware */ + CK_CHAR utcTime[16]; /* time */ +} CK_TOKEN_INFO; + +/* The flags parameter is defined as follows: + * Bit Flag Mask Meaning + */ +#define CKF_RNG 0x00000001 /* has random # + * generator */ +#define CKF_WRITE_PROTECTED 0x00000002 /* token is + * write- + * protected */ +#define CKF_LOGIN_REQUIRED 0x00000004 /* user must + * login */ +#define CKF_USER_PIN_INITIALIZED 0x00000008 /* normal user's + * PIN is set */ + +/* CKF_RESTORE_KEY_NOT_NEEDED is new for v2.0. If it is set, + * that means that *every* time the state of cryptographic + * operations of a session is successfully saved, all keys + * needed to continue those operations are stored in the state */ +#define CKF_RESTORE_KEY_NOT_NEEDED 0x00000020 + +/* CKF_CLOCK_ON_TOKEN is new for v2.0. If it is set, that means + * that the token has some sort of clock. The time on that + * clock is returned in the token info structure */ +#define CKF_CLOCK_ON_TOKEN 0x00000040 + +/* CKF_PROTECTED_AUTHENTICATION_PATH is new for v2.0. If it is + * set, that means that there is some way for the user to login + * without sending a PIN through the Cryptoki library itself */ +#define CKF_PROTECTED_AUTHENTICATION_PATH 0x00000100 + +/* CKF_DUAL_CRYPTO_OPERATIONS is new for v2.0. If it is true, + * that means that a single session with the token can perform + * dual simultaneous cryptographic operations (digest and + * encrypt; decrypt and digest; sign and encrypt; and decrypt + * and sign) */ +#define CKF_DUAL_CRYPTO_OPERATIONS 0x00000200 + +/* CKF_TOKEN_INITIALIZED if new for v2.10. If it is true, the + * token has been initialized using C_InitializeToken or an + * equivalent mechanism outside the scope of PKCS #11. + * Calling C_InitializeToken when this flag is set will cause + * the token to be reinitialized. */ +#define CKF_TOKEN_INITIALIZED 0x00000400 + +/* CKF_SECONDARY_AUTHENTICATION if new for v2.10. If it is + * true, the token supports secondary authentication for + * private key objects. This flag is deprecated in v2.11 and + onwards. */ +#define CKF_SECONDARY_AUTHENTICATION 0x00000800 + +/* CKF_USER_PIN_COUNT_LOW if new for v2.10. If it is true, an + * incorrect user login PIN has been entered at least once + * since the last successful authentication. */ +#define CKF_USER_PIN_COUNT_LOW 0x00010000 + +/* CKF_USER_PIN_FINAL_TRY if new for v2.10. If it is true, + * supplying an incorrect user PIN will it to become locked. */ +#define CKF_USER_PIN_FINAL_TRY 0x00020000 + +/* CKF_USER_PIN_LOCKED if new for v2.10. If it is true, the + * user PIN has been locked. User login to the token is not + * possible. */ +#define CKF_USER_PIN_LOCKED 0x00040000 + +/* CKF_USER_PIN_TO_BE_CHANGED if new for v2.10. If it is true, + * the user PIN value is the default value set by token + * initialization or manufacturing, or the PIN has been + * expired by the card. */ +#define CKF_USER_PIN_TO_BE_CHANGED 0x00080000 + +/* CKF_SO_PIN_COUNT_LOW if new for v2.10. If it is true, an + * incorrect SO login PIN has been entered at least once since + * the last successful authentication. */ +#define CKF_SO_PIN_COUNT_LOW 0x00100000 + +/* CKF_SO_PIN_FINAL_TRY if new for v2.10. If it is true, + * supplying an incorrect SO PIN will it to become locked. */ +#define CKF_SO_PIN_FINAL_TRY 0x00200000 + +/* CKF_SO_PIN_LOCKED if new for v2.10. If it is true, the SO + * PIN has been locked. SO login to the token is not possible. + */ +#define CKF_SO_PIN_LOCKED 0x00400000 + +/* CKF_SO_PIN_TO_BE_CHANGED if new for v2.10. If it is true, + * the SO PIN value is the default value set by token + * initialization or manufacturing, or the PIN has been + * expired by the card. */ +#define CKF_SO_PIN_TO_BE_CHANGED 0x00800000 + +typedef CK_TOKEN_INFO CK_PTR CK_TOKEN_INFO_PTR; + + +/* CK_SESSION_HANDLE is a Cryptoki-assigned value that + * identifies a session */ +typedef CK_ULONG CK_SESSION_HANDLE; + +typedef CK_SESSION_HANDLE CK_PTR CK_SESSION_HANDLE_PTR; + + +/* CK_USER_TYPE enumerates the types of Cryptoki users */ +/* CK_USER_TYPE has been changed from an enum to a CK_ULONG for + * v2.0 */ +typedef CK_ULONG CK_USER_TYPE; +/* Security Officer */ +#define CKU_SO 0 +/* Normal user */ +#define CKU_USER 1 +/* Context specific (added in v2.20) */ +#define CKU_CONTEXT_SPECIFIC 2 + +/* CK_STATE enumerates the session states */ +/* CK_STATE has been changed from an enum to a CK_ULONG for + * v2.0 */ +typedef CK_ULONG CK_STATE; +#define CKS_RO_PUBLIC_SESSION 0 +#define CKS_RO_USER_FUNCTIONS 1 +#define CKS_RW_PUBLIC_SESSION 2 +#define CKS_RW_USER_FUNCTIONS 3 +#define CKS_RW_SO_FUNCTIONS 4 + + +/* CK_SESSION_INFO provides information about a session */ +typedef struct CK_SESSION_INFO { + CK_SLOT_ID slotID; + CK_STATE state; + CK_FLAGS flags; /* see below */ + + /* ulDeviceError was changed from CK_USHORT to CK_ULONG for + * v2.0 */ + CK_ULONG ulDeviceError; /* device-dependent error code */ +} CK_SESSION_INFO; + +/* The flags are defined in the following table: + * Bit Flag Mask Meaning + */ +#define CKF_RW_SESSION 0x00000002 /* session is r/w */ +#define CKF_SERIAL_SESSION 0x00000004 /* no parallel */ + +typedef CK_SESSION_INFO CK_PTR CK_SESSION_INFO_PTR; + + +/* CK_OBJECT_HANDLE is a token-specific identifier for an + * object */ +typedef CK_ULONG CK_OBJECT_HANDLE; + +typedef CK_OBJECT_HANDLE CK_PTR CK_OBJECT_HANDLE_PTR; + + +/* CK_OBJECT_CLASS is a value that identifies the classes (or + * types) of objects that Cryptoki recognizes. It is defined + * as follows: */ +/* CK_OBJECT_CLASS was changed from CK_USHORT to CK_ULONG for + * v2.0 */ +typedef CK_ULONG CK_OBJECT_CLASS; + +/* The following classes of objects are defined: */ +/* CKO_HW_FEATURE is new for v2.10 */ +/* CKO_DOMAIN_PARAMETERS is new for v2.11 */ +/* CKO_MECHANISM is new for v2.20 */ +#define CKO_DATA 0x00000000 +#define CKO_CERTIFICATE 0x00000001 +#define CKO_PUBLIC_KEY 0x00000002 +#define CKO_PRIVATE_KEY 0x00000003 +#define CKO_SECRET_KEY 0x00000004 +#define CKO_HW_FEATURE 0x00000005 +#define CKO_DOMAIN_PARAMETERS 0x00000006 +#define CKO_MECHANISM 0x00000007 +#define CKO_VENDOR_DEFINED 0x80000000 + +typedef CK_OBJECT_CLASS CK_PTR CK_OBJECT_CLASS_PTR; + +/* CK_HW_FEATURE_TYPE is new for v2.10. CK_HW_FEATURE_TYPE is a + * value that identifies the hardware feature type of an object + * with CK_OBJECT_CLASS equal to CKO_HW_FEATURE. */ +typedef CK_ULONG CK_HW_FEATURE_TYPE; + +/* The following hardware feature types are defined */ +/* CKH_USER_INTERFACE is new for v2.20 */ +#define CKH_MONOTONIC_COUNTER 0x00000001 +#define CKH_CLOCK 0x00000002 +#define CKH_USER_INTERFACE 0x00000003 +#define CKH_VENDOR_DEFINED 0x80000000 + +/* CK_KEY_TYPE is a value that identifies a key type */ +/* CK_KEY_TYPE was changed from CK_USHORT to CK_ULONG for v2.0 */ +typedef CK_ULONG CK_KEY_TYPE; + +/* the following key types are defined: */ +#define CKK_RSA 0x00000000 +#define CKK_DSA 0x00000001 +#define CKK_DH 0x00000002 + +/* CKK_ECDSA and CKK_KEA are new for v2.0 */ +/* CKK_ECDSA is deprecated in v2.11, CKK_EC is preferred. */ +#define CKK_ECDSA 0x00000003 +#define CKK_EC 0x00000003 +#define CKK_X9_42_DH 0x00000004 +#define CKK_KEA 0x00000005 + +#define CKK_GENERIC_SECRET 0x00000010 +#define CKK_RC2 0x00000011 +#define CKK_RC4 0x00000012 +#define CKK_DES 0x00000013 +#define CKK_DES2 0x00000014 +#define CKK_DES3 0x00000015 + +/* all these key types are new for v2.0 */ +#define CKK_CAST 0x00000016 +#define CKK_CAST3 0x00000017 +/* CKK_CAST5 is deprecated in v2.11, CKK_CAST128 is preferred. */ +#define CKK_CAST5 0x00000018 +#define CKK_CAST128 0x00000018 +#define CKK_RC5 0x00000019 +#define CKK_IDEA 0x0000001A +#define CKK_SKIPJACK 0x0000001B +#define CKK_BATON 0x0000001C +#define CKK_JUNIPER 0x0000001D +#define CKK_CDMF 0x0000001E +#define CKK_AES 0x0000001F + +/* BlowFish and TwoFish are new for v2.20 */ +#define CKK_BLOWFISH 0x00000020 +#define CKK_TWOFISH 0x00000021 + +#define CKK_VENDOR_DEFINED 0x80000000 + + +/* CK_CERTIFICATE_TYPE is a value that identifies a certificate + * type */ +/* CK_CERTIFICATE_TYPE was changed from CK_USHORT to CK_ULONG + * for v2.0 */ +typedef CK_ULONG CK_CERTIFICATE_TYPE; + +/* The following certificate types are defined: */ +/* CKC_X_509_ATTR_CERT is new for v2.10 */ +/* CKC_WTLS is new for v2.20 */ +#define CKC_X_509 0x00000000 +#define CKC_X_509_ATTR_CERT 0x00000001 +#define CKC_WTLS 0x00000002 +#define CKC_VENDOR_DEFINED 0x80000000 + + +/* CK_ATTRIBUTE_TYPE is a value that identifies an attribute + * type */ +/* CK_ATTRIBUTE_TYPE was changed from CK_USHORT to CK_ULONG for + * v2.0 */ +typedef CK_ULONG CK_ATTRIBUTE_TYPE; + +/* The CKF_ARRAY_ATTRIBUTE flag identifies an attribute which + consists of an array of values. */ +#define CKF_ARRAY_ATTRIBUTE 0x40000000 + +/* The following attribute types are defined: */ +#define CKA_CLASS 0x00000000 +#define CKA_TOKEN 0x00000001 +#define CKA_PRIVATE 0x00000002 +#define CKA_LABEL 0x00000003 +#define CKA_APPLICATION 0x00000010 +#define CKA_VALUE 0x00000011 + +/* CKA_OBJECT_ID is new for v2.10 */ +#define CKA_OBJECT_ID 0x00000012 + +#define CKA_CERTIFICATE_TYPE 0x00000080 +#define CKA_ISSUER 0x00000081 +#define CKA_SERIAL_NUMBER 0x00000082 + +/* CKA_AC_ISSUER, CKA_OWNER, and CKA_ATTR_TYPES are new + * for v2.10 */ +#define CKA_AC_ISSUER 0x00000083 +#define CKA_OWNER 0x00000084 +#define CKA_ATTR_TYPES 0x00000085 + +/* CKA_TRUSTED is new for v2.11 */ +#define CKA_TRUSTED 0x00000086 + +/* CKA_CERTIFICATE_CATEGORY ... + * CKA_CHECK_VALUE are new for v2.20 */ +#define CKA_CERTIFICATE_CATEGORY 0x00000087 +#define CKA_JAVA_MIDP_SECURITY_DOMAIN 0x00000088 +#define CKA_URL 0x00000089 +#define CKA_HASH_OF_SUBJECT_PUBLIC_KEY 0x0000008A +#define CKA_HASH_OF_ISSUER_PUBLIC_KEY 0x0000008B +#define CKA_CHECK_VALUE 0x00000090 + +#define CKA_KEY_TYPE 0x00000100 +#define CKA_SUBJECT 0x00000101 +#define CKA_ID 0x00000102 +#define CKA_SENSITIVE 0x00000103 +#define CKA_ENCRYPT 0x00000104 +#define CKA_DECRYPT 0x00000105 +#define CKA_WRAP 0x00000106 +#define CKA_UNWRAP 0x00000107 +#define CKA_SIGN 0x00000108 +#define CKA_SIGN_RECOVER 0x00000109 +#define CKA_VERIFY 0x0000010A +#define CKA_VERIFY_RECOVER 0x0000010B +#define CKA_DERIVE 0x0000010C +#define CKA_START_DATE 0x00000110 +#define CKA_END_DATE 0x00000111 +#define CKA_MODULUS 0x00000120 +#define CKA_MODULUS_BITS 0x00000121 +#define CKA_PUBLIC_EXPONENT 0x00000122 +#define CKA_PRIVATE_EXPONENT 0x00000123 +#define CKA_PRIME_1 0x00000124 +#define CKA_PRIME_2 0x00000125 +#define CKA_EXPONENT_1 0x00000126 +#define CKA_EXPONENT_2 0x00000127 +#define CKA_COEFFICIENT 0x00000128 +#define CKA_PRIME 0x00000130 +#define CKA_SUBPRIME 0x00000131 +#define CKA_BASE 0x00000132 + +/* CKA_PRIME_BITS and CKA_SUB_PRIME_BITS are new for v2.11 */ +#define CKA_PRIME_BITS 0x00000133 +#define CKA_SUBPRIME_BITS 0x00000134 +#define CKA_SUB_PRIME_BITS CKA_SUBPRIME_BITS +/* (To retain backwards-compatibility) */ + +#define CKA_VALUE_BITS 0x00000160 +#define CKA_VALUE_LEN 0x00000161 + +/* CKA_EXTRACTABLE, CKA_LOCAL, CKA_NEVER_EXTRACTABLE, + * CKA_ALWAYS_SENSITIVE, CKA_MODIFIABLE, CKA_ECDSA_PARAMS, + * and CKA_EC_POINT are new for v2.0 */ +#define CKA_EXTRACTABLE 0x00000162 +#define CKA_LOCAL 0x00000163 +#define CKA_NEVER_EXTRACTABLE 0x00000164 +#define CKA_ALWAYS_SENSITIVE 0x00000165 + +/* CKA_KEY_GEN_MECHANISM is new for v2.11 */ +#define CKA_KEY_GEN_MECHANISM 0x00000166 + +#define CKA_MODIFIABLE 0x00000170 + +/* CKA_ECDSA_PARAMS is deprecated in v2.11, + * CKA_EC_PARAMS is preferred. */ +#define CKA_ECDSA_PARAMS 0x00000180 +#define CKA_EC_PARAMS 0x00000180 + +#define CKA_EC_POINT 0x00000181 + +/* CKA_SECONDARY_AUTH, CKA_AUTH_PIN_FLAGS, + * are new for v2.10. Deprecated in v2.11 and onwards. */ +#define CKA_SECONDARY_AUTH 0x00000200 +#define CKA_AUTH_PIN_FLAGS 0x00000201 + +/* CKA_ALWAYS_AUTHENTICATE ... + * CKA_UNWRAP_TEMPLATE are new for v2.20 */ +#define CKA_ALWAYS_AUTHENTICATE 0x00000202 + +#define CKA_WRAP_WITH_TRUSTED 0x00000210 +#define CKA_WRAP_TEMPLATE (CKF_ARRAY_ATTRIBUTE|0x00000211) +#define CKA_UNWRAP_TEMPLATE (CKF_ARRAY_ATTRIBUTE|0x00000212) + +/* CKA_HW_FEATURE_TYPE, CKA_RESET_ON_INIT, and CKA_HAS_RESET + * are new for v2.10 */ +#define CKA_HW_FEATURE_TYPE 0x00000300 +#define CKA_RESET_ON_INIT 0x00000301 +#define CKA_HAS_RESET 0x00000302 + +/* The following attributes are new for v2.20 */ +#define CKA_PIXEL_X 0x00000400 +#define CKA_PIXEL_Y 0x00000401 +#define CKA_RESOLUTION 0x00000402 +#define CKA_CHAR_ROWS 0x00000403 +#define CKA_CHAR_COLUMNS 0x00000404 +#define CKA_COLOR 0x00000405 +#define CKA_BITS_PER_PIXEL 0x00000406 +#define CKA_CHAR_SETS 0x00000480 +#define CKA_ENCODING_METHODS 0x00000481 +#define CKA_MIME_TYPES 0x00000482 +#define CKA_MECHANISM_TYPE 0x00000500 +#define CKA_REQUIRED_CMS_ATTRIBUTES 0x00000501 +#define CKA_DEFAULT_CMS_ATTRIBUTES 0x00000502 +#define CKA_SUPPORTED_CMS_ATTRIBUTES 0x00000503 +#define CKA_ALLOWED_MECHANISMS (CKF_ARRAY_ATTRIBUTE|0x00000600) + +#define CKA_VENDOR_DEFINED 0x80000000 + + +/* CK_ATTRIBUTE is a structure that includes the type, length + * and value of an attribute */ +typedef struct CK_ATTRIBUTE { + CK_ATTRIBUTE_TYPE type; + CK_VOID_PTR pValue; + + /* ulValueLen went from CK_USHORT to CK_ULONG for v2.0 */ + CK_ULONG ulValueLen; /* in bytes */ +} CK_ATTRIBUTE; + +typedef CK_ATTRIBUTE CK_PTR CK_ATTRIBUTE_PTR; + + +/* CK_DATE is a structure that defines a date */ +typedef struct CK_DATE{ + CK_CHAR year[4]; /* the year ("1900" - "9999") */ + CK_CHAR month[2]; /* the month ("01" - "12") */ + CK_CHAR day[2]; /* the day ("01" - "31") */ +} CK_DATE; + + +/* CK_MECHANISM_TYPE is a value that identifies a mechanism + * type */ +/* CK_MECHANISM_TYPE was changed from CK_USHORT to CK_ULONG for + * v2.0 */ +typedef CK_ULONG CK_MECHANISM_TYPE; + +/* the following mechanism types are defined: */ +#define CKM_RSA_PKCS_KEY_PAIR_GEN 0x00000000 +#define CKM_RSA_PKCS 0x00000001 +#define CKM_RSA_9796 0x00000002 +#define CKM_RSA_X_509 0x00000003 + +/* CKM_MD2_RSA_PKCS, CKM_MD5_RSA_PKCS, and CKM_SHA1_RSA_PKCS + * are new for v2.0. They are mechanisms which hash and sign */ +#define CKM_MD2_RSA_PKCS 0x00000004 +#define CKM_MD5_RSA_PKCS 0x00000005 +#define CKM_SHA1_RSA_PKCS 0x00000006 + +/* CKM_RIPEMD128_RSA_PKCS, CKM_RIPEMD160_RSA_PKCS, and + * CKM_RSA_PKCS_OAEP are new for v2.10 */ +#define CKM_RIPEMD128_RSA_PKCS 0x00000007 +#define CKM_RIPEMD160_RSA_PKCS 0x00000008 +#define CKM_RSA_PKCS_OAEP 0x00000009 + +/* CKM_RSA_X9_31_KEY_PAIR_GEN, CKM_RSA_X9_31, CKM_SHA1_RSA_X9_31, + * CKM_RSA_PKCS_PSS, and CKM_SHA1_RSA_PKCS_PSS are new for v2.11 */ +#define CKM_RSA_X9_31_KEY_PAIR_GEN 0x0000000A +#define CKM_RSA_X9_31 0x0000000B +#define CKM_SHA1_RSA_X9_31 0x0000000C +#define CKM_RSA_PKCS_PSS 0x0000000D +#define CKM_SHA1_RSA_PKCS_PSS 0x0000000E + +#define CKM_DSA_KEY_PAIR_GEN 0x00000010 +#define CKM_DSA 0x00000011 +#define CKM_DSA_SHA1 0x00000012 +#define CKM_DH_PKCS_KEY_PAIR_GEN 0x00000020 +#define CKM_DH_PKCS_DERIVE 0x00000021 + +/* CKM_X9_42_DH_KEY_PAIR_GEN, CKM_X9_42_DH_DERIVE, + * CKM_X9_42_DH_HYBRID_DERIVE, and CKM_X9_42_MQV_DERIVE are new for + * v2.11 */ +#define CKM_X9_42_DH_KEY_PAIR_GEN 0x00000030 +#define CKM_X9_42_DH_DERIVE 0x00000031 +#define CKM_X9_42_DH_HYBRID_DERIVE 0x00000032 +#define CKM_X9_42_MQV_DERIVE 0x00000033 + +/* CKM_SHA256/384/512 are new for v2.20 */ +#define CKM_SHA256_RSA_PKCS 0x00000040 +#define CKM_SHA384_RSA_PKCS 0x00000041 +#define CKM_SHA512_RSA_PKCS 0x00000042 +#define CKM_SHA256_RSA_PKCS_PSS 0x00000043 +#define CKM_SHA384_RSA_PKCS_PSS 0x00000044 +#define CKM_SHA512_RSA_PKCS_PSS 0x00000045 + +#define CKM_RC2_KEY_GEN 0x00000100 +#define CKM_RC2_ECB 0x00000101 +#define CKM_RC2_CBC 0x00000102 +#define CKM_RC2_MAC 0x00000103 + +/* CKM_RC2_MAC_GENERAL and CKM_RC2_CBC_PAD are new for v2.0 */ +#define CKM_RC2_MAC_GENERAL 0x00000104 +#define CKM_RC2_CBC_PAD 0x00000105 + +#define CKM_RC4_KEY_GEN 0x00000110 +#define CKM_RC4 0x00000111 +#define CKM_DES_KEY_GEN 0x00000120 +#define CKM_DES_ECB 0x00000121 +#define CKM_DES_CBC 0x00000122 +#define CKM_DES_MAC 0x00000123 + +/* CKM_DES_MAC_GENERAL and CKM_DES_CBC_PAD are new for v2.0 */ +#define CKM_DES_MAC_GENERAL 0x00000124 +#define CKM_DES_CBC_PAD 0x00000125 + +#define CKM_DES2_KEY_GEN 0x00000130 +#define CKM_DES3_KEY_GEN 0x00000131 +#define CKM_DES3_ECB 0x00000132 +#define CKM_DES3_CBC 0x00000133 +#define CKM_DES3_MAC 0x00000134 + +/* CKM_DES3_MAC_GENERAL, CKM_DES3_CBC_PAD, CKM_CDMF_KEY_GEN, + * CKM_CDMF_ECB, CKM_CDMF_CBC, CKM_CDMF_MAC, + * CKM_CDMF_MAC_GENERAL, and CKM_CDMF_CBC_PAD are new for v2.0 */ +#define CKM_DES3_MAC_GENERAL 0x00000135 +#define CKM_DES3_CBC_PAD 0x00000136 +#define CKM_CDMF_KEY_GEN 0x00000140 +#define CKM_CDMF_ECB 0x00000141 +#define CKM_CDMF_CBC 0x00000142 +#define CKM_CDMF_MAC 0x00000143 +#define CKM_CDMF_MAC_GENERAL 0x00000144 +#define CKM_CDMF_CBC_PAD 0x00000145 + +/* the following four DES mechanisms are new for v2.20 */ +#define CKM_DES_OFB64 0x00000150 +#define CKM_DES_OFB8 0x00000151 +#define CKM_DES_CFB64 0x00000152 +#define CKM_DES_CFB8 0x00000153 + +#define CKM_MD2 0x00000200 + +/* CKM_MD2_HMAC and CKM_MD2_HMAC_GENERAL are new for v2.0 */ +#define CKM_MD2_HMAC 0x00000201 +#define CKM_MD2_HMAC_GENERAL 0x00000202 + +#define CKM_MD5 0x00000210 + +/* CKM_MD5_HMAC and CKM_MD5_HMAC_GENERAL are new for v2.0 */ +#define CKM_MD5_HMAC 0x00000211 +#define CKM_MD5_HMAC_GENERAL 0x00000212 + +#define CKM_SHA_1 0x00000220 + +/* CKM_SHA_1_HMAC and CKM_SHA_1_HMAC_GENERAL are new for v2.0 */ +#define CKM_SHA_1_HMAC 0x00000221 +#define CKM_SHA_1_HMAC_GENERAL 0x00000222 + +/* CKM_RIPEMD128, CKM_RIPEMD128_HMAC, + * CKM_RIPEMD128_HMAC_GENERAL, CKM_RIPEMD160, CKM_RIPEMD160_HMAC, + * and CKM_RIPEMD160_HMAC_GENERAL are new for v2.10 */ +#define CKM_RIPEMD128 0x00000230 +#define CKM_RIPEMD128_HMAC 0x00000231 +#define CKM_RIPEMD128_HMAC_GENERAL 0x00000232 +#define CKM_RIPEMD160 0x00000240 +#define CKM_RIPEMD160_HMAC 0x00000241 +#define CKM_RIPEMD160_HMAC_GENERAL 0x00000242 + +/* CKM_SHA256/384/512 are new for v2.20 */ +#define CKM_SHA256 0x00000250 +#define CKM_SHA256_HMAC 0x00000251 +#define CKM_SHA256_HMAC_GENERAL 0x00000252 +#define CKM_SHA384 0x00000260 +#define CKM_SHA384_HMAC 0x00000261 +#define CKM_SHA384_HMAC_GENERAL 0x00000262 +#define CKM_SHA512 0x00000270 +#define CKM_SHA512_HMAC 0x00000271 +#define CKM_SHA512_HMAC_GENERAL 0x00000272 + +/* All of the following mechanisms are new for v2.0 */ +/* Note that CAST128 and CAST5 are the same algorithm */ +#define CKM_CAST_KEY_GEN 0x00000300 +#define CKM_CAST_ECB 0x00000301 +#define CKM_CAST_CBC 0x00000302 +#define CKM_CAST_MAC 0x00000303 +#define CKM_CAST_MAC_GENERAL 0x00000304 +#define CKM_CAST_CBC_PAD 0x00000305 +#define CKM_CAST3_KEY_GEN 0x00000310 +#define CKM_CAST3_ECB 0x00000311 +#define CKM_CAST3_CBC 0x00000312 +#define CKM_CAST3_MAC 0x00000313 +#define CKM_CAST3_MAC_GENERAL 0x00000314 +#define CKM_CAST3_CBC_PAD 0x00000315 +#define CKM_CAST5_KEY_GEN 0x00000320 +#define CKM_CAST128_KEY_GEN 0x00000320 +#define CKM_CAST5_ECB 0x00000321 +#define CKM_CAST128_ECB 0x00000321 +#define CKM_CAST5_CBC 0x00000322 +#define CKM_CAST128_CBC 0x00000322 +#define CKM_CAST5_MAC 0x00000323 +#define CKM_CAST128_MAC 0x00000323 +#define CKM_CAST5_MAC_GENERAL 0x00000324 +#define CKM_CAST128_MAC_GENERAL 0x00000324 +#define CKM_CAST5_CBC_PAD 0x00000325 +#define CKM_CAST128_CBC_PAD 0x00000325 +#define CKM_RC5_KEY_GEN 0x00000330 +#define CKM_RC5_ECB 0x00000331 +#define CKM_RC5_CBC 0x00000332 +#define CKM_RC5_MAC 0x00000333 +#define CKM_RC5_MAC_GENERAL 0x00000334 +#define CKM_RC5_CBC_PAD 0x00000335 +#define CKM_IDEA_KEY_GEN 0x00000340 +#define CKM_IDEA_ECB 0x00000341 +#define CKM_IDEA_CBC 0x00000342 +#define CKM_IDEA_MAC 0x00000343 +#define CKM_IDEA_MAC_GENERAL 0x00000344 +#define CKM_IDEA_CBC_PAD 0x00000345 +#define CKM_GENERIC_SECRET_KEY_GEN 0x00000350 +#define CKM_CONCATENATE_BASE_AND_KEY 0x00000360 +#define CKM_CONCATENATE_BASE_AND_DATA 0x00000362 +#define CKM_CONCATENATE_DATA_AND_BASE 0x00000363 +#define CKM_XOR_BASE_AND_DATA 0x00000364 +#define CKM_EXTRACT_KEY_FROM_KEY 0x00000365 +#define CKM_SSL3_PRE_MASTER_KEY_GEN 0x00000370 +#define CKM_SSL3_MASTER_KEY_DERIVE 0x00000371 +#define CKM_SSL3_KEY_AND_MAC_DERIVE 0x00000372 + +/* CKM_SSL3_MASTER_KEY_DERIVE_DH, CKM_TLS_PRE_MASTER_KEY_GEN, + * CKM_TLS_MASTER_KEY_DERIVE, CKM_TLS_KEY_AND_MAC_DERIVE, and + * CKM_TLS_MASTER_KEY_DERIVE_DH are new for v2.11 */ +#define CKM_SSL3_MASTER_KEY_DERIVE_DH 0x00000373 +#define CKM_TLS_PRE_MASTER_KEY_GEN 0x00000374 +#define CKM_TLS_MASTER_KEY_DERIVE 0x00000375 +#define CKM_TLS_KEY_AND_MAC_DERIVE 0x00000376 +#define CKM_TLS_MASTER_KEY_DERIVE_DH 0x00000377 + +/* CKM_TLS_PRF is new for v2.20 */ +#define CKM_TLS_PRF 0x00000378 + +#define CKM_SSL3_MD5_MAC 0x00000380 +#define CKM_SSL3_SHA1_MAC 0x00000381 +#define CKM_MD5_KEY_DERIVATION 0x00000390 +#define CKM_MD2_KEY_DERIVATION 0x00000391 +#define CKM_SHA1_KEY_DERIVATION 0x00000392 + +/* CKM_SHA256/384/512 are new for v2.20 */ +#define CKM_SHA256_KEY_DERIVATION 0x00000393 +#define CKM_SHA384_KEY_DERIVATION 0x00000394 +#define CKM_SHA512_KEY_DERIVATION 0x00000395 + +#define CKM_PBE_MD2_DES_CBC 0x000003A0 +#define CKM_PBE_MD5_DES_CBC 0x000003A1 +#define CKM_PBE_MD5_CAST_CBC 0x000003A2 +#define CKM_PBE_MD5_CAST3_CBC 0x000003A3 +#define CKM_PBE_MD5_CAST5_CBC 0x000003A4 +#define CKM_PBE_MD5_CAST128_CBC 0x000003A4 +#define CKM_PBE_SHA1_CAST5_CBC 0x000003A5 +#define CKM_PBE_SHA1_CAST128_CBC 0x000003A5 +#define CKM_PBE_SHA1_RC4_128 0x000003A6 +#define CKM_PBE_SHA1_RC4_40 0x000003A7 +#define CKM_PBE_SHA1_DES3_EDE_CBC 0x000003A8 +#define CKM_PBE_SHA1_DES2_EDE_CBC 0x000003A9 +#define CKM_PBE_SHA1_RC2_128_CBC 0x000003AA +#define CKM_PBE_SHA1_RC2_40_CBC 0x000003AB + +/* CKM_PKCS5_PBKD2 is new for v2.10 */ +#define CKM_PKCS5_PBKD2 0x000003B0 + +#define CKM_PBA_SHA1_WITH_SHA1_HMAC 0x000003C0 + +/* WTLS mechanisms are new for v2.20 */ +#define CKM_WTLS_PRE_MASTER_KEY_GEN 0x000003D0 +#define CKM_WTLS_MASTER_KEY_DERIVE 0x000003D1 +#define CKM_WTLS_MASTER_KEY_DERIVE_DH_ECC 0x000003D2 +#define CKM_WTLS_PRF 0x000003D3 +#define CKM_WTLS_SERVER_KEY_AND_MAC_DERIVE 0x000003D4 +#define CKM_WTLS_CLIENT_KEY_AND_MAC_DERIVE 0x000003D5 + +#define CKM_KEY_WRAP_LYNKS 0x00000400 +#define CKM_KEY_WRAP_SET_OAEP 0x00000401 + +/* CKM_CMS_SIG is new for v2.20 */ +#define CKM_CMS_SIG 0x00000500 + +/* Fortezza mechanisms */ +#define CKM_SKIPJACK_KEY_GEN 0x00001000 +#define CKM_SKIPJACK_ECB64 0x00001001 +#define CKM_SKIPJACK_CBC64 0x00001002 +#define CKM_SKIPJACK_OFB64 0x00001003 +#define CKM_SKIPJACK_CFB64 0x00001004 +#define CKM_SKIPJACK_CFB32 0x00001005 +#define CKM_SKIPJACK_CFB16 0x00001006 +#define CKM_SKIPJACK_CFB8 0x00001007 +#define CKM_SKIPJACK_WRAP 0x00001008 +#define CKM_SKIPJACK_PRIVATE_WRAP 0x00001009 +#define CKM_SKIPJACK_RELAYX 0x0000100a +#define CKM_KEA_KEY_PAIR_GEN 0x00001010 +#define CKM_KEA_KEY_DERIVE 0x00001011 +#define CKM_FORTEZZA_TIMESTAMP 0x00001020 +#define CKM_BATON_KEY_GEN 0x00001030 +#define CKM_BATON_ECB128 0x00001031 +#define CKM_BATON_ECB96 0x00001032 +#define CKM_BATON_CBC128 0x00001033 +#define CKM_BATON_COUNTER 0x00001034 +#define CKM_BATON_SHUFFLE 0x00001035 +#define CKM_BATON_WRAP 0x00001036 + +/* CKM_ECDSA_KEY_PAIR_GEN is deprecated in v2.11, + * CKM_EC_KEY_PAIR_GEN is preferred */ +#define CKM_ECDSA_KEY_PAIR_GEN 0x00001040 +#define CKM_EC_KEY_PAIR_GEN 0x00001040 + +#define CKM_ECDSA 0x00001041 +#define CKM_ECDSA_SHA1 0x00001042 + +/* CKM_ECDH1_DERIVE, CKM_ECDH1_COFACTOR_DERIVE, and CKM_ECMQV_DERIVE + * are new for v2.11 */ +#define CKM_ECDH1_DERIVE 0x00001050 +#define CKM_ECDH1_COFACTOR_DERIVE 0x00001051 +#define CKM_ECMQV_DERIVE 0x00001052 + +#define CKM_JUNIPER_KEY_GEN 0x00001060 +#define CKM_JUNIPER_ECB128 0x00001061 +#define CKM_JUNIPER_CBC128 0x00001062 +#define CKM_JUNIPER_COUNTER 0x00001063 +#define CKM_JUNIPER_SHUFFLE 0x00001064 +#define CKM_JUNIPER_WRAP 0x00001065 +#define CKM_FASTHASH 0x00001070 + +/* CKM_AES_KEY_GEN, CKM_AES_ECB, CKM_AES_CBC, CKM_AES_MAC, + * CKM_AES_MAC_GENERAL, CKM_AES_CBC_PAD, CKM_DSA_PARAMETER_GEN, + * CKM_DH_PKCS_PARAMETER_GEN, and CKM_X9_42_DH_PARAMETER_GEN are + * new for v2.11 */ +#define CKM_AES_KEY_GEN 0x00001080 +#define CKM_AES_ECB 0x00001081 +#define CKM_AES_CBC 0x00001082 +#define CKM_AES_MAC 0x00001083 +#define CKM_AES_MAC_GENERAL 0x00001084 +#define CKM_AES_CBC_PAD 0x00001085 + +/* BlowFish and TwoFish are new for v2.20 */ +#define CKM_BLOWFISH_KEY_GEN 0x00001090 +#define CKM_BLOWFISH_CBC 0x00001091 +#define CKM_TWOFISH_KEY_GEN 0x00001092 +#define CKM_TWOFISH_CBC 0x00001093 + + +/* CKM_xxx_ENCRYPT_DATA mechanisms are new for v2.20 */ +#define CKM_DES_ECB_ENCRYPT_DATA 0x00001100 +#define CKM_DES_CBC_ENCRYPT_DATA 0x00001101 +#define CKM_DES3_ECB_ENCRYPT_DATA 0x00001102 +#define CKM_DES3_CBC_ENCRYPT_DATA 0x00001103 +#define CKM_AES_ECB_ENCRYPT_DATA 0x00001104 +#define CKM_AES_CBC_ENCRYPT_DATA 0x00001105 + +#define CKM_DSA_PARAMETER_GEN 0x00002000 +#define CKM_DH_PKCS_PARAMETER_GEN 0x00002001 +#define CKM_X9_42_DH_PARAMETER_GEN 0x00002002 + +#define CKM_VENDOR_DEFINED 0x80000000 + +typedef CK_MECHANISM_TYPE CK_PTR CK_MECHANISM_TYPE_PTR; + + +/* CK_MECHANISM is a structure that specifies a particular + * mechanism */ +typedef struct CK_MECHANISM { + CK_MECHANISM_TYPE mechanism; + CK_VOID_PTR pParameter; + + /* ulParameterLen was changed from CK_USHORT to CK_ULONG for + * v2.0 */ + CK_ULONG ulParameterLen; /* in bytes */ +} CK_MECHANISM; + +typedef CK_MECHANISM CK_PTR CK_MECHANISM_PTR; + + +/* CK_MECHANISM_INFO provides information about a particular + * mechanism */ +typedef struct CK_MECHANISM_INFO { + CK_ULONG ulMinKeySize; + CK_ULONG ulMaxKeySize; + CK_FLAGS flags; +} CK_MECHANISM_INFO; + +/* The flags are defined as follows: + * Bit Flag Mask Meaning */ +#define CKF_HW 0x00000001 /* performed by HW */ + +/* The flags CKF_ENCRYPT, CKF_DECRYPT, CKF_DIGEST, CKF_SIGN, + * CKG_SIGN_RECOVER, CKF_VERIFY, CKF_VERIFY_RECOVER, + * CKF_GENERATE, CKF_GENERATE_KEY_PAIR, CKF_WRAP, CKF_UNWRAP, + * and CKF_DERIVE are new for v2.0. They specify whether or not + * a mechanism can be used for a particular task */ +#define CKF_ENCRYPT 0x00000100 +#define CKF_DECRYPT 0x00000200 +#define CKF_DIGEST 0x00000400 +#define CKF_SIGN 0x00000800 +#define CKF_SIGN_RECOVER 0x00001000 +#define CKF_VERIFY 0x00002000 +#define CKF_VERIFY_RECOVER 0x00004000 +#define CKF_GENERATE 0x00008000 +#define CKF_GENERATE_KEY_PAIR 0x00010000 +#define CKF_WRAP 0x00020000 +#define CKF_UNWRAP 0x00040000 +#define CKF_DERIVE 0x00080000 + +/* CKF_EC_F_P, CKF_EC_F_2M, CKF_EC_ECPARAMETERS, CKF_EC_NAMEDCURVE, + * CKF_EC_UNCOMPRESS, and CKF_EC_COMPRESS are new for v2.11. They + * describe a token's EC capabilities not available in mechanism + * information. */ +#define CKF_EC_F_P 0x00100000 +#define CKF_EC_F_2M 0x00200000 +#define CKF_EC_ECPARAMETERS 0x00400000 +#define CKF_EC_NAMEDCURVE 0x00800000 +#define CKF_EC_UNCOMPRESS 0x01000000 +#define CKF_EC_COMPRESS 0x02000000 + +#define CKF_EXTENSION 0x80000000 /* FALSE for this version */ + +typedef CK_MECHANISM_INFO CK_PTR CK_MECHANISM_INFO_PTR; + + +/* CK_RV is a value that identifies the return value of a + * Cryptoki function */ +/* CK_RV was changed from CK_USHORT to CK_ULONG for v2.0 */ +typedef CK_ULONG CK_RV; + +#define CKR_OK 0x00000000 +#define CKR_CANCEL 0x00000001 +#define CKR_HOST_MEMORY 0x00000002 +#define CKR_SLOT_ID_INVALID 0x00000003 + +/* CKR_FLAGS_INVALID was removed for v2.0 */ + +/* CKR_GENERAL_ERROR and CKR_FUNCTION_FAILED are new for v2.0 */ +#define CKR_GENERAL_ERROR 0x00000005 +#define CKR_FUNCTION_FAILED 0x00000006 + +/* CKR_ARGUMENTS_BAD, CKR_NO_EVENT, CKR_NEED_TO_CREATE_THREADS, + * and CKR_CANT_LOCK are new for v2.01 */ +#define CKR_ARGUMENTS_BAD 0x00000007 +#define CKR_NO_EVENT 0x00000008 +#define CKR_NEED_TO_CREATE_THREADS 0x00000009 +#define CKR_CANT_LOCK 0x0000000A + +#define CKR_ATTRIBUTE_READ_ONLY 0x00000010 +#define CKR_ATTRIBUTE_SENSITIVE 0x00000011 +#define CKR_ATTRIBUTE_TYPE_INVALID 0x00000012 +#define CKR_ATTRIBUTE_VALUE_INVALID 0x00000013 +#define CKR_DATA_INVALID 0x00000020 +#define CKR_DATA_LEN_RANGE 0x00000021 +#define CKR_DEVICE_ERROR 0x00000030 +#define CKR_DEVICE_MEMORY 0x00000031 +#define CKR_DEVICE_REMOVED 0x00000032 +#define CKR_ENCRYPTED_DATA_INVALID 0x00000040 +#define CKR_ENCRYPTED_DATA_LEN_RANGE 0x00000041 +#define CKR_FUNCTION_CANCELED 0x00000050 +#define CKR_FUNCTION_NOT_PARALLEL 0x00000051 + +/* CKR_FUNCTION_NOT_SUPPORTED is new for v2.0 */ +#define CKR_FUNCTION_NOT_SUPPORTED 0x00000054 + +#define CKR_KEY_HANDLE_INVALID 0x00000060 + +/* CKR_KEY_SENSITIVE was removed for v2.0 */ + +#define CKR_KEY_SIZE_RANGE 0x00000062 +#define CKR_KEY_TYPE_INCONSISTENT 0x00000063 + +/* CKR_KEY_NOT_NEEDED, CKR_KEY_CHANGED, CKR_KEY_NEEDED, + * CKR_KEY_INDIGESTIBLE, CKR_KEY_FUNCTION_NOT_PERMITTED, + * CKR_KEY_NOT_WRAPPABLE, and CKR_KEY_UNEXTRACTABLE are new for + * v2.0 */ +#define CKR_KEY_NOT_NEEDED 0x00000064 +#define CKR_KEY_CHANGED 0x00000065 +#define CKR_KEY_NEEDED 0x00000066 +#define CKR_KEY_INDIGESTIBLE 0x00000067 +#define CKR_KEY_FUNCTION_NOT_PERMITTED 0x00000068 +#define CKR_KEY_NOT_WRAPPABLE 0x00000069 +#define CKR_KEY_UNEXTRACTABLE 0x0000006A + +#define CKR_MECHANISM_INVALID 0x00000070 +#define CKR_MECHANISM_PARAM_INVALID 0x00000071 + +/* CKR_OBJECT_CLASS_INCONSISTENT and CKR_OBJECT_CLASS_INVALID + * were removed for v2.0 */ +#define CKR_OBJECT_HANDLE_INVALID 0x00000082 +#define CKR_OPERATION_ACTIVE 0x00000090 +#define CKR_OPERATION_NOT_INITIALIZED 0x00000091 +#define CKR_PIN_INCORRECT 0x000000A0 +#define CKR_PIN_INVALID 0x000000A1 +#define CKR_PIN_LEN_RANGE 0x000000A2 + +/* CKR_PIN_EXPIRED and CKR_PIN_LOCKED are new for v2.0 */ +#define CKR_PIN_EXPIRED 0x000000A3 +#define CKR_PIN_LOCKED 0x000000A4 + +#define CKR_SESSION_CLOSED 0x000000B0 +#define CKR_SESSION_COUNT 0x000000B1 +#define CKR_SESSION_HANDLE_INVALID 0x000000B3 +#define CKR_SESSION_PARALLEL_NOT_SUPPORTED 0x000000B4 +#define CKR_SESSION_READ_ONLY 0x000000B5 +#define CKR_SESSION_EXISTS 0x000000B6 + +/* CKR_SESSION_READ_ONLY_EXISTS and + * CKR_SESSION_READ_WRITE_SO_EXISTS are new for v2.0 */ +#define CKR_SESSION_READ_ONLY_EXISTS 0x000000B7 +#define CKR_SESSION_READ_WRITE_SO_EXISTS 0x000000B8 + +#define CKR_SIGNATURE_INVALID 0x000000C0 +#define CKR_SIGNATURE_LEN_RANGE 0x000000C1 +#define CKR_TEMPLATE_INCOMPLETE 0x000000D0 +#define CKR_TEMPLATE_INCONSISTENT 0x000000D1 +#define CKR_TOKEN_NOT_PRESENT 0x000000E0 +#define CKR_TOKEN_NOT_RECOGNIZED 0x000000E1 +#define CKR_TOKEN_WRITE_PROTECTED 0x000000E2 +#define CKR_UNWRAPPING_KEY_HANDLE_INVALID 0x000000F0 +#define CKR_UNWRAPPING_KEY_SIZE_RANGE 0x000000F1 +#define CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT 0x000000F2 +#define CKR_USER_ALREADY_LOGGED_IN 0x00000100 +#define CKR_USER_NOT_LOGGED_IN 0x00000101 +#define CKR_USER_PIN_NOT_INITIALIZED 0x00000102 +#define CKR_USER_TYPE_INVALID 0x00000103 + +/* CKR_USER_ANOTHER_ALREADY_LOGGED_IN and CKR_USER_TOO_MANY_TYPES + * are new to v2.01 */ +#define CKR_USER_ANOTHER_ALREADY_LOGGED_IN 0x00000104 +#define CKR_USER_TOO_MANY_TYPES 0x00000105 + +#define CKR_WRAPPED_KEY_INVALID 0x00000110 +#define CKR_WRAPPED_KEY_LEN_RANGE 0x00000112 +#define CKR_WRAPPING_KEY_HANDLE_INVALID 0x00000113 +#define CKR_WRAPPING_KEY_SIZE_RANGE 0x00000114 +#define CKR_WRAPPING_KEY_TYPE_INCONSISTENT 0x00000115 +#define CKR_RANDOM_SEED_NOT_SUPPORTED 0x00000120 + +/* These are new to v2.0 */ +#define CKR_RANDOM_NO_RNG 0x00000121 + +/* These are new to v2.11 */ +#define CKR_DOMAIN_PARAMS_INVALID 0x00000130 + +/* These are new to v2.0 */ +#define CKR_BUFFER_TOO_SMALL 0x00000150 +#define CKR_SAVED_STATE_INVALID 0x00000160 +#define CKR_INFORMATION_SENSITIVE 0x00000170 +#define CKR_STATE_UNSAVEABLE 0x00000180 + +/* These are new to v2.01 */ +#define CKR_CRYPTOKI_NOT_INITIALIZED 0x00000190 +#define CKR_CRYPTOKI_ALREADY_INITIALIZED 0x00000191 +#define CKR_MUTEX_BAD 0x000001A0 +#define CKR_MUTEX_NOT_LOCKED 0x000001A1 + +/* This is new to v2.20 */ +#define CKR_FUNCTION_REJECTED 0x00000200 + +#define CKR_VENDOR_DEFINED 0x80000000 + + +/* CK_NOTIFY is an application callback that processes events */ +typedef CK_CALLBACK_FUNCTION(CK_RV, CK_NOTIFY)( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_NOTIFICATION event, + CK_VOID_PTR pApplication /* passed to C_OpenSession */ +); + + +/* CK_FUNCTION_LIST is a structure holding a Cryptoki spec + * version and pointers of appropriate types to all the + * Cryptoki functions */ +/* CK_FUNCTION_LIST is new for v2.0 */ +typedef struct CK_FUNCTION_LIST CK_FUNCTION_LIST; + +typedef CK_FUNCTION_LIST CK_PTR CK_FUNCTION_LIST_PTR; + +typedef CK_FUNCTION_LIST_PTR CK_PTR CK_FUNCTION_LIST_PTR_PTR; + + +/* CK_CREATEMUTEX is an application callback for creating a + * mutex object */ +typedef CK_CALLBACK_FUNCTION(CK_RV, CK_CREATEMUTEX)( + CK_VOID_PTR_PTR ppMutex /* location to receive ptr to mutex */ +); + + +/* CK_DESTROYMUTEX is an application callback for destroying a + * mutex object */ +typedef CK_CALLBACK_FUNCTION(CK_RV, CK_DESTROYMUTEX)( + CK_VOID_PTR pMutex /* pointer to mutex */ +); + + +/* CK_LOCKMUTEX is an application callback for locking a mutex */ +typedef CK_CALLBACK_FUNCTION(CK_RV, CK_LOCKMUTEX)( + CK_VOID_PTR pMutex /* pointer to mutex */ +); + + +/* CK_UNLOCKMUTEX is an application callback for unlocking a + * mutex */ +typedef CK_CALLBACK_FUNCTION(CK_RV, CK_UNLOCKMUTEX)( + CK_VOID_PTR pMutex /* pointer to mutex */ +); + + +/* CK_C_INITIALIZE_ARGS provides the optional arguments to + * C_Initialize */ +typedef struct CK_C_INITIALIZE_ARGS { + CK_CREATEMUTEX CreateMutex; + CK_DESTROYMUTEX DestroyMutex; + CK_LOCKMUTEX LockMutex; + CK_UNLOCKMUTEX UnlockMutex; + CK_FLAGS flags; + CK_VOID_PTR pReserved; +} CK_C_INITIALIZE_ARGS; + +/* flags: bit flags that provide capabilities of the slot + * Bit Flag Mask Meaning + */ +#define CKF_LIBRARY_CANT_CREATE_OS_THREADS 0x00000001 +#define CKF_OS_LOCKING_OK 0x00000002 + +typedef CK_C_INITIALIZE_ARGS CK_PTR CK_C_INITIALIZE_ARGS_PTR; + + +/* additional flags for parameters to functions */ + +/* CKF_DONT_BLOCK is for the function C_WaitForSlotEvent */ +#define CKF_DONT_BLOCK 1 + +/* CK_RSA_PKCS_OAEP_MGF_TYPE is new for v2.10. + * CK_RSA_PKCS_OAEP_MGF_TYPE is used to indicate the Message + * Generation Function (MGF) applied to a message block when + * formatting a message block for the PKCS #1 OAEP encryption + * scheme. */ +typedef CK_ULONG CK_RSA_PKCS_MGF_TYPE; + +typedef CK_RSA_PKCS_MGF_TYPE CK_PTR CK_RSA_PKCS_MGF_TYPE_PTR; + +/* The following MGFs are defined */ +/* CKG_MGF1_SHA256, CKG_MGF1_SHA384, and CKG_MGF1_SHA512 + * are new for v2.20 */ +#define CKG_MGF1_SHA1 0x00000001 +#define CKG_MGF1_SHA256 0x00000002 +#define CKG_MGF1_SHA384 0x00000003 +#define CKG_MGF1_SHA512 0x00000004 + +/* CK_RSA_PKCS_OAEP_SOURCE_TYPE is new for v2.10. + * CK_RSA_PKCS_OAEP_SOURCE_TYPE is used to indicate the source + * of the encoding parameter when formatting a message block + * for the PKCS #1 OAEP encryption scheme. */ +typedef CK_ULONG CK_RSA_PKCS_OAEP_SOURCE_TYPE; + +typedef CK_RSA_PKCS_OAEP_SOURCE_TYPE CK_PTR CK_RSA_PKCS_OAEP_SOURCE_TYPE_PTR; + +/* The following encoding parameter sources are defined */ +#define CKZ_DATA_SPECIFIED 0x00000001 + +/* CK_RSA_PKCS_OAEP_PARAMS is new for v2.10. + * CK_RSA_PKCS_OAEP_PARAMS provides the parameters to the + * CKM_RSA_PKCS_OAEP mechanism. */ +typedef struct CK_RSA_PKCS_OAEP_PARAMS { + CK_MECHANISM_TYPE hashAlg; + CK_RSA_PKCS_MGF_TYPE mgf; + CK_RSA_PKCS_OAEP_SOURCE_TYPE source; + CK_VOID_PTR pSourceData; + CK_ULONG ulSourceDataLen; +} CK_RSA_PKCS_OAEP_PARAMS; + +typedef CK_RSA_PKCS_OAEP_PARAMS CK_PTR CK_RSA_PKCS_OAEP_PARAMS_PTR; + +/* CK_RSA_PKCS_PSS_PARAMS is new for v2.11. + * CK_RSA_PKCS_PSS_PARAMS provides the parameters to the + * CKM_RSA_PKCS_PSS mechanism(s). */ +typedef struct CK_RSA_PKCS_PSS_PARAMS { + CK_MECHANISM_TYPE hashAlg; + CK_RSA_PKCS_MGF_TYPE mgf; + CK_ULONG sLen; +} CK_RSA_PKCS_PSS_PARAMS; + +typedef CK_RSA_PKCS_PSS_PARAMS CK_PTR CK_RSA_PKCS_PSS_PARAMS_PTR; + +/* CK_EC_KDF_TYPE is new for v2.11. */ +typedef CK_ULONG CK_EC_KDF_TYPE; + +/* The following EC Key Derivation Functions are defined */ +#define CKD_NULL 0x00000001 +#define CKD_SHA1_KDF 0x00000002 + +/* CK_ECDH1_DERIVE_PARAMS is new for v2.11. + * CK_ECDH1_DERIVE_PARAMS provides the parameters to the + * CKM_ECDH1_DERIVE and CKM_ECDH1_COFACTOR_DERIVE mechanisms, + * where each party contributes one key pair. + */ +typedef struct CK_ECDH1_DERIVE_PARAMS { + CK_EC_KDF_TYPE kdf; + CK_ULONG ulSharedDataLen; + CK_BYTE_PTR pSharedData; + CK_ULONG ulPublicDataLen; + CK_BYTE_PTR pPublicData; +} CK_ECDH1_DERIVE_PARAMS; + +typedef CK_ECDH1_DERIVE_PARAMS CK_PTR CK_ECDH1_DERIVE_PARAMS_PTR; + + +/* CK_ECDH2_DERIVE_PARAMS is new for v2.11. + * CK_ECDH2_DERIVE_PARAMS provides the parameters to the + * CKM_ECMQV_DERIVE mechanism, where each party contributes two key pairs. */ +typedef struct CK_ECDH2_DERIVE_PARAMS { + CK_EC_KDF_TYPE kdf; + CK_ULONG ulSharedDataLen; + CK_BYTE_PTR pSharedData; + CK_ULONG ulPublicDataLen; + CK_BYTE_PTR pPublicData; + CK_ULONG ulPrivateDataLen; + CK_OBJECT_HANDLE hPrivateData; + CK_ULONG ulPublicDataLen2; + CK_BYTE_PTR pPublicData2; +} CK_ECDH2_DERIVE_PARAMS; + +typedef CK_ECDH2_DERIVE_PARAMS CK_PTR CK_ECDH2_DERIVE_PARAMS_PTR; + +typedef struct CK_ECMQV_DERIVE_PARAMS { + CK_EC_KDF_TYPE kdf; + CK_ULONG ulSharedDataLen; + CK_BYTE_PTR pSharedData; + CK_ULONG ulPublicDataLen; + CK_BYTE_PTR pPublicData; + CK_ULONG ulPrivateDataLen; + CK_OBJECT_HANDLE hPrivateData; + CK_ULONG ulPublicDataLen2; + CK_BYTE_PTR pPublicData2; + CK_OBJECT_HANDLE publicKey; +} CK_ECMQV_DERIVE_PARAMS; + +typedef CK_ECMQV_DERIVE_PARAMS CK_PTR CK_ECMQV_DERIVE_PARAMS_PTR; + +/* Typedefs and defines for the CKM_X9_42_DH_KEY_PAIR_GEN and the + * CKM_X9_42_DH_PARAMETER_GEN mechanisms (new for PKCS #11 v2.11) */ +typedef CK_ULONG CK_X9_42_DH_KDF_TYPE; +typedef CK_X9_42_DH_KDF_TYPE CK_PTR CK_X9_42_DH_KDF_TYPE_PTR; + +/* The following X9.42 DH key derivation functions are defined + (besides CKD_NULL already defined : */ +#define CKD_SHA1_KDF_ASN1 0x00000003 +#define CKD_SHA1_KDF_CONCATENATE 0x00000004 + +/* CK_X9_42_DH1_DERIVE_PARAMS is new for v2.11. + * CK_X9_42_DH1_DERIVE_PARAMS provides the parameters to the + * CKM_X9_42_DH_DERIVE key derivation mechanism, where each party + * contributes one key pair */ +typedef struct CK_X9_42_DH1_DERIVE_PARAMS { + CK_X9_42_DH_KDF_TYPE kdf; + CK_ULONG ulOtherInfoLen; + CK_BYTE_PTR pOtherInfo; + CK_ULONG ulPublicDataLen; + CK_BYTE_PTR pPublicData; +} CK_X9_42_DH1_DERIVE_PARAMS; + +typedef struct CK_X9_42_DH1_DERIVE_PARAMS CK_PTR CK_X9_42_DH1_DERIVE_PARAMS_PTR; + +/* CK_X9_42_DH2_DERIVE_PARAMS is new for v2.11. + * CK_X9_42_DH2_DERIVE_PARAMS provides the parameters to the + * CKM_X9_42_DH_HYBRID_DERIVE and CKM_X9_42_MQV_DERIVE key derivation + * mechanisms, where each party contributes two key pairs */ +typedef struct CK_X9_42_DH2_DERIVE_PARAMS { + CK_X9_42_DH_KDF_TYPE kdf; + CK_ULONG ulOtherInfoLen; + CK_BYTE_PTR pOtherInfo; + CK_ULONG ulPublicDataLen; + CK_BYTE_PTR pPublicData; + CK_ULONG ulPrivateDataLen; + CK_OBJECT_HANDLE hPrivateData; + CK_ULONG ulPublicDataLen2; + CK_BYTE_PTR pPublicData2; +} CK_X9_42_DH2_DERIVE_PARAMS; + +typedef CK_X9_42_DH2_DERIVE_PARAMS CK_PTR CK_X9_42_DH2_DERIVE_PARAMS_PTR; + +typedef struct CK_X9_42_MQV_DERIVE_PARAMS { + CK_X9_42_DH_KDF_TYPE kdf; + CK_ULONG ulOtherInfoLen; + CK_BYTE_PTR pOtherInfo; + CK_ULONG ulPublicDataLen; + CK_BYTE_PTR pPublicData; + CK_ULONG ulPrivateDataLen; + CK_OBJECT_HANDLE hPrivateData; + CK_ULONG ulPublicDataLen2; + CK_BYTE_PTR pPublicData2; + CK_OBJECT_HANDLE publicKey; +} CK_X9_42_MQV_DERIVE_PARAMS; + +typedef CK_X9_42_MQV_DERIVE_PARAMS CK_PTR CK_X9_42_MQV_DERIVE_PARAMS_PTR; + +/* CK_KEA_DERIVE_PARAMS provides the parameters to the + * CKM_KEA_DERIVE mechanism */ +/* CK_KEA_DERIVE_PARAMS is new for v2.0 */ +typedef struct CK_KEA_DERIVE_PARAMS { + CK_BBOOL isSender; + CK_ULONG ulRandomLen; + CK_BYTE_PTR pRandomA; + CK_BYTE_PTR pRandomB; + CK_ULONG ulPublicDataLen; + CK_BYTE_PTR pPublicData; +} CK_KEA_DERIVE_PARAMS; + +typedef CK_KEA_DERIVE_PARAMS CK_PTR CK_KEA_DERIVE_PARAMS_PTR; + + +/* CK_RC2_PARAMS provides the parameters to the CKM_RC2_ECB and + * CKM_RC2_MAC mechanisms. An instance of CK_RC2_PARAMS just + * holds the effective keysize */ +typedef CK_ULONG CK_RC2_PARAMS; + +typedef CK_RC2_PARAMS CK_PTR CK_RC2_PARAMS_PTR; + + +/* CK_RC2_CBC_PARAMS provides the parameters to the CKM_RC2_CBC + * mechanism */ +typedef struct CK_RC2_CBC_PARAMS { + /* ulEffectiveBits was changed from CK_USHORT to CK_ULONG for + * v2.0 */ + CK_ULONG ulEffectiveBits; /* effective bits (1-1024) */ + + CK_BYTE iv[8]; /* IV for CBC mode */ +} CK_RC2_CBC_PARAMS; + +typedef CK_RC2_CBC_PARAMS CK_PTR CK_RC2_CBC_PARAMS_PTR; + + +/* CK_RC2_MAC_GENERAL_PARAMS provides the parameters for the + * CKM_RC2_MAC_GENERAL mechanism */ +/* CK_RC2_MAC_GENERAL_PARAMS is new for v2.0 */ +typedef struct CK_RC2_MAC_GENERAL_PARAMS { + CK_ULONG ulEffectiveBits; /* effective bits (1-1024) */ + CK_ULONG ulMacLength; /* Length of MAC in bytes */ +} CK_RC2_MAC_GENERAL_PARAMS; + +typedef CK_RC2_MAC_GENERAL_PARAMS CK_PTR \ + CK_RC2_MAC_GENERAL_PARAMS_PTR; + + +/* CK_RC5_PARAMS provides the parameters to the CKM_RC5_ECB and + * CKM_RC5_MAC mechanisms */ +/* CK_RC5_PARAMS is new for v2.0 */ +typedef struct CK_RC5_PARAMS { + CK_ULONG ulWordsize; /* wordsize in bits */ + CK_ULONG ulRounds; /* number of rounds */ +} CK_RC5_PARAMS; + +typedef CK_RC5_PARAMS CK_PTR CK_RC5_PARAMS_PTR; + + +/* CK_RC5_CBC_PARAMS provides the parameters to the CKM_RC5_CBC + * mechanism */ +/* CK_RC5_CBC_PARAMS is new for v2.0 */ +typedef struct CK_RC5_CBC_PARAMS { + CK_ULONG ulWordsize; /* wordsize in bits */ + CK_ULONG ulRounds; /* number of rounds */ + CK_BYTE_PTR pIv; /* pointer to IV */ + CK_ULONG ulIvLen; /* length of IV in bytes */ +} CK_RC5_CBC_PARAMS; + +typedef CK_RC5_CBC_PARAMS CK_PTR CK_RC5_CBC_PARAMS_PTR; + + +/* CK_RC5_MAC_GENERAL_PARAMS provides the parameters for the + * CKM_RC5_MAC_GENERAL mechanism */ +/* CK_RC5_MAC_GENERAL_PARAMS is new for v2.0 */ +typedef struct CK_RC5_MAC_GENERAL_PARAMS { + CK_ULONG ulWordsize; /* wordsize in bits */ + CK_ULONG ulRounds; /* number of rounds */ + CK_ULONG ulMacLength; /* Length of MAC in bytes */ +} CK_RC5_MAC_GENERAL_PARAMS; + +typedef CK_RC5_MAC_GENERAL_PARAMS CK_PTR \ + CK_RC5_MAC_GENERAL_PARAMS_PTR; + + +/* CK_MAC_GENERAL_PARAMS provides the parameters to most block + * ciphers' MAC_GENERAL mechanisms. Its value is the length of + * the MAC */ +/* CK_MAC_GENERAL_PARAMS is new for v2.0 */ +typedef CK_ULONG CK_MAC_GENERAL_PARAMS; + +typedef CK_MAC_GENERAL_PARAMS CK_PTR CK_MAC_GENERAL_PARAMS_PTR; + +/* CK_DES/AES_ECB/CBC_ENCRYPT_DATA_PARAMS are new for v2.20 */ +typedef struct CK_DES_CBC_ENCRYPT_DATA_PARAMS { + CK_BYTE iv[8]; + CK_BYTE_PTR pData; + CK_ULONG length; +} CK_DES_CBC_ENCRYPT_DATA_PARAMS; + +typedef CK_DES_CBC_ENCRYPT_DATA_PARAMS CK_PTR CK_DES_CBC_ENCRYPT_DATA_PARAMS_PTR; + +typedef struct CK_AES_CBC_ENCRYPT_DATA_PARAMS { + CK_BYTE iv[16]; + CK_BYTE_PTR pData; + CK_ULONG length; +} CK_AES_CBC_ENCRYPT_DATA_PARAMS; + +typedef CK_AES_CBC_ENCRYPT_DATA_PARAMS CK_PTR CK_AES_CBC_ENCRYPT_DATA_PARAMS_PTR; + +/* CK_SKIPJACK_PRIVATE_WRAP_PARAMS provides the parameters to the + * CKM_SKIPJACK_PRIVATE_WRAP mechanism */ +/* CK_SKIPJACK_PRIVATE_WRAP_PARAMS is new for v2.0 */ +typedef struct CK_SKIPJACK_PRIVATE_WRAP_PARAMS { + CK_ULONG ulPasswordLen; + CK_BYTE_PTR pPassword; + CK_ULONG ulPublicDataLen; + CK_BYTE_PTR pPublicData; + CK_ULONG ulPAndGLen; + CK_ULONG ulQLen; + CK_ULONG ulRandomLen; + CK_BYTE_PTR pRandomA; + CK_BYTE_PTR pPrimeP; + CK_BYTE_PTR pBaseG; + CK_BYTE_PTR pSubprimeQ; +} CK_SKIPJACK_PRIVATE_WRAP_PARAMS; + +typedef CK_SKIPJACK_PRIVATE_WRAP_PARAMS CK_PTR \ + CK_SKIPJACK_PRIVATE_WRAP_PTR; + + +/* CK_SKIPJACK_RELAYX_PARAMS provides the parameters to the + * CKM_SKIPJACK_RELAYX mechanism */ +/* CK_SKIPJACK_RELAYX_PARAMS is new for v2.0 */ +typedef struct CK_SKIPJACK_RELAYX_PARAMS { + CK_ULONG ulOldWrappedXLen; + CK_BYTE_PTR pOldWrappedX; + CK_ULONG ulOldPasswordLen; + CK_BYTE_PTR pOldPassword; + CK_ULONG ulOldPublicDataLen; + CK_BYTE_PTR pOldPublicData; + CK_ULONG ulOldRandomLen; + CK_BYTE_PTR pOldRandomA; + CK_ULONG ulNewPasswordLen; + CK_BYTE_PTR pNewPassword; + CK_ULONG ulNewPublicDataLen; + CK_BYTE_PTR pNewPublicData; + CK_ULONG ulNewRandomLen; + CK_BYTE_PTR pNewRandomA; +} CK_SKIPJACK_RELAYX_PARAMS; + +typedef CK_SKIPJACK_RELAYX_PARAMS CK_PTR \ + CK_SKIPJACK_RELAYX_PARAMS_PTR; + + +typedef struct CK_PBE_PARAMS { + CK_BYTE_PTR pInitVector; + CK_UTF8CHAR_PTR pPassword; + CK_ULONG ulPasswordLen; + CK_BYTE_PTR pSalt; + CK_ULONG ulSaltLen; + CK_ULONG ulIteration; +} CK_PBE_PARAMS; + +typedef CK_PBE_PARAMS CK_PTR CK_PBE_PARAMS_PTR; + + +/* CK_KEY_WRAP_SET_OAEP_PARAMS provides the parameters to the + * CKM_KEY_WRAP_SET_OAEP mechanism */ +/* CK_KEY_WRAP_SET_OAEP_PARAMS is new for v2.0 */ +typedef struct CK_KEY_WRAP_SET_OAEP_PARAMS { + CK_BYTE bBC; /* block contents byte */ + CK_BYTE_PTR pX; /* extra data */ + CK_ULONG ulXLen; /* length of extra data in bytes */ +} CK_KEY_WRAP_SET_OAEP_PARAMS; + +typedef CK_KEY_WRAP_SET_OAEP_PARAMS CK_PTR \ + CK_KEY_WRAP_SET_OAEP_PARAMS_PTR; + + +typedef struct CK_SSL3_RANDOM_DATA { + CK_BYTE_PTR pClientRandom; + CK_ULONG ulClientRandomLen; + CK_BYTE_PTR pServerRandom; + CK_ULONG ulServerRandomLen; +} CK_SSL3_RANDOM_DATA; + + +typedef struct CK_SSL3_MASTER_KEY_DERIVE_PARAMS { + CK_SSL3_RANDOM_DATA RandomInfo; + CK_VERSION_PTR pVersion; +} CK_SSL3_MASTER_KEY_DERIVE_PARAMS; + +typedef struct CK_SSL3_MASTER_KEY_DERIVE_PARAMS CK_PTR \ + CK_SSL3_MASTER_KEY_DERIVE_PARAMS_PTR; + + +typedef struct CK_SSL3_KEY_MAT_OUT { + CK_OBJECT_HANDLE hClientMacSecret; + CK_OBJECT_HANDLE hServerMacSecret; + CK_OBJECT_HANDLE hClientKey; + CK_OBJECT_HANDLE hServerKey; + CK_BYTE_PTR pIVClient; + CK_BYTE_PTR pIVServer; +} CK_SSL3_KEY_MAT_OUT; + +typedef CK_SSL3_KEY_MAT_OUT CK_PTR CK_SSL3_KEY_MAT_OUT_PTR; + + +typedef struct CK_SSL3_KEY_MAT_PARAMS { + CK_ULONG ulMacSizeInBits; + CK_ULONG ulKeySizeInBits; + CK_ULONG ulIVSizeInBits; + CK_BBOOL bIsExport; + CK_SSL3_RANDOM_DATA RandomInfo; + CK_SSL3_KEY_MAT_OUT_PTR pReturnedKeyMaterial; +} CK_SSL3_KEY_MAT_PARAMS; + +typedef CK_SSL3_KEY_MAT_PARAMS CK_PTR CK_SSL3_KEY_MAT_PARAMS_PTR; + +/* CK_TLS_PRF_PARAMS is new for version 2.20 */ +typedef struct CK_TLS_PRF_PARAMS { + CK_BYTE_PTR pSeed; + CK_ULONG ulSeedLen; + CK_BYTE_PTR pLabel; + CK_ULONG ulLabelLen; + CK_BYTE_PTR pOutput; + CK_ULONG_PTR pulOutputLen; +} CK_TLS_PRF_PARAMS; + +typedef CK_TLS_PRF_PARAMS CK_PTR CK_TLS_PRF_PARAMS_PTR; + +/* WTLS is new for version 2.20 */ +typedef struct CK_WTLS_RANDOM_DATA { + CK_BYTE_PTR pClientRandom; + CK_ULONG ulClientRandomLen; + CK_BYTE_PTR pServerRandom; + CK_ULONG ulServerRandomLen; +} CK_WTLS_RANDOM_DATA; + +typedef CK_WTLS_RANDOM_DATA CK_PTR CK_WTLS_RANDOM_DATA_PTR; + +typedef struct CK_WTLS_MASTER_KEY_DERIVE_PARAMS { + CK_MECHANISM_TYPE DigestMechanism; + CK_WTLS_RANDOM_DATA RandomInfo; + CK_BYTE_PTR pVersion; +} CK_WTLS_MASTER_KEY_DERIVE_PARAMS; + +typedef CK_WTLS_MASTER_KEY_DERIVE_PARAMS CK_PTR \ + CK_WTLS_MASTER_KEY_DERIVE_PARAMS_PTR; + +typedef struct CK_WTLS_PRF_PARAMS { + CK_MECHANISM_TYPE DigestMechanism; + CK_BYTE_PTR pSeed; + CK_ULONG ulSeedLen; + CK_BYTE_PTR pLabel; + CK_ULONG ulLabelLen; + CK_BYTE_PTR pOutput; + CK_ULONG_PTR pulOutputLen; +} CK_WTLS_PRF_PARAMS; + +typedef CK_WTLS_PRF_PARAMS CK_PTR CK_WTLS_PRF_PARAMS_PTR; + +typedef struct CK_WTLS_KEY_MAT_OUT { + CK_OBJECT_HANDLE hMacSecret; + CK_OBJECT_HANDLE hKey; + CK_BYTE_PTR pIV; +} CK_WTLS_KEY_MAT_OUT; + +typedef CK_WTLS_KEY_MAT_OUT CK_PTR CK_WTLS_KEY_MAT_OUT_PTR; + +typedef struct CK_WTLS_KEY_MAT_PARAMS { + CK_MECHANISM_TYPE DigestMechanism; + CK_ULONG ulMacSizeInBits; + CK_ULONG ulKeySizeInBits; + CK_ULONG ulIVSizeInBits; + CK_ULONG ulSequenceNumber; + CK_BBOOL bIsExport; + CK_WTLS_RANDOM_DATA RandomInfo; + CK_WTLS_KEY_MAT_OUT_PTR pReturnedKeyMaterial; +} CK_WTLS_KEY_MAT_PARAMS; + +typedef CK_WTLS_KEY_MAT_PARAMS CK_PTR CK_WTLS_KEY_MAT_PARAMS_PTR; + +/* CMS is new for version 2.20 */ +typedef struct CK_CMS_SIG_PARAMS { + CK_OBJECT_HANDLE certificateHandle; + CK_MECHANISM_PTR pSigningMechanism; + CK_MECHANISM_PTR pDigestMechanism; + CK_UTF8CHAR_PTR pContentType; + CK_BYTE_PTR pRequestedAttributes; + CK_ULONG ulRequestedAttributesLen; + CK_BYTE_PTR pRequiredAttributes; + CK_ULONG ulRequiredAttributesLen; +} CK_CMS_SIG_PARAMS; + +typedef CK_CMS_SIG_PARAMS CK_PTR CK_CMS_SIG_PARAMS_PTR; + +typedef struct CK_KEY_DERIVATION_STRING_DATA { + CK_BYTE_PTR pData; + CK_ULONG ulLen; +} CK_KEY_DERIVATION_STRING_DATA; + +typedef CK_KEY_DERIVATION_STRING_DATA CK_PTR \ + CK_KEY_DERIVATION_STRING_DATA_PTR; + + +/* The CK_EXTRACT_PARAMS is used for the + * CKM_EXTRACT_KEY_FROM_KEY mechanism. It specifies which bit + * of the base key should be used as the first bit of the + * derived key */ +/* CK_EXTRACT_PARAMS is new for v2.0 */ +typedef CK_ULONG CK_EXTRACT_PARAMS; + +typedef CK_EXTRACT_PARAMS CK_PTR CK_EXTRACT_PARAMS_PTR; + +/* CK_PKCS5_PBKD2_PSEUDO_RANDOM_FUNCTION_TYPE is new for v2.10. + * CK_PKCS5_PBKD2_PSEUDO_RANDOM_FUNCTION_TYPE is used to + * indicate the Pseudo-Random Function (PRF) used to generate + * key bits using PKCS #5 PBKDF2. */ +typedef CK_ULONG CK_PKCS5_PBKD2_PSEUDO_RANDOM_FUNCTION_TYPE; + +typedef CK_PKCS5_PBKD2_PSEUDO_RANDOM_FUNCTION_TYPE CK_PTR CK_PKCS5_PBKD2_PSEUDO_RANDOM_FUNCTION_TYPE_PTR; + +/* The following PRFs are defined in PKCS #5 v2.0. */ +#define CKP_PKCS5_PBKD2_HMAC_SHA1 0x00000001 + + +/* CK_PKCS5_PBKDF2_SALT_SOURCE_TYPE is new for v2.10. + * CK_PKCS5_PBKDF2_SALT_SOURCE_TYPE is used to indicate the + * source of the salt value when deriving a key using PKCS #5 + * PBKDF2. */ +typedef CK_ULONG CK_PKCS5_PBKDF2_SALT_SOURCE_TYPE; + +typedef CK_PKCS5_PBKDF2_SALT_SOURCE_TYPE CK_PTR CK_PKCS5_PBKDF2_SALT_SOURCE_TYPE_PTR; + +/* The following salt value sources are defined in PKCS #5 v2.0. */ +#define CKZ_SALT_SPECIFIED 0x00000001 + +/* CK_PKCS5_PBKD2_PARAMS is new for v2.10. + * CK_PKCS5_PBKD2_PARAMS is a structure that provides the + * parameters to the CKM_PKCS5_PBKD2 mechanism. */ +typedef struct CK_PKCS5_PBKD2_PARAMS { + CK_PKCS5_PBKDF2_SALT_SOURCE_TYPE saltSource; + CK_VOID_PTR pSaltSourceData; + CK_ULONG ulSaltSourceDataLen; + CK_ULONG iterations; + CK_PKCS5_PBKD2_PSEUDO_RANDOM_FUNCTION_TYPE prf; + CK_VOID_PTR pPrfData; + CK_ULONG ulPrfDataLen; + CK_UTF8CHAR_PTR pPassword; + CK_ULONG_PTR ulPasswordLen; +} CK_PKCS5_PBKD2_PARAMS; + +typedef CK_PKCS5_PBKD2_PARAMS CK_PTR CK_PKCS5_PBKD2_PARAMS_PTR; + +#endif diff --git a/src/pluto/rsaref/unix.h b/src/pluto/rsaref/unix.h new file mode 100644 index 000000000..2e7eb6663 --- /dev/null +++ b/src/pluto/rsaref/unix.h @@ -0,0 +1,24 @@ + + +#ifndef UNIX_H +#define UNIX_H + +#define CK_PTR * + +#define CK_DEFINE_FUNCTION(returnType, name) \ + returnType name + +#define CK_DECLARE_FUNCTION(returnType, name) \ + returnType name + +#define CK_DECLARE_FUNCTION_POINTER(returnType, name) \ + returnType (* name) + +#define CK_CALLBACK_FUNCTION(returnType, name) \ + returnType (* name) + +#ifndef NULL_PTR +#define NULL_PTR 0 +#endif + +#endif diff --git a/src/pluto/server.c b/src/pluto/server.c new file mode 100644 index 000000000..1cc221515 --- /dev/null +++ b/src/pluto/server.c @@ -0,0 +1,996 @@ +/* get-next-event loop + * Copyright (C) 1997 Angelos D. Keromytis. + * Copyright (C) 1998-2002 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: server.c,v 1.9 2005/09/09 14:15:35 as Exp $ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <errno.h> +#include <signal.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#ifdef SOLARIS +# include <sys/sockio.h> /* for Solaris 2.6: defines SIOCGIFCONF */ +#endif +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/time.h> +#include <netdb.h> +#include <unistd.h> +#include <fcntl.h> +#include <net/if.h> +#include <sys/ioctl.h> +#include <resolv.h> +#include <arpa/nameser.h> /* missing from <resolv.h> on old systems */ +#include <sys/queue.h> + +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "state.h" +#include "connections.h" +#include "kernel.h" +#include "log.h" +#include "server.h" +#include "timer.h" +#include "packet.h" +#include "demux.h" /* needs packet.h */ +#include "rcv_whack.h" +#include "keys.h" +#include "adns.h" /* needs <resolv.h> */ +#include "dnskey.h" /* needs keys.h and adns.h */ +#include "whack.h" /* for RC_LOG_SERIOUS */ + +#include <pfkeyv2.h> +#include <pfkey.h> +#include "kameipsec.h" +#include "nat_traversal.h" + +/* + * Server main loop and socket initialization routines. + */ + +static const int on = TRUE; /* by-reference parameter; constant, we hope */ + +/* control (whack) socket */ +int ctl_fd = NULL_FD; /* file descriptor of control (whack) socket */ +struct sockaddr_un ctl_addr = { AF_UNIX, DEFAULT_CTLBASE CTL_SUFFIX }; + +/* info (showpolicy) socket */ +int policy_fd = NULL_FD; +struct sockaddr_un info_addr= { AF_UNIX, DEFAULT_CTLBASE INFO_SUFFIX }; + +/* Initialize the control socket. + * Note: this is called very early, so little infrastructure is available. + * It is important that the socket is created before the original + * Pluto process returns. + */ +err_t +init_ctl_socket(void) +{ + err_t failed = NULL; + + delete_ctl_socket(); /* preventative medicine */ + ctl_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (ctl_fd == -1) + failed = "create"; + else if (fcntl(ctl_fd, F_SETFD, FD_CLOEXEC) == -1) + failed = "fcntl FD+CLOEXEC"; + else if (setsockopt(ctl_fd, SOL_SOCKET, SO_REUSEADDR, (const void *)&on, sizeof(on)) < 0) + failed = "setsockopt"; + else + { + /* to keep control socket secure, use umask */ + mode_t ou = umask(~S_IRWXU); + + if (bind(ctl_fd, (struct sockaddr *)&ctl_addr + , offsetof(struct sockaddr_un, sun_path) + strlen(ctl_addr.sun_path)) < 0) + failed = "bind"; + umask(ou); + } + + /* 5 is a haphazardly chosen limit for the backlog. + * Rumour has it that this is the max on BSD systems. + */ + if (failed == NULL && listen(ctl_fd, 5) < 0) + failed = "listen() on"; + + return failed == NULL? NULL : builddiag("could not %s control socket: %d %s" + , failed, errno, strerror(errno)); +} + +void +delete_ctl_socket(void) +{ + /* Is noting failure useful? Not when used as preventative medicine. */ + unlink(ctl_addr.sun_path); +} + +bool listening = FALSE; /* should we pay attention to IKE messages? */ + +struct iface *interfaces = NULL; /* public interfaces */ + +/* Initialize the interface sockets. */ + +static void +mark_ifaces_dead(void) +{ + struct iface *p; + + for (p = interfaces; p != NULL; p = p->next) + p->change = IFN_DELETE; +} + +static void +free_dead_ifaces(void) +{ + struct iface *p; + bool some_dead = FALSE + , some_new = FALSE; + + for (p = interfaces; p != NULL; p = p->next) + { + if (p->change == IFN_DELETE) + { + plog("shutting down interface %s/%s %s" + , p->vname, p->rname, ip_str(&p->addr)); + some_dead = TRUE; + } + else if (p->change == IFN_ADD) + { + some_new = TRUE; + } + } + + if (some_dead) + { + struct iface **pp; + + release_dead_interfaces(); + for (pp = &interfaces; (p = *pp) != NULL; ) + { + if (p->change == IFN_DELETE) + { + *pp = p->next; /* advance *pp */ + pfree(p->vname); + pfree(p->rname); + close(p->fd); + pfree(p); + } + else + { + pp = &p->next; /* advance pp */ + } + } + } + + /* this must be done after the release_dead_interfaces + * in case some to the newly unoriented connections can + * become oriented here. + */ + if (some_dead || some_new) + check_orientations(); +} + +void +free_ifaces(void) +{ + mark_ifaces_dead(); + free_dead_ifaces(); +} + +struct raw_iface { + ip_address addr; + char name[IFNAMSIZ + 20]; /* what would be a safe size? */ + struct raw_iface *next; +}; + +/* Called to handle --interface <ifname> + * Semantics: if specified, only these (real) interfaces are considered. + */ +static const char *pluto_ifn[10]; +static int pluto_ifn_roof = 0; + +bool +use_interface(const char *rifn) +{ + if (pluto_ifn_roof >= (int)elemsof(pluto_ifn)) + { + return FALSE; + } + else + { + pluto_ifn[pluto_ifn_roof++] = rifn; + return TRUE; + } +} + +#ifndef IPSECDEVPREFIX +# define IPSECDEVPREFIX "ipsec" +#endif + +static struct raw_iface * +find_raw_ifaces4(void) +{ + int j; /* index into buf */ + struct ifconf ifconf; + struct ifreq buf[300]; /* for list of interfaces -- arbitrary limit */ + struct raw_iface *rifaces = NULL; + int master_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); /* Get a UDP socket */ + + /* get list of interfaces with assigned IPv4 addresses from system */ + + if (master_sock == -1) + exit_log_errno((e, "socket() failed in find_raw_ifaces4()")); + + if (setsockopt(master_sock, SOL_SOCKET, SO_REUSEADDR + , (const void *)&on, sizeof(on)) < 0) + exit_log_errno((e, "setsockopt() in find_raw_ifaces4()")); + + /* bind the socket */ + { + ip_address any; + + happy(anyaddr(AF_INET, &any)); + setportof(htons(pluto_port), &any); + if (bind(master_sock, sockaddrof(&any), sockaddrlenof(&any)) < 0) + exit_log_errno((e, "bind() failed in find_raw_ifaces4()")); + } + + /* Get local interfaces. See netdevice(7). */ + ifconf.ifc_len = sizeof(buf); + ifconf.ifc_buf = (void *) buf; + zero(buf); + + if (ioctl(master_sock, SIOCGIFCONF, &ifconf) == -1) + exit_log_errno((e, "ioctl(SIOCGIFCONF) in find_raw_ifaces4()")); + + /* Add an entry to rifaces for each interesting interface. */ + for (j = 0; (j+1) * sizeof(*buf) <= (size_t)ifconf.ifc_len; j++) + { + struct raw_iface ri; + const struct sockaddr_in *rs = (struct sockaddr_in *) &buf[j].ifr_addr; + struct ifreq auxinfo; + + /* ignore all but AF_INET interfaces */ + if (rs->sin_family != AF_INET) + continue; /* not interesting */ + + /* build a NUL-terminated copy of the rname field */ + memcpy(ri.name, buf[j].ifr_name, IFNAMSIZ); + ri.name[IFNAMSIZ] = '\0'; + + /* ignore if our interface names were specified, and this isn't one */ + if (pluto_ifn_roof != 0) + { + int i; + + for (i = 0; i != pluto_ifn_roof; i++) + if (streq(ri.name, pluto_ifn[i])) + break; + if (i == pluto_ifn_roof) + continue; /* not found -- skip */ + } + + /* Find out stuff about this interface. See netdevice(7). */ + zero(&auxinfo); /* paranoia */ + memcpy(auxinfo.ifr_name, buf[j].ifr_name, IFNAMSIZ); + if (ioctl(master_sock, SIOCGIFFLAGS, &auxinfo) == -1) + exit_log_errno((e + , "ioctl(SIOCGIFFLAGS) for %s in find_raw_ifaces4()" + , ri.name)); + if (!(auxinfo.ifr_flags & IFF_UP)) + continue; /* ignore an interface that isn't UP */ + + /* ignore unconfigured interfaces */ + if (rs->sin_addr.s_addr == 0) + continue; + + happy(initaddr((const void *)&rs->sin_addr, sizeof(struct in_addr) + , AF_INET, &ri.addr)); + + DBG(DBG_CONTROL, DBG_log("found %s with address %s" + , ri.name, ip_str(&ri.addr))); + ri.next = rifaces; + rifaces = clone_thing(ri, "struct raw_iface"); + } + + close(master_sock); + + return rifaces; +} + +static struct raw_iface * +find_raw_ifaces6(void) +{ + + /* Get list of interfaces with IPv6 addresses from system from /proc/net/if_inet6). + * + * Documentation of format? + * RTFS: linux-2.2.16/net/ipv6/addrconf.c:iface_proc_info() + * linux-2.4.9-13/net/ipv6/addrconf.c:iface_proc_info() + * + * Sample from Gerhard's laptop: + * 00000000000000000000000000000001 01 80 10 80 lo + * 30490009000000000000000000010002 02 40 00 80 ipsec0 + * 30490009000000000000000000010002 07 40 00 80 eth0 + * fe80000000000000025004fffefd5484 02 0a 20 80 ipsec0 + * fe80000000000000025004fffefd5484 07 0a 20 80 eth0 + * + * Each line contains: + * - IPv6 address: 16 bytes, in hex, no punctuation + * - ifindex: 1 byte, in hex + * - prefix_len: 1 byte, in hex + * - scope (e.g. global, link local): 1 byte, in hex + * - flags: 1 byte, in hex + * - device name: string, followed by '\n' + */ + struct raw_iface *rifaces = NULL; + static const char proc_name[] = "/proc/net/if_inet6"; + FILE *proc_sock = fopen(proc_name, "r"); + + if (proc_sock == NULL) + { + DBG(DBG_CONTROL, DBG_log("could not open %s", proc_name)); + } + else + { + for (;;) + { + struct raw_iface ri; + unsigned short xb[8]; /* IPv6 address as 8 16-bit chunks */ + char sb[8*5]; /* IPv6 address as string-with-colons */ + unsigned int if_idx; /* proc field, not used */ + unsigned int plen; /* proc field, not used */ + unsigned int scope; /* proc field, used to exclude link-local */ + unsigned int dad_status; /* proc field, not used */ + /* ??? I hate and distrust scanf -- DHR */ + int r = fscanf(proc_sock + , "%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx" + " %02x %02x %02x %02x %20s\n" + , xb+0, xb+1, xb+2, xb+3, xb+4, xb+5, xb+6, xb+7 + , &if_idx, &plen, &scope, &dad_status, ri.name); + + /* ??? we should diagnose any problems */ + if (r != 13) + break; + + /* ignore addresses with link local scope. + * From linux-2.4.9-13/include/net/ipv6.h: + * IPV6_ADDR_LINKLOCAL 0x0020U + * IPV6_ADDR_SCOPE_MASK 0x00f0U + */ + if ((scope & 0x00f0U) == 0x0020U) + continue; + + snprintf(sb, sizeof(sb) + , "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x" + , xb[0], xb[1], xb[2], xb[3], xb[4], xb[5], xb[6], xb[7]); + + happy(ttoaddr(sb, 0, AF_INET6, &ri.addr)); + + if (!isunspecaddr(&ri.addr)) + { + DBG(DBG_CONTROL + , DBG_log("found %s with address %s" + , ri.name, sb)); + ri.next = rifaces; + rifaces = clone_thing(ri, "struct raw_iface"); + } + } + fclose(proc_sock); + } + + return rifaces; +} + +#if 1 +static int +create_socket(struct raw_iface *ifp, const char *v_name, int port) +{ + int fd = socket(addrtypeof(&ifp->addr), SOCK_DGRAM, IPPROTO_UDP); + int fcntl_flags; + + if (fd < 0) + { + log_errno((e, "socket() in process_raw_ifaces()")); + return -1; + } + +#if 1 + /* Set socket Nonblocking */ + if ((fcntl_flags=fcntl(fd, F_GETFL)) >= 0) { + if (!(fcntl_flags & O_NONBLOCK)) { + fcntl_flags |= O_NONBLOCK; + fcntl(fd, F_SETFL, fcntl_flags); + } + } +#endif + + if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) + { + log_errno((e, "fcntl(,, FD_CLOEXEC) in process_raw_ifaces()")); + close(fd); + return -1; + } + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR + , (const void *)&on, sizeof(on)) < 0) + { + log_errno((e, "setsockopt SO_REUSEADDR in process_raw_ifaces()")); + close(fd); + return -1; + } + + /* To improve error reporting. See ip(7). */ +#if defined(IP_RECVERR) && defined(MSG_ERRQUEUE) + if (setsockopt(fd, SOL_IP, IP_RECVERR + , (const void *)&on, sizeof(on)) < 0) + { + log_errno((e, "setsockopt IP_RECVERR in process_raw_ifaces()")); + close(fd); + return -1; + } +#endif + + /* With IPv6, there is no fragmentation after + * it leaves our interface. PMTU discovery + * is mandatory but doesn't work well with IKE (why?). + * So we must set the IPV6_USE_MIN_MTU option. + * See draft-ietf-ipngwg-rfc2292bis-01.txt 11.1 + */ +#ifdef IPV6_USE_MIN_MTU /* YUCK: not always defined */ + if (addrtypeof(&ifp->addr) == AF_INET6 + && setsockopt(fd, SOL_SOCKET, IPV6_USE_MIN_MTU + , (const void *)&on, sizeof(on)) < 0) + { + log_errno((e, "setsockopt IPV6_USE_MIN_MTU in process_raw_ifaces()")); + close(fd); + return -1; + } +#endif + +#if defined(linux) && defined(KERNEL26_SUPPORT) + if (!no_klips && kernel_ops->type == KERNEL_TYPE_LINUX) + { + struct sadb_x_policy policy; + int level, opt; + + policy.sadb_x_policy_len = sizeof(policy) / IPSEC_PFKEYv2_ALIGN; + policy.sadb_x_policy_exttype = SADB_X_EXT_POLICY; + policy.sadb_x_policy_type = IPSEC_POLICY_BYPASS; + policy.sadb_x_policy_dir = IPSEC_DIR_INBOUND; + policy.sadb_x_policy_reserved = 0; + policy.sadb_x_policy_id = 0; + policy.sadb_x_policy_reserved2 = 0; + + if (addrtypeof(&ifp->addr) == AF_INET6) + { + level = IPPROTO_IPV6; + opt = IPV6_IPSEC_POLICY; + } + else + { + level = IPPROTO_IP; + opt = IP_IPSEC_POLICY; + } + + if (setsockopt(fd, level, opt + , &policy, sizeof(policy)) < 0) + { + log_errno((e, "setsockopt IPSEC_POLICY in process_raw_ifaces()")); + close(fd); + return -1; + } + + policy.sadb_x_policy_dir = IPSEC_DIR_OUTBOUND; + + if (setsockopt(fd, level, opt + , &policy, sizeof(policy)) < 0) + { + log_errno((e, "setsockopt IPSEC_POLICY in process_raw_ifaces()")); + close(fd); + return -1; + } + } +#endif + + setportof(htons(port), &ifp->addr); + if (bind(fd, sockaddrof(&ifp->addr), sockaddrlenof(&ifp->addr)) < 0) + { + log_errno((e, "bind() for %s/%s %s:%u in process_raw_ifaces()" + , ifp->name, v_name + , ip_str(&ifp->addr), (unsigned) port)); + close(fd); + return -1; + } + setportof(htons(pluto_port), &ifp->addr); + return fd; +} +#endif + +static void +process_raw_ifaces(struct raw_iface *rifaces) +{ + struct raw_iface *ifp; + + /* Find all virtual/real interface pairs. + * For each real interface... + */ + for (ifp = rifaces; ifp != NULL; ifp = ifp->next) + { + struct raw_iface *v = NULL; /* matching ipsecX interface */ + struct raw_iface fake_v; + bool after = FALSE; /* has vfp passed ifp on the list? */ + bool bad = FALSE; + struct raw_iface *vfp; + + /* ignore if virtual (ipsec*) interface */ + if (strncmp(ifp->name, IPSECDEVPREFIX, sizeof(IPSECDEVPREFIX)-1) == 0) + continue; + + for (vfp = rifaces; vfp != NULL; vfp = vfp->next) + { + if (vfp == ifp) + { + after = TRUE; + } + else if (sameaddr(&ifp->addr, &vfp->addr)) + { + /* Different entries with matching IP addresses. + * Many interesting cases. + */ + if (strncmp(vfp->name, IPSECDEVPREFIX, sizeof(IPSECDEVPREFIX)-1) == 0) + { + if (v != NULL && !streq(v->name, vfp->name)) + { + loglog(RC_LOG_SERIOUS + , "ipsec interfaces %s and %s share same address %s" + , v->name, vfp->name, ip_str(&ifp->addr)); + bad = TRUE; + } + else + { + v = vfp; /* current winner */ + } + } + else + { + /* ugh: a second real interface with the same IP address + * "after" allows us to avoid double reporting. + */ +#if defined(linux) && defined(KERNEL26_SUPPORT) + if (!no_klips && kernel_ops->type == KERNEL_TYPE_LINUX) + { + if (after) + { + bad = TRUE; + break; + } + continue; + } +#endif + if (after) + { + loglog(RC_LOG_SERIOUS + , "IP interfaces %s and %s share address %s!" + , ifp->name, vfp->name, ip_str(&ifp->addr)); + } + bad = TRUE; + } + } + } + + if (bad) + continue; + +#if defined(linux) && defined(KERNEL26_SUPPORT) + if (!no_klips && kernel_ops->type == KERNEL_TYPE_LINUX) + { + v = ifp; + goto add_entry; + } +#endif + + /* what if we didn't find a virtual interface? */ + if (v == NULL) + { + if (no_klips) + { + /* kludge for testing: invent a virtual device */ + static const char fvp[] = "virtual"; + fake_v = *ifp; + passert(sizeof(fake_v.name) > sizeof(fvp)); + strcpy(fake_v.name, fvp); + addrtot(&ifp->addr, 0, fake_v.name + sizeof(fvp) - 1 + , sizeof(fake_v.name) - (sizeof(fvp) - 1)); + v = &fake_v; + } + else + { + DBG(DBG_CONTROL, + DBG_log("IP interface %s %s has no matching ipsec* interface -- ignored" + , ifp->name, ip_str(&ifp->addr))); + continue; + } + } + + /* We've got all we need; see if this is a new thing: + * search old interfaces list. + */ +#if defined(linux) && defined(KERNEL26_SUPPORT) +add_entry: +#endif + { + struct iface **p = &interfaces; + + for (;;) + { + struct iface *q = *p; + + /* search is over if at end of list */ + if (q == NULL) + { + /* matches nothing -- create a new entry */ + int fd = create_socket(ifp, v->name, pluto_port); + + if (fd < 0) + break; + + if (nat_traversal_support_non_ike + && addrtypeof(&ifp->addr) == AF_INET) + { + nat_traversal_espinudp_socket(fd, ESPINUDP_WITH_NON_IKE); + } + + q = alloc_thing(struct iface, "struct iface"); + q->rname = clone_str(ifp->name, "real device name"); + q->vname = clone_str(v->name, "virtual device name"); + q->addr = ifp->addr; + q->fd = fd; + q->next = interfaces; + q->change = IFN_ADD; + interfaces = q; + plog("adding interface %s/%s %s:%d" + , q->vname, q->rname, ip_str(&q->addr), pluto_port); + + if (nat_traversal_support_port_floating + && addrtypeof(&ifp->addr) == AF_INET) + { + fd = create_socket(ifp, v->name, NAT_T_IKE_FLOAT_PORT); + if (fd < 0) + break; + nat_traversal_espinudp_socket(fd, + ESPINUDP_WITH_NON_ESP); + q = alloc_thing(struct iface, "struct iface"); + q->rname = clone_str(ifp->name, "real device name"); + q->vname = clone_str(v->name, "virtual device name"); + q->addr = ifp->addr; + setportof(htons(NAT_T_IKE_FLOAT_PORT), &q->addr); + q->fd = fd; + q->next = interfaces; + q->change = IFN_ADD; + q->ike_float = TRUE; + interfaces = q; + plog("adding interface %s/%s %s:%d", + q->vname, q->rname, ip_str(&q->addr), NAT_T_IKE_FLOAT_PORT); + } + break; + } + + /* search over if matching old entry found */ + if (streq(q->rname, ifp->name) + && streq(q->vname, v->name) + && sameaddr(&q->addr, &ifp->addr)) + { + /* matches -- rejuvinate old entry */ + q->change = IFN_KEEP; + + /* look for other interfaces to keep (due to NAT-T) */ + for (q = q->next ; q ; q = q->next) + { + if (streq(q->rname, ifp->name) + && streq(q->vname, v->name) + && sameaddr(&q->addr, &ifp->addr)) + { + q->change = IFN_KEEP; + } + } + break; + } + + /* try again */ + p = &q->next; + } /* for (;;) */ + } + } + + /* delete the raw interfaces list */ + while (rifaces != NULL) + { + struct raw_iface *t = rifaces; + + rifaces = t->next; + pfree(t); + } +} + +void +find_ifaces(void) +{ + mark_ifaces_dead(); + process_raw_ifaces(find_raw_ifaces4()); + process_raw_ifaces(find_raw_ifaces6()); + + free_dead_ifaces(); /* ditch remaining old entries */ + + if (interfaces == NULL) + loglog(RC_LOG_SERIOUS, "no public interfaces found"); +} + +void +show_ifaces_status(void) +{ + struct iface *p; + + for (p = interfaces; p != NULL; p = p->next) + whack_log(RC_COMMENT, "interface %s/%s %s:%d" + , p->vname, p->rname, ip_str(&p->addr), ntohs(portof(&p->addr))); +} + +void +show_debug_status(void) +{ +#ifdef DEBUG + whack_log(RC_COMMENT, "debug %s" + , bitnamesof(debug_bit_names, cur_debugging)); +#endif +} + +static volatile sig_atomic_t sighupflag = FALSE; + +static void +huphandler(int sig UNUSED) +{ + sighupflag = TRUE; +} + +static volatile sig_atomic_t sigtermflag = FALSE; + +static void +termhandler(int sig UNUSED) +{ + sigtermflag = TRUE; +} + +/* call_server listens for incoming ISAKMP packets and Whack messages, + * and handles timer events. + */ +void +call_server(void) +{ + struct iface *ifp; + + /* catch SIGHUP and SIGTERM */ + { + int r; + struct sigaction act; + + act.sa_handler = &huphandler; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; /* no SA_ONESHOT, no SA_RESTART, no nothing */ + r = sigaction(SIGHUP, &act, NULL); + passert(r == 0); + + act.sa_handler = &termhandler; + r = sigaction(SIGTERM, &act, NULL); + passert(r == 0); + } + + for (;;) + { + fd_set readfds; + fd_set writefds; + int ndes; + + /* wait for next interesting thing */ + + for (;;) + { + long next_time = next_event(); /* time to any pending timer event */ + int maxfd = ctl_fd; + + if (sigtermflag) + exit_pluto(0); + + if (sighupflag) + { + /* Ignorant folks think poking any daemon with SIGHUP + * is polite. We catch it and tell them otherwise. + * There is one use: unsticking a hung recvfrom. + * This sticking happens sometimes -- kernel bug? + */ + sighupflag = FALSE; + plog("Pluto ignores SIGHUP -- perhaps you want \"whack --listen\""); + } + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_SET(ctl_fd, &readfds); + + /* the only write file-descriptor of interest */ + if (adns_qfd != NULL_FD && unsent_ADNS_queries) + { + if (maxfd < adns_qfd) + maxfd = adns_qfd; + FD_SET(adns_qfd, &writefds); + } + + if (adns_afd != NULL_FD) + { + if (maxfd < adns_afd) + maxfd = adns_afd; + FD_SET(adns_afd, &readfds); + } + +#ifdef KLIPS + if (!no_klips) + { + int fd = *kernel_ops->async_fdp; + + if (kernel_ops->process_queue) + kernel_ops->process_queue(); + if (maxfd < fd) + maxfd = fd; + passert(!FD_ISSET(fd, &readfds)); + FD_SET(fd, &readfds); + } +#endif + + if (listening) + { + for (ifp = interfaces; ifp != NULL; ifp = ifp->next) + { + if (maxfd < ifp->fd) + maxfd = ifp->fd; + passert(!FD_ISSET(ifp->fd, &readfds)); + FD_SET(ifp->fd, &readfds); + } + } + + if (next_time == -1) + { + /* select without timer */ + + ndes = select(maxfd + 1, &readfds, &writefds, NULL, NULL); + } + else if (next_time == 0) + { + /* timer without select: there is a timer event pending, + * and it should fire now so don't bother to do the select. + */ + ndes = 0; /* signify timer expiration */ + } + else + { + /* select with timer */ + + struct timeval tm; + + tm.tv_sec = next_time; + tm.tv_usec = 0; + ndes = select(maxfd + 1, &readfds, &writefds, NULL, &tm); + } + + if (ndes != -1) + break; /* success */ + + if (errno != EINTR) + exit_log_errno((e, "select() failed in call_server()")); + + /* retry if terminated by signal */ + } + + /* figure out what is interesting */ + + if (ndes == 0) + { + /* timer event */ + + DBG(DBG_CONTROL, + DBG_log(BLANK_FORMAT); + DBG_log("*time to handle event")); + + handle_timer_event(); + passert(GLOBALS_ARE_RESET()); + } + else + { + /* at least one file descriptor is ready */ + + if (adns_qfd != NULL_FD && FD_ISSET(adns_qfd, &writefds)) + { + passert(ndes > 0); + send_unsent_ADNS_queries(); + passert(GLOBALS_ARE_RESET()); + ndes--; + } + + if (adns_afd != NULL_FD && FD_ISSET(adns_afd, &readfds)) + { + passert(ndes > 0); + DBG(DBG_CONTROL, + DBG_log(BLANK_FORMAT); + DBG_log("*received adns message")); + handle_adns_answer(); + passert(GLOBALS_ARE_RESET()); + ndes--; + } + +#ifdef KLIPS + if (!no_klips && FD_ISSET(*kernel_ops->async_fdp, &readfds)) + { + passert(ndes > 0); + DBG(DBG_CONTROL, + DBG_log(BLANK_FORMAT); + DBG_log("*received kernel message")); + kernel_ops->process_msg(); + passert(GLOBALS_ARE_RESET()); + ndes--; + } +#endif + + for (ifp = interfaces; ifp != NULL; ifp = ifp->next) + { + if (FD_ISSET(ifp->fd, &readfds)) + { + /* comm_handle will print DBG_CONTROL intro, + * with more info than we have here. + */ + + passert(ndes > 0); + comm_handle(ifp); + passert(GLOBALS_ARE_RESET()); + ndes--; + } + } + + if (FD_ISSET(ctl_fd, &readfds)) + { + passert(ndes > 0); + DBG(DBG_CONTROL, + DBG_log(BLANK_FORMAT); + DBG_log("*received whack message")); + whack_handle(ctl_fd); + passert(GLOBALS_ARE_RESET()); + ndes--; + } + + passert(ndes == 0); + } + } +} + +/* + * Local Variables: + * c-basic-offset: 4 + * End Variables: + */ diff --git a/src/pluto/server.h b/src/pluto/server.h new file mode 100644 index 000000000..d90e47c8f --- /dev/null +++ b/src/pluto/server.h @@ -0,0 +1,58 @@ +/* get-next-event loop + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: server.h,v 1.2 2004/03/22 21:53:20 as Exp $ + */ + +extern int ctl_fd; /* file descriptor of control (whack) socket */ +extern struct sockaddr_un ctl_addr; /* address of control (whack) socket */ + +extern int info_fd; /* file descriptor of control (info) socket */ +extern struct sockaddr_un info_addr; /* address of control (info) socket */ + +extern err_t init_ctl_socket(void); +extern void delete_ctl_socket(void); + +extern bool listening; /* should we pay attention to IKE messages? */ + + +/* interface: a terminal point for IKE traffic, IPsec transport mode + * and IPsec tunnels. + * Essentially: + * - an IP device (eg. eth1), and + * - its partner, an ipsec device (eg. ipsec0), and + * - their shared IP address (eg. 10.7.3.2) + * Note: the port for IKE is always implicitly UDP/pluto_port. + */ +struct iface { + char *vname; /* virtual (ipsec) device name */ + char *rname; /* real device name */ + ip_address addr; /* interface IP address */ + int fd; /* file descriptor of socket for IKE UDP messages */ + struct iface *next; + bool ike_float; + enum { IFN_ADD, IFN_KEEP, IFN_DELETE } change; +}; + +extern struct iface *interfaces; /* public interfaces */ + +extern bool use_interface(const char *rifn); +extern void find_ifaces(void); +extern void show_ifaces_status(void); +extern void free_ifaces(void); +extern void show_debug_status(void); +extern void call_server(void); + +/* in rcv_info.c */ +extern err_t init_info_socket(void); +extern void delete_info_socket(void); diff --git a/src/pluto/sha1.c b/src/pluto/sha1.c new file mode 100644 index 000000000..bbf062876 --- /dev/null +++ b/src/pluto/sha1.c @@ -0,0 +1,193 @@ +/* +SHA-1 in C +By Steve Reid <steve@edmweb.com> +100% Public Domain + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#define SHA1HANDSOFF + +#include <string.h> +#include <sys/types.h> /* for u_int*_t */ +#include <endian.h> /* sets BYTE_ORDER, LITTLE_ENDIAN, and BIG_ENDIAN */ + +#include "sha1.h" + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#if BYTE_ORDER == LITTLE_ENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#elif BYTE_ORDER == BIG_ENDIAN +#define blk0(i) block->l[i] +#else +#error "Endianness not defined!" +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +void SHA1Transform(u_int32_t state[5], const unsigned char buffer[64]) +{ +u_int32_t a, b, c, d, e; +typedef union { + unsigned char c[64]; + u_int32_t l[16]; +} CHAR64LONG16; +#ifdef SHA1HANDSOFF +CHAR64LONG16 block[1]; /* use array to appear as a pointer */ + memcpy(block, buffer, 64); +#else + /* The following had better never be used because it causes the + * pointer-to-const buffer to be cast into a pointer to non-const. + * And the result is written through. I threw a "const" in, hoping + * this will cause a diagnostic. + */ +CHAR64LONG16* block = (const CHAR64LONG16*)buffer; +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +#ifdef SHA1HANDSOFF + memset(block, '\0', sizeof(block)); +#endif +} + + +/* SHA1Init - Initialize new context */ + +void SHA1Init(SHA1_CTX* context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ + +void SHA1Update(SHA1_CTX* context, const unsigned char* data, u_int32_t len) +{ +u_int32_t i; +u_int32_t j; + + j = context->count[0]; + if ((context->count[0] += len << 3) < j) + context->count[1]++; + context->count[1] += (len>>29); + j = (j >> 3) & 63; + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +void SHA1Final(unsigned char digest[20], SHA1_CTX* context) +{ +unsigned i; +unsigned char finalcount[8]; +unsigned char c; + +#if 0 /* untested "improvement" by DHR */ + /* Convert context->count to a sequence of bytes + * in finalcount. Second element first, but + * big-endian order within element. + * But we do it all backwards. + */ + unsigned char *fcp = &finalcount[8]; + + for (i = 0; i < 2; i++) + { + u_int32_t t = context->count[i]; + int j; + + for (j = 0; j < 4; t >>= 8, j++) + *--fcp = (unsigned char) t + } +#else + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } +#endif + c = 0200; + SHA1Update(context, &c, 1); + while ((context->count[0] & 504) != 448) { + c = 0000; + SHA1Update(context, &c, 1); + } + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + /* Wipe variables */ + memset(context, '\0', sizeof(*context)); + memset(&finalcount, '\0', sizeof(finalcount)); +} diff --git a/src/pluto/sha1.h b/src/pluto/sha1.h new file mode 100644 index 000000000..64b3d2f5d --- /dev/null +++ b/src/pluto/sha1.h @@ -0,0 +1,16 @@ +/* +SHA-1 in C +By Steve Reid <steve@edmweb.com> +100% Public Domain +*/ + +typedef struct { + u_int32_t state[5]; + u_int32_t count[2]; + unsigned char buffer[64]; +} SHA1_CTX; + +void SHA1Transform(u_int32_t state[5], const unsigned char buffer[64]); +void SHA1Init(SHA1_CTX* context); +void SHA1Update(SHA1_CTX* context, const unsigned char* data, u_int32_t len); +void SHA1Final(unsigned char digest[20], SHA1_CTX* context); diff --git a/src/pluto/smallprime.c b/src/pluto/smallprime.c new file mode 100644 index 000000000..87497d096 --- /dev/null +++ b/src/pluto/smallprime.c @@ -0,0 +1,122 @@ +/* smallprime.c - List of small primes + * Copyright (C) 1998 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifdef PLUTO +#include <gmp.h> +#include <freeswan.h> +#include "constants.h" +#include "defs.h" +#include "gcryptfix.h" +#else +/* #include <config.h> */ +/* #include <stdio.h> */ +/* #include <stdlib.h> */ +/* #include "util.h" */ +/* #include "types.h" */ +#endif + +/* Note: 2 is not included because it can be tested more easily + * by looking at bit 0. The last entry in this list is marked by a zero + */ +ushort +small_prime_numbers[] = { + 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, + 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, + 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, + 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, + 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, + 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, + 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, + 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, + 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, + 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, + 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, + 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, + 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, + 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, + 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, + 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, + 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, + 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, + 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, + 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, + 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, + 1279, 1283, 1289, 1291, 1297, 1301, 1303, 1307, + 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, + 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, + 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, + 1499, 1511, 1523, 1531, 1543, 1549, 1553, 1559, + 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, + 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, + 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, + 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, + 1801, 1811, 1823, 1831, 1847, 1861, 1867, 1871, + 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, + 1933, 1949, 1951, 1973, 1979, 1987, 1993, 1997, + 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, + 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, + 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, + 2179, 2203, 2207, 2213, 2221, 2237, 2239, 2243, + 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, + 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, + 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, + 2417, 2423, 2437, 2441, 2447, 2459, 2467, 2473, + 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, + 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, + 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, + 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, + 2731, 2741, 2749, 2753, 2767, 2777, 2789, 2791, + 2797, 2801, 2803, 2819, 2833, 2837, 2843, 2851, + 2857, 2861, 2879, 2887, 2897, 2903, 2909, 2917, + 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, + 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, + 3067, 3079, 3083, 3089, 3109, 3119, 3121, 3137, + 3163, 3167, 3169, 3181, 3187, 3191, 3203, 3209, + 3217, 3221, 3229, 3251, 3253, 3257, 3259, 3271, + 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, + 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, + 3407, 3413, 3433, 3449, 3457, 3461, 3463, 3467, + 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, + 3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, + 3593, 3607, 3613, 3617, 3623, 3631, 3637, 3643, + 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, + 3719, 3727, 3733, 3739, 3761, 3767, 3769, 3779, + 3793, 3797, 3803, 3821, 3823, 3833, 3847, 3851, + 3853, 3863, 3877, 3881, 3889, 3907, 3911, 3917, + 3919, 3923, 3929, 3931, 3943, 3947, 3967, 3989, + 4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, + 4051, 4057, 4073, 4079, 4091, 4093, 4099, 4111, + 4127, 4129, 4133, 4139, 4153, 4157, 4159, 4177, + 4201, 4211, 4217, 4219, 4229, 4231, 4241, 4243, + 4253, 4259, 4261, 4271, 4273, 4283, 4289, 4297, + 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, + 4397, 4409, 4421, 4423, 4441, 4447, 4451, 4457, + 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, + 4523, 4547, 4549, 4561, 4567, 4583, 4591, 4597, + 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657, + 4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, + 4733, 4751, 4759, 4783, 4787, 4789, 4793, 4799, + 4801, 4813, 4817, 4831, 4861, 4871, 4877, 4889, + 4903, 4909, 4919, 4931, 4933, 4937, 4943, 4951, + 4957, 4967, 4969, 4973, 4987, 4993, 4999, + 0 +}; + + diff --git a/src/pluto/smartcard.c b/src/pluto/smartcard.c new file mode 100644 index 000000000..744f8a6f3 --- /dev/null +++ b/src/pluto/smartcard.c @@ -0,0 +1,1956 @@ +/* Support of smartcards and cryptotokens + * Copyright (C) 2003 Christoph Gysin, Simon Zwahlen + * Copyright (C) 2004 David Buechi, Michael Meier + * Zuercher Hochschule Winterthur, Switzerland + * + * Copyright (C) 2005 Michael Joosten + * + * Copyright (C) 2005 Andreas Steffen + * Hochschule für Technik Rapperswil, Switzerland + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: smartcard.c,v 1.41 2006/01/04 21:03:52 as Exp $ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <time.h> +#include <dlfcn.h> + +#include <freeswan.h> +#include <ipsec_policy.h> + +#include "constants.h" + +#ifdef SMARTCARD +#include "rsaref/unix.h" +#include "rsaref/pkcs11.h" +#endif + +#include "defs.h" +#include "mp_defs.h" +#include "log.h" +#include "x509.h" +#include "ca.h" +#include "certs.h" +#include "keys.h" +#include "smartcard.h" +#include "whack.h" +#include "fetch.h" + +#define DEFAULT_BASE 16 + +/* chained list of smartcard records */ +static smartcard_t *smartcards = NULL; + +/* number of generated sc objects */ +static int sc_number = 0; + +const smartcard_t empty_sc = { + NULL , /* next */ + 0 , /* last_load */ + { CERT_NONE, {NULL} }, /* last_cert */ + 0 , /* count */ + 0 , /* number */ + 999999 , /* slot */ + NULL , /* id */ + NULL , /* label */ + { NULL, 0 } , /* pin */ + FALSE , /* pinpad */ + FALSE , /* valid */ + FALSE , /* session_opened */ + FALSE , /* logged_in */ + TRUE , /* any_slot */ + 0L , /* session */ +}; + +#ifdef SMARTCARD /* compile with smartcard support */ + +#define SCX_MAGIC 0xd00bed00 + +struct scx_pkcs11_module { + u_int _magic; + void *handle; +}; + +typedef struct scx_pkcs11_module scx_pkcs11_module_t; + +/* PKCS #11 cryptoki context */ +static bool scx_initialized = FALSE; +static scx_pkcs11_module_t *pkcs11_module = NULL_PTR; +static CK_FUNCTION_LIST_PTR pkcs11_functions = NULL_PTR; + +/* crytoki v2.11 - return values of PKCS #11 functions*/ + +static const char *const pkcs11_return_name[] = { + "CKR_OK", + "CKR_CANCEL", + "CKR_HOST_MEMORY", + "CKR_SLOT_ID_INVALID", + "CKR_FLAGS_INVALID", + "CKR_GENERAL_ERROR", + "CKR_FUNCTION_FAILED", + "CKR_ARGUMENTS_BAD", + "CKR_NO_EVENT", + "CKR_NEED_TO_CREATE_THREADS", + "CKR_CANT_LOCK" + }; + +static const char *const pkcs11_return_name_10[] = { + "CKR_ATTRIBUTE_READ_ONLY", + "CKR_ATTRIBUTE_SENSITIVE", + "CKR_ATTRIBUTE_TYPE_INVALID", + "CKR_ATTRIBUTE_VALUE_INVALID" + }; + +static const char *const pkcs11_return_name_20[] = { + "CKR_DATA_INVALID", + "CKR_DATA_LEN_RANGE" + }; + +static const char *const pkcs11_return_name_30[] = { + "CKR_DEVICE_ERROR", + "CKR_DEVICE_MEMORY", + "CKR_DEVICE_REMOVED" + }; + +static const char *const pkcs11_return_name_40[] = { + "CKR_ENCRYPTED_DATA_INVALID", + "CKR_ENCRYPTED_DATA_LEN_RANGE" + }; + +static const char *const pkcs11_return_name_50[] = { + "CKR_FUNCTION_CANCELED", + "CKR_FUNCTION_NOT_PARALLEL", + "CKR_0x52_UNDEFINED", + "CKR_0x53_UNDEFINED", + "CKR_FUNCTION_NOT_SUPPORTED" + }; + +static const char *const pkcs11_return_name_60[] = { + "CKR_KEY_HANDLE_INVALID", + "CKR_KEY_SENSITIVE", + "CKR_KEY_SIZE_RANGE", + "CKR_KEY_TYPE_INCONSISTENT", + "CKR_KEY_NOT_NEEDED", + "CKR_KEY_CHANGED", + "CKR_KEY_NEEDED", + "CKR_KEY_INDIGESTIBLE", + "CKR_KEY_FUNCTION_NOT_PERMITTED", + "CKR_KEY_NOT_WRAPPABLE", + "CKR_KEY_UNEXTRACTABLE" + }; + +static const char *const pkcs11_return_name_70[] = { + "CKR_MECHANISM_INVALID", + "CKR_MECHANISM_PARAM_INVALID" + }; + +static const char *const pkcs11_return_name_80[] = { + "CKR_OBJECT_HANDLE_INVALID" + }; + +static const char *const pkcs11_return_name_90[] = { + "CKR_OPERATION_ACTIVE", + "CKR_OPERATION_NOT_INITIALIZED" + }; + +static const char *const pkcs11_return_name_A0[] = { + "CKR_PIN_INCORRECT", + "CKR_PIN_INVALID", + "CKR_PIN_LEN_RANGE", + "CKR_PIN_EXPIRED", + "CKR_PIN_LOCKED" + }; + +static const char *const pkcs11_return_name_B0[] = { + "CKR_SESSION_CLOSED", + "CKR_SESSION_COUNT", + "CKR_0xB2_UNDEFINED", + "CKR_SESSION_HANDLE_INVALID", + "CKR_SESSION_PARALLEL_NOT_SUPPORTED", + "CKR_SESSION_READ_ONLY", + "CKR_SESSION_EXISTS", + "CKR_SESSION_READ_ONLY_EXISTS", + "CKR_SESSION_READ_WRITE_SO_EXISTS" + }; + +static const char *const pkcs11_return_name_C0[] = { + "CKR_SIGNATURE_INVALID", + "CKR_SIGNATURE_LEN_RANGE" + }; + +static const char *const pkcs11_return_name_D0[] = { + "CKR_TEMPLATE_INCOMPLETE", + "CKR_TEMPLATE_INCONSISTENT" + }; + +static const char *const pkcs11_return_name_E0[] = { + "CKR_TOKEN_NOT_PRESENT", + "CKR_TOKEN_NOT_RECOGNIZED", + "CKR_TOKEN_WRITE_PROTECTED" + }; + +static const char *const pkcs11_return_name_F0[] = { + "CKR_UNWRAPPING_KEY_HANDLE_INVALID", + "CKR_UNWRAPPING_KEY_SIZE_RANGE", + "CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT" + }; + +static const char *const pkcs11_return_name_100[] = { + "CKR_USER_ALREADY_LOGGED_IN", + "CKR_USER_NOT_LOGGED_IN", + "CKR_USER_PIN_NOT_INITIALIZED", + "CKR_USER_TYPE_INVALID", + "CKR_USER_ANOTHER_ALREADY_LOGGED_IN", + "CKR_USER_TOO_MANY_TYPES" + }; + +static const char *const pkcs11_return_name_110[] = { + "CKR_WRAPPED_KEY_INVALID", + "CKR_0x111_UNDEFINED", + "CKR_WRAPPED_KEY_LEN_RANGE", + "CKR_WRAPPING_KEY_HANDLE_INVALID", + "CKR_WRAPPING_KEY_SIZE_RANGE", + "CKR_WRAPPING_KEY_TYPE_INCONSISTENT" + }; + +static const char *const pkcs11_return_name_120[] = { + "CKR_RANDOM_SEED_NOT_SUPPORTED", + "CKR_RANDOM_NO_RNG" + }; + +static const char *const pkcs11_return_name_130[] = { + "CKR_DOMAIN_PARAMS_INVALID" + }; + +static const char *const pkcs11_return_name_150[] = { + "CKR_BUFFER_TOO_SMALL" + }; + +static const char *const pkcs11_return_name_160[] = { + "CKR_SAVED_STATE_INVALID" + }; + +static const char *const pkcs11_return_name_170[] = { + "CKR_INFORMATION_SENSITIVE" + }; + +static const char *const pkcs11_return_name_180[] = { + "CKR_STATE_UNSAVEABLE" + }; + +static const char *const pkcs11_return_name_190[] = { + "CKR_CRYPTOKI_NOT_INITIALIZED", + "CKR_CRYPTOKI_ALREADY_INITIALIZED" + }; + +static const char *const pkcs11_return_name_1A0[] = { + "CKR_MUTEX_BAD", + "CKR_MUTEX_NOT_LOCKED" + }; + +static const char *const pkcs11_return_name_200[] = { + "CKR_FUNCTION_REJECTED" + }; + +static const char *const pkcs11_return_name_vendor[] = { + "CKR_VENDOR_DEFINED" + }; + +static enum_names pkcs11_return_names_vendor = + { CKR_VENDOR_DEFINED, CKR_VENDOR_DEFINED + , pkcs11_return_name_vendor, NULL }; + +static enum_names pkcs11_return_names_200 = + { CKR_FUNCTION_REJECTED, CKR_FUNCTION_REJECTED + , pkcs11_return_name_200, &pkcs11_return_names_vendor }; + +static enum_names pkcs11_return_names_1A0 = + { CKR_MUTEX_BAD, CKR_MUTEX_NOT_LOCKED + , pkcs11_return_name_1A0, &pkcs11_return_names_200 }; + +static enum_names pkcs11_return_names_190 = + { CKR_CRYPTOKI_NOT_INITIALIZED, CKR_CRYPTOKI_ALREADY_INITIALIZED + , pkcs11_return_name_190, &pkcs11_return_names_1A0 }; + +static enum_names pkcs11_return_names_180 = + { CKR_STATE_UNSAVEABLE, CKR_STATE_UNSAVEABLE + , pkcs11_return_name_180, &pkcs11_return_names_190 }; + +static enum_names pkcs11_return_names_170 = + { CKR_INFORMATION_SENSITIVE, CKR_INFORMATION_SENSITIVE + , pkcs11_return_name_170, &pkcs11_return_names_180 }; + +static enum_names pkcs11_return_names_160 = + { CKR_SAVED_STATE_INVALID, CKR_SAVED_STATE_INVALID + , pkcs11_return_name_160, &pkcs11_return_names_170 }; + +static enum_names pkcs11_return_names_150 = + { CKR_BUFFER_TOO_SMALL, CKR_BUFFER_TOO_SMALL + , pkcs11_return_name_150, &pkcs11_return_names_160 }; + +static enum_names pkcs11_return_names_130 = + { CKR_DOMAIN_PARAMS_INVALID, CKR_DOMAIN_PARAMS_INVALID + , pkcs11_return_name_130, &pkcs11_return_names_150 }; + +static enum_names pkcs11_return_names_120 = + { CKR_RANDOM_SEED_NOT_SUPPORTED, CKR_RANDOM_NO_RNG + , pkcs11_return_name_120, &pkcs11_return_names_130 }; + +static enum_names pkcs11_return_names_110 = + { CKR_WRAPPED_KEY_INVALID, CKR_WRAPPING_KEY_TYPE_INCONSISTENT + , pkcs11_return_name_110, &pkcs11_return_names_120 }; + +static enum_names pkcs11_return_names_100 = + { CKR_USER_ALREADY_LOGGED_IN, CKR_USER_TOO_MANY_TYPES + , pkcs11_return_name_100, &pkcs11_return_names_110 }; + +static enum_names pkcs11_return_names_F0 = + { CKR_UNWRAPPING_KEY_HANDLE_INVALID, CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT + , pkcs11_return_name_F0, &pkcs11_return_names_100 }; + +static enum_names pkcs11_return_names_E0 = + { CKR_TOKEN_NOT_PRESENT, CKR_TOKEN_WRITE_PROTECTED + , pkcs11_return_name_E0, &pkcs11_return_names_F0 }; + +static enum_names pkcs11_return_names_D0 = + { CKR_TEMPLATE_INCOMPLETE, CKR_TEMPLATE_INCONSISTENT + , pkcs11_return_name_D0,&pkcs11_return_names_E0 }; + +static enum_names pkcs11_return_names_C0 = + { CKR_SIGNATURE_INVALID, CKR_SIGNATURE_LEN_RANGE + , pkcs11_return_name_C0, &pkcs11_return_names_D0 }; + +static enum_names pkcs11_return_names_B0 = + { CKR_SESSION_CLOSED, CKR_SESSION_READ_WRITE_SO_EXISTS + , pkcs11_return_name_B0, &pkcs11_return_names_C0 }; + +static enum_names pkcs11_return_names_A0 = + { CKR_PIN_INCORRECT, CKR_PIN_LOCKED + , pkcs11_return_name_A0, &pkcs11_return_names_B0 }; + +static enum_names pkcs11_return_names_90 = + { CKR_OPERATION_ACTIVE, CKR_OPERATION_NOT_INITIALIZED + , pkcs11_return_name_90, &pkcs11_return_names_A0 }; + +static enum_names pkcs11_return_names_80 = + { CKR_OBJECT_HANDLE_INVALID, CKR_OBJECT_HANDLE_INVALID + , pkcs11_return_name_80, &pkcs11_return_names_90 }; + +static enum_names pkcs11_return_names_70 = + { CKR_MECHANISM_INVALID, CKR_MECHANISM_PARAM_INVALID + , pkcs11_return_name_70, &pkcs11_return_names_80 }; + +static enum_names pkcs11_return_names_60 = + { CKR_KEY_HANDLE_INVALID, CKR_KEY_UNEXTRACTABLE + , pkcs11_return_name_60, &pkcs11_return_names_70 }; + +static enum_names pkcs11_return_names_50 = + { CKR_FUNCTION_CANCELED, CKR_FUNCTION_NOT_SUPPORTED + , pkcs11_return_name_50, &pkcs11_return_names_60 }; + +static enum_names pkcs11_return_names_40 = + { CKR_ENCRYPTED_DATA_INVALID, CKR_ENCRYPTED_DATA_LEN_RANGE + , pkcs11_return_name_40, &pkcs11_return_names_50 }; + +static enum_names pkcs11_return_names_30 = + { CKR_DEVICE_ERROR, CKR_DEVICE_REMOVED + , pkcs11_return_name_30, &pkcs11_return_names_40 }; + +static enum_names pkcs11_return_names_20 = + { CKR_DATA_INVALID, CKR_DATA_LEN_RANGE + , pkcs11_return_name_20, &pkcs11_return_names_30 }; + +static enum_names pkcs11_return_names_10 = + { CKR_ATTRIBUTE_READ_ONLY, CKR_ATTRIBUTE_VALUE_INVALID + , pkcs11_return_name_10, &pkcs11_return_names_20}; + +static enum_names pkcs11_return_names = + { CKR_OK, CKR_CANT_LOCK + , pkcs11_return_name, &pkcs11_return_names_10}; + +/* + * Unload a PKCS#11 module. + * The calling application is responsible for cleaning up + * and calling C_Finalize() + */ +static CK_RV +scx_unload_pkcs11_module(scx_pkcs11_module_t *mod) +{ + if (!mod || mod->_magic != SCX_MAGIC) + return CKR_ARGUMENTS_BAD; + + if (dlclose(mod->handle) < 0) + return CKR_FUNCTION_FAILED; + + memset(mod, 0, sizeof(*mod)); + pfree(mod); + return CKR_OK; +} + +static scx_pkcs11_module_t* +scx_load_pkcs11_module(const char *name, CK_FUNCTION_LIST_PTR_PTR funcs) +{ + CK_RV (*c_get_function_list)(CK_FUNCTION_LIST_PTR_PTR); + scx_pkcs11_module_t *mod; + void *handle; + int rv; + + if (name == NULL || *name == '\0') + return NULL; + + /* Try to load PKCS#11 library module*/ + handle = dlopen(name, RTLD_NOW); + if (handle == NULL) + return NULL; + + mod = alloc_thing(scx_pkcs11_module_t, "scx_pkcs11_module"); + mod->_magic = SCX_MAGIC; + mod->handle = handle; + + /* Get the list of function pointers */ + c_get_function_list = (CK_RV (*)(CK_FUNCTION_LIST_PTR_PTR)) + dlsym(mod->handle, "C_GetFunctionList"); + if (!c_get_function_list) + goto failed; + + rv = c_get_function_list(funcs); + if (rv == CKR_OK) + return mod; + +failed: scx_unload_pkcs11_module(mod); + return NULL; +} + +/* + * retrieve a certificate object + */ +static bool +scx_find_cert_object(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object +, smartcard_t *sc, cert_t *cert) +{ + size_t hex_len, label_len; + u_char *hex_id = NULL; + chunk_t blob; + x509cert_t *x509cert; + + CK_ATTRIBUTE attr[] = { + { CKA_ID, NULL_PTR, 0L }, + { CKA_LABEL, NULL_PTR, 0L }, + { CKA_VALUE, NULL_PTR, 0L } + }; + + /* initialize the return argument */ + *cert = empty_cert; + + /* get the length of the attributes first */ + CK_RV rv = pkcs11_functions->C_GetAttributeValue(session, object, attr, 3); + if (rv != CKR_OK) + { + plog("couldn't read the attribute sizes: %s" + , enum_show(&pkcs11_return_names, rv)); + return FALSE; + } + + pfreeany(sc->label); + + hex_id = alloc_bytes(attr[0].ulValueLen, "hex id"); + hex_len = attr[0].ulValueLen; + sc->label = alloc_bytes(attr[1].ulValueLen + 1, "sc label"); + label_len = attr[1].ulValueLen; + blob.ptr = alloc_bytes(attr[2].ulValueLen, "x509cert blob"); + blob.len = attr[2].ulValueLen; + + attr[0].pValue = hex_id; + attr[1].pValue = sc->label; + attr[2].pValue = blob.ptr; + + /* now get the attributes */ + rv = pkcs11_functions->C_GetAttributeValue(session, object, attr, 3); + if (rv != CKR_OK) + { + plog("couldn't read the attributes: %s" + , enum_show(&pkcs11_return_names, rv)); + pfree(hex_id); + pfreeany(sc->label); + pfree(blob.ptr); + return FALSE; + } + + pfreeany(sc->id); + + /* convert id from hex to ASCII */ + sc->id = alloc_bytes(2*hex_len + 1, " sc id"); + datatot(hex_id, hex_len, 16, sc->id, 2*hex_len + 1); + pfree(hex_id); + + /* safeguard in case the label is not null terminated */ + sc->label[label_len] = '\0'; + + /* parse the retrieved cert */ + x509cert = alloc_thing(x509cert_t, "x509cert"); + *x509cert = empty_x509cert; + x509cert->smartcard = TRUE; + + if (!parse_x509cert(blob, 0, x509cert)) + { + plog("failed to load cert from smartcard, error in X.509 certificate"); + free_x509cert(x509cert); + return FALSE; + } + cert->type = CERT_X509_SIGNATURE; + cert->u.x509 = x509cert; + return TRUE; +} + +/* + * search a given slot for PKCS#11 certificate objects + */ +static void +scx_find_cert_objects(CK_SLOT_ID slot, CK_SESSION_HANDLE session) +{ + CK_RV rv; + CK_OBJECT_CLASS class = CKO_CERTIFICATE; + CK_ATTRIBUTE attr[] = {{ CKA_CLASS, &class, sizeof(class) }}; + + rv = pkcs11_functions->C_FindObjectsInit(session, attr, 1); + if (rv != CKR_OK) + { + plog("error in C_FindObjectsInit: %s" + , enum_show(&pkcs11_return_names, rv)); + return; + } + + for (;;) + { + CK_OBJECT_HANDLE object; + CK_ULONG obj_count = 0; + err_t ugh; + time_t valid_until; + smartcard_t *sc; + x509cert_t *cert; + + rv = pkcs11_functions->C_FindObjects(session, &object, 1, &obj_count); + if (rv != CKR_OK) + { + plog("error in C_FindObjects: %s" + , enum_show(&pkcs11_return_names, rv)); + break; + } + + /* no objects left */ + if (obj_count == 0) + break; + + /* create and initialize a new smartcard object */ + sc = alloc_thing(smartcard_t, "smartcard"); + *sc = empty_sc; + sc->any_slot = FALSE; + sc->slot = slot; + + if (!scx_find_cert_object(session, object, sc, &sc->last_cert)) + { + scx_free(sc); + continue; + } + DBG(DBG_CONTROL, + DBG_log("found cert in %s with id: %s, label: '%s'" + , scx_print_slot(sc, ""), sc->id, sc->label) + ) + + /* check validity of certificate */ + cert = sc->last_cert.u.x509; + valid_until = cert->notAfter; + ugh = check_validity(cert, &valid_until); + if (ugh != NULL) + { + plog(" %s", ugh); + free_x509cert(cert); + scx_free(sc); + continue; + } + else + { + DBG(DBG_CONTROL, + DBG_log(" certificate is valid") + ) + } + + sc = scx_add(sc); + + /* put end entity and ca certificates into different chains */ + if (cert->isCA) + add_authcert(cert, AUTH_CA); + else + { + add_x509_public_key(cert, valid_until, DAL_LOCAL); + sc->last_cert.u.x509 = add_x509cert(cert); + } + + share_cert(sc->last_cert); + time(&sc->last_load); + } + + rv = pkcs11_functions->C_FindObjectsFinal(session); + if (rv != CKR_OK) + { + plog("error in C_FindObjectsFinal: %s" + , enum_show(&pkcs11_return_names, rv)); + } +} + +/* + * search all slots for PKCS#11 certificate objects + */ +static void +scx_find_all_cert_objects(void) +{ + CK_RV rv; + CK_SLOT_ID_PTR slots = NULL_PTR; + CK_ULONG slot_count = 0; + CK_ULONG i; + + if (!scx_initialized) + { + plog("pkcs11 module not initialized"); + return; + } + + /* read size, always returns CKR_OK ! */ + rv = pkcs11_functions->C_GetSlotList(FALSE, NULL_PTR, &slot_count); + + /* allocate memory for the slots */ + slots = (CK_SLOT_ID *)alloc_bytes(slot_count * sizeof(CK_SLOT_ID), "slots"); + + rv = pkcs11_functions->C_GetSlotList(FALSE, slots, &slot_count); + if (rv != CKR_OK) + { + plog("error in C_GetSlotList: %s", enum_show(&pkcs11_return_names, rv)); + pfreeany(slots); + return; + } + + /* look in every slot for certificate objects */ + for (i = 0; i < slot_count; i++) + { + CK_SLOT_ID slot = slots[i]; + CK_SLOT_INFO info; + CK_SESSION_HANDLE session; + + rv = pkcs11_functions->C_GetSlotInfo(slot, &info); + + if (rv != CKR_OK) + { + plog("error in C_GetSlotInfo: %s" + , enum_show(&pkcs11_return_names, rv)); + continue; + } + + if (!(info.flags & CKF_TOKEN_PRESENT)) + { + plog("no token present in slot %lu", slot); + continue; + } + + rv = pkcs11_functions->C_OpenSession(slot + , CKF_SERIAL_SESSION, NULL_PTR, NULL_PTR, &session); + if (rv != CKR_OK) + { + plog("failed to open a session on slot %lu: %s" + , slot, enum_show(&pkcs11_return_names, rv)); + continue; + } + DBG(DBG_CONTROLMORE, + DBG_log("pkcs11 session #%ld for searching slot %lu", session, slot) + ) + scx_find_cert_objects(slot, session); + + rv = pkcs11_functions->C_CloseSession(session); + if (rv != CKR_OK) + { + plog("error in C_CloseSession: %s" + , enum_show(&pkcs11_return_names, rv)); + } + } + pfreeany(slots); +} +#endif + +/* + * load and initialize PKCS#11 cryptoki module + */ +void +scx_init(const char* module) +{ +#ifdef SMARTCARD + CK_RV rv; + + if (scx_initialized) + { + plog("weird - pkcs11 module seems already to be initialized"); + return; + } + + if (module == NULL) +#ifdef PKCS11_DEFAULT_LIB + module = PKCS11_DEFAULT_LIB; +#else + { + plog("no pkcs11 module defined"); + return; + } +#endif + + DBG(DBG_CONTROL | DBG_CRYPT, + DBG_log("pkcs11 module '%s' loading...", module) + ) + pkcs11_module = scx_load_pkcs11_module(module, &pkcs11_functions); + if (pkcs11_module == NULL) + { + plog("failed to load pkcs11 module '%s'", module); + return; + } + + DBG(DBG_CONTROL | DBG_CRYPT, + DBG_log("pkcs11 module initializing...") + ) + rv = pkcs11_functions->C_Initialize(NULL); + if (rv != CKR_OK) + { + plog("failed to initialize pkcs11 module: %s" + , enum_show(&pkcs11_return_names, rv)); + return; + } + + scx_initialized = TRUE; + DBG(DBG_CONTROL | DBG_CRYPT, + DBG_log("pkcs11 module loaded and initialized") + ) + + scx_find_all_cert_objects(); +#endif +} + +/* + * finalize and unload PKCS#11 cryptoki module + */ +void +scx_finalize(void) +{ +#ifdef SMARTCARD + while (smartcards != NULL) + { + scx_release(smartcards); + } + + if (pkcs11_functions != NULL_PTR) + { + pkcs11_functions->C_Finalize(NULL_PTR); + pkcs11_functions = NULL_PTR; + } + + if (pkcs11_module != NULL) + { + scx_unload_pkcs11_module(pkcs11_module); + pkcs11_module = NULL; + } + + scx_initialized = FALSE; + DBG(DBG_CONTROL | DBG_CRYPT, + DBG_log("pkcs11 module finalized and unloaded") + ) +#endif +} + +/* + * does a filename contain the token %smartcard? + */ +bool +scx_on_smartcard(const char *filename) +{ + return strncmp(filename, SCX_TOKEN, strlen(SCX_TOKEN)) == 0; +} + +#ifdef SMARTCARD +/* + * find a specific object on the smartcard + */ +static bool +scx_pkcs11_find_object( CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE_PTR object, + CK_OBJECT_CLASS class, + const char* id) +{ + size_t len; + char buf[BUF_LEN]; + CK_RV rv; + CK_ULONG obj_count = 0; + CK_ULONG attr_count = 1; + + CK_ATTRIBUTE attr[] = { + { CKA_CLASS, &class, sizeof(class) }, + { CKA_ID, &buf, 0L } + }; + + if (id != NULL) + { + ttodata(id, strlen(id), 16, buf, BUF_LEN, &len); + attr[1].ulValueLen = len; + attr_count = 2; + } + + /* get info for certificate with id */ + rv = pkcs11_functions->C_FindObjectsInit(session, attr, attr_count); + if (rv != CKR_OK) + { + plog("error in C_FindObjectsInit: %s" + , enum_show(&pkcs11_return_names, rv)); + return FALSE; + } + + rv = pkcs11_functions->C_FindObjects(session, object, 1, &obj_count); + if (rv != CKR_OK) + { + plog("error in C_FindObjects: %s" + , enum_show(&pkcs11_return_names, rv)); + return FALSE; + } + + rv = pkcs11_functions->C_FindObjectsFinal(session); + if (rv != CKR_OK) + { + plog("error in C_FindObjectsFinal: %s" + , enum_show(&pkcs11_return_names, rv)); + return FALSE; + } + + return (obj_count != 0); +} + +/* + * check if a given certificate object id is found in a slot + */ +static bool +scx_find_cert_id_in_slot(smartcard_t *sc, CK_SLOT_ID slot) +{ + CK_SESSION_HANDLE session; + CK_OBJECT_HANDLE object; + CK_SLOT_INFO info; + + CK_RV rv = pkcs11_functions->C_GetSlotInfo(slot, &info); + + if (rv != CKR_OK) + { + plog("error in C_GetSlotInfo: %s" + , enum_show(&pkcs11_return_names, rv)); + return FALSE; + } + + if (!(info.flags & CKF_TOKEN_PRESENT)) + { + plog("no token present in slot %lu", slot); + return FALSE; + } + + rv = pkcs11_functions->C_OpenSession(slot + , CKF_SERIAL_SESSION, NULL_PTR, NULL_PTR, &session); + if (rv != CKR_OK) + { + plog("failed to open a session on slot %lu: %s" + , slot, enum_show(&pkcs11_return_names, rv)); + return FALSE; + } + DBG(DBG_CONTROLMORE, + DBG_log("pkcs11 session #%ld for searching slot %lu", session, slot) + ) + + /* check if there is a certificate on the card in the specified slot */ + if (scx_pkcs11_find_object(session, &object, CKO_CERTIFICATE, sc->id)) + { + sc->slot = slot; + sc->any_slot = FALSE; + sc->session = session; + sc->session_opened = TRUE; + return TRUE; + } + + rv = pkcs11_functions->C_CloseSession(session); + if (rv != CKR_OK) + { + plog("error in C_CloseSession: %s" + , enum_show(&pkcs11_return_names, rv)); + } + return FALSE; +} +#endif + +/* + * Connect to the smart card in the reader and select the correct slot + */ +bool +scx_establish_context(smartcard_t *sc) +{ +#ifdef SMARTCARD + bool id_found = FALSE; + + if (!scx_initialized) + { + plog("pkcs11 module not initialized"); + return FALSE; + } + + if (sc->session_opened) + { + DBG(DBG_CONTROL | DBG_CRYPT, + DBG_log("pkcs11 session #%ld already open", sc->session) + ) + return TRUE; + } + + if (!sc->any_slot) + id_found = scx_find_cert_id_in_slot(sc, sc->slot); + + if (!id_found) + { + CK_RV rv; + CK_SLOT_ID slot; + CK_SLOT_ID_PTR slots = NULL_PTR; + CK_ULONG slot_count = 0; + CK_ULONG i; + + /* read size, always returns CKR_OK ! */ + rv = pkcs11_functions->C_GetSlotList(FALSE, NULL_PTR, &slot_count); + + /* allocate memory for the slots */ + slots = (CK_SLOT_ID *)alloc_bytes(slot_count * sizeof(CK_SLOT_ID), "slots"); + + rv = pkcs11_functions->C_GetSlotList(FALSE, slots, &slot_count); + if (rv != CKR_OK) + { + plog("error in C_GetSlotList: %s" + , enum_show(&pkcs11_return_names, rv)); + pfreeany(slots); + return FALSE; + } + + /* look in every slot for a certificate with a given object ID */ + for (i = 0; i < slot_count; i++) + { + slot = slots[i]; + id_found = scx_find_cert_id_in_slot(sc, slot); + if (id_found) + break; + } + pfreeany(slots) + } + + if (id_found) + { + DBG(DBG_CONTROL | DBG_CRYPT, + DBG_log("found token with id %s in slot %lu", sc->id, sc->slot); + DBG_log("pkcs11 session #%ld opened", sc->session) + ) + } + else + { + plog(" no certificate with id %s found on smartcard", sc->id); + } + return id_found; +#else + plog("warning: SMARTCARD support is deactivated in pluto/Makefile!"); + return FALSE; +#endif +} + +/* + * log in to a session + */ +bool +scx_login(smartcard_t *sc) +{ +#ifdef SMARTCARD + CK_RV rv; + + if (sc->logged_in) + { + DBG(DBG_CONTROL | DBG_CRYPT, + DBG_log("pkcs11 session #%ld login already done", sc->session) + ) + return TRUE; + } + + if (sc->pin.ptr == NULL) + { + plog("unable to log in without PIN!"); + return FALSE; + } + + if (!sc->session_opened) + { + plog("session not opened"); + return FALSE; + } + + rv = pkcs11_functions->C_Login(sc->session, CKU_USER + , (CK_UTF8CHAR *) sc->pin.ptr, sc->pin.len); + if (rv != CKR_OK && rv != CKR_USER_ALREADY_LOGGED_IN) + { + plog("unable to login: %s" + , enum_show(&pkcs11_return_names, rv)); + return FALSE; + } + DBG(DBG_CONTROL | DBG_CRYPT, + DBG_log("pkcs11 session #%ld login successful", sc->session) + ) + sc->logged_in = TRUE; + return TRUE; +#else + return FALSE; +#endif +} + +#ifdef SMARTCARD +/* + * logout from a session + */ +static void +scx_logout(smartcard_t *sc) +{ + CK_RV rv; + + rv = pkcs11_functions->C_Logout(sc->session); + if (rv != CKR_OK) + plog("error in C_Logout: %s" + , enum_show(&pkcs11_return_names, rv)); + else + DBG(DBG_CONTROL | DBG_CRYPT, + DBG_log("pkcs11 session #%ld logout", sc->session) + ) + sc->logged_in = FALSE; +} +#endif + + +/* + * Release context and disconnect from card + */ +void +scx_release_context(smartcard_t *sc) +{ +#ifdef SMARTCARD + CK_RV rv; + + if (!scx_initialized) + return; + + if (sc->session_opened) + { + if (sc->logged_in) + scx_logout(sc); + + sc->session_opened = FALSE; + + rv = pkcs11_functions->C_CloseSession(sc->session); + if (rv != CKR_OK) + plog("error in C_CloseSession: %s" + , enum_show(&pkcs11_return_names, rv)); + else + DBG(DBG_CONTROL | DBG_CRYPT, + DBG_log("pkcs11 session #%ld closed", sc->session) + ) + } +#endif +} + +/* + * Load host certificate from smartcard + */ +bool +scx_load_cert(const char *filename, smartcard_t **scp, cert_t *cert +, bool *cached) +{ +#ifdef SMARTCARD /* compile with smartcard support */ + CK_OBJECT_HANDLE object; + + const char *number_slot_id = filename + strlen(SCX_TOKEN); + + smartcard_t *sc = scx_add(scx_parse_number_slot_id(number_slot_id)); + + /* return the smartcard object */ + *scp = sc; + + /* is there a cached smartcard certificate? */ + *cached = sc->last_cert.type != CERT_NONE + && (time(NULL) - sc->last_load) < SCX_CERT_CACHE_INTERVAL; + + if (*cached) + { + *cert = sc->last_cert; + plog(" using cached cert from smartcard #%d (%s, id: %s, label: '%s')" + , sc->number + , scx_print_slot(sc, "") + , sc->id + , sc->label); + return TRUE; + } + + if (!scx_establish_context(sc)) + { + scx_release_context(sc); + return FALSE; + } + + /* find the certificate object */ + if (!scx_pkcs11_find_object(sc->session, &object, CKO_CERTIFICATE, sc->id)) + { + scx_release_context(sc); + return FALSE; + } + + /* retrieve the certificate object */ + if (!scx_find_cert_object(sc->session, object, sc, cert)) + { + scx_release_context(sc); + return FALSE; + } + + if (!pkcs11_keep_state) + scx_release_context(sc); + + plog(" loaded cert from smartcard #%d (%s, id: %s, label: '%s')" + , sc->number + , scx_print_slot(sc, "") + , sc->id + , sc->label); + + return TRUE; +#else + plog(" warning: SMARTCARD support is deactivated in pluto/Makefile!"); + return FALSE; +#endif +} + +/* + * parse slot number and key id + * the following syntax is allowed + * number slot id + * %smartcard 1 - - + * %smartcard#2 2 - - + * %smartcard0 - 0 - + * %smartcard:45 - - 45 + * %smartcard0:45 - 0 45 + */ +smartcard_t* +scx_parse_number_slot_id(const char *number_slot_id) +{ + int len = strlen(number_slot_id); + smartcard_t *sc = alloc_thing(smartcard_t, "smartcard"); + + /* assign default values */ + *sc = empty_sc; + + if (len == 0) /* default: use certificate #1 */ + { + sc->number = 1; + } + else if (*number_slot_id == '#') /* #number scheme */ + { + err_t ugh; + unsigned long ul; + + ugh = atoul(number_slot_id+1, len-1 , 10, &ul); + if (ugh == NULL) + sc->number = (int)ul; + else + plog("error parsing smartcard number: %s", ugh); + } + else /* slot:id scheme */ + { + int slot_len = len; + char *p = strchr(number_slot_id, ':'); + + if (p != NULL) + { + int id_len = len - (p + 1 - number_slot_id); + slot_len -= (1 + id_len); + + if (id_len > 0) /* we have an id */ + sc->id = p + 1; + } + if (slot_len > 0) /* we have a slot */ + { + err_t ugh = NULL; + unsigned long ul; + + ugh = atoul(number_slot_id, slot_len, 10, &ul); + if (ugh == NULL) + { + sc->slot = ul; + sc->any_slot = FALSE; + } + else + plog("error parsing smartcard slot number: %s", ugh); + } + } + /* unshare the id string */ + sc->id = clone_str(sc->id, "key id"); + return sc; +} + +/* + * Verify pin on card + */ +bool +scx_verify_pin(smartcard_t *sc) +{ +#ifdef SMARTCARD + CK_RV rv; + + if (!sc->pinpad) + sc->valid = FALSE; + + if (sc->pin.ptr == NULL) + { + plog("unable to verify without PIN"); + return FALSE; + } + + /* establish context */ + if (!scx_establish_context(sc)) + { + scx_release_context(sc); + return FALSE; + } + + rv = pkcs11_functions->C_Login(sc->session, CKU_USER, + (CK_UTF8CHAR *) sc->pin.ptr, sc->pin.len); + if (rv == CKR_OK || rv == CKR_USER_ALREADY_LOGGED_IN) + { + sc->valid = TRUE; + sc->logged_in = TRUE; + DBG(DBG_CONTROL | DBG_CRYPT, + DBG_log((rv == CKR_OK) + ? "PIN code correct" + : "already logged in, no PIN entry required"); + DBG_log("pkcs11 session #%ld login successful", sc->session) + ) + } + else + { + DBG(DBG_CONTROL | DBG_CRYPT, + DBG_log("PIN code incorrect") + ) + } + if (!pkcs11_keep_state) + scx_release_context(sc); +#else + sc->valid = FALSE; +#endif + return sc->valid; +} + +/* + * Sign hash on smartcard + */ +bool +scx_sign_hash(smartcard_t *sc, const u_char *in, size_t inlen +, u_char *out, size_t outlen) +{ +#ifdef SMARTCARD + CK_RV rv; + CK_OBJECT_HANDLE object; + CK_ULONG siglen = (CK_ULONG)outlen; + CK_BBOOL sign_flag, decrypt_flag; + CK_ATTRIBUTE attr[] = { + { CKA_SIGN, &sign_flag, sizeof(sign_flag) }, + { CKA_DECRYPT, &decrypt_flag, sizeof(decrypt_flag) } + }; + + if (!sc->logged_in) + return FALSE; + + if (!scx_pkcs11_find_object(sc->session, &object, CKO_PRIVATE_KEY, sc->id)) + { + plog("unable to find private key with id '%s'", sc->id); + return FALSE; + } + + rv = pkcs11_functions->C_GetAttributeValue(sc->session, object, attr, 2); + if (rv != CKR_OK) + { + plog("couldn't read the private key attributes: %s" + , enum_show(&pkcs11_return_names, rv)); + return FALSE; + } + DBG(DBG_CONTROL, + DBG_log("RSA key flags: sign = %s, decrypt = %s" + , (sign_flag)? "true":"false" + , (decrypt_flag)? "true":"false") + ) + + if (sign_flag) + { + CK_MECHANISM mech = { CKM_RSA_PKCS, NULL_PTR, 0 }; + + rv = pkcs11_functions->C_SignInit(sc->session, &mech, object); + if (rv != CKR_OK) + { + plog("error in C_SignInit: %s" + , enum_show(&pkcs11_return_names, rv)); + return FALSE; + } + + rv = pkcs11_functions->C_Sign(sc->session, (CK_BYTE_PTR)in, inlen + , out, &siglen); + if (rv != CKR_OK) + { + plog("error in C_Sign: %s" + , enum_show(&pkcs11_return_names, rv)); + return FALSE; + } + } + else if (decrypt_flag) + { + CK_MECHANISM mech = { CKM_RSA_X_509, NULL_PTR, 0 }; + size_t padlen; + u_char *p = out ; + + /* PKCS#1 v1.5 8.1 encryption-block formatting */ + *p++ = 0x00; + *p++ = 0x01; /* BT (block type) 01 */ + padlen = outlen - 3 - inlen; + memset(p, 0xFF, padlen); + p += padlen; + *p++ = 0x00; + memcpy(p, in, inlen); + + rv = pkcs11_functions->C_DecryptInit(sc->session, &mech, object); + if (rv != CKR_OK) + { + plog("error in C_DecryptInit: %s" + , enum_show(&pkcs11_return_names, rv)); + return FALSE; + } + + rv = pkcs11_functions->C_Decrypt(sc->session, out, outlen + , out, &siglen); + if (rv != CKR_OK) + { + plog("error in C_Decrypt: %s" + , enum_show(&pkcs11_return_names, rv)); + return FALSE; + } + } + else + { + plog("private key has neither sign nor decrypt flag set"); + return FALSE; + } + + if (siglen > (CK_ULONG)outlen) + { + plog("signature length (%lu) larger than allocated buffer (%d)" + , siglen, (int)outlen); + return FALSE; + } + return TRUE; +#else + return FALSE; +#endif +} + +/* + * encrypt data block with an RSA public key + */ +bool +scx_encrypt(smartcard_t *sc, const u_char *in, size_t inlen +, u_char *out, size_t *outlen) +{ +#ifdef SMARTCARD + CK_RV rv; + CK_OBJECT_HANDLE object; + CK_ULONG len = (CK_ULONG)(*outlen); + CK_BBOOL encrypt_flag; + CK_ATTRIBUTE attr[] = { + { CKA_MODULUS, NULL_PTR, 0L }, + { CKA_PUBLIC_EXPONENT, NULL_PTR, 0L }, + { CKA_ENCRYPT, &encrypt_flag, sizeof(encrypt_flag) } + }; + CK_MECHANISM mech = { CKM_RSA_PKCS, NULL_PTR, 0 }; + + if (!scx_establish_context(sc)) + { + scx_release_context(sc); + return FALSE; + } + + if (!scx_pkcs11_find_object(sc->session, &object, CKO_PUBLIC_KEY, sc->id)) + { + plog("unable to find public key with id '%s'", sc->id); + return FALSE; + } + + rv = pkcs11_functions->C_GetAttributeValue(sc->session, object, attr, 3); + if (rv != CKR_OK) + { + plog("couldn't read the public key attributes: %s" + , enum_show(&pkcs11_return_names, rv)); + scx_release_context(sc); + return FALSE; + } + + if (!encrypt_flag) + { + plog("public key cannot be used for encryption"); + scx_release_context(sc); + return FALSE; + } + + /* there must be enough space left for the PKCS#1 v1.5 padding */ + if (inlen > attr[0].ulValueLen - 11) + { + plog("smartcard input data length (%d) exceeds maximum of %lu bytes" + , (int)inlen, attr[0].ulValueLen - 11); + if (!pkcs11_keep_state) + scx_release_context(sc); + return FALSE; + } + + rv = pkcs11_functions->C_EncryptInit(sc->session, &mech, object); + + if (rv != CKR_OK) + { + if (rv == CKR_FUNCTION_NOT_SUPPORTED) + { + RSA_public_key_t rsa; + chunk_t plain_text = {in, inlen}; + chunk_t cipher_text; + + DBG(DBG_CONTROL, + DBG_log("doing RSA encryption in software") + ) + attr[0].pValue = alloc_bytes(attr[0].ulValueLen, "modulus"); + attr[1].pValue = alloc_bytes(attr[1].ulValueLen, "exponent"); + + rv = pkcs11_functions->C_GetAttributeValue(sc->session, object, attr, 2); + if (rv != CKR_OK) + { + plog("couldn't read modulus and public exponent: %s" + , enum_show(&pkcs11_return_names, rv)); + pfree(attr[0].pValue); + pfree(attr[1].pValue); + scx_release_context(sc); + return FALSE; + } + rsa.k = attr[0].ulValueLen; + n_to_mpz(&rsa.n, attr[0].pValue, attr[0].ulValueLen); + n_to_mpz(&rsa.e, attr[1].pValue, attr[1].ulValueLen); + pfree(attr[0].pValue); + pfree(attr[1].pValue); + + cipher_text = RSA_encrypt(&rsa, plain_text); + free_RSA_public_content(&rsa); + if (cipher_text.ptr == NULL) + { + plog("smartcard input data length is too large"); + if (!pkcs11_keep_state) + scx_release_context(sc); + return FALSE; + } + + memcpy(out, cipher_text.ptr, cipher_text.len); + *outlen = cipher_text.len; + freeanychunk(cipher_text); + if (!pkcs11_keep_state) + scx_release_context(sc); + return TRUE; + } + else + { + plog("error in C_EncryptInit: %s" + , enum_show(&pkcs11_return_names, rv)); + scx_release_context(sc); + return FALSE; + } + } + + DBG(DBG_CONTROL, + DBG_log("doing RSA encryption on smartcard") + ) + rv = pkcs11_functions->C_Encrypt(sc->session, in, inlen + , out, &len); + if (rv != CKR_OK) + { + plog("error in C_Encrypt: %s" + , enum_show(&pkcs11_return_names, rv)); + scx_release_context(sc); + return FALSE; + } + if (!pkcs11_keep_state) + scx_release_context(sc); + + *outlen = (size_t)len; + return TRUE; +#else + return FALSE; +#endif +} +/* + * decrypt a data block with an RSA private key + */ +bool +scx_decrypt(smartcard_t *sc, const u_char *in, size_t inlen +, u_char *out, size_t *outlen) +{ +#ifdef SMARTCARD + CK_RV rv; + CK_OBJECT_HANDLE object; + CK_ULONG len = (CK_ULONG)(*outlen); + CK_BBOOL decrypt_flag; + CK_ATTRIBUTE attr[] = { + { CKA_DECRYPT, &decrypt_flag, sizeof(decrypt_flag) } + }; + CK_MECHANISM mech = { CKM_RSA_PKCS, NULL_PTR, 0 }; + + if (!scx_establish_context(sc) || !scx_login(sc)) + { + scx_release_context(sc); + return FALSE; + } + + if (!scx_pkcs11_find_object(sc->session, &object, CKO_PRIVATE_KEY, sc->id)) + { + plog("unable to find private key with id '%s'", sc->id); + return FALSE; + } + + rv = pkcs11_functions->C_GetAttributeValue(sc->session, object, attr, 1); + if (rv != CKR_OK) + { + plog("couldn't read the private key attributes: %s" + , enum_show(&pkcs11_return_names, rv)); + return FALSE; + } + + if (!decrypt_flag) + { + plog("private key cannot be used for decryption"); + scx_release_context(sc); + return FALSE; + } + + DBG(DBG_CONTROL, + DBG_log("doing RSA decryption on smartcard") + ) + rv = pkcs11_functions->C_DecryptInit(sc->session, &mech, object); + if (rv != CKR_OK) + { + plog("error in C_DecryptInit: %s" + , enum_show(&pkcs11_return_names, rv)); + scx_release_context(sc); + return FALSE; + } + + rv = pkcs11_functions->C_Decrypt(sc->session, in, inlen + , out, &len); + if (rv != CKR_OK) + { + plog("error in C_Decrypt: %s" + , enum_show(&pkcs11_return_names, rv)); + scx_release_context(sc); + return FALSE; + } + if (!pkcs11_keep_state) + scx_release_context(sc); + + *outlen = (size_t)len; + return TRUE; +#else + return FALSE; +#endif +} + +/* receive an encrypted data block via whack, + * decrypt it using a private RSA key and + * return the decrypted data block via whack + */ +bool +scx_op_via_whack(const char* msg, int inbase, int outbase, sc_op_t op +, const char* keyid, int whackfd) +{ + char inbuf[RSA_MAX_OCTETS]; + char outbuf[2*RSA_MAX_OCTETS + 1]; + size_t outlen = sizeof(inbuf); + size_t inlen; + smartcard_t *sc,*sc_new; + + const char *number_slot_id = ""; + + err_t ugh = ttodata(msg, 0, inbase, inbuf, sizeof(inbuf), &inlen); + + /* no prefix - use default base */ + if (ugh != NULL && inbase == 0) + ugh = ttodata(msg, 0, DEFAULT_BASE, inbuf, sizeof(inbuf), &inlen); + + if (ugh != NULL) + { + plog("format error in smartcard input data: %s", ugh); + return FALSE; + } + + if (keyid != NULL) + { + number_slot_id = (strncmp(keyid, SCX_TOKEN, strlen(SCX_TOKEN)) == 0) + ? keyid + strlen(SCX_TOKEN) : keyid; + } + + sc_new = scx_parse_number_slot_id(number_slot_id); + sc = scx_add(sc_new); + if (sc == sc_new) + scx_share(sc); + + DBG((op == SC_OP_ENCRYPT)? DBG_PRIVATE:DBG_RAW, + DBG_dump("smartcard input data:\n", inbuf, inlen) + ) + + if (op == SC_OP_DECRYPT) + { + if (!sc->valid && whackfd != NULL_FD) + scx_get_pin(sc, whackfd); + + if (!sc->valid) + { + loglog(RC_NOVALIDPIN, "cannot decrypt without valid PIN"); + return FALSE; + } + } + + DBG(DBG_CONTROL | DBG_CRYPT, + DBG_log("using RSA key from smartcard (slot: %d, id: %s)" + , (int)sc->slot, sc->id) + ) + + switch (op) + { + case SC_OP_ENCRYPT: + if (!scx_encrypt(sc, inbuf, inlen, inbuf, &outlen)) + return FALSE; + break; + case SC_OP_DECRYPT: + if (!scx_decrypt(sc, inbuf, inlen, inbuf, &outlen)) + return FALSE; + break; + default: + break; + } + + DBG((op == SC_OP_DECRYPT)? DBG_PRIVATE:DBG_RAW, + DBG_dump("smartcard output data:\n", inbuf, outlen) + ) + + if (outbase == 0) /* use default base */ + outbase = DEFAULT_BASE; + + if (outbase == 256) /* ascii plain text */ + whack_log(RC_COMMENT, "%.*s", (int)outlen, inbuf); + else + { + outlen = datatot(inbuf, outlen, outbase, outbuf, sizeof(outbuf)); + if (outlen == 0) + { + plog("error in output format conversion"); + return FALSE; + } + whack_log(RC_COMMENT, "%s", outbuf); + } + return TRUE; +} + + /* + * get length of RSA key in bytes + */ +size_t +scx_get_keylength(smartcard_t *sc) +{ +#ifdef SMARTCARD + CK_RV rv; + CK_OBJECT_HANDLE object; + CK_ATTRIBUTE attr[] = {{ CKA_MODULUS, NULL_PTR, 0}}; + + if (!sc->logged_in) + return FALSE; + + if (!scx_pkcs11_find_object(sc->session, &object, CKO_PRIVATE_KEY, sc->id)) + { + plog("unable to find private key with id '%s'", sc->id); + return FALSE; + } + + /* get the length of the private key */ + rv = pkcs11_functions->C_GetAttributeValue(sc->session, object + , (CK_ATTRIBUTE_PTR)&attr, 1); + if (rv != CKR_OK) + { + plog("failed to get key length: %s" + , enum_show(&pkcs11_return_names, rv)); + return FALSE; + } + + return attr[0].ulValueLen; /*Return key length in bytes */ +#else + return 0; +#endif +} + +/* + * prompt for pin and verify it + */ +bool +scx_get_pin(smartcard_t *sc, int whackfd) +{ +#ifdef SMARTCARD + char pin[BUF_LEN]; + int i, n; + + whack_log(RC_ENTERSECRET, "need PIN for #%d (%s, id: %s, label: '%s')" + , sc->number, scx_print_slot(sc, ""), sc->id, sc->label); + + for (i = 0; i < SCX_MAX_PIN_TRIALS; i++) + { + if (i > 0) + whack_log(RC_ENTERSECRET, "invalid PIN, please try again"); + + n = read(whackfd, pin, BUF_LEN); + + if (n == -1) + { + whack_log(RC_LOG_SERIOUS, "read(whackfd) failed"); + return FALSE; + } + + if (strlen(pin) == 0) + { + whack_log(RC_LOG_SERIOUS, "no PIN entered, aborted"); + return FALSE; + } + + sc->pin.ptr = pin; + sc->pin.len = strlen(pin); + + /* verify the pin */ + if (scx_verify_pin(sc)) + { + clonetochunk(sc->pin, pin, strlen(pin), "pin"); + break; + } + + /* wrong pin - we try another round */ + sc->pin = empty_chunk; + } + + if (sc->valid) + whack_log(RC_SUCCESS, "valid PIN"); + else + whack_log(RC_LOG_SERIOUS, "invalid PIN, too many trials"); +#else + sc->valid = FALSE; + whack_log(RC_LOG_SERIOUS, "SMARTCARD support is deactivated in pluto/Makefile!"); +#endif + return sc->valid; +} + + +/* + * free the pin code + */ +void +scx_free_pin(chunk_t *pin) +{ + if (pin->ptr != NULL) + { + /* clear pin field in memory */ + memset(pin->ptr, '\0', pin->len); + pfree(pin->ptr); + *pin = empty_chunk; + } +} + +/* + * frees a smartcard record + */ +void +scx_free(smartcard_t *sc) +{ + if (sc != NULL) + { + scx_release_context(sc); + pfreeany(sc->id); + pfreeany(sc->label); + scx_free_pin(&sc->pin); + pfree(sc); + } +} + +/* release of a smartcard record decreases the count by one + " the record is freed when the counter reaches zero + */ +void +scx_release(smartcard_t *sc) +{ + if (sc != NULL && --sc->count == 0) + { + smartcard_t **pp = &smartcards; + while (*pp != sc) + pp = &(*pp)->next; + *pp = sc->next; + release_cert(sc->last_cert); + scx_free(sc); + } +} + +/* + * compare two smartcard records by comparing their slots and ids + */ +static bool +scx_same(smartcard_t *a, smartcard_t *b) +{ + if (a->number && b->number) + { + /* same number */ + return a->number == b->number; + } + else + { + /* same id and/or same slot */ + return (!a->id || (b->id && streq(a->id, b->id))) + && (a->any_slot || b->any_slot || a->slot == b->slot); + } +} + +/* for each link pointing to the smartcard record + " increase the count by one + */ +void +scx_share(smartcard_t *sc) +{ + if (sc != NULL) + sc->count++; +} + +/* + * adds a smartcard record to the chained list + */ +smartcard_t* +scx_add(smartcard_t *smartcard) +{ + smartcard_t *sc = smartcards; + smartcard_t **psc = &smartcards; + + while (sc != NULL) + { + if (scx_same(smartcard, sc)) /* already in chain, free smartcard record */ + { + scx_free(smartcard); + return sc; + } + psc = &sc->next; + sc = sc->next; + } + + /* insert new smartcard record at the end of the chain */ + *psc = smartcard; + smartcard->number = ++sc_number; + smartcard->count = 1; + DBG(DBG_CONTROL | DBG_PARSING, + DBG_log(" smartcard #%d added", sc_number) + ) + return smartcard; +} + +/* + * get the smartcard that belongs to an X.509 certificate + */ +smartcard_t* +scx_get(x509cert_t *cert) +{ + smartcard_t *sc = smartcards; + + while (sc != NULL) + { + if (sc->last_cert.u.x509 == cert) + return sc; + sc = sc->next; + } + return NULL; +} + +/* + * prints either the slot number or 'any slot' + */ +char * +scx_print_slot(smartcard_t *sc, const char *whitespace) +{ + char *buf = temporary_cyclic_buffer(); + + if (sc->any_slot) + snprintf(buf, BUF_LEN, "any slot"); + else + snprintf(buf, BUF_LEN, "slot: %s%lu", whitespace, sc->slot); + return buf; +} + +/* + * list all smartcard info records in a chained list + */ +void +scx_list(bool utc) +{ + smartcard_t *sc = smartcards; + + if (sc != NULL) + { + whack_log(RC_COMMENT, " "); + whack_log(RC_COMMENT, "List of Smartcard Objects:"); + whack_log(RC_COMMENT, " "); + } + + while (sc != NULL) + { + whack_log(RC_COMMENT, "%s, #%d, count: %d" + , timetoa(&sc->last_load, utc) + , sc->number + , sc->count); + whack_log(RC_COMMENT, " %s, session %s, logged %s, has %s" + , scx_print_slot(sc, " ") + , sc->session_opened? "opened" : "closed" + , sc->logged_in? "in" : "out" + , sc->pinpad? "pin pad" + : ((sc->pin.ptr == NULL)? "no pin" + : sc->valid? "valid pin" : "invalid pin")); + if (sc->id != NULL) + whack_log(RC_COMMENT, " id: %s", sc->id); + if (sc->label != NULL) + whack_log(RC_COMMENT, " label: '%s'", sc->label); + if (sc->last_cert.type == CERT_X509_SIGNATURE) + { + char buf[BUF_LEN]; + + dntoa(buf, BUF_LEN, sc->last_cert.u.x509->subject); + whack_log(RC_COMMENT, " subject: '%s'", buf); + } + sc = sc->next; + } +} diff --git a/src/pluto/smartcard.h b/src/pluto/smartcard.h new file mode 100644 index 000000000..c004ca7dd --- /dev/null +++ b/src/pluto/smartcard.h @@ -0,0 +1,100 @@ +/* Support of smartcards and cryptotokens + * Copyright (C) 2003 Christoph Gysin, Simon Zwahlen + * Copyright (C) 2004 David Buechi, Michael Meier + * Zuercher Hochschule Winterthur + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: smartcard.h,v 1.14 2005/11/06 22:55:41 as Exp $ + */ + +#ifndef _SMARTCARD_H +#define _SMARTCARD_H + +#include "certs.h" + +#define SCX_TOKEN "%smartcard" +#define SCX_CERT_CACHE_INTERVAL 60 /* seconds */ +#define SCX_MAX_PIN_TRIALS 3 + +/* smartcard operations */ + +typedef enum { + SC_OP_NONE = 0, + SC_OP_ENCRYPT = 1, + SC_OP_DECRYPT = 2, + SC_OP_SIGN = 3, +} sc_op_t; + +/* smartcard record */ + +typedef struct smartcard smartcard_t; + +struct smartcard { + smartcard_t *next; + time_t last_load; + cert_t last_cert; + int count; + int number; + unsigned long slot; + char *id; + char *label; + chunk_t pin; + bool pinpad; + bool valid; + bool session_opened; + bool logged_in; + bool any_slot; + long session; +}; + +extern const smartcard_t empty_sc; + +/* keep a PKCS#11 login during the lifetime of pluto + * flag set in plutomain.c and used in ipsec_doi.c and ocsp.c + */ +extern bool pkcs11_keep_state; + +/* allow other applications access to pluto's PKCS#11 interface + * via whack. Could be used e.g. for disk encryption + */ +extern bool pkcs11_proxy; + +extern smartcard_t* scx_parse_number_slot_id(const char *number_slot_id); +extern void scx_init(const char *module); +extern void scx_finalize(void); +extern bool scx_establish_context(smartcard_t *sc); +extern bool scx_login(smartcard_t *sc); +extern bool scx_on_smartcard(const char *filename); +extern bool scx_load_cert(const char *filename, smartcard_t **scp + , cert_t *cert, bool *cached); +extern bool scx_verify_pin(smartcard_t *sc); +extern void scx_share(smartcard_t *sc); +extern bool scx_sign_hash(smartcard_t *sc, const u_char *in, size_t inlen + , u_char *out, size_t outlen); +extern bool scx_encrypt(smartcard_t *sc, const u_char *in, size_t inlen + , u_char *out, size_t *outlen); +extern bool scx_decrypt(smartcard_t *sc, const u_char *in, size_t inlen + , u_char *out, size_t *outlen); +extern bool scx_op_via_whack(const char* msg, int inbase, int outbase + , sc_op_t op, const char *keyid, int whackfd); +extern bool scx_get_pin(smartcard_t *sc, int whackfd); +extern size_t scx_get_keylength(smartcard_t *sc); +extern smartcard_t* scx_add(smartcard_t *sc); +extern smartcard_t* scx_get(x509cert_t *cert); +extern void scx_release(smartcard_t *sc); +extern void scx_release_context(smartcard_t *sc); +extern void scx_free_pin(chunk_t *pin); +extern void scx_free(smartcard_t *sc); +extern void scx_list(bool utc); +extern char *scx_print_slot(smartcard_t *sc, const char *whitespace); + +#endif /* _SMARTCARD_H */ diff --git a/src/pluto/spdb.c b/src/pluto/spdb.c new file mode 100644 index 000000000..996585135 --- /dev/null +++ b/src/pluto/spdb.c @@ -0,0 +1,2314 @@ +/* Security Policy Data Base (such as it is) + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: spdb.c,v 1.9 2006/04/22 21:59:20 as Exp $ + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/queue.h> + +#include <freeswan.h> +#include <ipsec_policy.h> + +#include "constants.h" +#include "defs.h" +#include "id.h" +#include "connections.h" +#include "state.h" +#include "packet.h" +#include "keys.h" +#include "kernel.h" +#include "log.h" +#include "spdb.h" +#include "whack.h" +#include "sha1.h" +#include "md5.h" +#include "crypto.h" /* requires sha1.h and md5.h */ +#include "alg_info.h" +#include "kernel_alg.h" +#include "ike_alg.h" +#include "db_ops.h" +#include "nat_traversal.h" + +#define AD(x) x, elemsof(x) /* Array Description */ +#define AD_NULL NULL, 0 + +/**************** Oakely (main mode) SA database ****************/ + +/* array of proposals to be conjoined (can only be one for Oakley) */ + +static struct db_prop oakley_pc[] = + { { PROTO_ISAKMP, AD_NULL } }; + +/* array of proposal conjuncts (can only be one) */ + +static struct db_prop_conj oakley_props[] = { { AD(oakley_pc) } }; + +/* the sadb entry */ +struct db_sa oakley_sadb = { AD(oakley_props) }; + +/**************** IPsec (quick mode) SA database ****************/ + +/* arrays of attributes for transforms */ + +static struct db_attr espsha1_attr[] = { + { AUTH_ALGORITHM, AUTH_ALGORITHM_HMAC_SHA1 }, + }; + +static struct db_attr ah_HMAC_SHA1_attr[] = { + { AUTH_ALGORITHM, AUTH_ALGORITHM_HMAC_SHA1 }, + }; + +/* arrays of transforms, each in in preference order */ + +static struct db_trans espa_trans[] = { + { ESP_3DES, AD(espsha1_attr) }, + }; + +static struct db_trans esp_trans[] = { + { ESP_3DES, AD_NULL }, + }; + +#ifdef SUPPORT_ESP_NULL +static struct db_trans espnull_trans[] = { + { ESP_NULL, AD(espsha1_attr) }, + }; +#endif /* SUPPORT_ESP_NULL */ + +static struct db_trans ah_trans[] = { + { AH_SHA, AD(ah_HMAC_SHA1_attr) }, + }; + +static struct db_trans ipcomp_trans[] = { + { IPCOMP_DEFLATE, AD_NULL }, + }; + +/* arrays of proposals to be conjoined */ + +static struct db_prop ah_pc[] = { + { PROTO_IPSEC_AH, AD(ah_trans) }, + }; + +#ifdef SUPPORT_ESP_NULL +static struct db_prop espnull_pc[] = { + { PROTO_IPSEC_ESP, AD(espnull_trans) }, + }; +#endif /* SUPPORT_ESP_NULL */ + +static struct db_prop esp_pc[] = { + { PROTO_IPSEC_ESP, AD(espa_trans) }, + }; + +static struct db_prop ah_esp_pc[] = { + { PROTO_IPSEC_AH, AD(ah_trans) }, + { PROTO_IPSEC_ESP, AD(esp_trans) }, + }; + +static struct db_prop compress_pc[] = { + { PROTO_IPCOMP, AD(ipcomp_trans) }, + }; + +static struct db_prop ah_compress_pc[] = { + { PROTO_IPSEC_AH, AD(ah_trans) }, + { PROTO_IPCOMP, AD(ipcomp_trans) }, + }; + +#ifdef SUPPORT_ESP_NULL +static struct db_prop espnull_compress_pc[] = { + { PROTO_IPSEC_ESP, AD(espnull_trans) }, + { PROTO_IPCOMP, AD(ipcomp_trans) }, + }; +#endif /* SUPPORT_ESP_NULL */ + +static struct db_prop esp_compress_pc[] = { + { PROTO_IPSEC_ESP, AD(espa_trans) }, + { PROTO_IPCOMP, AD(ipcomp_trans) }, + }; + +static struct db_prop ah_esp_compress_pc[] = { + { PROTO_IPSEC_AH, AD(ah_trans) }, + { PROTO_IPSEC_ESP, AD(esp_trans) }, + { PROTO_IPCOMP, AD(ipcomp_trans) }, + }; + +/* arrays of proposal alternatives (each element is a conjunction) */ + +static struct db_prop_conj ah_props[] = { + { AD(ah_pc) }, +#ifdef SUPPORT_ESP_NULL + { AD(espnull_pc) } +#endif + }; + +static struct db_prop_conj esp_props[] = + { { AD(esp_pc) } }; + +static struct db_prop_conj ah_esp_props[] = + { { AD(ah_esp_pc) } }; + +static struct db_prop_conj compress_props[] = { + { AD(compress_pc) }, + }; + +static struct db_prop_conj ah_compress_props[] = { + { AD(ah_compress_pc) }, +#ifdef SUPPORT_ESP_NULL + { AD(espnull_compress_pc) } +#endif + }; + +static struct db_prop_conj esp_compress_props[] = + { { AD(esp_compress_pc) } }; + +static struct db_prop_conj ah_esp_compress_props[] = + { { AD(ah_esp_compress_pc) } }; + +/* The IPsec sadb is subscripted by a bitset (subset of policy) + * with members from { POLICY_ENCRYPT, POLICY_AUTHENTICATE, POLICY_COMPRESS } + * shifted right by POLICY_IPSEC_SHIFT. + */ +struct db_sa ipsec_sadb[1 << 3] = { + { AD_NULL }, /* none */ + { AD(esp_props) }, /* POLICY_ENCRYPT */ + { AD(ah_props) }, /* POLICY_AUTHENTICATE */ + { AD(ah_esp_props) }, /* POLICY_ENCRYPT+POLICY_AUTHENTICATE */ + { AD(compress_props) }, /* POLICY_COMPRESS */ + { AD(esp_compress_props) }, /* POLICY_ENCRYPT+POLICY_COMPRESS */ + { AD(ah_compress_props) }, /* POLICY_AUTHENTICATE+POLICY_COMPRESS */ + { AD(ah_esp_compress_props) }, /* POLICY_ENCRYPT+POLICY_AUTHENTICATE+POLICY_COMPRESS */ + }; + +#undef AD +#undef AD_NULL + +/* output an attribute (within an SA) */ +static bool +out_attr(int type +, unsigned long val +, struct_desc *attr_desc +, enum_names **attr_val_descs USED_BY_DEBUG +, pb_stream *pbs) +{ + struct isakmp_attribute attr; + + if (val >> 16 == 0) + { + /* short value: use TV form */ + attr.isaat_af_type = type | ISAKMP_ATTR_AF_TV; + attr.isaat_lv = val; + if (!out_struct(&attr, attr_desc, pbs, NULL)) + return FALSE; + } + else + { + /* This is a real fudge! Since we rarely use long attributes + * and since this is the only place where we can cause an + * ISAKMP message length to be other than a multiple of 4 octets, + * we force the length of the value to be a multiple of 4 octets. + * Furthermore, we only handle values up to 4 octets in length. + * Voila: a fixed format! + */ + pb_stream val_pbs; + u_int32_t nval = htonl(val); + + attr.isaat_af_type = type | ISAKMP_ATTR_AF_TLV; + if (!out_struct(&attr, attr_desc, pbs, &val_pbs) + || !out_raw(&nval, sizeof(nval), &val_pbs, "long attribute value")) + return FALSE; + close_output_pbs(&val_pbs); + } + DBG(DBG_EMITTING, + enum_names *d = attr_val_descs[type]; + + if (d != NULL) + DBG_log(" [%lu is %s]" + , val, enum_show(d, val))); + return TRUE; +} +#define return_on(var, val) do { var=val;goto return_out; } while(0); +/* Output an SA, as described by a db_sa. + * This has the side-effect of allocating SPIs for us. + */ +bool +out_sa(pb_stream *outs +, struct db_sa *sadb +, struct state *st +, bool oakley_mode +, u_int8_t np) +{ + pb_stream sa_pbs; + int pcn; + bool ret = FALSE; + bool ah_spi_generated = FALSE + , esp_spi_generated = FALSE + , ipcomp_cpi_generated = FALSE; +#if !defined NO_KERNEL_ALG || !defined NO_IKE_ALG + struct db_context *db_ctx = NULL; +#endif + + /* SA header out */ + { + struct isakmp_sa sa; + + sa.isasa_np = np; + st->st_doi = sa.isasa_doi = ISAKMP_DOI_IPSEC; /* all we know */ + if (!out_struct(&sa, &isakmp_sa_desc, outs, &sa_pbs)) + return_on(ret, FALSE); + } + + /* within SA: situation out */ + st->st_situation = SIT_IDENTITY_ONLY; + if (!out_struct(&st->st_situation, &ipsec_sit_desc, &sa_pbs, NULL)) + return_on(ret, FALSE); + + /* within SA: Proposal Payloads + * + * Multiple Proposals with the same number are simultaneous + * (conjuncts) and must deal with different protocols (AH or ESP). + * Proposals with different numbers are alternatives (disjuncts), + * in preference order. + * Proposal numbers must be monotonic. + * See RFC 2408 "ISAKMP" 4.2 + */ + + for (pcn = 0; pcn != sadb->prop_conj_cnt; pcn++) + { + struct db_prop_conj *pc = &sadb->prop_conjs[pcn]; + int pn; + + for (pn = 0; pn != pc->prop_cnt; pn++) + { + struct db_prop *p = &pc->props[pn]; + pb_stream proposal_pbs; + struct isakmp_proposal proposal; + struct_desc *trans_desc; + struct_desc *attr_desc; + enum_names **attr_val_descs; + int tn; + bool tunnel_mode; + + tunnel_mode = (pn == pc->prop_cnt-1) + && (st->st_policy & POLICY_TUNNEL); + + /* Proposal header */ + proposal.isap_np = pcn == sadb->prop_conj_cnt-1 && pn == pc->prop_cnt-1 + ? ISAKMP_NEXT_NONE : ISAKMP_NEXT_P; + proposal.isap_proposal = pcn; + proposal.isap_protoid = p->protoid; + proposal.isap_spisize = oakley_mode ? 0 + : p->protoid == PROTO_IPCOMP ? IPCOMP_CPI_SIZE + : IPSEC_DOI_SPI_SIZE; + + /* In quick mode ONLY, create proposal for runtime kernel algos. + * Replace ESP proposals with runtime created one + */ + if (!oakley_mode && p->protoid == PROTO_IPSEC_ESP) + { + DBG(DBG_CONTROL | DBG_CRYPT, + if (st->st_connection->alg_info_esp) + { + static char buf[256]=""; + + alg_info_snprint(buf, sizeof (buf), + (struct alg_info *)st->st_connection->alg_info_esp); + DBG_log(buf); + } + ) + db_ctx = kernel_alg_db_new(st->st_connection->alg_info_esp, st->st_policy); + p = db_prop_get(db_ctx); + + if (!p || p->trans_cnt == 0) + { + loglog(RC_LOG_SERIOUS, + "empty IPSEC SA proposal to send " + "(no kernel algorithms for esp selection)"); + return_on(ret, FALSE); + } + } + + if (oakley_mode && p->protoid == PROTO_ISAKMP) + { + DBG(DBG_CONTROL | DBG_CRYPT, + if (st->st_connection->alg_info_ike) + { + static char buf[256]=""; + + alg_info_snprint(buf, sizeof (buf), + (struct alg_info *)st->st_connection->alg_info_ike); + DBG_log(buf); + } + ) + db_ctx = ike_alg_db_new(st->st_connection->alg_info_ike, st->st_policy); + p = db_prop_get(db_ctx); + + if (!p || p->trans_cnt == 0) + { + loglog(RC_LOG_SERIOUS, + "empty ISAKMP SA proposal to send " + "(no algorithms for ike selection?)"); + return_on(ret, FALSE); + } + } + + proposal.isap_notrans = p->trans_cnt; + if (!out_struct(&proposal, &isakmp_proposal_desc, &sa_pbs, &proposal_pbs)) + return_on(ret, FALSE); + + /* Per-protocols stuff: + * Set trans_desc. + * Set attr_desc. + * Set attr_val_descs. + * If not oakley_mode, emit SPI. + * We allocate SPIs on demand. + * All ESPs in an SA will share a single SPI. + * All AHs in an SAwill share a single SPI. + * AHs' SPI will be distinct from ESPs'. + * This latter is needed because KLIPS doesn't + * use the protocol when looking up a (dest, protocol, spi). + * ??? If multiple ESPs are composed, how should their SPIs + * be allocated? + */ + { + ipsec_spi_t *spi_ptr = NULL; + int proto = 0; + bool *spi_generated = NULL; + + switch (p->protoid) + { + case PROTO_ISAKMP: + passert(oakley_mode); + trans_desc = &isakmp_isakmp_transform_desc; + attr_desc = &isakmp_oakley_attribute_desc; + attr_val_descs = oakley_attr_val_descs; + /* no SPI needed */ + break; + case PROTO_IPSEC_AH: + passert(!oakley_mode); + trans_desc = &isakmp_ah_transform_desc; + attr_desc = &isakmp_ipsec_attribute_desc; + attr_val_descs = ipsec_attr_val_descs; + spi_ptr = &st->st_ah.our_spi; + spi_generated = &ah_spi_generated; + proto = IPPROTO_AH; + break; + case PROTO_IPSEC_ESP: + passert(!oakley_mode); + trans_desc = &isakmp_esp_transform_desc; + attr_desc = &isakmp_ipsec_attribute_desc; + attr_val_descs = ipsec_attr_val_descs; + spi_ptr = &st->st_esp.our_spi; + spi_generated = &esp_spi_generated; + proto = IPPROTO_ESP; + break; + case PROTO_IPCOMP: + passert(!oakley_mode); + trans_desc = &isakmp_ipcomp_transform_desc; + attr_desc = &isakmp_ipsec_attribute_desc; + attr_val_descs = ipsec_attr_val_descs; + + /* a CPI isn't quite the same as an SPI + * so we use specialized code to emit it. + */ + if (!ipcomp_cpi_generated) + { + st->st_ipcomp.our_spi = get_my_cpi( + &st->st_connection->spd, tunnel_mode); + if (st->st_ipcomp.our_spi == 0) + return_on(ret, FALSE); /* problem generating CPI */ + + ipcomp_cpi_generated = TRUE; + } + /* CPI is stored in network low order end of an + * ipsec_spi_t. So we start a couple of bytes in. + */ + if (!out_raw((u_char *)&st->st_ipcomp.our_spi + + IPSEC_DOI_SPI_SIZE - IPCOMP_CPI_SIZE + , IPCOMP_CPI_SIZE + , &proposal_pbs, "CPI")) + return_on(ret, FALSE); + break; + default: + bad_case(p->protoid); + } + if (spi_ptr != NULL) + { + if (!*spi_generated) + { + *spi_ptr = get_ipsec_spi(0 + , proto + , &st->st_connection->spd + , tunnel_mode); + if (*spi_ptr == 0) + return FALSE; + *spi_generated = TRUE; + } + if (!out_raw((u_char *)spi_ptr, IPSEC_DOI_SPI_SIZE + , &proposal_pbs, "SPI")) + return_on(ret, FALSE); + } + } + + /* within proposal: Transform Payloads */ + for (tn = 0; tn != p->trans_cnt; tn++) + { + struct db_trans *t = &p->trans[tn]; + pb_stream trans_pbs; + struct isakmp_transform trans; + int an; + + trans.isat_np = (tn == p->trans_cnt - 1) + ? ISAKMP_NEXT_NONE : ISAKMP_NEXT_T; + trans.isat_transnum = tn; + trans.isat_transid = t->transid; + if (!out_struct(&trans, trans_desc, &proposal_pbs, &trans_pbs)) + return_on(ret, FALSE); + + /* Within tranform: Attributes. */ + + /* For Phase 2 / Quick Mode, GROUP_DESCRIPTION is + * automatically generated because it must be the same + * in every transform. Except IPCOMP. + */ + if (p->protoid != PROTO_IPCOMP + && st->st_pfs_group != NULL) + { + passert(!oakley_mode); + passert(st->st_pfs_group != &unset_group); + out_attr(GROUP_DESCRIPTION, st->st_pfs_group->group + , attr_desc, attr_val_descs + , &trans_pbs); + } + + /* automatically generate duration + * and, for Phase 2 / Quick Mode, encapsulation. + */ + if (oakley_mode) + { + out_attr(OAKLEY_LIFE_TYPE, OAKLEY_LIFE_SECONDS + , attr_desc, attr_val_descs + , &trans_pbs); + out_attr(OAKLEY_LIFE_DURATION + , st->st_connection->sa_ike_life_seconds + , attr_desc, attr_val_descs + , &trans_pbs); + } + else + { + /* RFC 2407 (IPSEC DOI) 4.5 specifies that + * the default is "unspecified (host-dependent)". + * This makes little sense, so we always specify it. + * + * Unlike other IPSEC transforms, IPCOMP defaults + * to Transport Mode, so we can exploit the default + * (draft-shacham-ippcp-rfc2393bis-05.txt 4.1). + */ + if (p->protoid != PROTO_IPCOMP + || st->st_policy & POLICY_TUNNEL) + { +#ifndef I_KNOW_TRANSPORT_MODE_HAS_SECURITY_CONCERN_BUT_I_WANT_IT + if ((st->nat_traversal & NAT_T_DETECTED) + && !(st->st_policy & POLICY_TUNNEL)) + { + /* Inform user that we will not respect policy and only + * propose Tunnel Mode + */ + loglog(RC_LOG_SERIOUS, "NAT-Traversal: " + "Transport Mode not allowed due to security concerns -- " + "using Tunnel mode"); + } +#endif + out_attr(ENCAPSULATION_MODE +#ifdef I_KNOW_TRANSPORT_MODE_HAS_SECURITY_CONCERN_BUT_I_WANT_IT + , NAT_T_ENCAPSULATION_MODE(st, st->st_policy) +#else + /* If NAT-T is detected, use UDP_TUNNEL as long as Transport + * Mode has security concerns. + * + * User has been informed of that + */ + , NAT_T_ENCAPSULATION_MODE(st, POLICY_TUNNEL) +#endif + , attr_desc, attr_val_descs + , &trans_pbs); + } + out_attr(SA_LIFE_TYPE, SA_LIFE_TYPE_SECONDS + , attr_desc, attr_val_descs + , &trans_pbs); + out_attr(SA_LIFE_DURATION + , st->st_connection->sa_ipsec_life_seconds + , attr_desc, attr_val_descs + , &trans_pbs); + } + + /* spit out attributes from table */ + for (an = 0; an != t->attr_cnt; an++) + { + struct db_attr *a = &t->attrs[an]; + + out_attr(a->type, a->val + , attr_desc, attr_val_descs + , &trans_pbs); + } + + close_output_pbs(&trans_pbs); + } + close_output_pbs(&proposal_pbs); + } + /* end of a conjunction of proposals */ + } + close_output_pbs(&sa_pbs); + ret = TRUE; + +return_out: + +#if !defined NO_KERNEL_ALG || !defined NO_IKE_ALG + if (db_ctx) + db_destroy(db_ctx); +#endif + return ret; +} + +/* Handle long form of duration attribute. + * The code is can only handle values that can fit in unsigned long. + * "Clamping" is probably an acceptable way to impose this limitation. + */ +static u_int32_t +decode_long_duration(pb_stream *pbs) +{ + u_int32_t val = 0; + + /* ignore leading zeros */ + while (pbs_left(pbs) != 0 && *pbs->cur == '\0') + pbs->cur++; + + if (pbs_left(pbs) > sizeof(val)) + { + /* "clamp" too large value to max representable value */ + val -= 1; /* portable way to get to maximum value */ + DBG(DBG_PARSING, DBG_log(" too large duration clamped to: %lu" + , (unsigned long)val)); + } + else + { + /* decode number */ + while (pbs_left(pbs) != 0) + val = (val << BITS_PER_BYTE) | *pbs->cur++; + DBG(DBG_PARSING, DBG_log(" long duration: %lu", (unsigned long)val)); + } + return val; +} + +/* Preparse the body of an ISAKMP SA Payload and + * return body of ISAKMP Proposal Payload + * + * Only IPsec DOI is accepted (what is the ISAKMP DOI?). + * Error response is rudimentary. + */ +notification_t +preparse_isakmp_sa_body(const struct isakmp_sa *sa + , pb_stream *sa_pbs + , u_int32_t *ipsecdoisit + , pb_stream *proposal_pbs + , struct isakmp_proposal *proposal) +{ + /* DOI */ + if (sa->isasa_doi != ISAKMP_DOI_IPSEC) + { + loglog(RC_LOG_SERIOUS, "Unknown/unsupported DOI %s", enum_show(&doi_names, sa->isasa_doi)); + /* XXX Could send notification back */ + return DOI_NOT_SUPPORTED; + } + + /* Situation */ + if (!in_struct(ipsecdoisit, &ipsec_sit_desc, sa_pbs, NULL)) + return SITUATION_NOT_SUPPORTED; + + if (*ipsecdoisit != SIT_IDENTITY_ONLY) + { + loglog(RC_LOG_SERIOUS, "unsupported IPsec DOI situation (%s)" + , bitnamesof(sit_bit_names, *ipsecdoisit)); + /* XXX Could send notification back */ + return SITUATION_NOT_SUPPORTED; + } + + /* The rules for ISAKMP SAs are scattered. + * RFC 2409 "IKE" section 5 says that there + * can only be one SA, and it can have only one proposal in it. + * There may well be multiple transforms. + */ + if (!in_struct(proposal, &isakmp_proposal_desc, sa_pbs, proposal_pbs)) + return PAYLOAD_MALFORMED; + + if (proposal->isap_np != ISAKMP_NEXT_NONE) + { + loglog(RC_LOG_SERIOUS, "Proposal Payload must be alone in Oakley SA; found %s following Proposal" + , enum_show(&payload_names, proposal->isap_np)); + return PAYLOAD_MALFORMED; + } + + if (proposal->isap_protoid != PROTO_ISAKMP) + { + loglog(RC_LOG_SERIOUS, "unexpected Protocol ID (%s) found in Oakley Proposal" + , enum_show(&protocol_names, proposal->isap_protoid)); + return INVALID_PROTOCOL_ID; + } + + /* Just what should we accept for the SPI field? + * The RFC is sort of contradictory. We will ignore the SPI + * as long as it is of the proper size. + * + * From RFC2408 2.4 Identifying Security Associations: + * During phase 1 negotiations, the initiator and responder cookies + * determine the ISAKMP SA. Therefore, the SPI field in the Proposal + * payload is redundant and MAY be set to 0 or it MAY contain the + * transmitting entity's cookie. + * + * From RFC2408 3.5 Proposal Payload: + * o SPI Size (1 octet) - Length in octets of the SPI as defined by + * the Protocol-Id. In the case of ISAKMP, the Initiator and + * Responder cookie pair from the ISAKMP Header is the ISAKMP SPI, + * therefore, the SPI Size is irrelevant and MAY be from zero (0) to + * sixteen (16). If the SPI Size is non-zero, the content of the + * SPI field MUST be ignored. If the SPI Size is not a multiple of + * 4 octets it will have some impact on the SPI field and the + * alignment of all payloads in the message. The Domain of + * Interpretation (DOI) will dictate the SPI Size for other + * protocols. + */ + if (proposal->isap_spisize == 0) + { + /* empty (0) SPI -- fine */ + } + else if (proposal->isap_spisize <= MAX_ISAKMP_SPI_SIZE) + { + u_char junk_spi[MAX_ISAKMP_SPI_SIZE]; + + if (!in_raw(junk_spi, proposal->isap_spisize, proposal_pbs, "Oakley SPI")) + return PAYLOAD_MALFORMED; + } + else + { + loglog(RC_LOG_SERIOUS, "invalid SPI size (%u) in Oakley Proposal" + , (unsigned)proposal->isap_spisize); + return INVALID_SPI; + } + return NOTHING_WRONG; +} + +static struct { + u_int8_t *start; + u_int8_t *cur; + u_int8_t *roof; +} backup; + +/* + * backup the pointer into a pb_stream + */ +void +backup_pbs(pb_stream *pbs) +{ + backup.start = pbs->start; + backup.cur = pbs->cur; + backup.roof = pbs->roof; +} + +/* + * restore the pointer into a pb_stream + */ +void +restore_pbs(pb_stream *pbs) +{ + pbs->start = backup.start; + pbs->cur = backup.cur; + pbs->roof = backup.roof; +} + +/* + * Parse an ISAKMP Proposal Payload for RSA and PSK authentication policies + */ +notification_t +parse_isakmp_policy(pb_stream *proposal_pbs + , u_int notrans + , lset_t *policy) +{ + int last_transnum = -1; + + *policy = LEMPTY; + + while (notrans--) + { + pb_stream trans_pbs; + u_char *attr_start; + size_t attr_len; + struct isakmp_transform trans; + + if (!in_struct(&trans, &isakmp_isakmp_transform_desc, proposal_pbs, &trans_pbs)) + return BAD_PROPOSAL_SYNTAX; + + if (trans.isat_transnum <= last_transnum) + { + /* picky, picky, picky */ + loglog(RC_LOG_SERIOUS, "Transform Numbers are not monotonically increasing" + " in Oakley Proposal"); + return BAD_PROPOSAL_SYNTAX; + } + last_transnum = trans.isat_transnum; + + if (trans.isat_transid != KEY_IKE) + { + loglog(RC_LOG_SERIOUS, "expected KEY_IKE but found %s in Oakley Transform" + , enum_show(&isakmp_transformid_names, trans.isat_transid)); + return INVALID_TRANSFORM_ID; + } + + attr_start = trans_pbs.cur; + attr_len = pbs_left(&trans_pbs); + + /* preprocess authentication attributes only */ + while (pbs_left(&trans_pbs) != 0) + { + struct isakmp_attribute a; + pb_stream attr_pbs; + + if (!in_struct(&a, &isakmp_oakley_attribute_desc, &trans_pbs, &attr_pbs)) + return BAD_PROPOSAL_SYNTAX; + + passert((a.isaat_af_type & ISAKMP_ATTR_RTYPE_MASK) < 32); + + switch (a.isaat_af_type) + { + case OAKLEY_AUTHENTICATION_METHOD | ISAKMP_ATTR_AF_TV: + switch (a.isaat_lv) + { + case OAKLEY_PRESHARED_KEY: + *policy |= POLICY_PSK; + break; + case OAKLEY_RSA_SIG: + *policy |= POLICY_RSASIG; + break; + case XAUTHInitPreShared: + *policy |= POLICY_XAUTH_SERVER; + /* fall through */ + case XAUTHRespPreShared: + *policy |= POLICY_XAUTH_PSK; + break; + case XAUTHInitRSA: + *policy |= POLICY_XAUTH_SERVER; + /* fall through */ + case XAUTHRespRSA: + *policy |= POLICY_XAUTH_RSASIG; + break; + default: + break; + } + break; + default: + break; + } + } + } + DBG(DBG_CONTROL|DBG_PARSING, + DBG_log("preparse_isakmp_policy: peer requests %s authentication" + , prettypolicy(*policy)) + ) + return NOTHING_WRONG; +} + +/* + * check that we can find a preshared secret + */ +static err_t +find_preshared_key(struct state* st) +{ + err_t ugh = NULL; + struct connection *c = st->st_connection; + + if (get_preshared_secret(c) == NULL) + { + char my_id[BUF_LEN], his_id[BUF_LEN]; + + idtoa(&c->spd.this.id, my_id, sizeof(my_id)); + if (his_id_was_instantiated(c)) + strcpy(his_id, "%any"); + else + idtoa(&c->spd.that.id, his_id, sizeof(his_id)); + ugh = builddiag("Can't authenticate: no preshared key found for `%s' and `%s'" + , my_id, his_id); + } + return ugh; +} + +/* Parse the body of an ISAKMP SA Payload (i.e. Phase 1 / Main Mode). + * Various shortcuts are taken. In particular, the policy, such as + * it is, is hardwired. + * + * If r_sa is non-NULL, the body of an SA representing the selected + * proposal is emitted. + * + * This routine is used by main_inI1_outR1() and main_inR1_outI2(). + */ +notification_t +parse_isakmp_sa_body(u_int32_t ipsecdoisit + , pb_stream *proposal_pbs + , struct isakmp_proposal *proposal + , pb_stream *r_sa_pbs + , struct state *st + , bool initiator) +{ + struct connection *c = st->st_connection; + unsigned no_trans_left; + + /* for each transform payload... */ + no_trans_left = proposal->isap_notrans; + + for (;;) + { + pb_stream trans_pbs; + u_char *attr_start; + size_t attr_len; + struct isakmp_transform trans; + lset_t seen_attrs = 0; + lset_t seen_durations = 0; + u_int16_t life_type = 0; + struct oakley_trans_attrs ta; + err_t ugh = NULL; /* set to diagnostic when problem detected */ + + /* initialize only optional field in ta */ + ta.life_seconds = OAKLEY_ISAKMP_SA_LIFETIME_DEFAULT; /* When this SA expires (seconds) */ + + if (no_trans_left == 0) + { + loglog(RC_LOG_SERIOUS, "number of Transform Payloads disagrees with Oakley Proposal Payload"); + return BAD_PROPOSAL_SYNTAX; + } + + in_struct(&trans, &isakmp_isakmp_transform_desc, proposal_pbs, &trans_pbs); + attr_start = trans_pbs.cur; + attr_len = pbs_left(&trans_pbs); + + /* process all the attributes that make up the transform */ + + while (pbs_left(&trans_pbs) != 0) + { + struct isakmp_attribute a; + pb_stream attr_pbs; + u_int32_t val; /* room for larger values */ + + if (!in_struct(&a, &isakmp_oakley_attribute_desc, &trans_pbs, &attr_pbs)) + return BAD_PROPOSAL_SYNTAX; + + passert((a.isaat_af_type & ISAKMP_ATTR_RTYPE_MASK) < 32); + + if (LHAS(seen_attrs, a.isaat_af_type & ISAKMP_ATTR_RTYPE_MASK)) + { + loglog(RC_LOG_SERIOUS, "repeated %s attribute in Oakley Transform %u" + , enum_show(&oakley_attr_names, a.isaat_af_type) + , trans.isat_transnum); + return BAD_PROPOSAL_SYNTAX; + } + + seen_attrs |= LELEM(a.isaat_af_type & ISAKMP_ATTR_RTYPE_MASK); + + val = a.isaat_lv; + + DBG(DBG_PARSING, + { + enum_names *vdesc = oakley_attr_val_descs + [a.isaat_af_type & ISAKMP_ATTR_RTYPE_MASK]; + + if (vdesc != NULL) + { + const char *nm = enum_name(vdesc, val); + + if (nm != NULL) + DBG_log(" [%u is %s]", (unsigned)val, nm); + } + }); + + switch (a.isaat_af_type) + { + case OAKLEY_ENCRYPTION_ALGORITHM | ISAKMP_ATTR_AF_TV: + if (ike_alg_enc_present(val)) + { + ta.encrypt = val; + ta.encrypter = ike_alg_get_encrypter(val); + ta.enckeylen = ta.encrypter->keydeflen; + } + else + { + ugh = builddiag("%s is not supported" + , enum_show(&oakley_enc_names, val)); + } + break; + + case OAKLEY_HASH_ALGORITHM | ISAKMP_ATTR_AF_TV: + if (ike_alg_hash_present(val)) + { + ta.hash = val; + ta.hasher = ike_alg_get_hasher(val); + } + else + { + ugh = builddiag("%s is not supported" + , enum_show(&oakley_hash_names, val)); + } + break; + + case OAKLEY_AUTHENTICATION_METHOD | ISAKMP_ATTR_AF_TV: + { + /* check that authentication method is acceptable */ + lset_t iap = st->st_policy & POLICY_ID_AUTH_MASK; + + /* is the initiator the XAUTH client? */ + bool xauth_init = initiator && (st->st_policy & POLICY_XAUTH_SERVER) == LEMPTY + || !initiator && (st->st_policy & POLICY_XAUTH_SERVER) != LEMPTY; + + switch (val) + { + case OAKLEY_PRESHARED_KEY: + if ((iap & POLICY_PSK) == LEMPTY) + { + ugh = "policy does not allow OAKLEY_PRESHARED_KEY authentication"; + } + else + { + ugh = find_preshared_key(st); + ta.auth = OAKLEY_PRESHARED_KEY; + } + break; + case XAUTHInitPreShared: + if ((iap & POLICY_XAUTH_PSK) == LEMPTY || !xauth_init) + { + ugh = "policy does not allow XAUTHInitPreShared authentication"; + } + else + { + ugh = find_preshared_key(st); + ta.auth = XAUTHInitPreShared; + } + break; + case XAUTHRespPreShared: + if ((iap & POLICY_XAUTH_PSK) == LEMPTY || xauth_init) + { + ugh = "policy does not allow XAUTHRespPreShared authentication"; + } + else + { + ugh = find_preshared_key(st); + ta.auth = XAUTHRespPreShared; + } + break; + case OAKLEY_RSA_SIG: + /* Accept if policy specifies RSASIG or is default */ + if ((iap & POLICY_RSASIG) == LEMPTY) + { + ugh = "policy does not allow OAKLEY_RSA_SIG authentication"; + } + else + { + ta.auth = OAKLEY_RSA_SIG; + } + break; + case XAUTHInitRSA: + if ((iap & POLICY_XAUTH_RSASIG) == LEMPTY || !xauth_init) + { + ugh = "policy does not allow XAUTHInitRSA authentication"; + } + else + { + ta.auth = XAUTHInitRSA; + } + break; + case XAUTHRespRSA: + if ((iap & POLICY_XAUTH_RSASIG) == LEMPTY || xauth_init) + { + ugh = "policy does not allow XAUTHRespRSA authentication"; + } + else + { + ta.auth = XAUTHRespRSA; + } + break; + default: + ugh = builddiag("Pluto does not support %s authentication" + , enum_show(&oakley_auth_names, val)); + break; + } + } + break; + + case OAKLEY_GROUP_DESCRIPTION | ISAKMP_ATTR_AF_TV: + ta.group = lookup_group(val); + if (ta.group == NULL) + { + ugh = "only OAKLEY_GROUP_MODP1024 and OAKLEY_GROUP_MODP1536 supported"; + } + break; + + case OAKLEY_LIFE_TYPE | ISAKMP_ATTR_AF_TV: + switch (val) + { + case OAKLEY_LIFE_SECONDS: + case OAKLEY_LIFE_KILOBYTES: + if (LHAS(seen_durations, val)) + { + loglog(RC_LOG_SERIOUS + , "attribute OAKLEY_LIFE_TYPE value %s repeated" + , enum_show(&oakley_lifetime_names, val)); + return BAD_PROPOSAL_SYNTAX; + } + seen_durations |= LELEM(val); + life_type = val; + break; + default: + ugh = builddiag("unknown value %s" + , enum_show(&oakley_lifetime_names, val)); + break; + } + break; + + case OAKLEY_LIFE_DURATION | ISAKMP_ATTR_AF_TLV: + val = decode_long_duration(&attr_pbs); + /* fall through */ + case OAKLEY_LIFE_DURATION | ISAKMP_ATTR_AF_TV: + if (!LHAS(seen_attrs, OAKLEY_LIFE_TYPE)) + { + ugh = "OAKLEY_LIFE_DURATION attribute not preceded by OAKLEY_LIFE_TYPE attribute"; + break; + } + seen_attrs &= ~(LELEM(OAKLEY_LIFE_DURATION) | LELEM(OAKLEY_LIFE_TYPE)); + + switch (life_type) + { + case OAKLEY_LIFE_SECONDS: + if (val > OAKLEY_ISAKMP_SA_LIFETIME_MAXIMUM) + { +#ifdef CISCO_QUIRKS + plog("peer requested %lu seconds" + " which exceeds our limit %d seconds" + , (long) val + , OAKLEY_ISAKMP_SA_LIFETIME_MAXIMUM); + plog("lifetime reduced to %d seconds " + "(todo: IPSEC_RESPONDER_LIFETIME notification)" + , OAKLEY_ISAKMP_SA_LIFETIME_MAXIMUM); + val = OAKLEY_ISAKMP_SA_LIFETIME_MAXIMUM; +#else + ugh = builddiag("peer requested %lu seconds" + " which exceeds our limit %d seconds" + , (long) val + , OAKLEY_ISAKMP_SA_LIFETIME_MAXIMUM); +#endif + } + ta.life_seconds = val; + break; + case OAKLEY_LIFE_KILOBYTES: + ta.life_kilobytes = val; + break; + default: + bad_case(life_type); + } + break; + + case OAKLEY_KEY_LENGTH | ISAKMP_ATTR_AF_TV: + if ((seen_attrs & LELEM(OAKLEY_ENCRYPTION_ALGORITHM)) == 0) + { + ugh = "OAKLEY_KEY_LENGTH attribute not preceded by " + "OAKLEY_ENCRYPTION_ALGORITHM attribute"; + break; + } + if (ta.encrypter == NULL) + { + ugh = "NULL encrypter with seen OAKLEY_ENCRYPTION_ALGORITHM"; + break; + } + /* + * check if this keylen is compatible with specified algorithm + */ + if (val + && (val < ta.encrypter->keyminlen || val > ta.encrypter->keymaxlen)) + { + ugh = "peer proposed key length not valid for " + "encryption algorithm specified"; + } + ta.enckeylen = val; + break; +#if 0 /* not yet supported */ + case OAKLEY_GROUP_TYPE | ISAKMP_ATTR_AF_TV: + case OAKLEY_PRF | ISAKMP_ATTR_AF_TV: + case OAKLEY_FIELD_SIZE | ISAKMP_ATTR_AF_TV: + + case OAKLEY_GROUP_PRIME | ISAKMP_ATTR_AF_TV: + case OAKLEY_GROUP_PRIME | ISAKMP_ATTR_AF_TLV: + case OAKLEY_GROUP_GENERATOR_ONE | ISAKMP_ATTR_AF_TV: + case OAKLEY_GROUP_GENERATOR_ONE | ISAKMP_ATTR_AF_TLV: + case OAKLEY_GROUP_GENERATOR_TWO | ISAKMP_ATTR_AF_TV: + case OAKLEY_GROUP_GENERATOR_TWO | ISAKMP_ATTR_AF_TLV: + case OAKLEY_GROUP_CURVE_A | ISAKMP_ATTR_AF_TV: + case OAKLEY_GROUP_CURVE_A | ISAKMP_ATTR_AF_TLV: + case OAKLEY_GROUP_CURVE_B | ISAKMP_ATTR_AF_TV: + case OAKLEY_GROUP_CURVE_B | ISAKMP_ATTR_AF_TLV: + case OAKLEY_GROUP_ORDER | ISAKMP_ATTR_AF_TV: + case OAKLEY_GROUP_ORDER | ISAKMP_ATTR_AF_TLV: +#endif + default: + ugh = "unsupported OAKLEY attribute"; + break; + } + + if (ugh != NULL) + { + loglog(RC_LOG_SERIOUS, "%s. Attribute %s" + , ugh, enum_show(&oakley_attr_names, a.isaat_af_type)); + break; + } + } + + /* + * ML: at last check for allowed transforms in alg_info_ike + * (ALG_INFO_F_STRICT flag) + */ + if (ugh == NULL) + { + if (!ike_alg_ok_final(ta.encrypt, ta.enckeylen, ta.hash, + ta.group ? ta.group->group : -1, c->alg_info_ike)) + { + ugh = "OAKLEY proposal refused"; + } + } + + if (ugh == NULL) + { + /* a little more checking is in order */ + { + lset_t missing + = ~seen_attrs + & (LELEM(OAKLEY_ENCRYPTION_ALGORITHM) + | LELEM(OAKLEY_HASH_ALGORITHM) + | LELEM(OAKLEY_AUTHENTICATION_METHOD) + | LELEM(OAKLEY_GROUP_DESCRIPTION)); + + if (missing) + { + loglog(RC_LOG_SERIOUS, "missing mandatory attribute(s) %s in Oakley Transform %u" + , bitnamesof(oakley_attr_bit_names, missing) + , trans.isat_transnum); + return BAD_PROPOSAL_SYNTAX; + } + } + /* We must have liked this transform. + * Lets finish early and leave. + */ + + DBG(DBG_PARSING | DBG_CRYPT + , DBG_log("Oakley Transform %u accepted", trans.isat_transnum)); + + if (r_sa_pbs != NULL) + { + struct isakmp_proposal r_proposal = *proposal; + pb_stream r_proposal_pbs; + struct isakmp_transform r_trans = trans; + pb_stream r_trans_pbs; + + /* Situation */ + if (!out_struct(&ipsecdoisit, &ipsec_sit_desc, r_sa_pbs, NULL)) + impossible(); + + /* Proposal */ +#ifdef EMIT_ISAKMP_SPI + r_proposal.isap_spisize = COOKIE_SIZE; +#else + r_proposal.isap_spisize = 0; +#endif + r_proposal.isap_notrans = 1; + if (!out_struct(&r_proposal, &isakmp_proposal_desc, r_sa_pbs, &r_proposal_pbs)) + impossible(); + + /* SPI */ +#ifdef EMIT_ISAKMP_SPI + if (!out_raw(my_cookie, COOKIE_SIZE, &r_proposal_pbs, "SPI")) + impossible(); + r_proposal.isap_spisize = COOKIE_SIZE; +#else + /* none (0) */ +#endif + + /* Transform */ + r_trans.isat_np = ISAKMP_NEXT_NONE; + if (!out_struct(&r_trans, &isakmp_isakmp_transform_desc, &r_proposal_pbs, &r_trans_pbs)) + impossible(); + + if (!out_raw(attr_start, attr_len, &r_trans_pbs, "attributes")) + impossible(); + close_output_pbs(&r_trans_pbs); + close_output_pbs(&r_proposal_pbs); + close_output_pbs(r_sa_pbs); + } + + /* copy over the results */ + st->st_oakley = ta; + return NOTHING_WRONG; + } + + /* on to next transform */ + no_trans_left--; + + if (trans.isat_np == ISAKMP_NEXT_NONE) + { + if (no_trans_left != 0) + { + loglog(RC_LOG_SERIOUS, "number of Transform Payloads disagrees with Oakley Proposal Payload"); + return BAD_PROPOSAL_SYNTAX; + } + break; + } + if (trans.isat_np != ISAKMP_NEXT_T) + { + loglog(RC_LOG_SERIOUS, "unexpected %s payload in Oakley Proposal" + , enum_show(&payload_names, proposal->isap_np)); + return BAD_PROPOSAL_SYNTAX; + } + } + loglog(RC_LOG_SERIOUS, "no acceptable Oakley Transform"); + return NO_PROPOSAL_CHOSEN; +} + +/* Parse the body of an IPsec SA Payload (i.e. Phase 2 / Quick Mode). + * + * The main routine is parse_ipsec_sa_body; other functions defined + * between here and there are just helpers. + * + * Various shortcuts are taken. In particular, the policy, such as + * it is, is hardwired. + * + * If r_sa is non-NULL, the body of an SA representing the selected + * proposal is emitted into it. + * + * If "selection" is true, the SA is supposed to represent the + * single tranform that the peer has accepted. + * ??? We only check that it is acceptable, not that it is one that we offered! + * + * Only IPsec DOI is accepted (what is the ISAKMP DOI?). + * Error response is rudimentary. + * + * Since all ISAKMP groups in all SA Payloads must match, st->st_pfs_group + * holds this across multiple payloads. + * &unset_group signifies not yet "set"; NULL signifies NONE. + * + * This routine is used by quick_inI1_outR1() and quick_inR1_outI2(). + */ + +static const struct ipsec_trans_attrs null_ipsec_trans_attrs = { + 0, /* transid (NULL, for now) */ + 0, /* spi */ + SA_LIFE_DURATION_DEFAULT, /* life_seconds */ + SA_LIFE_DURATION_K_DEFAULT, /* life_kilobytes */ + ENCAPSULATION_MODE_UNSPECIFIED, /* encapsulation */ + AUTH_ALGORITHM_NONE, /* auth */ + 0, /* key_len */ + 0, /* key_rounds */ +}; + +static bool +parse_ipsec_transform(struct isakmp_transform *trans +, struct ipsec_trans_attrs *attrs +, pb_stream *prop_pbs +, pb_stream *trans_pbs +, struct_desc *trans_desc +, int previous_transnum /* or -1 if none */ +, bool selection +, bool is_last +, bool is_ipcomp +, struct state *st) /* current state object */ +{ + lset_t seen_attrs = 0; + lset_t seen_durations = 0; + u_int16_t life_type = 0; + const struct oakley_group_desc *pfs_group = NULL; + + if (!in_struct(trans, trans_desc, prop_pbs, trans_pbs)) + return FALSE; + + if (trans->isat_transnum <= previous_transnum) + { + loglog(RC_LOG_SERIOUS, "Transform Numbers in Proposal are not monotonically increasing"); + return FALSE; + } + + switch (trans->isat_np) + { + case ISAKMP_NEXT_T: + if (is_last) + { + loglog(RC_LOG_SERIOUS, "Proposal Payload has more Transforms than specified"); + return FALSE; + } + break; + case ISAKMP_NEXT_NONE: + if (!is_last) + { + loglog(RC_LOG_SERIOUS, "Proposal Payload has fewer Transforms than specified"); + return FALSE; + } + break; + default: + loglog(RC_LOG_SERIOUS, "expecting Transform Payload, but found %s in Proposal" + , enum_show(&payload_names, trans->isat_np)); + return FALSE; + } + + *attrs = null_ipsec_trans_attrs; + attrs->transid = trans->isat_transid; + + while (pbs_left(trans_pbs) != 0) + { + struct isakmp_attribute a; + pb_stream attr_pbs; + enum_names *vdesc; + u_int32_t val; /* room for larger value */ + bool ipcomp_inappropriate = is_ipcomp; /* will get reset if OK */ + + if (!in_struct(&a, &isakmp_ipsec_attribute_desc, trans_pbs, &attr_pbs)) + return FALSE; + + passert((a.isaat_af_type & ISAKMP_ATTR_RTYPE_MASK) < 32); + + if (LHAS(seen_attrs, a.isaat_af_type & ISAKMP_ATTR_RTYPE_MASK)) + { + loglog(RC_LOG_SERIOUS, "repeated %s attribute in IPsec Transform %u" + , enum_show(&ipsec_attr_names, a.isaat_af_type) + , trans->isat_transnum); + return FALSE; + } + + seen_attrs |= LELEM(a.isaat_af_type & ISAKMP_ATTR_RTYPE_MASK); + + val = a.isaat_lv; + + vdesc = ipsec_attr_val_descs[a.isaat_af_type & ISAKMP_ATTR_RTYPE_MASK]; + if (vdesc != NULL) + { + if (enum_name(vdesc, val) == NULL) + { + loglog(RC_LOG_SERIOUS, "invalid value %u for attribute %s in IPsec Transform" + , (unsigned)val, enum_show(&ipsec_attr_names, a.isaat_af_type)); + return FALSE; + } + DBG(DBG_PARSING + , if ((a.isaat_af_type & ISAKMP_ATTR_AF_MASK) == ISAKMP_ATTR_AF_TV) + DBG_log(" [%u is %s]" + , (unsigned)val, enum_show(vdesc, val))); + } + + switch (a.isaat_af_type) + { + case SA_LIFE_TYPE | ISAKMP_ATTR_AF_TV: + ipcomp_inappropriate = FALSE; + if (LHAS(seen_durations, val)) + { + loglog(RC_LOG_SERIOUS, "attribute SA_LIFE_TYPE value %s repeated in message" + , enum_show(&sa_lifetime_names, val)); + return FALSE; + } + seen_durations |= LELEM(val); + life_type = val; + break; + case SA_LIFE_DURATION | ISAKMP_ATTR_AF_TLV: + val = decode_long_duration(&attr_pbs); + /* fall through */ + case SA_LIFE_DURATION | ISAKMP_ATTR_AF_TV: + ipcomp_inappropriate = FALSE; + if (!LHAS(seen_attrs, SA_LIFE_DURATION)) + { + loglog(RC_LOG_SERIOUS, "SA_LIFE_DURATION IPsec attribute not preceded by SA_LIFE_TYPE attribute"); + return FALSE; + } + seen_attrs &= ~(LELEM(SA_LIFE_DURATION) | LELEM(SA_LIFE_TYPE)); + + switch (life_type) + { + case SA_LIFE_TYPE_SECONDS: + /* silently limit duration to our maximum */ + attrs->life_seconds = val <= SA_LIFE_DURATION_MAXIMUM + ? val : SA_LIFE_DURATION_MAXIMUM; + break; + case SA_LIFE_TYPE_KBYTES: + attrs->life_kilobytes = val; + break; + default: + bad_case(life_type); + } + break; + case GROUP_DESCRIPTION | ISAKMP_ATTR_AF_TV: + if (is_ipcomp) + { + /* Accept reluctantly. Should not happen, according to + * draft-shacham-ippcp-rfc2393bis-05.txt 4.1. + */ + ipcomp_inappropriate = FALSE; + loglog(RC_COMMENT + , "IPCA (IPcomp SA) contains GROUP_DESCRIPTION." + " Ignoring inapproprate attribute."); + } + pfs_group = lookup_group(val); + if (pfs_group == NULL) + { + loglog(RC_LOG_SERIOUS, "only OAKLEY_GROUP_MODP1024 and OAKLEY_GROUP_MODP1536 supported for PFS"); + return FALSE; + } + break; + case ENCAPSULATION_MODE | ISAKMP_ATTR_AF_TV: + ipcomp_inappropriate = FALSE; + switch (val) + { + case ENCAPSULATION_MODE_TUNNEL: + case ENCAPSULATION_MODE_TRANSPORT: + if (st->nat_traversal & NAT_T_DETECTED) + { + loglog(RC_LOG_SERIOUS + , "%s must only be used if NAT-Traversal is not detected" + , enum_name(&enc_mode_names, val)); + /* + * Accept it anyway because SSH-Sentinel does not + * use UDP_TUNNEL or UDP_TRANSPORT for the diagnostic. + * + * remove when SSH-Sentinel is fixed + */ +#ifdef I_DONT_CARE_OF_SSH_SENTINEL + return FALSE; +#endif + } + attrs->encapsulation = val; + break; + case ENCAPSULATION_MODE_UDP_TRANSPORT_DRAFTS: +#ifndef I_KNOW_TRANSPORT_MODE_HAS_SECURITY_CONCERN_BUT_I_WANT_IT + loglog(RC_LOG_SERIOUS + , "NAT-Traversal: Transport mode disabled due to security concerns"); + return FALSE; +#endif + case ENCAPSULATION_MODE_UDP_TUNNEL_DRAFTS: + if (st->nat_traversal & NAT_T_WITH_RFC_VALUES) + { + loglog(RC_LOG_SERIOUS + , "%s must only be used with old IETF drafts" + , enum_name(&enc_mode_names, val)); + return FALSE; + } + else if (st->nat_traversal & NAT_T_DETECTED) + { + attrs->encapsulation = val + - ENCAPSULATION_MODE_UDP_TUNNEL_DRAFTS + + ENCAPSULATION_MODE_TUNNEL; + } + else + { + loglog(RC_LOG_SERIOUS + , "%s must only be used if NAT-Traversal is detected" + , enum_name(&enc_mode_names, val)); + return FALSE; + } + break; + case ENCAPSULATION_MODE_UDP_TRANSPORT_RFC: +#ifndef I_KNOW_TRANSPORT_MODE_HAS_SECURITY_CONCERN_BUT_I_WANT_IT + loglog(RC_LOG_SERIOUS + , "NAT-Traversal: Transport mode disabled due " + "to security concerns"); + return FALSE; +#endif + case ENCAPSULATION_MODE_UDP_TUNNEL_RFC: + if ((st->nat_traversal & NAT_T_DETECTED) + && (st->nat_traversal & NAT_T_WITH_RFC_VALUES)) + { + attrs->encapsulation = val + - ENCAPSULATION_MODE_UDP_TUNNEL_RFC + + ENCAPSULATION_MODE_TUNNEL; + } + else if (st->nat_traversal & NAT_T_DETECTED) + { + loglog(RC_LOG_SERIOUS + , "%s must only be used with NAT-T RFC" + , enum_name(&enc_mode_names, val)); + return FALSE; + } + else + { + loglog(RC_LOG_SERIOUS + , "%s must only be used if NAT-Traversal is detected" + , enum_name(&enc_mode_names, val)); + return FALSE; + } + break; + default: + loglog(RC_LOG_SERIOUS + , "unknown ENCAPSULATION_MODE %d in IPSec SA", val); + return FALSE; + } + break; + case AUTH_ALGORITHM | ISAKMP_ATTR_AF_TV: + attrs->auth = val; + break; + case KEY_LENGTH | ISAKMP_ATTR_AF_TV: + attrs->key_len = val; + break; + case KEY_ROUNDS | ISAKMP_ATTR_AF_TV: + attrs->key_rounds = val; + break; +#if 0 /* not yet implemented */ + case COMPRESS_DICT_SIZE | ISAKMP_ATTR_AF_TV: + break; + case COMPRESS_PRIVATE_ALG | ISAKMP_ATTR_AF_TV: + break; + + case SA_LIFE_DURATION | ISAKMP_ATTR_AF_TLV: + break; + case COMPRESS_PRIVATE_ALG | ISAKMP_ATTR_AF_TLV: + break; +#endif + default: + loglog(RC_LOG_SERIOUS, "unsupported IPsec attribute %s" + , enum_show(&ipsec_attr_names, a.isaat_af_type)); + return FALSE; + } + if (ipcomp_inappropriate) + { + loglog(RC_LOG_SERIOUS, "IPsec attribute %s inappropriate for IPCOMP" + , enum_show(&ipsec_attr_names, a.isaat_af_type)); + return FALSE; + } + } + + /* Although an IPCOMP SA (IPCA) ought not to have a pfs_group, + * if it does, demand that it be consistent. + * See draft-shacham-ippcp-rfc2393bis-05.txt 4.1. + */ + if (!is_ipcomp || pfs_group != NULL) + { + if (st->st_pfs_group == &unset_group) + st->st_pfs_group = pfs_group; + + if (st->st_pfs_group != pfs_group) + { + loglog(RC_LOG_SERIOUS, "GROUP_DESCRIPTION inconsistent with that of %s in IPsec SA" + , selection? "the Proposal" : "a previous Transform"); + return FALSE; + } + } + + if (LHAS(seen_attrs, SA_LIFE_DURATION)) + { + loglog(RC_LOG_SERIOUS, "SA_LIFE_TYPE IPsec attribute not followed by SA_LIFE_DURATION attribute in message"); + return FALSE; + } + + if (!LHAS(seen_attrs, ENCAPSULATION_MODE)) + { + if (is_ipcomp) + { + /* draft-shacham-ippcp-rfc2393bis-05.txt 4.1: + * "If the Encapsulation Mode is unspecified, + * the default value of Transport Mode is assumed." + * This contradicts/overrides the DOI (quuoted below). + */ + attrs->encapsulation = ENCAPSULATION_MODE_TRANSPORT; + } + else + { + /* ??? Technically, RFC 2407 (IPSEC DOI) 4.5 specifies that + * the default is "unspecified (host-dependent)". + * This makes little sense, so we demand that it be specified. + */ + loglog(RC_LOG_SERIOUS, "IPsec Transform must specify ENCAPSULATION_MODE"); + return FALSE; + } + } + + /* ??? should check for key_len and/or key_rounds if required */ + + return TRUE; +} + +static void +echo_proposal( + struct isakmp_proposal r_proposal, /* proposal to emit */ + struct isakmp_transform r_trans, /* winning transformation within it */ + u_int8_t np, /* Next Payload for proposal */ + pb_stream *r_sa_pbs, /* SA PBS into which to emit */ + struct ipsec_proto_info *pi, /* info about this protocol instance */ + struct_desc *trans_desc, /* descriptor for this transformation */ + pb_stream *trans_pbs, /* PBS for incoming transform */ + struct spd_route *sr, /* host details for the association */ + bool tunnel_mode) /* true for inner most tunnel SA */ +{ + pb_stream r_proposal_pbs; + pb_stream r_trans_pbs; + + /* Proposal */ + r_proposal.isap_np = np; + r_proposal.isap_notrans = 1; + if (!out_struct(&r_proposal, &isakmp_proposal_desc, r_sa_pbs, &r_proposal_pbs)) + impossible(); + + /* allocate and emit our CPI/SPI */ + if (r_proposal.isap_protoid == PROTO_IPCOMP) + { + /* CPI is stored in network low order end of an + * ipsec_spi_t. So we start a couple of bytes in. + * Note: we may fail to generate a satisfactory CPI, + * but we'll ignore that. + */ + pi->our_spi = get_my_cpi(sr, tunnel_mode); + out_raw((u_char *) &pi->our_spi + + IPSEC_DOI_SPI_SIZE - IPCOMP_CPI_SIZE + , IPCOMP_CPI_SIZE + , &r_proposal_pbs, "CPI"); + } + else + { + pi->our_spi = get_ipsec_spi(pi->attrs.spi + , r_proposal.isap_protoid == PROTO_IPSEC_AH ? + IPPROTO_AH : IPPROTO_ESP + , sr + , tunnel_mode); + /* XXX should check for errors */ + out_raw((u_char *) &pi->our_spi, IPSEC_DOI_SPI_SIZE + , &r_proposal_pbs, "SPI"); + } + + /* Transform */ + r_trans.isat_np = ISAKMP_NEXT_NONE; + if (!out_struct(&r_trans, trans_desc, &r_proposal_pbs, &r_trans_pbs)) + impossible(); + + /* Transform Attributes: pure echo */ + trans_pbs->cur = trans_pbs->start + sizeof(struct isakmp_transform); + if (!out_raw(trans_pbs->cur, pbs_left(trans_pbs) + , &r_trans_pbs, "attributes")) + impossible(); + + close_output_pbs(&r_trans_pbs); + close_output_pbs(&r_proposal_pbs); +} + +notification_t +parse_ipsec_sa_body( + pb_stream *sa_pbs, /* body of input SA Payload */ + const struct isakmp_sa *sa, /* header of input SA Payload */ + pb_stream *r_sa_pbs, /* if non-NULL, where to emit body of winning SA */ + bool selection, /* if this SA is a selection, only one transform may appear */ + struct state *st) /* current state object */ +{ + const struct connection *c = st->st_connection; + u_int32_t ipsecdoisit; + pb_stream next_proposal_pbs; + + struct isakmp_proposal next_proposal; + ipsec_spi_t next_spi; + + bool next_full = TRUE; + + /* DOI */ + if (sa->isasa_doi != ISAKMP_DOI_IPSEC) + { + loglog(RC_LOG_SERIOUS, "Unknown or unsupported DOI %s", enum_show(&doi_names, sa->isasa_doi)); + /* XXX Could send notification back */ + return DOI_NOT_SUPPORTED; + } + + /* Situation */ + if (!in_struct(&ipsecdoisit, &ipsec_sit_desc, sa_pbs, NULL)) + return SITUATION_NOT_SUPPORTED; + + if (ipsecdoisit != SIT_IDENTITY_ONLY) + { + loglog(RC_LOG_SERIOUS, "unsupported IPsec DOI situation (%s)" + , bitnamesof(sit_bit_names, ipsecdoisit)); + /* XXX Could send notification back */ + return SITUATION_NOT_SUPPORTED; + } + + /* The rules for IPsec SAs are scattered. + * RFC 2408 "ISAKMP" section 4.2 gives some info. + * There may be multiple proposals. Those with identical proposal + * numbers must be considered as conjuncts. Those with different + * numbers are disjuncts. + * Each proposal may have several transforms, each considered + * an alternative. + * Each transform may have several attributes, all applying. + * + * To handle the way proposals are combined, we need to do a + * look-ahead. + */ + + if (!in_struct(&next_proposal, &isakmp_proposal_desc, sa_pbs, &next_proposal_pbs)) + return BAD_PROPOSAL_SYNTAX; + + /* for each conjunction of proposals... */ + while (next_full) + { + int propno = next_proposal.isap_proposal; + pb_stream ah_prop_pbs, esp_prop_pbs, ipcomp_prop_pbs; + struct isakmp_proposal ah_proposal, esp_proposal, ipcomp_proposal; + ipsec_spi_t ah_spi = 0; + ipsec_spi_t esp_spi = 0; + ipsec_spi_t ipcomp_cpi = 0; + bool ah_seen = FALSE; + bool esp_seen = FALSE; + bool ipcomp_seen = FALSE; + bool tunnel_mode = FALSE; + int inner_proto = 0; + u_int16_t well_known_cpi = 0; + + pb_stream ah_trans_pbs, esp_trans_pbs, ipcomp_trans_pbs; + struct isakmp_transform ah_trans, esp_trans, ipcomp_trans; + struct ipsec_trans_attrs ah_attrs, esp_attrs, ipcomp_attrs; + + /* for each proposal in the conjunction */ + do { + + if (next_proposal.isap_protoid == PROTO_IPCOMP) + { + /* IPCOMP CPI */ + if (next_proposal.isap_spisize == IPSEC_DOI_SPI_SIZE) + { + /* This code is to accommodate those peculiar + * implementations that send a CPI in the bottom of an + * SPI-sized field. + * See draft-shacham-ippcp-rfc2393bis-05.txt 4.1 + */ + u_int8_t filler[IPSEC_DOI_SPI_SIZE - IPCOMP_CPI_SIZE]; + + if (!in_raw(filler, sizeof(filler) + , &next_proposal_pbs, "CPI filler") + || !all_zero(filler, sizeof(filler))) + return INVALID_SPI; + } + else if (next_proposal.isap_spisize != IPCOMP_CPI_SIZE) + { + loglog(RC_LOG_SERIOUS, "IPsec Proposal with improper CPI size (%u)" + , next_proposal.isap_spisize); + return INVALID_SPI; + } + + /* We store CPI in the low order of a network order + * ipsec_spi_t. So we start a couple of bytes in. + */ + zero(&next_spi); + if (!in_raw((u_char *)&next_spi + + IPSEC_DOI_SPI_SIZE - IPCOMP_CPI_SIZE + , IPCOMP_CPI_SIZE, &next_proposal_pbs, "CPI")) + return INVALID_SPI; + + /* If sanity ruled, CPIs would have to be such that + * the SAID (the triple (CPI, IPCOM, destination IP)) + * would be unique, just like for SPIs. But there is a + * perversion where CPIs can be well-known and consequently + * the triple is not unique. We hide this fact from + * ourselves by fudging the top 16 bits to make + * the property true internally! + */ + switch (ntohl(next_spi)) + { + case IPCOMP_DEFLATE: + well_known_cpi = ntohl(next_spi); + next_spi = uniquify_his_cpi(next_spi, st); + if (next_spi == 0) + { + loglog(RC_LOG_SERIOUS + , "IPsec Proposal contains well-known CPI that I cannot uniquify"); + return INVALID_SPI; + } + break; + default: + if (ntohl(next_spi) < IPCOMP_FIRST_NEGOTIATED + || ntohl(next_spi) > IPCOMP_LAST_NEGOTIATED) + { + loglog(RC_LOG_SERIOUS, "IPsec Proposal contains CPI from non-negotiated range (0x%lx)" + , (unsigned long) ntohl(next_spi)); + return INVALID_SPI; + } + break; + } + } + else + { + /* AH or ESP SPI */ + if (next_proposal.isap_spisize != IPSEC_DOI_SPI_SIZE) + { + loglog(RC_LOG_SERIOUS, "IPsec Proposal with improper SPI size (%u)" + , next_proposal.isap_spisize); + return INVALID_SPI; + } + + if (!in_raw((u_char *)&next_spi, sizeof(next_spi), &next_proposal_pbs, "SPI")) + return INVALID_SPI; + + /* SPI value 0 is invalid and values 1-255 are reserved to IANA. + * RFC 2402 (ESP) 2.4, RFC 2406 (AH) 2.1 + * IPCOMP??? + */ + if (ntohl(next_spi) < IPSEC_DOI_SPI_MIN) + { + loglog(RC_LOG_SERIOUS, "IPsec Proposal contains invalid SPI (0x%lx)" + , (unsigned long) ntohl(next_spi)); + return INVALID_SPI; + } + } + + if (next_proposal.isap_notrans == 0) + { + loglog(RC_LOG_SERIOUS, "IPsec Proposal contains no Transforms"); + return BAD_PROPOSAL_SYNTAX; + } + + switch (next_proposal.isap_protoid) + { + case PROTO_IPSEC_AH: + if (ah_seen) + { + loglog(RC_LOG_SERIOUS, "IPsec SA contains two simultaneous AH Proposals"); + return BAD_PROPOSAL_SYNTAX; + } + ah_seen = TRUE; + ah_prop_pbs = next_proposal_pbs; + ah_proposal = next_proposal; + ah_spi = next_spi; + break; + + case PROTO_IPSEC_ESP: + if (esp_seen) + { + loglog(RC_LOG_SERIOUS, "IPsec SA contains two simultaneous ESP Proposals"); + return BAD_PROPOSAL_SYNTAX; + } + esp_seen = TRUE; + esp_prop_pbs = next_proposal_pbs; + esp_proposal = next_proposal; + esp_spi = next_spi; + break; + + case PROTO_IPCOMP: + if (ipcomp_seen) + { + loglog(RC_LOG_SERIOUS, "IPsec SA contains two simultaneous IPCOMP Proposals"); + return BAD_PROPOSAL_SYNTAX; + } + ipcomp_seen = TRUE; + ipcomp_prop_pbs = next_proposal_pbs; + ipcomp_proposal = next_proposal; + ipcomp_cpi = next_spi; + break; + + default: + loglog(RC_LOG_SERIOUS, "unexpected Protocol ID (%s) in IPsec Proposal" + , enum_show(&protocol_names, next_proposal.isap_protoid)); + return INVALID_PROTOCOL_ID; + } + + /* refill next_proposal */ + if (next_proposal.isap_np == ISAKMP_NEXT_NONE) + { + next_full = FALSE; + break; + } + else if (next_proposal.isap_np != ISAKMP_NEXT_P) + { + loglog(RC_LOG_SERIOUS, "unexpected in Proposal: %s" + , enum_show(&payload_names, next_proposal.isap_np)); + return BAD_PROPOSAL_SYNTAX; + } + + if (!in_struct(&next_proposal, &isakmp_proposal_desc, sa_pbs, &next_proposal_pbs)) + return BAD_PROPOSAL_SYNTAX; + } while (next_proposal.isap_proposal == propno); + + /* Now that we have all conjuncts, we should try + * the Cartesian product of eachs tranforms! + * At the moment, we take short-cuts on account of + * our rudimentary hard-wired policy. + * For now, we find an acceptable AH (if any) + * and then an acceptable ESP. The only interaction + * is that the ESP acceptance can know whether there + * was an acceptable AH and hence not require an AUTH. + */ + + if (ah_seen) + { + int previous_transnum = -1; + int tn; + + for (tn = 0; tn != ah_proposal.isap_notrans; tn++) + { + int ok_transid = 0; + bool ok_auth = FALSE; + + if (!parse_ipsec_transform(&ah_trans + , &ah_attrs + , &ah_prop_pbs + , &ah_trans_pbs + , &isakmp_ah_transform_desc + , previous_transnum + , selection + , tn == ah_proposal.isap_notrans - 1 + , FALSE + , st)) + return BAD_PROPOSAL_SYNTAX; + + previous_transnum = ah_trans.isat_transnum; + + /* we must understand ah_attrs.transid + * COMBINED with ah_attrs.auth. + * See RFC 2407 "IPsec DOI" section 4.4.3 + * The following combinations are legal, + * but we don't implement all of them: + * It seems as if each auth algorithm + * only applies to one ah transid. + * AH_MD5, AUTH_ALGORITHM_HMAC_MD5 + * AH_MD5, AUTH_ALGORITHM_KPDK (unimplemented) + * AH_SHA, AUTH_ALGORITHM_HMAC_SHA1 + * AH_DES, AUTH_ALGORITHM_DES_MAC (unimplemented) + */ + switch (ah_attrs.auth) + { + case AUTH_ALGORITHM_NONE: + loglog(RC_LOG_SERIOUS, "AUTH_ALGORITHM attribute missing in AH Transform"); + return BAD_PROPOSAL_SYNTAX; + + case AUTH_ALGORITHM_HMAC_MD5: + ok_auth = TRUE; + /* fall through */ + case AUTH_ALGORITHM_KPDK: + ok_transid = AH_MD5; + break; + + case AUTH_ALGORITHM_HMAC_SHA1: + ok_auth = TRUE; + ok_transid = AH_SHA; + break; + + case AUTH_ALGORITHM_DES_MAC: + ok_transid = AH_DES; + break; + } + if (ah_attrs.transid != ok_transid) + { + loglog(RC_LOG_SERIOUS, "%s attribute inappropriate in %s Transform" + , enum_name(&auth_alg_names, ah_attrs.auth) + , enum_show(&ah_transformid_names, ah_attrs.transid)); + return BAD_PROPOSAL_SYNTAX; + } + if (!ok_auth) + { + DBG(DBG_CONTROL | DBG_CRYPT + , DBG_log("%s attribute unsupported" + " in %s Transform from %s" + , enum_name(&auth_alg_names, ah_attrs.auth) + , enum_show(&ah_transformid_names, ah_attrs.transid) + , ip_str(&c->spd.that.host_addr))); + continue; /* try another */ + } + break; /* we seem to be happy */ + } + if (tn == ah_proposal.isap_notrans) + continue; /* we didn't find a nice one */ + ah_attrs.spi = ah_spi; + inner_proto = IPPROTO_AH; + if (ah_attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL) + tunnel_mode = TRUE; + } + + if (esp_seen) + { + int previous_transnum = -1; + int tn; + + for (tn = 0; tn != esp_proposal.isap_notrans; tn++) + { + if (!parse_ipsec_transform(&esp_trans + , &esp_attrs + , &esp_prop_pbs + , &esp_trans_pbs + , &isakmp_esp_transform_desc + , previous_transnum + , selection + , tn == esp_proposal.isap_notrans - 1 + , FALSE + , st)) + return BAD_PROPOSAL_SYNTAX; + + previous_transnum = esp_trans.isat_transnum; + + /* set default key length for AES encryption */ + if (!esp_attrs.key_len && esp_attrs.transid == ESP_AES) + { + esp_attrs.key_len = 128 / BITS_PER_BYTE; + } + + if (!kernel_alg_esp_enc_ok(esp_attrs.transid, esp_attrs.key_len + ,c->alg_info_esp)) + { + switch (esp_attrs.transid) + { + case ESP_3DES: + break; +#ifdef SUPPORT_ESP_NULL /* should be about as secure as AH-only */ + case ESP_NULL: + if (esp_attrs.auth == AUTH_ALGORITHM_NONE) + { + loglog(RC_LOG_SERIOUS, "ESP_NULL requires auth algorithm"); + return BAD_PROPOSAL_SYNTAX; + } + if (st->st_policy & POLICY_ENCRYPT) + { + DBG(DBG_CONTROL | DBG_CRYPT + , DBG_log("ESP_NULL Transform Proposal from %s" + " does not satisfy POLICY_ENCRYPT" + , ip_str(&c->spd.that.host_addr))); + continue; /* try another */ + } + break; +#endif + default: + DBG(DBG_CONTROL | DBG_CRYPT + , DBG_log("unsupported ESP Transform %s from %s" + , enum_show(&esp_transformid_names, esp_attrs.transid) + , ip_str(&c->spd.that.host_addr))); + continue; /* try another */ + } + } + + if (!kernel_alg_esp_auth_ok(esp_attrs.auth, c->alg_info_esp)) + { + switch (esp_attrs.auth) + { + case AUTH_ALGORITHM_NONE: + if (!ah_seen) + { + DBG(DBG_CONTROL | DBG_CRYPT + , DBG_log("ESP from %s must either have AUTH or be combined with AH" + , ip_str(&c->spd.that.host_addr))); + continue; /* try another */ + } + break; + case AUTH_ALGORITHM_HMAC_MD5: + case AUTH_ALGORITHM_HMAC_SHA1: + break; + default: + DBG(DBG_CONTROL | DBG_CRYPT + , DBG_log("unsupported ESP auth alg %s from %s" + , enum_show(&auth_alg_names, esp_attrs.auth) + , ip_str(&c->spd.that.host_addr))); + continue; /* try another */ + } + } + + /* A last check for allowed transforms in alg_info_esp + * (ALG_INFO_F_STRICT flag) + */ + if (!kernel_alg_esp_ok_final(esp_attrs.transid, esp_attrs.key_len + ,esp_attrs.auth, c->alg_info_esp)) + { + continue; + } + + if (ah_seen && ah_attrs.encapsulation != esp_attrs.encapsulation) + { + /* ??? This should be an error, but is it? */ + DBG(DBG_CONTROL | DBG_CRYPT + , DBG_log("AH and ESP transforms disagree about encapsulation; TUNNEL presumed")); + } + + break; /* we seem to be happy */ + } + if (tn == esp_proposal.isap_notrans) + continue; /* we didn't find a nice one */ + + esp_attrs.spi = esp_spi; + inner_proto = IPPROTO_ESP; + if (esp_attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL) + tunnel_mode = TRUE; + } + else if (st->st_policy & POLICY_ENCRYPT) + { + DBG(DBG_CONTROL | DBG_CRYPT + , DBG_log("policy for \"%s\" requires encryption but ESP not in Proposal from %s" + , c->name, ip_str(&c->spd.that.host_addr))); + continue; /* we needed encryption, but didn't find ESP */ + } + else if ((st->st_policy & POLICY_AUTHENTICATE) && !ah_seen) + { + DBG(DBG_CONTROL | DBG_CRYPT + , DBG_log("policy for \"%s\" requires authentication" + " but none in Proposal from %s" + , c->name, ip_str(&c->spd.that.host_addr))); + continue; /* we need authentication, but we found neither ESP nor AH */ + } + + if (ipcomp_seen) + { + int previous_transnum = -1; + int tn; + +#ifdef NEVER /* we think IPcomp is working now */ + /**** FUDGE TO PREVENT UNREQUESTED IPCOMP: + **** NEEDED BECAUSE OUR IPCOMP IS EXPERIMENTAL (UNSTABLE). + ****/ + if (!(st->st_policy & POLICY_COMPRESS)) + { + plog("compression proposed by %s, but policy for \"%s\" forbids it" + , ip_str(&c->spd.that.host_addr), c->name); + continue; /* unwanted compression proposal */ + } +#endif + if (!can_do_IPcomp) + { + plog("compression proposed by %s, but KLIPS is not configured with IPCOMP" + , ip_str(&c->spd.that.host_addr)); + continue; + } + + if (well_known_cpi != 0 && !ah_seen && !esp_seen) + { + plog("illegal proposal: bare IPCOMP used with well-known CPI"); + return BAD_PROPOSAL_SYNTAX; + } + + for (tn = 0; tn != ipcomp_proposal.isap_notrans; tn++) + { + if (!parse_ipsec_transform(&ipcomp_trans + , &ipcomp_attrs + , &ipcomp_prop_pbs + , &ipcomp_trans_pbs + , &isakmp_ipcomp_transform_desc + , previous_transnum + , selection + , tn == ipcomp_proposal.isap_notrans - 1 + , TRUE + , st)) + return BAD_PROPOSAL_SYNTAX; + + previous_transnum = ipcomp_trans.isat_transnum; + + if (well_known_cpi != 0 && ipcomp_attrs.transid != well_known_cpi) + { + plog("illegal proposal: IPCOMP well-known CPI disagrees with transform"); + return BAD_PROPOSAL_SYNTAX; + } + + switch (ipcomp_attrs.transid) + { + case IPCOMP_DEFLATE: /* all we can handle! */ + break; + + default: + DBG(DBG_CONTROL | DBG_CRYPT + , DBG_log("unsupported IPCOMP Transform %s from %s" + , enum_show(&ipcomp_transformid_names, ipcomp_attrs.transid) + , ip_str(&c->spd.that.host_addr))); + continue; /* try another */ + } + + if (ah_seen && ah_attrs.encapsulation != ipcomp_attrs.encapsulation) + { + /* ??? This should be an error, but is it? */ + DBG(DBG_CONTROL | DBG_CRYPT + , DBG_log("AH and IPCOMP transforms disagree about encapsulation; TUNNEL presumed")); + } else if (esp_seen && esp_attrs.encapsulation != ipcomp_attrs.encapsulation) + { + /* ??? This should be an error, but is it? */ + DBG(DBG_CONTROL | DBG_CRYPT + , DBG_log("ESP and IPCOMP transforms disagree about encapsulation; TUNNEL presumed")); + } + + break; /* we seem to be happy */ + } + if (tn == ipcomp_proposal.isap_notrans) + continue; /* we didn't find a nice one */ + ipcomp_attrs.spi = ipcomp_cpi; + inner_proto = IPPROTO_COMP; + if (ipcomp_attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL) + tunnel_mode = TRUE; + } + + /* Eureka: we liked what we saw -- accept it. */ + + if (r_sa_pbs != NULL) + { + /* emit what we've accepted */ + + /* Situation */ + if (!out_struct(&ipsecdoisit, &ipsec_sit_desc, r_sa_pbs, NULL)) + impossible(); + + /* AH proposal */ + if (ah_seen) + echo_proposal(ah_proposal + , ah_trans + , esp_seen || ipcomp_seen? ISAKMP_NEXT_P : ISAKMP_NEXT_NONE + , r_sa_pbs + , &st->st_ah + , &isakmp_ah_transform_desc + , &ah_trans_pbs + , &st->st_connection->spd + , tunnel_mode && inner_proto == IPPROTO_AH); + + /* ESP proposal */ + if (esp_seen) + echo_proposal(esp_proposal + , esp_trans + , ipcomp_seen? ISAKMP_NEXT_P : ISAKMP_NEXT_NONE + , r_sa_pbs + , &st->st_esp + , &isakmp_esp_transform_desc + , &esp_trans_pbs + , &st->st_connection->spd + , tunnel_mode && inner_proto == IPPROTO_ESP); + + /* IPCOMP proposal */ + if (ipcomp_seen) + echo_proposal(ipcomp_proposal + , ipcomp_trans + , ISAKMP_NEXT_NONE + , r_sa_pbs + , &st->st_ipcomp + , &isakmp_ipcomp_transform_desc + , &ipcomp_trans_pbs + , &st->st_connection->spd + , tunnel_mode && inner_proto == IPPROTO_COMP); + + close_output_pbs(r_sa_pbs); + } + + /* save decoded version of winning SA in state */ + + st->st_ah.present = ah_seen; + if (ah_seen) + st->st_ah.attrs = ah_attrs; + + st->st_esp.present = esp_seen; + if (esp_seen) + st->st_esp.attrs = esp_attrs; + + st->st_ipcomp.present = ipcomp_seen; + if (ipcomp_seen) + st->st_ipcomp.attrs = ipcomp_attrs; + + return NOTHING_WRONG; + } + + loglog(RC_LOG_SERIOUS, "no acceptable Proposal in IPsec SA"); + return NO_PROPOSAL_CHOSEN; +} diff --git a/src/pluto/spdb.h b/src/pluto/spdb.h new file mode 100644 index 000000000..0df488841 --- /dev/null +++ b/src/pluto/spdb.h @@ -0,0 +1,112 @@ +/* Security Policy Data Base (such as it is) + * Copyright (C) 1998, 1999 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: spdb.h,v 1.4 2006/04/22 21:59:20 as Exp $ + */ + +#ifndef _SPDB_H +#define _SPDB_H + +#include "packet.h" + +/* database of SA properties */ + +/* Attribute type and value pair. + * Note: only "basic" values are represented so far. + */ +struct db_attr { + u_int16_t type; /* ISAKMP_ATTR_AF_TV is implied; 0 for end */ + u_int16_t val; +}; + +/* transform */ +struct db_trans { + u_int8_t transid; /* Transform-Id */ + struct db_attr *attrs; /* array */ + int attr_cnt; /* number of elements */ +}; + +/* proposal */ +struct db_prop { + u_int8_t protoid; /* Protocol-Id */ + struct db_trans *trans; /* array (disjunction) */ + int trans_cnt; /* number of elements */ + /* SPI size and value isn't part of DB */ +}; + +/* conjunction of proposals */ +struct db_prop_conj { + struct db_prop *props; /* array */ + int prop_cnt; /* number of elements */ +}; + +/* security association */ +struct db_sa { + struct db_prop_conj *prop_conjs; /* array */ + int prop_conj_cnt; /* number of elements */ + /* Hardwired for now; + * DOI: ISAKMP_DOI_IPSEC + * Situation: SIT_IDENTITY_ONLY + */ +}; + +/* The oakley sadb */ +extern struct db_sa oakley_sadb; + +/* The ipsec sadb is subscripted by a bitset with members + * from POLICY_ENCRYPT, POLICY_AUTHENTICATE, POLICY_COMPRESS + */ +extern struct db_sa ipsec_sadb[1 << 3]; + +/* forward declaration */ +struct state; + +extern bool out_sa( + pb_stream *outs, + struct db_sa *sadb, + struct state *st, + bool oakley_mode, + u_int8_t np); + +extern notification_t preparse_isakmp_sa_body( + const struct isakmp_sa *sa, /* header of input SA Payload */ + pb_stream *sa_pbs, /* body of input SA Payload */ + u_int32_t *ipsecdoisit, /* IPsec DOI SIT bitset */ + pb_stream *proposal_pbs, /* body of proposal Payload */ + struct isakmp_proposal *proposal); + +extern notification_t parse_isakmp_policy( + pb_stream *proposal_pbs, /* body of proposal Payload */ + u_int notrans, /* number of transforms */ + lset_t *policy); /* RSA, PSK or XAUTH policy */ + +extern notification_t parse_isakmp_sa_body( + u_int32_t ipsecdoisit, /* IPsec DOI SIT bitset */ + pb_stream *proposal_pbs, /* body of proposal Payload */ + struct isakmp_proposal *proposal, + pb_stream *r_sa_pbs, /* if non-NULL, where to emit winning SA */ + struct state *st, /* current state object */ + bool initiator); /* is caller initiator? */ + +extern notification_t parse_ipsec_sa_body( + pb_stream *sa_pbs, /* body of input SA Payload */ + const struct isakmp_sa *sa, /* header of input SA Payload */ + pb_stream *r_sa_pbs, /* if non-NULL, where to emit winning SA */ + bool selection, /* if this SA is a selection, only one tranform can appear */ + struct state *st); /* current state object */ + +extern void backup_pbs(pb_stream *pbs); +extern void restore_pbs(pb_stream *pbs); + +#endif /* _SPDB_H */ + diff --git a/src/pluto/state.c b/src/pluto/state.c new file mode 100644 index 000000000..80c3156b1 --- /dev/null +++ b/src/pluto/state.c @@ -0,0 +1,1012 @@ +/* routines for state objects + * Copyright (C) 1997 Angelos D. Keromytis. + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: state.c,v 1.13 2006/04/29 18:16:02 as Exp $ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <fcntl.h> +#include <sys/queue.h> + +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "connections.h" +#include "state.h" +#include "kernel.h" +#include "log.h" +#include "packet.h" /* so we can calculate sizeof(struct isakmp_hdr) */ +#include "keys.h" /* for free_public_key */ +#include "rnd.h" +#include "timer.h" +#include "whack.h" +#include "demux.h" /* needs packet.h */ +#include "ipsec_doi.h" /* needs demux.h and state.h */ + +#include "sha1.h" +#include "md5.h" +#include "crypto.h" /* requires sha1.h and md5.h */ + +/* + * Global variables: had to go somewhere, might as well be this file. + */ + +u_int16_t pluto_port = IKE_UDP_PORT; /* Pluto's port */ + +/* + * This file has the functions that handle the + * state hash table and the Message ID list. + */ + +/* Message-IDs + * + * A Message ID is contained in each IKE message header. + * For Phase 1 exchanges (Main and Aggressive), it will be zero. + * For other exchanges, which must be under the protection of an + * ISAKMP SA, the Message ID must be unique within that ISAKMP SA. + * Effectively, this labels the message as belonging to a particular + * exchange. + * BTW, we feel this uniqueness allows rekeying to be somewhat simpler + * than specified by draft-jenkins-ipsec-rekeying-06.txt. + * + * A MessageID is a 32 bit unsigned number. We represent the value + * internally in network order -- they are just blobs to us. + * They are unsigned numbers to make hashing and comparing easy. + * + * The following mechanism is used to allocate message IDs. This + * requires that we keep track of which numbers have already been used + * so that we don't allocate one in use. + */ + +struct msgid_list +{ + msgid_t msgid; /* network order */ + struct msgid_list *next; +}; + +bool +reserve_msgid(struct state *isakmp_sa, msgid_t msgid) +{ + struct msgid_list *p; + + passert(msgid != MAINMODE_MSGID); + passert(IS_ISAKMP_ENCRYPTED(isakmp_sa->st_state)); + + for (p = isakmp_sa->st_used_msgids; p != NULL; p = p->next) + if (p->msgid == msgid) + return FALSE; + + p = alloc_thing(struct msgid_list, "msgid"); + p->msgid = msgid; + p->next = isakmp_sa->st_used_msgids; + isakmp_sa->st_used_msgids = p; + return TRUE; +} + +msgid_t +generate_msgid(struct state *isakmp_sa) +{ + int timeout = 100; /* only try so hard for unique msgid */ + msgid_t msgid; + + passert(IS_ISAKMP_ENCRYPTED(isakmp_sa->st_state)); + + for (;;) + { + get_rnd_bytes((void *) &msgid, sizeof(msgid)); + if (msgid != 0 && reserve_msgid(isakmp_sa, msgid)) + break; + + if (--timeout == 0) + { + plog("gave up looking for unique msgid; using 0x%08lx" + , (unsigned long) msgid); + break; + } + } + return msgid; +} + + +/* state table functions */ + +#define STATE_TABLE_SIZE 32 + +static struct state *statetable[STATE_TABLE_SIZE]; + +static struct state ** +state_hash(const u_char *icookie, const u_char *rcookie, const ip_address *peer) +{ + u_int i = 0, j; + const unsigned char *byte_ptr; + size_t length = addrbytesptr(peer, &byte_ptr); + + DBG(DBG_RAW | DBG_CONTROL, + DBG_dump("ICOOKIE:", icookie, COOKIE_SIZE); + DBG_dump("RCOOKIE:", rcookie, COOKIE_SIZE); + DBG_dump("peer:", byte_ptr, length)); + + /* XXX the following hash is pretty pathetic */ + + for (j = 0; j < COOKIE_SIZE; j++) + i = i * 407 + icookie[j] + rcookie[j]; + + for (j = 0; j < length; j++) + i = i * 613 + byte_ptr[j]; + + i = i % STATE_TABLE_SIZE; + + DBG(DBG_CONTROL, DBG_log("state hash entry %d", i)); + + return &statetable[i]; +} + +/* Get a state object. + * Caller must schedule an event for this object so that it doesn't leak. + * Caller must insert_state(). + */ +struct state * +new_state(void) +{ + static const struct state blank_state; /* initialized all to zero & NULL */ + static so_serial_t next_so = SOS_FIRST; + struct state *st; + + st = clone_thing(blank_state, "struct state in new_state()"); + st->st_serialno = next_so++; + passert(next_so > SOS_FIRST); /* overflow can't happen! */ + st->st_whack_sock = NULL_FD; + DBG(DBG_CONTROL, DBG_log("creating state object #%lu at %p", + st->st_serialno, (void *) st)); + return st; +} + +/* + * Initialize the state table (and mask*). + */ +void +init_states(void) +{ + int i; + + for (i = 0; i < STATE_TABLE_SIZE; i++) + statetable[i] = (struct state *) NULL; +} + +/* Find the state object with this serial number. + * This allows state object references that don't turn into dangerous + * dangling pointers: reference a state by its serial number. + * Returns NULL if there is no such state. + * If this turns out to be a significant CPU hog, it could be + * improved to use a hash table rather than sequential seartch. + */ +struct state * +state_with_serialno(so_serial_t sn) +{ + if (sn >= SOS_FIRST) + { + struct state *st; + int i; + + for (i = 0; i < STATE_TABLE_SIZE; i++) + for (st = statetable[i]; st != NULL; st = st->st_hashchain_next) + if (st->st_serialno == sn) + return st; + } + return NULL; +} + +/* Insert a state object in the hash table. The object is inserted + * at the begining of list. + * Needs cookies, connection, and msgid. + */ +void +insert_state(struct state *st) +{ + struct state **p = state_hash(st->st_icookie, st->st_rcookie + , &st->st_connection->spd.that.host_addr); + + passert(st->st_hashchain_prev == NULL && st->st_hashchain_next == NULL); + + if (*p != NULL) + { + passert((*p)->st_hashchain_prev == NULL); + (*p)->st_hashchain_prev = st; + } + st->st_hashchain_next = *p; + *p = st; + + /* Ensure that somebody is in charge of killing this state: + * if no event is scheduled for it, schedule one to discard the state. + * If nothing goes wrong, this event will be replaced by + * a more appropriate one. + */ + if (st->st_event == NULL) + event_schedule(EVENT_SO_DISCARD, 0, st); +} + +/* unlink a state object from the hash table, but don't free it + */ +void +unhash_state(struct state *st) +{ + /* unlink from forward chain */ + struct state **p = st->st_hashchain_prev == NULL + ? state_hash(st->st_icookie, st->st_rcookie + , &st->st_connection->spd.that.host_addr) + : &st->st_hashchain_prev->st_hashchain_next; + + /* unlink from forward chain */ + passert(*p == st); + *p = st->st_hashchain_next; + + /* unlink from backward chain */ + if (st->st_hashchain_next != NULL) + { + passert(st->st_hashchain_next->st_hashchain_prev == st); + st->st_hashchain_next->st_hashchain_prev = st->st_hashchain_prev; + } + + st->st_hashchain_next = st->st_hashchain_prev = NULL; +} + +/* Free the Whack socket file descriptor. + * This has the side effect of telling Whack that we're done. + */ +void +release_whack(struct state *st) +{ + close_any(st->st_whack_sock); +} + +/* delete a state object */ +void +delete_state(struct state *st) +{ + struct connection *const c = st->st_connection; + struct state *old_cur_state = cur_state == st? NULL : cur_state; + + set_cur_state(st); + + /* If DPD is enabled on this state object, clear any pending events */ + if(st->st_dpd_event != NULL) + delete_dpd_event(st); + + /* if there is a suspended state transition, disconnect us */ + if (st->st_suspended_md != NULL) + { + passert(st->st_suspended_md->st == st); + st->st_suspended_md->st = NULL; + } + + /* tell the other side of any IPSEC SAs that are going down */ + if (IS_IPSEC_SA_ESTABLISHED(st->st_state) + || IS_ISAKMP_SA_ESTABLISHED(st->st_state)) + send_delete(st); + + delete_event(st); /* delete any pending timer event */ + + /* Ditch anything pending on ISAKMP SA being established. + * Note: this must be done before the unhash_state to prevent + * flush_pending_by_state inadvertently and prematurely + * deleting our connection. + */ + flush_pending_by_state(st); + + /* effectively, this deletes any ISAKMP SA that this state represents */ + unhash_state(st); + + /* tell kernel to delete any IPSEC SA + * ??? we ought to tell peer to delete IPSEC SAs + */ + if (IS_IPSEC_SA_ESTABLISHED(st->st_state)) + delete_ipsec_sa(st, FALSE); + else if (IS_ONLY_INBOUND_IPSEC_SA_ESTABLISHED(st->st_state)) + delete_ipsec_sa(st, TRUE); + + if (c->newest_ipsec_sa == st->st_serialno) + c->newest_ipsec_sa = SOS_NOBODY; + + if (c->newest_isakmp_sa == st->st_serialno) + c->newest_isakmp_sa = SOS_NOBODY; + + st->st_connection = NULL; /* we might be about to free it */ + cur_state = old_cur_state; /* without st_connection, st isn't complete */ + connection_discard(c); + + release_whack(st); + + /* from here on we are just freeing RAM */ + + { + struct msgid_list *p = st->st_used_msgids; + + while (p != NULL) + { + struct msgid_list *q = p; + p = p->next; + pfree(q); + } + } + + unreference_key(&st->st_peer_pubkey); + + if (st->st_sec_in_use) + mpz_clear(&(st->st_sec)); + + pfreeany(st->st_tpacket.ptr); + pfreeany(st->st_rpacket.ptr); + pfreeany(st->st_p1isa.ptr); + pfreeany(st->st_gi.ptr); + pfreeany(st->st_gr.ptr); + pfreeany(st->st_shared.ptr); + pfreeany(st->st_ni.ptr); + pfreeany(st->st_nr.ptr); + pfreeany(st->st_skeyid.ptr); + pfreeany(st->st_skeyid_d.ptr); + pfreeany(st->st_skeyid_a.ptr); + pfreeany(st->st_skeyid_e.ptr); + pfreeany(st->st_enc_key.ptr); + pfreeany(st->st_ah.our_keymat); + pfreeany(st->st_ah.peer_keymat); + pfreeany(st->st_esp.our_keymat); + pfreeany(st->st_esp.peer_keymat); + + pfree(st); +} + +/* + * Is a connection in use by some state? + */ +bool +states_use_connection(struct connection *c) +{ + /* are there any states still using it? */ + struct state *st = NULL; + int i; + + for (i = 0; st == NULL && i < STATE_TABLE_SIZE; i++) + for (st = statetable[i]; st != NULL; st = st->st_hashchain_next) + if (st->st_connection == c) + return TRUE; + + return FALSE; +} + +/* + * delete all states that were created for a given connection. + * if relations == TRUE, then also delete states that share + * the same phase 1 SA. + */ +void +delete_states_by_connection(struct connection *c, bool relations) +{ + int pass; + /* this kludge avoids an n^2 algorithm */ + enum connection_kind ck = c->kind; + struct spd_route *sr; + + /* save this connection's isakmp SA, since it will get set to later SOS_NOBODY */ + so_serial_t parent_sa = c->newest_isakmp_sa; + + if (ck == CK_INSTANCE) + c->kind = CK_GOING_AWAY; + + /* We take two passes so that we delete any ISAKMP SAs last. + * This allows Delete Notifications to be sent. + * ?? We could probably double the performance by caching any + * ISAKMP SA states found in the first pass, avoiding a second. + */ + for (pass = 0; pass != 2; pass++) + { + int i; + + /* For each hash chain... */ + for (i = 0; i < STATE_TABLE_SIZE; i++) + { + struct state *st; + + /* For each state in the hash chain... */ + for (st = statetable[i]; st != NULL; ) + { + struct state *this = st; + + st = st->st_hashchain_next; /* before this is deleted */ + + + if ((this->st_connection == c + || (relations && parent_sa != SOS_NOBODY + && this->st_clonedfrom == parent_sa)) + && (pass == 1 || !IS_ISAKMP_SA_ESTABLISHED(this->st_state))) + { + struct state *old_cur_state + = cur_state == this? NULL : cur_state; +#ifdef DEBUG + lset_t old_cur_debugging = cur_debugging; +#endif + + set_cur_state(this); + plog("deleting state (%s)" + , enum_show(&state_names, this->st_state)); + delete_state(this); + cur_state = old_cur_state; +#ifdef DEBUG + cur_debugging = old_cur_debugging; +#endif + } + } + } + } + + sr = &c->spd; + while (sr != NULL) + { + passert(sr->eroute_owner == SOS_NOBODY); + passert(sr->routing != RT_ROUTED_TUNNEL); + sr = sr->next; + } + + if (ck == CK_INSTANCE) + { + c->kind = ck; + delete_connection(c, relations); + } +} + +/* Walk through the state table, and delete each state whose phase 1 (IKE) + * peer is among those given. + */ +void +delete_states_by_peer(ip_address *peer) +{ + char peerstr[ADDRTOT_BUF]; + int i; + + addrtot(peer, 0, peerstr, sizeof(peerstr)); + + /* For each hash chain... */ + for (i = 0; i < STATE_TABLE_SIZE; i++) + { + struct state *st; + + /* For each state in the hash chain... */ + for (st = statetable[i]; st != NULL; ) + { + struct state *this = st; + struct spd_route *sr; + struct connection *c = this->st_connection; + + st = st->st_hashchain_next; /* before this is deleted */ + + /* ??? Is it not the case that the peer is the same for all spds? */ + for (sr = &c->spd; sr != NULL; sr = sr->next) + { + if (sameaddr(&sr->that.host_addr, peer)) + { + plog("peer %s for connection %s deleting - claimed to have crashed" + , peerstr + , c->name); + delete_states_by_connection(c, TRUE); + break; /* can only delete it once */ + } + } + } + } +} + +/* Duplicate a Phase 1 state object, to create a Phase 2 object. + * Caller must schedule an event for this object so that it doesn't leak. + * Caller must insert_state(). + */ +struct state * +duplicate_state(struct state *st) +{ + struct state *nst; + + DBG(DBG_CONTROL, DBG_log("duplicating state object #%lu", + st->st_serialno)); + + /* record use of the Phase 1 state */ + st->st_outbound_count++; + st->st_outbound_time = now(); + + nst = new_state(); + + memcpy(nst->st_icookie, st->st_icookie, COOKIE_SIZE); + memcpy(nst->st_rcookie, st->st_rcookie, COOKIE_SIZE); + + nst->st_connection = st->st_connection; + nst->st_doi = st->st_doi; + nst->st_situation = st->st_situation; + nst->st_clonedfrom = st->st_serialno; + nst->st_oakley = st->st_oakley; + nst->st_modecfg = st->st_modecfg; + +# define clone_chunk(ch, name) \ + clonetochunk(nst->ch, st->ch.ptr, st->ch.len, name) + + clone_chunk(st_skeyid_d, "st_skeyid_d in duplicate_state"); + clone_chunk(st_skeyid_a, "st_skeyid_a in duplicate_state"); + clone_chunk(st_skeyid_e, "st_skeyid_e in duplicate_state"); + clone_chunk(st_enc_key, "st_enc_key in duplicate_state"); + +# undef clone_chunk + + return nst; +} + +#if 1 +void for_each_state(void *(f)(struct state *, void *data), void *data) +{ + struct state *st, *ocs = cur_state; + int i; + for (i=0; i<STATE_TABLE_SIZE; i++) { + for (st = statetable[i]; st != NULL; st = st->st_hashchain_next) { + set_cur_state(st); + f(st, data); + } + } + cur_state = ocs; +} +#endif + +/* + * Find a state object. + */ +struct state * +find_state(const u_char *icookie +, const u_char *rcookie +, const ip_address *peer +, msgid_t /*network order*/ msgid) +{ + struct state *st = *state_hash(icookie, rcookie, peer); + + while (st != (struct state *) NULL) + if (sameaddr(peer, &st->st_connection->spd.that.host_addr) + && memcmp(icookie, st->st_icookie, COOKIE_SIZE) == 0 + && memcmp(rcookie, st->st_rcookie, COOKIE_SIZE) == 0 + && msgid == st->st_msgid) + break; + else + st = st->st_hashchain_next; + + DBG(DBG_CONTROL, + if (st == NULL) + DBG_log("state object not found"); + else + DBG_log("state object #%lu found, in %s" + , st->st_serialno + , enum_show(&state_names, st->st_state))); + + return st; +} + +/* Find the state that sent a packet + * ??? this could be expensive -- it should be rate-limited to avoid DoS + */ +struct state * +find_sender(size_t packet_len, u_char *packet) +{ + int i; + struct state *st; + + if (packet_len >= sizeof(struct isakmp_hdr)) + for (i = 0; i < STATE_TABLE_SIZE; i++) + for (st = statetable[i]; st != NULL; st = st->st_hashchain_next) + if (st->st_tpacket.ptr != NULL + && st->st_tpacket.len == packet_len + && memcmp(st->st_tpacket.ptr, packet, packet_len) == 0) + return st; + + return NULL; +} + +struct state * +find_phase2_state_to_delete(const struct state *p1st +, u_int8_t protoid +, ipsec_spi_t spi +, bool *bogus) +{ + struct state *st; + int i; + + *bogus = FALSE; + for (i = 0; i < STATE_TABLE_SIZE; i++) + { + for (st = statetable[i]; st != NULL; st = st->st_hashchain_next) + { + if (IS_IPSEC_SA_ESTABLISHED(st->st_state) + && p1st->st_connection->host_pair == st->st_connection->host_pair + && same_peer_ids(p1st->st_connection, st->st_connection, NULL)) + { + struct ipsec_proto_info *pr = protoid == PROTO_IPSEC_AH + ? &st->st_ah : &st->st_esp; + + if (pr->present) + { + if (pr->attrs.spi == spi) + return st; + if (pr->our_spi == spi) + *bogus = TRUE; + } + } + } + } + return NULL; +} + +/* Find newest Phase 1 negotiation state object for suitable for connection c + */ +struct state * +find_phase1_state(const struct connection *c, lset_t ok_states) +{ + struct state + *st, + *best = NULL; + int i; + + for (i = 0; i < STATE_TABLE_SIZE; i++) + for (st = statetable[i]; st != NULL; st = st->st_hashchain_next) + if (LHAS(ok_states, st->st_state) + && c->host_pair == st->st_connection->host_pair + && same_peer_ids(c, st->st_connection, NULL) + && (best == NULL || best->st_serialno < st->st_serialno)) + best = st; + + return best; +} + +void +state_eroute_usage(ip_subnet *ours, ip_subnet *his +, unsigned long count, time_t nw) +{ + struct state *st; + int i; + + for (i = 0; i < STATE_TABLE_SIZE; i++) + { + for (st = statetable[i]; st != NULL; st = st->st_hashchain_next) + { + struct connection *c = st->st_connection; + + /* XXX spd-enum */ + if (IS_IPSEC_SA_ESTABLISHED(st->st_state) + && c->spd.eroute_owner == st->st_serialno + && c->spd.routing == RT_ROUTED_TUNNEL + && samesubnet(&c->spd.this.client, ours) + && samesubnet(&c->spd.that.client, his)) + { + if (st->st_outbound_count != count) + { + st->st_outbound_count = count; + st->st_outbound_time = nw; + } + return; + } + } + } + DBG(DBG_CONTROL, + { + char ourst[SUBNETTOT_BUF]; + char hist[SUBNETTOT_BUF]; + + subnettot(ours, 0, ourst, sizeof(ourst)); + subnettot(his, 0, hist, sizeof(hist)); + DBG_log("unknown tunnel eroute %s -> %s found in scan" + , ourst, hist); + }); +} + +void fmt_state(bool all, struct state *st, time_t n +, char *state_buf, size_t state_buf_len +, char *state_buf2, size_t state_buf2_len) +{ + /* what the heck is interesting about a state? */ + const struct connection *c = st->st_connection; + + long delta = st->st_event->ev_time >= n + ? (long)(st->st_event->ev_time - n) + : -(long)(n - st->st_event->ev_time); + + char inst[CONN_INST_BUF]; + const char *np1 = c->newest_isakmp_sa == st->st_serialno + ? "; newest ISAKMP" : ""; + const char *np2 = c->newest_ipsec_sa == st->st_serialno + ? "; newest IPSEC" : ""; + /* XXX spd-enum */ + const char *eo = c->spd.eroute_owner == st->st_serialno + ? "; eroute owner" : ""; + const char *dpd = (all && st->st_dpd && c->dpd_action != DPD_ACTION_NONE) + ? "; DPD active" : ""; + + passert(st->st_event != 0); + + fmt_conn_instance(c, inst); + + snprintf(state_buf, state_buf_len + , "#%lu: \"%s\"%s %s (%s); %s in %lds%s%s%s%s" + , st->st_serialno + , c->name, inst + , enum_name(&state_names, st->st_state) + , state_story[st->st_state - STATE_MAIN_R0] + , enum_name(&timer_event_names, st->st_event->ev_type) + , delta + , np1, np2, eo, dpd); + + /* print out SPIs if SAs are established */ + if (state_buf2_len != 0) + state_buf2[0] = '\0'; /* default to empty */ + if (IS_IPSEC_SA_ESTABLISHED(st->st_state)) + { + + bool tunnel; + char buf[SATOT_BUF*6 + 2*20 + 1]; + const char *p_end = buf + sizeof(buf); + char *p = buf; + +# define add_said(adst, aspi, aproto) { \ + ip_said s; \ + \ + initsaid(adst, aspi, aproto, &s); \ + if (p < p_end - 1) \ + { \ + *p++ = ' '; \ + p += satot(&s, 0, p, p_end - p) - 1; \ + } \ + } + +# define add_sa_info(st, inbound) { \ + u_int bytes; \ + time_t use_time; \ + \ + if (get_sa_info(st, inbound, &bytes, &use_time)) \ + { \ + p += snprintf(p, p_end - p, " (%'u bytes", bytes); \ + if (bytes > 0 && use_time != UNDEFINED_TIME) \ + p += snprintf(p, p_end - p, ", %ds ago", (int)(now - use_time)); \ + p += snprintf(p, p_end - p, ")"); \ + } \ + } + + *p = '\0'; + if (st->st_ah.present) + { + add_said(&c->spd.that.host_addr, st->st_ah.attrs.spi, SA_AH); + add_said(&c->spd.this.host_addr, st->st_ah.our_spi, SA_AH); + } + if (st->st_esp.present) + { + time_t now = time(NULL); + + add_said(&c->spd.that.host_addr, st->st_esp.attrs.spi, SA_ESP); + add_sa_info(st, FALSE); + add_said(&c->spd.this.host_addr, st->st_esp.our_spi, SA_ESP); + add_sa_info(st, TRUE); + } + if (st->st_ipcomp.present) + { + add_said(&c->spd.that.host_addr, st->st_ipcomp.attrs.spi, SA_COMP); + add_said(&c->spd.this.host_addr, st->st_ipcomp.our_spi, SA_COMP); + } +#ifdef KLIPS + tunnel = st->st_ah.attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL + || st->st_esp.attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL + || st->st_ipcomp.attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL; + p += snprintf(p, p_end - p, "; %s", tunnel? "tunnel":"transport"); +#endif + + snprintf(state_buf2, state_buf2_len + , "#%lu: \"%s\"%s%s" + , st->st_serialno + , c->name, inst + , buf); + +# undef add_said +# undef add_sa_info + } +} + +/* + * sorting logic is: + * + * name + * type + * instance# + * isakmp_sa (XXX probably wrong) + * + */ +static int +state_compare(const void *a, const void *b) +{ + const struct state *sap = *(const struct state *const *)a; + struct connection *ca = sap->st_connection; + const struct state *sbp = *(const struct state *const *)b; + struct connection *cb = sbp->st_connection; + + /* DBG_log("comparing %s to %s", ca->name, cb->name); */ + + return connection_compare(ca, cb); +} + +void +show_states_status(bool all, const char *name) +{ + time_t n = now(); + int i; + char state_buf[LOG_WIDTH]; + char state_buf2[LOG_WIDTH]; + int count; + struct state **array; + + /* make count of states */ + count = 0; + for (i = 0; i < STATE_TABLE_SIZE; i++) + { + struct state *st; + + for (st = statetable[i]; st != NULL; st = st->st_hashchain_next) + { + if (name == NULL || streq(name, st->st_connection->name)) + count++; + } + } + + /* build the array */ + array = alloc_bytes(sizeof(struct state *)*count, "state array"); + count = 0; + for (i = 0; i < STATE_TABLE_SIZE; i++) + { + struct state *st; + + for (st = statetable[i]; st != NULL; st = st->st_hashchain_next) + { + if (name == NULL || streq(name, st->st_connection->name)) + array[count++]=st; + } + } + + /* sort it! */ + qsort(array, count, sizeof(struct state *), state_compare); + + /* now print sorted results */ + for (i = 0; i < count; i++) + { + struct state *st; + + st = array[i]; + + fmt_state(all, st, n + , state_buf, sizeof(state_buf) + , state_buf2, sizeof(state_buf2)); + whack_log(RC_COMMENT, state_buf); + if (state_buf2[0] != '\0') + whack_log(RC_COMMENT, state_buf2); + + /* show any associated pending Phase 2s */ + if (IS_PHASE1(st->st_state)) + show_pending_phase2(st->st_connection->host_pair, st); + } + if (count > 0) + whack_log(RC_COMMENT, BLANK_FORMAT); /* spacer */ + + /* free the array */ + pfree(array); +} + +/* Given that we've used up a range of unused CPI's, + * search for a new range of currently unused ones. + * Note: this is very expensive when not trivial! + * If we can't find one easily, choose 0 (a bad SPI, + * no matter what order) indicating failure. + */ +void +find_my_cpi_gap(cpi_t *latest_cpi, cpi_t *first_busy_cpi) +{ + int tries = 0; + cpi_t base = *latest_cpi; + cpi_t closest; + int i; + +startover: + closest = ~0; /* not close at all */ + for (i = 0; i < STATE_TABLE_SIZE; i++) + { + struct state *st; + + for (st = statetable[i]; st != NULL; st = st->st_hashchain_next) + { + if (st->st_ipcomp.present) + { + cpi_t c = ntohl(st->st_ipcomp.our_spi) - base; + + if (c < closest) + { + if (c == 0) + { + /* oops: next spot is occupied; start over */ + if (++tries == 20) + { + /* FAILURE */ + *latest_cpi = *first_busy_cpi = 0; + return; + } + base++; + if (base > IPCOMP_LAST_NEGOTIATED) + base = IPCOMP_FIRST_NEGOTIATED; + goto startover; /* really a tail call */ + } + closest = c; + } + } + } + } + *latest_cpi = base; /* base is first in next free range */ + *first_busy_cpi = closest + base; /* and this is the roof */ +} + +/* Muck with high-order 16 bits of this SPI in order to make + * the corresponding SAID unique. + * Its low-order 16 bits hold a well-known IPCOMP CPI. + * Oh, and remember that SPIs are stored in network order. + * Kludge!!! So I name it with the non-English word "uniquify". + * If we can't find one easily, return 0 (a bad SPI, + * no matter what order) indicating failure. + */ +ipsec_spi_t +uniquify_his_cpi(ipsec_spi_t cpi, struct state *st) +{ + int tries = 0; + int i; + +startover: + + /* network order makes first two bytes our target */ + get_rnd_bytes((u_char *)&cpi, 2); + + /* Make sure that the result is unique. + * Hard work. If there is no unique value, we'll loop forever! + */ + for (i = 0; i < STATE_TABLE_SIZE; i++) + { + struct state *s; + + for (s = statetable[i]; s != NULL; s = s->st_hashchain_next) + { + if (s->st_ipcomp.present + && sameaddr(&s->st_connection->spd.that.host_addr + , &st->st_connection->spd.that.host_addr) + && cpi == s->st_ipcomp.attrs.spi) + { + if (++tries == 20) + return 0; /* FAILURE */ + goto startover; + } + } + } + return cpi; +} + +/* + * Local Variables: + * c-basic-offset:4 + * End: + */ diff --git a/src/pluto/state.h b/src/pluto/state.h new file mode 100644 index 000000000..d3a980564 --- /dev/null +++ b/src/pluto/state.h @@ -0,0 +1,273 @@ +/* state and event objects + * Copyright (C) 1997 Angelos D. Keromytis. + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: state.h,v 1.11 2006/03/08 22:12:37 as Exp $ + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <time.h> +#include <gmp.h> /* GNU MP library */ + +#include "connections.h" + +/* Message ID mechanism. + * + * A Message ID is contained in each IKE message header. + * For Phase 1 exchanges (Main and Aggressive), it will be zero. + * For other exchanges, which must be under the protection of an + * ISAKMP SA, the Message ID must be unique within that ISAKMP SA. + * Effectively, this labels the message as belonging to a particular + * exchange. + * + * RFC2408 "ISAKMP" 3.1 "ISAKMP Header Format" (near end) states that + * the Message ID must be unique. We interpret this to be "unique within + * one ISAKMP SA". + * + * BTW, we feel this uniqueness allows rekeying to be somewhat simpler + * than specified by draft-jenkins-ipsec-rekeying-06.txt. + */ + +typedef u_int32_t msgid_t; /* Network order! */ +#define MAINMODE_MSGID ((msgid_t) 0) + +struct state; /* forward declaration of tag */ +extern bool reserve_msgid(struct state *isakmp_sa, msgid_t msgid); +extern msgid_t generate_msgid(struct state *isakmp_sa); + + +/* Oakley (Phase 1 / Main Mode) transform and attributes + * This is a flattened/decoded version of what is represented + * in the Transaction Payload. + * Names are chosen to match corresponding names in state. + */ +struct oakley_trans_attrs { + u_int16_t encrypt; /* Encryption algorithm */ + u_int16_t enckeylen; /* encryption key len (bits) */ + const struct encrypt_desc *encrypter; /* package of encryption routines */ + u_int16_t hash; /* Hash algorithm */ + const struct hash_desc *hasher; /* package of hashing routines */ + u_int16_t auth; /* Authentication method */ + const struct oakley_group_desc *group; /* Oakley group */ + time_t life_seconds; /* When this SA expires (seconds) */ + u_int32_t life_kilobytes; /* When this SA is exhausted (kilobytes) */ +#if 0 /* not yet */ + u_int16_t prf; /* Pseudo Random Function */ +#endif +}; + +/* IPsec (Phase 2 / Quick Mode) transform and attributes + * This is a flattened/decoded version of what is represented + * by a Transaction Payload. There may be one for AH, one + * for ESP, and a funny one for IPCOMP. + */ +struct ipsec_trans_attrs { + u_int8_t transid; /* transform id */ + ipsec_spi_t spi; /* his SPI */ + time_t life_seconds; /* When this SA expires */ + u_int32_t life_kilobytes; /* When this SA expires */ + u_int16_t encapsulation; + u_int16_t auth; + u_int16_t key_len; + u_int16_t key_rounds; +#if 0 /* not implemented yet */ + u_int16_t cmprs_dict_sz; + u_int32_t cmprs_alg; +#endif +}; + +/* IPsec per protocol state information */ +struct ipsec_proto_info { + bool present; /* was this transform specified? */ + struct ipsec_trans_attrs attrs; + ipsec_spi_t our_spi; + u_int16_t keymat_len; /* same for both */ + u_char *our_keymat; + u_char *peer_keymat; +}; + +/* state object: record the state of a (possibly nascent) SA + * + * Invariants (violated only during short transitions): + * - each state object will be in statetable exactly once. + * - each state object will always have a pending event. + * This prevents leaks. + */ +struct state +{ + so_serial_t st_serialno; /* serial number (for seniority) */ + so_serial_t st_clonedfrom; /* serial number of parent */ + + struct connection *st_connection; /* connection for this SA */ + + int st_whack_sock; /* fd for our Whack TCP socket. + * Single copy: close when freeing struct. + */ + + struct msg_digest *st_suspended_md; /* suspended state-transition */ + + struct oakley_trans_attrs st_oakley; + + struct ipsec_proto_info st_ah; + struct ipsec_proto_info st_esp; + struct ipsec_proto_info st_ipcomp; +#ifdef KLIPS + ipsec_spi_t st_tunnel_in_spi; /* KLUDGE */ + ipsec_spi_t st_tunnel_out_spi; /* KLUDGE */ +#endif + + const struct oakley_group_desc *st_pfs_group; /* group for Phase 2 PFS */ + + u_int32_t st_doi; /* Domain of Interpretation */ + u_int32_t st_situation; + + lset_t st_policy; /* policy for IPsec SA */ + + msgid_t st_msgid; /* MSG-ID from header. Network Order! */ + + /* only for a state representing an ISAKMP SA */ + struct msgid_list *st_used_msgids; /* used-up msgids */ + +/* symmetric stuff */ + + /* initiator stuff */ + chunk_t st_gi; /* Initiator public value */ + u_int8_t st_icookie[COOKIE_SIZE];/* Initiator Cookie */ + chunk_t st_ni; /* Ni nonce */ + + /* responder stuff */ + chunk_t st_gr; /* Responder public value */ + u_int8_t st_rcookie[COOKIE_SIZE];/* Responder Cookie */ + chunk_t st_nr; /* Nr nonce */ + + + /* my stuff */ + + chunk_t st_tpacket; /* Transmitted packet */ + + /* Phase 2 ID payload info about my user */ + u_int8_t st_myuserprotoid; /* IDcx.protoid */ + u_int16_t st_myuserport; + + /* his stuff */ + + chunk_t st_rpacket; /* Received packet */ + + /* Phase 2 ID payload info about peer's user */ + u_int8_t st_peeruserprotoid; /* IDcx.protoid */ + u_int16_t st_peeruserport; + +/* end of symmetric stuff */ + + u_int8_t st_sec_in_use; /* bool: does st_sec hold a value */ + MP_INT st_sec; /* Our local secret value */ + + chunk_t st_shared; /* Derived shared secret + * Note: during Quick Mode, + * presence indicates PFS + * selected. + */ + + /* In a Phase 1 state, preserve peer's public key after authentication */ + struct pubkey *st_peer_pubkey; + + enum state_kind st_state; /* State of exchange */ + u_int8_t st_retransmit; /* Number of retransmits */ + unsigned long st_try; /* number of times rekeying attempted */ + /* 0 means the only time */ + time_t st_margin; /* life after EVENT_SA_REPLACE */ + unsigned long st_outbound_count; /* traffic through eroute */ + time_t st_outbound_time; /* time of last change to st_outbound_count */ + chunk_t st_p1isa; /* Phase 1 initiator SA (Payload) for HASH */ + chunk_t st_skeyid; /* Key material */ + chunk_t st_skeyid_d; /* KM for non-ISAKMP key derivation */ + chunk_t st_skeyid_a; /* KM for ISAKMP authentication */ + chunk_t st_skeyid_e; /* KM for ISAKMP encryption */ + u_char st_iv[MAX_DIGEST_LEN]; /* IV for encryption */ + u_char st_new_iv[MAX_DIGEST_LEN]; + u_char st_ph1_iv[MAX_DIGEST_LEN]; /* IV at end if phase 1 */ + unsigned int st_iv_len; + unsigned int st_new_iv_len; + unsigned int st_ph1_iv_len; + + chunk_t st_enc_key; /* Oakley Encryption key */ + + struct event *st_event; /* backpointer for certain events */ + struct state *st_hashchain_next; /* Next in list */ + struct state *st_hashchain_prev; /* Previous in list */ + + struct { + bool vars_set; + bool started; + } st_modecfg; + + struct { + int attempt; + bool started; + bool status; + } st_xauth; + + u_int32_t nat_traversal; + ip_address nat_oa; + + /* RFC 3706 Dead Peer Detection */ + bool st_dpd; /* Peer supports DPD */ + time_t st_last_dpd; /* Time of last DPD transmit */ + u_int32_t st_dpd_seqno; /* Next R_U_THERE to send */ + u_int32_t st_dpd_expectseqno; /* Next R_U_THERE_ACK to receive */ + u_int32_t st_dpd_peerseqno; /* global variables */ + struct event *st_dpd_event; /* backpointer for DPD events */ + + u_int32_t st_seen_vendorid; /* Bit field about recognized Vendor ID */ +}; + +/* global variables */ + +extern u_int16_t pluto_port; /* Pluto's port */ + +extern bool states_use_connection(struct connection *c); + +/* state functions */ + +extern struct state *new_state(void); +extern void init_states(void); +extern void insert_state(struct state *st); +extern void unhash_state(struct state *st); +extern void release_whack(struct state *st); +extern void state_eroute_usage(ip_subnet *ours, ip_subnet *his + , unsigned long count, time_t nw); +extern void delete_state(struct state *st); +extern void delete_states_by_connection(struct connection *c, bool relations); + +extern struct state + *duplicate_state(struct state *st), + *find_state(const u_char *icookie + , const u_char *rcookie + , const ip_address *peer + , msgid_t msgid), + *state_with_serialno(so_serial_t sn), + *find_phase2_state_to_delete(const struct state *p1st, u_int8_t protoid + , ipsec_spi_t spi, bool *bogus), + *find_phase1_state(const struct connection *c, lset_t ok_states), + *find_sender(size_t packet_len, u_char *packet); + +extern void show_states_status(bool all, const char *name); +extern void for_each_state(void *(f)(struct state *, void *data), void *data); +extern void find_my_cpi_gap(cpi_t *latest_cpi, cpi_t *first_busy_cpi); +extern ipsec_spi_t uniquify_his_cpi(ipsec_spi_t cpi, struct state *st); +extern void fmt_state(bool all, struct state *st, time_t n + , char *state_buf, size_t state_buf_len + , char *state_buf2, size_t state_buf_len2); +extern void delete_states_by_peer(ip_address *peer); diff --git a/src/pluto/timer.c b/src/pluto/timer.c new file mode 100644 index 000000000..9d3f90ce3 --- /dev/null +++ b/src/pluto/timer.c @@ -0,0 +1,532 @@ +/* timer event handling + * Copyright (C) 1997 Angelos D. Keromytis. + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: timer.c,v 1.5 2004/09/17 21:36:57 as Exp $ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/queue.h> + +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "connections.h" +#include "state.h" +#include "demux.h" +#include "ipsec_doi.h" /* needs demux.h and state.h */ +#include "kernel.h" +#include "server.h" +#include "log.h" +#include "rnd.h" +#include "timer.h" +#include "whack.h" +#include "nat_traversal.h" + +/* monotonic version of time(3) */ +time_t +now(void) +{ + static time_t delta = 0 + , last_time = 0; + time_t n = time((time_t)NULL); + + passert(n != (time_t)-1); + if (last_time > n) + { + plog("time moved backwards %ld seconds", (long)(last_time - n)); + delta += last_time - n; + } + last_time = n; + return n + delta; +} + +/* This file has the event handling routines. Events are + * kept as a linked list of event structures. These structures + * have information like event type, expiration time and a pointer + * to event specific data (for example, to a state structure). + */ + +static struct event *evlist = (struct event *) NULL; + +/* + * This routine places an event in the event list. + */ +void +event_schedule(enum event_type type, time_t tm, struct state *st) +{ + struct event *ev = alloc_thing(struct event, "struct event in event_schedule()"); + + ev->ev_type = type; + ev->ev_time = tm + now(); + ev->ev_state = st; + + /* If the event is associated with a state, put a backpointer to the + * event in the state object, so we can find and delete the event + * if we need to (for example, if we receive a reply). + */ + if (st != NULL) + { + if (type == EVENT_DPD || type == EVENT_DPD_TIMEOUT) + { + passert(st->st_dpd_event == NULL); + st->st_dpd_event = ev; + } + else + { + passert(st->st_event == NULL); + st->st_event = ev; + } + } + + DBG(DBG_CONTROL, + if (st == NULL) + DBG_log("inserting event %s, timeout in %lu seconds" + , enum_show(&timer_event_names, type), (unsigned long)tm); + else + DBG_log("inserting event %s, timeout in %lu seconds for #%lu" + , enum_show(&timer_event_names, type), (unsigned long)tm + , ev->ev_state->st_serialno)); + + if (evlist == (struct event *) NULL + || evlist->ev_time >= ev->ev_time) + { + ev->ev_next = evlist; + evlist = ev; + } + else + { + struct event *evt; + + for (evt = evlist; evt->ev_next != NULL; evt = evt->ev_next) + if (evt->ev_next->ev_time >= ev->ev_time) + break; + +#ifdef NEVER /* this seems to be overkill */ + DBG(DBG_CONTROL, + if (evt->ev_state == NULL) + DBG_log("event added after event %s" + , enum_show(&timer_event_names, evt->ev_type)); + else + DBG_log("event added after event %s for #%lu" + , enum_show(&timer_event_names, evt->ev_type) + , evt->ev_state->st_serialno)); +#endif /* NEVER */ + + ev->ev_next = evt->ev_next; + evt->ev_next = ev; + } +} + +/* + * Handle the first event on the list. + */ +void +handle_timer_event(void) +{ + time_t tm; + struct event *ev = evlist; + int type; + struct state *st; + struct connection *c = NULL; + ip_address peer; + + if (ev == (struct event *) NULL) /* Just paranoid */ + { + DBG(DBG_CONTROL, DBG_log("empty event list, yet we're called")); + return; + } + + type = ev->ev_type; + st = ev->ev_state; + + tm = now(); + + if (tm < ev->ev_time) + { + DBG(DBG_CONTROL, DBG_log("called while no event expired (%lu/%lu, %s)" + , (unsigned long)tm, (unsigned long)ev->ev_time + , enum_show(&timer_event_names, type))); + + /* This will happen if the most close-to-expire event was + * a retransmission or cleanup, and we received a packet + * at the same time as the event expired. Due to the processing + * order in call_server(), the packet processing will happen first, + * and the event will be removed. + */ + return; + } + + evlist = evlist->ev_next; /* Ok, we'll handle this event */ + + DBG(DBG_CONTROL, + if (evlist != (struct event *) NULL) + DBG_log("event after this is %s in %ld seconds" + , enum_show(&timer_event_names, evlist->ev_type) + , (long) (evlist->ev_time - tm))); + + /* for state-associated events, pick up the state pointer + * and remove the backpointer from the state object. + * We'll eventually either schedule a new event, or delete the state. + */ + passert(GLOBALS_ARE_RESET()); + if (st != NULL) + { + c = st->st_connection; + if (type == EVENT_DPD || type == EVENT_DPD_TIMEOUT) + { + passert(st->st_dpd_event == ev); + st->st_dpd_event = NULL; + } + else + { + passert(st->st_event == ev); + st->st_event = NULL; + } + peer = c->spd.that.host_addr; + set_cur_state(st); + } + + switch (type) + { + case EVENT_REINIT_SECRET: + passert(st == NULL); + DBG(DBG_CONTROL, DBG_log("event EVENT_REINIT_SECRET handled")); + init_secret(); + break; + +#ifdef KLIPS + case EVENT_SHUNT_SCAN: + passert(st == NULL); + scan_proc_shunts(); + break; +#endif + + case EVENT_LOG_DAILY: + daily_log_event(); + break; + + case EVENT_RETRANSMIT: + /* Time to retransmit, or give up. + * + * Generally, we'll only try to send the message + * MAXIMUM_RETRANSMISSIONS times. Each time we double + * our patience. + * + * As a special case, if this is the first initiating message + * of a Main Mode exchange, and we have been directed to try + * forever, we'll extend the number of retransmissions to + * MAXIMUM_RETRANSMISSIONS_INITIAL times, with all these + * extended attempts having the same patience. The intention + * is to reduce the bother when nobody is home. + */ + { + time_t delay = 0; + + DBG(DBG_CONTROL, DBG_log( + "handling event EVENT_RETRANSMIT for %s \"%s\" #%lu" + , ip_str(&peer), c->name, st->st_serialno)); + + if (st->st_retransmit < MAXIMUM_RETRANSMISSIONS) + delay = EVENT_RETRANSMIT_DELAY_0 << (st->st_retransmit + 1); + else if (st->st_state == STATE_MAIN_I1 + && c->sa_keying_tries == 0 + && st->st_retransmit < MAXIMUM_RETRANSMISSIONS_INITIAL) + delay = EVENT_RETRANSMIT_DELAY_0 << MAXIMUM_RETRANSMISSIONS; + + if (delay != 0) + { + st->st_retransmit++; + whack_log(RC_RETRANSMISSION + , "%s: retransmission; will wait %lus for response" + , enum_name(&state_names, st->st_state) + , (unsigned long)delay); + send_packet(st, "EVENT_RETRANSMIT"); + event_schedule(EVENT_RETRANSMIT, delay, st); + } + else + { + /* check if we've tried rekeying enough times. + * st->st_try == 0 means that this should be the only try. + * c->sa_keying_tries == 0 means that there is no limit. + */ + unsigned long try = st->st_try; + unsigned long try_limit = c->sa_keying_tries; + const char *details = ""; + + switch (st->st_state) + { + case STATE_MAIN_I3: + details = ". Possible authentication failure:" + " no acceptable response to our" + " first encrypted message"; + break; + case STATE_MAIN_I1: + details = ". No response (or no acceptable response) to our" + " first IKE message"; + break; + case STATE_QUICK_I1: + if (c->newest_ipsec_sa == SOS_NOBODY) + details = ". No acceptable response to our" + " first Quick Mode message:" + " perhaps peer likes no proposal"; + break; + default: + break; + } + loglog(RC_NORETRANSMISSION + , "max number of retransmissions (%d) reached %s%s" + , st->st_retransmit + , enum_show(&state_names, st->st_state), details); + if (try != 0 && try != try_limit) + { + /* A lot like EVENT_SA_REPLACE, but over again. + * Since we know that st cannot be in use, + * we can delete it right away. + */ + char story[80]; /* arbitrary limit */ + + try++; + snprintf(story, sizeof(story), try_limit == 0 + ? "starting keying attempt %ld of an unlimited number" + : "starting keying attempt %ld of at most %ld" + , try, try_limit); + + if (st->st_whack_sock != NULL_FD) + { + /* Release whack because the observer will get bored. */ + loglog(RC_COMMENT, "%s, but releasing whack" + , story); + release_pending_whacks(st, story); + } + else + { + /* no whack: just log to syslog */ + plog("%s", story); + } + ipsecdoi_replace(st, try); + } + delete_state(st); + } + } + break; + + case EVENT_SA_REPLACE: + case EVENT_SA_REPLACE_IF_USED: + { + so_serial_t newest = IS_PHASE1(st->st_state) + ? c->newest_isakmp_sa : c->newest_ipsec_sa; + + if (newest != st->st_serialno + && newest != SOS_NOBODY) + { + /* not very interesting: no need to replace */ + DBG(DBG_LIFECYCLE + , plog("not replacing stale %s SA: #%lu will do" + , IS_PHASE1(st->st_state)? "ISAKMP" : "IPsec" + , newest)); + } + else if (type == EVENT_SA_REPLACE_IF_USED + && st->st_outbound_time <= tm - c->sa_rekey_margin) + { + /* we observed no recent use: no need to replace + * + * The sampling effects mean that st_outbound_time + * could be up to SHUNT_SCAN_INTERVAL more recent + * than actual traffic because the sampler looks at change + * over that interval. + * st_outbound_time could also not yet reflect traffic + * in the last SHUNT_SCAN_INTERVAL. + * We expect that SHUNT_SCAN_INTERVAL is smaller than + * c->sa_rekey_margin so that the effects of this will + * be unimportant. + * This is just an optimization: correctness is not + * at stake. + * + * Note: we are abusing the DBG mechanism to control + * normal log output. + */ + DBG(DBG_LIFECYCLE + , plog("not replacing stale %s SA: inactive for %lus" + , IS_PHASE1(st->st_state)? "ISAKMP" : "IPsec" + , (unsigned long)(tm - st->st_outbound_time))); + } + else + { + DBG(DBG_LIFECYCLE + , plog("replacing stale %s SA" + , IS_PHASE1(st->st_state)? "ISAKMP" : "IPsec")); + ipsecdoi_replace(st, 1); + } + delete_dpd_event(st); + event_schedule(EVENT_SA_EXPIRE, st->st_margin, st); + } + break; + + case EVENT_SA_EXPIRE: + { + const char *satype; + so_serial_t latest; + + if (IS_PHASE1(st->st_state)) + { + satype = "ISAKMP"; + latest = c->newest_isakmp_sa; + } + else + { + satype = "IPsec"; + latest = c->newest_ipsec_sa; + } + + if (st->st_serialno != latest) + { + /* not very interesting: already superseded */ + DBG(DBG_LIFECYCLE + , plog("%s SA expired (superseded by #%lu)" + , satype, latest)); + } + else + { + plog("%s SA expired (%s)", satype + , (c->policy & POLICY_DONT_REKEY) + ? "--dontrekey" + : "LATEST!" + ); + } + } + /* FALLTHROUGH */ + case EVENT_SO_DISCARD: + /* Delete this state object. It must be in the hash table. */ + delete_state(st); + break; + + case EVENT_DPD: + dpd_outI(st); + break; + case EVENT_DPD_TIMEOUT: + dpd_timeout(st); + break; + case EVENT_NAT_T_KEEPALIVE: + nat_traversal_ka_event(); + break; + default: + loglog(RC_LOG_SERIOUS, "INTERNAL ERROR: ignoring unknown expiring event %s" + , enum_show(&timer_event_names, type)); + } + + pfree(ev); + reset_cur_state(); +} + +/* + * Return the time until the next event in the queue + * expires (never negative), or -1 if no jobs in queue. + */ +long +next_event(void) +{ + time_t tm; + + if (evlist == (struct event *) NULL) + return -1; + + tm = now(); + + DBG(DBG_CONTROL, + if (evlist->ev_state == NULL) + DBG_log("next event %s in %ld seconds" + , enum_show(&timer_event_names, evlist->ev_type) + , (long)evlist->ev_time - (long)tm); + else + DBG_log("next event %s in %ld seconds for #%lu" + , enum_show(&timer_event_names, evlist->ev_type) + , (long)evlist->ev_time - (long)tm + , evlist->ev_state->st_serialno)); + + if (evlist->ev_time - tm <= 0) + return 0; + else + return evlist->ev_time - tm; +} + +/* + * Delete an event. + */ +void +delete_event(struct state *st) +{ + if (st->st_event != (struct event *) NULL) + { + struct event **ev; + + for (ev = &evlist; ; ev = &(*ev)->ev_next) + { + if (*ev == NULL) + { + DBG(DBG_CONTROL, DBG_log("event %s to be deleted not found", + enum_show(&timer_event_names, st->st_event->ev_type))); + break; + } + if ((*ev) == st->st_event) + { + *ev = (*ev)->ev_next; + + if (st->st_event->ev_type == EVENT_RETRANSMIT) + st->st_retransmit = 0; + pfree(st->st_event); + st->st_event = (struct event *) NULL; + + break; + } + } + } +} + +/* + * Delete a DPD event. + */ +void +delete_dpd_event(struct state *st) +{ + if (st->st_dpd_event != (struct event *) NULL) + { + struct event **ev; + + for (ev = &evlist; ; ev = &(*ev)->ev_next) + { + if (*ev == NULL) + { + DBG(DBG_CONTROL, DBG_log("event %s to be deleted not found", + enum_show(&timer_event_names, st->st_dpd_event->ev_type))); + break; + } + if ((*ev) == st->st_dpd_event) + { + *ev = (*ev)->ev_next; + pfree(st->st_dpd_event); + st->st_dpd_event = (struct event *) NULL; + break; + } + } + } +} + + diff --git a/src/pluto/timer.h b/src/pluto/timer.h new file mode 100644 index 000000000..92464192c --- /dev/null +++ b/src/pluto/timer.h @@ -0,0 +1,34 @@ +/* timing machinery + * Copyright (C) 1998-2001 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: timer.h,v 1.2 2004/07/29 18:33:45 as Exp $ + */ + +extern time_t now(void); /* careful version of time(2) */ + +struct state; /* forward declaration */ + +struct event +{ + time_t ev_time; + int ev_type; /* Event type */ + struct state *ev_state; /* Pointer to relevant state (if any) */ + struct event *ev_next; /* Pointer to next event */ +}; + +extern void event_schedule(enum event_type type, time_t tm, struct state *st); +extern void handle_timer_event(void); +extern long next_event(void); +extern void delete_event(struct state *st); +extern void delete_dpd_event(struct state *st); +extern void daily_log_event(void); diff --git a/src/pluto/vendor.c b/src/pluto/vendor.c new file mode 100644 index 000000000..e888d5e16 --- /dev/null +++ b/src/pluto/vendor.c @@ -0,0 +1,528 @@ +/* ISAKMP VendorID + * Copyright (C) 2002-2005 Mathieu Lafon - Arkoon Network Security + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: vendor.c,v 1.35 2006/04/12 16:44:28 as Exp $ + */ + +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <sys/queue.h> +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "md5.h" +#include "connections.h" +#include "packet.h" +#include "demux.h" +#include "whack.h" +#include "vendor.h" +#include "kernel.h" +#include "nat_traversal.h" + +/** + * Unknown/Special VID: + * + * SafeNet SoftRemote 8.0.0: + * 47bbe7c993f1fc13b4e6d0db565c68e5010201010201010310382e302e3020284275696c6420313029000000 + * >> 382e302e3020284275696c6420313029 = '8.0.0 (Build 10)' + * da8e937880010000 + * + * SafeNet SoftRemote 9.0.1 + * 47bbe7c993f1fc13b4e6d0db565c68e5010201010201010310392e302e3120284275696c6420313229000000 + * >> 392e302e3120284275696c6420313229 = '9.0.1 (Build 12)' + * da8e937880010000 + * + * Netscreen: + * d6b45f82f24bacb288af59a978830ab7 + * cf49908791073fb46439790fdeb6aeed981101ab0000000500000300 + * + * Cisco: + * 1f07f70eaa6514d3b0fa96542a500300 (VPN 3000 version 3.0.0) + * 1f07f70eaa6514d3b0fa96542a500301 (VPN 3000 version 3.0.1) + * 1f07f70eaa6514d3b0fa96542a500305 (VPN 3000 version 3.0.5) + * 1f07f70eaa6514d3b0fa96542a500407 (VPN 3000 version 4.0.7) + * (Can you see the pattern?) + * afcad71368a1f1c96b8696fc77570100 (Non-RFC Dead Peer Detection ?) + * c32364b3b4f447eb17c488ab2a480a57 + * 6d761ddc26aceca1b0ed11fabbb860c4 + * 5946c258f99a1a57b03eb9d1759e0f24 (From a Cisco VPN 3k) + * ebbc5b00141d0c895e11bd395902d690 (From a Cisco VPN 3k) + * + * Microsoft L2TP (???): + * 47bbe7c993f1fc13b4e6d0db565c68e5010201010201010310382e312e3020284275696c6420313029000000 + * >> 382e312e3020284275696c6420313029 = '8.1.0 (Build 10)' + * 3025dbd21062b9e53dc441c6aab5293600000000 + * da8e937880010000 + * + * 3COM-superstack + * da8e937880010000 + * 404bf439522ca3f6 + * + + * If someone know what they mean, mail me. + */ + +#define MAX_LOG_VID_LEN 32 + +#define VID_KEEP 0x0000 +#define VID_MD5HASH 0x0001 +#define VID_STRING 0x0002 +#define VID_FSWAN_HASH 0x0004 + +#define VID_SUBSTRING_DUMPHEXA 0x0100 +#define VID_SUBSTRING_DUMPASCII 0x0200 +#define VID_SUBSTRING_MATCH 0x0400 +#define VID_SUBSTRING (VID_SUBSTRING_DUMPHEXA | VID_SUBSTRING_DUMPASCII | VID_SUBSTRING_MATCH) + +struct vid_struct { + enum known_vendorid id; + unsigned short flags; + const char *data; + const char *descr; + const char *vid; + u_int vid_len; +}; + +#define DEC_MD5_VID_D(id,str,descr) \ + { VID_##id, VID_MD5HASH, str, descr, NULL, 0 }, +#define DEC_MD5_VID(id,str) \ + { VID_##id, VID_MD5HASH, str, NULL, NULL, 0 }, +#define DEC_FSWAN_VID(id,str,descr) \ + { VID_##id, VID_FSWAN_HASH, str, descr, NULL, 0 }, + +static struct vid_struct _vid_tab[] = { + + /* Implementation names */ + + { VID_OPENPGP, VID_STRING, "OpenPGP10171", "OpenPGP", NULL, 0 }, + + DEC_MD5_VID(KAME_RACOON, "KAME/racoon") + + { VID_MS_NT5, VID_MD5HASH | VID_SUBSTRING_DUMPHEXA, + "MS NT5 ISAKMPOAKLEY", NULL, NULL, 0 }, + + DEC_MD5_VID(SSH_SENTINEL, "SSH Sentinel") + DEC_MD5_VID(SSH_SENTINEL_1_1, "SSH Sentinel 1.1") + DEC_MD5_VID(SSH_SENTINEL_1_2, "SSH Sentinel 1.2") + DEC_MD5_VID(SSH_SENTINEL_1_3, "SSH Sentinel 1.3") + DEC_MD5_VID(SSH_SENTINEL_1_4, "SSH Sentinel 1.4") + DEC_MD5_VID(SSH_SENTINEL_1_4_1, "SSH Sentinel 1.4.1") + + /* These ones come from SSH vendors.txt */ + DEC_MD5_VID(SSH_IPSEC_1_1_0, + "Ssh Communications Security IPSEC Express version 1.1.0") + DEC_MD5_VID(SSH_IPSEC_1_1_1, + "Ssh Communications Security IPSEC Express version 1.1.1") + DEC_MD5_VID(SSH_IPSEC_1_1_2, + "Ssh Communications Security IPSEC Express version 1.1.2") + DEC_MD5_VID(SSH_IPSEC_1_2_1, + "Ssh Communications Security IPSEC Express version 1.2.1") + DEC_MD5_VID(SSH_IPSEC_1_2_2, + "Ssh Communications Security IPSEC Express version 1.2.2") + DEC_MD5_VID(SSH_IPSEC_2_0_0, + "SSH Communications Security IPSEC Express version 2.0.0") + DEC_MD5_VID(SSH_IPSEC_2_1_0, + "SSH Communications Security IPSEC Express version 2.1.0") + DEC_MD5_VID(SSH_IPSEC_2_1_1, + "SSH Communications Security IPSEC Express version 2.1.1") + DEC_MD5_VID(SSH_IPSEC_2_1_2, + "SSH Communications Security IPSEC Express version 2.1.2") + DEC_MD5_VID(SSH_IPSEC_3_0_0, + "SSH Communications Security IPSEC Express version 3.0.0") + DEC_MD5_VID(SSH_IPSEC_3_0_1, + "SSH Communications Security IPSEC Express version 3.0.1") + DEC_MD5_VID(SSH_IPSEC_4_0_0, + "SSH Communications Security IPSEC Express version 4.0.0") + DEC_MD5_VID(SSH_IPSEC_4_0_1, + "SSH Communications Security IPSEC Express version 4.0.1") + DEC_MD5_VID(SSH_IPSEC_4_1_0, + "SSH Communications Security IPSEC Express version 4.1.0") + DEC_MD5_VID(SSH_IPSEC_4_2_0, + "SSH Communications Security IPSEC Express version 4.2.0") + + /* note: md5('CISCO-UNITY') = 12f5f28c457168a9702d9fe274cc02d4 */ + { VID_CISCO_UNITY, VID_KEEP, NULL, "Cisco-Unity", + "\x12\xf5\xf2\x8c\x45\x71\x68\xa9\x70\x2d\x9f\xe2\x74\xcc\x01\x00", + 16 }, + + { VID_CISCO3K, VID_KEEP | VID_SUBSTRING_MATCH, + NULL, "Cisco VPN 3000 Series" , "\x1f\x07\xf7\x0e\xaa\x65\x14\xd3\xb0\xfa\x96\x54\x2a\x50", 14}, + + { VID_CISCO_IOS, VID_KEEP | VID_SUBSTRING_MATCH, + NULL, "Cisco IOS Device", "\x3e\x98\x40\x48", 4}, + + /* + * Timestep VID seen: + * - 54494d455354455020312053475720313532302033313520322e303145303133 + * = 'TIMESTEP 1 SGW 1520 315 2.01E013' + */ + { VID_TIMESTEP, VID_STRING | VID_SUBSTRING_DUMPASCII, "TIMESTEP", + NULL, NULL, 0 }, + + /* + * Netscreen: + * 4865617274426561745f4e6f74696679386b0100 (HeartBeat_Notify + 386b0100) + */ + { VID_MISC_HEARTBEAT_NOTIFY, VID_STRING | VID_SUBSTRING_DUMPHEXA, + "HeartBeat_Notify", "HeartBeat Notify", NULL, 0 }, + + /* + * MacOS X + */ + { VID_MACOSX, VID_STRING|VID_SUBSTRING_DUMPHEXA, "Mac OSX 10.x", + "\x4d\xf3\x79\x28\xe9\xfc\x4f\xd1\xb3\x26\x21\x70\xd5\x15\xc6\x62", NULL, 0}, + + /* + * Openswan + */ + DEC_FSWAN_VID(OPENSWAN2, "Openswan 2.2.0", "Openswan 2.2.0") + + /* NCP */ + { VID_NCP_SERVER, VID_KEEP | VID_SUBSTRING_MATCH, NULL, "NCP Server", + "\xc6\xf5\x7a\xc3\x98\xf4\x93\x20\x81\x45\xb7\x58", 12}, + { VID_NCP_CLIENT, VID_KEEP | VID_SUBSTRING_MATCH, NULL, "NCP Client", + "\xeb\x4c\x1b\x78\x8a\xfd\x4a\x9c\xb7\x73\x0a\x68", 12}, + + /* + * Windows Vista (and Longhorn?) + */ + DEC_MD5_VID(VISTA_AUTHIP, "MS-Negotiation Discovery Capable") + DEC_MD5_VID(VISTA_AUTHIP2, "IKE CGA version 1") + + /* + * strongSwan + */ + DEC_MD5_VID(STRONGSWAN, "strongSwan 4.1.1") + DEC_MD5_VID(STRONGSWAN_4_1_0, "strongSwan 4.1.0") + DEC_MD5_VID(STRONGSWAN_4_0_7, "strongSwan 4.0.7") + DEC_MD5_VID(STRONGSWAN_4_0_6, "strongSwan 4.0.6") + DEC_MD5_VID(STRONGSWAN_4_0_5, "strongSwan 4.0.5") + DEC_MD5_VID(STRONGSWAN_4_0_4, "strongSwan 4.0.4") + DEC_MD5_VID(STRONGSWAN_4_0_3, "strongSwan 4.0.3") + DEC_MD5_VID(STRONGSWAN_4_0_2, "strongSwan 4.0.2") + DEC_MD5_VID(STRONGSWAN_4_0_1, "strongSwan 4.0.1") + DEC_MD5_VID(STRONGSWAN_4_0_0, "strongSwan 4.0.0") + + DEC_MD5_VID(STRONGSWAN_2_8_4, "strongSwan 2.8.4") + DEC_MD5_VID(STRONGSWAN_2_8_3, "strongSwan 2.8.3") + DEC_MD5_VID(STRONGSWAN_2_8_2, "strongSwan 2.8.2") + DEC_MD5_VID(STRONGSWAN_2_8_1, "strongSwan 2.8.1") + DEC_MD5_VID(STRONGSWAN_2_8_0, "strongSwan 2.8.0") + DEC_MD5_VID(STRONGSWAN_2_7_3, "strongSwan 2.7.3") + DEC_MD5_VID(STRONGSWAN_2_7_2, "strongSwan 2.7.2") + DEC_MD5_VID(STRONGSWAN_2_7_1, "strongSwan 2.7.1") + DEC_MD5_VID(STRONGSWAN_2_7_0, "strongSwan 2.7.0") + DEC_MD5_VID(STRONGSWAN_2_6_4, "strongSwan 2.6.4") + DEC_MD5_VID(STRONGSWAN_2_6_3, "strongSwan 2.6.3") + DEC_MD5_VID(STRONGSWAN_2_6_2, "strongSwan 2.6.2") + DEC_MD5_VID(STRONGSWAN_2_6_1, "strongSwan 2.6.1") + DEC_MD5_VID(STRONGSWAN_2_6_0, "strongSwan 2.6.0") + DEC_MD5_VID(STRONGSWAN_2_5_7, "strongSwan 2.5.7") + DEC_MD5_VID(STRONGSWAN_2_5_6, "strongSwan 2.5.6") + DEC_MD5_VID(STRONGSWAN_2_5_5, "strongSwan 2.5.5") + DEC_MD5_VID(STRONGSWAN_2_5_4, "strongSwan 2.5.4") + DEC_MD5_VID(STRONGSWAN_2_5_3, "strongSwan 2.5.3") + DEC_MD5_VID(STRONGSWAN_2_5_2, "strongSwan 2.5.2") + DEC_MD5_VID(STRONGSWAN_2_5_1, "strongSwan 2.5.1") + DEC_MD5_VID(STRONGSWAN_2_5_0, "strongSwan 2.5.0") + DEC_MD5_VID(STRONGSWAN_2_4_4, "strongSwan 2.4.4") + DEC_MD5_VID(STRONGSWAN_2_4_3, "strongSwan 2.4.3") + DEC_MD5_VID(STRONGSWAN_2_4_2, "strongSwan 2.4.2") + DEC_MD5_VID(STRONGSWAN_2_4_1, "strongSwan 2.4.1") + DEC_MD5_VID(STRONGSWAN_2_4_0, "strongSwan 2.4.0") + DEC_MD5_VID(STRONGSWAN_2_3_2, "strongSwan 2.3.2") + DEC_MD5_VID(STRONGSWAN_2_3_1, "strongSwan 2.3.1") + DEC_MD5_VID(STRONGSWAN_2_3_0, "strongSwan 2.3.0") + DEC_MD5_VID(STRONGSWAN_2_2_2, "strongSwan 2.2.2") + DEC_MD5_VID(STRONGSWAN_2_2_1, "strongSwan 2.2.1") + DEC_MD5_VID(STRONGSWAN_2_2_0, "strongSwan 2.2.0") + + /* NAT-Traversal */ + + DEC_MD5_VID(NATT_STENBERG_01, "draft-stenberg-ipsec-nat-traversal-01") + DEC_MD5_VID(NATT_STENBERG_02, "draft-stenberg-ipsec-nat-traversal-02") + DEC_MD5_VID(NATT_HUTTUNEN, "ESPThruNAT") + DEC_MD5_VID(NATT_HUTTUNEN_ESPINUDP, "draft-huttunen-ipsec-esp-in-udp-00.txt") + DEC_MD5_VID(NATT_IETF_00, "draft-ietf-ipsec-nat-t-ike-00") + DEC_MD5_VID(NATT_IETF_02, "draft-ietf-ipsec-nat-t-ike-02") + /* hash in draft-ietf-ipsec-nat-t-ike-02 contains '\n'... Accept both */ + DEC_MD5_VID_D(NATT_IETF_02_N, "draft-ietf-ipsec-nat-t-ike-02\n", "draft-ietf-ipsec-nat-t-ike-02_n") + DEC_MD5_VID(NATT_IETF_03, "draft-ietf-ipsec-nat-t-ike-03") + DEC_MD5_VID(NATT_RFC, "RFC 3947") + + /* misc */ + + { VID_MISC_XAUTH, VID_KEEP, NULL, "XAUTH", + "\x09\x00\x26\x89\xdf\xd6\xb7\x12", 8 }, + + { VID_MISC_DPD, VID_KEEP, NULL, "Dead Peer Detection", + "\xaf\xca\xd7\x13\x68\xa1\xf1\xc9\x6b\x86\x96\xfc\x77\x57\x01\x00", 16 }, + + DEC_MD5_VID(MISC_FRAGMENTATION, "FRAGMENTATION") + + DEC_MD5_VID(INITIAL_CONTACT, "Vid-Initial-Contact") + + /** + * Cisco VPN 3000 + */ + { VID_MISC_FRAGMENTATION, VID_MD5HASH | VID_SUBSTRING_DUMPHEXA, + "FRAGMENTATION", NULL, NULL, 0 }, + + /* -- */ + { 0, 0, NULL, NULL, NULL, 0 } + +}; + +static const char _hexdig[] = "0123456789abcdef"; + +static int _vid_struct_init = 0; + +void +init_vendorid(void) +{ + struct vid_struct *vid; + MD5_CTX ctx; + int i; + + for (vid = _vid_tab; vid->id; vid++) + { + if (vid->flags & VID_STRING) + { + /** VendorID is a string **/ + vid->vid = strdup(vid->data); + vid->vid_len = strlen(vid->data); + } + else if (vid->flags & VID_MD5HASH) + { + /** VendorID is a string to hash with MD5 **/ + char *vidm = malloc(MD5_DIGEST_SIZE); + + vid->vid = vidm; + if (vidm) + { + MD5Init(&ctx); + MD5Update(&ctx, (const u_char *)vid->data, strlen(vid->data)); + MD5Final(vidm, &ctx); + vid->vid_len = MD5_DIGEST_SIZE; + } + } + else if (vid->flags & VID_FSWAN_HASH) + { + /** FreeS/WAN 2.00+ specific hash **/ +#define FSWAN_VID_SIZE 12 + unsigned char hash[MD5_DIGEST_SIZE]; + char *vidm = malloc(FSWAN_VID_SIZE); + + vid->vid = vidm; + if (vidm) + { + MD5Init(&ctx); + MD5Update(&ctx, (const u_char *)vid->data, strlen(vid->data)); + MD5Final(hash, &ctx); + vidm[0] = 'O'; + vidm[1] = 'E'; +#if FSWAN_VID_SIZE - 2 <= MD5_DIGEST_SIZE + memcpy(vidm + 2, hash, FSWAN_VID_SIZE - 2); +#else + memcpy(vidm + 2, hash, MD5_DIGEST_SIZE); + memset(vidm + 2 + MD5_DIGEST_SIZE, '\0', + FSWAN_VID_SIZE - 2 - MD5_DIGEST_SIZE); +#endif + for (i = 2; i < FSWAN_VID_SIZE; i++) + { + vidm[i] &= 0x7f; + vidm[i] |= 0x40; + } + vid->vid_len = FSWAN_VID_SIZE; + } + } + + if (vid->descr == NULL) + { + /** Find something to display **/ + vid->descr = vid->data; + } + } + _vid_struct_init = 1; +} + +static void +handle_known_vendorid (struct msg_digest *md +, const char *vidstr, size_t len, struct vid_struct *vid) +{ + char vid_dump[128]; + bool vid_useful = FALSE; + size_t i, j; + + switch (vid->id) { + /* Remote side supports OpenPGP certificates */ + case VID_OPENPGP: + md->openpgp = TRUE; + vid_useful = TRUE; + break; + + /* + * Use most recent supported NAT-Traversal method and ignore the + * other ones (implementations will send all supported methods but + * only one will be used) + * + * Note: most recent == higher id in vendor.h + */ + case VID_NATT_IETF_00: + if (!nat_traversal_support_non_ike) + break; + if ((nat_traversal_enabled) && (!md->nat_traversal_vid)) + { + md->nat_traversal_vid = vid->id; + vid_useful = TRUE; + } + break; + case VID_NATT_IETF_02: + case VID_NATT_IETF_02_N: + case VID_NATT_IETF_03: + case VID_NATT_RFC: + if (nat_traversal_support_port_floating + && md->nat_traversal_vid < vid->id) + { + md->nat_traversal_vid = vid->id; + vid_useful = TRUE; + } + break; + + /* Remote side would like to do DPD with us on this connection */ + case VID_MISC_DPD: + md->dpd = TRUE; + vid_useful = TRUE; + break; + case VID_MISC_XAUTH: + vid_useful = TRUE; + break; + default: + break; + } + + if (vid->flags & VID_SUBSTRING_DUMPHEXA) + { + /* Dump description + Hexa */ + memset(vid_dump, 0, sizeof(vid_dump)); + snprintf(vid_dump, sizeof(vid_dump), "%s ", + vid->descr ? vid->descr : ""); + for (i = strlen(vid_dump), j = vid->vid_len; + j < len && i < sizeof(vid_dump) - 2; + i += 2, j++) + { + vid_dump[i] = _hexdig[(vidstr[j] >> 4) & 0xF]; + vid_dump[i+1] = _hexdig[vidstr[j] & 0xF]; + } + } + else if (vid->flags & VID_SUBSTRING_DUMPASCII) + { + /* Dump ASCII content */ + memset(vid_dump, 0, sizeof(vid_dump)); + for (i = 0; i < len && i < sizeof(vid_dump) - 1; i++) + { + vid_dump[i] = (isprint(vidstr[i])) ? vidstr[i] : '.'; + } + } + else + { + /* Dump description (descr) */ + snprintf(vid_dump, sizeof(vid_dump), "%s", + vid->descr ? vid->descr : ""); + } + + loglog(RC_LOG_SERIOUS, "%s Vendor ID payload [%s]", + vid_useful ? "received" : "ignoring", vid_dump); +} + +void +handle_vendorid (struct msg_digest *md, const char *vid, size_t len) +{ + struct vid_struct *pvid; + + if (!_vid_struct_init) + init_vendorid(); + + /* + * Find known VendorID in _vid_tab + */ + for (pvid = _vid_tab; pvid->id; pvid++) + { + if (pvid->vid && vid && pvid->vid_len && len) + { + if (pvid->vid_len == len) + { + if (memcmp(pvid->vid, vid, len) == 0) + { + handle_known_vendorid(md, vid, len, pvid); + return; + } + } + else if ((pvid->vid_len < len) && (pvid->flags & VID_SUBSTRING)) + { + if (memcmp(pvid->vid, vid, pvid->vid_len) == 0) + { + handle_known_vendorid(md, vid, len, pvid); + return; + } + } + } + } + + /* + * Unknown VendorID. Log the beginning. + */ + { + char log_vid[2*MAX_LOG_VID_LEN+1]; + size_t i; + + memset(log_vid, 0, sizeof(log_vid)); + + for (i = 0; i < len && i < MAX_LOG_VID_LEN; i++) + { + log_vid[2*i] = _hexdig[(vid[i] >> 4) & 0xF]; + log_vid[2*i+1] = _hexdig[vid[i] & 0xF]; + } + loglog(RC_LOG_SERIOUS, "ignoring Vendor ID payload [%s%s]", + log_vid, (len>MAX_LOG_VID_LEN) ? "..." : ""); + } +} + +/** + * Add a vendor id payload to the msg + */ +bool +out_vendorid (u_int8_t np, pb_stream *outs, enum known_vendorid vid) +{ + struct vid_struct *pvid; + + if (!_vid_struct_init) + init_vendorid(); + + for (pvid = _vid_tab; pvid->id && pvid->id != vid; pvid++); + + if (pvid->id != vid) + return STF_INTERNAL_ERROR; /* not found */ + if (!pvid->vid) + return STF_INTERNAL_ERROR; /* not initialized */ + + DBG(DBG_EMITTING, + DBG_log("out_vendorid(): sending [%s]", pvid->descr) + ) + return out_generic_raw(np, &isakmp_vendor_id_desc, outs, + pvid->vid, pvid->vid_len, "V_ID"); +} + diff --git a/src/pluto/vendor.h b/src/pluto/vendor.h new file mode 100644 index 000000000..8e0444f4d --- /dev/null +++ b/src/pluto/vendor.h @@ -0,0 +1,131 @@ +/* FreeS/WAN ISAKMP VendorID + * Copyright (C) 2002-2003 Mathieu Lafon - Arkoon Network Security + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: vendor.h,v 1.30 2006/04/12 16:44:28 as Exp $ + */ + +#ifndef _VENDOR_H_ +#define _VENDOR_H_ + +enum known_vendorid { +/* 1 - 100 : Implementation names */ + VID_OPENPGP = 1, + VID_KAME_RACOON = 2, + VID_MS_NT5 = 3, + VID_SSH_SENTINEL = 4, + VID_SSH_SENTINEL_1_1 = 5, + VID_SSH_SENTINEL_1_2 = 6, + VID_SSH_SENTINEL_1_3 = 7, + VID_SSH_SENTINEL_1_4 = 8, + VID_SSH_SENTINEL_1_4_1 = 9, + VID_SSH_IPSEC_1_1_0 = 10, + VID_SSH_IPSEC_1_1_1 = 11, + VID_SSH_IPSEC_1_1_2 = 12, + VID_SSH_IPSEC_1_2_1 = 13, + VID_SSH_IPSEC_1_2_2 = 14, + VID_SSH_IPSEC_2_0_0 = 15, + VID_SSH_IPSEC_2_1_0 = 16, + VID_SSH_IPSEC_2_1_1 = 17, + VID_SSH_IPSEC_2_1_2 = 18, + VID_SSH_IPSEC_3_0_0 = 19, + VID_SSH_IPSEC_3_0_1 = 20, + VID_SSH_IPSEC_4_0_0 = 21, + VID_SSH_IPSEC_4_0_1 = 22, + VID_SSH_IPSEC_4_1_0 = 23, + VID_SSH_IPSEC_4_2_0 = 24, + VID_CISCO_UNITY = 25, + VID_CISCO3K = 26, + VID_CISCO_IOS = 27, + VID_TIMESTEP = 28, + VID_SAFENET = 29, + VID_MACOSX = 30, + VID_OPENSWAN2 = 31, + VID_NCP_SERVER = 32, + VID_NCP_CLIENT = 33, + VID_VISTA_AUTHIP = 34, + VID_VISTA_AUTHIP2 = 35, + + VID_STRONGSWAN = 36, + VID_STRONGSWAN_2_2_0 = 37, + VID_STRONGSWAN_2_2_1 = 38, + VID_STRONGSWAN_2_2_2 = 39, + VID_STRONGSWAN_2_3_0 = 40, + VID_STRONGSWAN_2_3_1 = 41, + VID_STRONGSWAN_2_3_2 = 42, + VID_STRONGSWAN_2_4_0 = 43, + VID_STRONGSWAN_2_4_1 = 44, + VID_STRONGSWAN_2_4_2 = 45, + VID_STRONGSWAN_2_4_3 = 46, + VID_STRONGSWAN_2_4_4 = 47, + VID_STRONGSWAN_2_5_0 = 48, + VID_STRONGSWAN_2_5_1 = 49, + VID_STRONGSWAN_2_5_2 = 50, + VID_STRONGSWAN_2_5_3 = 51, + VID_STRONGSWAN_2_5_4 = 52, + VID_STRONGSWAN_2_5_5 = 53, + VID_STRONGSWAN_2_5_6 = 54, + VID_STRONGSWAN_2_5_7 = 55, + VID_STRONGSWAN_2_6_0 = 56, + VID_STRONGSWAN_2_6_1 = 57, + VID_STRONGSWAN_2_6_2 = 58, + VID_STRONGSWAN_2_6_3 = 59, + VID_STRONGSWAN_2_6_4 = 60, + VID_STRONGSWAN_2_7_0 = 61, + VID_STRONGSWAN_2_7_1 = 62, + VID_STRONGSWAN_2_7_2 = 63, + VID_STRONGSWAN_2_7_3 = 64, + VID_STRONGSWAN_2_8_0 = 65, + VID_STRONGSWAN_2_8_1 = 66, + VID_STRONGSWAN_2_8_2 = 67, + VID_STRONGSWAN_2_8_3 = 68, + VID_STRONGSWAN_2_8_4 = 69, + + VID_STRONGSWAN_4_0_0 = 70, + VID_STRONGSWAN_4_0_1 = 71, + VID_STRONGSWAN_4_0_2 = 72, + VID_STRONGSWAN_4_0_3 = 73, + VID_STRONGSWAN_4_0_4 = 74, + VID_STRONGSWAN_4_0_5 = 75, + VID_STRONGSWAN_4_0_6 = 76, + VID_STRONGSWAN_4_0_7 = 77, + VID_STRONGSWAN_4_1_0 = 78, + + /* 101 - 200 : NAT-Traversal */ + VID_NATT_STENBERG_01 =101, + VID_NATT_STENBERG_02 =102, + VID_NATT_HUTTUNEN =103, + VID_NATT_HUTTUNEN_ESPINUDP =104, + VID_NATT_IETF_00 =105, + VID_NATT_IETF_02_N =106, + VID_NATT_IETF_02 =107, + VID_NATT_IETF_03 =108, + VID_NATT_RFC =109, + + /* 201 - 300 : Misc */ + VID_MISC_XAUTH =201, + VID_MISC_DPD =202, + VID_MISC_HEARTBEAT_NOTIFY =203, + VID_MISC_FRAGMENTATION =204, + VID_INITIAL_CONTACT =205, + VID_CISCO3K_FRAGMENTATION =206 +}; + +void init_vendorid(void); + +struct msg_digest; +void handle_vendorid (struct msg_digest *md, const char *vid, size_t len); + +bool out_vendorid (u_int8_t np, pb_stream *outs, enum known_vendorid vid); + +#endif /* _VENDOR_H_ */ + diff --git a/src/pluto/virtual.c b/src/pluto/virtual.c new file mode 100644 index 000000000..d1553364e --- /dev/null +++ b/src/pluto/virtual.c @@ -0,0 +1,334 @@ +/* FreeS/WAN Virtual IP Management + * Copyright (C) 2002 Mathieu Lafon - Arkoon Network Security + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: virtual.c,v 1.4 2004/04/02 10:38:52 as Exp $ + */ + +#include <freeswan.h> + +#include <stdlib.h> +#include <string.h> +#include <sys/queue.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "connections.h" +#include "whack.h" +#include "virtual.h" + +#define F_VIRTUAL_NO 1 +#define F_VIRTUAL_DHCP 2 +#define F_VIRTUAL_IKE_CONFIG 4 +#define F_VIRTUAL_PRIVATE 8 +#define F_VIRTUAL_ALL 16 +#define F_VIRTUAL_HOST 32 + +struct virtual_t { + unsigned short flags; + unsigned short n_net; + ip_subnet net[0]; +}; + +static ip_subnet *private_net_ok=NULL, *private_net_ko=NULL; +static unsigned short private_net_ok_len=0, private_net_ko_len=0; + +/** + * read %v4:x.x.x.x/y or %v6:xxxxxxxxx/yy + * or %v4:!x.x.x.x/y if dstko not NULL + */ +static bool +_read_subnet(const char *src, size_t len, ip_subnet *dst, ip_subnet *dstko, + bool *isok) +{ + bool ok; + int af; + + if ((len > 4) && (strncmp(src, "%v4:", 4)==0)) + { + af = AF_INET; + } + else if ((len > 4) && (strncmp(src, "%v6:", 4)==0)) + { + af = AF_INET6; + } + else + { + return FALSE; + } + + ok = (src[4] != '!'); + src += ok ? 4 : 5; + len -= ok ? 4 : 5; + + if (!len) + return FALSE; + if (!ok && !dstko) + return FALSE; + + passert ( ((ok)?(dst):(dstko))!=NULL ); + + if (ttosubnet(src, len, af, ((ok)?(dst):(dstko)))) + { + return FALSE; + } + if (isok) + *isok = ok; + return TRUE; +} + +void +init_virtual_ip(const char *private_list) +{ + const char *next, *str=private_list; + unsigned short ign = 0, i_ok, i_ko; + ip_subnet sub; + bool ok; + + /** Count **/ + private_net_ok_len=0; + private_net_ko_len=0; + + while (str) + { + next = strchr(str,','); + if (!next) + next = str + strlen(str); + if (_read_subnet(str, next-str, &sub, &sub, &ok)) + if (ok) + private_net_ok_len++; + else + private_net_ko_len++; + else + ign++; + str = *next ? next+1 : NULL; + } + + if (!ign) + { + /** Allocate **/ + if (private_net_ok_len) + { + private_net_ok = (ip_subnet *)alloc_bytes( + (private_net_ok_len*sizeof(ip_subnet)), + "private_net_ok subnets"); + } + if (private_net_ko_len) + { + private_net_ko = (ip_subnet *)alloc_bytes( + (private_net_ko_len*sizeof(ip_subnet)), + "private_net_ko subnets"); + } + if ((private_net_ok_len && !private_net_ok) + || (private_net_ko_len && !private_net_ko)) + { + loglog(RC_LOG_SERIOUS, + "can't alloc in init_virtual_ip"); + pfreeany(private_net_ok); + private_net_ok = NULL; + pfreeany(private_net_ko); + private_net_ko = NULL; + } + else + { + /** Fill **/ + str = private_list; + i_ok = 0; + i_ko = 0; + + while (str) + { + next = strchr(str,','); + if (!next) + next = str + strlen(str); + if (_read_subnet(str, next-str, + &(private_net_ok[i_ok]), &(private_net_ko[i_ko]), &ok)) + { + if (ok) + i_ok++; + else + i_ko++; + } + str = *next ? next+1 : NULL; + } + } + } + else + loglog(RC_LOG_SERIOUS, + "%d bad entries in virtual_private - none loaded", ign); +} + +/** + * virtual string must be : + * {vhost,vnet}:[%method]* + * + * vhost = accept only a host (/32) + * vnet = accept any network + * + * %no = no virtual IP (accept public IP) + * %dhcp = accept DHCP SA (0.0.0.0/0) of affected IP [not implemented] + * %ike = accept affected IKE Config Mode IP [not implemented] + * %priv = accept system-wide private net list + * %v4:x = accept ipv4 in list 'x' + * %v6:x = accept ipv6 in list 'x' + * %all = accept all ips [only for testing] + * + * ex: vhost:%no,%dhcp,%priv,%v4:192.168.1.0/24 + */ +struct virtual_t +*create_virtual(const struct connection *c, const char *string) +{ + unsigned short flags=0, n_net=0, i; + const char *str = string, *next, *first_net=NULL; + ip_subnet sub; + struct virtual_t *v; + + if (!string || string[0] == '\0') + return NULL; + + if (strlen(string) >= 6 && strncmp(string,"vhost:",6) == 0) + { + flags |= F_VIRTUAL_HOST; + str += 6; + } + else if (strlen(string) >= 5 && strncmp(string,"vnet:",5) == 0) + str += 5; + else + goto fail; + + /** + * Parse string : fill flags & count subnets + */ + while ((str) && (*str)) + { + next = strchr(str,','); + if (!next) next = str + strlen(str); + if (next-str == 3 && strncmp(str, "%no", 3) == 0) + flags |= F_VIRTUAL_NO; +#if 0 + else if (next-str == 4 && strncmp(str, "%ike", 4) == 0) + flags |= F_VIRTUAL_IKE_CONFIG; + else if (next-str == 5 && strncmp(str, "%dhcp", 5) == 0) + flags |= F_VIRTUAL_DHCP; +#endif + else if (next-str == 5 && strncmp(str, "%priv", 5) == 0) + flags |= F_VIRTUAL_PRIVATE; + else if (next-str == 4 && strncmp(str, "%all", 4) == 0) + flags |= F_VIRTUAL_ALL; + else if (_read_subnet(str, next-str, &sub, NULL, NULL)) + { + n_net++; + if (!first_net) + first_net = str; + } + else + goto fail; + + str = *next ? next+1 : NULL; + } + + v = (struct virtual_t *)alloc_bytes( + sizeof(struct virtual_t) + (n_net*sizeof(ip_subnet)), + "virtual description"); + if (!v) goto fail; + + v->flags = flags; + v->n_net = n_net; + if (n_net && first_net) + { + /** + * Save subnets in newly allocated struct + */ + for (str = first_net, i = 0; str && *str; ) + { + next = strchr(str,','); + if (!next) next = str + strlen(str); + if (_read_subnet(str, next-str, &(v->net[i]), NULL, NULL)) + i++; + str = *next ? next+1 : NULL; + } + } + + return v; + +fail: + plog("invalid virtual string [%s] - " + "virtual selection disabled for connection '%s'", string, c->name); + return NULL; +} + +bool +is_virtual_end(const struct end *that) +{ + return ((that->virt)?TRUE:FALSE); +} + +bool +is_virtual_connection(const struct connection *c) +{ + return ((c->spd.that.virt)?TRUE:FALSE); +} + +static bool +net_in_list(const ip_subnet *peer_net, const ip_subnet *list, + unsigned short len) +{ + unsigned short i; + + if (!list || !len) + return FALSE; + + for (i = 0; i < len; i++) + { + if (subnetinsubnet(peer_net, &(list[i]))) + return TRUE; + } + return FALSE; +} + +bool +is_virtual_net_allowed(const struct connection *c, const ip_subnet *peer_net, + const ip_address *his_addr) +{ + if (c->spd.that.virt == NULL) + return FALSE; + + if ((c->spd.that.virt->flags & F_VIRTUAL_HOST) + && !subnetishost(peer_net)) + return FALSE; + + if ((c->spd.that.virt->flags & F_VIRTUAL_NO) + && subnetishost(peer_net) && addrinsubnet(his_addr, peer_net)) + return TRUE; + + if ((c->spd.that.virt->flags & F_VIRTUAL_PRIVATE) + && net_in_list(peer_net, private_net_ok, private_net_ok_len) + && !net_in_list(peer_net, private_net_ko, private_net_ko_len)) + return TRUE; + + if (c->spd.that.virt->n_net + && net_in_list(peer_net, c->spd.that.virt->net, c->spd.that.virt->n_net)) + return TRUE; + + if (c->spd.that.virt->flags & F_VIRTUAL_ALL) + { + /** %all must only be used for testing - log it **/ + loglog(RC_LOG_SERIOUS, "Warning - " + "v%s:%%all must only be used for testing", + (c->spd.that.virt->flags & F_VIRTUAL_HOST) ? "host" : "net"); + return TRUE; + } + + return FALSE; +} + diff --git a/src/pluto/virtual.h b/src/pluto/virtual.h new file mode 100644 index 000000000..2d5bf27ae --- /dev/null +++ b/src/pluto/virtual.h @@ -0,0 +1,31 @@ +/* FreeS/WAN Virtual IP Management + * Copyright (C) 2002 Mathieu Lafon - Arkoon Network Security + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: virtual.h,v 1.2 2004/03/22 21:53:20 as Exp $ + */ + +#ifndef _VIRTUAL_IP_H +#define _VIRTUAL_IP_H + +extern void init_virtual_ip(const char *private_list); + +extern struct virtual_t *create_virtual(const struct connection *c, + const char *string); + +extern bool is_virtual_end(const struct end *that); +extern bool is_virtual_connection(const struct connection *c); +extern bool is_virtual_net_allowed(const struct connection *c, + const ip_subnet *peer_net, const ip_address *his_addr); + +#endif /* _VIRTUAL_IP_H */ + diff --git a/src/pluto/x509.c b/src/pluto/x509.c new file mode 100644 index 000000000..2521244f7 --- /dev/null +++ b/src/pluto/x509.c @@ -0,0 +1,2241 @@ +/* Support of X.509 certificates + * Copyright (C) 2000 Andreas Hess, Patric Lichtsteiner, Roger Wegmann + * Copyright (C) 2001 Marco Bertossa, Andreas Schleiss + * Copyright (C) 2002 Mario Strasser + * Copyright (C) 2000-2004 Andreas Steffen, Zuercher Hochschule Winterthur + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: x509.c,v 1.36 2006/04/10 16:08:33 as Exp $ + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <dirent.h> +#include <time.h> +#include <sys/types.h> + +#include <freeswan.h> +#include <ipsec_policy.h> + +#include "constants.h" +#include "defs.h" +#include "mp_defs.h" +#include "log.h" +#include "id.h" +#include "asn1.h" +#include "oid.h" +#include "pkcs1.h" +#include "x509.h" +#include "crl.h" +#include "ca.h" +#include "certs.h" +#include "keys.h" +#include "whack.h" +#include "fetch.h" +#include "ocsp.h" +#include "sha1.h" + +/* chained lists of X.509 end certificates */ + +static x509cert_t *x509certs = NULL; + +/* ASN.1 definition of a basicConstraints extension */ + +static const asn1Object_t basicConstraintsObjects[] = { + { 0, "basicConstraints", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */ + { 1, "CA", ASN1_BOOLEAN, ASN1_DEF | + ASN1_BODY }, /* 1 */ + { 1, "pathLenConstraint", ASN1_INTEGER, ASN1_OPT | + ASN1_BODY }, /* 2 */ + { 1, "end opt", ASN1_EOC, ASN1_END } /* 3 */ +}; + +#define BASIC_CONSTRAINTS_CA 1 +#define BASIC_CONSTRAINTS_ROOF 4 + +/* ASN.1 definition of time */ + +static const asn1Object_t timeObjects[] = { + { 0, "utcTime", ASN1_UTCTIME, ASN1_OPT | + ASN1_BODY }, /* 0 */ + { 0, "end opt", ASN1_EOC, ASN1_END }, /* 1 */ + { 0, "generalizeTime", ASN1_GENERALIZEDTIME, ASN1_OPT | + ASN1_BODY }, /* 2 */ + { 0, "end opt", ASN1_EOC, ASN1_END } /* 3 */ +}; + +#define TIME_UTC 0 +#define TIME_GENERALIZED 2 +#define TIME_ROOF 4 + +/* ASN.1 definition of a keyIdentifier */ + +static const asn1Object_t keyIdentifierObjects[] = { + { 0, "keyIdentifier", ASN1_OCTET_STRING, ASN1_BODY } /* 0 */ +}; + +/* ASN.1 definition of a authorityKeyIdentifier extension */ + +static const asn1Object_t authorityKeyIdentifierObjects[] = { + { 0, "authorityKeyIdentifier", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */ + { 1, "keyIdentifier", ASN1_CONTEXT_S_0, ASN1_OPT | + ASN1_OBJ }, /* 1 */ + { 1, "end opt", ASN1_EOC, ASN1_END }, /* 2 */ + { 1, "authorityCertIssuer", ASN1_CONTEXT_C_1, ASN1_OPT | + ASN1_OBJ }, /* 3 */ + { 1, "end opt", ASN1_EOC, ASN1_END }, /* 4 */ + { 1, "authorityCertSerialNumber", ASN1_CONTEXT_S_2, ASN1_OPT | + ASN1_BODY }, /* 5 */ + { 1, "end opt", ASN1_EOC, ASN1_END } /* 6 */ +}; + +#define AUTH_KEY_ID_KEY_ID 1 +#define AUTH_KEY_ID_CERT_ISSUER 3 +#define AUTH_KEY_ID_CERT_SERIAL 5 +#define AUTH_KEY_ID_ROOF 7 + +/* ASN.1 definition of a authorityInfoAccess extension */ + +static const asn1Object_t authorityInfoAccessObjects[] = { + { 0, "authorityInfoAccess", ASN1_SEQUENCE, ASN1_LOOP }, /* 0 */ + { 1, "accessDescription", ASN1_SEQUENCE, ASN1_NONE }, /* 1 */ + { 2, "accessMethod", ASN1_OID, ASN1_BODY }, /* 2 */ + { 2, "accessLocation", ASN1_EOC, ASN1_RAW }, /* 3 */ + { 0, "end loop", ASN1_EOC, ASN1_END } /* 4 */ +}; + +#define AUTH_INFO_ACCESS_METHOD 2 +#define AUTH_INFO_ACCESS_LOCATION 3 +#define AUTH_INFO_ACCESS_ROOF 5 + +/* ASN.1 definition of a extendedKeyUsage extension */ + +static const asn1Object_t extendedKeyUsageObjects[] = { + { 0, "extendedKeyUsage", ASN1_SEQUENCE, ASN1_LOOP }, /* 0 */ + { 1, "keyPurposeID", ASN1_OID, ASN1_BODY }, /* 1 */ + { 0, "end loop", ASN1_EOC, ASN1_END }, /* 2 */ +}; + +#define EXT_KEY_USAGE_PURPOSE_ID 1 +#define EXT_KEY_USAGE_ROOF 3 + +/* ASN.1 definition of generalNames */ + +static const asn1Object_t generalNamesObjects[] = { + { 0, "generalNames", ASN1_SEQUENCE, ASN1_LOOP }, /* 0 */ + { 1, "generalName", ASN1_EOC, ASN1_RAW }, /* 1 */ + { 0, "end loop", ASN1_EOC, ASN1_END } /* 2 */ +}; + +#define GENERAL_NAMES_GN 1 +#define GENERAL_NAMES_ROOF 3 + +/* ASN.1 definition of generalName */ + +static const asn1Object_t generalNameObjects[] = { + { 0, "otherName", ASN1_CONTEXT_C_0, ASN1_OPT | + ASN1_BODY }, /* 0 */ + { 0, "end choice", ASN1_EOC, ASN1_END }, /* 1 */ + { 0, "rfc822Name", ASN1_CONTEXT_S_1, ASN1_OPT | + ASN1_BODY }, /* 2 */ + { 0, "end choice", ASN1_EOC, ASN1_END }, /* 3 */ + { 0, "dnsName", ASN1_CONTEXT_S_2, ASN1_OPT | + ASN1_BODY }, /* 4 */ + { 0, "end choice", ASN1_EOC, ASN1_END }, /* 5 */ + { 0, "x400Address", ASN1_CONTEXT_S_3, ASN1_OPT | + ASN1_BODY }, /* 6 */ + { 0, "end choice", ASN1_EOC, ASN1_END }, /* 7 */ + { 0, "directoryName", ASN1_CONTEXT_C_4, ASN1_OPT | + ASN1_BODY }, /* 8 */ + { 0, "end choice", ASN1_EOC, ASN1_END }, /* 9 */ + { 0, "ediPartyName", ASN1_CONTEXT_C_5, ASN1_OPT | + ASN1_BODY }, /* 10 */ + { 0, "end choice", ASN1_EOC, ASN1_END }, /* 11 */ + { 0, "uniformResourceIdentifier", ASN1_CONTEXT_S_6, ASN1_OPT | + ASN1_BODY }, /* 12 */ + { 0, "end choice", ASN1_EOC, ASN1_END }, /* 13 */ + { 0, "ipAddress", ASN1_CONTEXT_S_7, ASN1_OPT | + ASN1_BODY }, /* 14 */ + { 0, "end choice", ASN1_EOC, ASN1_END }, /* 15 */ + { 0, "registeredID", ASN1_CONTEXT_S_8, ASN1_OPT | + ASN1_BODY }, /* 16 */ + { 0, "end choice", ASN1_EOC, ASN1_END } /* 17 */ +}; + +#define GN_OBJ_OTHER_NAME 0 +#define GN_OBJ_RFC822_NAME 2 +#define GN_OBJ_DNS_NAME 4 +#define GN_OBJ_X400_ADDRESS 6 +#define GN_OBJ_DIRECTORY_NAME 8 +#define GN_OBJ_EDI_PARTY_NAME 10 +#define GN_OBJ_URI 12 +#define GN_OBJ_IP_ADDRESS 14 +#define GN_OBJ_REGISTERED_ID 16 +#define GN_OBJ_ROOF 18 + +/* ASN.1 definition of otherName */ + +static const asn1Object_t otherNameObjects[] = { + {0, "type-id", ASN1_OID, ASN1_BODY }, /* 0 */ + {0, "value", ASN1_CONTEXT_C_0, ASN1_BODY } /* 1 */ +}; + +#define ON_OBJ_ID_TYPE 0 +#define ON_OBJ_VALUE 1 +#define ON_OBJ_ROOF 2 + +/* ASN.1 definition of crlDistributionPoints */ + +static const asn1Object_t crlDistributionPointsObjects[] = { + { 0, "crlDistributionPoints", ASN1_SEQUENCE, ASN1_LOOP }, /* 0 */ + { 1, "DistributionPoint", ASN1_SEQUENCE, ASN1_NONE }, /* 1 */ + { 2, "distributionPoint", ASN1_CONTEXT_C_0, ASN1_OPT | + ASN1_LOOP }, /* 2 */ + { 3, "fullName", ASN1_CONTEXT_C_0, ASN1_OPT | + ASN1_OBJ }, /* 3 */ + { 3, "end choice", ASN1_EOC, ASN1_END }, /* 4 */ + { 3, "nameRelativeToCRLIssuer", ASN1_CONTEXT_C_1, ASN1_OPT | + ASN1_BODY }, /* 5 */ + { 3, "end choice", ASN1_EOC, ASN1_END }, /* 6 */ + { 2, "end opt", ASN1_EOC, ASN1_END }, /* 7 */ + { 2, "reasons", ASN1_CONTEXT_C_1, ASN1_OPT | + ASN1_BODY }, /* 8 */ + { 2, "end opt", ASN1_EOC, ASN1_END }, /* 9 */ + { 2, "crlIssuer", ASN1_CONTEXT_C_2, ASN1_OPT | + ASN1_BODY }, /* 10 */ + { 2, "end opt", ASN1_EOC, ASN1_END }, /* 11 */ + { 0, "end loop", ASN1_EOC, ASN1_END }, /* 12 */ +}; + +#define CRL_DIST_POINTS_FULLNAME 3 +#define CRL_DIST_POINTS_ROOF 13 + +/* ASN.1 definition of an X.509v3 certificate */ + +static const asn1Object_t certObjects[] = { + { 0, "certificate", ASN1_SEQUENCE, ASN1_OBJ }, /* 0 */ + { 1, "tbsCertificate", ASN1_SEQUENCE, ASN1_OBJ }, /* 1 */ + { 2, "DEFAULT v1", ASN1_CONTEXT_C_0, ASN1_DEF }, /* 2 */ + { 3, "version", ASN1_INTEGER, ASN1_BODY }, /* 3 */ + { 2, "serialNumber", ASN1_INTEGER, ASN1_BODY }, /* 4 */ + { 2, "signature", ASN1_EOC, ASN1_RAW }, /* 5 */ + { 2, "issuer", ASN1_SEQUENCE, ASN1_OBJ }, /* 6 */ + { 2, "validity", ASN1_SEQUENCE, ASN1_NONE }, /* 7 */ + { 3, "notBefore", ASN1_EOC, ASN1_RAW }, /* 8 */ + { 3, "notAfter", ASN1_EOC, ASN1_RAW }, /* 9 */ + { 2, "subject", ASN1_SEQUENCE, ASN1_OBJ }, /* 10 */ + { 2, "subjectPublicKeyInfo", ASN1_SEQUENCE, ASN1_NONE }, /* 11 */ + { 3, "algorithm", ASN1_EOC, ASN1_RAW }, /* 12 */ + { 3, "subjectPublicKey", ASN1_BIT_STRING, ASN1_NONE }, /* 13 */ + { 4, "RSAPublicKey", ASN1_SEQUENCE, ASN1_OBJ }, /* 14 */ + { 5, "modulus", ASN1_INTEGER, ASN1_BODY }, /* 15 */ + { 5, "publicExponent", ASN1_INTEGER, ASN1_BODY }, /* 16 */ + { 2, "issuerUniqueID", ASN1_CONTEXT_C_1, ASN1_OPT }, /* 17 */ + { 2, "end opt", ASN1_EOC, ASN1_END }, /* 18 */ + { 2, "subjectUniqueID", ASN1_CONTEXT_C_2, ASN1_OPT }, /* 19 */ + { 2, "end opt", ASN1_EOC, ASN1_END }, /* 20 */ + { 2, "optional extensions", ASN1_CONTEXT_C_3, ASN1_OPT }, /* 21 */ + { 3, "extensions", ASN1_SEQUENCE, ASN1_LOOP }, /* 22 */ + { 4, "extension", ASN1_SEQUENCE, ASN1_NONE }, /* 23 */ + { 5, "extnID", ASN1_OID, ASN1_BODY }, /* 24 */ + { 5, "critical", ASN1_BOOLEAN, ASN1_DEF | + ASN1_BODY }, /* 25 */ + { 5, "extnValue", ASN1_OCTET_STRING, ASN1_BODY }, /* 26 */ + { 3, "end loop", ASN1_EOC, ASN1_END }, /* 27 */ + { 2, "end opt", ASN1_EOC, ASN1_END }, /* 28 */ + { 1, "signatureAlgorithm", ASN1_EOC, ASN1_RAW }, /* 29 */ + { 1, "signatureValue", ASN1_BIT_STRING, ASN1_BODY } /* 30 */ +}; + +#define X509_OBJ_CERTIFICATE 0 +#define X509_OBJ_TBS_CERTIFICATE 1 +#define X509_OBJ_VERSION 3 +#define X509_OBJ_SERIAL_NUMBER 4 +#define X509_OBJ_SIG_ALG 5 +#define X509_OBJ_ISSUER 6 +#define X509_OBJ_NOT_BEFORE 8 +#define X509_OBJ_NOT_AFTER 9 +#define X509_OBJ_SUBJECT 10 +#define X509_OBJ_SUBJECT_PUBLIC_KEY_ALGORITHM 12 +#define X509_OBJ_SUBJECT_PUBLIC_KEY 13 +#define X509_OBJ_RSA_PUBLIC_KEY 14 +#define X509_OBJ_MODULUS 15 +#define X509_OBJ_PUBLIC_EXPONENT 16 +#define X509_OBJ_EXTN_ID 24 +#define X509_OBJ_CRITICAL 25 +#define X509_OBJ_EXTN_VALUE 26 +#define X509_OBJ_ALGORITHM 29 +#define X509_OBJ_SIGNATURE 30 +#define X509_OBJ_ROOF 31 + + +const x509cert_t empty_x509cert = { + NULL , /* *next */ + UNDEFINED_TIME, /* installed */ + 0 , /* count */ + FALSE , /* smartcard */ + AUTH_NONE , /* authority_flags */ + { NULL, 0 } , /* certificate */ + { NULL, 0 } , /* tbsCertificate */ + 1 , /* version */ + { NULL, 0 } , /* serialNumber */ + OID_UNKNOWN , /* sigAlg */ + { NULL, 0 } , /* issuer */ + /* validity */ + 0 , /* notBefore */ + 0 , /* notAfter */ + { NULL, 0 } , /* subject */ + /* subjectPublicKeyInfo */ + OID_UNKNOWN , /* subjectPublicKeyAlgorithm */ + { NULL, 0 } , /* subjectPublicKey */ + { NULL, 0 } , /* modulus */ + { NULL, 0 } , /* publicExponent */ + /* issuerUniqueID */ + /* subjectUniqueID */ + /* extensions */ + /* extension */ + /* extnID */ + /* critical */ + /* extnValue */ + FALSE , /* isCA */ + FALSE , /* isOcspSigner */ + { NULL, 0 } , /* subjectKeyID */ + { NULL, 0 } , /* authKeyID */ + { NULL, 0 } , /* authKeySerialNumber */ + { NULL, 0 } , /* accessLocation */ + NULL , /* subjectAltName */ + NULL , /* crlDistributionPoints */ + OID_UNKNOWN , /* algorithm */ + { NULL, 0 } /* signature */ +}; + +/* coding of X.501 distinguished name */ + +typedef struct { + const u_char *name; + chunk_t oid; + u_char type; +} x501rdn_t; + +/* X.501 acronyms for well known object identifiers (OIDs) */ + +static u_char oid_ND[] = {0x02, 0x82, 0x06, 0x01, + 0x0A, 0x07, 0x14}; +static u_char oid_UID[] = {0x09, 0x92, 0x26, 0x89, 0x93, + 0xF2, 0x2C, 0x64, 0x01, 0x01}; +static u_char oid_DC[] = {0x09, 0x92, 0x26, 0x89, 0x93, + 0xF2, 0x2C, 0x64, 0x01, 0x19}; +static u_char oid_CN[] = {0x55, 0x04, 0x03}; +static u_char oid_S[] = {0x55, 0x04, 0x04}; +static u_char oid_SN[] = {0x55, 0x04, 0x05}; +static u_char oid_C[] = {0x55, 0x04, 0x06}; +static u_char oid_L[] = {0x55, 0x04, 0x07}; +static u_char oid_ST[] = {0x55, 0x04, 0x08}; +static u_char oid_O[] = {0x55, 0x04, 0x0A}; +static u_char oid_OU[] = {0x55, 0x04, 0x0B}; +static u_char oid_T[] = {0x55, 0x04, 0x0C}; +static u_char oid_D[] = {0x55, 0x04, 0x0D}; +static u_char oid_N[] = {0x55, 0x04, 0x29}; +static u_char oid_G[] = {0x55, 0x04, 0x2A}; +static u_char oid_I[] = {0x55, 0x04, 0x2B}; +static u_char oid_ID[] = {0x55, 0x04, 0x2D}; +static u_char oid_EN[] = {0x60, 0x86, 0x48, 0x01, 0x86, + 0xF8, 0x42, 0x03, 0x01, 0x03}; +static u_char oid_E[] = {0x2A, 0x86, 0x48, 0x86, 0xF7, + 0x0D, 0x01, 0x09, 0x01}; +static u_char oid_UN[] = {0x2A, 0x86, 0x48, 0x86, 0xF7, + 0x0D, 0x01, 0x09, 0x02}; +static u_char oid_TCGID[] = {0x2B, 0x06, 0x01, 0x04, 0x01, 0x89, + 0x31, 0x01, 0x01, 0x02, 0x02, 0x4B}; + +static const x501rdn_t x501rdns[] = { + {"ND" , {oid_ND, 7}, ASN1_PRINTABLESTRING}, + {"UID" , {oid_UID, 10}, ASN1_PRINTABLESTRING}, + {"DC" , {oid_DC, 10}, ASN1_PRINTABLESTRING}, + {"CN" , {oid_CN, 3}, ASN1_PRINTABLESTRING}, + {"S" , {oid_S, 3}, ASN1_PRINTABLESTRING}, + {"SN" , {oid_SN, 3}, ASN1_PRINTABLESTRING}, + {"serialNumber" , {oid_SN, 3}, ASN1_PRINTABLESTRING}, + {"C" , {oid_C, 3}, ASN1_PRINTABLESTRING}, + {"L" , {oid_L, 3}, ASN1_PRINTABLESTRING}, + {"ST" , {oid_ST, 3}, ASN1_PRINTABLESTRING}, + {"O" , {oid_O, 3}, ASN1_PRINTABLESTRING}, + {"OU" , {oid_OU, 3}, ASN1_PRINTABLESTRING}, + {"T" , {oid_T, 3}, ASN1_PRINTABLESTRING}, + {"D" , {oid_D, 3}, ASN1_PRINTABLESTRING}, + {"N" , {oid_N, 3}, ASN1_PRINTABLESTRING}, + {"G" , {oid_G, 3}, ASN1_PRINTABLESTRING}, + {"I" , {oid_I, 3}, ASN1_PRINTABLESTRING}, + {"ID" , {oid_ID, 3}, ASN1_PRINTABLESTRING}, + {"EN" , {oid_EN, 10}, ASN1_PRINTABLESTRING}, + {"employeeNumber" , {oid_EN, 10}, ASN1_PRINTABLESTRING}, + {"E" , {oid_E, 9}, ASN1_IA5STRING}, + {"Email" , {oid_E, 9}, ASN1_IA5STRING}, + {"emailAddress" , {oid_E, 9}, ASN1_IA5STRING}, + {"UN" , {oid_UN, 9}, ASN1_IA5STRING}, + {"unstructuredName", {oid_UN, 9}, ASN1_IA5STRING}, + {"TCGID" , {oid_TCGID, 12}, ASN1_PRINTABLESTRING} +}; + +#define X501_RDN_ROOF 26 + +static u_char ASN1_subjectAltName_oid_str[] = { + 0x06, 0x03, 0x55, 0x1D, 0x11 +}; + +static const chunk_t ASN1_subjectAltName_oid = strchunk(ASN1_subjectAltName_oid_str); + +static void +update_chunk(chunk_t *ch, int n) +{ + n = (n > -1 && n < (int)ch->len)? n : (int)ch->len-1; + ch->ptr += n; ch->len -= n; +} + + +/* + * Pointer is set to the first RDN in a DN + */ +static err_t +init_rdn(chunk_t dn, chunk_t *rdn, chunk_t *attribute, bool *next) +{ + *rdn = empty_chunk; + *attribute = empty_chunk; + + /* a DN is a SEQUENCE OF RDNs */ + + if (*dn.ptr != ASN1_SEQUENCE) + { + return "DN is not a SEQUENCE"; + } + + rdn->len = asn1_length(&dn); + + if (rdn->len == ASN1_INVALID_LENGTH) + return "Invalid RDN length"; + + rdn->ptr = dn.ptr; + + /* are there any RDNs ? */ + *next = rdn->len > 0; + + return NULL; +} + +/* + * Fetches the next RDN in a DN + */ +static err_t +get_next_rdn(chunk_t *rdn, chunk_t * attribute, chunk_t *oid, chunk_t *value +, asn1_t *type, bool *next) +{ + chunk_t body; + + /* initialize return values */ + *oid = empty_chunk; + *value = empty_chunk; + + /* if all attributes have been parsed, get next rdn */ + if (attribute->len <= 0) + { + /* an RDN is a SET OF attributeTypeAndValue */ + if (*rdn->ptr != ASN1_SET) + return "RDN is not a SET"; + + attribute->len = asn1_length(rdn); + + if (attribute->len == ASN1_INVALID_LENGTH) + return "Invalid attribute length"; + + attribute->ptr = rdn->ptr; + + /* advance to start of next RDN */ + rdn->ptr += attribute->len; + rdn->len -= attribute->len; + } + + /* an attributeTypeAndValue is a SEQUENCE */ + if (*attribute->ptr != ASN1_SEQUENCE) + return "attributeTypeAndValue is not a SEQUENCE"; + + /* extract the attribute body */ + body.len = asn1_length(attribute); + + if (body.len == ASN1_INVALID_LENGTH) + return "Invalid attribute body length"; + + body.ptr = attribute->ptr; + + /* advance to start of next attribute */ + attribute->ptr += body.len; + attribute->len -= body.len; + + /* attribute type is an OID */ + if (*body.ptr != ASN1_OID) + return "attributeType is not an OID"; + + /* extract OID */ + oid->len = asn1_length(&body); + + if (oid->len == ASN1_INVALID_LENGTH) + return "Invalid attribute OID length"; + + oid->ptr = body.ptr; + + /* advance to the attribute value */ + body.ptr += oid->len; + body.len -= oid->len; + + /* extract string type */ + *type = *body.ptr; + + /* extract string value */ + value->len = asn1_length(&body); + + if (value->len == ASN1_INVALID_LENGTH) + return "Invalid attribute string length"; + + value->ptr = body.ptr; + + /* are there any RDNs left? */ + *next = rdn->len > 0 || attribute->len > 0; + + return NULL; +} + +/* + * Parses an ASN.1 distinguished name int its OID/value pairs + */ +static err_t +dn_parse(chunk_t dn, chunk_t *str) +{ + chunk_t rdn, oid, attribute, value; + asn1_t type; + int oid_code; + bool next; + bool first = TRUE; + + err_t ugh = init_rdn(dn, &rdn, &attribute, &next); + + if (ugh != NULL) /* a parsing error has occured */ + return ugh; + + while (next) + { + ugh = get_next_rdn(&rdn, &attribute, &oid, &value, &type, &next); + + if (ugh != NULL) /* a parsing error has occured */ + return ugh; + + if (first) /* first OID/value pair */ + first = FALSE; + else /* separate OID/value pair by a comma */ + update_chunk(str, snprintf(str->ptr,str->len,", ")); + + /* print OID */ + oid_code = known_oid(oid); + if (oid_code == OID_UNKNOWN) /* OID not found in list */ + hex_str(oid, str); + else + update_chunk(str, snprintf(str->ptr,str->len,"%s", + oid_names[oid_code].name)); + + /* print value */ + update_chunk(str, snprintf(str->ptr,str->len,"=%.*s", + (int)value.len,value.ptr)); + } + return NULL; +} + +/* + * Count the number of wildcard RDNs in a distinguished name + */ +int +dn_count_wildcards(chunk_t dn) +{ + chunk_t rdn, attribute, oid, value; + asn1_t type; + bool next; + int wildcards = 0; + + err_t ugh = init_rdn(dn, &rdn, &attribute, &next); + + if (ugh != NULL) /* a parsing error has occured */ + return -1; + + while (next) + { + ugh = get_next_rdn(&rdn, &attribute, &oid, &value, &type, &next); + + if (ugh != NULL) /* a parsing error has occured */ + return -1; + if (value.len == 1 && *value.ptr == '*') + wildcards++; /* we have found a wildcard RDN */ + } + return wildcards; +} + +/* + * Prints a binary string in hexadecimal form + */ +void +hex_str(chunk_t bin, chunk_t *str) +{ + u_int i; + update_chunk(str, snprintf(str->ptr,str->len,"0x")); + for (i=0; i < bin.len; i++) + update_chunk(str, snprintf(str->ptr,str->len,"%02X",*bin.ptr++)); +} + + +/* Converts a binary DER-encoded ASN.1 distinguished name + * into LDAP-style human-readable ASCII format + */ +int +dntoa(char *dst, size_t dstlen, chunk_t dn) +{ + err_t ugh = NULL; + chunk_t str; + + str.ptr = dst; + str.len = dstlen; + ugh = dn_parse(dn, &str); + + if (ugh != NULL) /* error, print DN as hex string */ + { + DBG(DBG_PARSING, + DBG_log("error in DN parsing: %s", ugh) + ) + str.ptr = dst; + str.len = dstlen; + hex_str(dn, &str); + } + return (int)(dstlen - str.len); +} + +/* + * Same as dntoa but prints a special string for a null dn + */ +int +dntoa_or_null(char *dst, size_t dstlen, chunk_t dn, const char* null_dn) +{ + if (dn.ptr == NULL) + return snprintf(dst, dstlen, "%s", null_dn); + else + return dntoa(dst, dstlen, dn); +} + +/* Converts an LDAP-style human-readable ASCII-encoded + * ASN.1 distinguished name into binary DER-encoded format + */ +err_t +atodn(char *src, chunk_t *dn) +{ + /* finite state machine for atodn */ + + typedef enum { + SEARCH_OID = 0, + READ_OID = 1, + SEARCH_NAME = 2, + READ_NAME = 3, + UNKNOWN_OID = 4 + } state_t; + + u_char oid_len_buf[3]; + u_char name_len_buf[3]; + u_char rdn_seq_len_buf[3]; + u_char rdn_set_len_buf[3]; + u_char dn_seq_len_buf[3]; + + chunk_t asn1_oid_len = { oid_len_buf, 0 }; + chunk_t asn1_name_len = { name_len_buf, 0 }; + chunk_t asn1_rdn_seq_len = { rdn_seq_len_buf, 0 }; + chunk_t asn1_rdn_set_len = { rdn_set_len_buf, 0 }; + chunk_t asn1_dn_seq_len = { dn_seq_len_buf, 0 }; + chunk_t oid = empty_chunk; + chunk_t name = empty_chunk; + + int whitespace = 0; + int rdn_seq_len = 0; + int rdn_set_len = 0; + int dn_seq_len = 0; + int pos = 0; + + err_t ugh = NULL; + + u_char *dn_ptr = dn->ptr + 4; + + state_t state = SEARCH_OID; + + do + { + switch (state) + { + case SEARCH_OID: + if (*src != ' ' && *src != '/' && *src != ',') + { + oid.ptr = src; + oid.len = 1; + state = READ_OID; + } + break; + case READ_OID: + if (*src != ' ' && *src != '=') + oid.len++; + else + { + for (pos = 0; pos < X501_RDN_ROOF; pos++) + { + if (strlen(x501rdns[pos].name) == oid.len && + strncasecmp(x501rdns[pos].name, oid.ptr, oid.len) == 0) + break; /* found a valid OID */ + } + if (pos == X501_RDN_ROOF) + { + ugh = "unknown OID in distinguished name"; + state = UNKNOWN_OID; + break; + } + code_asn1_length(x501rdns[pos].oid.len, &asn1_oid_len); + + /* reset oid and change state */ + oid = empty_chunk; + state = SEARCH_NAME; + } + break; + case SEARCH_NAME: + if (*src != ' ' && *src != '=') + { + name.ptr = src; + name.len = 1; + whitespace = 0; + state = READ_NAME; + } + break; + case READ_NAME: + if (*src != ',' && *src != '/' && *src != '\0') + { + name.len++; + if (*src == ' ') + whitespace++; + else + whitespace = 0; + } + else + { + name.len -= whitespace; + code_asn1_length(name.len, &asn1_name_len); + + /* compute the length of the relative distinguished name sequence */ + rdn_seq_len = 1 + asn1_oid_len.len + x501rdns[pos].oid.len + + 1 + asn1_name_len.len + name.len; + code_asn1_length(rdn_seq_len, &asn1_rdn_seq_len); + + /* compute the length of the relative distinguished name set */ + rdn_set_len = 1 + asn1_rdn_seq_len.len + rdn_seq_len; + code_asn1_length(rdn_set_len, &asn1_rdn_set_len); + + /* encode the relative distinguished name */ + *dn_ptr++ = ASN1_SET; + chunkcpy(dn_ptr, asn1_rdn_set_len); + *dn_ptr++ = ASN1_SEQUENCE; + chunkcpy(dn_ptr, asn1_rdn_seq_len); + *dn_ptr++ = ASN1_OID; + chunkcpy(dn_ptr, asn1_oid_len); + chunkcpy(dn_ptr, x501rdns[pos].oid); + /* encode the ASN.1 character string type of the name */ + *dn_ptr++ = (x501rdns[pos].type == ASN1_PRINTABLESTRING + && !is_printablestring(name))? ASN1_T61STRING : x501rdns[pos].type; + chunkcpy(dn_ptr, asn1_name_len); + chunkcpy(dn_ptr, name); + + /* accumulate the length of the distinguished name sequence */ + dn_seq_len += 1 + asn1_rdn_set_len.len + rdn_set_len; + + /* reset name and change state */ + name = empty_chunk; + state = SEARCH_OID; + } + break; + case UNKNOWN_OID: + break; + } + } while (*src++ != '\0'); + + /* complete the distinguished name sequence*/ + code_asn1_length(dn_seq_len, &asn1_dn_seq_len); + dn->ptr += 3 - asn1_dn_seq_len.len; + dn->len = 1 + asn1_dn_seq_len.len + dn_seq_len; + dn_ptr = dn->ptr; + *dn_ptr++ = ASN1_SEQUENCE; + chunkcpy(dn_ptr, asn1_dn_seq_len); + return ugh; +} + +/* compare two distinguished names by + * comparing the individual RDNs + */ +bool +same_dn(chunk_t a, chunk_t b) +{ + chunk_t rdn_a, rdn_b, attribute_a, attribute_b; + chunk_t oid_a, oid_b, value_a, value_b; + asn1_t type_a, type_b; + bool next_a, next_b; + + /* same lengths for the DNs */ + if (a.len != b.len) + return FALSE; + + /* try a binary comparison first */ + if (memcmp(a.ptr, b.ptr, b.len) == 0) + return TRUE; + + /* initialize DN parsing */ + if (init_rdn(a, &rdn_a, &attribute_a, &next_a) != NULL + || init_rdn(b, &rdn_b, &attribute_b, &next_b) != NULL) + return FALSE; + + /* fetch next RDN pair */ + while (next_a && next_b) + { + /* parse next RDNs and check for errors */ + if (get_next_rdn(&rdn_a, &attribute_a, &oid_a, &value_a, &type_a, &next_a) != NULL + || get_next_rdn(&rdn_b, &attribute_b, &oid_b, &value_b, &type_b, &next_b) != NULL) + { + return FALSE; + } + + /* OIDs must agree */ + if (oid_a.len != oid_b.len || memcmp(oid_a.ptr, oid_b.ptr, oid_b.len) != 0) + return FALSE; + + /* same lengths for values */ + if (value_a.len != value_b.len) + return FALSE; + + /* printableStrings and email RDNs require uppercase comparison */ + if (type_a == type_b && (type_a == ASN1_PRINTABLESTRING || + (type_a == ASN1_IA5STRING && known_oid(oid_a) == OID_PKCS9_EMAIL))) + { + if (strncasecmp(value_a.ptr, value_b.ptr, value_b.len) != 0) + return FALSE; + } + else + { + if (strncmp(value_a.ptr, value_b.ptr, value_b.len) != 0) + return FALSE; + } + } + /* both DNs must have same number of RDNs */ + if (next_a || next_b) + return FALSE; + + /* the two DNs are equal! */ + return TRUE; +} + + +/* compare two distinguished names by comparing the individual RDNs. + * A single'*' character designates a wildcard RDN in DN b. + */ +bool +match_dn(chunk_t a, chunk_t b, int *wildcards) +{ + chunk_t rdn_a, rdn_b, attribute_a, attribute_b; + chunk_t oid_a, oid_b, value_a, value_b; + asn1_t type_a, type_b; + bool next_a, next_b; + + /* initialize wildcard counter */ + *wildcards = 0; + + /* initialize DN parsing */ + if (init_rdn(a, &rdn_a, &attribute_a, &next_a) != NULL + || init_rdn(b, &rdn_b, &attribute_b, &next_b) != NULL) + return FALSE; + + /* fetch next RDN pair */ + while (next_a && next_b) + { + /* parse next RDNs and check for errors */ + if (get_next_rdn(&rdn_a, &attribute_a, &oid_a, &value_a, &type_a, &next_a) != NULL + || get_next_rdn(&rdn_b, &attribute_b, &oid_b, &value_b, &type_b, &next_b) != NULL) + { + return FALSE; + } + + /* OIDs must agree */ + if (oid_a.len != oid_b.len || memcmp(oid_a.ptr, oid_b.ptr, oid_b.len) != 0) + return FALSE; + + /* does rdn_b contain a wildcard? */ + if (value_b.len == 1 && *value_b.ptr == '*') + { + (*wildcards)++; + continue; + } + + /* same lengths for values */ + if (value_a.len != value_b.len) + return FALSE; + + /* printableStrings and email RDNs require uppercase comparison */ + if (type_a == type_b && (type_a == ASN1_PRINTABLESTRING || + (type_a == ASN1_IA5STRING && known_oid(oid_a) == OID_PKCS9_EMAIL))) + { + if (strncasecmp(value_a.ptr, value_b.ptr, value_b.len) != 0) + return FALSE; + } + else + { + if (strncmp(value_a.ptr, value_b.ptr, value_b.len) != 0) + return FALSE; + } + } + /* both DNs must have same number of RDNs */ + if (next_a || next_b) + return FALSE; + + /* the two DNs match! */ + return TRUE; +} + +/* + * compare two X.509 certificates by comparing their signatures + */ +bool +same_x509cert(const x509cert_t *a, const x509cert_t *b) +{ + return same_chunk(a->signature, b->signature); +} + +/* for each link pointing to the certificate + " increase the count by one + */ +void +share_x509cert(x509cert_t *cert) +{ + if (cert != NULL) + cert->count++; +} + +/* + * add a X.509 user/host certificate to the chained list + */ +x509cert_t* +add_x509cert(x509cert_t *cert) +{ + x509cert_t *c = x509certs; + + while (c != NULL) + { + if (same_x509cert(c, cert)) /* already in chain, free cert */ + { + free_x509cert(cert); + return c; + } + c = c->next; + } + + /* insert new cert at the root of the chain */ + lock_certs_and_keys("add_x509cert"); + cert->next = x509certs; + x509certs = cert; + DBG(DBG_CONTROL | DBG_PARSING, + DBG_log(" x509 cert inserted") + ) + unlock_certs_and_keys("add_x509cert"); + return cert; +} + +/* + * choose either subject DN or a subjectAltName as connection end ID + */ +void +select_x509cert_id(x509cert_t *cert, struct id *end_id) +{ + bool copy_subject_dn = TRUE; /* ID is subject DN */ + + if (end_id->kind != ID_NONE) /* check for matching subjectAltName */ + { + generalName_t *gn = cert->subjectAltName; + + while (gn != NULL) + { + struct id id = empty_id; + + gntoid(&id, gn); + if (same_id(&id, end_id)) + { + copy_subject_dn = FALSE; /* take subjectAltName instead */ + break; + } + gn = gn->next; + } + } + + if (copy_subject_dn) + { + if (end_id->kind != ID_NONE && end_id->kind != ID_DER_ASN1_DN) + { + char buf[BUF_LEN]; + + idtoa(end_id, buf, BUF_LEN); + plog(" no subjectAltName matches ID '%s', replaced by subject DN", buf); + } + end_id->kind = ID_DER_ASN1_DN; + end_id->name.len = cert->subject.len; + end_id->name.ptr = temporary_cyclic_buffer(); + memcpy(end_id->name.ptr, cert->subject.ptr, cert->subject.len); + } +} + +/* + * check for equality between two key identifiers + */ +bool +same_keyid(chunk_t a, chunk_t b) +{ + if (a.ptr == NULL || b.ptr == NULL) + return FALSE; + + return same_chunk(a, b); +} + +/* + * check for equality between two serial numbers + */ +bool +same_serial(chunk_t a, chunk_t b) +{ + /* do not compare serial numbers if one of them is not defined */ + if (a.ptr == NULL || b.ptr == NULL) + return TRUE; + + return same_chunk(a, b); +} + +/* + * get a X.509 certificate with a given issuer found at a certain position + */ +x509cert_t* +get_x509cert(chunk_t issuer, chunk_t serial, chunk_t keyid, x509cert_t *chain) +{ + x509cert_t *cert = (chain != NULL)? chain->next : x509certs; + + while (cert != NULL) + { + if ((keyid.ptr != NULL) ? same_keyid(keyid, cert->authKeyID) + : (same_dn(issuer, cert->issuer) + && same_serial(serial, cert->authKeySerialNumber))) + { + return cert; + } + cert = cert->next; + } + return NULL; +} + +/* + * encode a linked list of subjectAltNames + */ +chunk_t +build_subjectAltNames(generalName_t *subjectAltNames) +{ + u_char *pos; + chunk_t names; + size_t len = 0; + generalName_t *gn = subjectAltNames; + + /* compute the total size of the ASN.1 attributes object */ + while (gn != NULL) + { + len += gn->name.len; + gn = gn->next; + } + + pos = build_asn1_object(&names, ASN1_SEQUENCE, len); + + gn = subjectAltNames; + while (gn != NULL) + { + chunkcpy(pos, gn->name); + gn = gn->next; + } + + return asn1_wrap(ASN1_SEQUENCE, "cm" + , ASN1_subjectAltName_oid + , asn1_wrap(ASN1_OCTET_STRING, "m", names)); +} + +/* + * build a to-be-signed X.509 certificate body + */ +static chunk_t +build_tbs_x509cert(x509cert_t *cert, const RSA_public_key_t *rsa) +{ + /* version is always X.509v3 */ + chunk_t version = asn1_simple_object(ASN1_CONTEXT_C_0, ASN1_INTEGER_2); + + chunk_t extensions = empty_chunk; + + if (cert->subjectAltName != NULL) + { + extensions = asn1_wrap(ASN1_CONTEXT_C_3, "m" + , asn1_wrap(ASN1_SEQUENCE, "m" + , build_subjectAltNames(cert->subjectAltName))); + } + + return asn1_wrap(ASN1_SEQUENCE, "mmccmcmm" + , version + , asn1_simple_object(ASN1_INTEGER, cert->serialNumber) + , asn1_algorithmIdentifier(cert->sigAlg) + , cert->issuer + , asn1_wrap(ASN1_SEQUENCE, "mm" + , timetoasn1(&cert->notBefore, ASN1_UTCTIME) + , timetoasn1(&cert->notAfter, ASN1_UTCTIME) + ) + , cert->subject + , pkcs1_build_publicKeyInfo(rsa) + , extensions + ); +} + +/* + * build a DER-encoded X.509 certificate + */ +void +build_x509cert(x509cert_t *cert, const RSA_public_key_t *cert_key +, const RSA_private_key_t *signer_key) +{ + chunk_t tbs_cert = build_tbs_x509cert(cert, cert_key); + + chunk_t signature = pkcs1_build_signature(tbs_cert, cert->sigAlg + , signer_key, TRUE); + + cert->certificate = asn1_wrap(ASN1_SEQUENCE, "mcm" + , tbs_cert + , asn1_algorithmIdentifier(cert->sigAlg) + , signature); +} + +/* + * free the dynamic memory used to store generalNames + */ +void +free_generalNames(generalName_t* gn, bool free_name) +{ + while (gn != NULL) + { + generalName_t *gn_top = gn; + if (free_name) + { + pfree(gn->name.ptr); + } + gn = gn->next; + pfree(gn_top); + } +} + +/* + * free a X.509 certificate + */ +void +free_x509cert(x509cert_t *cert) +{ + if (cert != NULL) + { + free_generalNames(cert->subjectAltName, FALSE); + free_generalNames(cert->crlDistributionPoints, FALSE); + pfreeany(cert->certificate.ptr); + pfree(cert); + cert = NULL; + } +} + +/* release of a certificate decreases the count by one + " the certificate is freed when the counter reaches zero + */ +void +release_x509cert(x509cert_t *cert) +{ + if (cert != NULL && --cert->count == 0) + { + x509cert_t **pp = &x509certs; + while (*pp != cert) + pp = &(*pp)->next; + *pp = cert->next; + free_x509cert(cert); + } +} + + +/* + * stores a chained list of end certs and CA certs + */ +void +store_x509certs(x509cert_t **firstcert, bool strict) +{ + x509cert_t *cacerts = NULL; + x509cert_t **pp = firstcert; + + /* first extract CA certs, discarding root CA certs */ + + while (*pp != NULL) + { + x509cert_t *cert = *pp; + + if (cert->isCA) + { + *pp = cert->next; + + /* we don't accept self-signed CA certs */ + if (same_dn(cert->issuer, cert->subject)) + { + plog("self-signed cacert rejected"); + free_x509cert(cert); + } + else + { + /* insertion into temporary chain of candidate CA certs */ + cert->next = cacerts; + cacerts = cert; + } + } + else + pp = &cert->next; + } + + /* now verify the candidate CA certs */ + + while (cacerts != NULL) + { + x509cert_t *cert = cacerts; + + cacerts = cacerts->next; + + if (trust_authcert_candidate(cert, cacerts)) + { + add_authcert(cert, AUTH_CA); + } + else + { + plog("intermediate cacert rejected"); + free_x509cert(cert); + } + } + + /* now verify the end certificates */ + + pp = firstcert; + + while (*pp != NULL) + { + time_t valid_until; + x509cert_t *cert = *pp; + + if (verify_x509cert(cert, strict, &valid_until)) + { + DBG(DBG_CONTROL | DBG_PARSING, + DBG_log("public key validated") + ) + add_x509_public_key(cert, valid_until, DAL_SIGNED); + } + else + { + plog("X.509 certificate rejected"); + } + *pp = cert->next; + free_x509cert(cert); + } +} + +/* + * decrypts an RSA signature using the issuer's certificate + */ +static bool +decrypt_sig(chunk_t sig, int alg, const x509cert_t *issuer_cert, + chunk_t *digest) +{ + switch (alg) + { + chunk_t decrypted; + + case OID_RSA_ENCRYPTION: + case OID_MD2_WITH_RSA: + case OID_MD5_WITH_RSA: + case OID_SHA1_WITH_RSA: + case OID_SHA1_WITH_RSA_OIW: + case OID_SHA256_WITH_RSA: + case OID_SHA384_WITH_RSA: + case OID_SHA512_WITH_RSA: + { + mpz_t s; + RSA_public_key_t rsa; + + init_RSA_public_key(&rsa, issuer_cert->publicExponent + , issuer_cert->modulus); + + /* decrypt the signature s = s^e mod n */ + n_to_mpz(s, sig.ptr, sig.len); + mpz_powm(s, s, &rsa.e, &rsa.n); + + /* convert back to bytes */ + decrypted = mpz_to_n(s, rsa.k); + DBG(DBG_PARSING, + DBG_dump_chunk(" decrypted signature: ", decrypted) + ) + + /* copy the least significant bits of decrypted signature + * into the digest string + */ + memcpy(digest->ptr, decrypted.ptr + decrypted.len - digest->len, + digest->len); + + /* free memory */ + free_RSA_public_content(&rsa); + pfree(decrypted.ptr); + mpz_clear(s); + return TRUE; + } + default: + digest->len = 0; + return FALSE; + } +} + +/* + * Check if a signature over binary blob is genuine + */ +bool +check_signature(chunk_t tbs, chunk_t sig, int digest_alg, int enc_alg +, const x509cert_t *issuer_cert) +{ + u_char digest_buf[MAX_DIGEST_LEN]; + u_char decrypted_buf[MAX_DIGEST_LEN]; + chunk_t digest = {digest_buf, MAX_DIGEST_LEN}; + chunk_t decrypted = {decrypted_buf, MAX_DIGEST_LEN}; + + DBG(DBG_PARSING, + if (digest_alg != OID_UNKNOWN) + DBG_log("signature digest algorithm: '%s'",oid_names[digest_alg].name); + else + DBG_log("unknown signature digest algorithm"); + ) + + if (!compute_digest(tbs, digest_alg, &digest)) + { + plog(" digest algorithm not supported"); + return FALSE; + } + + DBG(DBG_PARSING, + DBG_dump_chunk(" digest:", digest) + ) + + decrypted.len = digest.len; /* we want the same digest length */ + + DBG(DBG_PARSING, + if (enc_alg != OID_UNKNOWN) + DBG_log("signature encryption algorithm: '%s'",oid_names[enc_alg].name); + else + DBG_log("unknown signature encryption algorithm"); + ) + + if (!decrypt_sig(sig, enc_alg, issuer_cert, &decrypted)) + { + plog(" decryption algorithm not supported"); + return FALSE; + } + + /* check if digests are equal */ + return !memcmp(decrypted.ptr, digest.ptr, digest.len); +} + +/* + * extracts the basicConstraints extension + */ +static bool +parse_basicConstraints(chunk_t blob, int level0) +{ + asn1_ctx_t ctx; + chunk_t object; + u_int level; + int objectID = 0; + bool isCA = FALSE; + + asn1_init(&ctx, blob, level0, FALSE, DBG_RAW); + + while (objectID < BASIC_CONSTRAINTS_ROOF) { + + if (!extract_object(basicConstraintsObjects, &objectID, + &object,&level, &ctx)) + break; + + if (objectID == BASIC_CONSTRAINTS_CA) + { + isCA = object.len && *object.ptr; + DBG(DBG_PARSING, + DBG_log(" %s",(isCA)?"TRUE":"FALSE"); + ) + } + objectID++; + } + return isCA; +} + +/* + * Converts a X.500 generalName into an ID + */ +void +gntoid(struct id *id, const generalName_t *gn) +{ + switch(gn->kind) + { + case GN_DNS_NAME: /* ID type: ID_FQDN */ + id->kind = ID_FQDN; + id->name = gn->name; + break; + case GN_IP_ADDRESS: /* ID type: ID_IPV4_ADDR */ + { + const struct af_info *afi = &af_inet4_info; + err_t ugh = NULL; + + id->kind = afi->id_addr; + ugh = initaddr(gn->name.ptr, gn->name.len, afi->af, &id->ip_addr); + } + break; + case GN_RFC822_NAME: /* ID type: ID_USER_FQDN */ + id->kind = ID_USER_FQDN; + id->name = gn->name; + break; + default: + id->kind = ID_NONE; + id->name = empty_chunk; + } +} + +/* compute the subjectKeyIdentifier according to section 4.2.1.2 of RFC 3280 + * as the 160 bit SHA-1 hash of the public key + */ +void +compute_subjectKeyID(x509cert_t *cert, chunk_t subjectKeyID) +{ + SHA1_CTX context; + + SHA1Init(&context); + SHA1Update(&context + , cert->subjectPublicKey.ptr + , cert->subjectPublicKey.len); + SHA1Final(subjectKeyID.ptr, &context); + subjectKeyID.len = SHA1_DIGEST_SIZE; +} + +/* + * extracts an otherName + */ +static bool +parse_otherName(chunk_t blob, int level0) +{ + asn1_ctx_t ctx; + chunk_t object; + int objectID = 0; + u_int level; + int oid = OID_UNKNOWN; + + asn1_init(&ctx, blob, level0, FALSE, DBG_RAW); + + while (objectID < ON_OBJ_ROOF) + { + if (!extract_object(otherNameObjects, &objectID, &object, &level, &ctx)) + return FALSE; + + switch (objectID) + { + case ON_OBJ_ID_TYPE: + oid = known_oid(object); + break; + case ON_OBJ_VALUE: + if (oid == OID_XMPP_ADDR) + { + if (!parse_asn1_simple_object(&object, ASN1_UTF8STRING + , level + 1, "xmppAddr")) + { + return FALSE; + } + } + break; + default: + break; + } + objectID++; + } + return TRUE; +} + + +/* + * extracts a generalName + */ +static generalName_t* +parse_generalName(chunk_t blob, int level0) +{ + u_char buf[BUF_LEN]; + asn1_ctx_t ctx; + chunk_t object; + int objectID = 0; + u_int level; + + asn1_init(&ctx, blob, level0, FALSE, DBG_RAW); + + while (objectID < GN_OBJ_ROOF) + { + bool valid_gn = FALSE; + + if (!extract_object(generalNameObjects, &objectID, &object, &level, &ctx)) + return NULL; + + switch (objectID) { + case GN_OBJ_RFC822_NAME: + case GN_OBJ_DNS_NAME: + case GN_OBJ_URI: + DBG(DBG_PARSING, + DBG_log(" '%.*s'", (int)object.len, object.ptr); + ) + valid_gn = TRUE; + break; + case GN_OBJ_DIRECTORY_NAME: + DBG(DBG_PARSING, + dntoa(buf, BUF_LEN, object); + DBG_log(" '%s'", buf) + ) + valid_gn = TRUE; + break; + case GN_OBJ_IP_ADDRESS: + DBG(DBG_PARSING, + DBG_log(" '%d.%d.%d.%d'", *object.ptr, *(object.ptr+1), + *(object.ptr+2), *(object.ptr+3)); + ) + valid_gn = TRUE; + break; + case GN_OBJ_OTHER_NAME: + if (!parse_otherName(object, level + 1)) + return NULL; + break; + case GN_OBJ_X400_ADDRESS: + case GN_OBJ_EDI_PARTY_NAME: + case GN_OBJ_REGISTERED_ID: + break; + default: + break; + } + + if (valid_gn) + { + generalName_t *gn = alloc_thing(generalName_t, "generalName"); + gn->kind = (objectID - GN_OBJ_OTHER_NAME) / 2; + gn->name = object; + gn->next = NULL; + return gn; + } + objectID++; + } + return NULL; +} + + +/* + * extracts one or several GNs and puts them into a chained list + */ +static generalName_t* +parse_generalNames(chunk_t blob, int level0, bool implicit) +{ + asn1_ctx_t ctx; + chunk_t object; + u_int level; + int objectID = 0; + + generalName_t *top_gn = NULL; + + asn1_init(&ctx, blob, level0, implicit, DBG_RAW); + + while (objectID < GENERAL_NAMES_ROOF) + { + if (!extract_object(generalNamesObjects, &objectID, &object, &level, &ctx)) + return NULL; + + if (objectID == GENERAL_NAMES_GN) + { + generalName_t *gn = parse_generalName(object, level+1); + if (gn != NULL) + { + gn->next = top_gn; + top_gn = gn; + } + } + objectID++; + } + return top_gn; +} + +/* + * returns a directoryName + */ +chunk_t get_directoryName(chunk_t blob, int level, bool implicit) +{ + chunk_t name = empty_chunk; + generalName_t * gn = parse_generalNames(blob, level, implicit); + + if (gn != NULL && gn->kind == GN_DIRECTORY_NAME) + name= gn->name; + + free_generalNames(gn, FALSE); + + return name; +} + +/* + * extracts and converts a UTCTIME or GENERALIZEDTIME object + */ +time_t +parse_time(chunk_t blob, int level0) +{ + asn1_ctx_t ctx; + chunk_t object; + u_int level; + int objectID = 0; + + asn1_init(&ctx, blob, level0, FALSE, DBG_RAW); + + while (objectID < TIME_ROOF) + { + if (!extract_object(timeObjects, &objectID, &object, &level, &ctx)) + return UNDEFINED_TIME; + + if (objectID == TIME_UTC || objectID == TIME_GENERALIZED) + { + return asn1totime(&object, (objectID == TIME_UTC) + ? ASN1_UTCTIME : ASN1_GENERALIZEDTIME); + } + objectID++; + } + return UNDEFINED_TIME; + } + +/* + * extracts a keyIdentifier + */ +static chunk_t +parse_keyIdentifier(chunk_t blob, int level0, bool implicit) +{ + asn1_ctx_t ctx; + chunk_t object; + u_int level; + int objectID = 0; + + asn1_init(&ctx, blob, level0, implicit, DBG_RAW); + + extract_object(keyIdentifierObjects, &objectID, &object, &level, &ctx); + return object; +} + +/* + * extracts an authoritykeyIdentifier + */ +void +parse_authorityKeyIdentifier(chunk_t blob, int level0 + , chunk_t *authKeyID, chunk_t *authKeySerialNumber) +{ + asn1_ctx_t ctx; + chunk_t object; + u_int level; + int objectID = 0; + + asn1_init(&ctx, blob, level0, FALSE, DBG_RAW); + + while (objectID < AUTH_KEY_ID_ROOF) + { + if (!extract_object(authorityKeyIdentifierObjects, &objectID, &object, &level, &ctx)) + return; + + switch (objectID) { + case AUTH_KEY_ID_KEY_ID: + *authKeyID = parse_keyIdentifier(object, level+1, TRUE); + break; + case AUTH_KEY_ID_CERT_ISSUER: + { + generalName_t * gn = parse_generalNames(object, level+1, TRUE); + + free_generalNames(gn, FALSE); + } + break; + case AUTH_KEY_ID_CERT_SERIAL: + *authKeySerialNumber = object; + break; + default: + break; + } + objectID++; + } +} + +/* + * extracts an authorityInfoAcess location + */ +static void +parse_authorityInfoAccess(chunk_t blob, int level0, chunk_t *accessLocation) +{ + asn1_ctx_t ctx; + chunk_t object; + u_int level; + int objectID = 0; + + u_int accessMethod = OID_UNKNOWN; + + asn1_init(&ctx, blob, level0, FALSE, DBG_RAW); + + while (objectID < AUTH_INFO_ACCESS_ROOF) + { + if (!extract_object(authorityInfoAccessObjects, &objectID, &object, &level, &ctx)) + return; + + switch (objectID) { + case AUTH_INFO_ACCESS_METHOD: + accessMethod = known_oid(object); + break; + case AUTH_INFO_ACCESS_LOCATION: + { + switch (accessMethod) + { + case OID_OCSP: + if (*object.ptr == ASN1_CONTEXT_S_6) + { + if (asn1_length(&object) == ASN1_INVALID_LENGTH) + return; + + DBG(DBG_PARSING, + DBG_log(" '%.*s'",(int)object.len, object.ptr) + ) + + /* only HTTP(S) URIs accepted */ + if (strncasecmp(object.ptr, "http", 4) == 0) + { + *accessLocation = object; + return; + } + } + plog("warning: ignoring OCSP InfoAccessLocation with unkown protocol"); + break; + default: + /* unkown accessMethod, ignoring */ + break; + } + } + break; + default: + break; + } + objectID++; + } + +} + +/* + * extracts extendedKeyUsage OIDs + */ +static bool +parse_extendedKeyUsage(chunk_t blob, int level0) +{ + asn1_ctx_t ctx; + chunk_t object; + u_int level; + int objectID = 0; + + asn1_init(&ctx, blob, level0, FALSE, DBG_RAW); + + while (objectID < EXT_KEY_USAGE_ROOF) + { + if (!extract_object(extendedKeyUsageObjects, &objectID + , &object, &level, &ctx)) + return FALSE; + + if (objectID == EXT_KEY_USAGE_PURPOSE_ID + && known_oid(object) == OID_OCSP_SIGNING) + return TRUE; + objectID++; + } + return FALSE; +} + +/* extracts one or several crlDistributionPoints and puts them into + * a chained list + */ +static generalName_t* +parse_crlDistributionPoints(chunk_t blob, int level0) +{ + asn1_ctx_t ctx; + chunk_t object; + u_int level; + int objectID = 0; + + generalName_t *top_gn = NULL; /* top of the chained list */ + generalName_t **tail_gn = &top_gn; /* tail of the chained list */ + + asn1_init(&ctx, blob, level0, FALSE, DBG_RAW); + + while (objectID < CRL_DIST_POINTS_ROOF) + { + if (!extract_object(crlDistributionPointsObjects, &objectID, + &object, &level, &ctx)) + return NULL; + + if (objectID == CRL_DIST_POINTS_FULLNAME) + { + generalName_t *gn = parse_generalNames(object, level+1, TRUE); + /* append extracted generalNames to existing chained list */ + *tail_gn = gn; + /* find new tail of the chained list */ + while (gn != NULL) + { + tail_gn = &gn->next; gn = gn->next; + } + } + objectID++; + } + return top_gn; +} + + +/* + * Parses an X.509v3 certificate + */ +bool +parse_x509cert(chunk_t blob, u_int level0, x509cert_t *cert) +{ + u_char buf[BUF_LEN]; + asn1_ctx_t ctx; + bool critical; + chunk_t object; + u_int level; + u_int extn_oid = OID_UNKNOWN; + int objectID = 0; + + asn1_init(&ctx, blob, level0, FALSE, DBG_RAW); + + while (objectID < X509_OBJ_ROOF) + { + if (!extract_object(certObjects, &objectID, &object, &level, &ctx)) + return FALSE; + + /* those objects which will parsed further need the next higher level */ + level++; + + switch (objectID) { + case X509_OBJ_CERTIFICATE: + cert->certificate = object; + break; + case X509_OBJ_TBS_CERTIFICATE: + cert->tbsCertificate = object; + break; + case X509_OBJ_VERSION: + cert->version = (object.len) ? (1+(u_int)*object.ptr) : 1; + DBG(DBG_PARSING, + DBG_log(" v%d", cert->version); + ) + break; + case X509_OBJ_SERIAL_NUMBER: + cert->serialNumber = object; + break; + case X509_OBJ_SIG_ALG: + cert->sigAlg = parse_algorithmIdentifier(object, level, NULL); + break; + case X509_OBJ_ISSUER: + cert->issuer = object; + DBG(DBG_PARSING, + dntoa(buf, BUF_LEN, object); + DBG_log(" '%s'",buf) + ) + break; + case X509_OBJ_NOT_BEFORE: + cert->notBefore = parse_time(object, level); + break; + case X509_OBJ_NOT_AFTER: + cert->notAfter = parse_time(object, level); + break; + case X509_OBJ_SUBJECT: + cert->subject = object; + DBG(DBG_PARSING, + dntoa(buf, BUF_LEN, object); + DBG_log(" '%s'",buf) + ) + break; + case X509_OBJ_SUBJECT_PUBLIC_KEY_ALGORITHM: + if (parse_algorithmIdentifier(object, level, NULL) == OID_RSA_ENCRYPTION) + cert->subjectPublicKeyAlgorithm = PUBKEY_ALG_RSA; + else + { + plog(" unsupported public key algorithm"); + return FALSE; + } + break; + case X509_OBJ_SUBJECT_PUBLIC_KEY: + if (ctx.blobs[4].len > 0 && *ctx.blobs[4].ptr == 0x00) + { + /* skip initial bit string octet defining 0 unused bits */ + ctx.blobs[4].ptr++; ctx.blobs[4].len--; + } + else + { + plog(" invalid RSA public key format"); + return FALSE; + } + break; + case X509_OBJ_RSA_PUBLIC_KEY: + cert->subjectPublicKey = object; + break; + case X509_OBJ_MODULUS: + if (object.len < RSA_MIN_OCTETS + 1) + { + plog(" " RSA_MIN_OCTETS_UGH); + return FALSE; + } + if (object.len > RSA_MAX_OCTETS + (size_t)(*object.ptr == 0x00)) + { + plog(" " RSA_MAX_OCTETS_UGH); + return FALSE; + } + cert->modulus = object; + break; + case X509_OBJ_PUBLIC_EXPONENT: + cert->publicExponent = object; + break; + case X509_OBJ_EXTN_ID: + extn_oid = known_oid(object); + break; + case X509_OBJ_CRITICAL: + critical = object.len && *object.ptr; + DBG(DBG_PARSING, + DBG_log(" %s",(critical)?"TRUE":"FALSE"); + ) + break; + case X509_OBJ_EXTN_VALUE: + { + switch (extn_oid) { + case OID_SUBJECT_KEY_ID: + cert->subjectKeyID = + parse_keyIdentifier(object, level, FALSE); + break; + case OID_SUBJECT_ALT_NAME: + cert->subjectAltName = + parse_generalNames(object, level, FALSE); + break; + case OID_BASIC_CONSTRAINTS: + cert->isCA = + parse_basicConstraints(object, level); + break; + case OID_CRL_DISTRIBUTION_POINTS: + cert->crlDistributionPoints = + parse_crlDistributionPoints(object, level); + break; + case OID_AUTHORITY_KEY_ID: + parse_authorityKeyIdentifier(object, level + , &cert->authKeyID, &cert->authKeySerialNumber); + break; + case OID_AUTHORITY_INFO_ACCESS: + parse_authorityInfoAccess(object, level, &cert->accessLocation); + break; + case OID_EXTENDED_KEY_USAGE: + cert->isOcspSigner = parse_extendedKeyUsage(object, level); + break; + case OID_NS_REVOCATION_URL: + case OID_NS_CA_REVOCATION_URL: + case OID_NS_CA_POLICY_URL: + case OID_NS_COMMENT: + if (!parse_asn1_simple_object(&object, ASN1_IA5STRING + , level, oid_names[extn_oid].name)) + { + return FALSE; + } + break; + default: + break; + } + } + break; + case X509_OBJ_ALGORITHM: + cert->algorithm = parse_algorithmIdentifier(object, level, NULL); + break; + case X509_OBJ_SIGNATURE: + cert->signature = object; + break; + default: + break; + } + objectID++; + } + time(&cert->installed); + return TRUE; +} + +/* verify the validity of a certificate by + * checking the notBefore and notAfter dates + */ +err_t +check_validity(const x509cert_t *cert, time_t *until) +{ + time_t current_time; + + time(¤t_time); + DBG(DBG_CONTROL | DBG_PARSING , + DBG_log(" not before : %s", timetoa(&cert->notBefore, TRUE)); + DBG_log(" current time: %s", timetoa(¤t_time, TRUE)); + DBG_log(" not after : %s", timetoa(&cert->notAfter, TRUE)); + ) + + if (cert->notAfter < *until) *until = cert->notAfter; + + if (current_time < cert->notBefore) + return "certificate is not valid yet"; + if (current_time > cert->notAfter) + return "certificate has expired"; + else + return NULL; +} + +/* + * verifies a X.509 certificate + */ +bool +verify_x509cert(const x509cert_t *cert, bool strict, time_t *until) +{ + int pathlen; + + *until = cert->notAfter; + + for (pathlen = 0; pathlen < MAX_CA_PATH_LEN; pathlen++) + { + x509cert_t *issuer_cert; + u_char buf[BUF_LEN]; + err_t ugh = NULL; + + DBG(DBG_CONTROL, + dntoa(buf, BUF_LEN, cert->subject); + DBG_log("subject: '%s'",buf); + dntoa(buf, BUF_LEN, cert->issuer); + DBG_log("issuer: '%s'",buf); + if (cert->authKeyID.ptr != NULL) + { + datatot(cert->authKeyID.ptr, cert->authKeyID.len, ':' + , buf, BUF_LEN); + DBG_log("authkey: %s", buf); + } + ) + + ugh = check_validity(cert, until); + + if (ugh != NULL) + { + plog("%s", ugh); + return FALSE; + } + + DBG(DBG_CONTROL, + DBG_log("certificate is valid") + ) + + lock_authcert_list("verify_x509cert"); + issuer_cert = get_authcert(cert->issuer, cert->authKeySerialNumber + , cert->authKeyID, AUTH_CA); + + if (issuer_cert == NULL) + { + plog("issuer cacert not found"); + unlock_authcert_list("verify_x509cert"); + return FALSE; + } + DBG(DBG_CONTROL, + DBG_log("issuer cacert found") + ) + + if (!check_signature(cert->tbsCertificate, cert->signature + , cert->algorithm, cert->algorithm, issuer_cert)) + { + plog("certificate signature is invalid"); + unlock_authcert_list("verify_x509cert"); + return FALSE; + } + DBG(DBG_CONTROL, + DBG_log("certificate signature is valid") + ) + unlock_authcert_list("verify_x509cert"); + + /* check if cert is a self-signed root ca */ + if (pathlen > 0 && same_dn(cert->issuer, cert->subject)) + { + DBG(DBG_CONTROL, + DBG_log("reached self-signed root ca") + ) + return TRUE; + } + else + { + time_t nextUpdate = *until; + time_t revocationDate = UNDEFINED_TIME; + crl_reason_t revocationReason = REASON_UNSPECIFIED; + + /* first check certificate revocation using ocsp */ + cert_status_t status = verify_by_ocsp(cert, &nextUpdate + , &revocationDate, &revocationReason); + + /* if ocsp service is not available then fall back to crl */ + if ((status == CERT_UNDEFINED) + || (status == CERT_UNKNOWN && strict)) + { + status = verify_by_crl(cert, &nextUpdate, &revocationDate + , &revocationReason); + } + + switch (status) + { + case CERT_GOOD: + /* if status information is stale */ + if (strict && nextUpdate < time(NULL)) + { + DBG(DBG_CONTROL, + DBG_log("certificate is good but status is stale") + ) + remove_x509_public_key(cert); + return FALSE; + } + DBG(DBG_CONTROL, + DBG_log("certificate is good") + ) + + /* with strict crl policy the public key must have the same + * lifetime as the validity of the ocsp status or crl lifetime + */ + if (strict && nextUpdate < *until) + *until = nextUpdate; + break; + case CERT_REVOKED: + plog("certificate was revoked on %s, reason: %s" + , timetoa(&revocationDate, TRUE) + , enum_name(&crl_reason_names, revocationReason)); + remove_x509_public_key(cert); + return FALSE; + case CERT_UNKNOWN: + case CERT_UNDEFINED: + default: + plog("certificate status unknown"); + if (strict) + { + remove_x509_public_key(cert); + return FALSE; + } + break; + } + } + + /* go up one step in the trust chain */ + cert = issuer_cert; + } + plog("maximum ca path length of %d levels exceeded", MAX_CA_PATH_LEN); + return FALSE; +} + +/* + * list all X.509 certs in a chained list + */ +void +list_x509cert_chain(const char *caption, x509cert_t* cert, u_char auth_flags + , bool utc) +{ + bool first = TRUE; + time_t now; + + /* determine the current time */ + time(&now); + + while (cert != NULL) + { + if (auth_flags == AUTH_NONE || (auth_flags & cert->authority_flags)) + { + unsigned keysize; + char keyid[KEYID_BUF]; + u_char buf[BUF_LEN]; + cert_t c; + + c.type = CERT_X509_SIGNATURE; + c.u.x509 = cert; + + if (first) + { + whack_log(RC_COMMENT, " "); + whack_log(RC_COMMENT, "List of X.509 %s Certificates:", caption); + whack_log(RC_COMMENT, " "); + first = FALSE; + } + + whack_log(RC_COMMENT, "%s, count: %d", timetoa(&cert->installed, utc), + cert->count); + dntoa(buf, BUF_LEN, cert->subject); + whack_log(RC_COMMENT, " subject: '%s'", buf); + dntoa(buf, BUF_LEN, cert->issuer); + whack_log(RC_COMMENT, " issuer: '%s'", buf); + datatot(cert->serialNumber.ptr, cert->serialNumber.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT, " serial: %s", buf); + form_keyid(cert->publicExponent, cert->modulus, keyid, &keysize); + whack_log(RC_COMMENT, " pubkey: %4d RSA Key %s%s" + , 8*keysize, keyid + , cert->smartcard ? ", on smartcard" : + (has_private_key(c)? ", has private key" : "")); + whack_log(RC_COMMENT, " validity: not before %s %s", + timetoa(&cert->notBefore, utc), + (cert->notBefore < now)?"ok":"fatal (not valid yet)"); + whack_log(RC_COMMENT, " not after %s %s", + timetoa(&cert->notAfter, utc), + check_expiry(cert->notAfter, CA_CERT_WARNING_INTERVAL, TRUE)); + if (cert->subjectKeyID.ptr != NULL) + { + datatot(cert->subjectKeyID.ptr, cert->subjectKeyID.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT, " subjkey: %s", buf); + } + if (cert->authKeyID.ptr != NULL) + { + datatot(cert->authKeyID.ptr, cert->authKeyID.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT, " authkey: %s", buf); + } + if (cert->authKeySerialNumber.ptr != NULL) + { + datatot(cert->authKeySerialNumber.ptr, cert->authKeySerialNumber.len + , ':', buf, BUF_LEN); + whack_log(RC_COMMENT, " aserial: %s", buf); + } + } + cert = cert->next; + } +} + +/* + * list all X.509 end certificates in a chained list + */ +void +list_x509_end_certs(bool utc) +{ + list_x509cert_chain("End", x509certs, AUTH_NONE, utc); +} diff --git a/src/pluto/x509.h b/src/pluto/x509.h new file mode 100644 index 000000000..d15b3da53 --- /dev/null +++ b/src/pluto/x509.h @@ -0,0 +1,138 @@ +/* Support of X.509 certificates + * Copyright (C) 2000 Andreas Hess, Patric Lichtsteiner, Roger Wegmann + * Copyright (C) 2001 Marco Bertossa, Andreas Schleiss + * Copyright (C) 2002 Mario Strasser + * Copyright (C) 2000-2004 Andreas Steffen, Zuercher Hochschule Winterthur + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: x509.h,v 1.10 2005/12/06 22:52:44 as Exp $ + */ + +#ifndef _X509_H +#define _X509_H + +#include "pkcs1.h" +#include "id.h" + +/* Definition of generalNames kinds */ + +typedef enum { + GN_OTHER_NAME = 0, + GN_RFC822_NAME = 1, + GN_DNS_NAME = 2, + GN_X400_ADDRESS = 3, + GN_DIRECTORY_NAME = 4, + GN_EDI_PARTY_NAME = 5, + GN_URI = 6, + GN_IP_ADDRESS = 7, + GN_REGISTERED_ID = 8 +} generalNames_t; + +/* access structure for a GeneralName */ + +typedef struct generalName generalName_t; + +struct generalName { + generalName_t *next; + generalNames_t kind; + chunk_t name; +}; + +/* access structure for an X.509v3 certificate */ + +typedef struct x509cert x509cert_t; + +struct x509cert { + x509cert_t *next; + time_t installed; + int count; + bool smartcard; + u_char authority_flags; + chunk_t certificate; + chunk_t tbsCertificate; + u_int version; + chunk_t serialNumber; + /* signature */ + int sigAlg; + chunk_t issuer; + /* validity */ + time_t notBefore; + time_t notAfter; + chunk_t subject; + /* subjectPublicKeyInfo */ + enum pubkey_alg subjectPublicKeyAlgorithm; + chunk_t subjectPublicKey; + chunk_t modulus; + chunk_t publicExponent; + /* issuerUniqueID */ + /* subjectUniqueID */ + /* v3 extensions */ + /* extension */ + /* extension */ + /* extnID */ + /* critical */ + /* extnValue */ + bool isCA; + bool isOcspSigner; /* ocsp */ + chunk_t subjectKeyID; + chunk_t authKeyID; + chunk_t authKeySerialNumber; + chunk_t accessLocation; /* ocsp */ + generalName_t *subjectAltName; + generalName_t *crlDistributionPoints; + /* signatureAlgorithm */ + int algorithm; + chunk_t signature; +}; + +/* used for initialization */ +extern const x509cert_t empty_x509cert; + +extern bool same_serial(chunk_t a, chunk_t b); +extern bool same_keyid(chunk_t a, chunk_t b); +extern bool same_dn(chunk_t a, chunk_t b); +extern bool match_dn(chunk_t a, chunk_t b, int *wildcards); +extern bool same_x509cert(const x509cert_t *a, const x509cert_t *b); +extern void hex_str(chunk_t bin, chunk_t *str); +extern int dn_count_wildcards(chunk_t dn); +extern int dntoa(char *dst, size_t dstlen, chunk_t dn); +extern int dntoa_or_null(char *dst, size_t dstlen, chunk_t dn + , const char* null_dn); +extern err_t atodn(char *src, chunk_t *dn); +extern void gntoid(struct id *id, const generalName_t *gn); +extern void compute_subjectKeyID(x509cert_t *cert, chunk_t subjectKeyID); +extern void select_x509cert_id(x509cert_t *cert, struct id *end_id); +extern bool parse_x509cert(chunk_t blob, u_int level0, x509cert_t *cert); +extern time_t parse_time(chunk_t blob, int level0); +extern void parse_authorityKeyIdentifier(chunk_t blob, int level0 + , chunk_t *authKeyID, chunk_t *authKeySerialNumber); +extern chunk_t get_directoryName(chunk_t blob, int level, bool implicit); +extern err_t check_validity(const x509cert_t *cert, time_t *until); +extern bool check_signature(chunk_t tbs, chunk_t sig, int digest_alg + , int enc_alg, const x509cert_t *issuer_cert); +extern bool verify_x509cert(const x509cert_t *cert, bool strict, time_t *until); +extern x509cert_t* add_x509cert(x509cert_t *cert); +extern x509cert_t* get_x509cert(chunk_t issuer, chunk_t serial, chunk_t keyid + , x509cert_t* chain); +extern void build_x509cert(x509cert_t *cert, const RSA_public_key_t *cert_key + , const RSA_private_key_t *signer_key); +extern chunk_t build_subjectAltNames(generalName_t *subjectAltNames); +extern void share_x509cert(x509cert_t *cert); +extern void release_x509cert(x509cert_t *cert); +extern void free_x509cert(x509cert_t *cert); +extern void store_x509certs(x509cert_t **firstcert, bool strict); +extern void list_x509cert_chain(const char *caption, x509cert_t* cert + , u_char auth_flags, bool utc); +extern void list_x509_end_certs(bool utc); +extern void free_generalNames(generalName_t* gn, bool free_name); + +#endif /* _X509_H */ diff --git a/src/pluto/xauth.c b/src/pluto/xauth.c new file mode 100644 index 000000000..3d30ad227 --- /dev/null +++ b/src/pluto/xauth.c @@ -0,0 +1,79 @@ +/* Initialization and finalization of the dynamic XAUTH module + * Copyright (C) 2006 Andreas Steffen + * Hochschule fuer Technik Rapperswil, Switzerland + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: xauth.c,v 1.1 2005/01/06 22:10:15 as Exp $ + */ + +#include <dlfcn.h> + +#include <freeswan.h> + +#include "constants.h" +#include "defs.h" +#include "xauth.h" +#include "keys.h" +#include "log.h" + +void +xauth_init(void) +{ +#ifdef XAUTH_DEFAULT_LIB + xauth_module.handle = dlopen(XAUTH_DEFAULT_LIB, RTLD_NOW); + + if (xauth_module.handle != NULL) + { + DBG(DBG_CONTROL, + DBG_log("xauth module '%s' loading'", XAUTH_DEFAULT_LIB) + ) + xauth_module.get_secret = (bool (*) (const xauth_t*)) + dlsym(xauth_module.handle, "get_secret"); + DBG(DBG_CONTROL, + if (xauth_module.get_secret != NULL) + { + DBG_log("xauth module: found get_secret() function"); + } + ) + xauth_module.verify_secret = (bool (*) (const xauth_t*)) + dlsym(xauth_module.handle, "verify_secret"); + DBG(DBG_CONTROL, + if (xauth_module.verify_secret != NULL) + { + DBG_log("xauth module: found verify_secret() function"); + } + ) + } +#endif + /* any null function pointers will be filled in by default functions */ + xauth_defaults(); +} + +void +xauth_finalize(void) +{ +#ifdef XAUTH_DEFAULT_LIB + if (xauth_module.handle != NULL) + { + if (dlclose(xauth_module.handle)) + { + plog("failed to unload xauth module"); + } + else + { + DBG(DBG_CONTROL, + DBG_log("xauth module unloaded") + ) + } + } +#endif +} diff --git a/src/pluto/xauth.h b/src/pluto/xauth.h new file mode 100644 index 000000000..1f06aefd9 --- /dev/null +++ b/src/pluto/xauth.h @@ -0,0 +1,41 @@ +/* Interface definition of the XAUTH server and|or client module + * Copyright (C) 2006 Andreas Steffen + * Hochschule fuer Technik Rapperswil, Switzerland + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: xauth.h,v 1.1 2005/01/06 22:10:15 as Exp $ + */ + +#ifndef _XAUTH_H +#define _XAUTH_H + +/* XAUTH credentials */ + +struct chunk_t; + +typedef struct { + chunk_t user_name; + chunk_t user_password; +} xauth_t; + +typedef struct { + void *handle; + bool (*get_secret) (xauth_t *xauth_secret); + bool (*verify_secret) (const xauth_t *xauth_secret); +} xauth_module_t; + +extern xauth_module_t xauth_module; + +extern void xauth_init(void); +extern void xauth_finalize(void); + +#endif /* _XAUTH_H */ |