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

spcIndirectDataContextデータを含んだSignedData型は、 ファイルに格納できる署名として成立します。 署名をファイルに格納するには、CryptSIPPutSignedDataMsgを呼び出します。

BOOL WINAPI CryptSIPPutSignedDataMsg(
  SIP_SUBJECTINFO *pSubjectInfo,
  DWORD dwEncodingType,
  DWORD *pdwIndex,
  DWORD cbSignedDataMsg,
  BYTE *pbSignedDataMsg
);

pSubjectInfoは、初期化済みのSIP_SUBJECTINFO構造体のアドレスを指定します。 dwEncodingTypeは、エンコーディングタイプを指定します。 pdwIndexは、署名を格納するインデックスを格納した変数のアドレスを指定します。 cbSignedDataMsgは、pbSignedDataMsgのサイズを指定します。 pbSignedDataMsgは、SignedData型のデータを指定します。

今回のプログラムはSignedData型のデータを作成し、 それをカレントディレクトリに存在するsample.exeに格納します。 ファイルが変更されることになるため、事前にバックアップを取っておいてください。

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

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

BOOL CreateSignedDataMsg(LPBYTE lpIndirectData, DWORD dwIndirectDataSize, 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;
	PSIP_INDIRECT_DATA pIndirectData;
	LPBYTE             lpContentData;
	DWORD              dwDataSize;
	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.hFile         = INVALID_HANDLE_VALUE;
	subejectInfo.pwsFileName   = szFileName;
	subejectInfo.pgSubjectType = &guidSubject;

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

	CryptSIPCreateIndirectData(&subejectInfo, &dwDataSize, NULL);
	pIndirectData = (PSIP_INDIRECT_DATA)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
	if (!CryptSIPCreateIndirectData(&subejectInfo, &dwDataSize, pIndirectData)) {
		HeapFree(GetProcessHeap(), 0, pIndirectData);
		return 0;
	}

	CryptEncodeObject(PKCS_7_ASN_ENCODING, SPC_INDIRECT_DATA_OBJID, pIndirectData, NULL, &dwDataSize);
	lpContentData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
	CryptEncodeObject(PKCS_7_ASN_ENCODING, SPC_INDIRECT_DATA_OBJID, pIndirectData, lpContentData, &dwDataSize);

	if (!CreateSignedDataMsg(lpContentData, dwDataSize, &lpSignedDataMsg, &dwSignedDataMsgSize)) {
		HeapFree(GetProcessHeap(), 0, pIndirectData);
		HeapFree(GetProcessHeap(), 0, lpContentData);
		return 0;
	}

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

	HeapFree(GetProcessHeap(), 0, pIndirectData);
	HeapFree(GetProcessHeap(), 0, lpContentData);
	HeapFree(GetProcessHeap(), 0, lpSignedDataMsg);

	return 0;
}

