EternalWindows
ファイル署名 / 副署名の設定

「認証されていない属性」として存在する副署名は、 署名にタイムスタンプを設定する目的で利用されます。 タイムスタンプが設定されていると、 ある時間帯にそのデータが改ざんされていないことが証明されるため、 署名の有効期間が署名に含まれる証明書の有効期間に依存しないようになります。

通常、タイムスタンプの設定は、タイムスタンプサーバーが行います。 これは、タイムスタンプで設定する時間が世界標準のUTC形式でなければならないため、 任意に変更可能なコンピュータ内のシステムタイムを利用することができないためです。 タイムスタンプサーバーのURLは、証明書を発行してもらったCAのHPなどに 記述されていると思われるので、そのURLを署名ツールや関数に指定することになるでしょう。 なお、SIP関数には、タイムスタンプサーバーについてのサポートがないため、 タイムスタンプサーバーが行う作業はアプリケーション自ら行うことになります。 具体的には、既存の署名に対して、タイムスタンプ属性を含んだ副署名を設定することになります。

副署名は、Low-Level Message FunctionsのCryptMsgCountersignで行うことができます。

BOOL WINAPI CryptMsgCountersign(
  HCRYPTMSG hCryptMsg,
  DWORD dwIndex,
  DWORD cCountersigners,
  PCMSG_SIGNER_ENCODE_INFO rgCountersigners
);

hCryptMsgは、メッセージのハンドルを指定します。 dwIndexは、副署名を格納するインデックスを指定します。 cCountersignersは、rgCountersignersの要素数を指定します。 rgCountersignersは、副署名の情報を維持するCMSG_SIGNER_ENCODE_INFO構造体のアドレスを指定します。

副署名を設定する側は、それを他のアプリケーションが検証できるように、 自身の証明書を追加しておかなければなりません。 これには、CryptMsgControlを呼び出します。

BOOL WINAPI CryptMsgControl(
  HCRYPTMSG hCryptMsg,
  DWORD dwFlags,
  DWORD dwCtrlType,
  const void *pvCtrlPara
);

hCryptMsgは、メッセージのハンドルを指定します。 dwFlagsは、予約されているため0を指定します。 dwCtrlTypeは、メッセージに対しての操作を表す定数を指定します。 pvCtrlParaは、実行する操作に関連する構造体を指定します。 dwCtrlTypeに指定できる定数の一部を示します。

定数 説明
CMSG_CTRL_VERIFY_SIGNATURE 署名を検証する。
CMSG_CTRL_DECRYPT データを複合化する。
CMSG_CTRL_ADD_SIGNER 署名者情報を追加する。
CMSG_CTRL_ADD_CERT 証明書を追加する。

今回のプログラムは、カレントディレクトリに存在するsample.exeに副署名を設定します。 sample.exeは、既に署名されていることを前提としています。

#include <windows.h>
#include <mssip.h>
#include <wintrust.h>

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

