スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

WinInetでHTTPSつまりSSL接続

WinInetはHTTP,FTP,今は殆ど使われてないであろうGopherなんかのプロトコルを、ソケットを意識しないで使えるように準備されたAPI。

自由度は高くないけど、HTTPで言えばGETやPOSTでファイルを受信するような手続き的な処理はエラー処理を考えないと数行で記述できるので個人的には多用している。ちなみに、CHttpConnectionとかCFtpConnectionとかWinInetのAPIをラップしたクラスもある(使ったこと無し)。

で、お手軽といっても実際はそう簡単でもない。

WinInetではない通常のSocketプログラミングは煩雑ではあるけど、具体的に何を処理しているかプログラマは把握しやすいので、細やかに制御ができる。しかしWinInetを使う場合は、APIの背後でどういった処理をしているのかはブラックボックスに近いので、前処理や後処理などに一定の手順があり、いわば形式的なコーディング手順を知っておく必要がある。

さて今回、某公的研究機関のサーバーにHTTPSで繋いでXMLデータ(RESTってやつかな)を取るのが目的でWinInetを使ったプログラムを作ろうとおもったのだがSSLの処理でつまずいた。最初に書いたのは以下のようなコード(注:細かいところは省いて書き換えてるのでコピペしても満足に動きません)。

CString url=_T("https://www.none.com/index.html");
DWORD flag=INTERNET_FLAG_RELOAD;
HINTERNET hInet = InternetOpen(_T("MyProgram"),INTERNET_OPEN_TYPE_PRECONFIG,NULL,NULL,0);
HINTERNET hFile = InternetOpenUrl(hInet,LPCTSTR(url),NULL,0,flag,0);
(以下hFileハンドルからInternetReadFileで受信バッファを読み込んでいく)

SSLの場合、上記のInternetOpenUrlで使うflagにINTERNET_FLAG_SECUREというフラグを設定してもいいんだけど、urlの指定がhttps://で始まる場合InternetOpenUrlはINTERNET_FLAG_SECUREを指定しなくてもSSLだと判断し処理てくれるらしい。MSDNには無駄でっせみたいに書いてある。

ただ、実際にこれを動かしてみるとhFileがNULLになって動かない。

何が起こってるのだろうとGetLastError()でエラーコードを取ると戻り値がセキュリティ証明書が無効もしくは不明を意味するERROR_INTERNET_INVALID_CA=12045となってた。

InternetOpenUrlではINTERNET_FLAG_IGNORE_CERT_CN_INVALID というオプションフラグがあるので、
flag |= INTERNET_FLAG_IGNORE_CERT_CN_INVALID;
とInternetOpenUrlの前に一行加えてInternetOpenUrlに入れてやっても・・同じエラーコードで変わらない。

ググるとInternetSetOptionでセキュリティ関係をIGNOREするフラグを設定してやればいいなんて情報もあるけど、hFileハンドルがNULLなので設定できない。

ほんで、InternetOpenUrlをあきらめ、変わりにInternetConnectを使うことにした。

InternetConnectは接続先のアドレス、ポート番号、取ってくるコンテンツファイルをそれぞれに別個に関数に入れないといけないので、実装上はurlを分割するようなコードを最初に入れる必要がある。これがめんどくさいので避けてたのだが仕方が無い。

INTERNET_PORT defport=***; //実際は引数にしている

CString protocol=_T("https"); //実際はurlから分割関数を書いてる
CString address=_T("www.none.com");
CString content=_T("index.html");

BOOL ssl=(protocol.CompareNoCase(_T("https"))==0)

INTERNET_PORT port=(defport)?defport:(ssl)?INTERNET_DEFAULT_HTTPS_PORT:INTERNET_DEFAULT_HTTP_PORT;
DWORD flag=(ssl)?INTERNET_FLAG_SECURE|INTERNET_FLAG_RELOAD:INTERNET_FLAG_RELOAD;

HINTERNET hInet=InternetOpen(_T("MyProgram"),INTERNET_OPEN_TYPE_PRECONFIG,NULL,NULL,0);
HINTERNET hConnect=InternetConnect(hInet,LPCTSTR(address),port,NULL,NULL,INTERNET_SERVICE_HTTP,0,0);
HINTERNET hFile = HttpOpenRequest( hConnect,NULL,LPCTSTR(content),NULL,NULL,NULL,flag,0);

while(!HttpSendRequest (hFile,0,0,0,0))
{
 DWORD code=GetLastError();
 if( code & (ERROR_INTERNET_HTTP_TO_HTTPS_ON_REDIR | ERROR_INTERNET_INVALID_CA | ERROR_INTERNET_SEC_CERT_CN_INVALID | ERROR_INTERNET_SEC_CERT_DATE_INVALID))
 {
  if(ERROR_CANCELLED==InternetErrorDlg (GetDesktopWindow(),hFile, code, FLAGS_ERROR_UI_FILTER_FOR_ERRORS | FLAGS_ERROR_UI_FLAGS_GENERATE_DATA | FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS,NULL))
  {
   InternetCloseHandle(hFile);
   InternetCloseHandle(hInet);
   InternetCloseHandle(hConnect);
   return;
  }
 }
}
(以下hFileハンドルからInternetReadFileで受信バッファを読み込んでいく)

見にくくなってしまった・・

InternetErrorDlgはIEなんかでも出てくるセキュリティの警告ダイアログを出す関数で、「このサイトの証明書発行元は信頼されていないか不明です。接続を続行しますか」とか「このサイトのセキュリティで保護された接続は確認できません。続行しますか?」などと聞いててくる。

実際に動かすとERROR_INTERNET_INVALID_CA以外にもERROR_INTERNET_SEC_CERT_CN_INVALID=12038というエラーも出たのだが上記コードは順番にそれも処理できる。

ダイアログの表示をしないでサクッと通したいときは、InternetSetOptionでセキュリティ関係の無視フラグをセットしてやればいい。

これで一応動いた。めんどくさい。実際にはこの後にユーザー認証ダイアログが出る処理なんかも入れてて、ここで書いたとおりのコーディングはしていないんのだけど、どなたかのご参考になれば。

という覚書。

コメントの投稿

非公開コメント

InternetCrackUrl

表題の関数は既にご存知かもしれませんが、僕はこんな感じでUrl分解クラスを作ってます。
http://tinypaste.com/9693e

InternetCrackUrlは・・

・・知りませんでした、コードサンプル有難うございます。これに変えてみようかと思います。
プロフィール

N-Soft

Author:N-Soft
最近の記事
カテゴリー
最近のコメント
リンク
RSSフィード
カウンター
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。