From: Petr Tesarik <ptesarik@suse.cz>
Subject: Check SSH host key when saving dump
References: bnc#722440

Signed-off-by: Petr Tesarik <ptesarik@suse.cz>

---
 CMakeLists.txt             |   15 ++++++++
 cmake/Findlibssl.cmake     |   77 +++++++++++++++++++++++++++++++++++++++++++++
 config.h.in                |    1 
 doc/man/kdump.5.txt.in     |   14 ++++++++
 init/setup-kdump.sh        |   53 +++++++++++++++++++++++-------
 init/setup-kdumpfs.sh      |    5 +-
 kdumptool/configuration.cc |   14 +++++++-
 kdumptool/configuration.h  |   10 +++++
 kdumptool/stringutil.cc    |   60 +++++++++++++++++++++++++++++++++++
 kdumptool/stringutil.h     |   18 ++++++++++
 kdumptool/transfer.cc      |   43 +++++++++++++++++++++----
 sysconfig.kdump            |   10 +++++
 12 files changed, 298 insertions(+), 22 deletions(-)

--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -106,6 +106,21 @@ IF(NOT ESMTP_FOUND)
     SET(ESMTP_FOUND FALSE)
 ENDIF(NOT ESMTP_FOUND)
 
+# OpenSSL
+INCLUDE(Findlibssl)
+
+IF (LIBSSL_FOUND)
+    SET(EXTRA_LIBS ${EXTRA_LIBS} ${LIBSSL_LIBRARIES})
+    INCLUDE_DIRECTORIES(${LIBSSL_INCLUDE_DIRS})
+ENDIF (LIBSSL_FOUND)
+
+IF(NOT LIBSSL_FOUND)
+    MESSAGE(
+        "libssl not found. Install libopenssl-devel or something like that")
+    MESSAGE("Building without OpenSSL support")
+    SET(LIBSSL_FOUND FALSE)
+ENDIF(NOT LIBSSL_FOUND)
+
 
 #
 # Configure file
--- /dev/null
+++ b/cmake/Findlibssl.cmake
@@ -0,0 +1,77 @@
+# - Try to find libssl
+# Once done this will define
+#
+#  LIBSSL_FOUND - system has OpenSSL
+#  LIBSSL_INCLUDE_DIRS - the OpenSSL include directory
+#  LIBSSL_LIBRARIES - Link these to use OpenSSL
+#  LIBSSL_DEFINITIONS - Compiler switches required for using OpenSSL
+#
+#  Copyright (c) 2008 Bernhard Walle <bwalle@suse.de>
+#  Copyright (c) 2011 Petr Tesarik <ptesarik@suse.cz>
+#
+#  Redistribution and use is allowed according to the terms of the New
+#  BSD license.
+#  For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+#
+
+
+if (LIBSSL_LIBRARIES AND LIBCRYPTO_INCLUDE_DIRS)
+  # in cache already
+  set(LIBSSL_FOUND TRUE)
+else (LIBSSL_LIBRARIES AND LIBCRYPTO_INCLUDE_DIRS)
+  find_path(LIBSSL_INCLUDE_DIR
+    NAMES
+      openssl/ssl.h
+    PATHS
+      /usr/include
+      /usr/local/include
+      /opt/local/include
+      /sw/include
+  )
+
+  find_library(LIBSSL_LIBRARY
+    NAMES
+      ssl
+      crypto
+    PATHS
+      /usr/lib
+      /usr/local/lib
+      /opt/local/lib
+      /sw/lib
+  )
+  mark_as_advanced(LIBSSL_LIBRARY)
+
+  if (LIBSSL_LIBRARY)
+    set(LIBSSL_FOUND TRUE)
+  endif (LIBSSL_LIBRARY)
+
+  set(LIBSSL_INCLUDE_DIRS
+    ${LIBSSL_INCLUDE_DIR}
+  )
+
+  if (LIBSSL_FOUND)
+    set(LIBSSL_LIBRARIES
+      ${LIBSSL_LIBRARIES}
+      ${LIBSSL_LIBRARY}
+    )
+  endif (LIBSSL_FOUND)
+
+  if (LIBSSL_INCLUDE_DIRS AND LIBCRYPTO_LIBRARIES)
+     set(LIBSSL_FOUND TRUE)
+  endif (LIBSSL_INCLUDE_DIRS AND LIBCRYPTO_LIBRARIES)
+
+  if (LIBSSL_FOUND)
+    if (NOT libssh2_FIND_QUIETLY)
+      message(STATUS "Found libssl: ${LIBSSL_LIBRARIES}")
+    endif (NOT libssh2_FIND_QUIETLY)
+  else (LIBSSL_FOUND)
+    if (libssh2_FIND_REQUIRED)
+      message(FATAL_ERROR "Could not find libssl")
+    endif (libssh2_FIND_REQUIRED)
+  endif (LIBSSL_FOUND)
+
+  # show the LIBSSL_INCLUDE_DIRS and LIBCRYPTO_LIBRARIES variables only in the advanced view
+  mark_as_advanced(LIBSSL_INCLUDE_DIRS LIBCRYPTO_INCLUDE_DIR LIBCRYPTO_LIBRARIES)
+
+endif (LIBSSL_LIBRARIES AND LIBCRYPTO_INCLUDE_DIRS)
+
--- a/init/setup-kdump.sh
+++ b/init/setup-kdump.sh
@@ -35,33 +35,60 @@ if [ ! -f "$CONFIG" ] ; then
     return 1
 fi
 
