IE11 は TLS_FALLBACK_SCSV を送信しない

へー、そうなんだという内容です。

上記にあるレジストリは Win8.1 + IE11 では効きませんでした。
SSL ハンドシェイクを行う際、Client Hello にて TLS_FALLBACK_SCSV を送信すると Cipher Suites に {0x56,0x00} が
追加されるので

Cipher Suites: Unknown (0x5600)

があれば、TLS_FALLBACK_SCSV を送信していることがわかるのですが、IE11 では送信していませんでした。
IE は独自実装でフォールバックをしているのかなと思いきや、SChannel が独自に実装しているっぽい。

Microsoft connect サイトを見たら

Internet Explorer should send TLS_FALLBACK_SCSV
https://connect.microsoft.com/IE/feedback/details/1002874/internet-explorer-should-send-tls-fallback-scsv

を見つけたのですが、TLS_FALLBACK_SCSV はやらんと言ってますな。

もう一つ、ついでに

RFC5746 の ClientHello では renegotiation_info Extenstion が空であるか、TLS_EMPTY_RENEGOTIATION_INFO_SCSV として Signaling Cipher Suite Value (SCSV) を指定しなければ
ならない。

とある。では、IE はどのようにして Signaling Cipher Suite Value (SCSV) を指定しているのかというと、
IE11 に限らず、IE は renegotiation_info Extenstion を使ってた。(IE8 以降でパケット採って確認してみた)

IE11_SSLTLSHandShake

renegotiation_info というのは TLS 拡張で、SSL ハンドシェイク中の Client Hello と Server Hello の body の
extensions に付いてきます。初回ハンドシェイクの場合、ここには 00 が入ります。
(拡張の実際の中身は ff 01 00 01 00: “ff01” = 拡張タイプ、”0001” = 拡張の長さ、”00” = 1byte 分の 0 が入る)

IE11_SSLTLSHandShake2

クライアント
自身で renegotiation_info Extenstion  を送信した後、ServerHello を受信したら、サーバーが renegotiation_info Extenstion があるかをチェックする必要があり、あれば、secure_renegotiation = TRUE に、なければ
secure_renegotiation = FALSE に設定する。
secure_renegotiation = TRUE の場合には、再ネゴシエーションを行う際に renegotiation_info Extenstion を
使い、特定の情報を送信する。
secure_renegotiation = FALSE の場合には、再ネゴシエーションを中止する。
サーバー
Client Hello にて TLS_EMPTY_RENEGOTIATION_INFO_SCSV もしくは renegotiation_info Extenstion  があるかを
チェックして、どちらかがあれば secure_renegotiation = TRUE に設定する。
どちらも無ければ secure_renegotiation = FALSE に設定して、実装によってはハンドシェイクを中止させる。

面倒なのは、TLS_EMPTY_RENEGOTIATION_INFO_SCSV と renegotiation_info Extenstion  は両方一度に使うのは
ダメらしい。なんか、大変だね。

IE11 の互換性 (VBScript)

ポツポツとネットでも見かけるようになったネタなので、メモ代わりに。

Windows 8.1 / Windows Server 2012 R2 の標準ブラウザは Internet Explorer 11 なわけです。

すでに自動更新も始まっていますので、もし、自動更新したくなかったら、ブロッキングツールを入れときましょう。

Internet Explorer 11 自動配布の無効化ツールキット
http://www.microsoft.com/ja-jp/download/details.aspx?id=40722

今回の本題は、IE11 の互換性で気になる VBScript についてです。まずは、IE に関する開発者向けリソースである

Internet Explorer 11 guide for developers
http://msdn.microsoft.com/en-us/library/ie/bg182636(v=vs.85).aspx

を覗いてみます。いや、ここよりも互換性クックブック(Compat Cookbook)の方がいいですね。

Internet Explorer compatibility cookbook
http://msdn.microsoft.com/en-us/library/ie/dn384059(vs.85)

大体、ここに互換性の情報がそろっていますので、探してみると…ありました!

VBScript is no longer supported in IE11 edge mode for the Internet zone
http://msdn.microsoft.com/en-us/library/ie/dn384057(v=vs.85).aspx

これですね。
そんで、サポートしないよと言っているのは、インターネットゾーンの場合と書いてあります。
そこら辺で見かけたコードで検証してみました。わー、シンプル。

