EternalWindows
自己署名入り証明書 / 証明書作成サンプル(拡張情報版)

これまで紹介してきた拡張情報を、実際に証明書を作成するコードに追加してみましょう。 認証局鍵識別子は、自己署名入り証明書においてはサブジェクト鍵識別子と同じ値になるため、 この拡張情報は追加していません。

#include <windows.h>

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

BOOL CreateCertificate(HCRYPTPROV hProv, DWORD dwKeySpec, LPTSTR lpszSubjectName, LPBYTE *lplpCertData, LPDWORD lpdwCertSize, PCERT_EXTENSIONS pCertExtensions);
BOOL AddCertificateToStore(PCRYPT_KEY_PROV_INFO pCryptKeyProvInfo, LPBYTE lpCertData, DWORD dwCertSize);
BOOL SaveCertificate(LPTSTR lpszFileName, LPBYTE lpCertData, DWORD dwCertSize);
BOOL GetCryptProvider(HCRYPTPROV *phProv, LPTSTR lpszContainerName, LPTSTR lpszProviderName, DWORD dwProvType, DWORD dwKeySpec);
void AddSubjectKeyIdentifier(PCERT_EXTENSION pCertExtension, HCRYPTPROV hProv, DWORD dwKeySpec);
void AddKeyUsage(PCERT_EXTENSION pCertExtension, DWORD dwKeySpec);
void AddBasicConstraints(PCERT_EXTENSION pCertExtension);
void AddEnhKeyUsage(PCERT_EXTENSION pCertExtension);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	WCHAR               szContainerName[] = L"MyContainer";
	LPWSTR              lpszProviderName = MS_DEF_PROV_W;
	DWORD               dwProvType = PROV_RSA_FULL;
	DWORD               dwKeySpec = AT_SIGNATURE;
	HCRYPTPROV          hProv;
	TCHAR               szSubjectName[] = TEXT("CN=ExtCert Publisher");
	BOOL                bSaveStore = TRUE;
	LPBYTE              lpCertData;
	DWORD               dwCertSize;
	CRYPT_KEY_PROV_INFO cryptKeyProvInfo;
	DWORD               i;
	CERT_EXTENSION      certExtension[4];
	CERT_EXTENSIONS     certExtensions;

	if (!GetCryptProvider(&hProv, szContainerName, lpszProviderName, dwProvType, dwKeySpec))
		return 0;
	
	AddSubjectKeyIdentifier(&certExtension[0], hProv, dwKeySpec);
	AddKeyUsage(&certExtension[1], dwKeySpec);
	AddBasicConstraints(&certExtension[2]);
	AddEnhKeyUsage(&certExtension[3]);
	
	certExtensions.cExtension  = sizeof(certExtension) / sizeof(certExtension[0]);
	certExtensions.rgExtension = certExtension;
	
	if (!CreateCertificate(hProv, dwKeySpec, szSubjectName, &lpCertData, &dwCertSize, &certExtensions)) {
		MessageBox(NULL, TEXT("証明書データの作成に失敗しました。"), NULL, MB_ICONWARNING);
		for (i = 0; i < certExtensions.cExtension; i++)
			HeapFree(GetProcessHeap(), 0, certExtensions.rgExtension[i].Value.pbData);
		CryptReleaseContext(hProv, 0);
		return 0;
	}
	
	if (bSaveStore) {
		ZeroMemory(&cryptKeyProvInfo, sizeof(cryptKeyProvInfo));
		cryptKeyProvInfo.pwszContainerName = szContainerName;
		cryptKeyProvInfo.pwszProvName      = lpszProviderName;
		cryptKeyProvInfo.dwProvType        = dwProvType;
		cryptKeyProvInfo.dwKeySpec         = dwKeySpec;

		if (AddCertificateToStore(&cryptKeyProvInfo, lpCertData, dwCertSize))
			MessageBox(NULL, TEXT("証明書を証明書ストアに追加しました。"), TEXT("OK"), MB_OK);
		else
			MessageBox(NULL, TEXT("証明書を証明書ストアに追加できませんでした。"), NULL, MB_ICONWARNING);
	}
	else {
		SaveCertificate(TEXT("sample.cer"), lpCertData, dwCertSize);
		MessageBox(NULL, TEXT("証明書をファイルに保存しました。"), TEXT("OK"), MB_OK);
	}
	
	for (i = 0; i < certExtensions.cExtension; i++)
		HeapFree(GetProcessHeap(), 0, certExtensions.rgExtension[i].Value.pbData);

	HeapFree(GetProcessHeap(), 0, lpCertData);
	CryptReleaseContext(hProv, 0);

	return 0;
}