BOOL CreateSignedDataMsg(LPBYTE lpContentData, DWORD dwDataSize, LPBYTE *lplpSignedDataMsg, LPDWORD lpdwSignedDataMsgSize)
{
	CMSG_SIGNER_ENCODE_INFO    signerEncodeInfo;
	CMSG_SIGNED_ENCODE_INFO    signedEncodeInfo;
	CRYPT_ALGORITHM_IDENTIFIER hashAlgorithm;
	HCERTSTORE                 hStore;
	PCCERT_CONTEXT             pSignerContext;
	HCRYPTPROV                 hProv;
	DWORD                      dwKeySpec;
	CERT_BLOB                  certBlob;
	HCRYPTMSG                  hMsg;
	
	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;
	}

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

	CryptAcquireCertificatePrivateKey(pSignerContext, 0, NULL, &hProv, &dwKeySpec, NULL);
	ZeroMemory(&signerEncodeInfo, sizeof(CMSG_SIGNER_ENCODE_INFO));
	signerEncodeInfo.cbSize        = sizeof(CMSG_SIGNER_ENCODE_INFO);
	signerEncodeInfo.pCertInfo     = pSignerContext->pCertInfo;
	signerEncodeInfo.hCryptProv    = hProv;
	signerEncodeInfo.dwKeySpec     = dwKeySpec;
	signerEncodeInfo.HashAlgorithm = hashAlgorithm;

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

	ZeroMemory(&signedEncodeInfo, sizeof(CMSG_SIGNED_ENCODE_INFO));
	signedEncodeInfo.cbSize        = sizeof(CMSG_SIGNED_ENCODE_INFO);
	signedEncodeInfo.cSigners      = 1;
	signedEncodeInfo.rgSigners     = &signerEncodeInfo;
	signedEncodeInfo.cCertEncoded  = 1;
	signedEncodeInfo.rgCertEncoded = &certBlob;

	hMsg = CryptMsgOpenToEncode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, CMSG_SIGNED, &signedEncodeInfo, SPC_INDIRECT_DATA_OBJID, NULL);
	if (hMsg == NULL) {
		CertFreeCertificateContext(pSignerContext);
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return FALSE;
	}

	if (!CryptMsgUpdate(hMsg, lpContentData, dwDataSize, TRUE)) {
		MessageBox(NULL, TEXT("署名に失敗しました。"), NULL, MB_ICONWARNING);
		CryptMsgClose(hMsg);
		CertFreeCertificateContext(pSignerContext);
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return FALSE;
	}	

	CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, NULL, lpdwSignedDataMsgSize);
	*lplpSignedDataMsg = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, *lpdwSignedDataMsgSize);
	CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, *lplpSignedDataMsg, lpdwSignedDataMsgSize);
	
	CryptMsgClose(hMsg);
	CertFreeCertificateContext(pSignerContext);
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);

	return TRUE;
}

自作関数のCreateSignedDataMsgは、CryptSIPPutSignedDataMsgに 指定することになるSignedData型のデータを作成します。 この関数には、SignedData型のデータが含むべきspcIndirectDataContextデータとサイズを指定し、 関数が成功した場合は、第3引数と第4引数でSignedData型のデータとサイズを受け取ります。 SignedData型のデータを作成するには、まずCryptMsgOpenToEncodeでメッセージのハンドルを取得します。

CryptAcquireCertificatePrivateKey(pSignerContext, 0, NULL, &hProv, &dwKeySpec, NULL);
ZeroMemory(&signerEncodeInfo, sizeof(CMSG_SIGNER_ENCODE_INFO));
signerEncodeInfo.cbSize        = sizeof(CMSG_SIGNER_ENCODE_INFO);
signerEncodeInfo.pCertInfo     = pSignerContext->pCertInfo;
signerEncodeInfo.hCryptProv    = hProv;
signerEncodeInfo.dwKeySpec     = dwKeySpec;
signerEncodeInfo.HashAlgorithm = hashAlgorithm;

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

ZeroMemory(&signedEncodeInfo, sizeof(CMSG_SIGNED_ENCODE_INFO));
signedEncodeInfo.cbSize        = sizeof(CMSG_SIGNED_ENCODE_INFO);
signedEncodeInfo.cSigners      = 1;
signedEncodeInfo.rgSigners     = &signerEncodeInfo;
signedEncodeInfo.cCertEncoded  = 1;
signedEncodeInfo.rgCertEncoded = &certBlob;

hMsg = CryptMsgOpenToEncode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, CMSG_SIGNED, &signedEncodeInfo, SPC_INDIRECT_DATA_OBJID, NULL);
if (hMsg == NULL) {
	CertFreeCertificateContext(pSignerContext);
	CertCloseStore(hStore, 0);
	return FALSE;
}

