ソースが全然洗われない迷走ブログ

09/08/23: QHttpとQSslSocketでHTTPS通信

Category: C++ ( Qt )
Posted by: okayu

 QtのQHttpを使ってHTTPS通信させてみた。

 結果を先にいうと、実用は難しいかも(自分が使いこなせてないだけかも知れない)。
SSLのセッション再開をさせることができなかった。
そうすると接続する度に鍵を交換することになって、サーバーの負荷がそりゃもう大変なことに。




■QSslSocketがない…

 VCのエディタでQSslSocketのオブジェクトを作って、メソッドを呼び出そう、…としてメンバヒントが出ないことに気付く。
またインテリセンスが壊れたかと、ncbファイルを消してから再度VCを起動。
…出ない。
まぁそれもVCでは日常茶飯事なので気にしなかったんだけど、嫌な予感がして試しにビルド。
「QSslSocketなんてねーよ!」と言われる。ガッデム!

 どうやらQtのSSLサポートはオプションだったらしい。
しかもOpenSSLが別途必要になるみたい。


■OpenSSLのインストール

 仕方なくOpenSSLをダウンロードしてインストール、…しようと思ったら今度は別途Perlが必要とか。
あぁなるほど、「Perlはいい、ライトワンスランエニウェア」ってことか。
ActivePerlなんて、今使ってるマシンに入れてね―よ…

 ActivePerlをインストールすると、今度はVC用のコンフィグ。
内部でアセンブラを使用させることができるらしい。
NASMとMASMを指定できる。
NASMってよく知らないんで、とりあえずMASMを指定…。
「コマンド、またはバッチファイル 'ml' が見つかりません」
あ?
嘘だろ承り太郎!?
VC++にMASMが入ってない?

 そう、Visual Studio 2005のStandardにはMASMは入っていない。
どうもMSもその認識がないようで、Express用に別途用意されているMASMを独自に追加インストールする必要があるらしい。
仕方なくそいつを入れて、今度こそOpenSSLのインストール。

 Pathを通して、再度Qtのコンフィグに「-openssl」を追加してビルド。
1行で「ビルド」とか書いてるけど、その間3時間半。

 ようやくビルドが終わって、恐る恐る「QSslSocket::」と入力。
…あぁやっと出た、メンバ一覧が。


■サーバー認証させてみる

基本的には、
・QSslSocketオブジェクトに信頼させる認証局の証明書を渡す(QSslSocket::setCaCertificates())。
・QHttp::setSocket()にQSslSocketオブジェクトを渡す。
・QHttp::setHost()またはコンストラクタのモードをHTTPSにする。
で、HTTPS通信でサーバー認証させられる。

HTTP通信ラッパを作るとして、コードはこんな感じ?


メンバ変数
QSslSocket            _sslSocket;        //    QSslSocketオブジェクト
QSslCertificate        _sslCert;        //    CA証明書オブジェクト
QHttp                _http;            //    QHttpオブジェクト


コンストラクタ
{
    //    CA証明書ファイル読み込み → arrCert
    QFile    certFile(QString("path/to/certFile.pem"));
    certFile.open(QIODevice::ReadOnly);
    QByteArray arrCert = certFile.readAll();
    certFile.close();
    
    
    //    QSslCertificate作成 → _sslCert
    this->_sslCert = QSslCertificate(arrCert);
    ifthis->_sslCert.isNull() == true )
    {
        //    エラー処理
    }
    
    
    //    QSslSocket初期化
    QList<QSslCertificate> certs;
    certs.append(this->_sslCert);
    
    this->_sslSocket.setCaCertificates(certs);    //    信頼させる認証局の証明書を設定
    this->_sslSocket.setProtocol(QSsl::TlsV1);    //    TLSv1限定
    
    
    //    QHttp初期化 Scheme=HTTPS:Port=443
    this->_http.setHost(QString("address.to.server"),QHttp::ConnectionModeHttps,443);
    this->_http.setSocket(&this->_sslSocket);,
}




■パケットを覗くと何か変? その1 Encrypted Alert

 上記コードで動かすと、一応通信は出来るようになる。
念のためWiresharkでパケットを覗く。

「Encrypted Alert」

あん?
通信の最後にサーバーから何か警告が…。

 こいつを調べるのにあちこちネットで調べまわったんだけど、どうやらコレのようだ。
