EternalWindows
ファイル署名 / 署名の格納

前節では、SignerSignを呼び出すために必要な構造体と定義を示しました。 今回は、実際にSignerSignの呼び出しを行います。 まず、関数の定義を確認します。

HRESULT WINAPI SignerSign(
  SIGNER_SUBJECT_INFO *pSubjectInfo,
  SIGNER_CERT *pSignerCert,
  SIGNER_SIGNATURE_INFO *pSignatureInfo,
  SIGNER_PROVIDER_INFO *pProviderInfo,
  LPCWSTR pwszHttpTimeStamp,
  PCRYPT_ATTRIBUTES psRequest,
  LPVOID pSipData
);

pSubjectInfoは、署名が格納されるファイルを表すSIGNER_SUBJECT_INFO構造体のアドレスを指定します。 pSignerCertは、署名に格納する証明書を表すSIGNER_CERT構造体のアドレスを指定します。 pSignatureInfoは、署名アルゴリズムなどを表すSIGNER_SIGNATURE_INFO構造体のアドレスを指定します。 pProviderInfoは、CSP情報を表すSIGNER_PROVIDER_INFO構造体のアドレスを指定します。 pwszHttpTimeStampは、タイムスタンプサーバーのURLを指定します。 署名にタイムスタンプを設定するつもりがない場合は、NULLで構いません。 psRequestは、タイムスタンプサーバーに送信するリクエストを指定します。 pwszHttpTimeStampがNULLの場合は、NULLで構いません。 pSipDataは、内部的に呼ばれているSIPに渡すデータを指定します。 不要な場合は、NULLで構いません。

SignerSignによる署名は、CryptSIPPutSignedDataMsgによる署名より優れているといえます。 たとえば、CryptSIPPutSignedDataMsgの場合は、.msiへの署名に対してサブジェクトGUIDを調整する手順が発生しますが、 SignerSignならば他のファイル形式と同様に署名を行うことができます。 また、CryptSIPPutSignedDataMsgでは.jsや.vbsのスクリプトファイルに署名を行うことができませんが、 SignerSignならば問題なく成功します。

今回のプログラムは、カレントディレクトリに存在する.spcファイルと.pvkファイルを使用して ファイルに署名を行います。 この2つのファイルの手に入れる方法は、次節で説明します。

#include <windows.h>
#include "signer.h"

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

