EternalWindows
PKCS #7 / EnvelopedData型とエンコード

今回は、データをEnvelopedData型へエンコードする方法について説明します。 このエンコードには、CMSG_ENVELOPED_ENCODE_INFO構造体を使用します。

typedef struct _CMSG_ENVELOPED_ENCODE_INFO {
  DWORD                        cbSize;
  HCRYPTPROV                   hCryptProv;
  CRYPT_ALGORITHM_IDENTIFIER   ContentEncryptionAlgorithm;
  void*                        pvEncryptionAuxInfo;
  DWORD                        cRecipients;
  PCERT_INFO*                  rgpRecipients;
#ifdef CMSG_ENVELOPED_ENCODE_INFO_HAS_CMS_FIELDS
  PCMSG_RECIPIENT_ENCODE_INFO  rgCmsRecipients;
  DWORD                        cCertEncoded;
  PCERT_BLOB                   rgCertEncoded;
  DWORD                        cCrlEncoded;
  PCRL_BLOB                    rgCrlEncoded;
  DWORD                        cAttrCertEncoded;
  PCERT_BLOB                   rgAttrCertEncoded;
  DWORD                        cUnprotectedAttr;
  PCRYPT_ATTRIBUTE             rgUnprotectedAttr;
#endif
} CMSG_ENVELOPED_ENCODE_INFO,  *PCMSG_ENVELOPED_ENCODE_INFO;

cbSizeは、この構造体のサイズをバイト単位で指定します。 hCryptProvは、CSPのハンドルを指定します。 0を指定した場合、デフォルトのCSPが使用されます。 ContentEncryptionAlgorithmは、暗号化アルゴリズムを 格納したCRYPT_ALGORITHM_IDENTIFIER構造体のアドレスを指定します。 pvEncryptionAuxInfoは、暗号化アルゴリズム固有の構造体を指定します。 NULLを指定しても問題ありません。 cRecipientsは、rgpRecipientsの要素数を指定します。 rgpRecipientsは、受取人の証明書を表すCERT_INFO構造体の配列を指定します。

今回は、データをEnvelopedData型へエンコードしてファイルに保存します。 受取人の証明書は、前節で作成したsign.datから取得します。

#include <windows.h>

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

