「Qtの基礎 - パケット」の版間の差分

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動
4行目: 4行目:


== SSLを使用した暗号化 ==
== SSLを使用した暗号化 ==
以下の例では、OpenSSLライブラリとQTCPSocketクラスを使用して、パケットを暗号化して送信している。<br>
==== 暗号化 ====
<code>QSslSocket</code>クラスは、暗号化されたデータの送信に使用できる安全で暗号化されたTCP接続を確立する。<br>
クライアントモードとサーバーモードの両方で動作し、TLS 1.3を含む最新のTLSプロトコルをサポートしている。<br>
<br>
デフォルトでは、<code>QSslSocket</code>クラスは、安全であると考えられているTLSプロトコル(<code>QSsl::SecureProtocols</code>)のみを使用するが、<br>
ハンドシェイクが始まる前に行う限り、<code>setProtocol</code>メソッドを呼び出してTLSプロトコルを変更することが可能である。<br>
<br>
SSL暗号化は、ソケットが<code>ConnectedState</code>になった後、既存のTCPストリームの上で動作する。<br>
<br>
<code>QSslSocket</code>クラスを使用して安全な接続を確立するには、以下に示す2つの方法がある。<br>
* 即座にSSLハンドシェイクを行う方法
* 非暗号化モードで接続が確立された後、遅延SSLハンドシェイクを行う方法
<br>
<code>QSslSocket</code>クラスの最も一般的な使用方法は、<code>QSslSocket</code>クラスのインスタンスを生成して、<code>connectToHostEncrypted</code>メソッドを呼び出して、安全な接続を開始する方法である。<br>
<code>connectToHostEncrypted</code>メソッドは、接続が確立されると即座にSSLハンドシェイクを開始する。<br>
<syntaxhighlight lang="c++">
auto pSocket = std::make_unique<QSslSocket>(this);
connect(pSocket, &QSslSocket::encrypted, this, &CSampleClass::ready);
pSocket->connectToHostEncrypted("example.com", 993);
</syntaxhighlight>
<br>
プレーンな<code>QTcpSocket</code>クラスと同様、<code>QSslSocket</code>クラスは<code>HostLookupState</code> -> <code>ConnectingState</code>になり、接続が成功すると<code>ConnectedState</code>になる。<br>
その後、自動的にハンドシェイクが開始されて、成功するとソケットが暗号化状態になり、使用可能な状態になったことを示す<code>encrypted</code>シグナルが送信される。<br>
<u>この時、<code>connectToHostEncrypted</code>メソッドのリターン直後(<code>encrypted</code>シグナルが送信される前)に、ソケットにデータを書き込むことができることに注意する。</u><br>
<br>
データは、<code>encrypted</code>シグナルが送信されるまで、<code>QSslSocket</code>クラスにキューイングされる。<br<
<br>
既存の接続を保護するために遅延SSLハンドシェイクを使用する例として、SSLサーバが着信接続を保護する場合がある。<br>
<br>
以下の例のように、<code>QTcpServer</code>クラスを継承して、SslServerクラスを定義したとする。<br>
まず、<code>QSslSocket</code>クラスのインスタンスを生成した後、<code>setSocketDescriptor</code>メソッドを呼んで、新しいソケットのディスクリプタを渡された既存のものに設定する。<br>
そして、<code>QTcpServer::incomingConnection</code>メソッドをオーバーライドして、そのメソッド内で<code>startServerEncryption</code>メソッドを呼び出すことにより、SSLハンドシェイクを開始する。<br>
<syntaxhighlight lang="c++">
void SslServer::incomingConnection(qintptr socketDescriptor)
{
    auto pServerSocket = std::make_unique<QSslSocket>();
    if(pServerSocket->setSocketDescriptor(socketDescriptor))
    {
      addPendingConnection(pServerSocket);
      connect(pServerSocket, &QSslSocket::encrypted, this, &SslServer::ready);
      pServerSocket->startServerEncryption();
    }
}
</syntaxhighlight>
<br>
エラーが発生した場合、<code>QSslSocket</code>クラスは<code>sslErrors</code>シグナルを送信する。<br>
この時、エラーを無視するためのアクションが取られない場合は、接続は切断される。<br>
<br>
エラーが発生しても接続を継続する場合は、エラー発生後にこのスロット内、または、<code>QSslSocket</code>クラスのインスタンス生成後、接続が試行される前であれば<code>ignoreSslErrors</code>メソッドを呼び出すことができる。<br>
これは、<code>QSslSocket</code>クラスが相手のIDを確立する時に遭遇したエラーを無視することができる。<br>
<br>
安全な接続は成功したハンドシェイクで確立されるべきであるため、SSLハンドシェイク中のエラーを無視することは慎重になるべきである。<br>
<br>
1度暗号化されると、<code>QSslSocket</code>クラスは通常の<code>QTcpSocket</code>クラスとして使用される。<br>
<code>readyRead</code>シグナルが送信される時、<code>read</code>メソッド、<code>canReadLine</code>メソッド、<code>readLine</code>メソッド、<code>getChar</code>メソッドを使用して<code>QSslSocket</code>クラスの内部バッファから復号したデータを読み、<br>
<code>write</code>メソッド、<code>putChar</code>メソッドを使用して相手にデータを書き戻すことができる。<br>
<br>
<code>QSslSocket</code>クラスは、書き込まれたデータを自動的に暗号化して、データがピアに書き込まれると<code>encryptedBytesWritten</code>シグナルを送信する。<br>
<br>
便利なことに、<code>QSslSocket</code>クラスは、<code>QTcpSocket</code>クラスのブロッキングメソッドである以下に示すメソッドが存在する。<br>
* <code>waitForConnected</code>メソッド
* <code>waitForReadyRead</code>メソッド
* <code>waitForBytesWritten</code>メソッド
* <code>waitForDisconnected</code>メソッド
<br>
また、暗号化された接続が確立されるまで呼び出し側のスレッドをブロックする<code>waitForEncrypted</code>メソッドも提供されている。<br>
<br>
<syntaxhighlight lang="c++">
QSslSocket socket;
socket.connectToHostEncrypted("http.example.com", 443);
if(!socket.waitForEncrypted())
{
    qDebug() << socket.errorString();
    return false;
}
socket.write("GET / HTTP/1.0\r\n\r\n");
while(socket.waitForReadyRead())
{
    qDebug() << socket.readAll().data();
}
</syntaxhighlight>
<br>
==== サンプルコード ====
以下の例では、OpenSSLライブラリと<code>QTcpSocket</code>クラスを使用して、パケットを暗号化して送信している。<br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  #include <QTcpSocket>
  #include <QTcpSocket>

