EternalWindows
証明書管理 / PFXのインポート

エクスポートされたPFXパケットは、PFXImportCertStoreでインポートすることができます。 このインポートで生じる結果は、エクスポートする前の状態の結果と完全に同一になることが望まれますが、 実際には一部その限りではありません。 PFXパケットから取り出された秘密鍵を格納するために作成される鍵コンテナの名前は、ランダムなGUID文字列となり、 元々の鍵コンテナの名前とは一致することはないのです。 この点に注意して、PFXImportCertStoreの使い方を見ていきます。

HCERTSTORE WINAPI PFXImportCertStore(
  CRYPT_DATA_BLOB *pPFX,
  LPCWSTR szPassword,
  DWORD dwFlags
);

pPFXは、インポートしたいPFXパケットを指定します。 szPasswordは、PFXパケットを複合化するためのパスワードを指定します。 これは、PFXパケットのエクスポート時に指定したパスワードと同一の値です。 dwFlagsは、インポート時の詳細を定義する定数を指定します。 戻り値は、インポートされた証明書を格納する証明書ストアのハンドルとなります。

dwFlagsに指定できる定数を次に示します。

定数 意味
CRYPT_EXPORTABLE インポートされた秘密鍵をエクスポート可能とする。 つまり、インポートされた証明書と秘密鍵は、 再びPFX形式としてエクスポート可能となる。
CRYPT_USER_PROTECTED インポートされた秘密鍵に保護を設定する。 これにより、その秘密鍵の参照時にダイアログが表示される。
CRYPT_MACHINE_KEYSET インポートされた秘密鍵を格納する鍵コンテナをマシンコンテナとする。
CRYPT_USER_KEYSET インポートされた秘密鍵を格納する鍵コンテナを現在のユーザーのものとする。 PFXパケットにマシンコンテナの旨が書かれていても、それは考慮しない。

今回のプログラムは、カレントディレクトリに存在するPFXファイルから証明書を取得し、 それをMY証明書に追加します。 PFXVerifyPasswordまでのコードは前節のものと同一です。

