如何添加的PAdES-LTV利用iText如何添加的PAdES-LTV利用iText(how can

2019-05-12 10:54发布

我试图让LTV没有LTV格式的已签署的PDF文件内。 我发现在作为链路描述的所有情况下,同样的例子如何启用LTV的时间戳签名 , iText的LTV启用-如何增加更多的CRL? ,其中,定义什么是获得预期结果的过程。 碰巧我不工作,它并没有给我任何错误,但我不添加LTV。

的一些想法,为什么在执行下面的代码不给我任何错误,但无论如何,我不添加LTV的时间。

这是与我尝试添加LTV的方法:

public void addLtv(String src, String dest, OcspClient ocsp, CrlClient crl, TSAClient tsa)
    throws IOException, DocumentException, GeneralSecurityException {
    PdfReader r = new PdfReader(src);
    FileOutputStream fos = new FileOutputStream(dest);
    PdfStamper stp = PdfStamper.createSignature(r, fos, '\0', null, true);
    LtvVerification v = stp.getLtvVerification();
    AcroFields fields = stp.getAcroFields();
    List<String> names = fields.getSignatureNames();
    String sigName = names.get(names.size() - 1);
    PdfPKCS7 pkcs7 = fields.verifySignature(sigName);
    if (pkcs7.isTsp()) {
        v.addVerification(sigName, ocsp, crl,
            LtvVerification.CertificateOption.SIGNING_CERTIFICATE,
            LtvVerification.Level.OCSP_CRL,
            LtvVerification.CertificateInclusion.NO);
    }
    else {
        for (String name : names) {
            v.addVerification(name, ocsp, crl,
                LtvVerification.CertificateOption.WHOLE_CHAIN,
                LtvVerification.Level.OCSP_CRL,
                LtvVerification.CertificateInclusion.NO);
        }
    }
    PdfSignatureAppearance sap = stp.getSignatureAppearance();
    LtvTimestamp.timestamp(sap, tsa, null);
}

那我一起工作的版本:

  • iText的:5.5.11
  • java的:8

Answer 1:

正如在此评论横空出世

我想是Adobe LTV启用

任务是与少的PAdES(即使在引进的PAdES机制被使用),但集中在一个Adobe的专有标识轮廓,“LTV启用”签名

不幸的是,这种专有标识轮廓中未正确指定。 所有Adobe告诉我们

LTV启用意味着所有必要的信息,以验证被包含内的文件(减去根证书)。

(详情和背景阅读这个答案 )

因此,实施方式LTV能够参与一些试验和错误的例子签名,我不能保证Adobe将接受该代码的输出为“LTV启用”在Adobe Acrobat版本来。

此外,目前iText的5签名API不足够了,因为任务框的(因为事实证明)的Adobe需要将iText的代码不会产生一定另有可选的结构。 最简单的方法来解决,这是更新的iText类LtvVerification在两个方面,所以我会在这里描述的方式。 可替代地一个也可以使用Java反射或复制和调整相当多的代码; 如果如下图所示,你不能更新iText的,你必须选择一个这样的替代方法。

LTV启用签署PDF的签名

本节显示的代码添加和更改与哪一个可以使LTV像OP的例子PDF文档sign_without_LTV.pdf

利用iText的做法LtvVerification

这是原来的代码,这使得使用的LtvVerification类从iText的签名API。 不幸的是,这个功能已经被添加到这个类。

修补LtvVerification

该iText的5 LtvVerification类只提供addVerification方法接受一个签名字段名。 我们需要这些方法也是功能,没有绑定到表单字段,例如用于OCSP响应签名的签名。 为此,我补充说方法的以下过载:

public boolean addVerification(PdfName signatureHash, Collection<byte[]> ocsps, Collection<byte[]> crls, Collection<byte[]> certs) throws IOException, GeneralSecurityException {
    if (used)
        throw new IllegalStateException(MessageLocalization.getComposedMessage("verification.already.output"));
    ValidationData vd = new ValidationData();
    if (ocsps != null) {
        for (byte[] ocsp : ocsps) {
            vd.ocsps.add(buildOCSPResponse(ocsp));
        }
    }
    if (crls != null) {
        for (byte[] crl : crls) {
            vd.crls.add(crl);
        }
    }
    if (certs != null) {
        for (byte[] cert : certs) {
            vd.certs.add(cert);
        }
    }
    validated.put(signatureHash, vd);
    return true;
}

此外,(全说明书可选)时间在最终VRI字典条目是必需的。 因此,我增加了在一个线outputDss方法如下:

...
if (ocsp.size() > 0)
    vri.put(PdfName.OCSP, writer.addToBody(ocsp, false).getIndirectReference());
if (crl.size() > 0)
    vri.put(PdfName.CRL, writer.addToBody(crl, false).getIndirectReference());
if (cert.size() > 0)
    vri.put(PdfName.CERT, writer.addToBody(cert, false).getIndirectReference());
// v--- added line
vri.put(PdfName.TU, new PdfDate());
// ^--- added line
vrim.put(vkey, writer.addToBody(vri, false).getIndirectReference());
...

一些低级别的辅助方法

关于安全操作的原语的一些辅助方法是必需的。 这些方法大多是从现有的iText类(不能直接使用,因为它们是私有的)或源自代码存在收集:

static X509Certificate getOcspSignerCertificate(byte[] basicResponseBytes) throws CertificateException, OCSPException, OperatorCreationException {
    JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME);
    BasicOCSPResponse borRaw = BasicOCSPResponse.getInstance(basicResponseBytes);
    BasicOCSPResp bor = new BasicOCSPResp(borRaw);

    for (final X509CertificateHolder x509CertificateHolder : bor.getCerts()) {
        X509Certificate x509Certificate = converter.getCertificate(x509CertificateHolder);

        JcaContentVerifierProviderBuilder jcaContentVerifierProviderBuilder = new JcaContentVerifierProviderBuilder();
        jcaContentVerifierProviderBuilder.setProvider(BouncyCastleProvider.PROVIDER_NAME);
        final PublicKey publicKey = x509Certificate.getPublicKey();
        ContentVerifierProvider contentVerifierProvider = jcaContentVerifierProviderBuilder.build(publicKey);

        if (bor.isSignatureValid(contentVerifierProvider))
            return x509Certificate;
    }

    return null;
}

