「Qtの基礎 - パケット」の版間の差分
4行目: | 4行目: | ||
== SSLを使用した暗号化 == | == SSLを使用した暗号化 == | ||
==== 暗号化 ==== | |||
<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();
}