Browse Source

applied #2406: OpenSSL alternate crypto backend

Francis Dupont 10 years ago
parent
commit
4b4110dd68

+ 7 - 0
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

+ 10 - 1
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 \

+ 208 - 82
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 <botan/botan.h>
@@ -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<path_to_libbotan>, 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 <botan/version.h>
 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 <botan/botan.h>
                           #include <botan/hash.h>
                          ],
@@ -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 <openssl/opensslv.h>
+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}

+ 1 - 1
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

+ 19 - 4
doc/guide/bind10-guide.xml

@@ -138,9 +138,13 @@
       </para>
 
       <para>
-        Kea uses the Botan crypto library for C++
-        (<ulink url="http://botan.randombit.net/"/>).
-        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++ (<ulink url="http://botan.randombit.net/"/>).
+        It requires at least Botan version 1.8. As an alternative to Botan,
+        Kea can use the OpenSSL crypto library
+        (<ulink url="http://www.openssl.org/"/>).
+        It requires a version with SHA-2 support.
       </para>
 
       <para>
@@ -546,7 +550,7 @@ $ <userinput>./configure</userinput></screen>
 
         <para>
           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.
         </para>
 
@@ -697,6 +701,17 @@ as a dependency earlier -->
           </varlistentry>
 
           <varlistentry>
+            <term>--with-openssl</term>
+            <listitem>
+              <simpara>Replace Botan by OpenSSL for the crypto library.
+                The default is to try to find a working Botan then
+                OpenSSL only if not found.
+<!-- missing -with-botan-config -->
+              </simpara>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
             <term>--without-werror</term>
             <listitem>
               <simpara>Disable the default use of the

+ 10 - 3
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

src/bin/cmdctl/b10-certgen.cc → src/bin/cmdctl/botan-certgen.cc


+ 416 - 0
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 <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+#include <cstring>
+#include <iostream>
+#include <fstream>
+#include <memory>
+#include <getopt.h>
+
+// For cleaner 'does not exist or is not readable' output than
+// openssl provides
+#include <unistd.h>
+#include <errno.h>
+
+// 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<char*>("certfile"), required_argument, NULL, 'c' },
+        { const_cast<char*>("force"), no_argument, NULL, 'f' },
+        { const_cast<char*>("help"), no_argument, NULL, 'h' },
+        { const_cast<char*>("keyfile"), required_argument, NULL, 'k' },
+        { const_cast<char*>("write"), no_argument, NULL, 'w' },
+        { const_cast<char*>("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));
+}

+ 12 - 2
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

src/bin/cmdctl/tests/b10-certgen_test.py → src/bin/cmdctl/tests/botan-certgen_test.py


+ 253 - 0
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
+        <certfile>), 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()
+

+ 2 - 2
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)

+ 14 - 3
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}