+#
+# read in the configuration
 source "$CONFIG"
-mkdir -p ${tmp_mnt}/etc/sysconfig/
-cp "$CONFIG" ${tmp_mnt}/etc/sysconfig/
 
 #
 # replace the KDUMP_SAVEDIR with the resolved path
 if [ "$kdump_protocol" = "file" ] ; then
-    sed -i "s#KDUMP_SAVEDIR=.*#KDUMP_SAVEDIR=\"file://$kdump_path\"#g" ${tmp_mnt}/$CONFIG
+    KDUMP_SAVEDIR="file://$kdump_path"
+fi
+
+#
+# get the host key, if needed
+if [ "$kdump_protocol" = "sftp" -a -z "$KDUMP_HOST_KEY" ] ; then
+    KDUMP_HOST_KEY=$(ssh-keygen -F "$kdump_host" 2>/dev/null | \
+                     awk '/^[^#]/ { if (NF==3) { print $3; exit } }')
+    if [ -z "$KDUMP_HOST_KEY" ] ; then
+        echo "WARNING: target SSH host key not found. " \
+             "Man-in-the-middle attack is possible." >&2
+    fi
 fi
 
 #
+# copy the configuration file, modifying:
+#   KDUMP_SAVEDIR  -> resolved path
+#   KDUMP_HOST_KEY -> target host public key
+mkdir -p ${tmp_mnt}/etc/sysconfig/
+sed -e 's#^[ 	]*\(KDUMP_SAVEDIR\)=.*#\1="'"$KDUMP_SAVEDIR"'"#g' \
+    -e 's#^[ 	]*\(KDUMP_HOST_KEY\)=.*#\1="'"$KDUMP_HOST_KEY"'"#g' \
+    "$CONFIG" > "${tmp_mnt}${CONFIG}"
+
+#
+# add the host key explicitly if the option was missing
+grep '^[ 	]*KDUMP_HOST_KEY=' ${tmp_mnt}${CONFIG} >/dev/null 2>&1 \
+    || echo 'KDUMP_HOST_KEY="'"$KDUMP_HOST_KEY"'"' >> "${tmp_mnt}${CONFIG}"
+
+#
 # remember the host name
 #
 hostname >> ${tmp_mnt}/etc/hostname.kdump
 
 #
-# copy public and private key
+# copy public and private key if needed
 #
-if [ -f /root/.ssh/id_dsa ] && [ -f /root/.ssh/id_dsa.pub ] ; then
-    mkdir ${tmp_mnt}/.ssh
-    cp /root/.ssh/id_dsa ${tmp_mnt}/.ssh
-    cp /root/.ssh/id_dsa.pub ${tmp_mnt}/.ssh
-fi
-if [ -f /root/.ssh/id_rsa ] && [ -f /root/.ssh/id_rsa.pub ] ; then
-    mkdir ${tmp_mnt}/.ssh
-    cp /root/.ssh/id_rsa ${tmp_mnt}/.ssh
-    cp /root/.ssh/id_rsa.pub ${tmp_mnt}/.ssh
+if [ "$kdump_protocol" = "sftp" ] ; then
+    if [ -f /root/.ssh/id_dsa ] && [ -f /root/.ssh/id_dsa.pub ] ; then
+        mkdir -p ${tmp_mnt}/.ssh
+        cp /root/.ssh/id_dsa ${tmp_mnt}/.ssh
+        cp /root/.ssh/id_dsa.pub ${tmp_mnt}/.ssh
+    fi
+    if [ -f /root/.ssh/id_rsa ] && [ -f /root/.ssh/id_rsa.pub ] ; then
+        mkdir -p ${tmp_mnt}/.ssh
+        cp /root/.ssh/id_rsa ${tmp_mnt}/.ssh
+        cp /root/.ssh/id_rsa.pub ${tmp_mnt}/.ssh
+    fi
 fi
 
 #
