Преглед на файлове

Add --ssh-host-key-md option

Set checksum for SSH host public key.  Use same syntax with --checksum
option.  TYPE is hash type.  The supported hash type is sha-1 or
md5. DIGEST is hex digest.  For example:
sha-1=b030503d4de4539dc7885e6f0f5e256704edf4c3.  This option can be
used to validate server's public key when SFTP is used.  If this
option is not set, which is default, no validation takes place.
Tatsuhiro Tsujikawa преди 10 години
родител
ревизия
c26da09687
променени са 13 файла, в които са добавени 111 реда и са изтрити 5 реда
  1. 10 0
      doc/manual-src/en/aria2c.rst
  2. 11 0
      src/OptionHandlerFactory.cc
  3. 16 0
      src/OptionHandlerImpl.cc
  4. 9 0
      src/OptionHandlerImpl.h
  5. 18 1
      src/SSHSession.cc
  6. 4 0
      src/SSHSession.h
  7. 9 1
      src/SftpNegotiationCommand.cc
  8. 4 1
      src/SftpNegotiationCommand.h
  9. 15 1
      src/SocketCore.cc
  10. 1 1
      src/SocketCore.h
  11. 2 0
      src/prefs.cc
  12. 2 0
      src/prefs.h
  13. 10 0
      src/usage_text.h

+ 10 - 0
doc/manual-src/en/aria2c.rst

@@ -592,6 +592,15 @@ FTP/SFTP Specific Options
   Reuse connection in FTP.
   Default: ``true``
 
+.. option:: --ssh-host-key-md=<TYPE>=<DIGEST>
+
+  Set checksum for SSH host public key. TYPE is hash type. The
+  supported hash type is ``sha-1`` or ``md5``. DIGEST is hex
+  digest. For example:
+  ``sha-1=b030503d4de4539dc7885e6f0f5e256704edf4c3``.  This option can
+  be used to validate server's public key when SFTP is used. If this
+  option is not set, which is default, no validation takes place.
+
 BitTorrent/Metalink Options
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 .. option:: --select-file=<INDEX>...
@@ -2030,6 +2039,7 @@ of URIs. These optional lines must start with white space(s).
   * :option:`seed-time <--seed-time>`
   * :option:`select-file <--select-file>`
   * :option:`split <-s>`
+  * :option:`ssh-host-key-md <--ssh-host-key-md>`
   * :option:`stream-piece-selector <--stream-piece-selector>`
   * :option:`timeout <-t>`
   * :option:`uri-selector <--uri-selector>`

+ 11 - 0
src/OptionHandlerFactory.cc

@@ -1506,6 +1506,17 @@ std::vector<OptionHandler*> OptionHandlerFactory::createOptionHandlers()
     op->setChangeOptionForReserved(true);
     handlers.push_back(op);
   }