2023年2月11日 (土) 21:55時点における版

概要



SSLを使用した暗号化

暗号化

QSslSocketクラスは、暗号化されたデータの送信に使用できる安全で暗号化されたTCP接続を確立する。
クライアントモードとサーバーモードの両方で動作し、TLS 1.3を含む最新のTLSプロトコルをサポートしている。

デフォルトでは、QSslSocketクラスは、安全であると考えられているTLSプロトコル(QSsl::SecureProtocols)のみを使用するが、
ハンドシェイクが始まる前に行う限り、setProtocolメソッドを呼び出してTLSプロトコルを変更することが可能である。

SSL暗号化は、ソケットがConnectedStateになった後、既存のTCPストリームの上で動作する。

QSslSocketクラスを使用して安全な接続を確立するには、以下に示す2つの方法がある。

  • 即座にSSLハンドシェイクを行う方法
  • 非暗号化モードで接続が確立された後、遅延SSLハンドシェイクを行う方法


QSslSocketクラスの最も一般的な使用方法は、QSslSocketクラスのインスタンスを生成して、connectToHostEncryptedメソッドを呼び出して、安全な接続を開始する方法である。
connectToHostEncryptedメソッドは、接続が確立されると即座にSSLハンドシェイクを開始する。

 auto pSocket = std::make_unique<QSslSocket>(this);
 connect(pSocket, &QSslSocket::encrypted, this, &CSampleClass::ready);
 
 pSocket->connectToHostEncrypted("example.com", 993);