BOOL SetCountersigner(LPBYTE *lplpSignedDataMsg, LPDWORD lpdwSignedDataMsgSize);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{	
	WCHAR           szFileName[] = L"sample.exe";
	GUID            guidSubject;
	SIP_SUBJECTINFO subejectInfo;
	DWORD           dwEncodingType;
	LPBYTE          lpSignedDataMsg;
	DWORD           dwSignedDataMsgSize;
	DWORD           dwIndex = 0;

	if (!CryptSIPRetrieveSubjectGuidForCatalogFile(szFileName, NULL, &guidSubject))
		return 0;
	
	ZeroMemory(&subejectInfo, sizeof(SIP_SUBJECTINFO));
	subejectInfo.cbSize        = sizeof(SIP_SUBJECTINFO);
	subejectInfo.pgSubjectType = &guidSubject;
	subejectInfo.hFile         = INVALID_HANDLE_VALUE;
	subejectInfo.pwsFileName   = szFileName;
	
	ZeroMemory(&subejectInfo.DigestAlgorithm, sizeof(CRYPT_ALGORITHM_IDENTIFIER));
	subejectInfo.DigestAlgorithm.pszObjId = szOID_OIWSEC_sha;

	if (!CryptSIPGetSignedDataMsg(&subejectInfo, &dwEncodingType, dwIndex, &dwSignedDataMsgSize, NULL))
		dwSignedDataMsgSize = 10 * 1024;
	lpSignedDataMsg = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSignedDataMsgSize);

	if (!CryptSIPGetSignedDataMsg(&subejectInfo, &dwEncodingType, 0, &dwSignedDataMsgSize, lpSignedDataMsg)) {
		MessageBox(NULL, TEXT("署名の取得に失敗しました。"), NULL, MB_ICONWARNING);
		HeapFree(GetProcessHeap(), 0, lpSignedDataMsg);
		return 0;
	}
	
	if (!SetCountersigner(&lpSignedDataMsg, &dwSignedDataMsgSize)) {
		MessageBox(NULL, TEXT("副署名の設定に失敗しました。"), NULL, MB_ICONWARNING);
		HeapFree(GetProcessHeap(), 0, lpSignedDataMsg);
		return 0;
	}
	
	CryptSIPRemoveSignedDataMsg(&subejectInfo, dwIndex);

	if (CryptSIPPutSignedDataMsg(&subejectInfo, 0, &dwIndex, dwSignedDataMsgSize, lpSignedDataMsg))
		MessageBox(NULL, TEXT("署名を格納しました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("署名の格納に失敗しました。"), NULL, MB_ICONWARNING);


	HeapFree(GetProcessHeap(), 0, lpSignedDataMsg);

	return 0;
}

BOOL SetCountersigner(LPBYTE *lplpSignedDataMsg, LPDWORD lpdwSignedDataMsgSize)
{
	HCRYPTMSG                  hMsg;
	HCERTSTORE                 hStore;
	PCCERT_CONTEXT             pSignerContext;
	HCRYPTPROV                 hProv;
	DWORD                      dwKeySpec;
	CRYPT_ALGORITHM_IDENTIFIER hashAlgorithm;
	CMSG_SIGNER_ENCODE_INFO    counterSignerInfo;
	FILETIME                   fileTime;
	CRYPT_ATTRIBUTE            attribute;
	CRYPT_ATTR_BLOB            attrBlob;
	CERT_BLOB                  certBlob;

	hStore = CertOpenSystemStore(0, TEXT("MY"));
	if (hStore == NULL)
		return FALSE;
	
	pSignerContext = CertFindCertificateInStore(hStore, X509_ASN_ENCODING, 0, CERT_FIND_SUBJECT_STR, L"MyCert Publisher", NULL);
	if (pSignerContext == NULL) {
		MessageBox(NULL, TEXT("証明書の取得に失敗しました。"), NULL, MB_ICONWARNING);
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);	
		return 0;
	}
	
	hMsg = CryptMsgOpenToDecode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, 0, 0, NULL, NULL);
	if (hMsg == NULL) {
		CertFreeCertificateContext(pSignerContext);
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return FALSE;
	}

	if (!CryptMsgUpdate(hMsg, *lplpSignedDataMsg, *lpdwSignedDataMsgSize, TRUE)) {
		CryptMsgClose(hMsg);
		CertFreeCertificateContext(pSignerContext);
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return FALSE;
	}

	GetSystemTimeAsFileTime(&fileTime);
	CryptEncodeObject(PKCS_7_ASN_ENCODING, szOID_RSA_signingTime, &fileTime, NULL, &attrBlob.cbData);
	attrBlob.pbData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, attrBlob.cbData);
	CryptEncodeObject(PKCS_7_ASN_ENCODING, szOID_RSA_signingTime, &fileTime, attrBlob.pbData, &attrBlob.cbData);

	attribute.pszObjId = szOID_RSA_signingTime;
	attribute.cValue   = 1;
	attribute.rgValue  = &attrBlob;
	
	ZeroMemory(&hashAlgorithm, sizeof(CRYPT_ALGORITHM_IDENTIFIER));
	hashAlgorithm.pszObjId = szOID_OIWSEC_sha;

	CryptAcquireCertificatePrivateKey(pSignerContext, 0, NULL, &hProv, &dwKeySpec, NULL);
	ZeroMemory(&counterSignerInfo, sizeof(CMSG_SIGNER_ENCODE_INFO));
	counterSignerInfo.cbSize        = sizeof(CMSG_SIGNER_ENCODE_INFO);
	counterSignerInfo.pCertInfo     = pSignerContext->pCertInfo;
	counterSignerInfo.hCryptProv    = hProv;
	counterSignerInfo.dwKeySpec     = dwKeySpec;
	counterSignerInfo.HashAlgorithm = hashAlgorithm;
	counterSignerInfo.cAuthAttr     = 1;
	counterSignerInfo.rgAuthAttr    = &attribute;

	if (!CryptMsgCountersign(hMsg, 0, 1, &counterSignerInfo)) {
		HeapFree(GetProcessHeap(), 0, attrBlob.pbData);
		CryptMsgClose(hMsg);
		CertFreeCertificateContext(pSignerContext);
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return FALSE;
	}

	certBlob.cbData = pSignerContext->cbCertEncoded;
	certBlob.pbData = pSignerContext->pbCertEncoded;

	CryptMsgControl(hMsg, 0, CMSG_CTRL_ADD_CERT, &certBlob);
	
	HeapFree(GetProcessHeap(), 0, *lplpSignedDataMsg);

	CryptMsgGetParam(hMsg, CMSG_ENCODED_MESSAGE, 0, NULL, lpdwSignedDataMsgSize);
	*lplpSignedDataMsg = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, *lpdwSignedDataMsgSize);
	CryptMsgGetParam(hMsg, CMSG_ENCODED_MESSAGE, 0, *lplpSignedDataMsg, lpdwSignedDataMsgSize);

	HeapFree(GetProcessHeap(), 0, attrBlob.pbData);
	CryptMsgClose(hMsg);
	CertFreeCertificateContext(pSignerContext);
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);

	return TRUE;
}