static PdfName getOcspSignatureKey(byte[] basicResponseBytes) throws NoSuchAlgorithmException, IOException {
    BasicOCSPResponse basicResponse = BasicOCSPResponse.getInstance(basicResponseBytes);
    byte[] signatureBytes = basicResponse.getSignature().getBytes();
    DEROctetString octetString = new DEROctetString(signatureBytes);
    byte[] octetBytes = octetString.getEncoded();
    byte[] octetHash = hashBytesSha1(octetBytes);
    PdfName octetName = new PdfName(Utilities.convertToHex(octetHash));
    return octetName;
}

static PdfName getCrlSignatureKey(byte[] crlBytes) throws NoSuchAlgorithmException, IOException, CRLException, CertificateException {
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    X509CRL crl = (X509CRL)cf.generateCRL(new ByteArrayInputStream(crlBytes));
    byte[] signatureBytes = crl.getSignature();
    DEROctetString octetString = new DEROctetString(signatureBytes);
    byte[] octetBytes = octetString.getEncoded();
    byte[] octetHash = hashBytesSha1(octetBytes);
    PdfName octetName = new PdfName(Utilities.convertToHex(octetHash));
    return octetName;
}

static X509Certificate getIssuerCertificate(X509Certificate certificate) throws IOException, StreamParsingException {
    String url = getCACURL(certificate);
    if (url != null && url.length() > 0) {
        HttpURLConnection con = (HttpURLConnection)new URL(url).openConnection();
        if (con.getResponseCode() / 100 != 2) {
            throw new IOException(MessageLocalization.getComposedMessage("invalid.http.response.1", con.getResponseCode()));
        }
        InputStream inp = (InputStream) con.getContent();
        byte[] buf = new byte[1024];
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        while (true) {
            int n = inp.read(buf, 0, buf.length);
            if (n <= 0)
                break;
            bout.write(buf, 0, n);
        }
        inp.close();

        X509CertParser parser = new X509CertParser();
        parser.engineInit(new ByteArrayInputStream(bout.toByteArray()));
        return (X509Certificate) parser.engineRead();
    }
    return null;
}

static String getCACURL(X509Certificate certificate) {
    ASN1Primitive obj;
    try {
        obj = getExtensionValue(certificate, Extension.authorityInfoAccess.getId());
        if (obj == null) {
            return null;
        }
        ASN1Sequence AccessDescriptions = (ASN1Sequence) obj;
        for (int i = 0; i < AccessDescriptions.size(); i++) {
            ASN1Sequence AccessDescription = (ASN1Sequence) AccessDescriptions.getObjectAt(i);
            if ( AccessDescription.size() != 2 ) {
                continue;
            }
            else if (AccessDescription.getObjectAt(0) instanceof ASN1ObjectIdentifier) {
                ASN1ObjectIdentifier id = (ASN1ObjectIdentifier)AccessDescription.getObjectAt(0);
                if ("1.3.6.1.5.5.7.48.2".equals(id.getId())) {
                    ASN1Primitive description = (ASN1Primitive)AccessDescription.getObjectAt(1);
                    String AccessLocation =  getStringFromGeneralName(description);
                    if (AccessLocation == null) {
                        return "" ;
                    }
                    else {
                        return AccessLocation ;
                    }
                }
            }
        }
    } catch (IOException e) {
        return null;
    }
    return null;
}

