微軟的CryptoAPI提供了一套解碼X509證書的函數,一個X509證書解碼之後,得到一個PCCERT_CONTEXT類型的結構體指針。通過該結構體,我們就可以擷取想要的證書項和屬性等。
X509證書檔案,根據封裝的不同,主要有以下三種類型:
*.cer:單個X509證書檔案,不私鑰,可以是二進制和Base64格式。該類型的證書最常見;
*.p7b:PKCS#7格式的證書鍊檔案,包含一個或多個X509證書,不含私鑰。通常從CA中心申請RSA證書時,傳回的簽名證書就是p7b格式的證書檔案;
*.pfx:PKCS#12格式的證書檔案,可以包含一個或者多個X509證書,含有私鑰,一般有密碼保護。通常從CA中心申請RSA證書時,加密證書和RSA加密私鑰就是一個pfx格式的檔案傳回。
下面,針對這三種類型的證書檔案,使用CryptoAPI進行解碼,得到對應的PCCERT_CONTEXT結構體指針。需要注意的是,示例代碼中的證書檔案内容都是指二進制資料,如果證書檔案本身使用的Base64格式,從檔案讀取之後,需要将Base64格式的内容轉化為二進制資料,才能使用下面的解碼函數。
一、解碼CER證書檔案
CER格式的檔案最簡單,隻需要調用API CertCreateCertificateContext()即可。示例代碼如下(lpCertData為二進制資料):
ULONG CCSPCertificate::_DecodeX509Cert(LPBYTE lpCertData, ULONG ulDataLen)
{
if (!lpCertData || ulDataLen == 0)
{
return CERT_ERR_INVALIDPARAM;
}
m_pCertContext = CertCreateCertificateContext(GLOBAL_ENCODING_TYPE, lpCertData, ulDataLen);
if (!m_pCertContext)
{
return GetLastError();
}
return CERT_ERR_OK;
}
二、解碼P7B證書檔案
由于P7B是個證書鍊檔案,理論上可以包含多個X509證書。但是實際應用中,往往隻包含一個檔案,是以我們隻處理第一個證書。示例代碼如下:
ULONG CCSPCertificate::_DecodeP7bCert(LPBYTE lpCertData, ULONG ulDataLen)
{
ULONG ulRes = CERT_ERR_OK;
ULONG ulFlag = CRYPT_FIRST;
ULONG ulContainerNameLen = 512;
CHAR csContainerName[512] = {0};
BOOL bFoundContainer = FALSE;
if (!lpCertData || ulDataLen == 0)
{
return CERT_ERR_INVALIDPARAM;
}
// 由證書鍊建立一個證書庫
HCERTSTORE hCertStore = NULL;
CRYPT_DATA_BLOB dataBlob = {ulDataLen, lpCertData};
hCertStore = CertOpenStore(CERT_STORE_PROV_PKCS7, GLOBAL_ENCODING_TYPE, NULL, 0, &dataBlob);
if (NULL == hCertStore)
{
ulRes = GetLastError();
return ulRes;
}
// 釋放之前的證書内容
if (m_pCertContext)
{
CertFreeCertificateContext(m_pCertContext);
m_pCertContext = NULL;
}
// 得到第一個證書内容
m_pCertContext = CertEnumCertificatesInStore(hCertStore, m_pCertContext);
if (NULL == m_pCertContext)
{
ulRes = GetLastError();
goto CLOSE_STORE;
}
// 關閉證書庫
CLOSE_STORE:
if (hCertStore)
{
CertCloseStore(hCertStore, 0);
hCertStore = NULL;
}
return ulRes;
}
如在特殊的情況下,需要處理整個證書鍊中的所有證書,則隻需要循環調用CertEnumCertificatesInStore()知道傳回為NULL為止。
三、解碼PFX證書檔案
解碼PFX證書時,和處理P7B很相似,隻是多了密碼檢驗。示例代碼如下:
ULONG CCSPCertificate::_DecodePfxCert(LPBYTE lpCertData, ULONG ulDataLen, LPSTR lpscPassword)
{
ULONG ulRes = 0;
HCERTSTORE hCertStore = NULL;
PCCERT_CONTEXT pCertContext = NULL;
USES_CONVERSION;
if (!lpCertData || ulDataLen == 0)
{
return CERT_ERR_INVALIDPARAM;
}
// 建立證書庫
CRYPT_DATA_BLOB dataBlob = {ulDataLen, lpCertData};
hCertStore = PFXImportCertStore(&dataBlob, lpscPassword ? A2W(lpscPassword) : NULL, CRYPT_EXPORTABLE);
if (NULL == hCertStore)
{
hCertStore = PFXImportCertStore(&dataBlob, L"", CRYPT_EXPORTABLE);
}
if (NULL == hCertStore)
{
ulRes = GetLastError();
return ulRes;
}
// 枚舉證書,隻處理第一個證書
while(pCertContext = CertEnumCertificatesInStore(hCertStore, pCertContext))
{
if (pCertContext->pbCertEncoded && pCertContext->cbCertEncoded > 0)
{
m_pCertContext = CertDuplicateCertificateContext(pCertContext);
break;
}
}
// 關閉證書庫
CertCloseStore(hCertStore, 0);
hCertStore = NULL;
return ulRes;
}
至此,三種常見證書檔案的解碼以完成,通過解碼得到的證書上下文結構體指針m_pCertContext 就可以解析證書的項和擴充屬性了。具體的解析方法,将在後續的Blog中逐一介紹。
相關博文:CSP:使用CryptoAPI解析X509證書基本項