--- a/kdumptool/stringutil.cc
+++ b/kdumptool/stringutil.cc
@@ -28,6 +28,11 @@
 #include "global.h"
 #include "debug.h"
 
+#if HAVE_LIBSSL
+#   include <openssl/bio.h>
+#   include <openssl/evp.h>
+#endif
+
 using std::string;
 using std::stringstream;
 using std::strcpy;
@@ -310,6 +315,61 @@ string Stringutil::formatCurrentTime(con
     return formatUnixTime(formatstring, time(NULL));
 }
 
+#if HAVE_LIBSSL
+
+// -----------------------------------------------------------------------------
+void Stringutil::digest_base64(const void *buf, size_t len,
+			       char *md5sum, char *sha1sum)
+    throw (KError)
+{
+	BIO *bio, *b64bio, *sha1bio, *md5bio;
+	char tmpbuf[256];
+	int res;
+
+	bio = BIO_new_mem_buf(const_cast<void *> (buf), len);
+	if (!bio)
+	    throw KError("Cannot initialize memory stream");
+
+	try {
+	    b64bio = BIO_new(BIO_f_base64());
+	    if (!b64bio)
+		throw KError("Cannot initilaize base64 decoder");
+	    BIO_set_flags(b64bio, BIO_FLAGS_BASE64_NO_NL);
+	    bio = BIO_push(b64bio, bio);
+
+	    sha1bio = BIO_new(BIO_f_md());
+	    if (!sha1bio)
+		throw KError("Cannot initialize MD5 sum");
+	    BIO_set_md(sha1bio, EVP_sha1());
+	    bio = BIO_push(sha1bio, bio);
+
+	    md5bio = BIO_new(BIO_f_md());
+	    if (!md5bio)
+		throw KError("Cannot initialize SHA1 sum");
+	    BIO_set_md(md5bio, EVP_md5());
+	    bio = BIO_push(md5bio, bio);
+
+	    while ( (res = BIO_read(md5bio, tmpbuf, sizeof tmpbuf)) > 0)
+		;
+	    if (res < 0)
+		throw KError("Decoding failed");
+
+	    if (BIO_gets(md5bio, md5sum, MD5SUM_LENGTH) != MD5SUM_LENGTH)
+		throw KError("Error getting MD5 sum");
+
+	    if (BIO_gets(sha1bio, sha1sum, SHA1SUM_LENGTH) != SHA1SUM_LENGTH)
+		throw KError("Error getting SHA1 sum");
+
+	} catch (...) {
+	    BIO_free_all(bio);
+	    throw;
+	}
+
+	BIO_free_all(bio);
+}
+
+#endif	// HAVE_LIBSSL
+
 //}}}
 
 // vim: set sw=4 ts=4 fdm=marker et: :collapseFolds=1:
--- a/kdumptool/stringutil.h
+++ b/kdumptool/stringutil.h
@@ -26,6 +26,8 @@
 #include "global.h"
 
 #define ISO_DATETIME "%Y-%m-%d-%H:%M"
+#define MD5SUM_LENGTH	16
+#define SHA1SUM_LENGTH	20
 
 //{{{ Stringutil ---------------------------------------------------------------
 
@@ -268,6 +270,22 @@ class Stringutil {
         static std::string formatUnixTime(const char *formatstring,
                                           time_t value)
         throw ();
+
+#if HAVE_LIBSSL
+
+	/**
+	 * Computes the MD5 and SHA1 digests of a buffer.
+	 *
+	 * @param[in] buf base-64 encoded data to be digested
+	 * @param[in] len length of the base64 input
+	 * @param[out] md5sum resulting MD5 sum
+	 * @param[out] sha1sum resulting SHA1 sum
+	 */
+	static void digest_base64(const void *buf, size_t len,
+				  char *md5sum, char *sha1sum)
+	throw (KError);
+
+#endif	// HAVE_LIBSSL
 };
 
 //}}}