<html>
<head>
</head>
<body>
<script type="text/javascript">
alert (‘Javascript’);
</script>
<script type="text/vbscript">
msgbox "Vbscript"
</script>
</body>
</html>

インターネットゾーンとイントタネットゾーンで動かしてみました。

InternetZone IntranetZone

確かに msdn に記述されているとおり、インターネットゾーンの場合だけ

msg1

と表示されます。イントラネットゾーンにすると、このメッセージボックスに加えて

msg2

も表示されました。
VBScript を動かしたかったら、インターネットゾーン以外にしなさいってことですね。
ただ、セキュリティゾーンを設定させるのは嫌だなぁって思うこともあります。
msdn をもう一度読んでみると、レガシー ドキュメントモード にすれば動きそうです。

<html>
<head>
<meta http-equiv="x-ua-compatible" content="IE=10">
</head>
<body>
<script type="text/javascript">
alert (‘Javascript’);
</script>
<script type="text/vbscript">
msgbox "Vbscript"
</script>
</body>
</html>

検証してみたところ、確かに VBScript の msgbox も動きました。
でも、msdn には、一時的な対処方法と書いてあるので、推奨していないってことですね。

とっとと JavaScript に移行するのが良さそうです。

IE9 は、Vista モード

以前、「Win7&2008R2: アプリケーション マニフェスト」 でマニフェストファイルを紹介しましたが
IE9 はどのモードで動いているのかなと思って、リソースモニターで見たところ、32bit の IE、64bit の IE 共に
Windows Vista モードでした。

IE9_Vistaモード

IE9 は、32bit バイナリと 64bit バイナリの 2 製品しか提供されていないので、必然的に

  • IE9 32bit
    Windows Vista 32bit
    Windows Server 2008 32bit
    Windows 7 32bit
  • IE9 64bit
    Windows Vista 64bit
    Windows Server 2008 64bit
    Windows 7 64bit
    Windows Server 2008 R2 64bit

となるわけで、各 OS 個別に提供されていないです。

 IE9_32bit IE9_64bit

互換性って重要ですね。

IE9 日本語版ダウンロード

2011/4/26 よりダウンロード開始です。

IE9

注意が必要なのは、Windows Vista SP2 以降のみサポートということです。
XP は未サポートなんですね~。

IE9 の機能をふんだんに味わうことができるデモサイトが Microsoft から公開されています。

あと、よく IE9 のデモ(HTML5)で見るのは、熱帯魚のベタがいっぱい泳いでいるやつです。

FishIETank

いろいろと楽しんでみましょう。

Internet Explorer の Cookie 操作

ご存じの通り、Windows Vista 以降の IE では保護モードというものが導入されています。

最新の IE8 でも同様になります。
今回は Cookie の設定/取得についてのネタです。
Cookie の設定/取得では、動いている IE がどのモード(保護モードかどうか)で動いているのかを意識する必要があります。
どのモードで動いているのかによって、Cookie ストアの場所が異なるからです。

  • 保護モード用の Cookie ストア
    Users\"username"\AppData\Roaming\Microsoft\Windows\Cookies\Low
  • 通常の Cookie ストア
    Users\"username"\AppData\Roaming\Microsoft\Windows\Cookies

では、これらのローカルの Cookie ストアに対して、どのように Cookie を取得/設定するのかを説明します。
大体、次のような処理になります。

  1. 動作中の IE が保護モードかどうかを判定する
  2. 保護モードであれば、保護モード用の Cookie 用 API を使い、非保護モードであれば、通常の Cookie 用 API を使う

1) 保護モードかどうかの判定

これには、IEIsProtectedModeProcess() を使います。

使い方は、こんな感じです。API が HRESULT を返すので、ラッパーにしてみました。

// IE が保護モードの場合には、0 を、非保護モードであれば、-1 を返します
int IsIEProtectedMode()
{
    BOOL bResult = FALSE;

    // IE が保護モードかどうかを確認する
    HRESULT hRes = IEIsProtectedModeProcess(&bResult);
    if (FAILED(hRes))
    {
        DWORD dwErr = GetLastError();
        // 必要に応じでエラー処理
        // エラーが発生したときには、GetLasetError()の値を返します
        return dwErr;
    }

#ifdef _DEBUG
    if (bResult)
    {
        MessageBox(_T("IE is Protected Mode"), _T("IE 保護モードの確認"), MB_OK | MB_ICONINFORMATION);
    }
    else
    {
        MessageBox(_T("IE is Not Protected Mode"), _T("IE 保護モードの確認"), MB_OK | MB_ICONINFORMATION);
    }
#endif

    return bResult ? 0: -1;
}

