EternalWindows
自己署名入り証明書 / OIDの列挙

証明書のバージョンが3であるX.509v3の証明書は、 拡張情報(エクステンション)と呼ばれる情報を含むことが可能となっています。 拡張情報はWindows固有の拡張プロパティと異なり、 どのようなプラットフォームにおいても認識され、 証明書の詳細な目的や他の証明書と関係を定義する情報を含むことができます。 CERT_INFO構造体において拡張情報を受け取るメンバはrgExtensionであり、 このメンバはCERT_EXTENSION構造体のアドレスを指定できるようになっています。 CERT_EXTENSION構造体の定義を次に示します。

typedef struct _CERT_EXTENSION {
  LPSTR              pszObjId;
  BOOL               fCritical;
  CRYPT_OBJID_BLOB   Value;
} CERT_EXTENSION,     *PCERT_EXTENSION;

pszObjIdは、拡張情報の種類を表すOIDを指定します。 fCriticalは、証明書の利用者がこの拡張情報の値を確認しなければ ならないかどうかを表すフラグを指定します。 TRUEが指定されている場合、この情報は確認しなければなりません。 Valueは、拡張情報の値を指定します。 この値は、拡張情報の種類を表すOIDに関連する構造体をエンコードしたものです。

OIDが表すことのできる情報は、拡張情報に限らず、 アルゴリズムIDや拡張鍵用途など様々であり、 OIDが一概に何を表すのかは定義しにくいものがあります。 次に示すCryptEnumOIDInfoを呼び出せば、情報の種類によってグループ化されたOIDを確認することができます。

BOOL WINAPI CryptEnumOIDInfo(
  DWORD dwGroupId,
  DWORD dwFlags,
  void *pvArg,
  PFN_CRYPT_ENUM_OID_INFO pfnEnumOIDInfo
);

dwGroupIdは、OIDのグループを示す定数を指定します。 dwFlagsは、0を指定します。 pvArgは、コールバック関数の引数としたい変数のアドレスを指定します。 pfnEnumOIDInfoは、OIDの情報を受け取るコールバック関数のアドレスを指定します。 コールバック関数の定義は、次のようになります。

BOOL (WINAPI *PFN_CRYPT_ENUM_OID_INFO)(
  PCCRYPT_OID_INFO pInfo,
  void *pvArg
); 

pInfoは、OIDの情報を格納したCRYPT_OID_INFO構造体のアドレスが格納されます。 pvArgは、CryptEnumOIDInfoの第3引数に指定した値が格納されます。 CRYPT_OID_INFO構造体の定義を次に示します。

typedef struct _CRYPT_OID_INFO {
  DWORD               cbSize;
  LPCSTR              pszOID;
  LPCWSTR             pwszName;
  DWORD               dwGroupId;
  union {
    DWORD               dwValue;
    ALG_ID              Algid;
    DWORD               dwLength;
  };
  CRYPT_DATA_BLOB     ExtraInfo;
} CRYPT_OID_INFO, *PCRYPT_OID_INFO;
typedef const CRYPT_OID_INFO CCRYPT_OID_INFO, *PCCRYPT_OID_INFO;

cbSizeは、構造体のサイズをバイト単位で指定します。 pszOIDは、OIDの値を指定します。 pwszNameは、OIDに関連付ける名前を指定します。 dwGroupIdは、OIDが属するグループを表す定数を指定します。 dwValue、Algid、dwLength、およびExtraInfoは、OIDによって使用されるかどうかが決まります。 次に、グループIDとして定義されている定数を示します。

関連する拡張情報
CRYPT_HASH_ALG_OID_GROUP_ID ハッシュアルゴリズム
CRYPT_ENCRYPT_ALG_OID_GROUP_ID 暗号化アルゴリズム
CRYPT_PUBKEY_ALG_OID_GROUP_ID 公開鍵アルゴリズム
CRYPT_SIGN_ALG_OID_GROUP_ID 署名アルゴリズム
CRYPT_RDN_ATTR_OID_GROUP_ID RDNの属性型
CRYPT_EXT_OR_ATTR_OID_GROUP_ID 拡張情報と属性
CRYPT_ENHKEY_USAGE_OID_GROUP_ID 拡張鍵用途
CRYPT_POLICY_OID_GROUP_ID ポリシー