static ASN1Primitive getExtensionValue(X509Certificate certificate, String oid) throws IOException {
    byte[] bytes = certificate.getExtensionValue(oid);
    if (bytes == null) {
        return null;
    }
    ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(bytes));
    ASN1OctetString octs = (ASN1OctetString) aIn.readObject();
    aIn = new ASN1InputStream(new ByteArrayInputStream(octs.getOctets()));
    return aIn.readObject();
}

static String getStringFromGeneralName(ASN1Primitive names) throws IOException {
    ASN1TaggedObject taggedObject = (ASN1TaggedObject) names ;
    return new String(ASN1OctetString.getInstance(taggedObject, false).getOctets(), "ISO-8859-1");
}

static byte[] hashBytesSha1(byte[] b) throws NoSuchAlgorithmException {
    MessageDigest sh = MessageDigest.getInstance("SHA1");
    return sh.digest(b);
}

(如在MakeLtvEnabled )

但并非最优的是,一个绝对可以让他们更高性能和更优雅。

添加LTV信息

基于这些补充和助手可以添加为LTV所需的LTV信息启用签名用这种方法makeLtvEnabled

public void makeLtvEnabled(PdfStamper stp, OcspClient ocspClient, CrlClient crlClient) throws IOException, GeneralSecurityException, StreamParsingException, OperatorCreationException, OCSPException {
    stp.getWriter().addDeveloperExtension(new PdfDeveloperExtension(PdfName.ADBE, new PdfName("1.7"), 8));
    LtvVerification v = stp.getLtvVerification();
    AcroFields fields = stp.getAcroFields();

    Map<PdfName, X509Certificate> moreToCheck = new HashMap<>();

    ArrayList<String> names = fields.getSignatureNames();
    for (String name : names)
    {
        PdfPKCS7 pdfPKCS7 = fields.verifySignature(name);
        List<X509Certificate> certificatesToCheck = new ArrayList<>();
        certificatesToCheck.add(pdfPKCS7.getSigningCertificate());
        while (!certificatesToCheck.isEmpty()) {
            X509Certificate certificate = certificatesToCheck.remove(0);
            addLtvForChain(certificate, ocspClient, crlClient,
                    (ocsps, crls, certs) -> {
                        try {
                            v.addVerification(name, ocsps, crls, certs);
                        } catch (IOException | GeneralSecurityException e) {
                            e.printStackTrace();
                        }
                    },
                    moreToCheck::put
            );
        }
    }

    while (!moreToCheck.isEmpty()) {
        PdfName key = moreToCheck.keySet().iterator().next();
        X509Certificate certificate = moreToCheck.remove(key);
        addLtvForChain(certificate, ocspClient, crlClient,
                (ocsps, crls, certs) -> {
                    try {
                        v.addVerification(key, ocsps, crls, certs);
                    } catch (IOException | GeneralSecurityException e) {
                        e.printStackTrace();
                    }
                },
                moreToCheck::put
        );
    }
}

void addLtvForChain(X509Certificate certificate, OcspClient ocspClient, CrlClient crlClient, VriAdder vriAdder,
        BiConsumer<PdfName, X509Certificate> moreSignersAndCertificates) throws GeneralSecurityException, IOException, StreamParsingException, OperatorCreationException, OCSPException {
    List<byte[]> ocspResponses = new ArrayList<>();
    List<byte[]> crls = new ArrayList<>();
    List<byte[]> certs = new ArrayList<>();

    while (certificate != null) {
        System.out.println(certificate.getSubjectX500Principal().getName());
        X509Certificate issuer = getIssuerCertificate(certificate);
        certs.add(certificate.getEncoded());
        byte[] ocspResponse = ocspClient.getEncoded(certificate, issuer, null);
        if (ocspResponse != null) {
            System.out.println("  with OCSP response");
            ocspResponses.add(ocspResponse);
            X509Certificate ocspSigner = getOcspSignerCertificate(ocspResponse);
            if (ocspSigner != null) {
                System.out.printf("  signed by %s\n", ocspSigner.getSubjectX500Principal().getName());
            }
            moreSignersAndCertificates.accept(getOcspSignatureKey(ocspResponse), ocspSigner);
        } else {
           Collection<byte[]> crl = crlClient.getEncoded(certificate, null);
           if (crl != null && !crl.isEmpty()) {
               System.out.printf("  with %s CRLs\n", crl.size());
               crls.addAll(crl);
               for (byte[] crlBytes : crl) {
                   moreSignersAndCertificates.accept(getCrlSignatureKey(crlBytes), null);
               }
           }
        }
        certificate = issuer;
    }

    vriAdder.accept(ocspResponses, crls, certs);
}