副署名は既存の署名に対して行うものであるため、まずはファイルから署名を取得する必要があります。 これは、WinMainでのCryptSIPGetSignedDataMsgで行われています。 ここで取得した署名とサイズはSetCountersignerという自作関数に指定され、 関数が成功した場合は、副署名が設定された新しい署名を受け取ることになります。 SetCountersignerの内部を順に見ていきます。

hMsg = CryptMsgOpenToDecode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, 0, 0, NULL, NULL);
if (hMsg == NULL) {
	CertFreeCertificateContext(pSignerContext);
	CertCloseStore(hStore, 0);
	return FALSE;
}

if (!CryptMsgUpdate(hMsg, *lplpSignedDataMsg, *lpdwSignedDataMsgSize, TRUE)) {
	CryptMsgClose(hMsg);
	CertFreeCertificateContext(pSignerContext);
	CertCloseStore(hStore, 0);
	return FALSE;
}

副署名に限らず、既存の署名に対して何らかの変更を加える場合は、 まず既存の署名をデコードする必要があります。 このため、CryptMsgOpenToDecodeでデコードのハンドルを取得し、 CryptMsgUpdateで署名を指定すれば、実際にデコードが行われることになります。

GetSystemTimeAsFileTime(&fileTime);
CryptEncodeObject(PKCS_7_ASN_ENCODING, szOID_RSA_signingTime, &fileTime, NULL, &attrBlob.cbData);
attrBlob.pbData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, attrBlob.cbData);
CryptEncodeObject(PKCS_7_ASN_ENCODING, szOID_RSA_signingTime, &fileTime, attrBlob.pbData, &attrBlob.cbData);

