【问题标题】:iText7: How to verify signature in detached PKCS7?iText7:如何在分离的 PKCS7 中验证签名?
【发布时间】:2018-01-14 19:47:10
【问题描述】:

我正在编写一个接收 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,hash值明显和真实的不一样……

编辑 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;
}

为此,我需要来自受信任来源的文档的原始签名哈希,就我而言,我有。这是否仍然验证通过哈希生成的签名是正确的?

【问题讨论】:

  • 关于您的编辑:“通常”的 BC 验证,就像通常的 iText 验证一样,也是为了检查签名是否真的签署了所谓的签名数据。由于您似乎不想检查,因此您还必须在此处稍微自定义工作流程。
  • 关于您的第二次编辑:这看起来是正确的(假设您在 cmets 中记录了所有更改)。 “这是否仍然验证通过哈希生成的签名是否正确?” - 是的,就PdfPKCS7 验证而言。作为通用签名容器类,它显然不会详细介绍,例如它不会检查 PAdES 基线配置文件,它不会检查算法适用性,...

标签: java pdf itext7 pkcs#7


【解决方案1】:

来自

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对象;出于验证目的,尤其是签名时间可能很重要。

【讨论】:

  • 谢谢。但是当我只获得 PKCS7 时,我看不到这些信息如何可用。特别是没有字典可以从PdfSignature signature = getSignature(name); 读取签名另一方面,已签名的哈希和签名在 PKCS7 中都可用,因此应该可以在不了解 PDF 结构的情况下验证这一点。或者这不能用 PdfPKCS7 完成吗?也许我需要直接使用BC?
  • “但是我不知道当我只获得 PKCS7 时这些信息将如何可用” - 对于一个不足够的正确验证。您实际上可能需要更多,特别是所有与验证相关的信息也存储在 pdf 文件中......
  • “已签名的散列和签名在 PKCS7 中都可用” - 但您还必须检查“已签名的散列”是否确实是散列原始pdf修订版!如果您不验证这一点,您只需检查内部签名是否正常,而不是它是否真的适用于它据称签名的 pdf 文档修订版!完整的验证是 PdfPKCS7 验证旨在检查的内容。
  • 如果你的服务真的应该只验证 CMS 容器的内部完整性,并且检查它是否适用于据称签名的 PDF 修订发生在其他地方,你可以简单地复制 PdfPKCS7.verify 方法的代码(以及该类中的一些非公共其他方法)并删除实际文档哈希的验证。
  • “我可以通过使用额外的 verify() 方法修补 PdfPKCS7 来解决这个问题,该方法接受原始摘要作为字节 []” - 即使如果您每次都必须修补它们,那么更新 iText 版本有点麻烦。但是 “当我加载 pkcs7 时,所有必需的数据都可用” - 如果您只想验证 ISO 32000-1 P7 签名但您希望尽快验证非平凡的 PAdES,这可能是正确的配置文件(使用也在 ISO 32000-2 中定义的结构),你运气不好。
猜你喜欢
  • 1970-01-01
  • 2020-02-05
  • 1970-01-01
  • 1970-01-01
  • 2017-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多