[Blog翻訳] USB デバイスの Container ID の生成方法
みなさん、こんにちは。Windows 開発統括部の古内です。今日は USB デバイス メーカーや USB ドライバー開発者の方を対象として、Container ID の生成方法に関する情報をご紹介します。これは、昨年の 12 月に Microsoft Windows USB Core Team Blog に投稿された 「How to Generate a Container ID for a USB Device」 の翻訳で、かなり長い記事ですが最後までお付き合いいただけると幸いです。明日は続きの 「その 2」 をお届けする予定ですので、どうぞご期待ください!
USB デバイスの Container ID の生成方法
こんにちは。USB チームでプログラム マネージャーを務める Kristina Hotz です。この記事では、Windows 7 と同様のメカニズムを使用して USB デバイスの Container ID を生成する方法を説明します。
今回は、マイクロソフトの USB ドライバー スタックに替わる USB ドライバー スタックを開発する開発者の方々や、Windows 7 OS で自社の USB デバイスがどのように認識されるかを理解したいと考える USB デバイス メーカーの方々にとって役立つ内容になっています。
Container ID とは、USB ドライバー スタックによって生成される ID 文字列のことで、物理デバイスごとに一意に割り当てられます。お使いのコンピューターに接続されたすべての物理デバイスを確認するには、[スタート] ボタンにある [デバイスとプリンター] を選択します。物理デバイスには、1 つの機能を持つ単機能デバイスと複数の機能を持つマルチファンクション (多機能) デバイスがあります。単機能デバイスの場合、[デバイスとプリンター] には、その物理デバイスの機能デバイスを表すアイコンが表示されます。一方、プリンター/スキャナー/ファックス複合機などのマルチファンクション デバイスの場合は、その物理デバイスのアイコンが示されます (プリンター/スキャナー/ファックス複合機の場合、プリンターのアイコンが表示されます)。このように表示されるのは、Windows では物理デバイスに関連するすべての機能デバイスが、Container ID によってグループ化されているためです。
コンピューターに USB デバイスが接続されると、USB ドライバー スタック (具体的にはバス ドライバー) が、物理デバイスに関連する各機能デバイスのデバイス ノード (devnode) を列挙し始めます。その後、バス ドライバーによって各 devnode に Container ID が割り当てられます。Container ID は devnode のプロパティであり、グローバル一意識別子 (GUID) で指定されます。その GUID が devnode の文字列プロパティとして設定されます。1 台の物理デバイスに起因するすべての devnode が、同じ Container ID を保持する必要があります。
外部デバイスの devnode の場合、バス ドライバーは、以下のいずれかの方法によって Container ID を取得します。
- デバイスが提供する Microsoft OS ContainerID ディスクリプター (記述子) を読み込む。詳細については、「Microsoft OS ContainerID ディスクリプターの使用 (英語)」を参照してください。
- 特定のデバイス情報からハッシュを作成し、Container ID を生成する (後述の「Container ID の文字列の生成方法」を参照してください)。
- Container ID のランダムな GUID を生成する。
- 親 devnode の Container ID を継承する。
注: Windows は ACPI を使用して、物理デバイスが外部デバイスであるか内部デバイスであるかを判断します。内部デバイスの devnode は、必ずそのコンピューター (すなわち親 devnode) の Container ID を継承します。
割り当てプロセスの詳細をまとめたフローチャートを確認するには、「USB デバイスへの Container ID の割り当て方法 (英語)」を参照してください。
Container ID 文字列の生成方法
マイクロソフトが提供する USB バス ドライバーは、以下のすべての条件を満たす場合に、デバイスの一意の Container ID 文字列を生成します。
- USB バス ドライバーが、デバイスのポートは取り外し不可であると報告する。
- デバイスに有効なシリアル番号がある。
- デバイスが Microsoft OS ContainerID ディスクリプターを提供しない。
Container ID を生成するために、バス ドライバーは、BCrypt Cryptographic API を使用してハッシュを作成します。Bcrypt 関数はヘッダー “bcrypt.h” 内で宣言され、ライブラリ “ksecdd.lib” によってエクスポートされています。
USB 3.0 デバイスのドライバー スタックの開発者の方は、IRP_MN_QUERY_ID 要求への応答として、Container ID のハッシュを提供してください。ハッシュの作成にあたっては、必ずこのセクションの手順に従ってください。この手順に従わない場合、USB 2.0 ポートから USB 3.0 ポートにデバイスを移行する際に、[デバイスとプリンター] に複数のエントリが作成される可能性があります。
Container ID のハッシュを作成するには、デバイス ディスクリプターから取得できる以下の一連の情報が必要です。
注: デバイス ディスクリプターの取得には、GET_DESCRIPTOR 制御要求を使用します。
- idVendor フィールドに表示される Vendor ID
- idProduct フィールドに表示される Product ID
- bcdDevice フィールドに表示される USB 仕様書番号
- iSerialNumber フィールドに表示される USB シリアル番号
以下に、ハッシュの作成に必要な手順を示します。
- 空の文字列を作成し、デバイスの Vendor ID、Product ID、および USB 仕様書番号を設定する。これらの識別子は、文字列にそれぞれ 4 文字の 16 進値として表示されます。たとえば “045E07730110” の場合、“045E” は Vendor ID、“0773” は Product ID、“0110” は USB 1.1 デバイスを表しています。
- 文字列に USB シリアル番号を追加する。
- USB Container ID の GUID を生成する。
- BCryptOpenAlgorithmProvider を呼び出し、SHA1 アルゴリズム プロバイダーを開く。
- BCryptCreateHash を呼び出し、ハッシュ操作用のハンドルを作成する。
- BCryptHashData を呼び出し、(手順 2 で作成した) USB Container ID の GUID をハッシュする。
- BCryptHashData を呼び出し、(手順 1 で作成した) 文字列をハッシュする。これらのいずれのハッシュ操作に対しても、必ず (手順 5 で作成した) 同一のハンドルを使用してください。
- BCryptFinishHash を呼び出し、ハッシュの結果を取得する。
- 結果のハッシュを GUID 変数にコピーし、以下の修正を行う。
- バージョン番号を削除する。
- バージョンを 5 で示される名前ベースの SHA-1 に設定する。
- バリアント ビットを削除し、Container ID の GUID が UUID 仕様書 (英語) に準拠することを確認する。
- RtlStringFromGUID を呼び出し、GUID を文字列に変換する。
バス ドライバーが IRP_MN_QUERY_ID 要求を受け取ったら、IO_STACK_LOCATION の Parameters.QueryId.IdType メンバーを確認します。Parameters.QueryId.IdType メンバーが BusQueryContainerID に設定されていると、IRP の応答中に GUID を含む文字列が返されます。
以下のサンプル コードに、Container ID のハッシュの作成方法を示します。このサンプルは、デバイスからデバイス ディスクリプターおよびそのシリアル番号を取得済みであることを前提としています。
入力パラメーター:
DeviceDescriptor - USB デバイス ディスクリプター
SerialNumberId - デバイスのシリアル番号を含んだ UNICODE_STRING
出力パラメーター:
containerIdString - 生成された Container ID 文字列を含んだ UNICODE_STRING
// {4B06FD46-C84E-4664-9C65-0C86D9047A0C}
static const GUID GUID_USB_CONTAINER_ID_NAME_SPACE =
{ 0x4b06fd46, 0xc84e, 0x4664, { 0x9c, 0x65, 0xc, 0x86, 0xd9, 0x4, 0x7a, 0xc } };
void CreateContainerIDString(USB_DEVICE_DESCRIPTOR DeviceDescriptor
UNICODE_STRING SerialNumberId)
{
GUID containerId;
NTSTATUS status;
BCRYPT_HASH_HANDLE hHash;
DWORD dwcb;
DWORD dwcbResult;
PUCHAR pbHashObject;
PUCHAR pbHash;
BCRYPT_ALG_HANDLE hContainerIDHashAlg;
ULONG length;
UNICODE_STRING uniqueString;
UNICODE_STRING containerIDString;
status = STATUS_SUCCESS;
hHash = NULL;
dwcb = 0;
dwcbResult = 0;
pbHashObject = NULL;
pbHash = NULL;
hContainerIDHashAlg = NULL;
RtlZeroMemory(&uniqueString,
sizeof(uniqueString));
RtlZeroMemory(&containerIDString,
sizeof(containerIDString));
length = sizeof(L"vvvvpppprrrr\0") + SerialNumberId.LengthInBytes;
uniqueString.Buffer = ExAllocatePoolWithTag(NonPagedPool,
length,
HUB_TAG);
if (uniqueString.Buffer == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
goto Exit;
}
uniqueString.MaximumLength = (USHORT)length;
uniqueString.Length = 0;
status = RtlUnicodeStringPrintf(&uniqueString,
L"%.4X%.4X%.4X",
DeviceDescriptor.idVendor,
DeviceDescriptor.idProduct,
DeviceDescriptor.bcdDevice);
if (!NT_SUCCESS(status))
{
goto Exit;
}
// バッファーが有効である場合、文字列にシリアル番号を追加する
if (SerialNumberId.Buffer)
{
status = RtlUnicodeStringCbCatStringN(&uniqueString,
SerialNumberId.Buffer,
SerialNumberId.LengthInBytes);
if (!NT_SUCCESS(status))
{
goto Exit;
}
}
// SHA1 アルゴリズム プロバイダーを開く
status = BCryptOpenAlgorithmProvider(&hContainerIDHashAlg,
BCRYPT_SHA1_ALGORITHM,
MS_PRIMITIVE_PROVIDER,
BCRYPT_PROV_DISPATCH);
if (!NT_SUCCESS(status))
{
hContainerIDHashAlg = NULL;
goto Exit;
}
// ハッシュ操作用のハンドルを作成する
status = BCryptGetProperty(hContainerIDHashAlg,
BCRYPT_OBJECT_LENGTH,
(PUCHAR)&dwcb,
sizeof(dwcb),
&dwcbResult,
0);
if (!NT_SUCCESS(status))
{
goto Exit;
}
if (sizeof(DWORD) != dwcbResult)
{
status = STATUS_INVALID_BUFFER_SIZE;
goto Exit;
}
pbHashObject = ExAllocatePoolWithTag(NonPagedPool,
dwcb,
HUB_TAG);
if (NULL == pbHashObject)
{
status = STATUS_INSUFFICIENT_RESOURCES;
goto Exit;
}
status = BCryptCreateHash(hContainerIDHashAlg,
&hHash,
pbHashObject,
dwcb,
NULL,
0,
0);
if (!NT_SUCCESS(status))
{
goto Exit;
}
// USB Container ID 名前空間でハッシュする
status = BCryptHashData(hHash,
(PUCHAR)&GUID_USB_CONTAINER_ID_NAME_SPACE,
sizeof(GUID_USB_CONTAINER_ID_NAME_SPACE),
0);
if (!NT_SUCCESS(status))
{
goto Exit;
}
// デバイス情報を含む文字列でハッシュする
status = BCryptHashData(hHash,
(PUCHAR)uniqueString.Buffer,
(ULONG) uniqueString.Length,
0);
if (!NT_SUCCESS(status))
{
goto Exit;
}
// 結果のハッシュを取得する
status = BCryptGetProperty(hContainerIDHashAlg,
BCRYPT_HASH_LENGTH,
(PUCHAR)&dwcb,
sizeof(dwcb),
&dwcbResult,
0);
if (!NT_SUCCESS(status))
{
goto Exit;
}
if (sizeof(DWORD) != dwcbResult)
{
status = STATUS_INVALID_PARAMETER;
goto Exit;
}
if (sizeof(GUID) > dwcb)
{
status = STATUS_INVALID_PARAMETER;
goto Exit;
}
pbHash = ExAllocatePoolWithTag(NonPagedPool,
dwcb,
HUB_TAG);
if (NULL == pbHash)
{
status = STATUS_INSUFFICIENT_RESOURCES;
goto Exit;
}
status = BCryptFinishHash(hHash,
(PUCHAR)pbHash,
dwcb,
0);
if (!NT_SUCCESS(status))
{
goto Exit;
}
// 結果のハッシュを Container ID 文字列にコピーし
// バージョン番号およびバリアント フィールドを設定する。
RtlCopyMemory(containerID,
pbHash,
sizeof(GUID));
containerID.Data3 &= 0x0FFF; // バージョン番号を削除する
containerID.Data3 |= (5 << 12); // バージョンを設定する = (名前ベースの SHA1) = 5
containerID.Data4[0] &= 0x3F; // バリアント ビットを削除する
containerID.Data4[0] |= 0x80;
status = RtlStringFromGUID(containerID,
&containerIDString);
if (!NT_SUCCESS(status))
{
goto Exit;
}
Exit:
if (hHash)
{
BCryptDestroyHash(hHash);
}
if (pbHash)
{
ExFreePool(pbHash);
}
if(pbHashObject)
{
ExFreePool(pbHashObject);
}
if (uniqueString.Buffer)
{
ExFreePool(uniqueString.Buffer);
}
if (hContainerIDHashAlg)
{
BCryptCloseAlgorithmProvider(hContainerIDHashAlg,0);
}
}