Close Notifyアラート。
TCP_FINメッセージが簡単に偽装できるから、セキュリティ上念のために終了時に送るメッセージらしい。
このメッセージの送信が必須かどうかは、RFCでもあいまいらしい。
実際、IE6でHTTPS通信しても、Apacheは送らないようにしてあるみたい。
IEは切断時にバグがあるからどうのこうのとか…。

詳しいところまではわからないけど、どうやら正常なメッセージということなので一安心。


■パケットを覗くと何か変? その2 ServerKeyExchange

 さらに正常な通信例とメッセージを比較する。
何か余計なメッセージがある…。

「ServerKeyExchange」

しかもIEの通信と比べると、この部分に結構時間もかかってる。
数百ミリ秒。

 @ITの記事を見ると、
「サーバが証明書を持っていない場合、あるいは持っていても署名にしか使用できない場合」
に発生するメッセージとある。
サーバーに証明書も認証局もあるし、設定は正しくできてると思うんだけどなぁ…。

そういえば買ったままだった、「マスタリングTCP/IP SSL/TLS編」があったのを思い出して、索引から調べてみる。
DSSとDHの項目に、ServerKeyExchangeメッセージのやり取りしている通信例がある。
一時的DH/DSSとやらを使用するときに、ServerKeyExchangeメッセージを送信するらしい。

かなりやぶれかぶれで、QSslSocket::ciphers()でcipherリストを取ってから、名前に「DH・DSS」が付くものを削除して、QSslSocket::setCiphers()で再設定してみる。
…と、ServerKeyExchangeメッセージはなくなった。
どうも、使用する暗号アルゴリズムも、数個に決め撃ちにしておいた方がいいのかもなぁ。


■パケットを覗くと何か変? その3 セッション再開できない?

 IEのパケットを見ると、2回目からの通信は1/10以下の時間で終わるのに、QHttpでの通信だと、2回目以降も同様の時間かかる。
なんでやねん…。

 パケットを比べると、QHttpの方は2回目のメッセージでも、ServerHelloメッセージ内のsession_idが設定されてこない。
つまりセッション再開ができていないんだろう。
よく見ると、IE側のClientHelloメッセージはProtocolがSSLv2、対してQHttpはTLS。
じゃぁ同じ形にできるだろうか、と上記コードのQSsl::TlsV1を、QSsl::AnyProtocolに変更してやる。
Apache側でTLSオンリーにしてるんで、どちらにしてもTLS通信になるだろう。

 で、実行。
確かにsession_idが入ってくる。
…けど、実行時間は短くならない。なんでー???

 どうもQSslSocketはsession_idを見てないっぽい。
QSslSocket自身にも、それっぽいインターフェースもプロパティも見当たらないし。
無念、ここまでか…。
(※自分がそう思っただけで、ホントはセッション再開サポートしてる可能性もあるので、鵜呑みにしないでください)

 サーバー負荷を考えると、セッションキャッシュが効かないのはかなり痛い。
何もQtにこだわる必要もないか。
libcURLとかってどうなんだろう…。


■おまけ、クライアント認証

上記コードで、QHttp::setSocket()する前に以下の2行を追加すると、クライアント認証も一応できた。


    this->_sslSocket.setLocalCertificate(QString("path/to/clientCert.pem"));
    this->_sslSocket.setPrivateKey(QString("path/to/privateKey.pem"),QSsl::Rsa,QSsl::Pem,QByteArray("pass phrase"));


多分、QHttpは使わないけど一応。



TrackBack


このエントリにトラックバックはありません



このトラックバックURLを使ってこの記事にトラックバックを送ることができます。 もしあなたのブログがトラックバック送信に対応していない場合にはこちらのフォームからトラックバックを送信することができます。.

Comments

ななし wrote:

QHttpはobsoleteですね
10/02/21 05:47:41

okayu wrote:

忠告サンクスです。

この記事書いた時点でインストールしていたのはQt4.5で、
そのときのアシスタントにはobsoleteとまでは書かれてなくて、
「QNetworkAccessManagerの使用を推奨します」てな感じの記述だったので、まぁいいかなと…。

時間を見つけてそっちのほうも試してみますね。
10/02/21 11:57:57

Add Comment