プレーンなQTcpSocketクラスと同様、QSslSocketクラスはHostLookupState -> ConnectingStateになり、接続が成功するとConnectedStateになる。
その後、自動的にハンドシェイクが開始されて、成功するとソケットが暗号化状態になり、使用可能な状態になったことを示すencryptedシグナルが送信される。
この時、connectToHostEncryptedメソッドのリターン直後(encryptedシグナルが送信される前)に、ソケットにデータを書き込むことができることに注意する。

データは、encryptedシグナルが送信されるまで、QSslSocketクラスにキューイングされる。<br<
既存の接続を保護するために遅延SSLハンドシェイクを使用する例として、SSLサーバが着信接続を保護する場合がある。

以下の例のように、QTcpServerクラスを継承して、SslServerクラスを定義したとする。
まず、QSslSocketクラスのインスタンスを生成した後、setSocketDescriptorメソッドを呼んで、新しいソケットのディスクリプタを渡された既存のものに設定する。
そして、QTcpServer::incomingConnectionメソッドをオーバーライドして、そのメソッド内でstartServerEncryptionメソッドを呼び出すことにより、SSLハンドシェイクを開始する。

 void SslServer::incomingConnection(qintptr socketDescriptor)
 {
    auto pServerSocket = std::make_unique<QSslSocket>();
    if(pServerSocket->setSocketDescriptor(socketDescriptor))
    {
       addPendingConnection(pServerSocket);
       connect(pServerSocket, &QSslSocket::encrypted, this, &SslServer::ready);
       pServerSocket->startServerEncryption();
    }
 }


エラーが発生した場合、QSslSocketクラスはsslErrorsシグナルを送信する。
この時、エラーを無視するためのアクションが取られない場合は、接続は切断される。

エラーが発生しても接続を継続する場合は、エラー発生後にこのスロット内、または、QSslSocketクラスのインスタンス生成後、接続が試行される前であればignoreSslErrorsメソッドを呼び出すことができる。
これは、QSslSocketクラスが相手のIDを確立する時に遭遇したエラーを無視することができる。

安全な接続は成功したハンドシェイクで確立されるべきであるため、SSLハンドシェイク中のエラーを無視することは慎重になるべきである。

1度暗号化されると、QSslSocketクラスは通常のQTcpSocketクラスとして使用される。
readyReadシグナルが送信される時、readメソッド、canReadLineメソッド、readLineメソッド、getCharメソッドを使用してQSslSocketクラスの内部バッファから復号したデータを読み、
writeメソッド、putCharメソッドを使用して相手にデータを書き戻すことができる。

QSslSocketクラスは、書き込まれたデータを自動的に暗号化して、データがピアに書き込まれるとencryptedBytesWrittenシグナルを送信する。

便利なことに、QSslSocketクラスは、QTcpSocketクラスのブロッキングメソッドである以下に示すメソッドが存在する。

  • waitForConnectedメソッド
  • waitForReadyReadメソッド
  • waitForBytesWrittenメソッド
  • waitForDisconnectedメソッド


また、暗号化された接続が確立されるまで呼び出し側のスレッドをブロックするwaitForEncryptedメソッドも提供されている。

 QSslSocket socket;
 socket.connectToHostEncrypted("http.example.com", 443);
 if(!socket.waitForEncrypted())
 {
    qDebug() << socket.errorString();
    return false;
 }
 
 socket.write("GET / HTTP/1.0\r\n\r\n");
 while(socket.waitForReadyRead())
 {
    qDebug() << socket.readAll().data();
 }


