EternalWindows
自己署名入り証明書 / 証明書作成サンプル(簡易版)

前節のような証明書を一から作成するコードは、 証明書の発行先と発行者を変える場合などに応用が利きますが、 単純に自己署名入り証明書を作成したい場合は、CertCreateSelfSignCertificateの方が便利です。 この関数は、次のように定義されています。

PCCERT_CONTEXT WINAPI CertCreateSelfSignCertificate(
  HCRYPTPROV_OR_NCRYPT_KEY_HANDLE hCryptProvOrNCryptKey,
  PCERT_NAME_BLOB pSubjectIssuerBlob,
  DWORD dwFlags,
  PCRYPT_KEY_PROV_INFO pKeyProvInfo,
  PCRYPT_ALGORITHM_IDENTIFIER pSignatureAlgorithm,
  PSYSTEMTIME pStartTime,
  PSYSTEMTIME pEndTime,
  PCERT_EXTENSIONS pExtensions
);

hCryptProvOrNCryptKeyは、CSPのハンドルであるHCRYPTPROV型、 もしくは、NCryptOpenKeyによって取得したNCRYPT_KEY_HANDLE型の変数を指定します。 NCryptOpenKeyについては、WindowsVistaから登場したCNGの関数ですが、 これについての説明はここでは割愛します。 NULLを指定した場合はデフォルトのCSPが選択され、 そのCSPに鍵コンテナと鍵ペアが作成されます。 pSubjectIssuerBlobは、発行先と発行者名をエンコードしたCERT_NAME_BLOB構造体のアドレスを指定します。 dwFlagsは、基本的に0を指定します。 pKeyProvInfoは、CRYPT_KEY_PROV_INFO構造体のアドレスを指定します。 基本的にはNULLを指定しますが、hCryptProvOrNCryptKeyにNULLを指定した場合に選択されるCSPの情報を 特定の値に限定したい場合には、この構造体を指定することがあります。 pSignatureAlgorithmは、署名に使うアルゴリズムを示すIDを指定します。 NULLを指定した場合は、SHA1RSAアルゴリズムが利用されます。 pStartTimeは、証明書の有効期間の開始日を指定します。 NULLを指定した場合は、現在の時間となります。 pEndTimeは、証明書の有効期間の終了日を指定します。 NULLを指定した場合は、1年後となります。 pExtensionsは、証明書の拡張情報を指定します。

CertCreateSelfSignCertificateで最低限、初期化しなければならない引数は、 pSubjectIssuerBlobとdwFlagsであり、それ以外の引数にはNULLを指定することできます。 ただし、hCryptProvOrNCryptKeyに関しては、可能な限りCSPのハンドルを指定するほうがよいでしょう。 デフォルトのCSPが使用され、さらに名前も知らない鍵コンテナが自動的に作成されるのは、気分のよいものではありません。 ところで、CertCreateSelfSignCertificateは鍵ペアの用途を受け取る引数を要求していませんが、 CSPのハンドルを指定した際には、これはどうなるのでしょうか。 答えは、鍵コンテナに格納されているAT_SIGNATUREの鍵ペアが利用されます。 AT_KEYEXCHANGEの鍵ペアのみしか鍵コンテナに存在しない場合は、 関数が失敗するので注意してください。 なお、hCryptProvOrNCryptKeyをNULLを指定した際に作成される鍵ペアもAT_SIGNATUREとなります。

CertCreateSelfSignCertificateは、証明書を証明書コンテキストとして返すため、 証明書ストアへの追加にあたり、CertAddEncodedCertificateToStoreは使用しません。 証明書コンテキストを要求するCertAddCertificateContextToStoreを使用します。

BOOL WINAPI CertAddCertificateContextToStore(
  HCERTSTORE hCertStore, 
  PCCERT_CONTEXT pCertContext, 
  DWORD dwAddDisposition, 
  PCCERT_CONTEXT *ppStoreContext 
);

hCertStoreは、証明書ストアのハンドルを指定します。 pCertContextは、証明書コンテキストのアドレスを指定します。 dwAddDispositionは、同一の証明書が存在している場合の処理を表す定数を指定します。 取り得る値は、CertAddEncodedCertificateToStoreと同一です。 ppStoreContextは、実際に証明書ストアに追加された証明書を受け取る変数のアドレスを指定します。

今回のプログラムは、CertCreateSelfSignCertificateを呼び出して証明書を作成します。 作成した証明書は前節と同様に、証明書ストアに追加するか、 またはファイルとして保存されることになります。

