EternalWindows
証明書管理 / PFXのエクスポート

PFXとは、Personal Information Exchangeの略で、 証明書とそれに関連した秘密鍵を共に格納できる形式を指します。 この形式は、証明書と秘密鍵をパスワードを基に暗号化しているため、 仮に第三者がPFX形式のファイルを入手しても、 パスワードが分からないことには証明書と秘密鍵が取り出されることはありません。 PFXは現在のPKCS #12の旧称されていますが、Windowsではどちらも同一の形式として扱われることになっています。

証明書のエクスポート形式には、主にDER形式(.cer)とPFX形式(.pfx)がありますが、 PFX形式でエクスポートできるのは基本的に個人の証明書に限ります。 理由は、個人の証明書を作成している時点で秘密鍵を作成しており、 その秘密鍵が証明書に関連付けられているはずだからです。 PFX形式でエクスポートすれば、証明書と秘密鍵を1つのファイルで管理することができ、 別コンピュータ上に証明書と秘密鍵をインポートするようなこともできるようになります。 証明書をPFX形式でエクスポートするには、PFXExportCertStoreExを呼び出します。

BOOL WINAPI PFXExportCertStoreEx(
  HCERTSTORE hStore,
  CRYPT_DATA_BLOB *pPFX,
  LPCWSTR szPassword,
  void *pvReserved,
  DWORD dwFlags
);

hStoreは、エクスポートしたい証明書を格納した証明書ストアのハンドルを指定します。 pPFXは、エクスポートされたPFXパケット(PFX形式のデータ)を受け取るCRYPT_DATA_BLOB構造体のアドレスを指定します。 szPasswordは、PFXパケットをデコードするためのパスワードを指定します。 pvReservedは、予約されているためNULLを指定します。 dwFlagsは、エクスポートに関数する定数を指定します。 次のような定数が定義されています。

定数 意味
EXPORT_PRIVATE_KEYS 証明書のエクスポートと共に秘密鍵もエクスポートする。 通常、この定数は指定することになるはずである。
REPORT_NO_PRIVATE_KEY 証明書に含まれる公開鍵に関連する秘密鍵が存在しない場合、関数はFALSEを返す。 また、GetLastErrorは、CRYPT_E_NOT_FOUNDを返す。
REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY 証明書に含まれる公開鍵に関連する秘密鍵がエクスポート不可の場合、関数はFALSEを返す。 また、GetLastErrorは、NTE_BAD_KEY_STATEを返す。

今回のプログラムは、PFXExportCertStoreExで証明書からPFXパケットを作成し、 それをファイルとして保存します。

#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)
{
	HCERTSTORE      hStore;
	PCCERT_CONTEXT  pContext;
	CRYPT_DATA_BLOB pfxPacket;
	HCERTSTORE      hMemoryStore;
	WCHAR           szPassword[] = L"pass";
	HANDLE          hFile;
	DWORD           dwWriteByte;
	
	hStore = CertOpenSystemStore(0, TEXT("MY"));
	if (hStore == NULL)
		return 0;

	pContext = CryptUIDlgSelectCertificateFromStore(hStore, NULL, NULL, NULL, 0, 0, NULL);
	if (pContext == NULL)
		return 0;
	
	hMemoryStore = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, NULL);
	CertAddCertificateContextToStore(hMemoryStore, pContext, CERT_STORE_ADD_ALWAYS, NULL);

	pfxPacket.pbData = NULL;
	if (!PFXExportCertStoreEx(hMemoryStore, &pfxPacket, szPassword, NULL, EXPORT_PRIVATE_KEYS | REPORT_NO_PRIVATE_KEY | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY)) {
		MessageBox(NULL, TEXT("エクスポートに失敗しました。"), NULL, MB_ICONWARNING);
		CertCloseStore(hMemoryStore, CERT_CLOSE_STORE_CHECK_FLAG);
		CertFreeCertificateContext(pContext);
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return 0;
	}

	pfxPacket.pbData = (LPBYTE)CryptMemAlloc(pfxPacket.cbData);
	if (!PFXExportCertStoreEx(hMemoryStore, &pfxPacket, szPassword, NULL, EXPORT_PRIVATE_KEYS | REPORT_NO_PRIVATE_KEY | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY)) {
		MessageBox(NULL, TEXT("エクスポートに失敗しました。"), NULL, MB_ICONWARNING);
		CryptMemFree(pfxPacket.pbData);
		CertCloseStore(hMemoryStore, CERT_CLOSE_STORE_CHECK_FLAG);
		CertFreeCertificateContext(pContext);
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return 0;
	}	

	hFile = CreateFile(TEXT("sample.pfx"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	WriteFile(hFile, pfxPacket.pbData, pfxPacket.cbData, &dwWriteByte, NULL);
	CloseHandle(hFile);
	
	MessageBox(NULL, TEXT("PFXファイルとしてエクスポートしました。"), TEXT("OK"), MB_OK);

	CryptMemFree(pfxPacket.pbData);
	CertCloseStore(hMemoryStore, CERT_CLOSE_STORE_CHECK_FLAG);
	CertFreeCertificateContext(pContext);
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);

	return 0;
}