attribute.pszObjId = szOID_RSA_signingTime;
attribute.cValue   = 1;
attribute.rgValue  = &attrBlob;

タイムスタンプの属性を副署名に設定するために、CRYPT_ATTRIBUTE構造体の初期化を行います。 まず、GetSystemTimeAsFileTimeでUTC時刻を取得し、 szOID_RSA_signingTimeを指定したCryptEncodeObjectでエンコードを行います。 これにより、CRYPT_ATTR_BLOB構造体がタイムスタンプ情報を維持したことになりますから、 この情報とOIDをCRYPT_ATTRIBUTE構造体に指定します。

ZeroMemory(&hashAlgorithm, sizeof(CRYPT_ALGORITHM_IDENTIFIER));
hashAlgorithm.pszObjId = szOID_OIWSEC_sha;

CryptAcquireCertificatePrivateKey(pSignerContext, 0, NULL, &hProv, &dwKeySpec, NULL);
ZeroMemory(&counterSignerInfo, sizeof(CMSG_SIGNER_ENCODE_INFO));
counterSignerInfo.cbSize        = sizeof(CMSG_SIGNER_ENCODE_INFO);
counterSignerInfo.pCertInfo     = pSignerContext->pCertInfo;
counterSignerInfo.hCryptProv    = hProv;
counterSignerInfo.dwKeySpec     = dwKeySpec;
counterSignerInfo.HashAlgorithm = hashAlgorithm;
counterSignerInfo.cAuthAttr     = 1;
counterSignerInfo.rgAuthAttr    = &attribute;

if (!CryptMsgCountersign(hMsg, 0, 1, &counterSignerInfo)) {
	HeapFree(GetProcessHeap(), 0, attrBlob.pbData);
	CryptMsgClose(hMsg);
	CertFreeCertificateContext(pSignerContext);
	CertCloseStore(hStore, 0);
	return FALSE;
}

CMSG_SIGNER_ENCODE_INFO構造体を初期化して、CryptMsgCountersignを呼び出します。 構造体の初期化は鍵用途やCSPのハンドルが必要になるため、 CryptAcquireCertificatePrivateKeyで証明書からそれらを取得し、 ハッシュアルゴリズムも初期化しておきます。 また、今回は属性を追加するために、cAuthAttrとrgAuthAttrの2つのメンバも初期化しています。 これら2つのメンバは「認証済み属性」の数と値を表しています。

certBlob.cbData = pSignerContext->cbCertEncoded;
certBlob.pbData = pSignerContext->pbCertEncoded;

CryptMsgControl(hMsg, 0, CMSG_CTRL_ADD_CERT, &certBlob);

署名者の証明書をメッセージに追加します。 これにより、証明書の公開鍵を使って副署名を検証できるようになります。 本来ならば、このような証明書は、タイムスタンプサーバーの証明書であるべきです。

HeapFree(GetProcessHeap(), 0, *lplpSignedDataMsg);

CryptMsgGetParam(hMsg, CMSG_ENCODED_MESSAGE, 0, NULL, lpdwSignedDataMsgSize);
*lplpSignedDataMsg = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, *lpdwSignedDataMsgSize);
CryptMsgGetParam(hMsg, CMSG_ENCODED_MESSAGE, 0, *lplpSignedDataMsg, lpdwSignedDataMsgSize);

メッセージに追加された副署名と証明書を基に、データを再エンコードします。 これにより、副署名が設定された署名が作成されることになります。 新しい署名を受け取るために、まず既存の署名を開放し、 CMSG_ENCODED_MESSAGEを指定したCryptMsgGetParamが返したデータを新しい署名とします。 当然ながらこの署名には、「認証されていない属性」として副署名が設定されています。

