From 4b4110dd68706b4171fc6d8a6f4f2a9cd820edac Mon Sep 17 00:00:00 2001 From: Francis Dupont Date: Mon, 30 Jun 2014 15:26:38 +0200 Subject: [PATCH] applied #2406: OpenSSL alternate crypto backend --- ChangeLog | 7 + Makefile.am | 11 +- configure.ac | 290 +++++--- dns++.pc.in | 2 +- doc/guide/bind10-guide.xml | 23 +- src/bin/cmdctl/Makefile.am | 13 +- .../{b10-certgen.cc => botan-certgen.cc} | 0 src/bin/cmdctl/openssl-certgen.cc | 416 +++++++++++ src/bin/cmdctl/tests/Makefile.am | 14 +- ...-certgen_test.py => botan-certgen_test.py} | 0 src/bin/cmdctl/tests/openssl-certgen_test.py | 253 +++++++ src/hooks/dhcp/user_chk/tests/Makefile.am | 4 +- src/lib/cryptolink/Makefile.am | 17 +- src/lib/cryptolink/botan_hash.cc | 202 ++++++ src/lib/cryptolink/botan_hmac.cc | 279 ++++++++ src/lib/cryptolink/botan_link.cc | 48 ++ src/lib/cryptolink/crypto_hash.cc | 42 ++ src/lib/cryptolink/crypto_hash.h | 146 ++++ src/lib/cryptolink/crypto_hmac.cc | 225 ------ src/lib/cryptolink/cryptolink.cc | 27 +- src/lib/cryptolink/cryptolink.h | 25 + src/lib/cryptolink/openssl_hash.cc | 183 +++++ src/lib/cryptolink/openssl_hmac.cc | 288 ++++++++ src/lib/cryptolink/openssl_link.cc | 51 ++ src/lib/cryptolink/tests/Makefile.am | 6 +- src/lib/cryptolink/tests/crypto_unittests.cc | 583 +-------------- src/lib/cryptolink/tests/hash_unittests.cc | 602 ++++++++++++++++ src/lib/cryptolink/tests/hmac_unittests.cc | 676 ++++++++++++++++++ src/lib/dhcp_ddns/Makefile.am | 6 +- src/lib/dhcp_ddns/ncr_msg.cc | 16 +- src/lib/dhcp_ddns/tests/Makefile.am | 4 +- src/lib/dns/tests/Makefile.am | 6 +- 32 files changed, 3517 insertions(+), 948 deletions(-) rename src/bin/cmdctl/{b10-certgen.cc => botan-certgen.cc} (100%) create mode 100644 src/bin/cmdctl/openssl-certgen.cc rename src/bin/cmdctl/tests/{b10-certgen_test.py => botan-certgen_test.py} (100%) create mode 100644 src/bin/cmdctl/tests/openssl-certgen_test.py create mode 100644 src/lib/cryptolink/botan_hash.cc create mode 100644 src/lib/cryptolink/botan_hmac.cc create mode 100644 src/lib/cryptolink/botan_link.cc create mode 100644 src/lib/cryptolink/crypto_hash.cc create mode 100644 src/lib/cryptolink/crypto_hash.h create mode 100644 src/lib/cryptolink/openssl_hash.cc create mode 100644 src/lib/cryptolink/openssl_hmac.cc create mode 100644 src/lib/cryptolink/openssl_link.cc create mode 100644 src/lib/cryptolink/tests/hash_unittests.cc create mode 100644 src/lib/cryptolink/tests/hmac_unittests.cc diff --git a/ChangeLog b/ChangeLog index ab6cdbc5c..41c028c68 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +794. [func] fdupont + cryptolink: add OpenSSL crypto backend as an alternative to Botan + by specifying --with-openssl[=PATH] on the "configure" command + line. Add hash support to the cryptolink API and use it in DHCP + DDNS, removing the Botan dependency. + (Trac #2406, git xxx) + 793. [func] tmark DHCP-DDNS: Implemented dynamic reconfiguration of the server, triggered when the SIGHUP signal is received by the server's diff --git a/Makefile.am b/Makefile.am index 014d7aa27..6c7f2b89f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -7,6 +7,7 @@ USE_LCOV=@USE_LCOV@ LCOV=@LCOV@ GENHTML=@GENHTML@ DISTCHECK_GTEST_CONFIGURE_FLAG=@DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_CRYPTO_CONFIGURE_FLAG=@DISTCHECK_CRYPTO_CONFIGURE_FLAG@ DISTCLEANFILES = config.report @@ -16,6 +17,9 @@ DISTCHECK_CONFIGURE_FLAGS = --disable-install-configurations # Use same --with-gtest flag if set DISTCHECK_CONFIGURE_FLAGS += $(DISTCHECK_GTEST_CONFIGURE_FLAG) +# Keep the crypto backend config +DISTCHECK_CONFIGURE_FLAGS += $(DISTCHECK_CRYPTO_CONFIGURE_FLAG) + dist_doc_DATA = AUTHORS COPYING ChangeLog README .PHONY: check-valgrind check-valgrind-suppress @@ -73,12 +77,17 @@ report-cpp-coverage: c++/4.4\*/ext/\* \ c++/4.4\*/\*-\*/bits/\* \ boost/\* \ +if HAVE_BOTAN botan/\* \ +endif ext/asio/\* \ ext/coroutine/\* \ gtest/\* \ - log4cplus/\* \ include/\* \ + log4cplus/\* \ +if HAVE_OPENSSL + openssl/\* \ +endif tests/\* \ unittests/\* \ \*_unittests.cc \ diff --git a/configure.ac b/configure.ac index 6e20c8a4f..deefc858f 100644 --- a/configure.ac +++ b/configure.ac @@ -646,6 +646,13 @@ AC_DEFUN([ACX_CHECK_PROG_NONCACHE], [ IFS="$IFS_SAVED" ]) +# Avoid checking Botan if OpenSSL is wanted +AC_ARG_WITH([openssl], + [AS_HELP_STRING([--with-openssl[[=PATH]]], [Enables OpenSSL, + location can be specified optionally])], + [use_openssl="$withval"], + [use_openssl="auto"]) + # Botan helper test function # Tries to compile a botan program, given the output of the given # config tool @@ -661,12 +668,12 @@ AC_DEFUN([ACX_TRY_BOTAN_TOOL], [ AC_MSG_CHECKING([usability of ${TOOL} ${TOOL_ARG}]) if test "$BOTAN_TOOL" != "" ; then if test -x ${BOTAN_TOOL}; then - BOTAN_LIBS=`$BOTAN_TOOL $TOOL_ARG --libs` + CRYPTO_LIBS=`$BOTAN_TOOL $TOOL_ARG --libs` LIBS_SAVED=${LIBS} - LIBS="$LIBS $BOTAN_LIBS" - BOTAN_INCLUDES=`$BOTAN_TOOL $TOOL_ARG --cflags` + LIBS="$LIBS $CRYPTO_LIBS" + CRYPTO_INCLUDES=`$BOTAN_TOOL $TOOL_ARG --cflags` CPPFLAGS_SAVED=${CPPFLAGS} - CPPFLAGS="$BOTAN_INCLUDES $CPPFLAGS" + CPPFLAGS="$CRYPTO_INCLUDES $CPPFLAGS" #AC_MSG_RESULT([found]) AC_LINK_IFELSE( [AC_LANG_PROGRAM([#include @@ -711,14 +718,18 @@ AC_DEFUN([ACX_TRY_BOTAN_TOOL], [ # against botan should neither -config scripts nor pkgconfig data exist). # botan_config="yes" +if test "${use_openssl}" != "auto" -a "${use_openssl}" != "no" ; then + botan_config="no" +fi AC_ARG_WITH([botan-config], AC_HELP_STRING([--with-botan-config=PATH], [specify the path to the botan-config script]), [botan_config="$withval"]) if test "${botan_config}" = "no" ; then - AC_MSG_ERROR([Need botan for libcryptolink]) -fi -if test "${botan_config}" != "yes" ; then + if test "${use_openssl}" = "no" ; then + AC_MSG_ERROR([Need Botan or OpenSSL for libcryptolink]) + fi +elif test "${botan_config}" != "yes" ; then if test -x "${botan_config}" ; then if test -d "${botan_config}" ; then AC_MSG_ERROR([${botan_config} is a directory]) @@ -763,91 +774,101 @@ fi if test "x${BOTAN_CONFIG}" != "x" then - BOTAN_LIBS=`${BOTAN_CONFIG} --libs` - BOTAN_INCLUDES=`${BOTAN_CONFIG} --cflags` + CRYPTO_LIBS=`${BOTAN_CONFIG} --libs` + CRYPTO_INCLUDES=`${BOTAN_CONFIG} --cflags` # We expect botan-config --libs to contain -L, but # this is not always the case. As a heuristics workaround we add # -L`botan-config --prefix/lib` in this case (if not present already). - # Same for BOTAN_INCLUDES (but using include instead of lib) below. + # Same for CRYPTO_INCLUDES (but using include instead of lib) below. if [ ${BOTAN_CONFIG} --prefix >/dev/null 2>&1 ] ; then - echo ${BOTAN_LIBS} | grep -- -L > /dev/null || \ - BOTAN_LIBS="-L`${BOTAN_CONFIG} --prefix`/lib ${BOTAN_LIBS}" - echo ${BOTAN_INCLUDES} | grep -- -I > /dev/null || \ - BOTAN_INCLUDES="-I`${BOTAN_CONFIG} --prefix`/include ${BOTAN_INCLUDES}" + echo ${CRYPTO_LIBS} | grep -- -L > /dev/null || \ + CRYPTO_LIBS="-L`${BOTAN_CONFIG} --prefix`/lib ${CRYPTO_LIBS}" + echo ${CRYPTO_INCLUDES} | grep -- -I > /dev/null || \ + CRYPTO_INCLUDES="-I`${BOTAN_CONFIG} --prefix`/include ${CRYPTO_INCLUDES}" fi fi -dnl Determine the Botan version -AC_MSG_CHECKING([Botan version]) -cat > conftest.cpp << EOF +if test "x${CRYPTO_LIBS}" != "x" +then + dnl Determine the Botan version + AC_MSG_CHECKING([Botan version]) + cat > conftest.cpp << EOF #include AUTOCONF_BOTAN_VERSION=BOTAN_VERSION_MAJOR . BOTAN_VERSION_MINOR . BOTAN_VERSION_PATCH EOF -BOTAN_VERSION=`$CPP $CPPFLAGS $BOTAN_INCLUDES conftest.cpp | grep '^AUTOCONF_BOTAN_VERSION=' | $SED -e 's/^AUTOCONF_BOTAN_VERSION=//' -e 's/[[ ]]//g' -e 's/"//g' 2> /dev/null` -if test -z "$BOTAN_VERSION"; then - BOTAN_VERSION="unknown" -fi -$RM -f conftest.cpp -AC_MSG_RESULT([$BOTAN_VERSION]) - -# botan-config script (and the way we call pkg-config) returns -L and -l -# as one string, but we need them in separate values -BOTAN_LDFLAGS= -BOTAN_NEWLIBS= -for flag in ${BOTAN_LIBS}; do - BOTAN_LDFLAGS="${BOTAN_LDFLAGS} `echo $flag | ${SED} -ne '/^\(\-L\)/p'`" - BOTAN_LIBS="${BOTAN_LIBS} `echo $flag | ${SED} -ne '/^\(\-l\)/p'`" -done - -# See python_rpath for some info on why we do this -if test "x$ISC_RPATH_FLAG" != "x"; then - BOTAN_RPATH= - for flag in ${BOTAN_LIBS}; do - BOTAN_RPATH="${BOTAN_RPATH} `echo $flag | ${SED} -ne "s/^\(\-L\)/${ISC_RPATH_FLAG}/p"`" - done -AC_SUBST(BOTAN_RPATH) - -# According to the libtool manual, it should be sufficient if we -# specify the "-R libdir" in our wrapper library of botan (no other -# programs will need libbotan directly); "libdir" should be added to -# the program's binary image. But we've seen in our build environments -# that (some versions of?) libtool doesn't propagate -R as documented, -# and it caused a linker error at run time. To work around this, we -# also add the rpath to the global LDFLAGS. - LDFLAGS="$BOTAN_RPATH $LDFLAGS" -fi - -AC_SUBST(BOTAN_LDFLAGS) -AC_SUBST(BOTAN_LIBS) -AC_SUBST(BOTAN_INCLUDES) -# Even though chances are high we already performed a real compilation check -# in the search for the right (pkg)config data, we try again here, to -# be sure. -CPPFLAGS_SAVED=$CPPFLAGS -CPPFLAGS="$BOTAN_INCLUDES $CPPFLAGS" -LIBS_SAVED="$LIBS" -LIBS="$LIBS $BOTAN_LIBS" - -# ac_header_preproc is an autoconf symbol (undocumented but stable) that -# is set if the pre-processor phase passes. Thus by adding a custom -# failure handler we can detect the difference between a header not existing -# (or not even passing the pre-processor phase) and a header file resulting -# in compilation failures. -AC_CHECK_HEADERS([botan/botan.h],,[ + CRYPTO_VERSION=`$CPP $CPPFLAGS $CRYPTO_INCLUDES conftest.cpp | grep '^AUTOCONF_BOTAN_VERSION=' | $SED -e 's/^AUTOCONF_BOTAN_VERSION=//' -e 's/[[ ]]//g' -e 's/"//g' 2> /dev/null` + if test -z "$CRYPTO_VERSION"; then + CRYPTO_VERSION="unknown" + fi + $RM -f conftest.cpp + AC_MSG_RESULT([$CRYPTO_VERSION]) + + # botan-config script (and the way we call pkg-config) returns -L and -l + # as one string, but we need them in separate values + CRYPTO_LDFLAGS= + for flag in ${CRYPTO_LIBS}; do + CRYPTO_LDFLAGS="${CRYPTO_LDFLAGS} `echo $flag | ${SED} -ne '/^\(\-L\)/p'`" + CRYPTO_LIBS="${CRYPTO_LIBS} `echo $flag | ${SED} -ne '/^\(\-l\)/p'`" + done + + # See crypto_rpath for some info on why we do this + if test "x$ISC_RPATH_FLAG" != "x"; then + CRYPTO_RPATH= + for flag in ${CRYPTO_LIBS}; do + CRYPTO_RPATH="${CRYPTO_RPATH} `echo $flag | ${SED} -ne "s/^\(\-L\)/${ISC_RPATH_FLAG}/p"`" + done + # According to the libtool manual, it should be sufficient if we + # specify the "-R libdir" in our wrapper library of botan (no other + # programs will need libbotan directly); "libdir" should be added to + # the program's binary image. But we've seen in our build environments + # that (some versions of?) libtool doesn't propagate -R as documented, + # and it caused a linker error at run time. To work around this, we + # also add the rpath to the global LDFLAGS. + LDFLAGS="$CRYPTO_RPATH $LDFLAGS" + fi + + # Even though chances are high we already performed a real compilation check + # in the search for the right (pkg)config data, we try again here, to + # be sure. + CPPFLAGS_SAVED=$CPPFLAGS + CPPFLAGS="$CRYPTO_INCLUDES $CPPFLAGS" + LIBS_SAVED="$LIBS" + LIBS="$LIBS $CRYPTO_LIBS" + + # ac_header_preproc is an autoconf symbol (undocumented but stable) that + # is set if the pre-processor phase passes. Thus by adding a custom + # failure handler we can detect the difference between a header not existing + # (or not even passing the pre-processor phase) and a header file resulting + # in compilation failures. + AC_CHECK_HEADERS([botan/botan.h],,[ + CRYPTO_INCLUDES="" + CRYPTO_LIBS="" + CRYPTO_LDFLAGS="" + CRYPTO_RPATH="" if test "x$ac_header_preproc" = "xyes"; then - AC_MSG_ERROR([ + AC_MSG_RESULT([ botan/botan.h was found but is unusable. The most common cause of this problem is attempting to use an updated C++ compiler with older C++ libraries, such as the version of Botan that comes with your distribution. If you have updated your C++ compiler we highly recommend that you use support libraries such as Boost and Botan that were compiled with the same compiler version.]) else - AC_MSG_ERROR([Missing required header files.]) + AC_MSG_RESULT([Missing required header files.]) fi] -) -AC_LINK_IFELSE( + ) + CPPFLAGS=$CPPFLAGS_SAVED + LIBS=$LIBS_SAVED +fi + +if test "x${CRYPTO_LIBS}" != "x" +then + CPPFLAGS_SAVED=$CPPFLAGS + CPPFLAGS="$CRYPTO_INCLUDES $CPPFLAGS" + LIBS_SAVED="$LIBS" + LIBS="$LIBS $CRYPTO_LIBS" + AC_LINK_IFELSE( [AC_LANG_PROGRAM([#include #include ], @@ -857,14 +878,116 @@ AC_LINK_IFELSE( ])], [AC_MSG_RESULT([checking for Botan library... yes])], [AC_MSG_RESULT([checking for Botan library... no]) - AC_MSG_ERROR([Needs Botan library 1.8 or higher. On some systems, + CRYPTO_INCLUDES="" + CRYPTO_LIBS="" + CRYPTO_LDFLAGS="" + CRYPTO_RPATH="" + AC_MSG_RESULT([Needs Botan library 1.8 or higher. On some systems, the botan package has a few missing dependencies (libbz2 and - libgmp), if libbotan has been installed and you see this error, + libgmp), if libbotan has been installed and you see this message, try upgrading to a higher version of botan or installing libbz2 and libgmp.])] -) -CPPFLAGS=$CPPFLAGS_SAVED -LIBS=$LIBS_SAVED + ) + CPPFLAGS=$CPPFLAGS_SAVED + LIBS=$LIBS_SAVED +fi + +if test "x${CRYPTO_LIBS}" != "x" +then + CRYPTO_NAME="Botan" + DISABLED_CRYPTO="OpenSSL" + CRYPTO_PACKAGE="botan-1.8" + CRYPTO_CFLAGS="" + DISTCHECK_CRYPTO_CONFIGURE_FLAG="--with-botan=$botan_config" + AC_DEFINE_UNQUOTED([WITH_BOTAN], [], [Compile with Botan crypto]) +else + CRYPTO_NAME="OpenSSL" + DISABLED_CRYPTO="Botan" + CRYPTO_PACKAGE="openssl-1.0.0" + AC_DEFINE_UNQUOTED([WITH_OPENSSL], [], [Compile with OpenSSL crypto]) + AC_MSG_CHECKING(for OpenSSL library) + # from bind9 + + if test "${use_openssl}" = "auto" ; then + use_openssl="yes" + fi + if test "${use_openssl}" = "yes" ; then + for d in /usr /usr/local /usr/local/ssl /usr/pkg /usr/sfw; do + if test -f $d/include/openssl/opensslv.h; then + use_openssl=$d; break + fi + done + fi + if test "${use_openssl}" = "yes" ; then + AC_MSG_ERROR([OpenSSL auto detection failed]) + fi + if ! test -f "${use_openssl}"/include/openssl/opensslv.h ; then + AC_MSG_ERROR([OpenSSL not found at ${use_openssl}]) + fi + AC_MSG_RESULT(yes) + if test "${use_openssl}" = "/usr" ; then + CRYPTO_INCLUDES="" + CRYPTO_LIBS="-lcrypto" + DISTCHECK_CRYPTO_CONFIGURE_FLAG="--with-openssl" + case "$host" in + *-apple-darwin*) + # Starting with OSX 10.7 (Lion) OpenSSL is deprecated + CRYPTO_CFLAGS="-Wno-deprecated-declarations" + ;; + *) + CRYPTO_CFLAGS="" + ;; + esac + else + CRYPTO_CFLAGS="" + CRYPTO_INCLUDES="-I${use_openssl}/include" + DISTCHECK_CRYPTO_CONFIGURE_FLAG="--with-openssl=${use_openssl}" + case $host in + *-solaris*) + CRYPTO_LIBS="-L${use_openssl}/lib -R${use_openssl}/lib -lcrypto" + ;; + *-hp-hpux*) + CRYPTO_LIBS="-L${use_openssl}/lib -Wl,+b: -lcrypto" + ;; + *-apple-darwin*) + if test -f "${use_openssl}/lib/libcrypto.dylib" ; then + CRYPTO_LIBS="-L${use_openssl}/lib -lcrypto" + else + CRYPTO_LIBS="${use_openssl}/lib/libcrypto.a" + fi + ;; + *) + CRYPTO_LIBS="-L${use_openssl}/lib -lcrypto" + ;; + esac + fi + dnl Determine the OpenSSL version + AC_MSG_CHECKING([OpenSSL version]) + cat > conftest.cpp << EOF +#include +AUTOCONF_OPENSSL_VERSION=OPENSSL_VERSION_TEXT +EOF + + CRYPTO_VERSION=`$CPP $CPPFLAGS $CRYPTO_INCLUDES conftest.cpp | grep '^AUTOCONF_OPENSSL_VERSION=' | $SED -e 's/^AUTOCONF_OPENSSL_VERSION=//' -e 's/"//g' 2> /dev/null` + if test -z "$CRYPTO_VERSION" ; then + CRYPTO_VERSION="unknown" + fi + $RM -f conftest.cpp + AC_MSG_RESULT([$CRYPTO_VERSION]) + #CRYPTO_LDFLAGS="-ldl" + CRYPTO_LDFLAGS="" + CRYPTO_RPATH="" +fi + +AM_CONDITIONAL(HAVE_BOTAN, test "$CRYPTO_NAME" = "Botan") +AM_CONDITIONAL(HAVE_OPENSSL, test "$CRYPTO_NAME" = "OpenSSL") +AC_SUBST(CRYPTO_INCLUDES) +AC_SUBST(CRYPTO_CFLAGS) +AC_SUBST(CRYPTO_LIBS) +AC_SUBST(CRYPTO_LDFLAGS) +AC_SUBST(CRYPTO_PACKAGE) +AC_SUBST(CRYPTO_RPATH) +AC_SUBST(DISTCHECK_CRYPTO_CONFIGURE_FLAG) # Check for MySql. The path to the mysql_config program is given with # the --with-mysql-config (default to /usr/bin/mysql-config). By default, @@ -1653,11 +1776,14 @@ Boost: BOOST_VERSION: ${BOOST_VERSION} BOOST_INCLUDES: ${BOOST_INCLUDES} -Botan: - BOTAN_VERSION: ${BOTAN_VERSION} - BOTAN_INCLUDES: ${BOTAN_INCLUDES} - BOTAN_LDFLAGS: ${BOTAN_LDFLAGS} - BOTAN_LIBS: ${BOTAN_LIBS} +${CRYPTO_NAME}: + CRYPTO_VERSION: ${CRYPTO_VERSION} + CRYPTO_CFLAGS: ${CRYPTO_CFLAGS} + CRYPTO_INCLUDES: ${CRYPTO_INCLUDES} + CRYPTO_LDFLAGS: ${CRYPTO_LDFLAGS} + CRYPTO_LIBS: ${CRYPTO_LIBS} + +${DISABLED_CRYPTO}: no Log4cplus: LOG4CPLUS_VERSION: ${LOG4CPLUS_VERSION} diff --git a/dns++.pc.in b/dns++.pc.in index 8d2725c99..ef65c7ed1 100644 --- a/dns++.pc.in +++ b/dns++.pc.in @@ -6,6 +6,6 @@ includedir=@includedir@ Name: dns++ Description: BIND 10 DNS library Version: @PACKAGE_VERSION@ -Requires: botan-1.8 +Requires: @CRYPTO_PACKAGE@ Cflags: -I${includedir}/@PACKAGE_NAME@ Libs: -L${libdir} -lb10-dns++ -lb10-cryptolink -lb10-util -lb10-exceptions -lm diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml index 08f3b343a..480cb6d4c 100644 --- a/doc/guide/bind10-guide.xml +++ b/doc/guide/bind10-guide.xml @@ -138,9 +138,13 @@ - Kea uses the Botan crypto library for C++ - (). - It requires at least Botan version 1.8. + Kea supports two crypto libraries: Botan and OpenSSL. Only one + of them is required during compilation. Kea uses the Botan crypto + library for C++ (). + It requires at least Botan version 1.8. As an alternative to Botan, + Kea can use the OpenSSL crypto library + (). + It requires a version with SHA-2 support. @@ -546,7 +550,7 @@ $ ./configure To build Kea, also install the Botan (at least version - 1.8) and the log4cplus (at least version 1.0.3) + 1.8) or OpenSSL, and the log4cplus (at least version 1.0.3) development include headers. @@ -696,6 +700,17 @@ as a dependency earlier --> + + --with-openssl + + Replace Botan by OpenSSL for the crypto library. + The default is to try to find a working Botan then + OpenSSL only if not found. + + + + + --without-werror diff --git a/src/bin/cmdctl/Makefile.am b/src/bin/cmdctl/Makefile.am index a2d04a362..9c9689331 100644 --- a/src/bin/cmdctl/Makefile.am +++ b/src/bin/cmdctl/Makefile.am @@ -52,9 +52,16 @@ b10-cmdctl: cmdctl.py $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py $(CERTFILE $(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" cmdctl.py >$@ chmod a+x $@ -b10_certgen_SOURCES = b10-certgen.cc -b10_certgen_CXXFLAGS = $(BOTAN_INCLUDES) -b10_certgen_LDFLAGS = $(BOTAN_LIBS) +if HAVE_BOTAN +b10_certgen_SOURCES = botan-certgen.cc +EXTRA_DIST += openssl-certgen.cc +endif +if HAVE_OPENSSL +b10_certgen_SOURCES = openssl-certgen.cc +EXTRA_DIST += botan-certgen.cc +endif +b10_certgen_CXXFLAGS = $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES) +b10_certgen_LDFLAGS = $(CRYPTO_LIBS) # Generate the initial certificates immediately cmdctl-keyfile.pem: b10-certgen diff --git a/src/bin/cmdctl/b10-certgen.cc b/src/bin/cmdctl/botan-certgen.cc similarity index 100% rename from src/bin/cmdctl/b10-certgen.cc rename to src/bin/cmdctl/botan-certgen.cc diff --git a/src/bin/cmdctl/openssl-certgen.cc b/src/bin/cmdctl/openssl-certgen.cc new file mode 100644 index 000000000..07e8a3e2f --- /dev/null +++ b/src/bin/cmdctl/openssl-certgen.cc @@ -0,0 +1,416 @@ +// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +// For cleaner 'does not exist or is not readable' output than +// openssl provides +#include +#include + +// This is a simple tool that creates a self-signed PEM certificate +// for use with BIND 10. It creates a simple certificate for initial +// setup. Currently, all values are hardcoded defaults. For future +// versions, we may want to add more options for administrators. + +// It will create a PEM file containing a certificate with the following +// values: +// common name: localhost +// organization: BIND10 +// country code: US + +// Additional error return codes; these are specifically +// chosen to be distinct from validation error codes as +// provided by OpenSSL. Their main use is to distinguish +// error cases in the unit tests. +const int DECODING_ERROR = 100; +const int BAD_OPTIONS = 101; +const int READ_ERROR = 102; +const int WRITE_ERROR = 103; +const int UNKNOWN_ERROR = 104; +const int NO_SUCH_FILE = 105; +const int FILE_PERMISSION_ERROR = 106; + +void +usage() { + std::cout << "Usage: b10-certgen [OPTION]..." << std::endl; + std::cout << "Validate, create, or update a self-signed certificate for " + "use with b10-cmdctl" << std::endl; + std::cout << "" << std::endl; + std::cout << "Options:" << std::endl; + std::cout << "-c, --certfile=FILE\t\tfile to read or store the certificate" + << std::endl; + std::cout << "-f, --force\t\t\toverwrite existing certificate even if it" + << std::endl <<"\t\t\t\tis valid" << std::endl; + std::cout << "-h, --help\t\t\tshow this help" << std::endl; + std::cout << "-k, --keyfile=FILE\t\tfile to store the generated private key" + << std::endl; + std::cout << "-w, --write\t\t\tcreate a new certificate if the given file" + << std::endl << "\t\t\t\tdoes not exist, or if is is not valid" + << std::endl; + std::cout << "-q, --quiet\t\t\tprint no output when creating or validating" + << std::endl; +} + +/// \brief Returns true if the given file exists +/// +/// \param filename The file to check +/// \return true if file exists +bool +fileExists(const std::string& filename) { + return (access(filename.c_str(), F_OK) == 0); +} + +/// \brief Returns true if the given file exists and is readable +/// +/// \param filename The file to check +/// \return true if file exists and is readable +bool +fileIsReadable(const std::string& filename) { + return (access(filename.c_str(), R_OK) == 0); +} + +/// \brief Returns true if the given file exists and is writable +/// +/// \param filename The file to check +/// \return true if file exists and is writable +bool +fileIsWritable(const std::string& filename) { + return (access(filename.c_str(), W_OK) == 0); +} + +class CertificateTool { +public: + CertificateTool(bool quiet) : quiet_(quiet) {} + + int + createKeyAndCertificate(const std::string& key_file_name, + const std::string& cert_file_name) { + // Create and store a private key + print("Creating key file " + key_file_name); + RSA* rsa = RSA_generate_key(2048, 65537UL, NULL, NULL); + std::ofstream key_file(key_file_name.c_str()); + if (!key_file.good()) { + print(std::string("Error writing to ") + key_file_name + + ": " + std::strerror(errno)); + return (WRITE_ERROR); + } + BIO* key_mem = BIO_new(BIO_s_mem()); + PEM_write_bio_RSAPrivateKey(key_mem, rsa, NULL, NULL, 0, NULL, NULL); + char* p; + long len = BIO_get_mem_data(key_mem, &p); + key_file.write(p, (unsigned) len); + BIO_free(key_mem); + if (!key_file.good()) { + print(std::string("Error writing to ") + key_file_name + + ": " + std::strerror(errno)); + return (WRITE_ERROR); + } + key_file.close(); + + // Certificate options, currently hardcoded. + // For a future version we may want to make these + // settable. + X509* cert = X509_new(); + X509_set_version(cert, 2); + BIGNUM* serial = BN_new(); + BN_pseudo_rand(serial, 64, 0, 0); + BN_to_ASN1_INTEGER(serial, X509_get_serialNumber(cert)); + BN_free(serial); + X509_NAME* name = X509_get_subject_name(cert); + std::string cn("localhost"); + X509_NAME_add_entry_by_NID(name, NID_commonName, MBSTRING_ASC, + (unsigned char*) cn.c_str(), cn.size(), + -1, 0); + std::string org("UNKNOWN"); + X509_NAME_add_entry_by_NID(name, NID_organizationName, MBSTRING_ASC, + (unsigned char*) org.c_str(), org.size(), + -1, 0); + std::string cc("XX"); + X509_NAME_add_entry_by_NID(name, NID_countryName, MBSTRING_ASC, + (unsigned char*) cc.c_str(), cc.size(), + -1, 0); + X509_set_issuer_name(cert, name); + X509_gmtime_adj(X509_get_notBefore(cert), 0); + X509_gmtime_adj(X509_get_notAfter(cert), 60*60*24*365L); + EVP_PKEY* pkey = EVP_PKEY_new(); + EVP_PKEY_assign_RSA(pkey, rsa); + X509_set_pubkey(cert, pkey); + X509V3_CTX ec; + X509V3_set_ctx_nodb(&ec); + X509V3_set_ctx(&ec, cert, cert, NULL, NULL, 0); + const std::string bc_val("critical,CA:TRUE,pathlen:1"); + X509_EXTENSION* bc = X509V3_EXT_conf_nid(NULL, &ec, + NID_basic_constraints, + (char*) bc_val.c_str()); + X509_add_ext(cert, bc, -1); + X509_EXTENSION_free(bc); + const std::string ku_val=("critical,keyCertSign,cRLSign"); + X509_EXTENSION* ku = X509V3_EXT_conf_nid(NULL, &ec, + NID_key_usage, + (char*) ku_val.c_str()); + X509_add_ext(cert, ku, -1); + X509_EXTENSION_free(ku); + const std::string ski_val("hash"); + X509_EXTENSION* ski = X509V3_EXT_conf_nid(NULL, &ec, + NID_subject_key_identifier, + (char*) ski_val.c_str()); + X509_add_ext(cert, ski, -1); + X509_EXTENSION_free(ski); + X509_sign(cert, pkey, EVP_sha256()); + + print("Creating certificate file " + cert_file_name); + std::ofstream cert_file(cert_file_name.c_str()); + if (!cert_file.good()) { + print(std::string("Error writing to ") + cert_file_name + + ": " + std::strerror(errno)); + return (WRITE_ERROR); + } + BIO* cert_mem = BIO_new(BIO_s_mem()); + PEM_write_bio_X509(cert_mem, cert); + p = NULL; + len = BIO_get_mem_data(cert_mem, &p); + cert_file.write(p, (unsigned) len); + BIO_free(cert_mem); + if (!cert_file.good()) { + print(std::string("Error writing to ") + cert_file_name + + ": " + std::strerror(errno)); + return (WRITE_ERROR); + } + cert_file.close(); + X509_free(cert); + RSA_free(rsa); + return (0); + } + + int + validateCertificate(const std::string& certfile) { + // Since we are dealing with a self-signed certificate here, we + // also use the certificate to check itself; i.e. we add it + // as a trusted certificate, then validate the certificate itself. + BIO* in = BIO_new_file(certfile.c_str(), "r"); + if (in == NULL) { + print("failed to read " + certfile); + return (READ_ERROR); + } + X509* cert = PEM_read_bio_X509(in, NULL, NULL, NULL); + BIO_free(in); + if (cert == NULL) { + print("failed to decode " + certfile); + return (DECODING_ERROR); + } + X509_STORE* store = X509_STORE_new(); + X509_STORE_CTX* csc = X509_STORE_CTX_new(); + X509_STORE_CTX_init(csc, store, cert, NULL); + STACK_OF(X509)* trusted = sk_X509_new_null(); + sk_X509_push(trusted, X509_dup(cert)); + X509_STORE_CTX_trusted_stack(csc, trusted); + const int result = X509_verify_cert(csc); + const int cerror = X509_STORE_CTX_get_error(csc); + X509_STORE_CTX_free(csc); + X509_free(cert); + + if (result > 0) { + print(certfile + " is valid"); + return (X509_V_OK); + } else { + print(certfile + " failed to verify: " + + X509_verify_cert_error_string(cerror)); + return (cerror); + } + } + + /// \brief Runs the tool + /// + /// \param create_cert Create certificate if true, validate if false. + /// Does nothing if certificate exists and is valid. + /// \param force_create Create new certificate even if it is valid. + /// \param certfile Certificate file to read to or write from. + /// \param keyfile Key file to write if certificate is created. + /// Ignored if create_cert is false + /// \return zero on success, non-zero on failure + int + run(bool create_cert, bool force_create, const std::string& certfile, + const std::string& keyfile) + { + if (create_cert) { + // Unless force is given, only create it if the current + // one is not OK + + // First do some basic permission checks; both files + // should either not exist, or be both readable + // and writable + // The checks are done one by one so all errors can + // be enumerated in one go + if (fileExists(certfile)) { + if (!fileIsReadable(certfile)) { + print(certfile + " not readable: " + std::strerror(errno)); + create_cert = false; + } + if (!fileIsWritable(certfile)) { + print(certfile + " not writable: " + std::strerror(errno)); + create_cert = false; + } + } + // The key file really only needs write permissions (for + // b10-certgen that is) + if (fileExists(keyfile)) { + if (!fileIsWritable(keyfile)) { + print(keyfile + " not writable: " + std::strerror(errno)); + create_cert = false; + } + } + if (!create_cert) { + print("Not creating new certificate, " + "check file permissions"); + return (FILE_PERMISSION_ERROR); + } + + // If we reach this, we know that if they exist, we can both + // read and write them, so now it's up to content checking + // and/or force_create + + if (force_create || !fileExists(certfile) || + validateCertificate(certfile) != X509_V_OK) { + return (createKeyAndCertificate(keyfile, certfile)); + } else { + print("Not creating a new certificate (use -f to force)"); + } + } else { + if (!fileExists(certfile)) { + print(certfile + ": " + std::strerror(errno)); + return (NO_SUCH_FILE); + } + if (!fileIsReadable(certfile)) { + print(certfile + " not readable: " + std::strerror(errno)); + return (FILE_PERMISSION_ERROR); + } + int result = validateCertificate(certfile); + if (result != 0) { + print("Running with -w would overwrite the certificate"); + } + return (result); + } + return (0); + } +private: + /// Prints the message to stdout unless quiet_ is true + void print(const std::string& msg) { + if (!quiet_) { + std::cout << msg << std::endl; + } + } + + bool quiet_; +}; + +int +main(int argc, char* argv[]) +{ + // ERR_load_crypto_strings(); + + // create or check certificate + bool create_cert = false; + // force creation even if not necessary + bool force_create = false; + // don't print any output + bool quiet = false; + + // default certificate file + std::string certfile("cmdctl-certfile.pem"); + // default key file + std::string keyfile("cmdctl-keyfile.pem"); + + // whether or not the above values have been + // overridden (used in command line checking) + bool certfile_default = true; + bool keyfile_default = true; + + // It would appear some environments insist on + // char* here (Sunstudio on Solaris), so we const_cast + // them to get rid of compiler warnings. + const struct option long_options[] = { + { const_cast("certfile"), required_argument, NULL, 'c' }, + { const_cast("force"), no_argument, NULL, 'f' }, + { const_cast("help"), no_argument, NULL, 'h' }, + { const_cast("keyfile"), required_argument, NULL, 'k' }, + { const_cast("write"), no_argument, NULL, 'w' }, + { const_cast("quiet"), no_argument, NULL, 'q' }, + { NULL, 0, NULL, 0 } + }; + + int opt, option_index; + while ((opt = getopt_long(argc, argv, "c:fhk:wq", long_options, + &option_index)) != -1) { + switch (opt) { + case 'c': + certfile = optarg; + certfile_default = false; + break; + case 'f': + force_create = true; + break; + case 'h': + usage(); + return (0); + break; + case 'k': + keyfile = optarg; + keyfile_default = false; + break; + case 'w': + create_cert = true; + break; + case 'q': + quiet = true; + break; + default: + // A message will have already been output about the error. + return (BAD_OPTIONS); + } + } + + if (optind < argc) { + std::cout << "Error: extraneous arguments" << std::endl << std::endl; + usage(); + return (BAD_OPTIONS); + } + + // Some sanity checks on option combinations + if (create_cert && (certfile_default ^ keyfile_default)) { + std::cout << "Error: keyfile and certfile must both be specified " + "if one of them is when calling b10-certgen in write " + "mode." << std::endl; + return (BAD_OPTIONS); + } + if (!create_cert && !keyfile_default) { + std::cout << "Error: keyfile is not used when not in write mode" + << std::endl; + return (BAD_OPTIONS); + } + + // Initialize the tool and perform the appropriate action(s) + CertificateTool tool(quiet); + return (tool.run(create_cert, force_create, certfile, keyfile)); +} diff --git a/src/bin/cmdctl/tests/Makefile.am b/src/bin/cmdctl/tests/Makefile.am index 93bf040c8..4cabd01c3 100644 --- a/src/bin/cmdctl/tests/Makefile.am +++ b/src/bin/cmdctl/tests/Makefile.am @@ -1,6 +1,16 @@ PYCOVERAGE_RUN=@PYCOVERAGE_RUN@ -PYTESTS = cmdctl_test.py b10-certgen_test.py -EXTRA_DIST = $(PYTESTS) +PYTESTS = cmdctl_test.py + +EXTRA_DIST = +if HAVE_BOTAN +PYTESTS += botan-certgen_test.py +EXTRA_DIST += openssl-certgen_test.py +endif +if HAVE_OPENSSL +EXTRA_DIST += botan-certgen_test.py +PYTESTS += openssl-certgen_test.py +endif +EXTRA_DIST += $(PYTESTS) EXTRA_DIST += testdata/expired-certfile.pem EXTRA_DIST += testdata/mangled-certfile.pem EXTRA_DIST += testdata/noca-certfile.pem diff --git a/src/bin/cmdctl/tests/b10-certgen_test.py b/src/bin/cmdctl/tests/botan-certgen_test.py similarity index 100% rename from src/bin/cmdctl/tests/b10-certgen_test.py rename to src/bin/cmdctl/tests/botan-certgen_test.py diff --git a/src/bin/cmdctl/tests/openssl-certgen_test.py b/src/bin/cmdctl/tests/openssl-certgen_test.py new file mode 100644 index 000000000..40e01af7c --- /dev/null +++ b/src/bin/cmdctl/tests/openssl-certgen_test.py @@ -0,0 +1,253 @@ +# Copyright (C) 2014 Internet Systems Consortium. +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM +# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL +# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING +# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION +# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# Note: the main code is in C++, but what we are mostly testing is +# options and behaviour (output/file creation, etc), which is easier +# to test in python. + +import unittest +import os +from subprocess import call +import subprocess +import ssl +import stat + +def run(command): + """ + Small helper function that returns a tuple of (rcode, stdout, stderr) after + running the given command (an array of command and arguments, as passed on + to subprocess). + """ + subp = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdout, stderr) = subp.communicate() + return (subp.returncode, stdout, stderr) + +class FileDeleterContext: + """ + Simple Context Manager that deletes a given set of files when the context + is left. + """ + def __init__(self, files): + self.files = files + + def __enter__(self): + pass + + def __exit__(self, type, value, traceback): + for f in self.files: + if os.path.exists(f): + os.unlink(f) + +class FilePermissionContext: + """ + Simple Context Manager that temporarily modifies file permissions for + a given file + """ + def __init__(self, f, unset_flags = [], set_flags = []): + """ + Initialize file permission context. + See the stat module for possible flags to set or unset. + The flags are changed when the context is entered (i.e. + you can create the context first without any change) + The flags are changed back when the context is left. + + Parameters: + f: string, file to change permissions for + unset_flags: list of flags to unset + set_flags: list of flags to set + """ + self.file = f + self.orig_mode = os.stat(f).st_mode + new_mode = self.orig_mode + for flag in unset_flags: + new_mode = new_mode & ~flag + for flag in set_flags: + new_mode = new_mode | flag + self.new_mode = new_mode + + def __enter__(self): + os.chmod(self.file, self.new_mode) + + def __exit__(self, type, value, traceback): + os.chmod(self.file, self.orig_mode) + +def read_file_data(filename): + """ + Simple text file reader that returns its contents as an array + """ + with open(filename) as f: + return f.readlines() + +class TestCertGenTool(unittest.TestCase): + TOOL = '../b10-certgen' + + def run_check(self, expected_returncode, expected_stdout, expected_stderr, command): + """ + Runs the given command, and checks return code, and outputs (if provided). + Arguments: + expected_returncode, return code of the command + expected_stdout, (multiline) string that is checked agains stdout. + May be None, in which case the check is skipped. + expected_stderr, (multiline) string that is checked agains stderr. + May be None, in which case the check is skipped. + """ + (returncode, stdout, stderr) = run(command) + self.assertEqual(expected_returncode, returncode, " ".join(command)) + if expected_stdout is not None: + self.assertEqual(expected_stdout, stdout.decode()) + if expected_stderr is not None: + self.assertEqual(expected_stderr, stderr.decode()) + + def validate_certificate(self, expected_result, certfile): + """ + Validate a certificate, using the quiet option of the tool; it runs + the check option (-c) for the given base name of the certificate (-f + ), and compares the return code to the given + expected_result value + """ + self.run_check(expected_result, '', '', + [self.TOOL, '-q', '-c', certfile]) + # Same with long options + self.run_check(expected_result, '', '', + [self.TOOL, '--quiet', '--certfile', certfile]) + + + def test_basic_creation(self): + """ + Tests whether basic creation with no arguments (except output + file name) successfully creates a key and certificate + """ + keyfile = 'test-keyfile.pem' + certfile = 'test-certfile.pem' + command = [ self.TOOL, '-q', '-w', '-c', certfile, '-k', keyfile ] + self.creation_helper(command, certfile, keyfile) + # Do same with long options + command = [ self.TOOL, '--quiet', '--write', '--certfile=' + certfile, '--keyfile=' + keyfile ] + self.creation_helper(command, certfile, keyfile) + + def creation_helper(self, command, certfile, keyfile): + """ + Helper method for test_basic_creation. + Performs the actual checks + """ + with FileDeleterContext([keyfile, certfile]): + self.assertFalse(os.path.exists(keyfile)) + self.assertFalse(os.path.exists(certfile)) + self.run_check(0, '', '', command) + self.assertTrue(os.path.exists(keyfile)) + self.assertTrue(os.path.exists(certfile)) + + # Validate the certificate that was just created + self.validate_certificate(0, certfile) + + # When run with the same options, it should *not* create it again, + # as the current certificate should still be valid + certdata = read_file_data(certfile) + keydata = read_file_data(keyfile) + + self.run_check(0, '', '', command) + + self.assertEqual(certdata, read_file_data(certfile)) + self.assertEqual(keydata, read_file_data(keyfile)) + + # but if we add -f, it should force a new creation + command.append('-f') + self.run_check(0, '', '', command) + self.assertNotEqual(certdata, read_file_data(certfile)) + self.assertNotEqual(keydata, read_file_data(keyfile)) + + def test_check_bad_certificates(self): + """ + Tests a few pre-created certificates with the -c option + """ + path = os.environ['CMDCTL_SRC_PATH'] + '/tests/testdata/' + self.validate_certificate(10, path + 'expired-certfile.pem') + self.validate_certificate(100, path + 'mangled-certfile.pem') + self.validate_certificate(20, path + 'noca-certfile.pem') + + def test_bad_options(self): + """ + Tests some combinations of commands that should fail. + """ + # specify -c but not -k + self.run_check(101, + 'Error: keyfile and certfile must both be specified ' + 'if one of them is when calling b10-certgen in write ' + 'mode.\n', + '', [self.TOOL, '-w', '-c', 'foo']) + self.run_check(101, + 'Error: keyfile and certfile must both be specified ' + 'if one of them is when calling b10-certgen in write ' + 'mode.\n', + '', [self.TOOL, '-w', '-k', 'foo']) + self.run_check(101, + 'Error: keyfile is not used when not in write mode\n', + '', [self.TOOL, '-k', 'foo']) + # Extraneous argument + self.run_check(101, None, None, [self.TOOL, 'foo']) + # No such file + self.run_check(105, None, None, [self.TOOL, '-c', 'foo']) + + @unittest.skipIf(os.getuid() == 0, + 'test cannot be run as root user') + def test_permissions(self): + """ + Test some combinations of correct and bad permissions. + """ + keyfile = 'mod-keyfile.pem' + certfile = 'mod-certfile.pem' + command = [ self.TOOL, '-q', '-w', '-c', certfile, '-k', keyfile ] + # Delete them at the end + with FileDeleterContext([keyfile, certfile]): + # Create the two files first + self.run_check(0, '', '', command) + self.validate_certificate(0, certfile) + + # Make the key file unwritable + with FilePermissionContext(keyfile, unset_flags = [stat.S_IWUSR]): + self.run_check(106, '', '', command) + # Should have no effect on validation + self.validate_certificate(0, certfile) + + # Make the cert file unwritable + with FilePermissionContext(certfile, unset_flags = [stat.S_IWUSR]): + self.run_check(106, '', '', command) + # Should have no effect on validation + self.validate_certificate(0, certfile) + + # Make the key file unreadable (this should not matter) + with FilePermissionContext(keyfile, unset_flags = [stat.S_IRUSR]): + self.run_check(0, '', '', command) + + # unreadable key file should also not have any effect on + # validation + self.validate_certificate(0, certfile) + + # Make the cert file unreadable (this should matter) + with FilePermissionContext(certfile, unset_flags = [stat.S_IRUSR]): + self.run_check(106, '', '', command) + + # Unreadable cert file should also fail validation + self.validate_certificate(106, certfile) + + # Not directly a permission problem, but trying to check or create + # in a nonexistent directory returns different error codes + self.validate_certificate(105, 'fakedir/cert') + self.run_check(103, '', '', [ self.TOOL, '-q', '-w', '-c', + 'fakedir/cert', '-k', 'fakedir/key' ]) + +if __name__== '__main__': + unittest.main() + diff --git a/src/hooks/dhcp/user_chk/tests/Makefile.am b/src/hooks/dhcp/user_chk/tests/Makefile.am index 457530c0d..ff17d6cc3 100644 --- a/src/hooks/dhcp/user_chk/tests/Makefile.am +++ b/src/hooks/dhcp/user_chk/tests/Makefile.am @@ -2,7 +2,7 @@ SUBDIRS = . AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib AM_CPPFLAGS += -I$(top_builddir)/src/hooks/dhcp/user_chk -I$(top_srcdir)/src/hooks/dhcp/user_chk -AM_CPPFLAGS += $(BOOST_INCLUDES) $(BOTAN_INCLUDES) +AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES) AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_srcdir)/src/hooks/dhcp/user_chk/tests\" AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\" @@ -71,7 +71,7 @@ libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.l libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la -libdhcp_user_chk_unittests_LDADD += ${BOTAN_LIBS} ${BOTAN_RPATH} +libdhcp_user_chk_unittests_LDADD += ${CRYPTO_LIBS} ${CRYPTO_RPATH} libdhcp_user_chk_unittests_LDADD += $(GTEST_LDADD) endif noinst_PROGRAMS = $(TESTS) diff --git a/src/lib/cryptolink/Makefile.am b/src/lib/cryptolink/Makefile.am index 29e06c005..b58c59a1a 100644 --- a/src/lib/cryptolink/Makefile.am +++ b/src/lib/cryptolink/Makefile.am @@ -1,7 +1,7 @@ SUBDIRS = . tests AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib -AM_CPPFLAGS += $(BOOST_INCLUDES) $(BOTAN_INCLUDES) +AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES) AM_CXXFLAGS = $(B10_CXXFLAGS) CLEANFILES = *.gcno *.gcda @@ -9,7 +9,18 @@ CLEANFILES = *.gcno *.gcda lib_LTLIBRARIES = libkea-cryptolink.la libkea_cryptolink_la_SOURCES = cryptolink.h cryptolink.cc +libkea_cryptolink_la_SOURCES += crypto_hash.h crypto_hash.cc libkea_cryptolink_la_SOURCES += crypto_hmac.h crypto_hmac.cc +if HAVE_BOTAN +libkea_cryptolink_la_SOURCES += botan_link.cc +libkea_cryptolink_la_SOURCES += botan_hash.cc +libkea_cryptolink_la_SOURCES += botan_hmac.cc +endif +if HAVE_OPENSSL +libkea_cryptolink_la_SOURCES += openssl_link.cc +libkea_cryptolink_la_SOURCES += openssl_hash.cc +libkea_cryptolink_la_SOURCES += openssl_hmac.cc +endif -libkea_cryptolink_la_LDFLAGS = ${BOTAN_LDFLAGS} -libkea_cryptolink_la_LIBADD = ${BOTAN_LIBS} ${BOTAN_RPATH} +libkea_cryptolink_la_LDFLAGS = ${CRYPTO_LDFLAGS} +libkea_cryptolink_la_LIBADD = ${CRYPTO_LIBS} ${CRYPTO_RPATH} diff --git a/src/lib/cryptolink/botan_hash.cc b/src/lib/cryptolink/botan_hash.cc new file mode 100644 index 000000000..2df255d35 --- /dev/null +++ b/src/lib/cryptolink/botan_hash.cc @@ -0,0 +1,202 @@ +// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include + +#include + +#include +#include +#include +#include + +#include + +namespace { +/// @brief Decode the HashAlgorithm enum into a name usable by Botan +/// +/// @param algorithm algorithm to be converted +/// @return text representation of the algorithm name +const char* +getBotanHashAlgorithmName(isc::cryptolink::HashAlgorithm algorithm) { + switch (algorithm) { + case isc::cryptolink::MD5: + return ("MD5"); + case isc::cryptolink::SHA1: + return ("SHA-1"); + case isc::cryptolink::SHA256: + return ("SHA-256"); + case isc::cryptolink::SHA224: + return ("SHA-224"); + case isc::cryptolink::SHA384: + return ("SHA-384"); + case isc::cryptolink::SHA512: + return ("SHA-512"); + case isc::cryptolink::UNKNOWN_HASH: + return ("Unknown"); + } + // compiler should have prevented us to reach this, since we have + // no default. But we need a return value anyway + return ("Unknown"); +} + +} // local namespace + + +namespace isc { +namespace cryptolink { + +/// @brief Botan implementation of Hash. Each method is the counterpart +/// of the Hash corresponding method. +class HashImpl { +public: + + /// @brief Constructor for specific hash algorithm + /// + /// @param hash_algorithm The hash algorithm + explicit HashImpl(const HashAlgorithm hash_algorithm) { + Botan::HashFunction* hash; + try { + hash = Botan::get_hash( + getBotanHashAlgorithmName(hash_algorithm)); + } catch (const Botan::Algorithm_Not_Found&) { + isc_throw(isc::cryptolink::UnsupportedAlgorithm, + "Unknown hash algorithm: " << + static_cast(hash_algorithm)); + } catch (const Botan::Exception& exc) { + isc_throw(isc::cryptolink::LibraryError, exc.what()); + } + + hash_.reset(hash); + } + + /// @brief Destructor + ~HashImpl() { } + + /// @brief Returns the output size of the digest + /// + /// @return output size of the digest + size_t getOutputLength() const { +#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,9,0) + return (hash_->output_length()); +#elif BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,8,0) + return (hash_->OUTPUT_LENGTH); +#else +#error "Unsupported Botan version (need 1.8 or higher)" + // added to suppress irrelevant compiler errors + return 0; +#endif + } + + /// @brief Adds data to the digest + /// + /// See @ref isc::cryptolink::Hash::update() for details. + void update(const void* data, const size_t len) { + try { + hash_->update(static_cast(data), len); + } catch (const Botan::Exception& exc) { + isc_throw(isc::cryptolink::LibraryError, exc.what()); + } + } + + /// @brief Calculate the final digest + /// + /// See @ref isc::cryptolink::Hash::final() for details. + void final(isc::util::OutputBuffer& result, size_t len) { + try { + Botan::SecureVector b_result(hash_->final()); + + if (len == 0 || len > b_result.size()) { + len = b_result.size(); + } + result.writeData(b_result.begin(), len); + } catch (const Botan::Exception& exc) { + isc_throw(isc::cryptolink::LibraryError, exc.what()); + } + } + + /// @brief Calculate the final digest + /// + /// See @ref isc::cryptolink::Hash::final() for details. + void final(void* result, size_t len) { + try { + Botan::SecureVector b_result(hash_->final()); + size_t output_size = getOutputLength(); + if (output_size > len) { + output_size = len; + } + std::memcpy(result, b_result.begin(), output_size); + } catch (const Botan::Exception& exc) { + isc_throw(isc::cryptolink::LibraryError, exc.what()); + } + } + + /// @brief Calculate the final digest + /// + /// See @ref isc::cryptolink::Hash::final() for details. + std::vector final(size_t len) { + try { + Botan::SecureVector b_result(hash_->final()); + if (len == 0 || len > b_result.size()) { + return (std::vector(b_result.begin(), b_result.end())); + } else { + return (std::vector(b_result.begin(), &b_result[len])); + } + } catch (const Botan::Exception& exc) { + isc_throw(isc::cryptolink::LibraryError, exc.what()); + } + } + +private: + /// \brief The protected pointer to the Botan HashFunction object + boost::scoped_ptr hash_; +}; + +Hash::Hash(const HashAlgorithm hash_algorithm) +{ + impl_ = new HashImpl(hash_algorithm); +} + +Hash::~Hash() { + delete impl_; +} + +size_t +Hash::getOutputLength() const { + return (impl_->getOutputLength()); +} + +void +Hash::update(const void* data, const size_t len) { + impl_->update(data, len); +} + +void +Hash::final(isc::util::OutputBuffer& result, size_t len) { + impl_->final(result, len); +} + +void +Hash::final(void* result, size_t len) { + impl_->final(result, len); +} + +std::vector +Hash::final(size_t len) { + return impl_->final(len); +} + +} // namespace cryptolink +} // namespace isc diff --git a/src/lib/cryptolink/botan_hmac.cc b/src/lib/cryptolink/botan_hmac.cc new file mode 100644 index 000000000..5346bdec1 --- /dev/null +++ b/src/lib/cryptolink/botan_hmac.cc @@ -0,0 +1,279 @@ +// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +namespace { + +/// @brief Decode the HashAlgorithm enum into a name usable by Botan +/// +/// @param algorithm algorithm to be converted +/// @return text representation of the algorithm name +const char* +getBotanHashAlgorithmName(isc::cryptolink::HashAlgorithm algorithm) { + switch (algorithm) { + case isc::cryptolink::MD5: + return ("MD5"); + case isc::cryptolink::SHA1: + return ("SHA-1"); + case isc::cryptolink::SHA256: + return ("SHA-256"); + case isc::cryptolink::SHA224: + return ("SHA-224"); + case isc::cryptolink::SHA384: + return ("SHA-384"); + case isc::cryptolink::SHA512: + return ("SHA-512"); + case isc::cryptolink::UNKNOWN_HASH: + return ("Unknown"); + } + // compiler should have prevented us to reach this, since we have + // no default. But we need a return value anyway + return ("Unknown"); +} + +} // local namespace + + +namespace isc { +namespace cryptolink { + +/// @brief Botan implementation of HMAC. Each method is the counterpart +/// of the HMAC corresponding method. +class HMACImpl { +public: + /// @brief Constructor from a secret and a hash algorithm + /// + /// See constructor of the @ref isc::cryptolink::HMAC class for details. + /// + /// @param secret The secret to sign with + /// @param secret_len The length of the secret + /// @param hash_algorithm The hash algorithm + explicit HMACImpl(const void* secret, size_t secret_len, + const HashAlgorithm hash_algorithm) { + Botan::HashFunction* hash; + try { + hash = Botan::get_hash( + getBotanHashAlgorithmName(hash_algorithm)); + } catch (const Botan::Algorithm_Not_Found&) { + isc_throw(isc::cryptolink::UnsupportedAlgorithm, + "Unknown hash algorithm: " << + static_cast(hash_algorithm)); + } catch (const Botan::Exception& exc) { + isc_throw(isc::cryptolink::LibraryError, exc.what()); + } + + hmac_.reset(new Botan::HMAC(hash)); + + // If the key length is larger than the block size, we hash the + // key itself first. + try { + // use a temp var so we don't have blocks spanning + // preprocessor directives +#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,9,0) + size_t block_length = hash->hash_block_size(); +#elif BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,8,0) + size_t block_length = hash->HASH_BLOCK_SIZE; +#else +#error "Unsupported Botan version (need 1.8 or higher)" + // added to suppress irrelevant compiler errors + size_t block_length = 0; +#endif + if (secret_len > block_length) { + Botan::SecureVector hashed_key = + hash->process(static_cast(secret), + secret_len); + hmac_->set_key(hashed_key.begin(), hashed_key.size()); + } else { + // Botan 1.8 considers len 0 a bad key. 1.9 does not, + // but we won't accept it anyway, and fail early + if (secret_len == 0) { + isc_throw(BadKey, "Bad HMAC secret length: 0"); + } + hmac_->set_key(static_cast(secret), + secret_len); + } + } catch (const Botan::Invalid_Key_Length& ikl) { + isc_throw(BadKey, ikl.what()); + } catch (const Botan::Exception& exc) { + isc_throw(isc::cryptolink::LibraryError, exc.what()); + } + } + + /// @brief Destructor + ~HMACImpl() { + } + + /// @brief Returns the output size of the digest + /// + /// @return output size of the digest + size_t getOutputLength() const { +#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,9,0) + return (hmac_->output_length()); +#elif BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,8,0) + return (hmac_->OUTPUT_LENGTH); +#else +#error "Unsupported Botan version (need 1.8 or higher)" + // added to suppress irrelevant compiler errors + return 0; +#endif + } + + /// @brief Add data to digest + /// + /// See @ref isc::cryptolink::HMAC::update() for details. + void update(const void* data, const size_t len) { + try { + hmac_->update(static_cast(data), len); + } catch (const Botan::Exception& exc) { + isc_throw(isc::cryptolink::LibraryError, exc.what()); + } + } + + /// @brief Calculate the final signature + /// + /// See @ref isc::cryptolink::HMAC::sign() for details. + void sign(isc::util::OutputBuffer& result, size_t len) { + try { + Botan::SecureVector b_result(hmac_->final()); + + if (len == 0 || len > b_result.size()) { + len = b_result.size(); + } + result.writeData(b_result.begin(), len); + } catch (const Botan::Exception& exc) { + isc_throw(isc::cryptolink::LibraryError, exc.what()); + } + } + + /// @brief Calculate the final signature + /// + /// See @ref isc::cryptolink::HMAC::sign() for details. + void sign(void* result, size_t len) { + try { + Botan::SecureVector b_result(hmac_->final()); + size_t output_size = getOutputLength(); + if (output_size > len) { + output_size = len; + } + std::memcpy(result, b_result.begin(), output_size); + } catch (const Botan::Exception& exc) { + isc_throw(isc::cryptolink::LibraryError, exc.what()); + } + } + + /// @brief Calculate the final signature + /// + /// See @ref isc::cryptolink::HMAC::sign() for details. + std::vector sign(size_t len) { + try { + Botan::SecureVector b_result(hmac_->final()); + if (len == 0 || len > b_result.size()) { + return (std::vector(b_result.begin(), b_result.end())); + } else { + return (std::vector(b_result.begin(), &b_result[len])); + } + } catch (const Botan::Exception& exc) { + isc_throw(isc::cryptolink::LibraryError, exc.what()); + } + } + + + /// @brief Verify an existing signature + /// + /// See @ref isc::cryptolink::HMAC::verify() for details. + bool verify(const void* sig, size_t len) { + /// @todo Botan's verify_mac checks if len matches the output_length, + /// which causes it to fail for truncated signatures, so we do + /// the check ourselves + /// SEE BELOW FOR TEMPORARY CHANGE + try { + Botan::SecureVector our_mac = hmac_->final(); + if (len < getOutputLength()) { + // Currently we don't support truncated signature in TSIG (see + // #920). To avoid validating too short signature accidently, + // we enforce the standard signature size for the moment. + // Once we support truncation correctly, this if-clause should + // (and the capitalized comment above) be removed. + return (false); + } + if (len == 0 || len > getOutputLength()) { + len = getOutputLength(); + } + return (Botan::same_mem(&our_mac[0], + static_cast(sig), + len)); + } catch (const Botan::Exception& exc) { + isc_throw(isc::cryptolink::LibraryError, exc.what()); + } + } + +private: + /// \brief The protected pointer to the Botan HMAC object + boost::scoped_ptr hmac_; +}; + +HMAC::HMAC(const void* secret, size_t secret_length, + const HashAlgorithm hash_algorithm) +{ + impl_ = new HMACImpl(secret, secret_length, hash_algorithm); +} + +HMAC::~HMAC() { + delete impl_; +} + +size_t +HMAC::getOutputLength() const { + return (impl_->getOutputLength()); +} + +void +HMAC::update(const void* data, const size_t len) { + impl_->update(data, len); +} + +void +HMAC::sign(isc::util::OutputBuffer& result, size_t len) { + impl_->sign(result, len); +} + +void +HMAC::sign(void* result, size_t len) { + impl_->sign(result, len); +} + +std::vector +HMAC::sign(size_t len) { + return impl_->sign(len); +} + +bool +HMAC::verify(const void* sig, const size_t len) { + return (impl_->verify(sig, len)); +} + +} // namespace cryptolink +} // namespace isc diff --git a/src/lib/cryptolink/botan_link.cc b/src/lib/cryptolink/botan_link.cc new file mode 100644 index 000000000..c76bbe519 --- /dev/null +++ b/src/lib/cryptolink/botan_link.cc @@ -0,0 +1,48 @@ +// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include +#include + +#include + +namespace isc { +namespace cryptolink { + +// For Botan, we use the CryptoLink class object in RAII style +class CryptoLinkImpl { +private: + Botan::LibraryInitializer botan_init_; +}; + +CryptoLink::~CryptoLink() { + delete impl_; +} + +void +CryptoLink::initialize() { + CryptoLink& c = getCryptoLinkInternal(); + if (c.impl_ == NULL) { + try { + c.impl_ = new CryptoLinkImpl(); + } catch (const Botan::Exception& ex) { + isc_throw(InitializationError, ex.what()); + } + } +} + +} // namespace cryptolink +} // namespace isc + diff --git a/src/lib/cryptolink/crypto_hash.cc b/src/lib/cryptolink/crypto_hash.cc new file mode 100644 index 000000000..95f0fadb4 --- /dev/null +++ b/src/lib/cryptolink/crypto_hash.cc @@ -0,0 +1,42 @@ +// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include + +#include + +#include + +namespace isc { +namespace cryptolink { + +void +digest(const void* data, const size_t data_len, + const HashAlgorithm hash_algorithm, + isc::util::OutputBuffer& result, size_t len) +{ + boost::scoped_ptr hash( + CryptoLink::getCryptoLink().createHash(hash_algorithm)); + hash->update(data, data_len); + hash->final(result, len); +} + +void +deleteHash(Hash* hash) { + delete hash; +} + +} // namespace cryptolink +} // namespace isc diff --git a/src/lib/cryptolink/crypto_hash.h b/src/lib/cryptolink/crypto_hash.h new file mode 100644 index 000000000..ba2fc04ab --- /dev/null +++ b/src/lib/cryptolink/crypto_hash.h @@ -0,0 +1,146 @@ +// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include + +#include + +#include + +#ifndef ISC_CRYPTO_HASH_H +#define ISC_CRYPTO_HASH_H + +namespace isc { +namespace cryptolink { + +/// Forward declaration, pimpl style +class HashImpl; + +/// \brief Hash support +/// +/// This class is used to create Hash digests. Instances +/// can be created with CryptoLink::createHash() +/// +class Hash : private boost::noncopyable { +private: + /// \brief Constructor from a hash algorithm + /// + /// \exception UnsupportedAlgorithmException if the given algorithm + /// is unknown or not supported by the underlying library + /// \exception LibraryError if there was any unexpected exception + /// in the underlying library + /// + /// \param hash_algorithm The hash algorithm + Hash(const HashAlgorithm hash_algorithm); + + friend Hash* CryptoLink::createHash(const HashAlgorithm); + +public: + /// \brief Destructor + ~Hash(); + + /// \brief Returns the output size of the digest + /// + /// \return output size of the digest + size_t getOutputLength() const; + + /// \brief Add data to digest + /// + /// \exception LibraryError if there was any unexpected exception + /// in the underlying library + /// + /// \param data The data to add + /// \param len The size of the data + void update(const void* data, const size_t len); + + /// \brief Calculate the final digest + /// + /// The result will be appended to the given outputbuffer + /// + /// \exception LibraryError if there was any unexpected exception + /// in the underlying library + /// + /// \param result The OutputBuffer to append the result to + /// \param len The number of bytes from the result to copy. If this + /// value is smaller than the algorithms output size, the + /// result will be truncated. If this value is larger, or 0 + /// (the default), it will be ignored + void final(isc::util::OutputBuffer& result, size_t len = 0); + + /// \brief Calculate the final digest + /// + /// len bytes of data from the result will be copied to *result + /// If len is larger than the output size, only output_size bytes + /// will be copied. If it is smaller, the output will be truncated + /// + /// \exception LibraryError if there was any unexpected exception + /// in the underlying library + /// + /// At least len bytes of data must be available for writing at + /// result. + /// + /// \param result The memory location the digest will be written to + /// \param len Specifies the size of the result location available + void final(void* result, size_t len); + + /// \brief Calculate the final digest + /// + /// The result will be returned as a std::vector + /// + /// \exception LibraryError if there was any unexpected exception + /// in the underlying library + /// + /// \param len The number of bytes from the result to copy. If this + /// value is smaller than the algorithms output size, the + /// result will be truncated. If this value is larger, or 0 + /// (the default), it will be ignored + /// \return a vector containing the signature + std::vector final(size_t len = 0); + +private: + HashImpl* impl_; +}; + +/// \brief Create an Hash digest for the given data +/// +/// This is a convenience function that calculates the hash digest, +/// given a fixed amount of data. Internally it does the same as +/// creating an Hash object, feeding it the data, and calculating the +/// resulting digest. +/// +/// \exception UnsupportedAlgorithm if the given algorithm is unknown +/// or not supported by the underlying library +/// \exception LibraryError if there was any unexpected exception +/// in the underlying library +/// +/// \param data The data to digest +/// \param data_len The length of the data +/// \param hash_algorithm The hash algorithm +/// \param result The digest will be appended to this buffer +/// \param len If this is non-zero and less than the output size, +/// the result will be truncated to len bytes +void digest(const void* data, + const size_t data_len, + const HashAlgorithm hash_algorithm, + isc::util::OutputBuffer& result, + size_t len = 0); + +/// \brief Delete an Hash object +void deleteHash(Hash* hash); + +} // namespace cryptolink +} // namespace isc + +#endif // ISC_CRYPTO_HASH_H + diff --git a/src/lib/cryptolink/crypto_hmac.cc b/src/lib/cryptolink/crypto_hmac.cc index c1bbfa865..f20a63982 100644 --- a/src/lib/cryptolink/crypto_hmac.cc +++ b/src/lib/cryptolink/crypto_hmac.cc @@ -17,236 +17,11 @@ #include -#include -#include -#include -#include -#include - #include -namespace { -const char* -getBotanHashAlgorithmName(isc::cryptolink::HashAlgorithm algorithm) { - switch (algorithm) { - case isc::cryptolink::MD5: - return ("MD5"); - break; - case isc::cryptolink::SHA1: - return ("SHA-1"); - break; - case isc::cryptolink::SHA256: - return ("SHA-256"); - break; - case isc::cryptolink::SHA224: - return ("SHA-224"); - break; - case isc::cryptolink::SHA384: - return ("SHA-384"); - break; - case isc::cryptolink::SHA512: - return ("SHA-512"); - break; - case isc::cryptolink::UNKNOWN_HASH: - return ("Unknown"); - break; - } - // compiler should have prevented us to reach this, since we have - // no default. But we need a return value anyway - return ("Unknown"); -} - -} // local namespace - - namespace isc { namespace cryptolink { -class HMACImpl { -public: - explicit HMACImpl(const void* secret, size_t secret_len, - const HashAlgorithm hash_algorithm) { - Botan::HashFunction* hash; - try { - hash = Botan::get_hash( - getBotanHashAlgorithmName(hash_algorithm)); - } catch (const Botan::Algorithm_Not_Found&) { - isc_throw(isc::cryptolink::UnsupportedAlgorithm, - "Unknown hash algorithm: " << - static_cast(hash_algorithm)); - } catch (const Botan::Exception& exc) { - isc_throw(isc::cryptolink::LibraryError, exc.what()); - } - - hmac_.reset(new Botan::HMAC(hash)); - - // If the key length is larger than the block size, we hash the - // key itself first. - try { - // use a temp var so we don't have blocks spanning - // preprocessor directives -#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,9,0) - size_t block_length = hash->hash_block_size(); -#elif BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,8,0) - size_t block_length = hash->HASH_BLOCK_SIZE; -#else -#error "Unsupported Botan version (need 1.8 or higher)" - // added to suppress irrelevant compiler errors - size_t block_length = 0; -#endif - if (secret_len > block_length) { - Botan::SecureVector hashed_key = - hash->process(static_cast(secret), - secret_len); - hmac_->set_key(hashed_key.begin(), hashed_key.size()); - } else { - // Botan 1.8 considers len 0 a bad key. 1.9 does not, - // but we won't accept it anyway, and fail early - if (secret_len == 0) { - isc_throw(BadKey, "Bad HMAC secret length: 0"); - } - hmac_->set_key(static_cast(secret), - secret_len); - } - } catch (const Botan::Invalid_Key_Length& ikl) { - isc_throw(BadKey, ikl.what()); - } catch (const Botan::Exception& exc) { - isc_throw(isc::cryptolink::LibraryError, exc.what()); - } - } - - ~HMACImpl() { } - - size_t getOutputLength() const { -#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,9,0) - return (hmac_->output_length()); -#elif BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,8,0) - return (hmac_->OUTPUT_LENGTH); -#else -#error "Unsupported Botan version (need 1.8 or higher)" - // added to suppress irrelevant compiler errors - return 0; -#endif - } - - void update(const void* data, const size_t len) { - try { - hmac_->update(static_cast(data), len); - } catch (const Botan::Exception& exc) { - isc_throw(isc::cryptolink::LibraryError, exc.what()); - } - } - - void sign(isc::util::OutputBuffer& result, size_t len) { - try { - Botan::SecureVector b_result(hmac_->final()); - - if (len == 0 || len > b_result.size()) { - len = b_result.size(); - } - result.writeData(b_result.begin(), len); - } catch (const Botan::Exception& exc) { - isc_throw(isc::cryptolink::LibraryError, exc.what()); - } - } - - void sign(void* result, size_t len) { - try { - Botan::SecureVector b_result(hmac_->final()); - size_t output_size = getOutputLength(); - if (output_size > len) { - output_size = len; - } - std::memcpy(result, b_result.begin(), output_size); - } catch (const Botan::Exception& exc) { - isc_throw(isc::cryptolink::LibraryError, exc.what()); - } - } - - std::vector sign(size_t len) { - try { - Botan::SecureVector b_result(hmac_->final()); - if (len == 0 || len > b_result.size()) { - return (std::vector(b_result.begin(), b_result.end())); - } else { - return (std::vector(b_result.begin(), &b_result[len])); - } - } catch (const Botan::Exception& exc) { - isc_throw(isc::cryptolink::LibraryError, exc.what()); - } - } - - - bool verify(const void* sig, size_t len) { - // Botan's verify_mac checks if len matches the output_length, - // which causes it to fail for truncated signatures, so we do - // the check ourselves - // SEE BELOW FOR TEMPORARY CHANGE - try { - Botan::SecureVector our_mac = hmac_->final(); - if (len < getOutputLength()) { - // Currently we don't support truncated signature in TSIG (see - // #920). To avoid validating too short signature accidently, - // we enforce the standard signature size for the moment. - // Once we support truncation correctly, this if-clause should - // (and the capitalized comment above) be removed. - return (false); - } - if (len == 0 || len > getOutputLength()) { - len = getOutputLength(); - } - return (Botan::same_mem(&our_mac[0], - static_cast(sig), - len)); - } catch (const Botan::Exception& exc) { - isc_throw(isc::cryptolink::LibraryError, exc.what()); - } - } - -private: - boost::scoped_ptr hmac_; -}; - -HMAC::HMAC(const void* secret, size_t secret_length, - const HashAlgorithm hash_algorithm) -{ - impl_ = new HMACImpl(secret, secret_length, hash_algorithm); -} - -HMAC::~HMAC() { - delete impl_; -} - -size_t -HMAC::getOutputLength() const { - return (impl_->getOutputLength()); -} - -void -HMAC::update(const void* data, const size_t len) { - impl_->update(data, len); -} - -void -HMAC::sign(isc::util::OutputBuffer& result, size_t len) { - impl_->sign(result, len); -} - -void -HMAC::sign(void* result, size_t len) { - impl_->sign(result, len); -} - -std::vector -HMAC::sign(size_t len) { - return impl_->sign(len); -} - -bool -HMAC::verify(const void* sig, const size_t len) { - return (impl_->verify(sig, len)); -} - void signHMAC(const void* data, const size_t data_len, const void* secret, size_t secret_len, const HashAlgorithm hash_algorithm, diff --git a/src/lib/cryptolink/cryptolink.cc b/src/lib/cryptolink/cryptolink.cc index d1c375d4a..d196f0a19 100644 --- a/src/lib/cryptolink/cryptolink.cc +++ b/src/lib/cryptolink/cryptolink.cc @@ -13,23 +13,12 @@ // PERFORMANCE OF THIS SOFTWARE. #include +#include #include -#include - namespace isc { namespace cryptolink { -// For Botan, we use the CryptoLink class object in RAII style -class CryptoLinkImpl { -private: - Botan::LibraryInitializer botan_init_; -}; - -CryptoLink::~CryptoLink() { - delete impl_; -} - CryptoLink& CryptoLink::getCryptoLink() { CryptoLink& c = getCryptoLinkInternal(); @@ -45,16 +34,10 @@ CryptoLink::getCryptoLinkInternal() { return (instance); } -void -CryptoLink::initialize() { - CryptoLink& c = getCryptoLinkInternal(); - if (c.impl_ == NULL) { - try { - c.impl_ = new CryptoLinkImpl(); - } catch (const Botan::Exception& ex) { - isc_throw(InitializationError, ex.what()); - } - } +Hash* +CryptoLink::createHash(const HashAlgorithm hash_algorithm) +{ + return (new Hash(hash_algorithm)); } HMAC* diff --git a/src/lib/cryptolink/cryptolink.h b/src/lib/cryptolink/cryptolink.h index 408ed0069..727c26f78 100644 --- a/src/lib/cryptolink/cryptolink.h +++ b/src/lib/cryptolink/cryptolink.h @@ -44,6 +44,9 @@ enum HashAlgorithm { }; +// Forward declaration for createHash() +class Hash; + // Forward declaration for createHMAC() class HMAC; @@ -158,6 +161,28 @@ public: /// static void initialize(); + /// \brief Factory function for Hash objects + /// + /// CryptoLink objects cannot be constructed directly. This + /// function creates a new Hash object usable for signing or + /// verification. + /// + /// The caller is responsible for deleting the object, and it is + /// therefore highly recommended to place the return value of this + /// function in a scoped_ptr or shared_ptr. + /// + /// If you want to safely delete objects created with this method, + /// you can use the function deleteHash() as defined in + /// crypto_hash.h + /// + /// \exception UnsupportedAlgorithmException if the given algorithm + /// is unknown or not supported by the underlying library + /// \exception LibraryError if there was any unexpected exception + /// in the underlying library + /// + /// \param hash_algorithm The hash algorithm + Hash* createHash(const HashAlgorithm hash_algorithm); + /// \brief Factory function for HMAC objects /// /// CryptoLink objects cannot be constructed directly. This diff --git a/src/lib/cryptolink/openssl_hash.cc b/src/lib/cryptolink/openssl_hash.cc new file mode 100644 index 000000000..b0ebe73c6 --- /dev/null +++ b/src/lib/cryptolink/openssl_hash.cc @@ -0,0 +1,183 @@ +// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include + +#include + +#include + +#include + +namespace { + +/// @brief Decode the HashAlgorithm enum into an EVP_MD pointer (or 0) +/// +/// EVP_MD pointer is a OpenSSL's way of identifying hash algorithms +/// @param algorithm algorithm to be converted +/// @return pointer to EVP_MD which identifies the algorithm +const EVP_MD* +getOpenSSLHashAlgorithm(isc::cryptolink::HashAlgorithm algorithm) { + switch (algorithm) { + case isc::cryptolink::MD5: + return (EVP_md5()); + case isc::cryptolink::SHA1: + return (EVP_sha1()); + case isc::cryptolink::SHA256: + return (EVP_sha256()); + case isc::cryptolink::SHA224: + return (EVP_sha224()); + case isc::cryptolink::SHA384: + return (EVP_sha384()); + case isc::cryptolink::SHA512: + return (EVP_sha512()); + case isc::cryptolink::UNKNOWN_HASH: + return (0); + } + // compiler should have prevented us to reach this, since we have + // no default. But we need a return value anyway + return (0); +} + +} // local namespace + + +namespace isc { +namespace cryptolink { + +/// \brief OpenSSL implementation of Hash. Each method is the counterpart +/// of the Hash corresponding method. +class HashImpl { +public: + + /// @brief Constructor for specific hash algorithm + /// + /// @param hash_algorithm The hash algorithm + explicit HashImpl(const HashAlgorithm hash_algorithm) { + const EVP_MD* algo = getOpenSSLHashAlgorithm(hash_algorithm); + if (algo == 0) { + isc_throw(isc::cryptolink::UnsupportedAlgorithm, + "Unknown hash algorithm: " << + static_cast(hash_algorithm)); + } + + md_.reset(new EVP_MD_CTX); + + EVP_MD_CTX_init(md_.get()); + + EVP_DigestInit_ex(md_.get(), algo, NULL); + } + + /// @brief Destrucotr + ~HashImpl() { + if (md_) { + EVP_MD_CTX_cleanup(md_.get()); + } + } + + /// @brief Returns the output size of the digest + /// + /// @return output size of the digest + size_t getOutputLength() const { + return (EVP_MD_CTX_size(md_.get())); + } + + /// @brief Adds data to the digest + /// + /// See @ref isc::cryptolink::Hash::update() for details. + void update(const void* data, const size_t len) { + EVP_DigestUpdate(md_.get(), data, len); + } + + /// @brief Calculate the final digest + /// + /// See @ref isc::cryptolink::Hash::final() for details. + void final(isc::util::OutputBuffer& result, size_t len) { + size_t size = getOutputLength(); + std::vector digest(size); + EVP_DigestFinal_ex(md_.get(), &digest[0], NULL); + if (len == 0 || len > size) { + len = size; + } + result.writeData(&digest[0], len); + } + + /// @brief Calculate the final digest + /// + /// See @ref isc::cryptolink::Hash::final() for details. + void final(void* result, size_t len) { + size_t size = getOutputLength(); + std::vector digest(size); + EVP_DigestFinal_ex(md_.get(), &digest[0], NULL); + if (len > size) { + len = size; + } + std::memcpy(result, &digest[0], len); + } + + /// @brief Calculate the final digest + /// + /// See @ref isc::cryptolink::Hash::final() for details. + std::vector final(size_t len) { + size_t size = getOutputLength(); + std::vector digest(size); + EVP_DigestFinal_ex(md_.get(), &digest[0], NULL); + if (len != 0 && len < size) { + digest.resize(len); + } + return (std::vector(digest.begin(), digest.end())); + } + +private: + /// @brief The protected pointer to the OpenSSL EVP_MD_CTX structure + boost::scoped_ptr md_; +}; + +Hash::Hash(const HashAlgorithm hash_algorithm) +{ + impl_ = new HashImpl(hash_algorithm); +} + +Hash::~Hash() { + delete impl_; +} + +size_t +Hash::getOutputLength() const { + return (impl_->getOutputLength()); +} + +void +Hash::update(const void* data, const size_t len) { + impl_->update(data, len); +} + +void +Hash::final(isc::util::OutputBuffer& result, size_t len) { + impl_->final(result, len); +} + +void +Hash::final(void* result, size_t len) { + impl_->final(result, len); +} + +std::vector +Hash::final(size_t len) { + return impl_->final(len); +} + +} // namespace cryptolink +} // namespace isc diff --git a/src/lib/cryptolink/openssl_hmac.cc b/src/lib/cryptolink/openssl_hmac.cc new file mode 100644 index 000000000..34940d349 --- /dev/null +++ b/src/lib/cryptolink/openssl_hmac.cc @@ -0,0 +1,288 @@ +// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include + +#include + +#include + +#include + +namespace { + +/// @brief Decode the HashAlgorithm enum into an EVP_MD pointer (or 0) +/// +/// EVP_MD pointer is a OpenSSL's way of identifying hash algorithms +/// @param algorithm algorithm to be converted +/// @return pointer to EVP_MD which identifies the algorithm +const EVP_MD* +getOpenSSLHashAlgorithm(isc::cryptolink::HashAlgorithm algorithm) { + switch (algorithm) { + case isc::cryptolink::MD5: + return (EVP_md5()); + case isc::cryptolink::SHA1: + return (EVP_sha1()); + case isc::cryptolink::SHA256: + return (EVP_sha256()); + case isc::cryptolink::SHA224: + return (EVP_sha224()); + case isc::cryptolink::SHA384: + return (EVP_sha384()); + case isc::cryptolink::SHA512: + return (EVP_sha512()); + case isc::cryptolink::UNKNOWN_HASH: + return (0); + } + // compiler should have prevented us to reach this, since we have + // no default. But we need a return value anyway + return (0); +} + +/// Secure Buffers which are wiped out when released. +template +struct SecBuf { +public: + typedef typename std::vector::iterator iterator; + + typedef typename std::vector::const_iterator const_iterator; + + explicit SecBuf() : vec_(std::vector()) {} + + explicit SecBuf(size_t n, const T& value = T()) : + vec_(std::vector(n, value)) + {} + + SecBuf(iterator first, iterator last) : + vec_(std::vector(first, last)) + {} + + SecBuf(const_iterator first, const_iterator last) : + vec_(std::vector(first, last)) + {} + + SecBuf(const std::vector& x) : vec_(x) {} + + ~SecBuf() { + std::memset(&vec_[0], 0, vec_.capacity() * sizeof(T)); + }; + + iterator begin() { + return (vec_.begin()); + }; + + const_iterator begin() const { + return (vec_.begin()); + }; + + iterator end() { + return (vec_.end()); + }; + + const_iterator end() const { + return (vec_.end()); + }; + + size_t size() const { + return (vec_.size()); + }; + + void resize(size_t sz) { + vec_.resize(sz); + }; + + SecBuf& operator=(const SecBuf& x) { + if (&x != *this) { + vec_ = x.vec_; + } + return (*this); + }; + + T& operator[](size_t n) { + return (vec_[n]); + }; + + const T& operator[](size_t n) const { + return (vec_[n]); + }; + +private: + std::vector vec_; +}; + +} // local namespace + +namespace isc { +namespace cryptolink { + +/// @brief OpenSSL implementation of HMAC. Each method is the counterpart +/// of the HMAC corresponding method. +class HMACImpl { +public: + /// @brief Constructor from a secret and a hash algorithm + /// + /// See constructor of the @ref isc::cryptolink::HMAC class for details. + /// + /// @param secret The secret to sign with + /// @param secret_len The length of the secret + /// @param hash_algorithm The hash algorithm + explicit HMACImpl(const void* secret, size_t secret_len, + const HashAlgorithm hash_algorithm) { + const EVP_MD* algo = getOpenSSLHashAlgorithm(hash_algorithm); + if (algo == 0) { + isc_throw(UnsupportedAlgorithm, + "Unknown hash algorithm: " << + static_cast(hash_algorithm)); + } + if (secret_len == 0) { + isc_throw(BadKey, "Bad HMAC secret length: 0"); + } + + md_.reset(new HMAC_CTX); + HMAC_CTX_init(md_.get()); + + HMAC_Init_ex(md_.get(), secret, + static_cast(secret_len), + algo, NULL); + } + + /// @brief Destructor + ~HMACImpl() { + if (md_) { + HMAC_CTX_cleanup(md_.get()); + } + } + + /// @brief Returns the output size of the digest + /// + /// @return output size of the digest + size_t getOutputLength() const { + int size = HMAC_size(md_.get()); + if (size < 0) { + isc_throw(isc::cryptolink::LibraryError, "EVP_MD_CTX_size"); + } + return (static_cast(size)); + } + + /// @brief Add data to digest + /// + /// See @ref isc::cryptolink::HMAC::update() for details. + void update(const void* data, const size_t len) { + HMAC_Update(md_.get(), static_cast(data), len); + } + + /// @brief Calculate the final signature + /// + /// See @ref isc::cryptolink::HMAC::sign() for details. + void sign(isc::util::OutputBuffer& result, size_t len) { + size_t size = getOutputLength(); + SecBuf digest(size); + HMAC_Final(md_.get(), &digest[0], NULL); + if (len == 0 || len > size) { + len = size; + } + result.writeData(&digest[0], len); + } + + /// @brief Calculate the final signature + /// + /// See @ref isc::cryptolink::HMAC::sign() for details. + void sign(void* result, size_t len) { + size_t size = getOutputLength(); + SecBuf digest(size); + HMAC_Final(md_.get(), &digest[0], NULL); + if (len > size) { + len = size; + } + std::memcpy(result, &digest[0], len); + } + + /// @brief Calculate the final signature + /// + /// See @ref isc::cryptolink::HMAC::sign() for details. + std::vector sign(size_t len) { + size_t size = getOutputLength(); + SecBuf digest(size); + HMAC_Final(md_.get(), &digest[0], NULL); + if (len != 0 && len < size) { + digest.resize(len); + } + return (std::vector(digest.begin(), digest.end())); + } + + /// @brief Verify an existing signature + /// + /// See @ref isc::cryptolink::HMAC::verify() for details. + bool verify(const void* sig, size_t len) { + size_t size = getOutputLength(); + if (len != 0 && len < size / 2) { + return (false); + } + SecBuf digest(size); + HMAC_Final(md_.get(), &digest[0], NULL); + if (len == 0 || len > size) { + len = size; + } + return (std::memcmp(&digest[0], sig, len) == 0); + } + +private: + + /// @brief The protected pointer to the OpenSSL HMAC_CTX structure + boost::scoped_ptr md_; +}; + +HMAC::HMAC(const void* secret, size_t secret_length, + const HashAlgorithm hash_algorithm) +{ + impl_ = new HMACImpl(secret, secret_length, hash_algorithm); +} + +HMAC::~HMAC() { + delete impl_; +} + +size_t +HMAC::getOutputLength() const { + return (impl_->getOutputLength()); +} + +void +HMAC::update(const void* data, const size_t len) { + impl_->update(data, len); +} + +void +HMAC::sign(isc::util::OutputBuffer& result, size_t len) { + impl_->sign(result, len); +} + +void +HMAC::sign(void* result, size_t len) { + impl_->sign(result, len); +} + +std::vector +HMAC::sign(size_t len) { + return impl_->sign(len); +} + +bool +HMAC::verify(const void* sig, const size_t len) { + return (impl_->verify(sig, len)); +} + +} // namespace cryptolink +} // namespace isc diff --git a/src/lib/cryptolink/openssl_link.cc b/src/lib/cryptolink/openssl_link.cc new file mode 100644 index 000000000..dd6a732e3 --- /dev/null +++ b/src/lib/cryptolink/openssl_link.cc @@ -0,0 +1,51 @@ +// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include +#include + +namespace isc { +namespace cryptolink { + +// For OpenSSL, we use the CryptoLink class object in RAII style +class CryptoLinkImpl { + // empty class +}; + +CryptoLink::~CryptoLink() { + delete impl_; +} + +void +CryptoLink::initialize() { + CryptoLink& c = getCryptoLinkInternal(); + if (c.impl_ == NULL) { + try { + c.impl_ = new CryptoLinkImpl(); + } catch (const std::exception &ex) { + // Should never happen + isc_throw(InitializationError, + "Error during OpenSSL initialization:" << ex.what()); + } catch (...) { + // Should never happen + isc_throw(InitializationError, + "Error during OpenSSL initialization"); + } + } +} + +} // namespace cryptolink +} // namespace isc + diff --git a/src/lib/cryptolink/tests/Makefile.am b/src/lib/cryptolink/tests/Makefile.am index fd82f4041..2f17e666c 100644 --- a/src/lib/cryptolink/tests/Makefile.am +++ b/src/lib/cryptolink/tests/Makefile.am @@ -18,9 +18,11 @@ if HAVE_GTEST TESTS += run_unittests run_unittests_SOURCES = run_unittests.cc run_unittests_SOURCES += crypto_unittests.cc +run_unittests_SOURCES += hash_unittests.cc +run_unittests_SOURCES += hmac_unittests.cc run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) -run_unittests_LDFLAGS = $(BOTAN_LDFLAGS) $(GTEST_LDFLAGS) $(AM_LDFLAGS) -run_unittests_LDADD = $(GTEST_LDADD) $(BOTAN_LIBS) +run_unittests_LDFLAGS = $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS) $(AM_LDFLAGS) +run_unittests_LDADD = $(GTEST_LDADD) $(CRYPTO_LIBS) run_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la run_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la diff --git a/src/lib/cryptolink/tests/crypto_unittests.cc b/src/lib/cryptolink/tests/crypto_unittests.cc index c8fe9c6d8..7cc5e8baa 100644 --- a/src/lib/cryptolink/tests/crypto_unittests.cc +++ b/src/lib/cryptolink/tests/crypto_unittests.cc @@ -14,596 +14,15 @@ #include -#include -#include - -#include - #include #include #include -#include - -#include -#include -#include - -using boost::lexical_cast; -using namespace isc::util; -using namespace isc::util::encode; using namespace isc::cryptolink; -namespace { - void checkData(const uint8_t* data, const uint8_t* expected, - size_t len) { - for (size_t i = 0; i < len; ++i) { - ASSERT_EQ(expected[i], data[i]); - } - } - - void checkBuffer(const OutputBuffer& buf, const uint8_t* expected, - size_t len) - { - ASSERT_EQ(len, buf.getLength()); - checkData(static_cast(buf.getData()), expected, - len); - } - - // Sign and verify with the convenience functions - void doHMACTestConv(const std::string& data, - const void* secret, - size_t secret_len, - const HashAlgorithm hash_algorithm, - const uint8_t* expected_hmac, - size_t hmac_len) { - OutputBuffer data_buf(data.size()); - data_buf.writeData(data.c_str(), data.size()); - OutputBuffer hmac_sig(0); - - // Sign it - signHMAC(data_buf.getData(), data_buf.getLength(), - secret, secret_len, hash_algorithm, hmac_sig, hmac_len); - - // Check if the signature is what we expect - checkBuffer(hmac_sig, expected_hmac, hmac_len); - - // Check whether we can verify it ourselves - EXPECT_TRUE(verifyHMAC(data_buf.getData(), data_buf.getLength(), - secret, secret_len, hash_algorithm, - hmac_sig.getData(), - hmac_sig.getLength())); - - // Change the sig by flipping the first octet, and check - // whether verification fails then - hmac_sig.writeUint8At(~hmac_sig[0], 0); - EXPECT_FALSE(verifyHMAC(data_buf.getData(), data_buf.getLength(), - secret, secret_len, hash_algorithm, - hmac_sig.getData(), - hmac_sig.getLength())); - } - - // Sign and verify with an instantiation of an HMAC object - void doHMACTestDirect(const std::string& data, - const void* secret, - size_t secret_len, - const HashAlgorithm hash_algorithm, - const uint8_t* expected_hmac, - size_t hmac_len) { - OutputBuffer data_buf(data.size()); - data_buf.writeData(data.c_str(), data.size()); - OutputBuffer hmac_sig(1); - CryptoLink& crypto = CryptoLink::getCryptoLink(); - - // Sign it - boost::shared_ptr hmac_sign(crypto.createHMAC(secret, - secret_len, - hash_algorithm), - deleteHMAC); - hmac_sign->update(data_buf.getData(), data_buf.getLength()); - hmac_sign->sign(hmac_sig, hmac_len); - - // Check if the signature is what we expect - checkBuffer(hmac_sig, expected_hmac, hmac_len); - - // Check whether we can verify it ourselves - boost::shared_ptr hmac_verify(crypto.createHMAC(secret, - secret_len, - hash_algorithm), - deleteHMAC); - hmac_verify->update(data_buf.getData(), data_buf.getLength()); - EXPECT_TRUE(hmac_verify->verify(hmac_sig.getData(), - hmac_sig.getLength())); - - // Change the sig by flipping the first octet, and check - // whether verification fails then - hmac_sig.writeUint8At(~hmac_sig[0], 0); - EXPECT_FALSE(hmac_verify->verify(hmac_sig.getData(), - hmac_sig.getLength())); - } - - void doHMACTestVector(const std::string& data, - const void* secret, - size_t secret_len, - const HashAlgorithm hash_algorithm, - const uint8_t* expected_hmac, - size_t hmac_len) { - CryptoLink& crypto = CryptoLink::getCryptoLink(); - boost::shared_ptr hmac_sign(crypto.createHMAC(secret, - secret_len, - hash_algorithm), - deleteHMAC); - hmac_sign->update(data.c_str(), data.size()); - std::vector sig = hmac_sign->sign(hmac_len); - ASSERT_EQ(hmac_len, sig.size()); - checkData(&sig[0], expected_hmac, hmac_len); - - boost::shared_ptr hmac_verify(crypto.createHMAC(secret, - secret_len, - hash_algorithm), - deleteHMAC); - hmac_verify->update(data.c_str(), data.size()); - EXPECT_TRUE(hmac_verify->verify(&sig[0], sig.size())); - - sig[0] = ~sig[0]; - EXPECT_FALSE(hmac_verify->verify(&sig[0], sig.size())); - } - - void doHMACTestArray(const std::string& data, - const void* secret, - size_t secret_len, - const HashAlgorithm hash_algorithm, - const uint8_t* expected_hmac, - size_t hmac_len) { - CryptoLink& crypto = CryptoLink::getCryptoLink(); - boost::shared_ptr hmac_sign(crypto.createHMAC(secret, - secret_len, - hash_algorithm), - deleteHMAC); - hmac_sign->update(data.c_str(), data.size()); - - // note: this is not exception-safe, and can leak, but - // if there is an unexpected exception in the code below we - // have more important things to fix. - uint8_t* sig = new uint8_t[hmac_len]; - - hmac_sign->sign(sig, hmac_len); - checkData(sig, expected_hmac, hmac_len); - - boost::shared_ptr hmac_verify(crypto.createHMAC(secret, - secret_len, - hash_algorithm), - deleteHMAC); - hmac_verify->update(data.c_str(), data.size()); - EXPECT_TRUE(hmac_verify->verify(sig, hmac_len)); - - sig[0] = ~sig[0]; - EXPECT_FALSE(hmac_verify->verify(sig, hmac_len)); - - delete[] sig; - } - - void doHMACTest(const std::string& data, - const void* secret, - size_t secret_len, - const HashAlgorithm hash_algorithm, - const uint8_t* expected_hmac, - size_t hmac_len) { - doHMACTestConv(data, secret, secret_len, hash_algorithm, - expected_hmac, hmac_len); - doHMACTestDirect(data, secret, secret_len, hash_algorithm, - expected_hmac, hmac_len); - doHMACTestVector(data, secret, secret_len, hash_algorithm, - expected_hmac, hmac_len); - doHMACTestArray(data, secret, secret_len, hash_algorithm, - expected_hmac, hmac_len); - } -} - -// -// Test values taken from RFC 2202 -// -TEST(CryptoLinkTest, HMAC_MD5_RFC2202_SIGN) { - const uint8_t secret[] = { 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, - 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, - 0x0b, 0x0b }; - const uint8_t hmac_expected[] = { 0x92, 0x94, 0x72, 0x7a, 0x36, - 0x38, 0xbb, 0x1c, 0x13, 0xf4, - 0x8e, 0xf8, 0x15, 0x8b, 0xfc, - 0x9d }; - doHMACTest("Hi There", secret, 16, MD5, hmac_expected, 16); - - const uint8_t hmac_expected2[] = { 0x75, 0x0c, 0x78, 0x3e, 0x6a, - 0xb0, 0xb5, 0x03, 0xea, 0xa8, - 0x6e, 0x31, 0x0a, 0x5d, 0xb7, - 0x38 }; - doHMACTest("what do ya want for nothing?", "Jefe", 4, MD5, - hmac_expected2, 16); - - const uint8_t secret3[] = { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa }; - const uint8_t hmac_expected3[] = { 0x56, 0xbe, 0x34, 0x52, 0x1d, - 0x14, 0x4c, 0x88, 0xdb, 0xb8, - 0xc7, 0x33, 0xf0, 0xe8, 0xb3, - 0xf6}; - doHMACTest(std::string(50, 0xdd), secret3, 16, MD5, hmac_expected3, 16); - - const std::string data4(50, 0xcd); - const uint8_t secret4[] = { 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 }; - const uint8_t hmac_expected4[] = { 0x69, 0x7e, 0xaf, 0x0a, 0xca, - 0x3a, 0x3a, 0xea, 0x3a, 0x75, - 0x16, 0x47, 0x46, 0xff, 0xaa, - 0x79 }; - doHMACTest(data4, secret4, 25, MD5, hmac_expected4, 16); - - const uint8_t hmac_expected6[] = { 0x6b, 0x1a, 0xb7, 0xfe, 0x4b, - 0xd7, 0xbf, 0x8f, 0x0b, 0x62, - 0xe6, 0xce, 0x61, 0xb9, 0xd0, - 0xcd }; - doHMACTest("Test Using Larger Than Block-Size Key - Hash Key First", - std::string(80, 0xaa).c_str(), 80, MD5, hmac_expected6, 16); - - const uint8_t hmac_expected7[] = { 0x6f, 0x63, 0x0f, 0xad, 0x67, - 0xcd, 0xa0, 0xee, 0x1f, 0xb1, - 0xf5, 0x62, 0xdb, 0x3a, 0xa5, - 0x3e }; - doHMACTest("Test Using Larger Than Block-Size Key and Larger Than " - "One Block-Size Data", - std::string(80, 0xaa).c_str(), 80, MD5, hmac_expected7, 16); -} - -// Temporarily disabled -TEST(CryptoLinkTest, DISABLED_HMAC_MD5_RFC2202_SIGN_TRUNCATED) { - const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, - 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, - 0x0c, 0x0c, 0x0c, 0x0c }; - const uint8_t hmac_expected5[] = { 0x56, 0x46, 0x1e, 0xf2, 0x34, - 0x2e, 0xdc, 0x00, 0xf9, 0xba, - 0xb9, 0x95, 0x69, 0x0e, 0xfd, - 0x4c }; - doHMACTest("Test With Truncation", secret5, 16, MD5, - hmac_expected5, 16); - doHMACTest("Test With Truncation", secret5, 16, MD5, - hmac_expected5, 12); -} - -// -// Test values taken from RFC 2202 -// -TEST(CryptoLinkTest, HMAC_SHA1_RFC2202_SIGN) { - const uint8_t secret[] = { 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, - 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, - 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b }; - const uint8_t hmac_expected[] = { 0xb6, 0x17, 0x31, 0x86, 0x55, - 0x05, 0x72, 0x64, 0xe2, 0x8b, - 0xc0, 0xb6, 0xfb, 0x37, 0x8c, - 0x8e, 0xf1, 0x46, 0xbe, 0x00 }; - doHMACTest("Hi There", secret, 20, SHA1, hmac_expected, 20); - - const uint8_t hmac_expected2[] = { 0xef, 0xfc, 0xdf, 0x6a, 0xe5, - 0xeb, 0x2f, 0xa2, 0xd2, 0x74, - 0x16, 0xd5, 0xf1, 0x84, 0xdf, - 0x9c, 0x25, 0x9a, 0x7c, 0x79 }; - doHMACTest("what do ya want for nothing?", "Jefe", 4, SHA1, - hmac_expected2, 20); - - const uint8_t secret3[] = { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa }; - const uint8_t hmac_expected3[] = { 0x12, 0x5d, 0x73, 0x42, 0xb9, - 0xac, 0x11, 0xcd, 0x91, 0xa3, - 0x9a, 0xf4, 0x8a, 0xa1, 0x7b, - 0x4f, 0x63, 0xf1, 0x75, 0xd3 }; - doHMACTest(std::string(50, 0xdd), secret3, 20, SHA1, hmac_expected3, 20); - - const uint8_t secret4[] = { 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 }; - const uint8_t hmac_expected4[] = { 0x4c, 0x90, 0x07, 0xf4, 0x02, - 0x62, 0x50, 0xc6, 0xbc, 0x84, - 0x14, 0xf9, 0xbf, 0x50, 0xc8, - 0x6c, 0x2d, 0x72, 0x35, 0xda }; - doHMACTest(std::string(50, 0xcd), secret4, 25, SHA1, hmac_expected4, 20); - - const uint8_t hmac_expected6[] = { 0xaa, 0x4a, 0xe5, 0xe1, 0x52, - 0x72, 0xd0, 0x0e, 0x95, 0x70, - 0x56, 0x37, 0xce, 0x8a, 0x3b, - 0x55, 0xed, 0x40, 0x21, 0x12 }; - doHMACTest("Test Using Larger Than Block-Size Key - Hash Key First", - std::string(80, 0xaa).c_str(), 80, SHA1, hmac_expected6, 20); - - const uint8_t hmac_expected7[] = { 0xe8, 0xe9, 0x9d, 0x0f, 0x45, - 0x23, 0x7d, 0x78, 0x6d, 0x6b, - 0xba, 0xa7, 0x96, 0x5c, 0x78, - 0x08, 0xbb, 0xff, 0x1a, 0x91 }; - doHMACTest("Test Using Larger Than Block-Size Key and Larger Than " - "One Block-Size Data", - std::string(80, 0xaa).c_str(), 80, SHA1, hmac_expected7, 20); -} - -// Temporarily disabled -TEST(CryptoLinkTest, DISABLED_HMAC_SHA1_RFC2202_SIGN_TRUNCATED) { - const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, - 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, - 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, - 0x0c, 0x0c }; - const uint8_t hmac_expected5[] = { 0x4c, 0x1a, 0x03, 0x42, 0x4b, - 0x55, 0xe0, 0x7f, 0xe7, 0xf2, - 0x7b, 0xe1, 0xd5, 0x8b, 0xb9, - 0x32, 0x4a, 0x9a, 0x5a, 0x04 }; - doHMACTest("Test With Truncation", secret5, 20, SHA1, - hmac_expected5, 20); - doHMACTest("Test With Truncation", secret5, 20, SHA1, - hmac_expected5, 12); -} - -// -// Test values taken from RFC 4231 -// -// Test data from RFC4231, including secret key -// and source data, they are common for sha224/256/384/512 -// so put them together within the separate function. -void -doRFC4231Tests(HashAlgorithm hash_algorithm, - const std::vector >& hmac_list) -{ - std::vector data_list; - std::vector secret_list; - - data_list.push_back("Hi There"); - data_list.push_back("what do ya want for nothing?"); - data_list.push_back(std::string(50, 0xdd)); - data_list.push_back(std::string(50, 0xcd)); - data_list.push_back("Test With Truncation"); - data_list.push_back("Test Using Larger Than Block-Size Key - " - "Hash Key First"); - data_list.push_back("This is a test using a larger than block-size " - "key and a larger than block-size data. The key " - "needs to be hashed before being used by the HMAC " - "algorithm."); - - secret_list.push_back(std::string(20, 0x0b)); - secret_list.push_back("Jefe"); - secret_list.push_back(std::string(20, 0xaa)); - const uint8_t secret_array[] = { - 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 - }; - secret_list.push_back(std::string(secret_array, - secret_array + sizeof(secret_array))); - secret_list.push_back(std::string(20, 0x0c)); - secret_list.push_back(std::string(131, 0xaa)); - secret_list.push_back(std::string(131, 0xaa)); - - // Make sure we provide a consistent size of test data - ASSERT_EQ(secret_list.size(), data_list.size()); - ASSERT_EQ(secret_list.size(), hmac_list.size()); - - for (std::vector::size_type i = 0; - i < data_list.size(); ++i) { - SCOPED_TRACE("RFC4231 HMAC test for algorithm ID: " + - lexical_cast(hash_algorithm) + - ", data ID: " + lexical_cast(i)); - // Until #920 is resolved we have to skip truncation cases. - if (data_list[i] == "Test With Truncation") { - continue; - } - doHMACTest(data_list[i], secret_list[i].c_str(), secret_list[i].size(), - hash_algorithm, &hmac_list[i][0], hmac_list[i].size()); - } -} - -TEST(CryptoLinkTest, HMAC_SHA256_RFC4231_SIGN) { - std::vector > hmac_expected_list(7); - - int i = 0; - decodeHex( - "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7", - hmac_expected_list[i++]); - decodeHex( - "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843", - hmac_expected_list[i++]); - decodeHex( - "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe", - hmac_expected_list[i++]); - decodeHex( - "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b", - hmac_expected_list[i++]); - decodeHex("a3b6167473100ee06e0c796c2955552b", hmac_expected_list[i++]); - decodeHex( - "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54", - hmac_expected_list[i++]); - decodeHex( - "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2", - hmac_expected_list[i++]); - - doRFC4231Tests(SHA256, hmac_expected_list); -} - -// -// Test values taken from RFC 4231, test optional algorithm 224,384,512 -// -TEST(CryptoLinkTest, HMAC_SHA224_RFC4231_SIGN) { - std::vector > hmac_expected_list(7); - - int i = 0; - decodeHex("896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22", - hmac_expected_list[i++]); - decodeHex("a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44", - hmac_expected_list[i++]); - decodeHex("7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea", - hmac_expected_list[i++]); - decodeHex("6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a", - hmac_expected_list[i++]); - decodeHex("0e2aea68a90c8d37c988bcdb9fca6fa8", hmac_expected_list[i++]); - decodeHex("95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e", - hmac_expected_list[i++]); - decodeHex("3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1", - hmac_expected_list[i++]); - - doRFC4231Tests(SHA224, hmac_expected_list); -} - -TEST(CryptoLinkTest, HMAC_SHA384_RFC4231_SIGN) { - std::vector > hmac_expected_list(7); - - int i = 0; - decodeHex("afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc5" - "9cfaea9ea9076ede7f4af152e8b2fa9cb6", hmac_expected_list[i++]); - decodeHex("af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec373632244" - "5e8e2240ca5e69e2c78b3239ecfab21649", hmac_expected_list[i++]); - decodeHex("88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e5596614" - "4b2a5ab39dc13814b94e3ab6e101a34f27", hmac_expected_list[i++]); - decodeHex("3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b" - "4e6801dd23c4a7d679ccf8a386c674cffb", hmac_expected_list[i++]); - decodeHex("3abf34c3503b2a23a46efc619baef897", hmac_expected_list[i++]); - decodeHex("4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4" - "c60c2ef6ab4030fe8296248df163f44952", hmac_expected_list[i++]); - decodeHex("6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99" - "c5a678cc31e799176d3860e6110c46523e", hmac_expected_list[i++]); - - doRFC4231Tests(SHA384, hmac_expected_list); -} - -TEST(CryptoLinkTest, HMAC_SHA512_RFC4231_SIGN) { - std::vector > hmac_expected_list(7); - - int i = 0; - decodeHex("87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17c" - "dedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a12" - "6854", hmac_expected_list[i++]); - decodeHex("164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505" - "549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bc" - "e737", hmac_expected_list[i++]); - decodeHex("fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d" - "39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e132" - "92fb", hmac_expected_list[i++]); - decodeHex("b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3" - "dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a2" - "98dd", hmac_expected_list[i++]); - decodeHex("415fad6271580a531d4179bc891d87a6", hmac_expected_list[i++]); - decodeHex("80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3" - "526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d78" - "6598", hmac_expected_list[i++]); - decodeHex("e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc9" - "44b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c" - "6a58", hmac_expected_list[i++]); - - doRFC4231Tests(SHA512, hmac_expected_list); -} - -TEST(CryptoLinkTest, DISABLED_HMAC_SHA256_RFC2202_SIGN_TRUNCATED) { - const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, - 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, - 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, - 0x0c, 0x0c }; - const uint8_t hmac_expected5[] = { 0xa3, 0xb6, 0x16, 0x74, 0x73, - 0x10, 0x0e, 0xe0, 0x6e, 0x0c, - 0x79, 0x6c, 0x29, 0x55, 0x55, - 0x2b }; - doHMACTest("Test With Truncation", secret5, 20, SHA256, - hmac_expected5, 16); -} - -namespace { - size_t - sigVectorLength(HashAlgorithm alg, size_t len) { - boost::shared_ptr hmac_sign( - CryptoLink::getCryptoLink().createHMAC("asdf", 4, alg), - deleteHMAC); - hmac_sign->update("asdf", 4); - const std::vector sig = hmac_sign->sign(len); - return (sig.size()); - } - - size_t - sigBufferLength(HashAlgorithm alg, size_t len) { - boost::shared_ptr hmac_sign( - CryptoLink::getCryptoLink().createHMAC("asdf", 4, alg), - deleteHMAC); - hmac_sign->update("asdf", 4); - OutputBuffer sig(0); - hmac_sign->sign(sig, len); - return (sig.getLength()); - } -} - -TEST(CryptoLinkTest, HMACSigLengthArgument) { - std::vector sig; - - EXPECT_EQ(16, sigVectorLength(MD5, 0)); - EXPECT_EQ(8, sigVectorLength(MD5, 8)); - EXPECT_EQ(16, sigVectorLength(MD5, 16)); - EXPECT_EQ(16, sigVectorLength(MD5, 40)); - EXPECT_EQ(16, sigVectorLength(MD5, 2000)); - - EXPECT_EQ(20, sigBufferLength(SHA1, 0)); - EXPECT_EQ(8, sigBufferLength(SHA1, 8)); - EXPECT_EQ(20, sigBufferLength(SHA1, 20)); - EXPECT_EQ(20, sigBufferLength(SHA1, 40)); - EXPECT_EQ(20, sigBufferLength(SHA1, 2000)); - - EXPECT_EQ(32, sigBufferLength(SHA256, 0)); - EXPECT_EQ(8, sigBufferLength(SHA256, 8)); - EXPECT_EQ(32, sigBufferLength(SHA256, 32)); - EXPECT_EQ(32, sigBufferLength(SHA256, 40)); - EXPECT_EQ(32, sigBufferLength(SHA256, 3200)); - - EXPECT_EQ(16, sigBufferLength(MD5, 0)); - EXPECT_EQ(8, sigBufferLength(MD5, 8)); - EXPECT_EQ(16, sigBufferLength(MD5, 16)); - EXPECT_EQ(16, sigBufferLength(MD5, 40)); - EXPECT_EQ(16, sigBufferLength(MD5, 2000)); - - EXPECT_EQ(20, sigBufferLength(SHA1, 0)); - EXPECT_EQ(8, sigBufferLength(SHA1, 8)); - EXPECT_EQ(20, sigBufferLength(SHA1, 20)); - EXPECT_EQ(20, sigBufferLength(SHA1, 40)); - EXPECT_EQ(20, sigBufferLength(SHA1, 2000)); - - EXPECT_EQ(32, sigBufferLength(SHA256, 0)); - EXPECT_EQ(8, sigBufferLength(SHA256, 8)); - EXPECT_EQ(32, sigBufferLength(SHA256, 32)); - EXPECT_EQ(32, sigBufferLength(SHA256, 40)); - EXPECT_EQ(32, sigBufferLength(SHA256, 3200)); -} - -TEST(CryptoLinkTest, BadKey) { - OutputBuffer data_buf(0); - OutputBuffer hmac_sig(0); - CryptoLink& crypto = CryptoLink::getCryptoLink(); - - EXPECT_THROW(crypto.createHMAC(NULL, 0, MD5), BadKey); - EXPECT_THROW(crypto.createHMAC(NULL, 0, UNKNOWN_HASH), UnsupportedAlgorithm); - - EXPECT_THROW(signHMAC(data_buf.getData(), data_buf.getLength(), - NULL, 0, MD5, hmac_sig), BadKey); - EXPECT_THROW(signHMAC(data_buf.getData(), data_buf.getLength(), - NULL, 0, UNKNOWN_HASH, hmac_sig), - UnsupportedAlgorithm); - - EXPECT_THROW(verifyHMAC(data_buf.getData(), data_buf.getLength(), - NULL, 0, MD5, hmac_sig.getData(), - hmac_sig.getLength()), BadKey); - EXPECT_THROW(verifyHMAC(data_buf.getData(), data_buf.getLength(), - NULL, 0, UNKNOWN_HASH, hmac_sig.getData(), - hmac_sig.getLength()), - UnsupportedAlgorithm); -} - +// Tests whether getCryptoLink() returns a singleton instance TEST(CryptoLinkTest, Singleton) { const CryptoLink& c1 = CryptoLink::getCryptoLink(); const CryptoLink& c2 = CryptoLink::getCryptoLink(); diff --git a/src/lib/cryptolink/tests/hash_unittests.cc b/src/lib/cryptolink/tests/hash_unittests.cc new file mode 100644 index 000000000..5185377e3 --- /dev/null +++ b/src/lib/cryptolink/tests/hash_unittests.cc @@ -0,0 +1,602 @@ +// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include + +#include +#include + +#include + +#include + +#include + +#include +#include + +#include +#include + +#include +#include + +using boost::lexical_cast; +using namespace isc::util; +using namespace isc::util::encode; +using namespace isc::cryptolink; + +namespace { + /// @brief Compare data with expected value + /// @param data Value to compare + /// @param expected Expected value + /// @param len Length of the expected value + void checkData(const uint8_t* data, const uint8_t* expected, + size_t len) { + for (size_t i = 0; i < len; ++i) { + ASSERT_EQ(expected[i], data[i]); + } + } + + /// @brief Compare OutputBuffer with expected value + /// encapsulated checkData() + /// @param buf buffer to compare + /// @param expected Expected value + /// @param len Length of the expected value + void checkBuffer(const OutputBuffer& buf, const uint8_t* expected, + size_t len) + { + ASSERT_EQ(len, buf.getLength()); + checkData(static_cast(buf.getData()), expected, + len); + } + + /// @brief Hash with the convenience functions + /// See @ref doHashTest for parameters + void doHashTestConv(const std::string& data, + const HashAlgorithm hash_algorithm, + const uint8_t* expected_hash, + size_t hash_len) { + OutputBuffer data_buf(data.size()); + data_buf.writeData(data.c_str(), data.size()); + OutputBuffer hash_digest(0); + + // Sign it + digest(data_buf.getData(), data_buf.getLength(), + hash_algorithm, hash_digest, hash_len); + + // Check if the signature is what we expect + checkBuffer(hash_digest, expected_hash, hash_len); + } + + /// @brief Hash with an instantiation of a Hash object + /// See @ref doHashTest for parameters + void doHashTestDirect(const std::string& data, + const HashAlgorithm hash_algorithm, + const uint8_t* expected_hash, + size_t hash_len) { + OutputBuffer data_buf(data.size()); + data_buf.writeData(data.c_str(), data.size()); + OutputBuffer result(1); + CryptoLink& crypto = CryptoLink::getCryptoLink(); + + // Do it + boost::shared_ptr hash_digest(crypto.createHash(hash_algorithm), + deleteHash); + hash_digest->update(data_buf.getData(), data_buf.getLength()); + hash_digest->final(result, hash_len); + + // Check if the digest is what we expect + checkBuffer(result, expected_hash, hash_len); + } + + /// @brief Hash with a vector representation + /// See @ref doHashTest for parameters + void doHashTestVector(const std::string& data, + const HashAlgorithm hash_algorithm, + const uint8_t* expected_hash, + size_t hash_len) { + CryptoLink& crypto = CryptoLink::getCryptoLink(); + boost::shared_ptr hash_digest(crypto.createHash(hash_algorithm), + deleteHash); + hash_digest->update(data.c_str(), data.size()); + std::vector result = hash_digest->final(hash_len); + ASSERT_EQ(hash_len, result.size()); + checkData(&result[0], expected_hash, hash_len); + } + + /// @brief Hash with an array representation + /// See @ref doHashTest for parameters + void doHashTestArray(const std::string& data, + const HashAlgorithm hash_algorithm, + const uint8_t* expected_hash, + size_t hash_len) { + CryptoLink& crypto = CryptoLink::getCryptoLink(); + boost::shared_ptr hash_digest(crypto.createHash(hash_algorithm), + deleteHash); + hash_digest->update(data.c_str(), data.size()); + + // note: this is not exception-safe, and can leak, but + // if there is an unexpected exception in the code below we + // have more important things to fix. + boost::scoped_array result(new uint8_t[hash_len]); + + hash_digest->final(result.get(), hash_len); + checkData(result.get(), expected_hash, hash_len); + } + + /// @brief Hash using all variants + /// @param data Input value + /// @param hash_algorithm Hash algorithm enum + /// @param expected_hash Expected value + /// @param hash_len Expected value length + void doHashTest(const std::string& data, + const HashAlgorithm hash_algorithm, + const uint8_t* expected_hash, + size_t hash_len) { + doHashTestConv(data, hash_algorithm, expected_hash, hash_len); + doHashTestDirect(data, hash_algorithm, expected_hash, hash_len); + doHashTestVector(data, hash_algorithm, expected_hash, hash_len); + doHashTestArray(data, hash_algorithm, expected_hash, hash_len); + } +} + +// +// Test values taken from RFC 1321 +// +TEST(HashTest, MD5_RFC1321) { + const uint8_t hash_expected[] = { 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, + 0x00, 0xb2, 0x04, 0xe9, 0x80, + 0x09, 0x98, 0xec, 0xf8, 0x42, + 0x7e }; + doHashTest("", MD5, hash_expected, 16); + + const uint8_t hash_expected2[] = { 0x0c, 0xc1, 0x75, 0xb9, 0xc0, + 0xf1, 0xb6, 0xa8, 0x31, 0xc3, + 0x99, 0xe2, 0x69, 0x77, 0x26, + 0x61 }; + doHashTest("a", MD5, hash_expected2, 16); + + const uint8_t hash_expected3[] = { 0x90, 0x01, 0x50, 0x98, 0x3c, + 0xd2, 0x4f, 0xb0, 0xd6, 0x96, + 0x3f, 0x7d, 0x28, 0xe1, 0x7f, + 0x72 }; + doHashTest("abc", MD5, hash_expected3, 16); + + const uint8_t hash_expected4[] = { 0xf9, 0x6b, 0x69, 0x7d, 0x7c, + 0xb7, 0x93, 0x8d, 0x52, 0x5a, + 0x2f, 0x31, 0xaa, 0xf1, 0x61, + 0xd0 }; + doHashTest("message digest", MD5, hash_expected4, 16); + + const uint8_t hash_expected6[] = { 0xc3, 0xfc, 0xd3, 0xd7, 0x61, + 0x92, 0xe4, 0x00, 0x7d, 0xfb, + 0x49, 0x6c, 0xca, 0x67, 0xe1, + 0x3b }; + doHashTest("abcdefghijklmnopqrstuvwxyz", MD5, hash_expected6, 16); + + const uint8_t hash_expected7[] = { 0xd1, 0x74, 0xab, 0x98, 0xd2, + 0x77, 0xd9, 0xf5, 0xa5, 0x61, + 0x1c, 0x2c, 0x9f, 0x41, 0x9d, + 0x9f }; + doHashTest("ABCDEFGHIJKLMNOPQRSTUVWXYZabcd" + "efghijklmnopqrstuvwxyz0123456789", + MD5, hash_expected7, 16); + + const uint8_t hash_expected8[] = { 0x57, 0xed, 0xf4, 0xa2, 0x2b, + 0xe3, 0xc9, 0x55, 0xac, 0x49, + 0xda, 0x2e, 0x21, 0x07, 0xb6, + 0x7a }; + doHashTest("1234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890", + MD5, hash_expected8, 16); +} + +// +// Test values taken from RFC 3174 +// +TEST(HashTest, SHA1_RFC3174) { + const uint8_t hash_expected[] = { 0xa9, 0x99, 0x3e, 0x36, 0x47, + 0x06, 0x81, 0x6a, 0xba, 0x3e, + 0x25, 0x71, 0x78, 0x50, 0xc2, + 0x6c, 0x9c, 0xd0, 0xd8, 0x9d }; + doHashTest("abc", SHA1, hash_expected, 20); + + const uint8_t hash_expected2[] = { 0x84, 0x98, 0x3e, 0x44, 0x1c, + 0x3b, 0xd2, 0x6e, 0xba, 0xae, + 0x4a, 0xa1, 0xf9, 0x51, 0x29, + 0xe5, 0xe5, 0x46, 0x70, 0xf1 }; + doHashTest("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + SHA1, hash_expected2, 20); + + const uint8_t hash_expected3[] = { 0x34, 0xaa, 0x97, 0x3c, 0xd4, + 0xc4, 0xda, 0xa4, 0xf6, 0x1e, + 0xeb, 0x2b, 0xdb, 0xad, 0x27, + 0x31, 0x65, 0x34, 0x01, 0x6f }; + doHashTest(std::string(1000000, 0x61), SHA1, hash_expected3, 20); + + const uint8_t hash_expected4[] = { 0xde, 0xa3, 0x56, 0xa2, 0xcd, + 0xdd, 0x90, 0xc7, 0xa7, 0xec, + 0xed, 0xc5, 0xeb, 0xb5, 0x63, + 0x93, 0x4f, 0x46, 0x04, 0x52 }; + doHashTest("01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567", + SHA1, hash_expected4, 20); +} + +// +// Test values taken from RFC 6234 +// +TEST(HashTest, SHA224_RFC6234) { + const uint8_t hash_expected[] = { 0x23, 0x09, 0x7d, 0x22, 0x34, + 0x05, 0xd8, 0x22, 0x86, 0x42, + 0xa4, 0x77, 0xbd, 0xa2, 0x55, + 0xb3, 0x2a, 0xad, 0xbc, 0xe4, + 0xbd, 0xa0, 0xb3, 0xf7, 0xe3, + 0x6c, 0x9d, 0xa7 }; + doHashTest("abc", SHA224, hash_expected, 28); + + const uint8_t hash_expected2[] = { 0x75, 0x38, 0x8b, 0x16, 0x51, + 0x27, 0x76, 0xcc, 0x5d, 0xba, + 0x5d, 0xa1, 0xfd, 0x89, 0x01, + 0x50, 0xb0, 0xc6, 0x45, 0x5c, + 0xb4, 0xf5, 0x8b, 0x19, 0x52, + 0x52, 0x25, 0x25 }; + doHashTest("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + SHA224, hash_expected2, 28); + + const uint8_t hash_expected3[] = { 0x20, 0x79, 0x46, 0x55, 0x98, + 0x0c, 0x91, 0xd8, 0xbb, 0xb4, + 0xc1, 0xea, 0x97, 0x61, 0x8a, + 0x4b, 0xf0, 0x3f, 0x42, 0x58, + 0x19, 0x48, 0xb2, 0xee, 0x4e, + 0xe7, 0xad, 0x67 }; + doHashTest(std::string(1000000, 0x61), SHA224, hash_expected3, 28); + + const uint8_t hash_expected4[] = { 0x56, 0x7f, 0x69, 0xf1, 0x68, + 0xcd, 0x78, 0x44, 0xe6, 0x52, + 0x59, 0xce, 0x65, 0x8f, 0xe7, + 0xaa, 0xdf, 0xa2, 0x52, 0x16, + 0xe6, 0x8e, 0xca, 0x0e, 0xb7, + 0xab, 0x82, 0x62 }; + doHashTest("01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567", + SHA224, hash_expected4, 28); +} + +TEST(HashTest, SHA256_RFC6234) { + const uint8_t hash_expected[] = { 0xba, 0x78, 0x16, 0xbf, 0x8f, + 0x01, 0xcf, 0xea, 0x41, 0x41, + 0x40, 0xde, 0x5d, 0xae, 0x22, + 0x23, 0xb0, 0x03, 0x61, 0xa3, + 0x96, 0x17, 0x7a, 0x9c, 0xb4, + 0x10, 0xff, 0x61, 0xf2, 0x00, + 0x15, 0xad }; + doHashTest("abc", SHA256, hash_expected, 32); + + const uint8_t hash_expected2[] = { 0x24, 0x8d, 0x6a, 0x61, 0xd2, + 0x06, 0x38, 0xb8, 0xe5, 0xc0, + 0x26, 0x93, 0x0c, 0x3e, 0x60, + 0x39, 0xa3, 0x3c, 0xe4, 0x59, + 0x64, 0xff, 0x21, 0x67, 0xf6, + 0xec, 0xed, 0xd4, 0x19, 0xdb, + 0x06, 0xc1 }; + doHashTest("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + SHA256, hash_expected2, 32); + + const uint8_t hash_expected3[] = { 0xcd, 0xc7, 0x6e, 0x5c, 0x99, + 0x14, 0xfb, 0x92, 0x81, 0xa1, + 0xc7, 0xe2, 0x84, 0xd7, 0x3e, + 0x67, 0xf1, 0x80, 0x9a, 0x48, + 0xa4, 0x97, 0x20, 0x0e, 0x04, + 0x6d, 0x39, 0xcc, 0xc7, 0x11, + 0x2c, 0xd0 }; + doHashTest(std::string(1000000, 0x61), SHA256, hash_expected3, 32); + + const uint8_t hash_expected4[] = { 0x59, 0x48, 0x47, 0x32, 0x84, + 0x51, 0xbd, 0xfa, 0x85, 0x05, + 0x62, 0x25, 0x46, 0x2c, 0xc1, + 0xd8, 0x67, 0xd8, 0x77, 0xfb, + 0x38, 0x8d, 0xf0, 0xce, 0x35, + 0xf2, 0x5a, 0xb5, 0x56, 0x2b, + 0xfb, 0xb5 }; + doHashTest("01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567", + SHA256, hash_expected4, 32); +} + +TEST(HashTest, SHA384_RFC6234) { + const uint8_t hash_expected[] = { 0xcb, 0x00, 0x75, 0x3f, 0x45, + 0xa3, 0x5e, 0x8b, 0xb5, 0xa0, + 0x3d, 0x69, 0x9a, 0xc6, 0x50, + 0x07, 0x27, 0x2c, 0x32, 0xab, + 0x0e, 0xde, 0xd1, 0x63, 0x1a, + 0x8b, 0x60, 0x5a, 0x43, 0xff, + 0x5b, 0xed, 0x80, 0x86, 0x07, + 0x2b, 0xa1, 0xe7, 0xcc, 0x23, + 0x58, 0xba, 0xec, 0xa1, 0x34, + 0xc8, 0x25, 0xa7 }; + doHashTest("abc", SHA384, hash_expected, 48); + + const uint8_t hash_expected2[] = { 0x09, 0x33, 0x0c, 0x33, 0xf7, + 0x11, 0x47, 0xe8, 0x3d, 0x19, + 0x2f, 0xc7, 0x82, 0xcd, 0x1b, + 0x47, 0x53, 0x11, 0x1b, 0x17, + 0x3b, 0x3b, 0x05, 0xd2, 0x2f, + 0xa0, 0x80, 0x86, 0xe3, 0xb0, + 0xf7, 0x12, 0xfc, 0xc7, 0xc7, + 0x1a, 0x55, 0x7e, 0x2d, 0xb9, + 0x66, 0xc3, 0xe9, 0xfa, 0x91, + 0x74, 0x60, 0x39 }; + doHashTest("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn" + "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", + SHA384, hash_expected2, 48); + + const uint8_t hash_expected3[] = { 0x9d, 0x0e, 0x18, 0x09, 0x71, + 0x64, 0x74, 0xcb, 0x08, 0x6e, + 0x83, 0x4e, 0x31, 0x0a, 0x4a, + 0x1c, 0xed, 0x14, 0x9e, 0x9c, + 0x00, 0xf2, 0x48, 0x52, 0x79, + 0x72, 0xce, 0xc5, 0x70, 0x4c, + 0x2a, 0x5b, 0x07, 0xb8, 0xb3, + 0xdc, 0x38, 0xec, 0xc4, 0xeb, + 0xae, 0x97, 0xdd, 0xd8, 0x7f, + 0x3d, 0x89, 0x85 }; + doHashTest(std::string(1000000, 0x61), SHA384, hash_expected3, 48); + + const uint8_t hash_expected4[] = { 0x2f, 0xc6, 0x4a, 0x4f, 0x50, + 0x0d, 0xdb, 0x68, 0x28, 0xf6, + 0xa3, 0x43, 0x0b, 0x8d, 0xd7, + 0x2a, 0x36, 0x8e, 0xb7, 0xf3, + 0xa8, 0x32, 0x2a, 0x70, 0xbc, + 0x84, 0x27, 0x5b, 0x9c, 0x0b, + 0x3a, 0xb0, 0x0d, 0x27, 0xa5, + 0xcc, 0x3c, 0x2d, 0x22, 0x4a, + 0xa6, 0xb6, 0x1a, 0x0d, 0x79, + 0xfb, 0x45, 0x96 }; + doHashTest("01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567", + SHA384, hash_expected4, 48); +} + +TEST(HashTest, SHA512_RFC6234) { + const uint8_t hash_expected[] = { 0xdd, 0xaf, 0x35, 0xa1, 0x93, + 0x61, 0x7a, 0xba, 0xcc, 0x41, + 0x73, 0x49, 0xae, 0x20, 0x41, + 0x31, 0x12, 0xe6, 0xfa, 0x4e, + 0x89, 0xa9, 0x7e, 0xa2, 0x0a, + 0x9e, 0xee, 0xe6, 0x4b, 0x55, + 0xd3, 0x9a, 0x21, 0x92, 0x99, + 0x2a, 0x27, 0x4f, 0xc1, 0xa8, + 0x36, 0xba, 0x3c, 0x23, 0xa3, + 0xfe, 0xeb, 0xbd, 0x45, 0x4d, + 0x44, 0x23, 0x64, 0x3c, 0xe8, + 0x0e, 0x2a, 0x9a, 0xc9, 0x4f, + 0xa5, 0x4c, 0xa4, 0x9f }; + doHashTest("abc", SHA512, hash_expected, 64); + + const uint8_t hash_expected2[] = { 0x8e, 0x95, 0x9b, 0x75, 0xda, + 0xe3, 0x13, 0xda, 0x8c, 0xf4, + 0xf7, 0x28, 0x14, 0xfc, 0x14, + 0x3f, 0x8f, 0x77, 0x79, 0xc6, + 0xeb, 0x9f, 0x7f, 0xa1, 0x72, + 0x99, 0xae, 0xad, 0xb6, 0x88, + 0x90, 0x18, 0x50, 0x1d, 0x28, + 0x9e, 0x49, 0x00, 0xf7, 0xe4, + 0x33, 0x1b, 0x99, 0xde, 0xc4, + 0xb5, 0x43, 0x3a, 0xc7, 0xd3, + 0x29, 0xee, 0xb6, 0xdd, 0x26, + 0x54, 0x5e, 0x96, 0xe5, 0x5b, + 0x87, 0x4b, 0xe9, 0x09 }; + doHashTest("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn" + "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", + SHA512, hash_expected2, 64); + + const uint8_t hash_expected3[] = { 0xe7, 0x18, 0x48, 0x3d, 0x0c, + 0xe7, 0x69, 0x64, 0x4e, 0x2e, + 0x42, 0xc7, 0xbc, 0x15, 0xb4, + 0x63, 0x8e, 0x1f, 0x98, 0xb1, + 0x3b, 0x20, 0x44, 0x28, 0x56, + 0x32, 0xa8, 0x03, 0xaf, 0xa9, + 0x73, 0xeb, 0xde, 0x0f, 0xf2, + 0x44, 0x87, 0x7e, 0xa6, 0x0a, + 0x4c, 0xb0, 0x43, 0x2c, 0xe5, + 0x77, 0xc3, 0x1b, 0xeb, 0x00, + 0x9c, 0x5c, 0x2c, 0x49, 0xaa, + 0x2e, 0x4e, 0xad, 0xb2, 0x17, + 0xad, 0x8c, 0xc0, 0x9b }; + doHashTest(std::string(1000000, 0x61), SHA512, hash_expected3, 64); + + const uint8_t hash_expected4[] = { 0x89, 0xd0, 0x5b, 0xa6, 0x32, + 0xc6, 0x99, 0xc3, 0x12, 0x31, + 0xde, 0xd4, 0xff, 0xc1, 0x27, + 0xd5, 0xa8, 0x94, 0xda, 0xd4, + 0x12, 0xc0, 0xe0, 0x24, 0xdb, + 0x87, 0x2d, 0x1a, 0xbd, 0x2b, + 0xa8, 0x14, 0x1a, 0x0f, 0x85, + 0x07, 0x2a, 0x9b, 0xe1, 0xe2, + 0xaa, 0x04, 0xcf, 0x33, 0xc7, + 0x65, 0xcb, 0x51, 0x08, 0x13, + 0xa3, 0x9c, 0xd5, 0xa8, 0x4c, + 0x4a, 0xca, 0xa6, 0x4d, 0x3f, + 0x3f, 0xb7, 0xba, 0xe9 }; + doHashTest("01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567" + "01234567012345670123456701234567", + SHA512, hash_expected4, 64); +} + +namespace { + /// @brief Compute the vector digest length + /// @param alg Hash algorithm enum + /// @param len Wanted length + /// @return Effective length + size_t + digestVectorLength(HashAlgorithm alg, size_t len) { + boost::shared_ptr hash_digest( + CryptoLink::getCryptoLink().createHash(alg), + deleteHash); + hash_digest->update("asdf", 4); + const std::vector result = hash_digest->final(len); + return (result.size()); + } + + /// @brief Compute the buffer digest length + /// @param alg Hash algorithm enum + /// @param len Wanted length + /// @return Effective length + size_t + digestBufferLength(HashAlgorithm alg, size_t len) { + boost::shared_ptr hash_digest( + CryptoLink::getCryptoLink().createHash(alg), + deleteHash); + hash_digest->update("asdf", 4); + OutputBuffer result(0); + hash_digest->final(result, len); + return (result.getLength()); + } + // There is no equivalent for array digest because it is copied + // in place +} + +TEST(HashTest, HashLength) { + std::vector result; + + EXPECT_EQ(16, digestVectorLength(MD5, 0)); + EXPECT_EQ(8, digestVectorLength(MD5, 8)); + EXPECT_EQ(16, digestVectorLength(MD5, 16)); + EXPECT_EQ(16, digestVectorLength(MD5, 40)); + EXPECT_EQ(16, digestVectorLength(MD5, 2000)); + + EXPECT_EQ(20, digestBufferLength(SHA1, 0)); + EXPECT_EQ(8, digestBufferLength(SHA1, 8)); + EXPECT_EQ(20, digestBufferLength(SHA1, 20)); + EXPECT_EQ(20, digestBufferLength(SHA1, 40)); + EXPECT_EQ(20, digestBufferLength(SHA1, 2000)); + + EXPECT_EQ(32, digestBufferLength(SHA256, 0)); + EXPECT_EQ(8, digestBufferLength(SHA256, 8)); + EXPECT_EQ(32, digestBufferLength(SHA256, 32)); + EXPECT_EQ(32, digestBufferLength(SHA256, 40)); + EXPECT_EQ(32, digestBufferLength(SHA256, 3200)); + + EXPECT_EQ(16, digestBufferLength(MD5, 0)); + EXPECT_EQ(8, digestBufferLength(MD5, 8)); + EXPECT_EQ(16, digestBufferLength(MD5, 16)); + EXPECT_EQ(16, digestBufferLength(MD5, 40)); + EXPECT_EQ(16, digestBufferLength(MD5, 2000)); + + EXPECT_EQ(20, digestBufferLength(SHA1, 0)); + EXPECT_EQ(8, digestBufferLength(SHA1, 8)); + EXPECT_EQ(20, digestBufferLength(SHA1, 20)); + EXPECT_EQ(20, digestBufferLength(SHA1, 40)); + EXPECT_EQ(20, digestBufferLength(SHA1, 2000)); + + EXPECT_EQ(32, digestBufferLength(SHA256, 0)); + EXPECT_EQ(8, digestBufferLength(SHA256, 8)); + EXPECT_EQ(32, digestBufferLength(SHA256, 32)); + EXPECT_EQ(32, digestBufferLength(SHA256, 40)); + EXPECT_EQ(32, digestBufferLength(SHA256, 3200)); +} + +// @todo Error cases? diff --git a/src/lib/cryptolink/tests/hmac_unittests.cc b/src/lib/cryptolink/tests/hmac_unittests.cc new file mode 100644 index 000000000..aef63df3d --- /dev/null +++ b/src/lib/cryptolink/tests/hmac_unittests.cc @@ -0,0 +1,676 @@ +// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include + +#include +#include + +#include + +#include + +#include + +#include +#include + +#include +#include + +#include + +using boost::lexical_cast; +using namespace isc::util; +using namespace isc::util::encode; +using namespace isc::cryptolink; + +namespace { + /// @brief Compare data with expected value + /// @param data Value to compare + /// @param expected Expected value + /// @param len Length of the expected value + void checkData(const uint8_t* data, const uint8_t* expected, + size_t len) { + for (size_t i = 0; i < len; ++i) { + ASSERT_EQ(expected[i], data[i]); + } + } + + /// @brief Compare OutputBuffer with expected value + /// encapsulated checkData() + /// @param buf buffer to compare + /// @param expected Expected value + /// @param len Length of the expected value + void checkBuffer(const OutputBuffer& buf, const uint8_t* expected, + size_t len) + { + ASSERT_EQ(len, buf.getLength()); + checkData(static_cast(buf.getData()), expected, + len); + } + + /// @brief Sign and verify with the convenience functions + /// See @ref doHMACTest for parameters + void doHMACTestConv(const std::string& data, + const void* secret, + size_t secret_len, + const HashAlgorithm hash_algorithm, + const uint8_t* expected_hmac, + size_t hmac_len) { + OutputBuffer data_buf(data.size()); + data_buf.writeData(data.c_str(), data.size()); + OutputBuffer hmac_sig(0); + + // Sign it + signHMAC(data_buf.getData(), data_buf.getLength(), + secret, secret_len, hash_algorithm, hmac_sig, hmac_len); + + // Check if the signature is what we expect + checkBuffer(hmac_sig, expected_hmac, hmac_len); + + // Check whether we can verify it ourselves + EXPECT_TRUE(verifyHMAC(data_buf.getData(), data_buf.getLength(), + secret, secret_len, hash_algorithm, + hmac_sig.getData(), + hmac_sig.getLength())); + + // Change the sig by flipping the first octet, and check + // whether verification fails then + hmac_sig.writeUint8At(~hmac_sig[0], 0); + EXPECT_FALSE(verifyHMAC(data_buf.getData(), data_buf.getLength(), + secret, secret_len, hash_algorithm, + hmac_sig.getData(), + hmac_sig.getLength())); + } + + /// @brief Sign and verify with an instantiation of an HMAC object + /// See @ref doHMACTest for parameters + void doHMACTestDirect(const std::string& data, + const void* secret, + size_t secret_len, + const HashAlgorithm hash_algorithm, + const uint8_t* expected_hmac, + size_t hmac_len) { + OutputBuffer data_buf(data.size()); + data_buf.writeData(data.c_str(), data.size()); + OutputBuffer hmac_sig(1); + CryptoLink& crypto = CryptoLink::getCryptoLink(); + + // Sign it + boost::shared_ptr hmac_sign(crypto.createHMAC(secret, + secret_len, + hash_algorithm), + deleteHMAC); + hmac_sign->update(data_buf.getData(), data_buf.getLength()); + hmac_sign->sign(hmac_sig, hmac_len); + + // Check if the signature is what we expect + checkBuffer(hmac_sig, expected_hmac, hmac_len); + + // Check whether we can verify it ourselves + boost::shared_ptr hmac_verify(crypto.createHMAC(secret, + secret_len, + hash_algorithm), + deleteHMAC); + hmac_verify->update(data_buf.getData(), data_buf.getLength()); + EXPECT_TRUE(hmac_verify->verify(hmac_sig.getData(), + hmac_sig.getLength())); + + // Change the sig by flipping the first octet, and check + // whether verification fails then + hmac_sig.writeUint8At(~hmac_sig[0], 0); + EXPECT_FALSE(hmac_verify->verify(hmac_sig.getData(), + hmac_sig.getLength())); + } + + /// @brief Sign and verify with vector representation of signature + /// See @ref doHMACTest for parameters + void doHMACTestVector(const std::string& data, + const void* secret, + size_t secret_len, + const HashAlgorithm hash_algorithm, + const uint8_t* expected_hmac, + size_t hmac_len) { + CryptoLink& crypto = CryptoLink::getCryptoLink(); + boost::shared_ptr hmac_sign(crypto.createHMAC(secret, + secret_len, + hash_algorithm), + deleteHMAC); + hmac_sign->update(data.c_str(), data.size()); + std::vector sig = hmac_sign->sign(hmac_len); + ASSERT_EQ(hmac_len, sig.size()); + checkData(&sig[0], expected_hmac, hmac_len); + + boost::shared_ptr hmac_verify(crypto.createHMAC(secret, + secret_len, + hash_algorithm), + deleteHMAC); + hmac_verify->update(data.c_str(), data.size()); + EXPECT_TRUE(hmac_verify->verify(&sig[0], sig.size())); + + sig[0] = ~sig[0]; + EXPECT_FALSE(hmac_verify->verify(&sig[0], sig.size())); + } + + /// @brief Sign and verify with array representation of signature + /// See @ref doHMACTest for parameters + void doHMACTestArray(const std::string& data, + const void* secret, + size_t secret_len, + const HashAlgorithm hash_algorithm, + const uint8_t* expected_hmac, + size_t hmac_len) { + CryptoLink& crypto = CryptoLink::getCryptoLink(); + boost::shared_ptr hmac_sign(crypto.createHMAC(secret, + secret_len, + hash_algorithm), + deleteHMAC); + hmac_sign->update(data.c_str(), data.size()); + + // note: this is not exception-safe, and can leak, but + // if there is an unexpected exception in the code below we + // have more important things to fix. + uint8_t* sig = new uint8_t[hmac_len]; + + hmac_sign->sign(sig, hmac_len); + checkData(sig, expected_hmac, hmac_len); + + boost::shared_ptr hmac_verify(crypto.createHMAC(secret, + secret_len, + hash_algorithm), + deleteHMAC); + hmac_verify->update(data.c_str(), data.size()); + EXPECT_TRUE(hmac_verify->verify(sig, hmac_len)); + + sig[0] = ~sig[0]; + EXPECT_FALSE(hmac_verify->verify(sig, hmac_len)); + + delete[] sig; + } + + /// @brief Sign and verify using all variants + /// @param data Input value + /// @param secret Secret value + /// @param secret_len Secret value length + /// @param hash_algorithm Hash algorithm enum + /// @param expected_hmac Expected value + /// @param hmac_len Expected value length + void doHMACTest(const std::string& data, + const void* secret, + size_t secret_len, + const HashAlgorithm hash_algorithm, + const uint8_t* expected_hmac, + size_t hmac_len) { + doHMACTestConv(data, secret, secret_len, hash_algorithm, + expected_hmac, hmac_len); + doHMACTestDirect(data, secret, secret_len, hash_algorithm, + expected_hmac, hmac_len); + doHMACTestVector(data, secret, secret_len, hash_algorithm, + expected_hmac, hmac_len); + doHMACTestArray(data, secret, secret_len, hash_algorithm, + expected_hmac, hmac_len); + } +} + +// +// Test values taken from RFC 2202 +// +TEST(HMACTest, HMAC_MD5_RFC2202_SIGN) { + const uint8_t secret[] = { 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b }; + const uint8_t hmac_expected[] = { 0x92, 0x94, 0x72, 0x7a, 0x36, + 0x38, 0xbb, 0x1c, 0x13, 0xf4, + 0x8e, 0xf8, 0x15, 0x8b, 0xfc, + 0x9d }; + doHMACTest("Hi There", secret, 16, MD5, hmac_expected, 16); + + const uint8_t hmac_expected2[] = { 0x75, 0x0c, 0x78, 0x3e, 0x6a, + 0xb0, 0xb5, 0x03, 0xea, 0xa8, + 0x6e, 0x31, 0x0a, 0x5d, 0xb7, + 0x38 }; + doHMACTest("what do ya want for nothing?", "Jefe", 4, MD5, + hmac_expected2, 16); + + const uint8_t secret3[] = { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa }; + const uint8_t hmac_expected3[] = { 0x56, 0xbe, 0x34, 0x52, 0x1d, + 0x14, 0x4c, 0x88, 0xdb, 0xb8, + 0xc7, 0x33, 0xf0, 0xe8, 0xb3, + 0xf6}; + doHMACTest(std::string(50, 0xdd), secret3, 16, MD5, hmac_expected3, 16); + + const std::string data4(50, 0xcd); + const uint8_t secret4[] = { 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 }; + const uint8_t hmac_expected4[] = { 0x69, 0x7e, 0xaf, 0x0a, 0xca, + 0x3a, 0x3a, 0xea, 0x3a, 0x75, + 0x16, 0x47, 0x46, 0xff, 0xaa, + 0x79 }; + doHMACTest(data4, secret4, 25, MD5, hmac_expected4, 16); + + const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c }; + const uint8_t hmac_expected5[] = { 0x56, 0x46, 0x1e, 0xf2, 0x34, + 0x2e, 0xdc, 0x00, 0xf9, 0xba, + 0xb9, 0x95, 0x69, 0x0e, 0xfd, + 0x4c }; + doHMACTest("Test With Truncation", secret5, 16, MD5, + hmac_expected5, 16); +#ifndef WITH_BOTAN + doHMACTest("Test With Truncation", secret5, 16, MD5, + hmac_expected5, 12); +#endif + + const uint8_t hmac_expected6[] = { 0x6b, 0x1a, 0xb7, 0xfe, 0x4b, + 0xd7, 0xbf, 0x8f, 0x0b, 0x62, + 0xe6, 0xce, 0x61, 0xb9, 0xd0, + 0xcd }; + doHMACTest("Test Using Larger Than Block-Size Key - Hash Key First", + std::string(80, 0xaa).c_str(), 80, MD5, hmac_expected6, 16); + + const uint8_t hmac_expected7[] = { 0x6f, 0x63, 0x0f, 0xad, 0x67, + 0xcd, 0xa0, 0xee, 0x1f, 0xb1, + 0xf5, 0x62, 0xdb, 0x3a, 0xa5, + 0x3e }; + doHMACTest("Test Using Larger Than Block-Size Key and Larger Than " + "One Block-Size Data", + std::string(80, 0xaa).c_str(), 80, MD5, hmac_expected7, 16); +} + +// Temporarily disabled +TEST(HMACTest, HMAC_MD5_RFC2202_SIGN_TRUNCATED) { + const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c }; + const uint8_t hmac_expected5[] = { 0x56, 0x46, 0x1e, 0xf2, 0x34, + 0x2e, 0xdc, 0x00, 0xf9, 0xba, + 0xb9, 0x95, 0x69, 0x0e, 0xfd, + 0x4c }; + doHMACTest("Test With Truncation", secret5, 16, MD5, + hmac_expected5, 16); +#ifndef WITH_BOTAN + doHMACTest("Test With Truncation", secret5, 16, MD5, + hmac_expected5, 12); +#endif +} + +// +// Test values taken from RFC 2202 +// +TEST(HMACTest, HMAC_SHA1_RFC2202_SIGN) { + const uint8_t secret[] = { 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b }; + const uint8_t hmac_expected[] = { 0xb6, 0x17, 0x31, 0x86, 0x55, + 0x05, 0x72, 0x64, 0xe2, 0x8b, + 0xc0, 0xb6, 0xfb, 0x37, 0x8c, + 0x8e, 0xf1, 0x46, 0xbe, 0x00 }; + doHMACTest("Hi There", secret, 20, SHA1, hmac_expected, 20); + + const uint8_t hmac_expected2[] = { 0xef, 0xfc, 0xdf, 0x6a, 0xe5, + 0xeb, 0x2f, 0xa2, 0xd2, 0x74, + 0x16, 0xd5, 0xf1, 0x84, 0xdf, + 0x9c, 0x25, 0x9a, 0x7c, 0x79 }; + doHMACTest("what do ya want for nothing?", "Jefe", 4, SHA1, + hmac_expected2, 20); + + const uint8_t secret3[] = { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa }; + const uint8_t hmac_expected3[] = { 0x12, 0x5d, 0x73, 0x42, 0xb9, + 0xac, 0x11, 0xcd, 0x91, 0xa3, + 0x9a, 0xf4, 0x8a, 0xa1, 0x7b, + 0x4f, 0x63, 0xf1, 0x75, 0xd3 }; + doHMACTest(std::string(50, 0xdd), secret3, 20, SHA1, hmac_expected3, 20); + + const uint8_t secret4[] = { 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 }; + const uint8_t hmac_expected4[] = { 0x4c, 0x90, 0x07, 0xf4, 0x02, + 0x62, 0x50, 0xc6, 0xbc, 0x84, + 0x14, 0xf9, 0xbf, 0x50, 0xc8, + 0x6c, 0x2d, 0x72, 0x35, 0xda }; + doHMACTest(std::string(50, 0xcd), secret4, 25, SHA1, hmac_expected4, 20); + + const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c }; + const uint8_t hmac_expected5[] = { 0x4c, 0x1a, 0x03, 0x42, 0x4b, + 0x55, 0xe0, 0x7f, 0xe7, 0xf2, + 0x7b, 0xe1, 0xd5, 0x8b, 0xb9, + 0x32, 0x4a, 0x9a, 0x5a, 0x04 }; + doHMACTest("Test With Truncation", secret5, 20, SHA1, + hmac_expected5, 20); +#ifndef WITH_BOTAN + doHMACTest("Test With Truncation", secret5, 20, SHA1, + hmac_expected5, 12); +#endif + + const uint8_t hmac_expected6[] = { 0xaa, 0x4a, 0xe5, 0xe1, 0x52, + 0x72, 0xd0, 0x0e, 0x95, 0x70, + 0x56, 0x37, 0xce, 0x8a, 0x3b, + 0x55, 0xed, 0x40, 0x21, 0x12 }; + doHMACTest("Test Using Larger Than Block-Size Key - Hash Key First", + std::string(80, 0xaa).c_str(), 80, SHA1, hmac_expected6, 20); + + const uint8_t hmac_expected7[] = { 0xe8, 0xe9, 0x9d, 0x0f, 0x45, + 0x23, 0x7d, 0x78, 0x6d, 0x6b, + 0xba, 0xa7, 0x96, 0x5c, 0x78, + 0x08, 0xbb, 0xff, 0x1a, 0x91 }; + doHMACTest("Test Using Larger Than Block-Size Key and Larger Than " + "One Block-Size Data", + std::string(80, 0xaa).c_str(), 80, SHA1, hmac_expected7, 20); +} + +// Temporarily disabled +TEST(HMACTest, HMAC_SHA1_RFC2202_SIGN_TRUNCATED) { + const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c }; + const uint8_t hmac_expected5[] = { 0x4c, 0x1a, 0x03, 0x42, 0x4b, + 0x55, 0xe0, 0x7f, 0xe7, 0xf2, + 0x7b, 0xe1, 0xd5, 0x8b, 0xb9, + 0x32, 0x4a, 0x9a, 0x5a, 0x04 }; + doHMACTest("Test With Truncation", secret5, 20, SHA1, + hmac_expected5, 20); +#ifndef WITH_BOTAN + doHMACTest("Test With Truncation", secret5, 20, SHA1, + hmac_expected5, 12); +#endif +} + +// +// Test values taken from RFC 4231 +// +// Test data from RFC4231, including secret key +// and source data, they are common for sha224/256/384/512 +// so put them together within the separate function. +void +doRFC4231Tests(HashAlgorithm hash_algorithm, + const std::vector >& hmac_list) +{ + std::vector data_list; + std::vector secret_list; + + data_list.push_back("Hi There"); + data_list.push_back("what do ya want for nothing?"); + data_list.push_back(std::string(50, 0xdd)); + data_list.push_back(std::string(50, 0xcd)); + data_list.push_back("Test With Truncation"); + data_list.push_back("Test Using Larger Than Block-Size Key - " + "Hash Key First"); + data_list.push_back("This is a test using a larger than block-size " + "key and a larger than block-size data. The key " + "needs to be hashed before being used by the HMAC " + "algorithm."); + + secret_list.push_back(std::string(20, 0x0b)); + secret_list.push_back("Jefe"); + secret_list.push_back(std::string(20, 0xaa)); + const uint8_t secret_array[] = { + 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 + }; + secret_list.push_back(std::string(secret_array, + secret_array + sizeof(secret_array))); + secret_list.push_back(std::string(20, 0x0c)); + secret_list.push_back(std::string(131, 0xaa)); + secret_list.push_back(std::string(131, 0xaa)); + + // Make sure we provide a consistent size of test data + ASSERT_EQ(secret_list.size(), data_list.size()); + ASSERT_EQ(secret_list.size(), hmac_list.size()); + + for (std::vector::size_type i = 0; + i < data_list.size(); ++i) { + SCOPED_TRACE("RFC4231 HMAC test for algorithm ID: " + + lexical_cast(hash_algorithm) + + ", data ID: " + lexical_cast(i)); + // Until #920 is resolved we have to skip truncation cases. + if (data_list[i] == "Test With Truncation") { + continue; + } + doHMACTest(data_list[i], secret_list[i].c_str(), secret_list[i].size(), + hash_algorithm, &hmac_list[i][0], hmac_list[i].size()); + } +} + +TEST(HMACTest, HMAC_SHA256_RFC4231_SIGN) { + std::vector > hmac_expected_list(7); + + int i = 0; + decodeHex( + "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7", + hmac_expected_list[i++]); + decodeHex( + "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843", + hmac_expected_list[i++]); + decodeHex( + "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe", + hmac_expected_list[i++]); + decodeHex( + "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b", + hmac_expected_list[i++]); + decodeHex("a3b6167473100ee06e0c796c2955552b", hmac_expected_list[i++]); + decodeHex( + "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54", + hmac_expected_list[i++]); + decodeHex( + "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2", + hmac_expected_list[i++]); + + doRFC4231Tests(SHA256, hmac_expected_list); +} + +// +// Test values taken from RFC 4231, test optional algorithm 224,384,512 +// +TEST(HMACTest, HMAC_SHA224_RFC4231_SIGN) { + std::vector > hmac_expected_list(7); + + int i = 0; + decodeHex("896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22", + hmac_expected_list[i++]); + decodeHex("a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44", + hmac_expected_list[i++]); + decodeHex("7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea", + hmac_expected_list[i++]); + decodeHex("6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a", + hmac_expected_list[i++]); + decodeHex("0e2aea68a90c8d37c988bcdb9fca6fa8", hmac_expected_list[i++]); + decodeHex("95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e", + hmac_expected_list[i++]); + decodeHex("3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1", + hmac_expected_list[i++]); + + doRFC4231Tests(SHA224, hmac_expected_list); +} + +TEST(HMACTest, HMAC_SHA384_RFC4231_SIGN) { + std::vector > hmac_expected_list(7); + + int i = 0; + decodeHex("afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc5" + "9cfaea9ea9076ede7f4af152e8b2fa9cb6", hmac_expected_list[i++]); + decodeHex("af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec373632244" + "5e8e2240ca5e69e2c78b3239ecfab21649", hmac_expected_list[i++]); + decodeHex("88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e5596614" + "4b2a5ab39dc13814b94e3ab6e101a34f27", hmac_expected_list[i++]); + decodeHex("3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b" + "4e6801dd23c4a7d679ccf8a386c674cffb", hmac_expected_list[i++]); + decodeHex("3abf34c3503b2a23a46efc619baef897", hmac_expected_list[i++]); + decodeHex("4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4" + "c60c2ef6ab4030fe8296248df163f44952", hmac_expected_list[i++]); + decodeHex("6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99" + "c5a678cc31e799176d3860e6110c46523e", hmac_expected_list[i++]); + + doRFC4231Tests(SHA384, hmac_expected_list); +} + +TEST(HMACTest, HMAC_SHA512_RFC4231_SIGN) { + std::vector > hmac_expected_list(7); + + int i = 0; + decodeHex("87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17c" + "dedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a12" + "6854", hmac_expected_list[i++]); + decodeHex("164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505" + "549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bc" + "e737", hmac_expected_list[i++]); + decodeHex("fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d" + "39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e132" + "92fb", hmac_expected_list[i++]); + decodeHex("b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3" + "dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a2" + "98dd", hmac_expected_list[i++]); + decodeHex("415fad6271580a531d4179bc891d87a6", hmac_expected_list[i++]); + decodeHex("80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3" + "526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d78" + "6598", hmac_expected_list[i++]); + decodeHex("e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc9" + "44b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c" + "6a58", hmac_expected_list[i++]); + + doRFC4231Tests(SHA512, hmac_expected_list); +} + +#ifndef WITH_BOTAN +TEST(HMACTest, HMAC_SHA256_RFC2202_SIGN_TRUNCATED) { +#else +TEST(HMACTest, DISABLED_HMAC_SHA256_RFC2202_SIGN_TRUNCATED) { +#endif + const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c }; + const uint8_t hmac_expected5[] = { 0xa3, 0xb6, 0x16, 0x74, 0x73, + 0x10, 0x0e, 0xe0, 0x6e, 0x0c, + 0x79, 0x6c, 0x29, 0x55, 0x55, + 0x2b }; + doHMACTest("Test With Truncation", secret5, 20, SHA256, + hmac_expected5, 16); +} + +namespace { + /// @brief Compute the vector signature length + /// @param alg Hash algorithm enum + /// @param len Wanted length + /// @return Effective length + size_t + sigVectorLength(HashAlgorithm alg, size_t len) { + boost::shared_ptr hmac_sign( + CryptoLink::getCryptoLink().createHMAC("asdf", 4, alg), + deleteHMAC); + hmac_sign->update("asdf", 4); + const std::vector sig = hmac_sign->sign(len); + return (sig.size()); + } + + /// @brief Compute the buffer signature length + /// @param alg Hash algorithm enum + /// @param len Wanted length + /// @return Effective length + size_t + sigBufferLength(HashAlgorithm alg, size_t len) { + boost::shared_ptr hmac_sign( + CryptoLink::getCryptoLink().createHMAC("asdf", 4, alg), + deleteHMAC); + hmac_sign->update("asdf", 4); + OutputBuffer sig(0); + hmac_sign->sign(sig, len); + return (sig.getLength()); + } + + // There is no equivalent for array signature because it is copied + // in place +} + +TEST(HMACTest, HMACSigLengthArgument) { + std::vector sig; + + EXPECT_EQ(16, sigVectorLength(MD5, 0)); + EXPECT_EQ(8, sigVectorLength(MD5, 8)); + EXPECT_EQ(16, sigVectorLength(MD5, 16)); + EXPECT_EQ(16, sigVectorLength(MD5, 40)); + EXPECT_EQ(16, sigVectorLength(MD5, 2000)); + + EXPECT_EQ(20, sigBufferLength(SHA1, 0)); + EXPECT_EQ(8, sigBufferLength(SHA1, 8)); + EXPECT_EQ(20, sigBufferLength(SHA1, 20)); + EXPECT_EQ(20, sigBufferLength(SHA1, 40)); + EXPECT_EQ(20, sigBufferLength(SHA1, 2000)); + + EXPECT_EQ(32, sigBufferLength(SHA256, 0)); + EXPECT_EQ(8, sigBufferLength(SHA256, 8)); + EXPECT_EQ(32, sigBufferLength(SHA256, 32)); + EXPECT_EQ(32, sigBufferLength(SHA256, 40)); + EXPECT_EQ(32, sigBufferLength(SHA256, 3200)); + + EXPECT_EQ(16, sigBufferLength(MD5, 0)); + EXPECT_EQ(8, sigBufferLength(MD5, 8)); + EXPECT_EQ(16, sigBufferLength(MD5, 16)); + EXPECT_EQ(16, sigBufferLength(MD5, 40)); + EXPECT_EQ(16, sigBufferLength(MD5, 2000)); + + EXPECT_EQ(20, sigBufferLength(SHA1, 0)); + EXPECT_EQ(8, sigBufferLength(SHA1, 8)); + EXPECT_EQ(20, sigBufferLength(SHA1, 20)); + EXPECT_EQ(20, sigBufferLength(SHA1, 40)); + EXPECT_EQ(20, sigBufferLength(SHA1, 2000)); + + EXPECT_EQ(32, sigBufferLength(SHA256, 0)); + EXPECT_EQ(8, sigBufferLength(SHA256, 8)); + EXPECT_EQ(32, sigBufferLength(SHA256, 32)); + EXPECT_EQ(32, sigBufferLength(SHA256, 40)); + EXPECT_EQ(32, sigBufferLength(SHA256, 3200)); +} + +// Error cases (not only BadKey) +TEST(HMACTest, BadKey) { + OutputBuffer data_buf(0); + OutputBuffer hmac_sig(0); + CryptoLink& crypto = CryptoLink::getCryptoLink(); + + EXPECT_THROW(crypto.createHMAC(NULL, 0, MD5), BadKey); + EXPECT_THROW(crypto.createHMAC(NULL, 0, UNKNOWN_HASH), UnsupportedAlgorithm); + + EXPECT_THROW(signHMAC(data_buf.getData(), data_buf.getLength(), + NULL, 0, MD5, hmac_sig), BadKey); + EXPECT_THROW(signHMAC(data_buf.getData(), data_buf.getLength(), + NULL, 0, UNKNOWN_HASH, hmac_sig), + UnsupportedAlgorithm); + + EXPECT_THROW(verifyHMAC(data_buf.getData(), data_buf.getLength(), + NULL, 0, MD5, hmac_sig.getData(), + hmac_sig.getLength()), BadKey); + EXPECT_THROW(verifyHMAC(data_buf.getData(), data_buf.getLength(), + NULL, 0, UNKNOWN_HASH, hmac_sig.getData(), + hmac_sig.getLength()), + UnsupportedAlgorithm); +} diff --git a/src/lib/dhcp_ddns/Makefile.am b/src/lib/dhcp_ddns/Makefile.am index e20bbb35a..f8b7ef0c7 100644 --- a/src/lib/dhcp_ddns/Makefile.am +++ b/src/lib/dhcp_ddns/Makefile.am @@ -1,7 +1,7 @@ SUBDIRS = . tests AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib -AM_CPPFLAGS += $(BOOST_INCLUDES) $(BOTAN_INCLUDES) +AM_CPPFLAGS += $(BOOST_INCLUDES) AM_CXXFLAGS = $(B10_CXXFLAGS) # Some versions of GCC warn about some versions of Boost regarding @@ -41,7 +41,7 @@ nodist_libkea_dhcp_ddns_la_SOURCES = dhcp_ddns_messages.cc dhcp_ddns_messages.h libkea_dhcp_ddns_la_CXXFLAGS = $(AM_CXXFLAGS) libkea_dhcp_ddns_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES) libkea_dhcp_ddns_la_LDFLAGS = $(AM_LDFLAGS) -libkea_dhcp_ddns_la_LDFLAGS += ${BOTAN_LDFLAGS} +libkea_dhcp_ddns_la_LDFLAGS += ${CRYPTO_LDFLAGS} libkea_dhcp_ddns_la_LIBADD = libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la @@ -51,7 +51,7 @@ libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la -libkea_dhcp_ddns_la_LIBADD += ${BOTAN_LIBS} ${BOTAN_RPATH} +libkea_dhcp_ddns_la_LIBADD += ${CRYPTO_LIBS} ${CRYPTO_RPATH} if USE_CLANGPP # Disable unused parameter warning caused by some of the diff --git a/src/lib/dhcp_ddns/ncr_msg.cc b/src/lib/dhcp_ddns/ncr_msg.cc index ae1816ff5..b124891b9 100644 --- a/src/lib/dhcp_ddns/ncr_msg.cc +++ b/src/lib/dhcp_ddns/ncr_msg.cc @@ -17,9 +17,9 @@ #include #include #include +#include #include -#include #include #include @@ -164,17 +164,11 @@ D2Dhcid::createDigest(const uint8_t identifier_type, // Use the DUID and FQDN to compute the digest (see RFC4701, section 3). - // The getCryptoLink is a common function to initialize the Botan library. - cryptolink::CryptoLink::getCryptoLink(); - // @todo The code below, which calculates the SHA-256 may need to be moved - // to the cryptolink library. - Botan::SecureVector secure; + isc::util::OutputBuffer hash(0); try { - Botan::SHA_256 sha; // We have checked already that the DUID and FQDN aren't empty // so it is safe to assume that the data buffer is not empty. - secure = sha.process(static_cast(&data[0]), - data.size()); + cryptolink::digest(&data[0], data.size(), cryptolink::SHA256, hash); } catch (const std::exception& ex) { isc_throw(isc::dhcp_ddns::DhcidRdataComputeError, "error while generating DHCID from DUID: " @@ -189,14 +183,14 @@ D2Dhcid::createDigest(const uint8_t identifier_type, // Let's allocate the space for the identifier-type (2 bytes) and // digest-type (1 byte). This is 3 bytes all together. - bytes_.resize(3); + bytes_.resize(3 + hash.getLength()); // Leave first byte 0 and set the second byte. Those two bytes // form the identifier-type. bytes_[1] = identifier_type; // Third byte is always equal to 1, which specifies SHA-256 digest type. bytes_[2] = 1; // Now let's append the digest. - bytes_.insert(bytes_.end(), secure.begin(), secure.end()); + std::memcpy(&bytes_[3], hash.getData(), hash.getLength()); } std::ostream& diff --git a/src/lib/dhcp_ddns/tests/Makefile.am b/src/lib/dhcp_ddns/tests/Makefile.am index d6d32d0b5..48ed91013 100644 --- a/src/lib/dhcp_ddns/tests/Makefile.am +++ b/src/lib/dhcp_ddns/tests/Makefile.am @@ -1,7 +1,7 @@ SUBDIRS = . AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib -AM_CPPFLAGS += $(BOOST_INCLUDES) $(BOTAN_INCLUDES) +AM_CPPFLAGS += $(BOOST_INCLUDES) AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcp_ddns/tests\" AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\" @@ -53,7 +53,7 @@ libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_dd libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la -libdhcp_ddns_unittests_LDADD += ${BOTAN_LIBS} ${BOTAN_RPATH} +libdhcp_ddns_unittests_LDADD += ${CRYPTO_LIBS} ${CRYPTO_RPATH} libdhcp_ddns_unittests_LDADD += $(GTEST_LDADD) endif diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am index 3beac329c..b3cd4eff9 100644 --- a/src/lib/dns/tests/Makefile.am +++ b/src/lib/dns/tests/Makefile.am @@ -83,16 +83,16 @@ run_unittests_SOURCES += rrset_collection_unittest.cc run_unittests_SOURCES += zone_checker_unittest.cc run_unittests_SOURCES += run_unittests.cc run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) -# We shouldn't need to include BOTAN_LIBS here, but there +# We shouldn't need to include CRYPTO_LIBS here, but there # is one test system where the path for GTEST_LDFLAGS contains # an older version of botan, and somehow that version gets # linked if we don't -run_unittests_LDFLAGS = $(BOTAN_LDFLAGS) $(GTEST_LDFLAGS) $(AM_LDFLAGS) +run_unittests_LDFLAGS = $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS) $(AM_LDFLAGS) run_unittests_LDADD = $(top_builddir)/src/lib/dns/libkea-dns++.la run_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la -run_unittests_LDADD += $(BOTAN_LIBS) $(GTEST_LDADD) +run_unittests_LDADD += $(CRYPTO_LIBS) $(GTEST_LDADD) endif noinst_PROGRAMS = $(TESTS) -- GitLab