PFXExportCertStoreExを呼び出すにあたって、まず気に留めておかなければらないことは、 この関数が証明書ストアのハンドルを要求しているというところです。 普通ならば、証明書コンテキストのハンドルを要求するように思えますが、 PFX形式は複数の証明書を格納することができるので、 それらの証明書を格納した証明書ストアのハンドルを受け取ることになっているのです。

hMemoryStore = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, NULL);
CertAddCertificateContextToStore(hMemoryStore, pContext, CERT_STORE_ADD_ALWAYS, NULL);

まず、PFXに追加したい一連の証明書を格納できるメモリ上の証明書ストアを作成します。 そして、この証明書ストアにCertAddCertificateContextToStoreで証明書を追加します。 今回の例では、追加する証明書は1つとしていますが、 場合によってはその証明書に署名を行ったCAの証明書を含めることもあります。

PFXExportCertStoreExおけるエクスポートとは、ファイルを作成してそこにPFXパケットを書き込むのではなく、 単純にPFXパケットを呼び出し元に返すだけです。 つまり、ファイルの作成と書き込みはアプリケーションが担当することになります。 1回目の呼び出しでは、PFXパケットのデータを受け取るだけのバッファを用意できていないので、 まず、データのサイズを受け取ることに専念します。

pfxPacket.pbData = NULL;
if (!PFXExportCertStoreEx(hMemoryStore, &pfxPacket, szPassword, NULL, EXPORT_PRIVATE_KEYS | REPORT_NO_PRIVATE_KEY | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY)) {
	MessageBox(NULL, TEXT("エクスポートに失敗しました。"), NULL, MB_ICONWARNING);
	CertCloseStore(hMemoryStore, CERT_CLOSE_STORE_CHECK_FLAG);
	CertFreeCertificateContext(pContext);
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
	return 0;
}

pbDataメンバにNULLを指定することで、関数が成功した際にcbDataメンバが初期化されます。 第3引数はデータを暗号化するためのパスワードで、ここではローカルに宣言している変数を指定します。 実際の開発では、ユーザー入力でパスワードを初期化するものと思われますが、 そのようなときはパスワードが不要になった時点でSecureZeroMemoryを呼び出すことになるでしょう。 第5引数は、EXPORT_PRIVATE_KEYSを指定するのは当然ですが、 これだけを指定した場合は、証明書に秘密鍵が関連付けられていない場合や、 秘密鍵がエクスポート可能でない場合にも関数は成功することになります。 これでは、作成されるPFXパケットには秘密鍵が含まれておらず、問題となりますから、 関連付けやエクスポートの有無を確認する定数も指定するべきといえます。

PFXパケットのサイズを取得したらその分のメモリを確保し、2回目のPFXExportCertStoreExの呼び出しに入ります。 ここでは、メモリの確保にCryptMemAllocという関数を利用していますが、 これはMicrosoftのPFXのサンプルにて、この関数を利用していたから倣っているだけであり、 HeapAllocを使っても何の問題もありません。 2回目の呼び出しが成功したら、pbDataメンバにPFXパケットを格納されたことになるため、 後はこれをファイルに保存すればよいことになります。 PFX形式のファイルを作成したいので、拡張子は.pfxか.p12のいずれかを指定することになります。

