mirror of https://github.com/aria2/aria2
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
parent
4273885f16
commit
c26da09687
|
@ -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>`
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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|.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.")
|
||||
|
|
Loading…
Reference in New Issue