+ 202 - 0
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 <cryptolink.h>
+#include <cryptolink/crypto_hash.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <botan/version.h>
+#include <botan/botan.h>
+#include <botan/hash.h>
+#include <botan/types.h>
+
+#include <cstring>
+
+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<int>(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<const Botan::byte*>(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<Botan::byte> 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<Botan::byte> 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<uint8_t> final(size_t len) {
+        try {
+            Botan::SecureVector<Botan::byte> b_result(hash_->final());
+            if (len == 0 || len > b_result.size()) {
+                return (std::vector<uint8_t>(b_result.begin(), b_result.end()));
+            } else {
+                return (std::vector<uint8_t>(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<Botan::HashFunction> 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<uint8_t>
+Hash::final(size_t len) {
+    return impl_->final(len);
+}
+
+} // namespace cryptolink
+} // namespace isc

+ 279 - 0
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 <cryptolink.h>
+#include <cryptolink/crypto_hmac.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <botan/version.h>
+#include <botan/botan.h>
+#include <botan/hmac.h>
+#include <botan/hash.h>
+#include <botan/types.h>
+
+#include <cstring>
+
+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<int>(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<Botan::byte> hashed_key =
+                    hash->process(static_cast<const Botan::byte*>(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<const Botan::byte*>(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<const Botan::byte*>(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<Botan::byte> 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<Botan::byte> 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<uint8_t> sign(size_t len) {
+        try {
+            Botan::SecureVector<Botan::byte> b_result(hmac_->final());
+            if (len == 0 || len > b_result.size()) {
+                return (std::vector<uint8_t>(b_result.begin(), b_result.end()));
+            } else {
+                return (std::vector<uint8_t>(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<Botan::byte> 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<const unsigned char*>(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<Botan::HMAC> 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<uint8_t>
+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

+ 48 - 0
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 <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_hash.h>
+#include <cryptolink/crypto_hmac.h>
+
+#include <botan/botan.h>
+
+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
+

+ 42 - 0
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 <cryptolink.h>
+#include <cryptolink/crypto_hash.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <cstring>
+
+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> 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

+ 146 - 0
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 <util/buffer.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <cryptolink/cryptolink.h>
+
+#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<uint8_t>
+    ///
+    /// \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<uint8_t> 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
+

+ 0 - 225
src/lib/cryptolink/crypto_hmac.cc

@@ -17,236 +17,11 @@
 
 #include <boost/scoped_ptr.hpp>
 
-#include <botan/version.h>
-#include <botan/botan.h>
-#include <botan/hmac.h>
-#include <botan/hash.h>
-#include <botan/types.h>
-
 #include <cstring>
 
-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<int>(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<Botan::byte> hashed_key =
-                    hash->process(static_cast<const Botan::byte*>(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<const Botan::byte*>(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<const Botan::byte*>(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<Botan::byte> 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<Botan::byte> 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<uint8_t> sign(size_t len) {
-        try {
-            Botan::SecureVector<Botan::byte> b_result(hmac_->final());
-            if (len == 0 || len > b_result.size()) {
-                return (std::vector<uint8_t>(b_result.begin(), b_result.end()));
-            } else {
-                return (std::vector<uint8_t>(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<Botan::byte> 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<const unsigned char*>(sig),
-                                    len));
-        } catch (const Botan::Exception& exc) {
-            isc_throw(isc::cryptolink::LibraryError, exc.what());
-        }
-    }
-
-private:
-    boost::scoped_ptr<Botan::HMAC> 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<uint8_t>
-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,

+ 5 - 22
src/lib/cryptolink/cryptolink.cc

@@ -13,23 +13,12 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_hash.h>
 #include <cryptolink/crypto_hmac.h>
 
-#include <botan/botan.h>
-
 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*

+ 25 - 0
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

+ 183 - 0
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 <cryptolink.h>
+#include <cryptolink/crypto_hash.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <openssl/evp.h>
+
+#include <cstring>
+
+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<int>(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<unsigned char> 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<unsigned char> 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<uint8_t> final(size_t len) {
+        size_t size = getOutputLength();
+        std::vector<unsigned char> digest(size);
+        EVP_DigestFinal_ex(md_.get(), &digest[0], NULL);
+        if (len != 0 && len < size) {
+            digest.resize(len);
+        }
+        return (std::vector<uint8_t>(digest.begin(), digest.end()));
+    }
+
+private:
+    /// @brief The protected pointer to the OpenSSL EVP_MD_CTX structure
+    boost::scoped_ptr<EVP_MD_CTX> 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<uint8_t>
+Hash::final(size_t len) {
+    return impl_->final(len);
+}
+
+} // namespace cryptolink
+} // namespace isc

+ 288 - 0
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 <cryptolink.h>
+#include <cryptolink/crypto_hmac.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <openssl/hmac.h>
+
+#include <cstring>
+
+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<typename T>
+struct SecBuf {
+public:
+    typedef typename std::vector<T>::iterator iterator;
+
+    typedef typename std::vector<T>::const_iterator const_iterator;
+
+    explicit SecBuf() : vec_(std::vector<T>()) {}
+
+    explicit SecBuf(size_t n, const T& value = T()) :
+        vec_(std::vector<T>(n, value))
+    {}
+
+    SecBuf(iterator first, iterator last) :
+        vec_(std::vector<T>(first, last))
+    {}
+
+    SecBuf(const_iterator first, const_iterator last) :
+        vec_(std::vector<T>(first, last))
+    {}
+
+    SecBuf(const std::vector<T>& 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<T> 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<int>(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<int>(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_t>(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<const unsigned char*>(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<unsigned char> 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<unsigned char> 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<uint8_t> sign(size_t len) {
+        size_t size = getOutputLength();
+        SecBuf<unsigned char> digest(size);
+        HMAC_Final(md_.get(), &digest[0], NULL);
+        if (len != 0 && len < size) {
+            digest.resize(len);
+        }
+        return (std::vector<uint8_t>(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<unsigned char> 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<HMAC_CTX> 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<uint8_t>
+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

+ 51 - 0
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 <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_hash.h>
+#include <cryptolink/crypto_hmac.h>
+
+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
+

+ 4 - 2
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

+ 1 - 582
src/lib/cryptolink/tests/crypto_unittests.cc

@@ -14,596 +14,15 @@
 
 #include <config.h>
 
-#include <string>
-#include <vector>
-
-#include <boost/lexical_cast.hpp>
-
 #include <gtest/gtest.h>
 
 #include <util/encode/hex.h>
 
 #include <cryptolink/cryptolink.h>
-#include <cryptolink/crypto_hmac.h>
-
-#include <util/buffer.h>
-#include <exceptions/exceptions.h>
 
-#include <boost/shared_ptr.hpp>
-
-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<const uint8_t*>(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> 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> 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> hmac_sign(crypto.createHMAC(secret,
-                                                            secret_len,
-                                                            hash_algorithm),
-                                          deleteHMAC);
-        hmac_sign->update(data.c_str(), data.size());
-        std::vector<uint8_t> sig = hmac_sign->sign(hmac_len);
-        ASSERT_EQ(hmac_len, sig.size());
-        checkData(&sig[0], expected_hmac, hmac_len);
-
-        boost::shared_ptr<HMAC> 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> 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> 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<std::vector<uint8_t> >& hmac_list)
-{
-    std::vector<std::string> data_list;
-    std::vector<std::string> 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<std::string>::size_type i = 0;
-         i < data_list.size(); ++i) {
-        SCOPED_TRACE("RFC4231 HMAC test for algorithm ID: " +
-                     lexical_cast<std::string>(hash_algorithm) +
-                     ", data ID: " + lexical_cast<std::string>(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<std::vector<uint8_t> > 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<std::vector<uint8_t> > 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<std::vector<uint8_t> > 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<std::vector<uint8_t> > 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> hmac_sign(
-            CryptoLink::getCryptoLink().createHMAC("asdf", 4, alg),
-            deleteHMAC);
-        hmac_sign->update("asdf", 4);
-        const std::vector<uint8_t> sig = hmac_sign->sign(len);
-        return (sig.size());
-    }
-
-    size_t
-    sigBufferLength(HashAlgorithm alg, size_t len) {
-        boost::shared_ptr<HMAC> 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<uint8_t> 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();

+ 602 - 0
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 <config.h>
+
+#include <string>
+#include <vector>
+
+#include <boost/lexical_cast.hpp>
+
+#include <gtest/gtest.h>
+
+#include <util/encode/hex.h>
+
+#include <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_hash.h>
+
+#include <util/buffer.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/scoped_array.hpp>
+#include <boost/shared_ptr.hpp>
+
+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<const uint8_t*>(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> 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> hash_digest(crypto.createHash(hash_algorithm),
+                                            deleteHash);
+        hash_digest->update(data.c_str(), data.size());
+        std::vector<uint8_t> 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> 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<uint8_t> 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> hash_digest(
+            CryptoLink::getCryptoLink().createHash(alg),
+            deleteHash);
+        hash_digest->update("asdf", 4);
+        const std::vector<uint8_t> 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> 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<uint8_t> 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?

+ 676 - 0
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 <config.h>
+
+#include <string>
+#include <vector>
+
+#include <boost/lexical_cast.hpp>
+
+#include <gtest/gtest.h>
+
+#include <util/encode/hex.h>
+
+#include <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_hmac.h>
+
+#include <util/buffer.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+
+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<const uint8_t*>(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> 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> 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> hmac_sign(crypto.createHMAC(secret,
+                                                            secret_len,
+                                                            hash_algorithm),
+                                          deleteHMAC);
+        hmac_sign->update(data.c_str(), data.size());
+        std::vector<uint8_t> sig = hmac_sign->sign(hmac_len);
+        ASSERT_EQ(hmac_len, sig.size());
+        checkData(&sig[0], expected_hmac, hmac_len);
+
+        boost::shared_ptr<HMAC> 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> 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> 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<std::vector<uint8_t> >& hmac_list)
+{
+    std::vector<std::string> data_list;
+    std::vector<std::string> 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<std::string>::size_type i = 0;
+         i < data_list.size(); ++i) {
+        SCOPED_TRACE("RFC4231 HMAC test for algorithm ID: " +
+                     lexical_cast<std::string>(hash_algorithm) +
+                     ", data ID: " + lexical_cast<std::string>(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<std::vector<uint8_t> > 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<std::vector<uint8_t> > 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<std::vector<uint8_t> > 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<std::vector<uint8_t> > 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> hmac_sign(
+            CryptoLink::getCryptoLink().createHMAC("asdf", 4, alg),
+            deleteHMAC);
+        hmac_sign->update("asdf", 4);
+        const std::vector<uint8_t> 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> 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<uint8_t> 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);
+}

+ 3 - 3
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

+ 5 - 11
src/lib/dhcp_ddns/ncr_msg.cc

@@ -17,9 +17,9 @@
 #include <asiolink/io_address.h>
 #include <asiolink/io_error.h>
 #include <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_hash.h>
 
 #include <boost/algorithm/string/predicate.hpp>
-#include <botan/sha2_32.h>
 
 #include <sstream>
 #include <limits>
@@ -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<Botan::byte> 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<const Botan::byte*>(&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&

+ 2 - 2
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
 

+ 3 - 3
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)