これまでの作業により、副署名が設定された新しい署名を取得できたため、 後はこれをCryptSIPPutSignedDataMsgでファイルに格納すればよいことになります。 ただし、既にファイルには以前の署名が格納されているため、 まずはこれをCryptSIPRemoveSignedDataMsgで削除することになります。

CryptSIPRemoveSignedDataMsg(&subejectInfo, dwIndex);

if (CryptSIPPutSignedDataMsg(&subejectInfo, 0, &dwIndex, dwSignedDataMsgSize, lpSignedDataMsg))
	MessageBox(NULL, TEXT("署名を格納しました。"), TEXT("OK"), MB_OK);
else
	MessageBox(NULL, TEXT("署名の格納に失敗しました。"), NULL, MB_ICONWARNING);

これにより、ファイルには副署名が設定された署名が格納されることになります。 また、副署名にタイムスタンプの属性を追加していたため、 その情報を確認できるはずです。

副署名の検証

副署名による署名方法というのは、通常の署名と変化するものではありません。 つまり、署名対象となるデータのハッシュ値を求め、 それを秘密鍵で暗号化したものを副署名としているのです。 検証の手順としては、この副署名を公開鍵で複合化し、 それを署名対象となるデータのハッシュ値と比較すればよいわけですから、 この副署名、公開鍵、署名対象となるデータをどう取得するかが要点となります。

#include <windows.h>
#include <mssip.h>
#include <wintrust.h>

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