このような方法で、事前に IE が保護モードで動いているのかどうかを判定することができます。

2) Cookie の設定/取得

Cookie を操作する API には、以下のものがあります。

Cookie 設定用 API

Cookie 取得用 API

では、使い方です。
Cookie はメモリ内に一時的に保存しておくものと、HDD に Cookie ファイルとして永続化しておく方法の 2 種類があります。
初めは、Cookie の設定です。

[IE が保護モードで動いている]

メモリ上(セッション内)に Cookie を一時的に作成する場合

HRESULT hRes = IESetProtectedModeCookie(szUrl,
    _T(“MyValue”), _T("777; expires = Fri,10-Sep-2010 00:00:00 GMT"), INTERNET_COOKIE_THIRD_PARTY);
if (FAILED(hRes))
{
    DWORD dwErr = GetLastError();
    // エラー
}

HDD の Cookie ストアに Cookie ファイルを永続化する場合(第 2 パラメーターを NULL にします)

HRESULT hRes = IESetProtectedModeCookie(szUrl,
    NULL, _T("777; expires = Fri,10-Sep-2010 00:00:00 GMT"), INTERNET_COOKIE_THIRD_PARTY);
if (FAILED(hRes))
{
    DWORD dwErr = GetLastError();
    // エラー
}

[IE が非保護モードで動いている]

メモリ上(セッション内)に Cookie を一時的に作成する場合

BOOL bRes = InternetSetCookieEx(szUrl,
    _T("MyValue"), _T("MyValue = 777; expires = Fri,10-Sep-2010 00:00:00 GMT"),
    INTERNET_COOKIE_THIRD_PARTY, NULL);
if (bRes == FALSE)
{
    dwErr = GetLastError();
    // エラー
}

HDD の Cookie ストアに Cookie ファイルを永続化する場合(第 2 パラメーターを NULL にします)

BOOL bRes = InternetSetCookieEx(szUrl,
    NULL, _T("MyValue = 777; expires = Fri,10-Sep-2010 00:00:00 GMT"),
    INTERNET_COOKIE_THIRD_PARTY, NULL);
if (bRes == FALSE)
{
    dwErr = GetLastError();
    // エラー
}

Cookie を使用する際、フォーマットが決められていますので、間違えないようにしてください。

また、Cookie の有効期限を設定する場合ですが、必ず、正しい日時を設定しなければなりません。
上記はコードを簡単にするために、ハードコーディングしていますが、きちんと API で取得した方がよいと思います。
InternetSetCookie() は、InternetSetCookieEx() を参考にしてください(最後の 2 つのパラメーターがないだけです)。

次は、Cookie の取得です。こちらは、少しコーディングっぽくしてみます。

TCHAR * pszCookieHeader = NULL;

