EternalWindows
自己署名入り証明書 / 認証局鍵識別子

今回は、拡張情報の1つである認証局鍵識別子(Authority Key Identifier)について説明します。 この情報は、サブジェクト鍵識別子と同じように公開鍵のハッシュ値となっていますが、 その公開鍵というのは証明書に含まれている公開鍵ではなく、 証明書の署名に利用した秘密鍵の対となる公開鍵です。 つまり、CAの公開鍵のハッシュ値が認証局鍵識別子ということになります。 認証局鍵識別子が証明書に含まれている場合、その証明書に署名を行ったCAの証明書を特定することができます。 何故なら、認証局鍵識別子と同じ値がCAの証明書のサブジェクト鍵識別子として格納されているはずだからです。 ただし、認証局鍵識別子は、自己署名入り証明書には含まれている必要はありません。 自己署名の場合は、秘密鍵と対になる公開鍵が証明書に含まれることになり、 サブジェクト鍵識別子と認証局鍵識別子の値が同一になるためです。 認証局鍵識別子は、クリティカルとして記さないようにします。

認証局鍵識別子はCAの公開鍵のハッシュ値と説明しましたが、 実際には後わずかな情報と共に証明書に格納されることになっています。 その情報は、CERT_AUTHORITY_KEY_ID_INFO構造体として定義されています。

typedef struct _CERT_AUTHORITY_KEY_ID_INFO {
  CRYPT_DATA_BLOB              KeyId;
  CERT_NAME_BLOB               CertIssuer;
  CRYPT_INTEGER_BLOB           CertSerialNumber;
 } CERT_AUTHORITY_KEY_ID_INFO,  *PCERT_AUTHORITY_KEY_ID_INFO;

KeyIdは、認証局鍵識別子を指定します。 CertIssuerは、CAの証明書の発行者の名前を指定します。 CertSerialNumberは、CAの証明書のシリアル番号を指定します。 この構造体の目的は、発行された証明書から発行者であるCAの証明書を特定することです。 これには既に述べた認証局鍵識別子を利用する方法があるわけですが、 この構造体はCertIssuerとCertSerialNumberを利用したもう1つの方法を提供しています。 証明書は、発行者とシリアル番号で一意に識別することができるため、 これらの情報を使っても証明書を特定することができるのです。

認証局鍵識別子を追加する例を次に指定します。

