diff options
| author | Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com> | 2015-05-16 19:34:06 +0900 |
|---|---|---|
| committer | Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com> | 2015-05-16 19:43:11 +0900 |
| commit | c26da096875d609b354ea5989f62af662f9c609e (patch) | |
| tree | 2e9e4a1279d0b325aed7dc960f39ab770e5efbce /src | |
| parent | 4273885f1677579675f71667d933bd308a020fdd (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.
Diffstat (limited to 'src')
| -rw-r--r-- | src/OptionHandlerFactory.cc | 11 | ||||
| -rw-r--r-- | src/OptionHandlerImpl.cc | 16 | ||||
| -rw-r--r-- | src/OptionHandlerImpl.h | 9 | ||||
| -rw-r--r-- | src/SSHSession.cc | 19 | ||||
| -rw-r--r-- | src/SSHSession.h | 4 | ||||
| -rw-r--r-- | src/SftpNegotiationCommand.cc | 10 | ||||
| -rw-r--r-- | src/SftpNegotiationCommand.h | 5 | ||||
| -rw-r--r-- | src/SocketCore.cc | 16 | ||||
| -rw-r--r-- | src/SocketCore.h | 2 | ||||
| -rw-r--r-- | src/prefs.cc | 2 | ||||
| -rw-r--r-- | src/prefs.h | 2 | ||||
| -rw-r--r-- | src/usage_text.h | 10 |
12 files changed, 101 insertions, 5 deletions
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.") |