__try
{
    DWORD dwDataSize = 512;
    pszCookieHeader = (TCHAR *)_malloca(dwDataSize);
    if (pszCookieHeader == NULL)
    {
        // メモリがとれない
        __leave;
    }
    SecureZeroMemory(pszCookieHeader, dwDataSize);

    TCHAR szUrl[1024];
    wsprintf(szUrl, _T("%s"), OLE2T(m_bstrString));

    if (IE が保護モード)
    {
        // HDD の Low Cookie ストアから Cookie を取得
        HRESULT hRes = IEGetProtectedModeCookie(szUrl,
            NULL, pszCookieHeader, &dwDataSize, INTERNET_COOKIE_THIRD_PARTY);
        if (FAILED(hRes))
        {
            DWORD dwErr = GetLastError();
            if (dwErr == ERROR_INSUFFICIENT_BUFFER)
            {
                // Cookie のサイズが大きかったので、もう一度、バッファを確保する
                _freea(pszCookieHeader);
                pszCookieHeader = (TCHAR *)_malloca(dwDataSize);
                if (pszCookieHeader == NULL)
                {
                    // メモリがとれない
                    __leave;
                }
                SecureZeroMemory(pszCookieHeader, dwDataSize);
                hRes = IEGetProtectedModeCookie(szUrl,
                    NULL, pszCookieHeader, &dwDataSize, INTERNET_COOKIE_THIRD_PARTY);
                if (FAILED(hRes))
                {
                    dwErr = GetLastError();
                    // エラー処理
                }
                else
                {
                    // Cookie を取得できた!
                    MessageBox(pszCookieHeader, _T("Get Cookie"), MB_OK | MB_ICONINFORMATION);
                }
            }
            else
            {
                // エラー処理
            }
        }
        else
        {
            // Cookie を取得できた!
            MessageBox(pszCookieHeader, _T("Get Cookie"), MB_OK | MB_ICONINFORMATION);
        }
    }
    else if (IE が非保護モード)
    {
        // HDD の Cookie ストアから Cookie を取得
        BOOL bRes = InternetGetCookieEx(szUrl,
            NULL, pszCookieHeader, &dwDataSize, INTERNET_COOKIE_THIRD_PARTY, NULL);
        if (bRes == FALSE)
        {
            DWORD dwErr = GetLastError();
            if (dwErr == ERROR_INSUFFICIENT_BUFFER)
            {
                // Cookie のサイズが大きかったので、もう一度、バッファを確保する
                _freea(pszCookieHeader);
                pszCookieHeader = (TCHAR *)_malloca(dwDataSize);
                if (pszCookieHeader == NULL)
                {
                    // メモリがとれない
                    __leave;
                }
                SecureZeroMemory(pszCookieHeader, dwDataSize);
                bRes = InternetGetCookieEx(szUrl,
                    NULL, pszCookieHeader, &dwDataSize, INTERNET_COOKIE_THIRD_PARTY, NULL);
                if (bRes == FALSE)
                {
                    dwErr = GetLastError();
                    // エラー処理
                }
                else
                {
                    // Cookie を取得できた!
                    MessageBox(pszCookieHeader, _T("Get Cookie"), MB_OK | MB_ICONINFORMATION);
                }
            }
            else
            {
                // エラー処理
            }
        }
        else
        {
            // Cookie を取得できた!
            MessageBox(pszCookieHeader, _T("Get Cookie"), MB_OK | MB_ICONINFORMATION);
        }
    }
}
__finally
{
    if (pszCookieHeader)
    {
        _freea(pszCookieHeader);
    }
}

メモリ(セッション)上の Cookie を取得する場合には、IEGetProtectedModeCookie()InternetGetCookieEx() の第 2 パラメーターを
NULL ではなく、_T("MyValue") みたいに変えてください。

これらの API を使うことで Cookie の操作ができます。
ポイントは、IE が保護モードがどうかで API を使い分けることです。

来てます IE8 が。

予定通り 3/20 に Internet Explorer 8 がリリースされました。
少し前からスケジュールは知っていましたが、最近は日本語版も英語版も同時にリリースされるのは良いですね。

IE8

ダウンロードは、この辺かな。

RC1 のバージョンが、8.0.6001.18372 で RTM が 8.0.6001.18702 なので、若干ビルドが上がりましたね。
何にしても、IE7 よりは非常に使いやすくなったし、軽くなったのでよかったです。
いつも悩むのですが、64bit 版の IE ってどんな用途に向いているんでしょうか…。

Browser Helper Objects を作ってみる

Browser Helper Objects (BHO) ってどうやって作るの?っていう相談を受けたのでネタにします。
今更 COM かよ!っていう感じもしますが、COM は今でも重要なテクノロジなんですね。
まず、MSDN だと、ここに情報があります。

この内容に沿って作成すると簡単なサンプルができます。
一緒に作成してみましょう!(ほとんど同じですが、VS 2008 を使ってみます。)

1) プロジェクトのセットアップ

ここで、作成するのは DLL になります。追加のオプションは必要ありません。

BHO作成1

BHO作成2BHO作成3

BHO は COM クラスになります。IE がホストになって、BHO の COM クラスをホスティングするわけです。
ATL シンプル オブジェクトを選択して、COM クラスを作成します。
ここでのポイントは、IObjectWithSite を選択しておくことです。

BHO作成4

BHO作成5BHO作成6

すると、VS はいろいろなファイルをジェネレートしてくれます。
なお、IObjectWithSite インターフェースについては、この辺をどうぞ。