CryptMsgOpenToEncodeを呼び出すためにはCMSG_SIGNED_ENCODE_INFO構造体が必要であり、 この構造体は署名者情報を表すCMSG_SIGNER_ENCODE_INFO構造体を要求しています。 今回は、署名に使う証明書はMY証明書ストアの中で最初に列挙されたものとし、 取得した証明書からCSPのハンドルと鍵用途をCryptAcquireCertificatePrivateKeyで取得しています。 これらとCERT_INFO構造体、そして署名ハッシュアルゴリズムを指定すれば、 CMSG_SIGNER_ENCODE_INFO構造体を初期化できたことになります。 CMSG_SIGNED_ENCODE_INFO構造体のrgCertEncodedメンバを通じて、 署名者の証明書を含ませることは重要な意味を持ちます。 この証明書がなければ、他のコンピュータ上で署名を検証することができないからです。 一通りの初期化が終了すれば、CMSG_SIGNEDを指定してCryptMsgOpenToEncodeを呼び出します。 これにより、SignedData型のデータを作成する旨を示したことになります。

CryptMsgOpenToEncodeの第5引数はコンテンツを表すOIDであり、 ここにSPC_INDIRECT_DATA_OBJIDを指定するのが最大の要点となります。 SignedData型のデータには、データがSignedData型であることを 示すszOID_RSA_signedDataというOIDが含まれていますが、 コンテンツ用のOIDも含まれることになります。 今回、コンテンツに含めるのはspcIndirectDataContextデータですから、 それを表すSPC_INDIRECT_DATA_OBJIDを指定することになります。 SignedData型のデータの作成にCryptSignMessageを使用できないのは、 この関数がコンテンツのOIDを常にszOID_RSA_dataとするためだったのです。 ちなみに、CryptMsgOpenToEncodeの第5引数に0をした場合も、 コンテンツのOIDがszOID_RSA_dataとなります。

CryptMsgOpenToEncodeが返したメッセージのハンドルには署名を行うための情報が含まれており、 CryptMsgUpdateを呼び出すと実際に署名が行われることになります。 当然ながら、署名対象のデータとなるのは、spcIndirectDataContextデータです。 CryptMsgUpdateが成功した場合、作成されたSignedData型がメッセージにコンテンツとして格納され、 CMSG_CONTENT_PARAMを指定したCryptMsgGetParamを呼び出せば、 その作成されたSignedData型をメッセージから取得することができます。 なお、メッセージに格納されているSignedData型をコンテンツと呼ぶ場合、 SignedData型に格納されているコンテンツ(今回の場合、spcIndirectDataContextデータ)を 内部コンテンツと呼ぶことがあります。

最後に、CryptSIPPutSignedDataMsgの呼び出しを確認します。

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

関数が成功した場合、lpSignedDataMsgで識別されるSignedData型のデータがファイルに格納されることになります。 ただし、その署名が格納されるインデックスは、必ずしも期待通りになるとは限りません。 たとえば、既に署名が格納されている場合は、それは0番目のインデックスを持ちますから、 新しく格納する署名は1番目のインデックスを持つことになります。 実際に何番目のインデックスに格納されたかは、第3引数の値で確認することができます。

署名されたファイルは「デジタル署名」というプロパティを持ちますが、 そこには署名の一覧というものが表示されることになっています。 ただし、CryptSIPPutSignedDataMsgの引数である署名のインデックスは、 この一覧におけるインデックスとは関係がありません。 「デジタル署名」のプロパティで確認できるのは、 あくまでCryptSIPPutSignedDataMsgで格納されたインデックス0の署名であり、 それ以降の署名はCryptSIPGetSignedDataMsgで取得して確認するしかないのです。 それでは、署名の一覧に複数の署名を表示するにはどうしたらよいのでしょうか。 答えは、SignedData型のデータに複数の署名を設定することです。 CryptMsgOpenToEncodeが受け取るCMSG_SIGNED_ENCODE_INFO構造体には、 cSignersという署名者の数を表すメンバがあるため、これを複数にし、 rgSignersに指定するCMSG_SIGNER_ENCODE_INFO構造体も複数にするのです。 また、署名者の証明書を格納するために、cCertEncodedとrgCertEncodedも複数になるでしょう。