--- a/config.h.in
+++ b/config.h.in
@@ -7,3 +7,4 @@
 
 #define HAVE_LIBSSH2        @LIBSSH2_FOUND@
 #define HAVE_LIBESMTP       @ESMTP_FOUND@
+#define HAVE_LIBSSL         @LIBSSL_FOUND@
--- a/init/setup-kdumpfs.sh
+++ b/init/setup-kdumpfs.sh
@@ -270,8 +270,9 @@ if [ -z "$target" ] ; then
     echo >&2 "kdumptool print_target failed."
     return 1
 fi
-kdump_protocol=$(echo "$target" | grep '^Protocol' | awk '{ print $2 }')
-kdump_path=$(echo "$target" | grep '^Realpath' | awk '{ print $2 }')
+kdump_protocol=$(echo "$target" | grep '^Protocol:' | awk '{ print $2 }')
+kdump_path=$(echo "$target" | grep '^Realpath:' | awk '{ print $2 }')
+kdump_host=$(echo "$target" | grep '^Host:' | awk '{ print $2 }')
 
 kdump_fsmod=
 
--- a/kdumptool/configuration.cc
+++ b/kdumptool/configuration.cc
@@ -46,7 +46,7 @@ Configuration::Configuration()
       m_dumpFormat("compressed"), m_continueOnError(true),
       m_requiredPrograms(""), m_prescript(""), m_postscript(""),
       m_copyKernel(""), m_kdumptoolFlags(""), m_smtpServer(""),
-      m_smtpUser(""), m_smtpPassword("")
+      m_smtpUser(""), m_smtpPassword(""), m_hostKey("")
 {}
 
 /* -------------------------------------------------------------------------- */