typedef HRESULT (WINAPI *LPFNSIGNERTSIGN)(SIGNER_SUBJECT_INFO *, SIGNER_CERT *, SIGNER_SIGNATURE_INFO *, SIGNER_PROVIDER_INFO *, LPCWSTR, PCRYPT_ATTRIBUTES, LPVOID);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HMODULE               hmod;
	LPFNSIGNERTSIGN       lpfnSignerSign;
	SIGNER_FILE_INFO      fileInfo;
	SIGNER_SUBJECT_INFO   subjectInfo;
	SIGNER_CERT           signerCert;
	SIGNER_SIGNATURE_INFO signatureInfo;
	SIGNER_PROVIDER_INFO  providerInfo;
	DWORD                 dwIndex = 0;
	HRESULT               hr;

	hmod = LoadLibrary(TEXT("mssign32.dll"));
	if (hmod == NULL) {
		MessageBox(NULL, TEXT("mssign32.dllが見つかりません。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	lpfnSignerSign = (LPFNSIGNERTSIGN)GetProcAddress(hmod, "SignerSign");
	if (lpfnSignerSign == NULL) {
		MessageBox(NULL, TEXT("関数がエクスポートされていません。"), NULL, MB_ICONWARNING);
		FreeLibrary(hmod);
		return 0;
	}

	fileInfo.cbSize       = sizeof(SIGNER_FILE_INFO);
	fileInfo.pwszFileName = L"sample.exe";
	fileInfo.hFile        = NULL;

	subjectInfo.cbSize          = sizeof(SIGNER_SUBJECT_INFO);
	subjectInfo.pdwIndex        = &dwIndex;
	subjectInfo.dwSubjectChoice = SIGNER_SUBJECT_FILE;
	subjectInfo.pSignerFileInfo = &fileInfo;

	signerCert.cbSize       = sizeof(SIGNER_CERT);
	signerCert.dwCertChoice = SIGNER_CERT_SPC_FILE;
	signerCert.pwszSpcFile  = L"sample.spc";
	signerCert.hwnd         = NULL;

	signatureInfo.cbSize            = sizeof(SIGNER_SIGNATURE_INFO);
	signatureInfo.algidHash         = CALG_MD5;
	signatureInfo.dwAttrChoice      = SIGNER_NO_ATTR;
	signatureInfo.pAttrAuthcode     = NULL;
	signatureInfo.psAuthenticated   = NULL;
	signatureInfo.psUnauthenticated = NULL;

	providerInfo.pwszProviderName = NULL;
	providerInfo.dwProviderType   = PROV_RSA_FULL;
	providerInfo.dwKeySpec        = 0;
	providerInfo.dwPvkChoice      = PVK_TYPE_FILE_NAME;
	providerInfo.pwszPvkFileName  = L"sample.pvk";

	hr = lpfnSignerSign(&subjectInfo, &signerCert, &signatureInfo, &providerInfo, NULL, NULL, NULL);
	if (hr == S_OK)
		MessageBox(NULL, TEXT("署名を格納しました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("署名の格納に失敗しました。"), NULL, MB_ICONWARNING);

	FreeLibrary(hmod);

	return 0;
}

SignerSignを呼び出すために必要な構造体などは、前節で示したようにsigner.hに定義しているので、 忘れずにインクルードしておくことになります。 各種構造体の初期化を順に見ていきます。

fileInfo.cbSize       = sizeof(SIGNER_FILE_INFO);
fileInfo.pwszFileName = L"sample.exe";
fileInfo.hFile        = NULL;

SIGNER_FILE_INFO構造体は、SignerSignの引数としてはありませんが、 SIGNER_SUBJECT_INFO構造体に含まれているので初期化しておくことになります。 pwszFileNameに署名対象のファイルを指定することになります。

subjectInfo.cbSize          = sizeof(SIGNER_SUBJECT_INFO);
subjectInfo.pdwIndex        = &dwIndex;
subjectInfo.dwSubjectChoice = SIGNER_SUBJECT_FILE;
subjectInfo.pSignerFileInfo = &fileInfo;

dwSubjectChoiceにSIGNER_SUBJECT_FILEを指定する場合は、 pSignerFileInfoにSIGNER_FILE_INFO構造体のアドレスを指定します。 dwIndexは署名を格納するインデックスですが、必ず0に初期化しておかなければなりません。

signerCert.cbSize       = sizeof(SIGNER_CERT);
signerCert.dwCertChoice = SIGNER_CERT_SPC_FILE;
signerCert.pwszSpcFile  = L"sample.spc";
signerCert.hwnd         = NULL;

dwCertChoiceにSIGNER_CERT_SPC_FILEを指定すると、pwszSpcFileに.spcファイルを指定することができます。

signatureInfo.cbSize            = sizeof(SIGNER_SIGNATURE_INFO);
signatureInfo.algidHash         = CALG_MD5;
signatureInfo.dwAttrChoice      = SIGNER_NO_ATTR;
signatureInfo.pAttrAuthcode     = NULL;
signatureInfo.psAuthenticated   = NULL;
signatureInfo.psUnauthenticated = NULL;

algidHashに署名に使うハッシュアルゴリズムを必ず指定します。 pAttrAuthcodeはAuthenticode属性ですが、不要な場合はNULLを指定することができます。 この場合、dwAttrChoiceはSIGNER_NO_ATTRとなります。 psAuthenticatedとpsUnauthenticatedは、それぞれ認証済み属性と認証されていない属性を表しますが、 不要な場合はNULLを指定することができます。

providerInfo.pwszProviderName = NULL;
providerInfo.dwProviderType   = PROV_RSA_FULL;
providerInfo.dwKeySpec        = 0;
providerInfo.dwPvkChoice      = PVK_TYPE_FILE_NAME;
providerInfo.pwszPvkFileName  = L"sample.pvk";

dwPvkChoiceにPVK_TYPE_FILE_NAMEを指定すると、pwszPvkFileNameに.pvkファイルを指定することができます。 .pvkファイルを利用する場合は、CSPの名前や鍵用途は分からないので、 それぞれNULLと0で構いません。 dwProviderTypeは、PROV_RSA_FULLを指定します。

hr = lpfnSignerSign(&subjectInfo, &signerCert, &signatureInfo, &providerInfo, NULL, NULL, NULL);
if (hr == S_OK)
	MessageBox(NULL, TEXT("署名を格納しました。"), TEXT("OK"), MB_OK);
else
	MessageBox(NULL, TEXT("署名の格納に失敗しました。"), NULL, MB_ICONWARNING);

基本的には、初期化した各種構造体のアドレスを指定するだけで十分です。 タイムスタンプサーバーやSIPにデータを渡さない場合は、残りの引数もNULLで構いません。 戻り値がS_OKである場合、関数は成功したことになります。

証明書とCSPの鍵コンテナの利用

今回は、.spcファイルと.pvkファイルとベースとした署名を行っていますが、 SignerSignは証明書ストアに保存された証明書とCSPの鍵コンテナを利用した 署名方法にも対応しています。 そのようなコードは、次のようになります。

#include <windows.h>
#include "signer.h"

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

typedef HRESULT (WINAPI *LPFNSIGNERTSIGN)(SIGNER_SUBJECT_INFO *, SIGNER_CERT *, SIGNER_SIGNATURE_INFO *, SIGNER_PROVIDER_INFO *, LPCWSTR, PCRYPT_ATTRIBUTES, LPVOID);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HMODULE                hmod;
	LPFNSIGNERTSIGN        lpfnSignerSign;
	SIGNER_FILE_INFO       fileInfo;
	SIGNER_SUBJECT_INFO    subjectInfo;
	SIGNER_CERT            signerCert;
	SIGNER_SIGNATURE_INFO  signatureInfo;
	SIGNER_PROVIDER_INFO   providerInfo;
	DWORD                  dwIndex = 0;
	HRESULT                hr;
	HCERTSTORE             hStore;
	PCCERT_CONTEXT         pSignerContext;
	SIGNER_CERT_STORE_INFO storeInfo;
	DWORD                  dwSize;
	PCRYPT_KEY_PROV_INFO   pCryptKeyProvInfo;
	
	hmod = LoadLibrary(TEXT("mssign32.dll"));
	if (hmod == NULL) {
		MessageBox(NULL, TEXT("mssign32.dllが見つかりません。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	lpfnSignerSign = (LPFNSIGNERTSIGN)GetProcAddress(hmod, "SignerSign");
	if (lpfnSignerSign == NULL) {
		FreeLibrary(hmod);
		return 0;
	}
		
	hStore = CertOpenSystemStore(0, TEXT("MY"));
	if (hStore == NULL) {
		FreeLibrary(hmod);
		return 0;
	}
	
	pSignerContext = CertFindCertificateInStore(hStore, X509_ASN_ENCODING, 0, CERT_FIND_SUBJECT_STR, L"MyCert Publisher", NULL);
	if (pSignerContext == NULL) {
		MessageBox(NULL, TEXT("署名に使うための証明書が存在しません。"), NULL, MB_ICONWARNING);
		FreeLibrary(hmod);
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return 0;
	}

	fileInfo.cbSize       = sizeof(SIGNER_FILE_INFO);
	fileInfo.pwszFileName = L"sample.exe";
	fileInfo.hFile        = NULL;

	subjectInfo.cbSize          = sizeof(SIGNER_SUBJECT_INFO);
	subjectInfo.pdwIndex        = &dwIndex;
	subjectInfo.dwSubjectChoice = SIGNER_SUBJECT_FILE;
	subjectInfo.pSignerFileInfo = &fileInfo;
	
	signatureInfo.cbSize            = sizeof(SIGNER_SIGNATURE_INFO);
	signatureInfo.algidHash         = CALG_MD5;
	signatureInfo.dwAttrChoice      = SIGNER_NO_ATTR;
	signatureInfo.pAttrAuthcode     = NULL;
	signatureInfo.psAuthenticated   = NULL;
	signatureInfo.psUnauthenticated = NULL;
	
	storeInfo.cbSize       = sizeof(SIGNER_CERT_STORE_INFO);
	storeInfo.pSigningCert = pSignerContext;
	storeInfo.dwCertPolicy = SIGNER_CERT_POLICY_CHAIN;
	storeInfo.hCertStore   = NULL;
	
	signerCert.cbSize         = sizeof(SIGNER_CERT);
	signerCert.dwCertChoice   = SIGNER_CERT_STORE;
	signerCert.pCertStoreInfo = &storeInfo;
	signerCert.hwnd           = NULL;

	CertGetCertificateContextProperty(pSignerContext, CERT_KEY_PROV_INFO_PROP_ID, NULL, &dwSize);
	pCryptKeyProvInfo = (PCRYPT_KEY_PROV_INFO)HeapAlloc(GetProcessHeap(), 0, dwSize);
	CertGetCertificateContextProperty(pSignerContext, CERT_KEY_PROV_INFO_PROP_ID, pCryptKeyProvInfo, &dwSize);

	providerInfo.cbSize           = sizeof(SIGNER_PROVIDER_INFO);
	providerInfo.pwszProviderName = pCryptKeyProvInfo->pwszProvName;
	providerInfo.dwProviderType   = pCryptKeyProvInfo->dwProvType;
	providerInfo.dwKeySpec        = pCryptKeyProvInfo->dwKeySpec;
	providerInfo.dwPvkChoice      = PVK_TYPE_KEYCONTAINER;
	providerInfo.pwszKeyContainer = pCryptKeyProvInfo->pwszContainerName; 

	hr = lpfnSignerSign(&subjectInfo, &signerCert, &signatureInfo, &providerInfo, NULL, NULL, NULL);
	if (hr == S_OK)
		MessageBox(NULL, TEXT("署名を格納しました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("署名の格納に失敗しました。"), NULL, MB_ICONWARNING);

	HeapFree(GetProcessHeap(), 0, pCryptKeyProvInfo);
	FreeLibrary(hmod);

	return 0;
}

今回のSIGNER_CERT構造体のdwCertChoiceにはSIGNER_CERT_STOREを指定し、 SIGNER_CERT_STORE_INFO構造体を表すpCertStoreInfoを初期化しています。 この構造体に証明書ストアから取得した証明書を指定することになります。 鍵コンテナを指定するは、SIGNER_PROVIDER_INFO構造体のdwPvkChoiceにPVK_TYPE_KEYCONTAINERを指定します。 これで、pwszKeyContainerに鍵コンテナの名前を指定できるようになります。 鍵コンテナの名前を含んだ各種CSP情報は、CertGetCertificateContextPropertyで取得できるため、 そこで取得した値を設定しています。



戻る