summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>2015-05-16 19:34:06 +0900
committerTatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>2015-05-16 19:43:11 +0900
commitc26da096875d609b354ea5989f62af662f9c609e (patch)
tree2e9e4a1279d0b325aed7dc960f39ab770e5efbce
parent4273885f1677579675f71667d933bd308a020fdd (diff)
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.
-rw-r--r--doc/manual-src/en/aria2c.rst10
-rw-r--r--src/OptionHandlerFactory.cc11
-rw-r--r--src/OptionHandlerImpl.cc16
-rw-r--r--src/OptionHandlerImpl.h9
-rw-r--r--src/SSHSession.cc19
-rw-r--r--src/SSHSession.h4
-rw-r--r--src/SftpNegotiationCommand.cc10
-rw-r--r--src/SftpNegotiationCommand.h5
-rw-r--r--src/SocketCore.cc16
-rw-r--r--src/SocketCore.h2
-rw-r--r--src/prefs.cc2
-rw-r--r--src/prefs.h2
-rw-r--r--src/usage_text.h10
13 files changed, 111 insertions, 5 deletions
diff --git a/doc/manual-src/en/aria2c.rst b/doc/manual-src/en/aria2c.rst
index c9da0644..280e454e 100644
--- a/doc/manual-src/en/aria2c.rst
+++ b/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>`
diff --git a/src/OptionHandlerFactory.cc b/src/OptionHandlerFactory.cc
index 1e368100..768d8471 100644
--- a/src/OptionHandlerFactory.cc
+++ b/src/OptionHandlerFactory.cc
@@ -1507,6 +1507,17 @@ std::vector<OptionHandler*> OptionHandlerFactory::createOptionHandlers()
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,
TEXT_NETRC_PATH,
diff --git a/src/OptionHandlerImpl.cc b/src/OptionHandlerImpl.cc
index adf751f8..f0771d8c 100644
--- a/src/OptionHandlerImpl.cc
+++ b/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);
diff --git a/src/OptionHandlerImpl.h b/src/OptionHandlerImpl.h
index faea9a27..7dc0295c 100644
--- a/src/OptionHandlerImpl.h
+++ b/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 {
diff --git a/src/SSHSession.cc b/src/SSHSession.cc
index 94abcece..1ee3c07d 100644
--- a/src/SSHSession.cc
+++ b/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)
{
diff --git a/src/SSHSession.h b/src/SSHSession.h
index 4e19c5df..87234eee 100644
--- a/src/SSHSession.h
+++ b/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.
diff --git a/src/SftpNegotiationCommand.cc b/src/SftpNegotiationCommand.cc
index ee836cf1..2bfdad96 100644
--- a/src/SftpNegotiationCommand.cc
+++ b/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()));
diff --git a/src/SftpNegotiationCommand.h b/src/SftpNegotiationCommand.h
index a6d1045c..3ce11d40 100644
--- a/src/SftpNegotiationCommand.h
+++ b/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;
diff --git a/src/SocketCore.cc b/src/SocketCore.cc
index 320f04a6..701fc33e 100644
--- a/src/SocketCore.cc
+++ b/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;
}
diff --git a/src/SocketCore.h b/src/SocketCore.h
index 065e120f..bc6d1daa 100644
--- a/src/SocketCore.h
+++ b/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|.
diff --git a/src/prefs.cc b/src/prefs.cc
index 903e9d5f..a3046b54 100644
--- a/src/prefs.cc
+++ b/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
diff --git a/src/prefs.h b/src/prefs.h
index 2b3ade6d..66eda2fb 100644
--- a/src/prefs.h
+++ b/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
diff --git a/src/usage_text.h b/src/usage_text.h
index 64f64a43..d595c233 100644
--- a/src/usage_text.h
+++ b/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.")