+  {
+    OptionHandler* op(new ChecksumOptionHandler
+                      (PREF_SSH_HOST_KEY_MD,
+                       TEXT_SSH_HOST_KEY_MD,
+                       {"sha-1", "md5"}));
+    op->addTag(TAG_FTP);
+    op->setInitialOption(true);
+    op->setChangeGlobalOption(true);
+    op->setChangeOptionForReserved(true);
+    handlers.push_back(op);
+  }
   {
     OptionHandler* op(new DefaultOptionHandler
                       (PREF_NETRC_PATH,

+ 16 - 0
src/OptionHandlerImpl.cc

@@ -373,6 +373,16 @@ ChecksumOptionHandler::ChecksumOptionHandler
                           OptionHandler::REQ_ARG, shortName)
 {}
 
+ChecksumOptionHandler::ChecksumOptionHandler
+(PrefPtr pref,
+ const char* description,
+ std::vector<std::string> acceptableTypes,
+ char shortName)
+  : AbstractOptionHandler(pref, description, NO_DEFAULT_VALUE,
+                          OptionHandler::REQ_ARG, shortName),
+    acceptableTypes_(std::move(acceptableTypes))
+{}
+
 ChecksumOptionHandler::~ChecksumOptionHandler() {}
 
 void ChecksumOptionHandler::parseArg(Option& option, const std::string& optarg)
@@ -380,6 +390,12 @@ void ChecksumOptionHandler::parseArg(Option& option, const std::string& optarg)
 {
   auto p = util::divide(std::begin(optarg), std::end(optarg), '=');
   std::string hashType(p.first.first, p.first.second);
+  if(!acceptableTypes_.empty() &&
+     std::find(std::begin(acceptableTypes_), std::end(acceptableTypes_),
+               hashType) == std::end(acceptableTypes_)) {
+    throw DL_ABORT_EX(fmt("Checksum type %s is not acceptable",
+                          hashType.c_str()));
+  }
   std::string hexDigest(p.second.first, p.second.second);
   util::lowercase(hashType);
   util::lowercase(hexDigest);

+ 9 - 0
src/OptionHandlerImpl.h

@@ -177,10 +177,19 @@ public:
   ChecksumOptionHandler(PrefPtr pref,
                         const char* description,
                         char shortName = 0);
+  ChecksumOptionHandler(PrefPtr pref,
+                        const char* description,
+                        std::vector<std::string> acceptableTypes,
+                        char shortName = 0);
   virtual ~ChecksumOptionHandler();
   virtual void parseArg(Option& option, const std::string& optarg) const
     CXX11_OVERRIDE;
   virtual std::string createPossibleValuesString() const CXX11_OVERRIDE;
+
+private:
+  // message digest type acceptable for this option.  Empty means that
+  // it accepts all supported types.
+  std::vector<std::string> acceptableTypes_;
 };
 
 class ParameterOptionHandler : public AbstractOptionHandler {

+ 18 - 1
src/SSHSession.cc

@@ -36,6 +36,8 @@
 
 #include <cassert>
 
+#include "MessageDigest.h"
+
 namespace aria2 {
 
 SSHSession::SSHSession()
@@ -172,10 +174,25 @@ int SSHSession::handshake()
   if (rv != 0) {
     return SSH_ERR_ERROR;
   }
-  // TODO we have to validate server's fingerprint
   return SSH_ERR_OK;
 }
 
+std::string SSHSession::hostkeyMessageDigest(const std::string& hashType) {
+  int h;
+  if (hashType == "sha-1") {
+    h = LIBSSH2_HOSTKEY_HASH_SHA1;
+  } else if (hashType == "md5") {
+    h = LIBSSH2_HOSTKEY_HASH_MD5;
+  } else {
+    return "";
+  }
+  auto fingerprint = libssh2_hostkey_hash(ssh2_, h);
+  if (!fingerprint) {
+    return "";
+  }
+  return std::string(fingerprint, MessageDigest::getDigestLength(hashType));
+}
+
 int SSHSession::authPassword(const std::string& user,
                                 const std::string& password)
 {

+ 4 - 0
src/SSHSession.h

@@ -100,6 +100,10 @@ public:
   // blocks, or SSH_ERR_ERROR.
   int handshake();
 
+  // Returns message digest of host's public key.  |hashType| must be
+  // either "sha-1" or "md5".
+  std::string hostkeyMessageDigest(const std::string& hashType);
+
   // Performs authentication using username and password.  This
   // function returns SSH_ERR_OK if it succeeds, or SSH_ERR_WOULDBLOCK
   // if the underlying transport blocks, or SSH_ERR_ERROR.

+ 9 - 1
src/SftpNegotiationCommand.cc

@@ -82,6 +82,14 @@ SftpNegotiationCommand::SftpNegotiationCommand
 {
   path_ = getPath();
   setWriteCheckSocket(getSocket());
+
+  const std::string& checksum = getOption()->get(PREF_SSH_HOST_KEY_MD);
+  if (!checksum.empty()) {
+    auto p = util::divide(std::begin(checksum), std::end(checksum), '=');
+    hashType_.assign(p.first.first, p.first.second);
+    util::lowercase(hashType_);
+    digest_ = util::fromHex(p.second.first, p.second.second);
+  }
 }
 
 SftpNegotiationCommand::~SftpNegotiationCommand() {}
@@ -92,7 +100,7 @@ bool SftpNegotiationCommand::executeInternal() {
     switch(sequence_) {
     case SEQ_HANDSHAKE:
       setReadCheckSocket(getSocket());
-      if (!getSocket()->sshHandshake()) {
+      if (!getSocket()->sshHandshake(hashType_, digest_)) {
         goto again;
       }
       A2_LOG_DEBUG(fmt("CUID#%" PRId64 " - SSH handshake success", getCuid()));

+ 4 - 1
src/SftpNegotiationCommand.h

@@ -68,7 +68,10 @@ private:
   std::unique_ptr<AuthConfig> authConfig_;
   // remote file path
   std::string path_;
-
+  // expected host's public key message digest: hash type and digest
+  // (raw binary value).
+  std::string hashType_;
+  std::string digest_;
 protected:
   virtual bool executeInternal() CXX11_OVERRIDE;
 

+ 15 - 1
src/SocketCore.cc

@@ -989,7 +989,8 @@ bool SocketCore::tlsHandshake(TLSContext* tlsctx, const std::string& hostname)
 
 #ifdef HAVE_LIBSSH2
 
-bool SocketCore::sshHandshake()
+bool SocketCore::sshHandshake(const std::string& hashType,
+                              const std::string& digest)
 {
   wantRead_ = false;
   wantWrite_ = false;
@@ -1009,6 +1010,19 @@ bool SocketCore::sshHandshake()
     throw DL_ABORT_EX(fmt("SSH handshake failure: %s",
                           sshSession_->getLastErrorString().c_str()));
   }
+  if (!hashType.empty()) {
+    auto actualDigest = sshSession_->hostkeyMessageDigest(hashType);
+    if (actualDigest.empty()) {
+      throw DL_ABORT_EX(fmt("Empty host key fingerprint from SSH layer: "
+                            "perhaps hash type %s is not supported?",
+                            hashType.c_str()));
+    }
+    if (digest != actualDigest) {
+      throw DL_ABORT_EX(fmt("Unexpected SSH host key: expected %s, actual %s",
+                            util::toHex(digest).c_str(),
+                            util::toHex(actualDigest).c_str()));
+    }
+  }
   return true;
 }
 

+ 1 - 1
src/SocketCore.h

@@ -302,7 +302,7 @@ public:
 
 #ifdef HAVE_LIBSSH2
   // Performs SSH handshake
-  bool sshHandshake();
+  bool sshHandshake(const std::string& hashType, const std::string& digest);
   // Performs SSH authentication using username and password.
   bool sshAuthPassword(const std::string& user, const std::string& password);
   // Starts sftp session and open remote file |path|.

+ 2 - 0
src/prefs.cc

@@ -387,6 +387,8 @@ PrefPtr PREF_FTP_TYPE = makePref("ftp-type");
 PrefPtr PREF_FTP_PASV = makePref("ftp-pasv");
 // values: true | false
 PrefPtr PREF_FTP_REUSE_CONNECTION = makePref("ftp-reuse-connection");
+// values: hashType=digest
+PrefPtr PREF_SSH_HOST_KEY_MD = makePref("ssh-host-key-md");
 
 /**
  * HTTP related preferences

+ 2 - 0
src/prefs.h

@@ -324,6 +324,8 @@ extern PrefPtr PREF_FTP_TYPE;
 extern PrefPtr PREF_FTP_PASV;
 // values: true | false
 extern PrefPtr PREF_FTP_REUSE_CONNECTION;
+// values: hashType=digest
+extern PrefPtr PREF_SSH_HOST_KEY_MD;
 
 /**
  * HTTP related preferences

+ 10 - 0
src/usage_text.h

@@ -1030,3 +1030,13 @@
     "                              If true is given, deny legacy BitTorrent\n" \
     "                              handshake and only use Obfuscation handshake and\n" \
     "                              always encrypt message payload.")
+#define TEXT_SSH_HOST_KEY_MD                                            \
+  _(" --ssh-host-key-md=TYPE=DIGEST\n"                                  \
+    "                              Set checksum for SSH host public key. TYPE is\n" \
+    "                              hash type. The supported hash type is sha-1 or\n" \
+    "                              md5. DIGEST is hex digest. For example:\n" \
+    "                              sha-1=b030503d4de4539dc7885e6f0f5e256704edf4c3\n" \
+    "                              This option can be used to validate server's\n" \
+    "                              public key when SFTP is used. If this option is\n" \
+    "                              not set, which is default, no validation takes\n" \
+    "                              place.")