interface VriAdder {
    void accept(Collection<byte[]> ocsps, Collection<byte[]> crls, Collection<byte[]> certs);
}

( MakeLtvEnabled如makeLtvEnabledV2

用法示例

对于在签署PDF INPUT_PDF和结果输出流RESULT_STREAM您可以使用上面这样的方法:

PdfReader pdfReader = new PdfReader(INPUT_PDF);
PdfStamper pdfStamper = new PdfStamper(pdfReader, RESULT_STREAM, (char)0, true);

OcspClient ocsp = new OcspClientBouncyCastle();
CrlClient crl = new CrlClientOnline();
makeLtvEnabledV2(pdfStamper, ocsp, crl);

pdfStamper.close();

( MakeLtvEnabled测试方法testV2

限制

以上只是这些方法在一些简化的限制问题,特别是:

  • 签名时间戳被忽略,
  • 检索的CRL被认为是直接和完整,
  • 完整的证书链,假设使用AIA项是可构建的。

您可以相应地提高了代码,如果这些限制是不适合你接受的。

使用自己的实用工具类的方法

为了避免来修补iText的类,这种方法需要从上面的方法和所需的代码LtvVerification从iText的签名API类和合并所有到一个新的实用工具类。 这个类可以使LTV文档,而不需要修补的iText版本。

AdobeLtvEnabling

这个类上面的代码和一些结合LtvVerification代码转化为LTV使文档的实用工具类。

不幸的是在这里复制它推动邮件大小超出了堆栈溢出的30000个字符的限制。 您可以检索从GitHub代码,但:

AdobeLtvEnabling.java

用法示例

对于在签署PDF INPUT_PDF和结果输出流RESULT_STREAM您可以使用上面这样的类:

PdfReader pdfReader = new PdfReader(INPUT_PDF);
PdfStamper pdfStamper = new PdfStamper(pdfReader, RESULT_STREAM, (char)0, true);

AdobeLtvEnabling adobeLtvEnabling = new AdobeLtvEnabling(pdfStamper);
OcspClient ocsp = new OcspClientBouncyCastle();
CrlClient crl = new CrlClientOnline();
adobeLtvEnabling.enable(ocsp, crl);

pdfStamper.close();

( MakeLtvEnabled测试方法testV3

限制

由于这个工具类只是重新包装从第一种方法的代码,相同的限制适用。

幕后

正如开始所提到的,所有Adobe向我们讲述了“启用LTV”标识轮廓是

启用LTV意味着所有必要的信息,以验证被包含内的文件(减去根证书)

但他们没有告诉我们,他们希望如何准确的信息被嵌入在文件中。

起初我只是收集了所有这些信息,并确保它被添加到PDF( 证书 ,OCSPsCRL)的适用文件安全存储的字典。

但是,即使所有必要的信息,以验证在其中所包含的文件(减去根证书),安装Adobe Acrobat没有考虑文件“LTV启用”。

然后我LTV启用使用Adobe Acrobat文档和分析的差异。 事实证明,以下额外的数据也是必要的:

  1. 对于每个OCSP应答的签名的Adobe Acrobat需要各自VRI字典的存在。 在OP的例子PDF此VRI字典不需要包含任何证书,CRL或OCSP响应可言,但VRI字典需要存在。

    相反,这是没有必要的CRL的签名。 这看起来有点武断。

    根据该规范,既ISO 32000-2和ETSI EN 319 142-1,使用这些VRI字典纯粹是可选的 。 对于基线的PAdES签名甚至有反对使用VRI字典的建议!

  2. 使用Adobe Acrobat预计VRI字典每个包含一个TU条目记录了各自VRI字典的创建时间。 (可能是一个TS也会做,我没有测试过)。

    根据该规范,既ISO 32000-2和ETSI EN 319 142-1,使用这些TU条目纯粹是可选的 。 对于签名的PAdES甚至有反对使用TUTS条目的推荐!

因此,这并不奇怪,根据PDF规格不会导致被应用程序添加默认LTV信息“LTV启用”作为报道的Adobe Acrobat签名。

PS

很显然,我不得不添加信任在Adobe Acrobat一些证书,把它考虑了OP的文档上面的代码的结果“LTV启用”可言。 我选择了根证书“CA RAIZ NACIONAL - 哥斯达黎加V2”。



文章来源: how can I add PAdES-LTV using iText
标签: java itext pades