今回のプログラムは、CryptEnumOIDInfoを呼び出して特定のグループのOIDを列挙します。 左側のリストボックスにグループを表す定数の名前が列挙されているため、 それを選択して右側のリストボックスでOIDを確認します。

#include <windows.h>

#pragma comment (lib, "crypt32.lib")

BOOL WINAPI EnumOIDInfo(PCCRYPT_OID_INFO pInfo, void *pvArg);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndListBoxLeft = NULL;
	static HWND hwndListBoxRight = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		int    i;
		LPTSTR lpszGroupId[] = {
			TEXT("CRYPT_HASH_ALG_OID_GROUP_ID"), TEXT("CRYPT_ENCRYPT_ALG_OID_GROUP_ID"),
			TEXT("CRYPT_PUBKEY_ALG_OID_GROUP_ID"), TEXT("CRYPT_SIGN_ALG_OID_GROUP_ID"),
			TEXT("CRYPT_RDN_ATTR_OID_GROUP_ID"), TEXT("CRYPT_EXT_OR_ATTR_OID_GROUP_ID"),
			TEXT("CRYPT_ENHKEY_USAGE_OID_GROUP_ID"), TEXT("CRYPT_POLICY_OID_GROUP_ID")
		};

		hwndListBoxLeft = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndListBoxRight = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)2, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
	
		for (i = 0; i < 8; i++)
			SendMessage(hwndListBoxLeft, LB_ADDSTRING, 0, (LPARAM)lpszGroupId[i]);

		return 0;
	}

	case WM_COMMAND: {
		DWORD dwGroupId; 

		if ((HWND)lParam == hwndListBoxRight || HIWORD(wParam) != LBN_SELCHANGE)
			return 0;
		
		SendMessage(hwndListBoxRight, LB_RESETCONTENT, 0, 0);
		
		dwGroupId = (int)SendMessage(hwndListBoxLeft, LB_GETCURSEL, 0, 0) + 1;

		CryptEnumOIDInfo(dwGroupId, 0, hwndListBoxRight, EnumOIDInfo);

		return 0;
	}

	case WM_SIZE:
		MoveWindow(hwndListBoxLeft, 0, 0, LOWORD(lParam) / 2, HIWORD(lParam), TRUE);
		MoveWindow(hwndListBoxRight, LOWORD(lParam) / 2, 0, LOWORD(lParam) - LOWORD(lParam) / 2, HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

BOOL WINAPI EnumOIDInfo(PCCRYPT_OID_INFO pOidInfo, void *pvArg)
{
	WCHAR  szBuf[256];
	LPWSTR lpszOid;
	DWORD  dwSize;

	dwSize = (lstrlenA(pOidInfo->pszOID) + 1) * sizeof(WCHAR);
	lpszOid = (LPWSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
	MultiByteToWideChar(CP_ACP, 0, pOidInfo->pszOID, lstrlenA(pOidInfo->pszOID), lpszOid, dwSize);

	wsprintfW(szBuf, L"%s (%s)", lpszOid, pOidInfo->pwszName);
	SendMessageW((HWND)pvArg, LB_ADDSTRING, 0, (LPARAM)szBuf);
	
	HeapFree(GetProcessHeap(), 0, lpszOid);

	return TRUE;
}

グループIDの定数は、CRYPT_HASH_ALG_OID_GROUP_IDを始めとして1から順に定義されているため、 選択したリストボックスのインデックスに1を加算すれば、 どのグループのOIDを列挙したいのかが特定できます。 CryptEnumOIDInfoは、OIDの列挙時にコールバック関数が呼び出されるため、 そこでリストボックスに文字列を追加できるように、 リストボックスのハンドルを指定します。 コールバック関数内では、OIDを表すpszOIDとOIDに関連付けられた 名前を表すpwszNameを1つにバッファに格納することになりますが、 これらの型はLPSTRとLPWSTRというように異なっています。 ここでは、バッファの型をWCHAR型にするということで、 pszOIDをUNICODE文字列に変換しています。

これから、各種拡張情報について説明していくにあたり、 その拡張情報がどのような値を持ったOIDで表されるのかを知っておくことは重要といえます。 今回のプログラムのCRYPT_EXT_OR_ATTR_OID_GROUP_IDを選択した場合、 各種拡張情報と属性のOIDが表示できるため、 そこで初期化したい拡張情報のOIDを確認しておくとよいでしょう。 たとえば、鍵用途(Windowsではキー使用法と表現)という拡張情報のOIDは、 2.5.29.15となっているため、CERT_EXTENSION構造体のpszObjIdにその値を指定することができます。 また、特定のOIDは専用の定義が存在するため、それを指定するのもよいでしょう。 次に、本章で取り上げる拡張情報の種類とそれを表すOIDの定義を示します。

OIDを表す定義 関連する拡張情報
szOID_SUBJECT_KEY_IDENTIFIER サブジェクト鍵識別子
szOID_AUTHORITY_KEY_IDENTIFIER 認証局鍵識別子
szOID_KEY_USAGE 鍵用途
szOID_BASIC_CONSTRAINTS2 基本制約
szOID_ENHANCED_KEY_USAGE 拡張鍵用途

一部の拡張情報は、それを表すOIDが2種類存在することがあることに注意してください。 たとえば、基本制約のOIDにはszOID_BASIC_CONSTRAINTSとszOID_BASIC_CONSTRAINTS2が存在し、 それぞれのCERT_EXTENSION構造体のValueには異なるデータが格納されることになります。 実際に拡張情報を処理することになった場合は、 どちらのOIDが指定されていても問題なく扱えるようになっているべきといえます。

OIDの登録について

アプリケーションが独自のOIDを定義して登録をした場合、 そのOIDはシステムに最初からインストールされているOIDと 同じように認識されることになっています。 OIDで表現することのできる情報の1つには、証明書の目的を表す拡張鍵用途がありますが、 予め自作のOIDを登録することにより、証明書に独自に追加したOIDを 正規の情報と見せかけるようなことも可能となります。 つまり、サーバー認証やコード署名といった既存の目的に加え、 データベース認証というような独自の目的を証明書に含むことができます。 次に、OIDを登録する例を示します。

#include <windows.h>

#pragma comment (lib, "crypt32.lib")

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	BOOL           bRegister = TRUE;
	CRYPT_OID_INFO oidInfo;

	ZeroMemory(&oidInfo, sizeof(CRYPT_OID_INFO));
	oidInfo.cbSize    = sizeof(CRYPT_OID_INFO);
	oidInfo.pszOID    = "1.23.456";
	oidInfo.pwszName  = L"データベース認証";
	oidInfo.dwGroupId = CRYPT_ENHKEY_USAGE_OID_GROUP_ID;

	if (bRegister)
		CryptRegisterOIDInfo(&oidInfo, 0);
	else
		CryptUnregisterOIDInfo(&oidInfo);

	MessageBox(NULL, TEXT("終了します。"), TEXT("OK"), MB_OK);

	return 0;
}

OIDを登録するには、CryptRegisterOIDInfoを呼び出すことになります。 この関数は、登録するOIDの情報をCRYPT_OID_INFO構造体で受け取るため、 構造体は事前に初期化しておくことになります。 ここでは、データベース認証という名前の拡張鍵用途を登録するということで、 pwszNameにその名前とdwGroupIdに拡張鍵用途を示すCRYPT_ENHKEY_USAGE_OID_GROUP_IDを指定しています。 OIDの値に関しては、好きなように決定して問題ないでしょう。 このプログラムを実行した後にCryptEnumOIDInfoにCRYPT_ENHKEY_USAGE_OID_GROUP_IDを指定した場合、 登録したOIDが列挙されることが分かります。 また、CryptRegisterOIDInfoの第2引数にCRYPT_INSTALL_OID_INFO_BEFORE_FLAGを指定すると、 登録されたOIDが先頭として列挙されるようになります。 このプログラムを実行せずに、"1.23.456"という値を証明書に追加しても、 それは不明な拡張鍵用途として解釈されるでしょう。

CryptRegisterOIDInfoは、次に示すレジストリキーのサブキーにOIDを登録します。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\OID\EncodingType 0\CryptDllFindOIDInfo

作成されるサブキーの名前はOIDとなり、Nameというエントリに関連付けられた名前が格納されます。 サブキーは、CryptEnumOIDInfoやCryptFindOIDInfoの呼び出しで参照されます。



戻る