BOOL VerifyCountersignature(LPBYTE lpSignedDataMsg, DWORD dwSignedDataMsgSize);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{	
	WCHAR           szFileName[] = L"sample.exe";
	GUID            guidSubject;
	SIP_SUBJECTINFO subejectInfo;
	DWORD           dwEncodingType;
	LPBYTE          lpSignedDataMsg;
	DWORD           dwSignedDataMsgSize;
	DWORD           dwIndex = 0;

	if (!CryptSIPRetrieveSubjectGuidForCatalogFile(szFileName, NULL, &guidSubject))
		return 0;
	
	ZeroMemory(&subejectInfo, sizeof(SIP_SUBJECTINFO));
	subejectInfo.cbSize        = sizeof(SIP_SUBJECTINFO);
	subejectInfo.pgSubjectType = &guidSubject;
	subejectInfo.hFile         = INVALID_HANDLE_VALUE;
	subejectInfo.pwsFileName   = szFileName;
	
	ZeroMemory(&subejectInfo.DigestAlgorithm, sizeof(CRYPT_ALGORITHM_IDENTIFIER));
	subejectInfo.DigestAlgorithm.pszObjId = szOID_OIWSEC_sha;

	if (!CryptSIPGetSignedDataMsg(&subejectInfo, &dwEncodingType, dwIndex, &dwSignedDataMsgSize, NULL))
		dwSignedDataMsgSize = 10 * 1024;
	lpSignedDataMsg = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSignedDataMsgSize);

	if (!CryptSIPGetSignedDataMsg(&subejectInfo, &dwEncodingType, 0, &dwSignedDataMsgSize, lpSignedDataMsg)) {
		MessageBox(NULL, TEXT("署名の取得に失敗しました。"), NULL, MB_ICONWARNING);
		HeapFree(GetProcessHeap(), 0, lpSignedDataMsg);
		return 0;
	}
	
	if (VerifyCountersignature(lpSignedDataMsg, dwSignedDataMsgSize))
		MessageBox(NULL, TEXT("副署名の検証に成功しました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("副署名の検証に失敗しました。"), NULL, MB_ICONWARNING);


	HeapFree(GetProcessHeap(), 0, lpSignedDataMsg);

	return 0;
}

BOOL VerifyCountersignature(LPBYTE lpSignedDataMsg, DWORD dwSignedDataMsgSize)
{
	HCRYPTMSG         hMsg;
	HCERTSTORE        hMsgStore;
	PCCERT_CONTEXT    pSignerContext;
	DWORD             dwEncodedSignerInfoSize;
	LPBYTE            lpEncodedSignerInfo;
	PCRYPT_ATTRIBUTES pCountersignerInfo;
	PCMSG_SIGNER_INFO pSignerInfo;
	DWORD             dwDataSize;
	CERT_INFO         certInfo;
	BOOL              bResult;
	
	hMsg = CryptMsgOpenToDecode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, 0, 0, NULL, NULL);
	if (hMsg == NULL)
		return FALSE;

	if (!CryptMsgUpdate(hMsg, lpSignedDataMsg, dwSignedDataMsgSize, TRUE)) {
		CryptMsgClose(hMsg);
		return FALSE;
	}

	if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_UNAUTH_ATTR_PARAM, 0, NULL, &dwDataSize)) {
		CryptMsgClose(hMsg);
		return FALSE;
	}
	pCountersignerInfo = (PCRYPT_ATTRIBUTES)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
	CryptMsgGetParam(hMsg, CMSG_SIGNER_UNAUTH_ATTR_PARAM, 0, pCountersignerInfo, &dwDataSize);

	CryptDecodeObject(PKCS_7_ASN_ENCODING, PKCS7_SIGNER_INFO, pCountersignerInfo->rgAttr->rgValue->pbData, pCountersignerInfo->rgAttr->rgValue->cbData, 0, NULL, &dwDataSize);
	pSignerInfo = (PCMSG_SIGNER_INFO)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
	CryptDecodeObject(PKCS_7_ASN_ENCODING, PKCS7_SIGNER_INFO, pCountersignerInfo->rgAttr->rgValue->pbData, pCountersignerInfo->rgAttr->rgValue->cbData, 0, pSignerInfo, &dwDataSize);
	
	certInfo.Issuer = pSignerInfo->Issuer;
	certInfo.SerialNumber = pSignerInfo->SerialNumber;
	hMsgStore = CertOpenStore(CERT_STORE_PROV_MSG, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, 0, hMsg);
	pSignerContext = CertGetSubjectCertificateFromStore(hMsgStore, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, &certInfo);
	if (pSignerContext == NULL) {
		HeapFree(GetProcessHeap(), 0, pSignerInfo);
		HeapFree(GetProcessHeap(), 0, pCountersignerInfo);
		CertCloseStore(hMsgStore, CERT_CLOSE_STORE_CHECK_FLAG);
		CryptMsgClose(hMsg);
		return FALSE;
	}

	CryptMsgGetParam(hMsg, CMSG_ENCODED_SIGNER, 0, NULL, &dwEncodedSignerInfoSize);
	lpEncodedSignerInfo = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwEncodedSignerInfoSize);
	CryptMsgGetParam(hMsg, CMSG_ENCODED_SIGNER, 0, lpEncodedSignerInfo, &dwEncodedSignerInfoSize);

	bResult = CryptMsgVerifyCountersignatureEncoded(0, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, lpEncodedSignerInfo, dwEncodedSignerInfoSize,
		pCountersignerInfo->rgAttr->rgValue->pbData, pCountersignerInfo->rgAttr->rgValue->cbData, pSignerContext->pCertInfo);

	HeapFree(GetProcessHeap(), 0, lpEncodedSignerInfo);
	HeapFree(GetProcessHeap(), 0, pSignerInfo);
	HeapFree(GetProcessHeap(), 0, pCountersignerInfo);
	CertCloseStore(hMsgStore, CERT_CLOSE_STORE_CHECK_FLAG);
	CryptMsgClose(hMsg);

	return bResult;
}

このコードは、CryptSIPGetSignedDataMsgでファイルから署名を取得し、 VerifyCountersignatureという自作関数で署名に設定された副署名を検証します。 CryptMsgUpdateにより、デコードされた署名がメッセージに格納されたら、 実際に副署名などを取得できるようになります。

