Qtの基礎 - SSH

提供:MochiuWiki - SUSE, Electronic Circuit, PCB
2024年1月28日 (日) 00:48時点におけるWiki (トーク | 投稿記録)による版 (→‎独自クラスの使用)
ナビゲーションに移動 検索に移動

概要

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ディレクトリに配置される。

  • Visual Studioを使用する場合
    libSSH2ディレクトリと同階層にプロジェクトを作成する。
    プロジェクトのプロパティを選択して、プロパティ画面左ペインにある[General] - プロパティ画面右ペインにある[ターゲット名]を"libSSH2_x64"等と入力する。
    プロパティ画面左ペインにある[リンカ] - [Advanced] - プロパティ画面右ペインにある[インポートライブラリ]プルダウンから[<Inherit from parent or project defaults>]を選択する。


また、ビルドされたDLLファイルは、以下に示すWebサイトからダウンロードできる。
https://download.csdn.net/download/sdhongjun/15682389


libSSHの使用例

 #include <QCoreApplication>
 #include <libssh/libssh.h>
 
 int VerifyKnownsHost(ssh_session session);
 
 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;
    }
 
    // Verify the server's identity
    // For the source code of verify_knownhost(), check previous example
    if(VerifyKnownsHost(ssh_session) < 0)
    {
       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(my_ssh_session, nullptr, private_key_path, nullptr);
 
    // 秘密鍵のパスフレーズを設定している場合
    rc = ssh_userauth_privatekey_file(my_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