所谓有来有往,前面介绍了如何拼装构造PKCS#7签名,今天就讲一下如何验证它。如果已经掌握了构造的方法,验证就相对容易很多。最核心的就是读取signerInfos(签名者信息)里的authenticatedAttributes(用户认证属性)和encryptedDigest(签名),然后进行验证。
首先读取签名用的证书,这里用了遍历,也可以直接取索引值3去访问。
//检查DER数据类型
ASN1_TAG tag=TAG_UNIVERSAL;
hr=signDer->get_Tag(&tag);
if (tag!=TAG_SEQUENCE)
_raise_error(不合理的ASN1类型。);
CComPtr dv1,dv2,dvopt,dvsigner,dvseq,dvauthed,dvsig;
CComPtr dvcert,dvattr,dvattype,dvdigest,dvset;
//读取证书
//biceng::CBytesSafeArray bcert;
hr=signDer->get_Item(1,&dv1);
if (hr!=S_OK)
_raise_error(解析数据失败,读取签名数据失败。);
hr=dv1->get_Item(0,&dv2);
if (hr!=S_OK)
_raise_error(解析数据失败。);
LONG iCount=0;
int sd_index;
hr=dv2->get_Count(&iCount);
for(sd_index=0;sd_index hr=dv2->get_Item(sd_index,&dvopt); hr=dvopt->get_Tag(&tag); if (tag==TAG_OPT){ //检查tag值是否为TAG_OPT hr=dvopt->get_Item(0,&dvcert); if (hr!=S_OK) _raise_error(获取证书数据失败。); break; } } if(!dvcert) _raise_error(未找到证书数据。); hr=cert.get_IStream(&pStm); hr=dvcert->SaveBigInteger(pStm,FALSE); hr=cert.put_IStream(pStm); if (hr!=S_OK) _raise_error(获取证书数据失败。); CCertContext pCertContext=::CertCreateCertificateContext(X509_ASN_ENCODING, cert, cert.GetSize()); if (!pCertContext) return MAKE_HRESULT2(1,FACILITY_WIN32,::GetLastError()); // 创建哈希对象 CAutoRelease CAutoRelease CERT_PUBLIC_KEY_INFO cpki=pCertContext->pCertInfo->SubjectPublicKeyInfo; //把签名者的公钥数据导入公钥句柄 if(!co.CPImportKey(cpki.PublicKey.pbData, cpki.PublicKey.cbData, 0, 0, &hPubKey)) return MAKE_HRESULT2(1,FACILITY_WIN32,::GetLastError()); sd_index++; hr=dv2->get_Item(sd_index,&dvsigner); //读下一个der if (hr!=S_OK) _raise_error(解析数据失败。); hr=dvsigner->get_Tag(&tag); if (tag==TAG_OPT+1){ //如果这der的tag是TAG_OPT+1,说明是 //证书crl列表元素,那继续读下一个der sd_index++; hr=dv2->get_Item(sd_index,&dvsigner); if (hr!=S_OK) _raise_error(解析数据失败。); hr=dvsigner->get_Tag(&tag); } 开始读取签名者信息。在读取用户认证属性时,属性是没有顺序的,所以只能通过遍历并比较属性类型确定属性内容。另外,根据PKCS#7的标准,如果签名里有用户认证属性,则必须验证原文的摘要和签名的摘要是否一致。 if (tag!=TAG_SET) _raise_error(获取签名者信息失败,不合理的ASN1类型。); biceng::CBytesSafeArray bsig,bdigest; hr=dvsigner->get_Item(0,&dvseq); if (hr!=S_OK) _raise_error(解析数据失败。); int sg_index; iCount=0; hr=dvseq->get_Count(&iCount); for(sg_index=0;sg_index hr=dvseq->get_Item(sg_index,&dvopt); hr=dvopt->get_Tag(&tag); if (tag==TAG_OPT){ //遍历元素,第一个tag值为TAG_OPT的是用户认证属性 dvopt.CopyTo(&dvauthed); break; } } CMemoryLocator pbAuthedAttr; if(!dvauthed){ //如果没有用户认证属性数据, sg_index–; //那就先把最后一个der当做签名数据 hr=dvseq->get_Item(sg_index,&dvsig); if (hr!=S_OK) _raise_error(解析数据失败。); hr=dvsig->get_Tag(&tag); if (tag==TAG_OPT+1) //如果最后一个der的tag是TAG_OPT+1,说明它是非 //用户认证属性数据,那读前一个der,作为签名数据 sg_index–; } else{ sg_index+=2; //如果有用户认证属性数据,那后面第2个就是签名数据 iCount=0; hr=dvauthed->get_Count(&iCount); BSTR bstype; _bstr_t b; for(int k=0;k { //遍历用户认证属性,获得正文摘要 hr=dvauthed->get_Item(k,&dvattr); hr=dvattr->get_Tag(&tag); if (tag==TAG_SEQUENCE) { hr=dvattr->get_Item(0,&dvattype); if (hr!=S_OK) _raise_error(获取用户认证属性数据失败。); hr=dvattype->get_Tag(&tag); if (tag==TAG_OBJECTID) { hr=dvattype->get_ObjectIdentifier(&bstype); if(!::strcmp((_bstr_t)bstype,szOID_RSA_messageDigest)) { hr=dvattr->get_Item(1,&dvset); if (hr!=S_OK) _raise_error(获取正文摘要失败。); hr=dvset->get_Item(0,&dvdigest); if (hr!=S_OK) _raise_error(获取正文摘要失败。); hr=dvdigest->get_Tag(&tag); if (tag!=TAG_OCTETSTRING) _raise_error(获取正文摘要数据失败,不合理的ASN1类型。); hr=dvdigest->get_Data(&bdigest); if (hr!=S_OK) _raise_error(获取正文摘要数据失败。); //这里通过一个自定义的工具类计算原文(szBuffer)摘要,具体过程就不展开 if(!co.CPCreateHash(CALG_SM3, 0, 0, &hHash)) return MAKE_HRESULT2(1,FACILITY_WIN32,::GetLastError()); if (!co.CPSetHashParam(hHash,HP_SM2_PUB_KEY,(BYTE*)&hPubKey,0)) return MAKE_HRESULT2(1,FACILITY_WIN32,::GetLastError()); // 对原数据进行hash运算 if(!co.CPHashData(hHash, szBuffer.GetBuffer(), szBuffer.GetSize(), 0)) return MAKE_HRESULT2(1,FACILITY_WIN32,::GetLastError()); SAFEARRAY* psay; if(S_OK!=co.GetHashData(hHash,&psay)) return MAKE_HRESULT2(1,FACILITY_WIN32,::GetLastError()); if (bdigest!=psay) //计算的正文摘要和签名里获取的正文摘要 //进行对比 _raise_error(正文摘要数据不一致。); break; } } } } hr=dvauthed->put_Tag(TAG_SET); //获取用户认证属性数据,这里要把tag设置 //成TAG_SET,原因与构造时设置原因相同。 hr=pbAuthedAttr.get_IStream(&pStm); //因为设置tag会引起节点属性变化,造成 //get_Count方法不正常返回,所以把这段代 //码放在遍历后面执行。 _handle_result2(); hr=dvauthed->SaveBigInteger(pStm,FALSE); _handle_result2(); hr=pbAuthedAttr.put_IStream(pStm); _handle_result2(); } hr=dvseq->get_Item(sg_index,&dvsig); if (hr!=S_OK) _raise_error(解析数据失败。); hr=dvsig->get_Tag(&tag); if (tag!=TAG_OCTETSTRING) _raise_error(获取签名数据失败,不合理的ASN1类型。); hr=dvsig->get_Data(&bsig); if (hr!=S_OK) _raise_error(获取签名数据失败。); 取得签名数据后,如果没有用户认证属性,则针对原文进行验证;如果有用户认证属性,则针对用户认证属性进行验证。示例代码中具体的验证过程依照国密标准,本文不展开叙述。 CMemoryLocator pbPureSignature; hr=pbPureSignature.put_SAFEARRAY(bsig); if (hr!=S_OK) return hr; if(!co.CPCreateHash(CALG_SM2, 0, 0, &hHash)) return MAKE_HRESULT2(1,FACILITY_WIN32,::GetLastError()); if (!co.CPSetHashParam(hHash,HP_SM2_PUB_KEY,(BYTE*)&hPubKey,0)) return MAKE_HRESULT2(1,FACILITY_WIN32,::GetLastError()); if(!dvauthed) //如果没有用户认证属性,则直接用原文摘要值验证 { if(!co.CPHashData(hHash, szBuffer.GetBuffer(), szBuffer.GetSize(), 0)) return MAKE_HRESULT2(1,FACILITY_WIN32,::GetLastError()); } else{ //如果有用户认证属性,则用用户认证属性摘要值验证 if(!co.CPHashData(hHash, pbAuthedAttr.GetBuffer(), pbAuthedAttr.GetSize(), 0)) return MAKE_HRESULT2(1,FACILITY_WIN32,::GetLastError()); } BSTR bs; pbAuthedAttr.Base64Encode(&bs); BSTR bs2; pbPureSignature.Base64Encode(&bs2); LPTSTR szDescription=NULL; // 验证数字签名 BOOLEAN verifyResult=FALSE; if(!co.CPVerifySignature(hHash, pbPureSignature, pbPureSignature.GetSize(), hPubKey, szDescription, 0)) { _raise_error(数字签名验证失败。); }