BOOL CreateCertificate(HCRYPTPROV hProv, DWORD dwKeySpec, LPTSTR lpszSubjectName, LPBYTE *lplpCertData, LPDWORD lpdwCertSize, PCERT_EXTENSIONS pCertExtensions)
{
	CERT_INFO             certInfo;
	BYTE                  serialNumber[4];
	SYSTEMTIME            systemTime;
	FILETIME              fileTime;
	PCERT_PUBLIC_KEY_INFO pPublicKeyInfo;
	DWORD                 dwSize;
	LPBYTE                lpData;

	ZeroMemory(&certInfo, sizeof(CERT_INFO));

	certInfo.dwVersion = CERT_V3;
	
	CryptGenRandom(hProv, sizeof(serialNumber), serialNumber);
	serialNumber[sizeof(serialNumber) - 1] &= 0x7f;
	certInfo.SerialNumber.cbData = sizeof(serialNumber);
	certInfo.SerialNumber.pbData = serialNumber;

	if (!CertStrToName(X509_ASN_ENCODING, lpszSubjectName, CERT_X500_NAME_STR, NULL, NULL, &dwSize, NULL))
		return FALSE;
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
	CertStrToName(X509_ASN_ENCODING, lpszSubjectName, CERT_X500_NAME_STR, NULL, lpData, &dwSize, NULL);
	certInfo.Subject.cbData = dwSize;
	certInfo.Subject.pbData = lpData;
	certInfo.Issuer = certInfo.Subject;

	GetSystemTime(&systemTime);
	SystemTimeToFileTime(&systemTime, &fileTime);
	certInfo.NotBefore = fileTime;
	systemTime.wYear += 1;
	SystemTimeToFileTime(&systemTime, &fileTime);
	certInfo.NotAfter = fileTime;
	
	if (!CryptExportPublicKeyInfo(hProv, dwKeySpec, X509_ASN_ENCODING, NULL, &dwSize)) {
		HeapFree(GetProcessHeap(), 0, lpData);
		return FALSE;
	}
	pPublicKeyInfo = (PCERT_PUBLIC_KEY_INFO)HeapAlloc(GetProcessHeap(), 0, dwSize);
	CryptExportPublicKeyInfo(hProv, dwKeySpec, X509_ASN_ENCODING, pPublicKeyInfo, &dwSize);
	certInfo.SubjectPublicKeyInfo = *pPublicKeyInfo;
	
	certInfo.cExtension  = pCertExtensions->cExtension;
	certInfo.rgExtension = pCertExtensions->rgExtension;

	certInfo.SignatureAlgorithm.pszObjId = szOID_RSA_SHA1RSA;
	CryptSignAndEncodeCertificate(hProv, dwKeySpec, X509_ASN_ENCODING, (LPSTR)X509_CERT_TO_BE_SIGNED, &certInfo, &certInfo.SignatureAlgorithm, NULL, NULL, lpdwCertSize);
	*lplpCertData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, *lpdwCertSize);
	CryptSignAndEncodeCertificate(hProv, dwKeySpec, X509_ASN_ENCODING, (LPSTR)X509_CERT_TO_BE_SIGNED, &certInfo, &certInfo.SignatureAlgorithm, NULL, *lplpCertData, lpdwCertSize);

	HeapFree(GetProcessHeap(), 0, lpData);
	HeapFree(GetProcessHeap(), 0, pPublicKeyInfo);
	
	return TRUE;
}

