我正在编写一个接收PKCS7数据的服务(从签名的PDF文档中提取)并需要验证它。
我正在使用iText7 PdfPKCS7,但签名验证总是失败。我可以从PKCS7读取所有其他信息(证书,时间戳等,我已经通过OpenSSL验证了)。只有签名显示为无效。
以下是测试用例:
public static void main(String[] args) throws IOException, GeneralSecurityException,
NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Logger logger = Logger.getLogger(PKCS7Test.class.getName());
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
String path ="/tmp/signed.pdf";
PdfDocument pdf = new PdfDocument(new PdfReader(path));
SignatureUtil signatureUtil = new SignatureUtil(pdf);
List<String> names = signatureUtil.getSignatureNames();
String outerRevisionName = names.get(names.size()-1);
PdfPKCS7 pkcs7In = signatureUtil.verifySignature(outerRevisionName);
boolean isValidSignature = pkcs7In.verify();
logger.log(Level.INFO, "pkcs7In signature is " + ((isValidSignature)?"":"not ") + "valid");
// get hash of original document
Field digestAttrField = PdfPKCS7.class.getDeclaredField("digestAttr");
digestAttrField.setAccessible(true);
byte[] originalDigest = (byte[]) digestAttrField.get(pkcs7In);
// get pkcs7 structure of original signature
PdfDictionary dict = signatureUtil.getSignatureDictionary(outerRevisionName);
PdfString contents = dict.getAsString(PdfName.Contents);
byte [] originalBytes = contents.getValueBytes();
String originalPkcs7 = Base64.getEncoder().encodeToString(originalBytes);
// now reverse process and import PKCS7 data back into object
byte[] pkcs7Bytes = Base64.getDecoder().decode(originalPkcs7);
PdfPKCS7 pkcs7Out = new PdfPKCS7(pkcs7Bytes, PdfName.Adbe_pkcs7_detached, provider.getName());
isValidSignature = pkcs7Out.verify();
logger.log(Level.INFO, "pkcs7Out signature is " + ((isValidSignature)?"":"not ") + "valid");
// get hash of original document from imported signature
digestAttrField = PdfPKCS7.class.getDeclaredField("digestAttr");
digestAttrField.setAccessible(true);
byte [] importedDigest = (byte[]) digestAttrField.get(pkcs7Out);
logger.log(Level.INFO, "Hash values are " + ((Arrays.areEqual(originalDigest, importedDigest))?"":"not ") + "equal");
输出总是:
pkcs7In signature is valid
pkcs7Out signature is not valid
Hash values are equal
我想我在导入时做错了什么,但却无法找到...
顺便说一下。 /tmp/signed.pdf
验证其他PDF工具(Adobe DC,PdfOnline等)中的OK(签名和内容)。
编辑: 我试图用BouncyCastle验证签名,但也失败了......
CMSSignedData signature = new CMSSignedData(pkcs7Bytes);
Store certStore = signature.getCertificates();
SignerInformationStore signers = signature.getSignerInfos();
Collection c = signers.getSigners();
Iterator it = c.iterator();
while (it.hasNext())
{
SignerInformation signer = (SignerInformation)it.next();
Collection certCollection = certStore.getMatches(signer.getSID());
Iterator certIt = certCollection.iterator();
X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
try {
signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert));
}
catch (Exception ex) {
logger.log(Level.INFO, "Failed to verify with: " + cert.getSubject().toString() + ": " + ex.getLocalizedMessage());
byte [] contentDigest = signer.getContentDigest();
bi = new BigInteger(1, contentDigest);
String hash = String.format("%0" + (contentDigest.length << 1) + "x", bi);
logger.log(Level.INFO, "Bouncycastle Hash from pkcs7Out is {0}", hash);
}
}
结果是Failed to verify with: ... message-digest attribute value does not match calculated value
,哈希值明显不同于实际值......
编辑2:
通过向PdfPKCS7添加单独的verify(byte[] msgDigestBytes)
方法来解决此问题:
public boolean verify(final byte[] msgDigestBytes) throws GeneralSecurityException {
if (verified)
return verifyResult;
if (isTsp) {
TimeStampTokenInfo info = timeStampToken.getTimeStampInfo();
MessageImprint imprint = info.toASN1Structure().getMessageImprint();
byte[] md = msgDigestBytes; // was: messageDigest.digest();
byte[] imphashed = imprint.getHashedMessage();
verifyResult = Arrays.equals(md, imphashed);
} else {
if (sigAttr != null || sigAttrDer != null) {
// was: final byte[] msgDigestBytes = messageDigest.digest();
boolean verifyRSAdata = true;
// Stefan Santesson fixed a bug, keeping the code backward compatible
boolean encContDigestCompare = false;
if (rsaData != null) {
verifyRSAdata = Arrays.equals(msgDigestBytes, rsaData);
encContDigest.update(rsaData);
encContDigestCompare = Arrays.equals(encContDigest.digest(), digestAttr);
}
boolean absentEncContDigestCompare = Arrays.equals(msgDigestBytes, digestAttr);
boolean concludingDigestCompare = absentEncContDigestCompare || encContDigestCompare;
boolean sigVerify = verifySigAttributes(sigAttr) || verifySigAttributes(sigAttrDer);
verifyResult = concludingDigestCompare && sigVerify && verifyRSAdata;
} else {
if (rsaData != null)
sig.update(msgDigestBytes); // was: sig.update(messageDigest.digest());
verifyResult = sig.verify(digest);
}
}
verified = true;
return verifyResult;
}
为了实现这一点,我需要来自可靠来源的文档的原始签名哈希,在我的情况下,我有。这是否仍然可以验证通过哈希进行的签名是否正确?
答案 0 :(得分:2)
来自
PdfPKCS7 pkcs7Out = new PdfPKCS7(pkcs7Bytes, PdfName.Adbe_pkcs7_detached, provider.getName());
isValidSignature = pkcs7Out.verify();
您不能指望签名的正确验证结果:此PdfPKCS7
仅知道CMS签名容器,签名 SubFilter 以及提供算法实现的安全提供程序。因此,它没有关于签名实际上要签名的PDF的信息。因此,这段代码无法验证相关签名,特别是它是否正确签署了所谓的签名数据!
如果要使用该PdfPKCS7
对象验证签名,则必须完成初始化,以便它具有PDF中所需的信息。
要查看所需内容,请查看SignatureUtil
方法verifySignature
:
PdfPKCS7 pk = null;
if (sub.equals(PdfName.Adbe_x509_rsa_sha1)) {
PdfString cert = signature.getPdfObject().getAsString(PdfName.Cert);
if (cert == null)
cert = signature.getPdfObject().getAsArray(PdfName.Cert).getAsString(0);
pk = new PdfPKCS7(PdfEncodings.convertToBytes(contents.getValue(), null), cert.getValueBytes(), provider);
}
else
pk = new PdfPKCS7(PdfEncodings.convertToBytes(contents.getValue(), null), sub, provider);
updateByteRange(pk, signature);
PdfString date = signature.getDate();
if (date != null)
pk.setSignDate(PdfDate.decode(date.toString()));
String signName = signature.getName();
pk.setSignName(signName);
String reason = signature.getReason();
if (reason != null)
pk.setReason(reason);
String location = signature.getLocation();
if (location != null)
pk.setLocation(location);
因此,你必须
SignatureUtil.updateByteRange
;这是向PdfPKCS7
对象通知实际签名数据以允许实际验证的步骤;和PdfPKCS7
对象;为了验证目的,特别是签署时间可能是有意义的。