#include <windows.h>

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

BOOL AddCertificateToStore(PCCERT_CONTEXT pContext);
BOOL SaveCertificate(LPTSTR lpszFileName, PCCERT_CONTEXT pContext);
BOOL GetCryptProvider(HCRYPTPROV *hProv, LPTSTR lpszContainerName, LPTSTR lpszProviderName, DWORD dwProvType, DWORD dwKeySpec);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HCRYPTPROV     hProv;
	TCHAR          szSubjectAndIssuerName[] = TEXT("CN=SelfCert Publisher");
	BOOL           bSaveStore = TRUE;
	CERT_NAME_BLOB subjectAndIssuerName;
	PCCERT_CONTEXT pContext;
	DWORD          dwSize;
	LPBYTE         lpData;

	if (!GetCryptProvider(&hProv, TEXT("MyContainer"), MS_DEF_PROV, PROV_RSA_FULL, AT_SIGNATURE))
		return 0;

	if (!CertStrToName(X509_ASN_ENCODING, szSubjectAndIssuerName, CERT_X500_NAME_STR, NULL, NULL, &dwSize, NULL))
		return 0;
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
	CertStrToName(X509_ASN_ENCODING, szSubjectAndIssuerName, CERT_X500_NAME_STR, NULL, lpData, &dwSize, NULL);
	
	subjectAndIssuerName.cbData = dwSize;
	subjectAndIssuerName.pbData = lpData;

	pContext = CertCreateSelfSignCertificate(hProv, &subjectAndIssuerName, 0, NULL, NULL, NULL, NULL, NULL);
	if (pContext == NULL) {
		MessageBox(NULL, TEXT("証明書コンテキストの作成に失敗しました。"), NULL, MB_ICONWARNING);
		HeapFree(GetProcessHeap(), 0, lpData);
		CryptReleaseContext(hProv, 0);
		return 0;
	}

	if (bSaveStore) {
		if (AddCertificateToStore(pContext))
			MessageBox(NULL, TEXT("証明書を証明書ストアに追加しました。"), TEXT("OK"), MB_OK);
		else
			MessageBox(NULL, TEXT("証明書を証明書ストアに追加できませんでした。"), NULL, MB_ICONWARNING);
	}
	else {
		SaveCertificate(TEXT("sample.cer"), pContext);
		MessageBox(NULL, TEXT("証明書をファイルに保存しました。"), TEXT("OK"), MB_OK);
	}

	HeapFree(GetProcessHeap(), 0, lpData);
	CertFreeCertificateContext(pContext);
	CryptReleaseContext(hProv, 0);

	return 0;
}


BOOL AddCertificateToStore(PCCERT_CONTEXT pContext)
{
	HCERTSTORE hStore;

	hStore = CertOpenSystemStore(0, TEXT("MY"));
	if (hStore == NULL)
		return FALSE;

	if (!CertAddCertificateContextToStore(hStore, pContext, CERT_STORE_ADD_REPLACE_EXISTING, NULL)) {
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return FALSE;
	}

	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);

	return TRUE;
}