BOOL AddCertificateToStore(PCRYPT_KEY_PROV_INFO pCryptKeyProvInfo, LPBYTE lpCertData, DWORD dwCertSize)
{
	HCERTSTORE     hStore;
	PCCERT_CONTEXT pContext;

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

	if (!CertAddEncodedCertificateToStore(hStore, X509_ASN_ENCODING, lpCertData, dwCertSize, CERT_STORE_ADD_REPLACE_EXISTING, &pContext)) {
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return FALSE;
	}

	CertSetCertificateContextProperty(pContext, CERT_KEY_PROV_INFO_PROP_ID, 0, pCryptKeyProvInfo);

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

	return TRUE;
}

BOOL SaveCertificate(LPTSTR lpszFileName, LPBYTE lpCertData, DWORD dwCertSize)
{
	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, lpCertData, dwCertSize, &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;
}

void AddSubjectKeyIdentifier(PCERT_EXTENSION pCertExtension, HCRYPTPROV hProv, DWORD dwKeySpec)
{
	PCERT_PUBLIC_KEY_INFO pPublicKeyInfo;
	CRYPT_DATA_BLOB       subjectKeyIdentifier;
	LPBYTE                lpPublicKeyHash;
	DWORD                 dwSize;
	LPBYTE                lpData;
	
	CryptExportPublicKeyInfo(hProv, dwKeySpec, X509_ASN_ENCODING, NULL, &dwSize);
	pPublicKeyInfo = (PCERT_PUBLIC_KEY_INFO)HeapAlloc(GetProcessHeap(), 0, dwSize);
	CryptExportPublicKeyInfo(hProv, dwKeySpec, X509_ASN_ENCODING, pPublicKeyInfo, &dwSize);
	
	CryptHashPublicKeyInfo(hProv, CALG_SHA1, 0, X509_ASN_ENCODING, pPublicKeyInfo, NULL, &dwSize);
	lpPublicKeyHash = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
	CryptHashPublicKeyInfo(hProv, CALG_SHA1, 0, X509_ASN_ENCODING, pPublicKeyInfo, lpPublicKeyHash, &dwSize);

	subjectKeyIdentifier.cbData = dwSize;
	subjectKeyIdentifier.pbData = lpPublicKeyHash;

	CryptEncodeObject(X509_ASN_ENCODING, szOID_SUBJECT_KEY_IDENTIFIER, &subjectKeyIdentifier, NULL, &dwSize);
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
	CryptEncodeObject(X509_ASN_ENCODING, szOID_SUBJECT_KEY_IDENTIFIER, &subjectKeyIdentifier, lpData, &dwSize);

	pCertExtension->pszObjId     = szOID_SUBJECT_KEY_IDENTIFIER;
	pCertExtension->fCritical    = FALSE;
	pCertExtension->Value.cbData = dwSize;
	pCertExtension->Value.pbData = lpData;

	HeapFree(GetProcessHeap(), 0, lpPublicKeyHash);
	HeapFree(GetProcessHeap(), 0, pPublicKeyInfo);
}

void AddKeyUsage(PCERT_EXTENSION pCertExtension, DWORD dwKeySpec)
{
	CRYPT_BIT_BLOB keyUsage;
	BYTE           identifier;
	DWORD          dwSize;
	LPBYTE         lpData;

	if (dwKeySpec == AT_KEYEXCHANGE)
		identifier = CERT_DIGITAL_SIGNATURE_KEY_USAGE | CERT_KEY_ENCIPHERMENT_KEY_USAGE | CERT_DATA_ENCIPHERMENT_KEY_USAGE | CERT_KEY_AGREEMENT_KEY_USAGE;
	else if (dwKeySpec == AT_SIGNATURE)
		identifier = CERT_DIGITAL_SIGNATURE_KEY_USAGE | CERT_NON_REPUDIATION_KEY_USAGE | CERT_KEY_CERT_SIGN_KEY_USAGE | CERT_CRL_SIGN_KEY_USAGE;
	else
		;
	
	keyUsage.cbData      = 1;
	keyUsage.pbData      = &identifier;
	keyUsage.cUnusedBits = 0;

	CryptEncodeObject(X509_ASN_ENCODING, szOID_KEY_USAGE, &keyUsage, NULL, &dwSize);
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
	CryptEncodeObject(X509_ASN_ENCODING, szOID_KEY_USAGE, &keyUsage, lpData, &dwSize);

	pCertExtension->pszObjId     = szOID_KEY_USAGE;
	pCertExtension->fCritical    = TRUE;
	pCertExtension->Value.cbData = dwSize;
	pCertExtension->Value.pbData = lpData;
}