PCCERT_CONTEXT GetRecipientContext(LPTSTR lpszFileName);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	PCCERT_CONTEXT             pRecipientContext;
	CRYPT_ALGORITHM_IDENTIFIER encryptAlgorithm;
	CMSG_ENVELOPED_ENCODE_INFO envelopedEncodeInfo;
	HCRYPTMSG                  hMsg;
	LPCBYTE                    lpData = (LPBYTE)TEXT("sample-data");
	DWORD                      dwDataSize = sizeof(TEXT("sample-data"));
	LPBYTE                     lpEncryptedBlob;
	DWORD                      dwEncryptedBlobSize;
	HANDLE                     hFile;
	DWORD                      dwWriteByte;

	pRecipientContext = GetRecipientContext(TEXT("sign.dat"));
	if (pRecipientContext == NULL) {
		MessageBox(NULL, TEXT("受取人の証明書の取得に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	ZeroMemory(&encryptAlgorithm, sizeof(CRYPT_ALGORITHM_IDENTIFIER));
	encryptAlgorithm.pszObjId = szOID_RSA_RC4;

	ZeroMemory(&envelopedEncodeInfo, sizeof(CMSG_ENVELOPED_ENCODE_INFO));
	envelopedEncodeInfo.cbSize                     = sizeof(CMSG_ENVELOPED_ENCODE_INFO);
	envelopedEncodeInfo.hCryptProv                 = 0;
	envelopedEncodeInfo.ContentEncryptionAlgorithm = encryptAlgorithm;
	envelopedEncodeInfo.cRecipients                = 1;
	envelopedEncodeInfo.rgpRecipients              = (PCERT_INFO *)&pRecipientContext->pCertInfo;

	hMsg = CryptMsgOpenToEncode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, CMSG_ENVELOPED, &envelopedEncodeInfo, NULL, NULL);
	if (hMsg == NULL) {
		CertFreeCertificateContext(pRecipientContext);
		return 0;
	}

	if (!CryptMsgUpdate(hMsg, lpData, dwDataSize, TRUE)) {
		MessageBox(NULL, TEXT("暗号化に失敗しました。"), NULL, MB_ICONWARNING);
		CertFreeCertificateContext(pRecipientContext);
		CryptMsgClose(hMsg);
		return 0;
	}
	
	MessageBox(NULL, TEXT("暗号化に成功しました。"), TEXT("OK"), MB_OK);

	CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, NULL, &dwEncryptedBlobSize);
	lpEncryptedBlob = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwEncryptedBlobSize);
	CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, lpEncryptedBlob, &dwEncryptedBlobSize);

	hFile = CreateFile(TEXT("encrypt.dat"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	WriteFile(hFile, lpEncryptedBlob, dwEncryptedBlobSize, &dwWriteByte, NULL);
	CloseHandle(hFile);
	
	HeapFree(GetProcessHeap(), 0, lpEncryptedBlob);
	CryptMsgClose(hMsg);
	CertFreeCertificateContext(pRecipientContext);

	return 0;
}

PCCERT_CONTEXT GetRecipientContext(LPTSTR lpszFileName)
{
	CRYPT_VERIFY_MESSAGE_PARA verifyPara;
	LPBYTE                    lpData;
	DWORD                     dwDataSize;
	BOOL                      bResult;
	LPBYTE                    lpSignedBlob;
	DWORD                     dwSignedBlobSize;
	HANDLE                    hFile;
	DWORD                     dwReadByte;
	PCCERT_CONTEXT            pSignerContext;

	hFile = CreateFile(lpszFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return NULL;

	dwSignedBlobSize = GetFileSize(hFile, NULL);
	lpSignedBlob = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSignedBlobSize);
	ReadFile(hFile, lpSignedBlob, dwSignedBlobSize, &dwReadByte, NULL);
	CloseHandle(hFile);	
	
	verifyPara.cbSize                   = sizeof(CRYPT_VERIFY_MESSAGE_PARA);
	verifyPara.dwMsgAndCertEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
	verifyPara.hCryptProv               = 0;
	verifyPara.pfnGetSignerCertificate  = NULL;
	verifyPara.pvGetArg                 = NULL;

	bResult = CryptVerifyMessageSignature(&verifyPara, 0, lpSignedBlob, dwSignedBlobSize, NULL, &dwDataSize, NULL);
	if (!bResult)
		return NULL;

	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
 
	bResult = CryptVerifyMessageSignature(&verifyPara, 0, lpSignedBlob, dwSignedBlobSize, lpData, &dwDataSize, &pSignerContext);
	if (!bResult) {
		HeapFree(GetProcessHeap(), 0, lpSignedBlob);
		return NULL;
	}
	
	HeapFree(GetProcessHeap(), 0, lpSignedBlob);

	return pSignerContext;
}

CMSG_ENVELOPED_ENCODE_INFO構造体を初期化するにあたり、 事前に受取人の証明書と暗号化アルゴリズムを初期化しておかなければなりません。 受取人の証明書は自作関数のGetRecipientContextで、SignedData型のデータから取得し、 暗号化アルゴリズムはszOID_RSA_RC4としています。 CryptMsgOpenToEncodeの第3引数に指定するメッセージタイプはCMSG_ENVELOPEDであり、 これにより、第4引数がCMSG_ENVELOPED_ENCODE_INFO構造体であると解釈されます。 CryptMsgUpdateが成功すれば、メッセージのコンテンツには 暗号化されたデータがEnvelopedData型で格納されていることになるので、 CryptMsgGetParamでこれを取得し、ファイルに保存します。

CryptMsgControlによるデータの複合化

EnvelopedData型のデータを複合化する手順を次に示します。

#include <windows.h>

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

BOOL GetRecipientCspParam(HCRYPTMSG hMsg, DWORD dwIndex, HCRYPTPROV *phProv, LPDWORD lpdwKeySpec);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HANDLE                 hFile;
	DWORD                  dwReadByte;
	LPBYTE                 lpEncryptedBlob;
	DWORD                  dwEncryptedBlobSize;
	LPBYTE                 lpData;
	DWORD                  dwDataSize;
	HCRYPTMSG              hMsg;
	DWORD                  dwKeySpec;
	HCRYPTPROV             hProv;
	CMSG_CTRL_DECRYPT_PARA decryptPara;

	hFile = CreateFile(TEXT("encrypt.dat"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return 0;

	dwEncryptedBlobSize = GetFileSize(hFile, NULL);
	lpEncryptedBlob = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwEncryptedBlobSize);
	ReadFile(hFile, lpEncryptedBlob, dwEncryptedBlobSize, &dwReadByte, NULL);
	CloseHandle(hFile);

	hMsg = CryptMsgOpenToDecode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, 0, 0, NULL, NULL);
	if (hMsg == NULL) {
		HeapFree(GetProcessHeap(), 0, lpEncryptedBlob);
		return 0;
	}

	if (!CryptMsgUpdate(hMsg, lpEncryptedBlob, dwEncryptedBlobSize, TRUE)) {
		HeapFree(GetProcessHeap(), 0, lpEncryptedBlob);
		CryptMsgClose(hMsg);
		return 0;
	}

	if (!GetRecipientCspParam(hMsg, 0, &hProv, &dwKeySpec)) {
		MessageBox(NULL, TEXT("受取人のCSP情報の取得に失敗しました。"), NULL, MB_ICONWARNING);
		HeapFree(GetProcessHeap(), 0, lpEncryptedBlob);
		CryptMsgClose(hMsg);
		return 0;
	}
	
	decryptPara.cbSize           = sizeof(CMSG_CTRL_DECRYPT_PARA);
	decryptPara.hCryptProv       = hProv;
	decryptPara.dwKeySpec        = dwKeySpec;
	decryptPara.dwRecipientIndex = 0;

	if (!CryptMsgControl(hMsg, 0, CMSG_CTRL_DECRYPT, &decryptPara)) {
		MessageBox(NULL, TEXT("複合化に失敗しました。"), NULL, MB_ICONWARNING);
		CryptMsgClose(hMsg);
		return 0;
	}

	CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, NULL, &dwDataSize);
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
	CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, lpData, &dwDataSize);

	MessageBox(NULL, (LPTSTR)lpData, TEXT("OK"), MB_OK);
	
	HeapFree(GetProcessHeap(), 0, lpEncryptedBlob);
	HeapFree(GetProcessHeap(), 0, lpData);
	CryptMsgClose(hMsg);

	return 0;
}