BOOL SaveCertificate(LPTSTR lpszFileName, PCCERT_CONTEXT pContext)
{
	HANDLE hFile;
	DWORD  dwWriteByte;
	
	hFile = CreateFile(lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return FALSE;

	WriteFile(hFile, pContext->pbCertEncoded, pContext->cbCertEncoded, &dwWriteByte, NULL);
	
	CloseHandle(hFile);

	return TRUE;
}

BOOL GetCryptProvider(HCRYPTPROV *phProv, LPTSTR lpszContainerName, LPTSTR lpszProviderName, DWORD dwProvType, DWORD dwKeySpec)
{
	HCRYPTKEY hKey;

	if (!CryptAcquireContext(phProv, lpszContainerName, lpszProviderName, dwProvType, 0)) {
		if (GetLastError() != NTE_BAD_KEYSET) {
			MessageBox(NULL, TEXT("CSPのハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING);
			return FALSE;
		}
		if (!CryptAcquireContext(phProv, lpszContainerName, lpszProviderName, dwProvType, CRYPT_NEWKEYSET)) {
			MessageBox(NULL, TEXT("鍵コンテナの作成に失敗しました。"), NULL, MB_ICONWARNING);
			return FALSE;
		}
	}
	
	if (!CryptGetUserKey(*phProv, dwKeySpec, &hKey)) {
		if (GetLastError() != NTE_NO_KEY) {
			MessageBox(NULL, TEXT("鍵ペアのハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING);
			CryptReleaseContext(*phProv, 0);
			return FALSE;
		}
		if (!CryptGenKey(*phProv, dwKeySpec, 0, &hKey)) {
			MessageBox(NULL, TEXT("鍵ペアの作成に失敗しました。"), NULL, MB_ICONWARNING);
			CryptReleaseContext(*phProv, 0);
			return FALSE;
		}
	}
	
	CryptDestroyKey(hKey);

	return TRUE;
}

基本的にはこのプログラムは、前節のCreateCertificateという自作関数が、 CertCreateSelfSignCertificateに置き換わったものであると 考えることができます。CertCreateSelfSignCertificateには、 発行先と発行者を表すCERT_NAME_BLOB構造体が必要であるため、 これに関しては明示的に初期化することになります。 また、今回のプログラムでは証明書の拡張プロパティにCSPの情報を 設定していませんが、CertCreateSelfSignCertificateの第3引数に0を指定した場合は、 自動的に拡張プロパティの設定は行われることになっています。 よって、取得した証明書コンテキストは、単純にCertAddCertificateContextToStoreに 指定するだけで問題ありません。

SELFCERT.exeについて

Microsoft Officeに付属するSELFCERT.exeは、 その主たる用途をVBAプロジェクトのデジタル署名しているものの、 実際には自己署名入り証明書を作成するツールとして機能します。 C:\Program Files\Microsoft Office\OFFICE11に存在するこのファイルを起動すると、 次のようなダイアログが表示されます。

このダイアログに入力できる唯一の情報は、証明書の名前だけです。 ここで入力した名前が、証明書の発行者と発行先に設定されます。 OKボタンを押すと、入力した名前を持った自己署名入り証明書がMY証明書に格納されます。

CertCreateSelfSignCertificateやSELFCERT.exeで自己署名入り証明書を作成した場合、 実際にどのような値が証明書に設定されたかが気になることは多いと思われます。 証明をダイアログで表示すれば多くの情報が格納されますが、 CSPの拡張情報に関しては表示されることがありません。 CertCreateSelfSignCertificateの第1引数にNULLを指定した場合や、SELFCERT.exeを実行した場合は、 次のようなコードで一度、CSPの情報を確認してみるとよいかもしれません。

#include <windows.h>

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HCERTSTORE           hStore;
	PCCERT_CONTEXT       pContext;
	TCHAR                szBuf[256];
	PCRYPT_KEY_PROV_INFO pCryptKeyProvInfo;
	DWORD                dwSize;
	
	hStore = CertOpenSystemStore(0, TEXT("MY"));
	if (hStore == NULL)
		return 0;

	pContext = CertFindCertificateInStore(hStore, X509_ASN_ENCODING, 0, CERT_FIND_SUBJECT_STR, L"SelfCert Publisher", NULL);
	if (pContext == NULL) {
		MessageBox(NULL, TEXT("証明書の取得に失敗しました。"), NULL, MB_ICONWARNING);
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);	
		return 0;
	}
	
	CertGetCertificateContextProperty(pContext, CERT_KEY_PROV_INFO_PROP_ID, NULL, &dwSize);
	pCryptKeyProvInfo = (PCRYPT_KEY_PROV_INFO)HeapAlloc(GetProcessHeap(), 0, dwSize);
	CertGetCertificateContextProperty(pContext, CERT_KEY_PROV_INFO_PROP_ID, pCryptKeyProvInfo, &dwSize);

	MessageBoxW(NULL, pCryptKeyProvInfo->pwszProvName, pCryptKeyProvInfo->pwszContainerName, MB_OK);

	CertFreeCertificateContext(pContext);
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
	HeapFree(GetProcessHeap(), 0, pCryptKeyProvInfo);

	return 0;
}

このプログラムは、CertFindCertificateInStoreの第5引数に検索したい証明書の名前を指定して実行します。 取得した証明書コンテキストは、CERT_KEY_PROV_INFO_PROP_IDを指定したCertGetCertificateContextPropertyに渡され、 これによりCRYPT_KEY_PROV_INFO構造体が初期化されることになります。 実際に試したところ、CertCreateSelfSignCertificateの第1引数にNULLを指定した場合では、 CSPがMS_STRONG_PROVとなり、鍵コンテナの名前はGUID文字列でした。 SELFCERT.exeで作成した証明書はCSPがMS_ENHANCED_PROVとなり、 鍵コンテナの名前がSelfSignedCertsになっていました。



戻る