サンプルコード

以下の例では、OpenSSLライブラリとQTcpSocketクラスを使用して、パケットを暗号化して送信している。

 #include <QTcpSocket>
 #include <QSslSocket>
 #include <QSslKey>
 #include <QSslCertificate>
 #include <QFile>
 
 // 暗号化
 void encryptPacket(QByteArray &packet)
 {
    // OpenSSLライブラリの初期化
    SSL_load_error_strings();
    SSL_library_init();
 
    // 公開鍵と秘密鍵を読み込む
    QSslKey privateKey("server.key", QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
    QSslCertificate publicKey("server.crt", QSsl::Pem);
 
    // QSslSocketオブジェクトの生成
    QSslSocket socket;
    socket.setPrivateKey(privateKey);
    socket.setLocalCertificate(publicKey);
    socket.connectToHostEncrypted("example.com", 443);
 
    // パケットの暗号化
    packet = socket.encrypt(packet);
 }
 
 int main()
 {
    // パケットの生成
    QByteArray packet = "Hello, world!";
 
    // パケットの暗号化
    encryptPacket(packet);
 
    // 暗号化されたパケットの送信
    QTcpSocket socket;
    socket.connectToHost("example.com", 1234);  // ホスト名とTCPポート番号の指定
    socket.write(packet);
 
    // 送信が完了するまで待機
    socket.waitForBytesWritten();
 
    return 0;
 }


以下の例では、暗号化されたパケットを送信するために、TCPソケットとパケットデータを引数として受け取り、OpenSSLライブラリを使用してSSLセッションを確立して、パケットを暗号化して送信する。
SSLセッションは、QSslSocketクラスを使用して確立される。

この時、事前に、サーバの証明書、クライアントの秘密鍵、サーバの公開鍵をファイルから読み込む必要がある。

 #include <QTcpSocket>
 #include <QSslSocket>
 #include <QSslKey>
 #include <QSslCertificate>
 #include <QFile>
 
 void sendEncryptedPacket(QTcpSocket *socket, const QByteArray &packet)
 {
    // OpenSSLライブラリの初期化
    SSL_load_error_strings();
    SSL_library_init();
 
    // SSLコンテキストを生成
    SSL_CTX *ctx = SSL_CTX_new(TLSv1_2_client_method());
 
    // サーバの証明書を読み込む
    QFile certFile("server.crt");
    certFile.open(QIODevice::ReadOnly);
    QSslCertificate cert(certFile.readAll());
    certFile.close();
    SSL_CTX_use_certificate(ctx, cert.handle());
 
    // クライアントの秘密鍵を読み込む
    QFile keyFile("client.key");
    keyFile.open(QIODevice::ReadOnly);
    QSslKey key(keyFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, "my passphrase");
    keyFile.close();
    SSL_CTX_use_PrivateKey(ctx, key.handle());
 
    // サーバの公開鍵を読み込む
    QFile pubkeyFile("server.pub");
    pubkeyFile.open(QIODevice::ReadOnly);
    QSslCertificate pubkey(pubkeyFile.readAll());
    pubkeyFile.close();
    SSL_CTX_add_extra_chain_cert(ctx, pubkey.handle());
 
    // SSLセッションの確立
    QSslSocket sslSocket;
    sslSocket.setSocketDescriptor(socket->socketDescriptor());
    sslSocket.setProtocol(QSsl::TlsV1_2);
    sslSocket.setLocalCertificate(cert);
    sslSocket.setPrivateKey(key);
    sslSocket.addCaCertificate(pubkey);
    sslSocket.startClientEncryption();
 
    // パケットの暗号化
    QByteArray encryptedPacket = sslSocket.encrypt(packet);
 
    // 暗号化されたパケットの送信
    socket->write(encryptedPacket);
    socket->flush();
    socket->waitForBytesWritten();
 
    // SSLセッションの終了
    sslSocket.close();
 }