BOOL GetRecipientCspParam(HCRYPTMSG hMsg, DWORD dwIndex, HCRYPTPROV *phProv, LPDWORD lpdwKeySpec)
{
	DWORD          dwDataSize;
	HCERTSTORE     hStore;
	PCERT_INFO     pRecipientInfo;
	PCCERT_CONTEXT pRecipientContext;
	BOOL           bResult;

	CryptMsgGetParam(hMsg, CMSG_RECIPIENT_INFO_PARAM, dwIndex, NULL, &dwDataSize);
	pRecipientInfo = (PCERT_INFO)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
	CryptMsgGetParam(hMsg, CMSG_RECIPIENT_INFO_PARAM, dwIndex, pRecipientInfo, &dwDataSize);

	hStore = CertOpenSystemStore(0, TEXT("MY"));
	if (hStore == NULL) {
		HeapFree(GetProcessHeap(), 0, pRecipientInfo);
		return FALSE;
	}

	pRecipientContext = CertGetSubjectCertificateFromStore(hStore, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, pRecipientInfo);
	if (pRecipientContext == NULL) {
		HeapFree(GetProcessHeap(), 0, pRecipientInfo);
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return FALSE;
	}

	bResult = CryptAcquireCertificatePrivateKey(pRecipientContext, 0, NULL, phProv, lpdwKeySpec, NULL);

	HeapFree(GetProcessHeap(), 0, pRecipientInfo);
	CertFreeCertificateContext(pRecipientContext);
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);

	return bResult;
}

まずは、EnvelopedData型のデータをファイルから読み取り、 それをCryptMsgUpdateに指定してデコードします。 デコードが終了すれば、CMSG_CTRL_DECRYPTを指定したCryptMsgControlで データを複合化することができますが、CMSG_CTRL_DECRYPT_PARA構造体のメンバから分かるように、 CSPのハンドルと鍵の用途が必要になります。 これらを取得するためのGetRecipientCspParamの内部を順に追っていきます。

CryptMsgGetParam(hMsg, CMSG_RECIPIENT_INFO_PARAM, dwIndex, NULL, &dwDataSize);
pRecipientInfo = (PCERT_INFO)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
CryptMsgGetParam(hMsg, CMSG_RECIPIENT_INFO_PARAM, dwIndex, pRecipientInfo, &dwDataSize);

CryptMsgGetParamにCMSG_RECIPIENT_INFO_PARAMを指定して、受取人の情報を取得します。 第3引数のdwIndexは、取得対象とする受取人のインデックスであり、 複数の受取人が存在する場合を考慮しています。 ここで得られるCERT_INFO構造体で初期化されているのは、 SerialNumber、Issuer、そしてPublicKeyAlgorithmだけであり、 CSPのハンドルと鍵用途は含まれていません。 しかし、これらは受取人の証明書を識別するだけの情報と成り得ますから、 MY証明書ストアにてこれらの情報を持っている証明書を検索すればよいことになります。

hStore = CertOpenSystemStore(0, TEXT("MY"));
if (hStore == NULL) {
	HeapFree(GetProcessHeap(), 0, pRecipientInfo);
	return FALSE;
}

pRecipientContext = CertGetSubjectCertificateFromStore(hStore, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, pRecipientInfo);
if (pRecipientContext == NULL) {
	HeapFree(GetProcessHeap(), 0, pRecipientInfo);
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
	return FALSE;
}

bResult = CryptAcquireCertificatePrivateKey(pRecipientContext, 0, NULL, phProv, lpdwKeySpec, NULL);

CertGetSubjectCertificateFromStoreは、第3引数に指定したCERT_INFO構造体のSerialNumberとIssuerに一致する証明書を 第1引数に指定した証明書ストアから検索します。 取得した証明書にはCSPの情報が関連付けられているはずですから、 CryptAcquireCertificatePrivateKeyでCSPのハンドルと鍵用途を取得することができます。



戻る