void AddAuthorityKeyIdentifier(PCERT_EXTENSION pCertExtension, PCERT_INFO pCertInfo)
{
	CERT_AUTHORITY_KEY_ID_INFO authorityKeyIdentifier;
	CRYPT_DATA_BLOB            identifier;
	LPBYTE                     lpPublicKeyHash;
	DWORD                      dwSize;
	LPBYTE                     lpData;
	
	CryptHashPublicKeyInfo(0, CALG_SHA1, 0, X509_ASN_ENCODING, &pCertInfo->SubjectPublicKeyInfo, NULL, &dwSize);
	lpPublicKeyHash = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
	CryptHashPublicKeyInfo(0, CALG_SHA1, 0, X509_ASN_ENCODING, &pCertInfo->SubjectPublicKeyInfo, lpPublicKeyHash, &dwSize);

	identifier.cbData = dwSize;
	identifier.pbData = lpPublicKeyHash;

	authorityKeyIdentifier.KeyId            = identifier;
	authorityKeyIdentifier.CertIssuer       = pCertInfo->Issuer;
	authorityKeyIdentifier.CertSerialNumber = pCertInfo->SerialNumber;

	CryptEncodeObject(X509_ASN_ENCODING, szOID_AUTHORITY_KEY_IDENTIFIER, &authorityKeyIdentifier, NULL, &dwSize);
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
	CryptEncodeObject(X509_ASN_ENCODING, szOID_AUTHORITY_KEY_IDENTIFIER, &authorityKeyIdentifier, lpData, &dwSize);

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

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

引数として渡されるpCertInfoは、CAの情報が入っていることが望まれます。 CryptHashPublicKeyInfoで証明書に含まれる公開鍵のハッシュ値を作成し、 これと証明書に含まれる発行先とシリアル番号をCERT_AUTHORITY_KEY_ID_INFO構造体に指定します。 以降の処理は、OIDをszOID_AUTHORITY_KEY_IDENTIFIERにしただけで、 特にこれまでと変化している点はありません。

CA証明書の特定

発行された証明書にCERT_AUTHORITY_KEY_ID_INFOが格納されているとして、 実際に発行者であるCAの証明書を特定する方法について考えてみましょう。 証明書を検索するCertFindCertificateInStoreには、 特定の情報を持った証明書のみを検索できる機能がありましたが、 ここに何を指定するかが重要となります。 証明書とは、発行者とシリアル番号で一意に識別することができるため、 これを受け取るCERT_ID構造体を指定することになります。

typedef struct _CERT_ID {
  DWORD   dwIdChoice;
  union {
      CERT_ISSUER_SERIAL_NUMBER   IssuerSerialNumber;
      CRYPT_HASH_BLOB             KeyId;
      CRYPT_HASH_BLOB             HashId;
  };
} CERT_ID, *PCERT_ID;

dwIdChoiceにCERT_ID_ISSUER_SERIAL_NUMBERを指定した場合、 IssuerSerialNumberメンバが有効となります。 CERT_ISSUER_SERIAL_NUMBER構造体には、発行者とシリアル番号を表すメンバが存在します。 dwIdChoiceにCERT_ID_KEY_IDENTIFIERを指定した場合は、KeyIdメンバが有効になります。 ここには、認証局鍵識別子のハッシュ値の部分を指定することになります。 dwIdChoiceにCERT_ID_SHA1_HASHを指定した場合は、HashIdメンバに証明書のハッシュ値を指定します。 これを利用する場合は、事前にCAの証明書に対してCryptHashCertificateを呼び出して ハッシュ値を維持してことになるでしょう。 いずれの場合も、特定の証明書を検索する手段に成り得ますが、 CERT_ID_ISSUER_SERIAL_NUMBERを使用した例を挙げたいと思います。

#include <windows.h>

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HCERTSTORE                  hStore;
	PCCERT_CONTEXT              pContext;
	TCHAR                       szBuf[256];
	CERT_ID                     certId;
	PCERT_EXTENSION             pCertExtension;
	DWORD                       dwSize;
	PCERT_AUTHORITY_KEY_ID_INFO pAuthorityKeyIdentifier;
	
	hStore = CertOpenSystemStore(0, TEXT("MY"));
	if (hStore == NULL)
		return 0;

	pContext = CertFindCertificateInStore(hStore, X509_ASN_ENCODING, 0, CERT_FIND_ISSUER_STR, L"name", NULL);
	if (pContext == NULL) {
		MessageBox(NULL, TEXT("MY証明書ストアから証明書を取得できませんでした。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	pCertExtension = CertFindExtension(szOID_AUTHORITY_KEY_IDENTIFIER, pContext->pCertInfo->cExtension, pContext->pCertInfo->rgExtension);
	if (pCertExtension == NULL) {
		MessageBox(NULL, TEXT("認証局鍵識別子が存在しません。"), NULL, MB_ICONWARNING);
		CertFreeCertificateContext(pContext);
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return 0;
	}

	CryptDecodeObject(X509_ASN_ENCODING, szOID_AUTHORITY_KEY_IDENTIFIER, pCertExtension->Value.pbData, pCertExtension->Value.cbData, 0, NULL, &dwSize);
	pAuthorityKeyIdentifier = (PCERT_AUTHORITY_KEY_ID_INFO)HeapAlloc(GetProcessHeap(), 0, dwSize);
	CryptDecodeObject(X509_ASN_ENCODING, szOID_AUTHORITY_KEY_IDENTIFIER, pCertExtension->Value.pbData, pCertExtension->Value.cbData, 0, pAuthorityKeyIdentifier, &dwSize);

	CertFreeCertificateContext(pContext);
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
	
	hStore = CertOpenSystemStore(0, TEXT("CA"));
	if (hStore == NULL)
		return 0;

	certId.dwIdChoice                      = CERT_ID_ISSUER_SERIAL_NUMBER;
	certId.IssuerSerialNumber.Issuer       = pAuthorityKeyIdentifier->CertIssuer;
	certId.IssuerSerialNumber.SerialNumber = pAuthorityKeyIdentifier->CertSerialNumber;
	
	pContext = CertFindCertificateInStore(hStore, X509_ASN_ENCODING, 0, CERT_FIND_CERT_ID, &certId, NULL);
	if (pContext == NULL) {
		MessageBox(NULL, TEXT("CA証明書ストアから証明書を取得できませんでした。"), NULL, MB_ICONWARNING);
		return 0;
	}

	CertGetNameString(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, szBuf, sizeof(szBuf));
	MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);

	HeapFree(GetProcessHeap(), 0, pAuthorityKeyIdentifier);
	CertFreeCertificateContext(pContext);
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
	
	return 0;
}

サブジェクトがnameである証明書がMY証明書ストアに存在するとして、まずはそれを取得します。 次に、その証明書からCertFindExtensiond認証局鍵識別子の部分を取得し、 これをCryptDecodeObjectでデコードします。 デコードして得られたCERT_AUTHORITY_KEY_ID_INFO構造体には、 CA証明書の発行者とシリアル番号が含まれているため、 これをCERT_ID構造体に指定し、CA(場合によってはRoot)証明書ストアを検索します。 ここで取得できた証明書が、MY証明書ストアで取得した証明書に署名を行ったCAの証明書ということになります。



戻る