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.
pull/388/head
Tatsuhiro Tsujikawa 2015-05-16 19:34:06 +09:00
parent 4273885f16
commit c26da09687
13 changed files with 111 additions and 5 deletions

View File

@ -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>`

View File

@ -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,

View File

@ -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);

View File

@ -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 {

View File

@ -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)
{

View File

@ -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.

View File

@ -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()));

View File

@ -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;

View File

@ -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;
}

View File

@ -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|.

View File

@ -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

View File

@ -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

View File

@ -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.")