Qtの基礎 - SSH
概要
Qtにおいて、簡潔にSSH接続を確立する手順を記載する。
- libsshまたはlibssh2を使用する。
- QProcessクラスを使用して、外部のSSHプロセスを呼び出して接続を確立する。
- 他のSSHライブラリを購入する。
http://netsieben.com/products/ssh
libSSHとlibSSH2の比較
libSSHとlibSSH2は、どちらもSSHプロトコルを実装したライブラリである。
libSSHはC言語、libSSH2はC++言語で記述されている。
また、libSSHは単独のライブラリとして提供されているのに対して、libSSH2はlibSSL等の他のライブラリと組み合わせて使用することが多い。
プロジェクトの起源と開発者
- libSSH
- libSSHは、フランスのBenjamin GilbertとArel Corderoによって開発された。
- libSSHは、SSHプロトコルの実装を提供しており、クライアントおよびサーバの機能をサポートしている。
- libSSH2
- libSSH2は、curlライブラリの主要な開発者でもあるスウェーデンのDaniel Stenbergによって開発された。
- libSSH2は、SSHのクライアントおよびサーバ機能を提供することを目的としている。
- ただし、libSSH2はサーバサイドのサポートは行っていない。
使用されるコードベース
- libSSH
- libSSHは、元々PuTTYの一部として開発され、その後独立したプロジェクトになった。
- PuTTYは、Windows環境でのSSHクライアントとして広く知られている。
- libSSH2
- libSSH2は、curlライブラリとは独立したプロジェクトとして開発されている。
項目 | libSSH2 | libSSH |
---|---|---|
ライブラリ名 | libssh2.so | libssh.so |
ライセンス | 3条項BSD | LGPL 2.1 |
サーバサイドのサポート | No | Yes |
GSSAPI認証 | No | Yes |
楕円曲線鍵の交換 | No | Yes |
楕円曲線鍵のホスト鍵 | No | Yes |
ナイトリーテストによるテストケースの自動化 | No | Yes |
Stable API | Yes | ほとんどの部分 |
C言語との互換 | C89 | C99 |
厳格な名前空間 | Yes | Yes |
全ての関数のマニュアル | Yes | No |
全ての関数のDoxygenドキュメント | No | Yes |
Tutorial | Yes | Yes |
SSH v1のサポート | No | Yes |
libSSHライブラリのインストール
libSSHとは
libSSHの多くは、LGPL 2.1ライセンスである。
一部の機能には、2条項BSDライセンスのものがある。
パッケージ管理システムからインストール
sudo zypper install libssh-devel
ソースコードからインストール
libSSHのビルドに必要なライブラリをインストールする。
sudo zypper install zlib-devel readline-devel libpcap-devel libopenssl-devel libopenssl-1_1-devel libgcrypt-devel p11-kit-devel libsodium-devel libcmocka-devel doxygen \ # 以下に示すライブラリは不要の可能性あり openpgm-devel ldns-devel zeromq-devel unbound-devel libunwind-devel \ libheimdal-devel libgssglue-devel gssntlmssp-devel
libSSHの公式WebサイトまたはlibSSH向けのGitにアクセスして、libSSHのソースコードをダウンロードする。
ダウンロードしたファイルを解凍する。
tar xf libssh-<バージョン>.tar.xz cd libssh-<バージョン>
libSSHをビルドおよびインストールする。
mkdir build && cd build cmake .. \ -DCMAKE_INSTALL_PREFIX=/<libSSHのインストールディレクトリ> -DCMAKE_BUILD_TYPE=Release \ -DWITH_GSSAPI=ON -DWITH_DSA=ON -DWITH_GCRYPT=ON \ # オプション -DWITH_PKCS11_URI=ON -DWITH_BLOWFISH_CIPHER=ON \ # オプション -DCMAKE_C_COMPILER=/<GCCのインストールディレクトリ>/bin/gcc \ # オプション -DCMAKE_CXX_COMPILER=/<GCCのインストールディレクトリ>/bin/g++ # オプション make -j $(nproc) make install
libSSHの使用例は、以下に示すURLを参考にすること。
https://api.libssh.org/stable/libssh_tutorial.html
libSSH2ライブラリのインストール
libSSH2とは
libSSH2は、3条項BSDライセンスである。(4条項BSDライセンスから、3番目にあった「宣伝条項」を削除したもの)
これにより、GPLと互換性が生まれたため、BSDライセンスのソフトウェアをGPLで配布することができる。(その逆は不可)
パッケージ管理システムからインストール
sudo zypper install libssh2-devel
ソースコードからインストール
libSSH2のビルドに必要なライブラリをインストールする。
sudo zypper install zlib-devel libopenssl-devel libopenssl-1_1-devel
libSSH2の公式WebサイトまたはGithubにアクセスして、libSSH2のソースコードをダウンロードする。
ダウンロードしたソースコードを解凍する。
tar xf libssh2.tar.xz cd libssh2
libSSH2をビルドおよびインストールする。
mkdir build && cd build # configureスクリプトを使用する場合 ../cofigur --prefix=<libSSH2のインストールディレクトリ> \ --enable-examples-build --disable-debug --with-libz make -j $(nproc) make install # CMakeを使用する場合 cmake .. \ -DCMAKE_INSTALL_PREFIX=/<libssh2のインストールディレクトリ> \ -DLINT=ON -DBUILD_SHARED_LIBS=ON -DCLEAR_MEMORY=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_DEBUG_LOGGING=OFF \ -DCMAKE_C_COMPILER=/<GCCのインストールディレクトリ>/bin/gcc \ # オプション -DCMAKE_CXX_COMPILER=/<GCCのインストールディレクトリ>/bin/g++ # オプション make -j $(nproc) make install
libSSH2の使用例は、以下に示すURLを参考にすること。
http://www.chaosstuff.com/2013/09/gnome-mplayer-remote-with-qt-and-libssh2.html
https://bitbucket.org/nchokoev/qtsshremote
Windowsの場合、CMakeおよびVisual Studioを使用してlibSSH2をビルドおよびインストールすることができる。
- CMakeを使用する場合
- 32ビット Windows
cmake -DBUILD_SHARED_LIBS=ON -DBUILD_EXAMPLES=OFF -DBUILD_TESTING=OFF -A Win32 .. -B "x86"
cmake --build x86 --config Release
- 64ビット Windows
cmake -DBUILD_SHARED_LIBS=ON -DBUILD_EXAMPLES=OFF -DBUILD_TESTING=OFF -A x64 .. -B "x64"
cmake --build x64 --config Release
- コンパイルされたDLLファイルとlibファイルは、x86とx64のディレクトリのsrc/Releaseディレクトリに配置される。
- 32ビット Windows
- Visual Studioを使用する場合
- libSSH2ディレクトリと同階層にプロジェクトを作成する。
- プロジェクトのプロパティを選択して、プロパティ画面左ペインにある[General] - プロパティ画面右ペインにある[ターゲット名]を"libSSH2_x64"等と入力する。
- プロパティ画面左ペインにある[リンカ] - [Advanced] - プロパティ画面右ペインにある[インポートライブラリ]プルダウンから[<Inherit from parent or project defaults>]を選択する。
また、ビルドされたDLLファイルは、以下に示すWebサイトからダウンロードできる。
https://download.csdn.net/download/sdhongjun/15682389
libSSHの使用例
以下の例では、公開鍵認証を使用してリモートPCへSSH接続している。
# .pro プロジェクトファイル
# pkg-configを使用する場合
CONFIG += link_pkgconfig
LIBS += \
-L/<libSSHのインストールディレクトリ>/lib64 -lssh
# pkg-configを使用しない場合
LIBS += \
-L/<libSSHのインストールディレクトリ>/lib64 -lssh
INCLUDEPATH += \
/<libSSHのインストールディレクトリ>/include
#include <QCoreApplication>
#include <QMessageBox>
#include <libssh/libssh.h>
int VerifyKnownsHost(ssh_session session, QString &strErrMsg);
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// SSHセッションの作成
ssh_session ssh_session = ssh_new();
if (my_ssh_session == NULL) {
// 作成に失敗した場合
return -1;
}
// SSHセッションの設定
QString host = "<リモートPCのIPアドレス または ホスト名>";
QString user = "<リモートPCのユーザ名>";
QString port = "<SSHポート番号 例: 22>";
ssh_options_set(ssh_session, SSH_OPTIONS_HOST, host.toUtf8().data());
ssh_options_set(ssh_session, SSH_OPTIONS_USER, user.toUtf8().data());
ssh_options_set(ssh_session, SSH_OPTIONS_PORT_STR, port.toUtf8().data());
// SSH接続
int rc = ssh_connect(ssh_session);
if (rc != SSH_OK) {
// 接続に失敗した場合
fprintf(stderr, "Error connecting to host: %s\n", ssh_get_error(ssh_session));
ssh_free(ssh_session);
return -1;
}
// ~/.sshディレクトリ等にあるファイルに記述されているサーバのIDを検証
QString strErrMsg = "";
if(VerifyKnownsHost(ssh_session, strErrMsg) < 0)
{
fprintf(stderr, "%s\n", strErrMsg.toUtf8().constData();
if(ssh_session != nullptr)
{
ssh_disconnect(ssh_session);
ssh_free(ssh_session);
}
return -1;
}
// 公開鍵認証
// 秘密鍵の設定
const char *private_key_path = "<秘密鍵のパス 例: /home/user/sshkey/id_rsa";
// 秘密鍵のパスフレーズを設定していない場合
rc = ssh_userauth_privatekey_file(ssh_session, nullptr, private_key_path, nullptr);
// 秘密鍵のパスフレーズを設定している場合
rc = ssh_userauth_privatekey_file(ssh_session, nullptr, private_key_path, "<秘密鍵のパスフレーズ>");
if (rc != SSH_AUTH_SUCCESS) {
// 認証に失敗した場合
fprintf(stderr, "Error authenticating with private key: %s\n", ssh_get_error(my_ssh_session));
ssh_disconnect(my_ssh_session);
ssh_free(my_ssh_session);
return -1;
}
// SSHセッションを使用して任意の処理を実行
// scpコマンドの実行、または、リモート先で任意のコマンドの実行等
// ...略
// SSH接続の切断
ssh_disconnect(ssh_session);
// SSHセッションの解放
ssh_free(ssh_session);
return 0;
}
int VerifyKnownsHost(ssh_session ssh_session, QString &strErrMsg)
{
// Authenticating the server.
ssh_key srv_pubkey = {};
if(ssh_get_server_publickey(ssh_session, &srv_pubkey) < 0)
{
strErrMsg = tr("Failed to get public key.");
return -1;
}
unsigned char *hash = nullptr;
size_t hlen = 0L;
auto iRet = ssh_get_publickey_hash(srv_pubkey, SSH_PUBLICKEY_HASH_SHA256, &hash, &hlen);
ssh_key_free(srv_pubkey);
if(iRet < 0)
{
strErrMsg = tr("Failed to get public key hash.");
return -1;
}
auto state = ssh_session_is_known_server(ssh_session);
if(state == ssh_known_hosts_e::SSH_KNOWN_HOSTS_OK)
{ // Authentication Successful
}
else if(state == ssh_known_hosts_e::SSH_KNOWN_HOSTS_CHANGED)
{
QString strHexa = ssh_get_hexa(hash, hlen);
// print string in reverse order
strErrMsg = tr("Host key for server changed:") + "<br>" +
tr("For security reasons, connection will be stopped.") + "<br><br>" +
tr("Public key hash:") + "<br>" + strHexa + "<br>" + hlen;
ssh_clean_pubkey_hash(&hash);
return -1;
}
else if(state == ssh_known_hosts_e::SSH_KNOWN_HOSTS_OTHER)
{
strErrMsg = tr("The host key for this server was not found but an other type of key exists.") + "<br>" +
tr("An attacker might change the default server key to confuse your client into") + "<br>" +
tr("thinking the key does not exist");
ssh_clean_pubkey_hash(&hash);
return -1;
}
else if(state == ssh_known_hosts_e::SSH_KNOWN_HOSTS_NOT_FOUND)
{
/* FALL THROUGH to SSH_KNOWN_HOSTS_UNKNOWN behavior */
QString strHexa = ssh_get_hexa(hash, hlen);
QString strAddHostMessage = tr("Could not find known host file.") + "\n" +
tr("If you accept the host key here, the file will be automatically created.") + "\n\n" +
tr("The server is unknown. Do you trust the host key?") + "\n" +
tr("Public key hash: ") + "\n" + strHexa;
auto ret = QMessageBox(QMessageBox::Warning, QMessageBox::tr("Add Host"), strAddHostMessage,
QMessageBox::Yes | QMessageBox::No, nullptr).exec();
ssh_clean_pubkey_hash(&hash);
if(ret == QMessageBox::No)
{
strErrMsg = tr("To connect, please add host key.");
return -1;
}
else
{
iRet = ssh_session_update_known_hosts(ssh_session);
if(iRet < 0)
{
strErrMsg = tr("Failed to update host key.");
return -1;
}
}
}
else if(state == ssh_known_hosts_e::SSH_KNOWN_HOSTS_UNKNOWN)
{
QString strHexa = ssh_get_hexa(hash, hlen);
QString strAddHostMessage = tr("The server is unknown. Do you trust the host key?") + "\n" +
tr("Public key hash: ") + "\n" + strHexa;
auto msgRet = QMessageBox(QMessageBox::Warning, QMessageBox::tr("Add Host"), strAddHostMessage,
QMessageBox::Yes | QMessageBox::No, nullptr).exec();
ssh_clean_pubkey_hash(&hash);
if(msgRet == QMessageBox::No)
{
strErrMsg = tr("To connect, please add host key.");
return -1;
}
else
{
iRet = ssh_session_update_known_hosts(ssh_session);
if(iRet < 0)
{
strErrMsg = tr("Failed to update host key.");
return -1;
}
}
}
else if(state == ssh_known_hosts_e::SSH_KNOWN_HOSTS_ERROR)
{
strErrMsg = tr("There was an error in checking the host.");
ssh_clean_pubkey_hash(&hash);
return -1;
}
ssh_clean_pubkey_hash(&hash);
return 0;
}
独自クラスの使用
以下に示すクラスは、クロスプラットフォームの非同期SSHおよびSCPソケットである。
このクラスは、libSSHを必要とする。(RSA鍵の受け渡しを隠したり、コマンドの応答をreadyReadシグナル経由ではなく、シングルショットで送信している)
Windowsで動作させるには、このクラスを使用するファイルの最上部にインクルードする必要がある。
これにより、QtがWindowsソケットをインクルードする前にWindows.hファイルをインクルードさせないようにする。
ファイル:CSSH.zip