@@ -78,6 +78,7 @@ void Configuration::readFile(const strin
     cp.addVariable("KDUMP_SMTP_PASSWORD");
     cp.addVariable("KDUMP_NOTIFICATION_TO");
     cp.addVariable("KDUMP_NOTIFICATION_CC");
+    cp.addVariable("KDUMP_HOST_KEY");
     cp.parse();
 
     m_kernelVersion = cp.getValue("KDUMP_KERNELVER");
@@ -104,6 +105,7 @@ void Configuration::readFile(const strin
     m_smtpPassword = cp.getValue("KDUMP_SMTP_PASSWORD");
     m_notificationTo = cp.getValue("KDUMP_NOTIFICATION_TO");
     m_notificationCc = cp.getValue("KDUMP_NOTIFICATION_CC");
+    m_hostKey = cp.getValue("KDUMP_HOST_KEY");
 
     m_readConfig = true;
 }
@@ -374,6 +376,16 @@ string Configuration::getNotificationCc(
     return m_notificationCc;
 }
 
+// -----------------------------------------------------------------------------
+string Configuration::getHostKey()
+    throw (KError)
+{
+    if (!m_readConfig)
+        throw KError("Configuration has not been read.");
+
+    return m_hostKey;
+}
+
 //}}}
 
 // vim: set sw=4 ts=4 fdm=marker et: :collapseFolds=1:
--- a/kdumptool/configuration.h
+++ b/kdumptool/configuration.h
@@ -302,6 +302,15 @@ class Configuration {
          std::string getNotificationCc()
          throw (KError);
 
+         /**
+          * Returns the value of KDUMP_HOST_KEY.
+          *
+          * @return the target host key, encoded with base64
+          * @exception KError if Configuration::readFile() was not called
+          */
+         std::string getHostKey()
+         throw (KError);
+
     protected:
         Configuration()
         throw ();
@@ -336,6 +345,7 @@ class Configuration {
         std::string m_smtpPassword;
         std::string m_notificationTo;
         std::string m_notificationCc;
+	std::string m_hostKey;
 };
 
 //}}}
--- a/kdumptool/transfer.cc
+++ b/kdumptool/transfer.cc
@@ -16,6 +16,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  * 02110-1301, USA.
  */
+#include <iostream>
 #include <cstdio>
 #include <cstdarg>
 #include <cerrno>
@@ -47,6 +48,8 @@ using std::fclose;
 using std::string;
 using std::copy;
 using std::strlen;
+using std::cerr;
+using std::endl;
 
 #define DEFAULT_MOUNTPOINT "/mnt"
 
@@ -472,12 +475,40 @@ SFTPTransfer::SFTPTransfer(const char *t
             Stringutil::number2string(ret) +".");
     }
 
-    // get the fingerprint for debugging
-    // XXX: compare the fingerprint
-    const char *fingerprint = libssh2_hostkey_hash(m_sshSession,
-        LIBSSH2_HOSTKEY_HASH_MD5);
-    Debug::debug()->info("SSH fingerprint: %s",
-        Stringutil::bytes2hexstr(fingerprint, 16, true).c_str());
+    // get the hostkey fingerprints
+    const char *hostsha1 =
+	libssh2_hostkey_hash(m_sshSession, LIBSSH2_HOSTKEY_HASH_SHA1);
+    Debug::debug()->info
+	("SSH SHA1 fingerprint: %s",
+	 Stringutil::bytes2hexstr(hostsha1, SHA1SUM_LENGTH, true).c_str());
+
+    const char *hostmd5 =
+	libssh2_hostkey_hash(m_sshSession, LIBSSH2_HOSTKEY_HASH_MD5);
+    Debug::debug()->info
+	("SSH MD5 fingerprint: %s",
+	 Stringutil::bytes2hexstr(hostmd5, MD5SUM_LENGTH, true).c_str());
+
+#if HAVE_LIBSSL
+    // check the fingerprints if possible
+    Configuration *config = Configuration::config();
+    string hostkey = config->getHostKey();
+
+    if (!hostkey.empty() && hostkey != "*") {
+	char expectmd5[MD5SUM_LENGTH];
+	char expectsha1[SHA1SUM_LENGTH];
+	Stringutil::digest_base64(hostkey.c_str(), hostkey.size(),
+				  expectmd5, expectsha1);
+
+	if (memcmp(hostsha1, expectsha1, SHA1SUM_LENGTH))
+	    throw KError("Target host key SHA1 fingerprint mismatch!");
+	Debug::debug()->info("SHA1 fingerprint matches");
+
+	if (memcmp(hostmd5, expectmd5, MD5SUM_LENGTH))
+	    throw KError("Target host key MD5 fingerprint mismatch!");
+	Debug::debug()->info("MD5 fingerprint matches");
+    } else
+	cerr << "WARNING: SSH host key accepted without checking!" << endl;
+#endif	// HAVE_LIBSSL
 
     // SSH authentication
     bool authenticated = false;
--- a/sysconfig.kdump
+++ b/sysconfig.kdump
@@ -312,3 +312,13 @@ KDUMP_NOTIFICATION_TO=""
 #
 KDUMP_NOTIFICATION_CC=""
 
+## Type:        string
+## Default:     ""
+## ServiceRestart:	kdump
+#
+# Target host public key, encoded with base64. This is used only for the
+# SFTP transfer protocol. If empty, kdump will run 'ssh-keygen -F' to get
+# the key from known hosts. If set to "*", any host key will be accepted.
+#
+# See also: kdump(5)
+KDUMP_HOST_KEY=""
--- a/doc/man/kdump.5.txt.in
+++ b/doc/man/kdump.5.txt.in
@@ -502,6 +502,20 @@ Example: "john@myprovider.de doe@myprovi
 
 Default: ""
 
+KDUMP_SSH_HOST_KEY
+~~~~~~~~~~~~~~~~~~
+
+Target host key encoded with base64, i.e. as found in +~/.ssh/known_hosts+.
+This is used for the SFTP transfer protocol to verify the authenticity of
+the remote host. If this field is left empty, the setup scripts will try
+to get it automatically using *ssh-keygen -F $target_host*.
+
+If you want to disable host key checking, set this variable to "*".
+
+Example: "AAAAB3NzaC1yc2EAAAABIwAAAIEAxIJIeuVX0F0u53wMgH22fdl62ifLXRwaPAXPsCrvyerMEb5DXHnIv1sYdzlJ1g/2Iw+ppjGDh1En/P12Dpl2uLBZh0MOrM71spTjvTwV2MKZtXrYjZUIPIZdd9vrI+/LEI0nw7LSdnKKgkAIXMhaRrq9N6uoZWe41ilU4NpnL/k="
+
+Default: ""
+
 
 
 URL FORMAT