CryptUIWizExportによるエクスポート

PXFファイルのエクスポートは、CryptUIWizExportでも行うことができます。 PFXExportCertStoreExと異なり、この関数はPFXパケットを返すのではなく、 PFXファイルを直接作成するため、場合によってはこちらの方が便利となります。 CryptUIWizExportの第1引数にCRYPTUI_WIZ_NO_UIを指定し、 UIを表示しないエクスポートを例に挙げます。

#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;
	HCERTSTORE                          hStore;
	PCCERT_CONTEXT                      pContext;
	CRYPTUI_WIZ_EXPORT_INFO             exportInfo;
	CRYPTUI_WIZ_EXPORT_CERTCONTEXT_INFO contextInfo;
	
	hStore = CertOpenSystemStore(0, TEXT("MY"));
	if (hStore == NULL)
		return 0;

	pContext = CryptUIDlgSelectCertificateFromStore(hStore, NULL, NULL, NULL, 0, 0, NULL);
	if (pContext == NULL)
		return 0;

	exportInfo.dwSize             = sizeof(CRYPTUI_WIZ_EXPORT_INFO);
	exportInfo.pwszExportFileName = L"sample.pfx";
	exportInfo.dwSubjectChoice    = CRYPTUI_WIZ_EXPORT_CERT_CONTEXT;
	exportInfo.pCertContext       = pContext;

	contextInfo.dwSize             = sizeof(CRYPTUI_WIZ_EXPORT_CERTCONTEXT_INFO);
	contextInfo.dwExportFormat     = CRYPTUI_WIZ_EXPORT_FORMAT_PFX;
	contextInfo.fExportChain       = FALSE;
	contextInfo.fExportPrivateKeys = TRUE;
	contextInfo.pwszPassword       = L"pass";
	contextInfo.fStrongEncryption  = TRUE;
	
	bResult = CryptUIWizExport(CRYPTUI_WIZ_NO_UI, NULL, NULL, &exportInfo, &contextInfo);
	if (bResult)
		MessageBox(NULL, TEXT("証明書をエクスポートしました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("証明書のエクスポートに失敗しました。"), NULL, MB_ICONWARNING);

	CertFreeCertificateContext(pContext);
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);

	return 0;
}

まず、CRYPTUI_WIZ_EXPORT_INFO構造体のpwszExportFileNameメンバを初期化しておきます。 UI経由でエクスポート先のファイル名を入力することができないため、 このメンバを使って必ずファイル名を与えなければなりません。 UIを表示しないエクスポートでは、CRYPTUI_WIZ_EXPORT_CERTCONTEXT_INFO構造体の初期化も必須となります。 dwExportFormatメンバは、証明書をどのような形式でエクスポートするかを表す定数で、 CRYPTUI_WIZ_EXPORT_FORMAT_PFXはPFX形式でエクスポートすることを意味します。 fExportChainメンバは、証明書チェーンを証明書に含めるかどうかですが、この例ではFALSEとしています。 fExportPrivateKeysメンバは、秘密鍵をエクスポートするかどうかです。 dwExportFormatメンバにCRYPTUI_WIZ_EXPORT_FORMAT_PFXを指定した場合は、 このメンバはTRUEを指定することになります。 また、それと同時にpwszPasswordメンバにPFXファイルのパスワードを設定することになります。 CRYPTUI_WIZ_EXPORT_FORMAT_PFXを指定しない場合は、たとえば、 DER形式のCRYPTUI_WIZ_EXPORT_FORMAT_DERを指定した場合は、それぞれFASLEとNULLを指定します。 fStrongEncryptionは、強力な暗号化を行うかどうかを指定しますが、これは常にTRUEとするべきだと思われます。 そうすることで、作成されたPFXファイルが以前のWinodwsで作成されたPFXファイルと互換性ができます。 なお、CryptUIWizExportでは、指定した証明書に関連する秘密鍵がエクスポート可能でない場合は失敗しますが、 関連する秘密鍵自体が存在しない場合には成功することに注意してください。



戻る