#include <windows.h>

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HANDLE          hFile;
	DWORD           dwReadByte;
	WCHAR           szPassword[] = L"pass";
	CRYPT_DATA_BLOB pfxPacket;
	HCERTSTORE      hMemoryStore;
	HCERTSTORE      hStore;
	PCCERT_CONTEXT  pContext = NULL;

	hFile = CreateFile(TEXT("sample.pfx"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return 0;
	
	pfxPacket.cbData = GetFileSize(hFile, NULL);
	pfxPacket.pbData = (LPBYTE)CryptMemAlloc(pfxPacket.cbData);
	ReadFile(hFile, pfxPacket.pbData, pfxPacket.cbData, &dwReadByte, NULL);
	CloseHandle(hFile);
	
	if (!PFXIsPFXBlob(&pfxPacket)) {
		MessageBox(NULL, TEXT("PFX形式ではありません。"), NULL, MB_ICONWARNING);
		CryptMemFree(pfxPacket.pbData);
		return 0;
	}
	
	if (!PFXVerifyPassword(&pfxPacket, szPassword, 0)) {
		MessageBox(NULL, TEXT("パスワードが不正です。"), NULL, MB_ICONWARNING);
		CryptMemFree(pfxPacket.pbData);
		return 0;
	}

	hStore = CertOpenSystemStore(0, TEXT("MY"));
	if (hStore == NULL) {
		CryptMemFree(pfxPacket.pbData);
		return 0;
	}

	hMemoryStore = PFXImportCertStore(&pfxPacket, szPassword, CRYPT_EXPORTABLE | CRYPT_USER_PROTECTED);
	if (hMemoryStore != NULL) {
		for (;;) {
			pContext = CertEnumCertificatesInStore(hMemoryStore, pContext);
			if (pContext == NULL)
				break;
			CertAddCertificateContextToStore(hStore, pContext, CERT_STORE_ADD_ALWAYS, NULL);
		}

		MessageBox(NULL, TEXT("インポートしました。"), TEXT("OK"), MB_OK);
	}
	else
		MessageBox(NULL, TEXT("インポートに失敗しました。"), NULL, MB_ICONWARNING);

	CryptMemFree(pfxPacket.pbData);
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
	CertCloseStore(hMemoryStore, CERT_CLOSE_STORE_CHECK_FLAG);
	
	return 0;
}

PFXImportCertStoreが成功した場合に起こる内部動作は、主に2つあります。 1つ目は、PFXパケットから秘密鍵が取り出され、 その秘密鍵を格納するための鍵コンテナが作成されます。 既に述べたように、この鍵コンテナの名前はランダムなGUID文字列となっています。 2つ目は、PFXパケットから証明書が取り出され、 それを証明書ストアに格納した形で呼び出し側に返します。 注意しなければならないのは、この証明書ストアがあくまでPFXImportCertStoreに よって内部的に作成されたものということであり、 実際に存在するMY証明書ストアなどに証明書が追加されたわけではないということです。 つまり、内部的に作成された証明書ストアから証明書を取得し、 それを実在の証明書ストアに追加するという作業が必要になります。

hMemoryStore = PFXImportCertStore(&pfxPacket, szPassword, CRYPT_EXPORTABLE | CRYPT_USER_PROTECTED);
if (hMemoryStore != NULL) {
	for (;;) {
		pContext = CertEnumCertificatesInStore(hMemoryStore, pContext);
		if (pContext == NULL)
			break;
		CertAddCertificateContextToStore(hStore, pContext, CERT_STORE_ADD_ALWAYS, NULL);
	}

	MessageBox(NULL, TEXT("インポートしました。"), TEXT("OK"), MB_OK);
}

PFXImportCertStoreが返した証明書ストアのハンドルをCertEnumCertificatesInStoreに指定し、 格納されている証明書が存在し続ける限り、CertAddCertificateContextToStoreで実在の証明書ストアに追加しています。 hStoreはMY証明書ストアのハンドルであり、この証明書ストアに秘密鍵に関連する証明書を追加するのは妥当な処理ですが、 上記コードはどのような証明書もMY証明書ストアに追加しようとしています。 実のところ、これは単一の自己署名入り証明書なら問題はありませんが、 個人の証明書の他にCAの証明書が含まれているならば、 それはCAの証明書ストアに追加するべきです。 本来ならば、証明書がCAの証明書かどうかを判断すめるために、 証明書の拡張情報に含まれる基本制約などを確認することになるでしょう。

CryptUIWizImportによるインポート

PFXImportCertStoreの呼び出しは、ファイルからデータを取得するところから始まり、 また、関数成功後には証明書を証明書ストアに追加しなければならないなど、 アプリケーションが自ら行うべき作業が多く大変です。 PFXパケットをメモリに維持するつもりがない場合は、 CryptUIWizImportによるPFXのインポートを利用した方が効率的といえるでしょう。 PFXImportCertStoreと同じく、UIを表示しない形でCryptUIWizImportを呼び出す例を示します。

#include <windows.h>
#include <cryptuiapi.h>

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	BOOL                        bResult;
	CRYPTUI_WIZ_IMPORT_SRC_INFO srcInfo;

	srcInfo.dwSize          = sizeof(CRYPTUI_WIZ_IMPORT_SRC_INFO);
	srcInfo.dwSubjectChoice = CRYPTUI_WIZ_IMPORT_SUBJECT_FILE;
	srcInfo.pwszFileName    = L"sample.pfx";
	srcInfo.dwFlags         = CRYPT_EXPORTABLE | CRYPT_USER_PROTECTED;
	srcInfo.pwszPassword    = L"pass";

	bResult = CryptUIWizImport(CRYPTUI_WIZ_NO_UI, NULL, NULL, &srcInfo, NULL);
	if (bResult)
		MessageBox(NULL, TEXT("インポートしました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("インポートに失敗しました。"), NULL, MB_ICONWARNING);

	return 0;
}

UIを表示しないインポートを行う場合は、CryptUIWizImportの第1引数にCRYPTUI_WIZ_NO_UIを指定し、 CRYPTUI_WIZ_IMPORT_SRC_INFO構造体を初期化することになります。 このような構造体が必要となるのは、インポートに関する情報をUI上で入力することができないからです。 dwSubjectChoiceにCRYPTUI_WIZ_IMPORT_SUBJECT_FILEを指定した場合、 pwszFileNameメンバにPFXファイルの名前を指定することになります。 dwFlagsメンバは、PFXImportCertStoreと一部同じ定数が指定可能で、 上記の例では鍵のエクスポートと鍵の保護を設定しています。 これらが不要な場合は、0を指定しても問題ありません。 pwszPasswordメンバは、PFX形式のデータを復号化するためのパスワードを指定します。 なお、CryptUIWizImportによるインポートでも、秘密鍵を格納する鍵コンテナの名前はランダムに決定されます。



戻る