void AddBasicConstraints(PCERT_EXTENSION pCertExtension)
{
	CERT_BASIC_CONSTRAINTS2_INFO basicConstraints;
	DWORD                        dwSize;
	LPBYTE                       lpData;
	
	basicConstraints.fCA                 = TRUE;
	basicConstraints.fPathLenConstraint  = TRUE;
	basicConstraints.dwPathLenConstraint = 1;

	CryptEncodeObject(X509_ASN_ENCODING, szOID_BASIC_CONSTRAINTS2, &basicConstraints, NULL, &dwSize);
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
	CryptEncodeObject(X509_ASN_ENCODING, szOID_BASIC_CONSTRAINTS2, &basicConstraints, lpData, &dwSize);

	pCertExtension->pszObjId     = szOID_BASIC_CONSTRAINTS2;
	pCertExtension->fCritical    = TRUE;
	pCertExtension->Value.cbData = dwSize;
	pCertExtension->Value.pbData = lpData;
}

void AddEnhKeyUsage(PCERT_EXTENSION pCertExtension)
{
	CERT_ENHKEY_USAGE enhKeyUsage;
	DWORD             dwSize;
	LPBYTE            lpData;
	LPSTR             lpszIdentifier[] = {
		szOID_PKIX_KP_SERVER_AUTH, szOID_PKIX_KP_CODE_SIGNING
	};

	enhKeyUsage.cUsageIdentifier     = 2;
	enhKeyUsage.rgpszUsageIdentifier = lpszIdentifier;
	
	CryptEncodeObject(X509_ASN_ENCODING, szOID_ENHANCED_KEY_USAGE, &enhKeyUsage, NULL, &dwSize);
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
	CryptEncodeObject(X509_ASN_ENCODING, szOID_ENHANCED_KEY_USAGE, &enhKeyUsage, lpData, &dwSize);

	pCertExtension->pszObjId     = szOID_ENHANCED_KEY_USAGE;
	pCertExtension->fCritical    = FALSE;
	pCertExtension->Value.cbData = dwSize;
	pCertExtension->Value.pbData = lpData;
}

まず、WinMainの処理に注目してみましょう。 AddSubjectKeyIdentifierなどの各拡張情報の関数は、第1引数にCERT_EXTENSION構造体を要求していました。 今回のプログラムでは追加する拡張情報が4つであるため、certExtension[4]という変数を宣言し、 各要素のアドレスを各拡張情報の関数に指定するようにしています。 最終的には、CERT_INFO構造体のrgExtensionにこの配列を指定することになりますが、 その際にはCERT_INFO構造体のcExtensionに配列の要素数を指定しなくてはならないため、 配列と要素数をCERT_EXTENSIONS構造体にまとめています。 つまり、CreateCertificateに指定する引数を配列と要素数に分けたのではなく、 構造体で指定するようにしたということです。 CERT_EXTENSIONS構造体を利用する方法は、CertCreateSelfSignCertificateで 証明書を作成する際にも用いられます。

CreateCertificateに指定したCERT_EXTENSIONS構造体は、 CERT_INFO構造体の初期化の際に利用されます。

certInfo.cExtension  = pCertExtensions->cExtension;
certInfo.rgExtension = pCertExtensions->rgExtension;

これにより、certInfoをCryptSignAndEncodeCertificateに指定した際に、 拡張情報を含めた証明書のバイトデータが作成されることになります。

今回作成した証明書は、CAの証明書として利用することを想定しています。 理由としては、基本制約の設定において、CERT_BASIC_CONSTRAINTS2_INFO.bCAをTRUEにしているからです。 また、自己署名の証明書であるため、鍵の用途にAT_SIGNATUREを指定し、 AddKeyUsageでCERT_KEY_CERT_SIGN_KEY_USAGEを含めようとしています。 CERT_BASIC_CONSTRAINTS2_INFO.bCAがTRUEである証明書を署名に利用した場合、 基本制約が監視されていないという警告が表示されます。


戻る