if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_UNAUTH_ATTR_PARAM, 0, NULL, &dwDataSize)) {
	CryptMsgClose(hMsg);
	return FALSE;
}
pCountersignerInfo = (PCRYPT_ATTRIBUTES)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
CryptMsgGetParam(hMsg, CMSG_SIGNER_UNAUTH_ATTR_PARAM, 0, pCountersignerInfo, &dwDataSize);

CryptMsgGetParamにCMSG_SIGNER_UNAUTH_ATTR_PARAMを指定すれば、 「認証されていない属性」を取得することができます。 今回のプログラムでは、「認証されていない属性」として設定した属性が副署名だけであるため、 取得した属性が副署名であると判断できますが、本来ならばOIDがszOID_RSA_counterSignであるかを 調べる処理が必要となるでしょう。

CryptDecodeObject(PKCS_7_ASN_ENCODING, PKCS7_SIGNER_INFO, pCountersignerInfo->rgAttr->rgValue->pbData, pCountersignerInfo->rgAttr->rgValue->cbData, 0, NULL, &dwDataSize);
pSignerInfo = (PCMSG_SIGNER_INFO)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
CryptDecodeObject(PKCS_7_ASN_ENCODING, PKCS7_SIGNER_INFO, pCountersignerInfo->rgAttr->rgValue->pbData, pCountersignerInfo->rgAttr->rgValue->cbData, 0, pSignerInfo, &dwDataSize);	

今回のプログラムでは、副署名を検証できるようにするために、CryptMsgControlで証明書を追加していましたが、 それを取得するには少し工夫が必要です。 まず、副署名をCMSG_SIGNER_INFO構造体にデコードします。 副署名には証明書そのものを格納することはできませんが、 証明書のシリアル番号と発行者名は含まれることになっているので、 この情報があれば、署名の中から副署名に利用した証明書を取得することが可能になります。 そのようなコードは、次のようになっています。

certInfo.Issuer = pSignerInfo->Issuer;
certInfo.SerialNumber = pSignerInfo->SerialNumber;
hMsgStore = CertOpenStore(CERT_STORE_PROV_MSG, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, 0, hMsg);
pSignerContext = CertGetSubjectCertificateFromStore(hMsgStore, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, &certInfo);

CertOpenStoreにCERT_STORE_PROV_MSGを指定した場合、メッセージを証明書ストアのように扱うことができるようになります。 CertGetSubjectCertificateFromStoreは、証明書ストアから特定の証明書を取得する関数であり、 証明書を特定するためのデータとしてCERT_INFO構造体を要求しています。 この構造体にCMSG_SIGNER_INFO構造体に含まれていたデータを指定すれば、 副署名に利用された証明書を取得できることになります。

CryptMsgGetParam(hMsg, CMSG_ENCODED_SIGNER, 0, NULL, &dwEncodedSignerInfoSize);
lpEncodedSignerInfo = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwEncodedSignerInfoSize);
CryptMsgGetParam(hMsg, CMSG_ENCODED_SIGNER, 0, lpEncodedSignerInfo, &dwEncodedSignerInfoSize);

これは、署名に含まれる署名情報を取得するコードです。 この署名情報はエンコードされており、副署名の署名対象となるデータそのものです。 つまり、このデータのハッシュ値が秘密鍵で暗号化されていたことになります。

bResult = CryptMsgVerifyCountersignatureEncoded(0, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, lpEncodedSignerInfo, dwEncodedSignerInfoSize,
	pCountersignerInfo->rgAttr->rgValue->pbData, pCountersignerInfo->rgAttr->rgValue->cbData, pSignerContext->pCertInfo);

CryptMsgVerifyCountersignatureEncodedは、副署名を検証する関数です。 その内部動作としては、最終引数のCERT_INFO構造体から公開鍵を取得し、 pCountersignerInfoのデータを複合化、そしてlpEncodedSignerInfoのハッシュ値と比較ということになります。



戻る