ファイル形式固有の署名方法

WindowsAPIの中には、特定のファイル形式の操作に特化した関数が用意されていることがあります。 たとえば、PEファイル(.exeや.dll)を操作するImage Help Libraryという関数群には、 ImageAddCertificateという署名を行う関数が含まれています。 このような専用の関数とSIPのような汎用的な関数を比べた場合、 有力となるのは明らかにSIPの関数です。 これには、SIPの方が他のファイル形式への署名にも対応できるという点もありますが、 最大の理由はImageAddCertificateを呼び出すにしても、 その途中でSIPの関数を呼び出すことになるという点です。 次に、コード例を示します。

HANDLE            hFile;
DWORD             dwIndex = 0;
DWORD             dwDataSize;
LPWIN_CERTIFICATE lpWinCertificate;

hFile = CreateFile(szFileName, FILE_READ_DATA | FILE_WRITE_DATA, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
	return 0;

dwDataSize = sizeof(WIN_CERTIFICATE) - sizeof(BYTE) + dwSignedDataMsgSize;
lpWinCertificate = (LPWIN_CERTIFICATE)HeapAlloc(GetProcessHeap(), 0, dwDataSize);

lpWinCertificate->dwLength         = dwDataSize;
lpWinCertificate->wRevision        = WIN_CERT_REVISION_2_0;
lpWinCertificate->wCertificateType = WIN_CERT_TYPE_PKCS_SIGNED_DATA;
CopyMemory(lpWinCertificate->bCertificate, lpSignedDataMsg, dwSignedDataMsgSize);

if (ImageAddCertificate(hFile, lpWinCertificate, &dwIndex))
	MessageBox(NULL, TEXT("署名を格納しました。"), TEXT("OK"), MB_OK);
else
	MessageBox(NULL, TEXT("署名の格納に失敗しました。"), NULL, MB_ICONWARNING);

ImageAddCertificateは、第2引数にWIN_CERTIFICATE構造体を要求します。 この構造体のbCertificateは要素数が1の配列であり、 このメンバにSignedData型のデータをコピーしなければならないという指標になっています。 このため、構造体のサイズとSignedData型のデータサイズを含めたメモリを事前に確保する必要があります。 ImageAddCertificateが行っていることは、SignedData型のデータをファイルに格納しているに過ぎません。 この関数は、署名として格納するデータ(SignedData型のデータ)を作成する機能を持っていないため、 上記のlpSignedDataMsgとdwSignedDataMsgSizeはSIPを利用して算出することになり、 明らかに効率が悪いといえます。 ちなみに、署名を取得するコードは次のようになります。

HANDLE            hFile;
DWORD             dwDataSize;
LPWIN_CERTIFICATE lpWinCertificate;

hFile = CreateFile(szFileName, FILE_READ_DATA, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
	return 0;

dwDataSize = 0;
ImageGetCertificateData(hFile, 0, NULL, &dwDataSize);
lpWinCertificate = (LPWIN_CERTIFICATE)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
ImageGetCertificateData(hFile, 0, lpWinCertificate, &dwDataSize);

dwDataSizeを0に初期化した状態でImageGetCertificateDataを呼び出せば、 署名を受け取れるだけのバッファサイズが返ることになります。 後は、バッファを確保してもう一度ImageGetCertificateDataを呼び出せば、 lpWinCertificate->bCertificateとすることで、SignedData型のデータを表すことができます。 ちなみに、PEファイルでは署名が1つのセクションとして格納されることになるため、 Debug Help LibraryのImageDirectoryEntryToDataにIMAGE_DIRECTORY_ENTRY_SECURITYを指定することでも、 署名を参照することができます。



戻る