2) 基本の実装

ここでは、記載されている通りに進めてください。

HelloWorldBHO.h (記事の通りに)
HelloWorldBHO.cpp (記事の通りに)

ATL で作っているので、普通に CComPtr クラスを使っていますね。スマートポインタは偉大です。

なお、HelloWorld.cpp で DllMain にコードを追加していますが、
VS 2008 では、DllMain は dllmain.cpp という別のファイルに生成されますので注意してください。

3) BHO の登録

BHO は COM であるため、レジストリ登録が必要になります。
ここでのポイントは、Windows Explorer から BHO がロードされないようにしていることです。
BHO はそもそも、Internet Explorer と Windows Explorer に対する機能拡張になりますので
IE だけで使おうとなると、少し細工が必要になるわけです。
(*) Windows 2000 で Internet Explorer と Windows Explorer が統合された結果です

ForceRemove で記載している CLSID を間違えないようにしてください。
サイトに記載されている uuid をコピペして、自分で作成している uuid と置き換えないように。

一応、これでビルドができるようになったのですが、IE 動かしてみても何も起こりません。
先に進めてください。

4) イベントへの応答

イベントを処理するには、Invoke メソッドを使います。Invoke メソッドを実際に書いてもいいのですが
今どきの ATL では、BEGIN_SINK_MAP マクロという便利なものがあるんです。
BEGIN_SINK_MAP マクロで DISPID_DOCUMENTCOMPLETE を登録して、OnDocumentComplete メソッドで
実装しています。

ちなみに、BEGIN_SINK_MAP マクロを使わないと

STDMETHODIMP CHelloWorldBHO::Invoke(DISPID dispidMember,
                                    REFIID riid, LCID lcid, WORD wFlags,
                                    DISPPARAMS* pDispParams, VARIANT* pvarResult,
                                    EXCEPINFO*  pExcepInfo,  UINT* puArgErr)
{
    if (!pDispParams)
    {
        return E_INVALIDARG;
    }
    switch (dispidMember)
    {
    case DISPID_DOCUMENTCOMPLETE:
        // ここに処理を書く
        break;
    case DISPID_BEFORENAVIGATE2:
        // ここに処理を書く
        break;

・・・・
}

という巨大な switch 文を作成しなければなりません。使えるマクロは、どんどん使った方がよさそうです。

さあ、これで目で見えるような動きになった BHO ができました。
ビルドして IE を起動してみましょう。(テストの再実行 というところです)

BHO作成7

こんな感じで記事と同じようにできました。
時間があったら、その後に続く記事の内容もやってみるといいと思います。

IE が発生させるイベントは、ここに記載されています。

記事で使っている DISPID_DOCUMENTCOMPLETE というイベントは

というもので、HTMLドキュメントのロードが終了したときに発生するイベントです。
Event DISPID に「DISPID_DOCUMENTCOMPLETE」という記述があります。
この DISPID に対して Invoke メソッドを使い、処理するイベントを実装するというわけです。

なお、余談ですが、C# で書くと、こんな感じになります。

using SHDocVw;
using mshtml;
using System.Runtime.InteropServices;

namespace BHO_HelloWorld
{
    public class WindowWrapper : IWin32Window
    {
        private IntPtr hWnd;
        public WindowWrapper(IntPtr handle)
        {
            hWnd = handle;
        }
        public IntPtr Handle
        {
            get { return hWnd; }
        }
    }

class BHO : IObjectWithSite
    {
        WebBrowser webBrowser;

public void OnDocumentComplete(object pDisp, ref object URL)
        {
            WindowWrapper owner = new WindowWrapper(webBrowser.Handle);
            MessageBox.Show(owner, "Hello World!", "BHO", MessageBoxButtons.OK);
        }

public void SetSite(object site)
        {
            if (site != null)
            {
                webBrowser = (WebBrowser)site;
                webBrowser.DocumentComplete += new DWebBrowserEvents2_DocumentCompleteEventHandler(this.OnDocumentComplete);
            }
            else
            {
                webBrowser.DocumentComplete -= new DWebBrowserEvents2_DocumentCompleteEventHandler(this.OnDocumentComplete);
                webBrowser = null;
            }
        }
    }
}

今回はこれでおしまいです。