iText7 LtvVerification.addVerification无法启用LTV [英] iText7 LtvVerification.addVerification not enabling LTV

查看:454
本文介绍了iText7 LtvVerification.addVerification无法启用LTV的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们正在尝试启用已签名的签名LTV。我使用以下代码添加验证。当signature.isTsp()为false时,PDF表示 Signature未启用LTV ,但在另一种情况下(signature.isTsp()为true),它显示为有效。
当我们打开PDF并尝试通过右键单击签名手动添加验证信息时,它会启用LTV而不会出现任何问题。不知道我们在这里缺少什么。
任何输入都非常有用。

We are trying to make the signed signature LTV enabled. I am using the below code to add verification. When signature.isTsp() is false, the PDF says Signature is not LTV enabled, though in the other case (signature.isTsp() is true) it shows as valid. When we open the PDF and try to manually add verification info by right clicking on the signature it enables LTV without any issue. Not sure what we are missing here. Any input will be highly helpful.

// Adds LTV-enabled information to the PDF document.
private ByteArrayOutputStream addLtv(final IOcspClient ocspClient,
                                     final ByteArrayOutputStream docStream)
        throws IOException, GeneralSecurityException {
    final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    final InputStream signedStream = new ByteArrayInputStream(docStream.toByteArray());
    final PdfReader reader = new PdfReader(signedStream);
    final PdfDocument document =
            new PdfDocument(reader, new PdfWriter(outputStream), new StampingProperties().useAppendMode());
    final LtvVerification verification = new LtvVerification(document);
    final SignatureUtil signatureUtil = new SignatureUtil(document);

    final List<String> signatureNames = signatureUtil.getSignatureNames();
    final String sigName = signatureNames.get(signatureNames.size() - 1);
    final PdfPKCS7 signature = signatureUtil.verifySignature(sigName);

    final CrlClientOnline crl = new CrlClientOnline();
    if (!signature.isTsp()) {
        for (final String name: signatureNames) {
            addVerificationInfo(ocspClient, verification, crl, name);
        }
    } else {
        addVerificationInfo(ocspClient, verification, crl, sigName);
    }

    document.close();

    return outputStream;
}

private void addVerificationInfo(final IOcspClient ocspClient, final LtvVerification verification,
                                 final CrlClientOnline crl,
                                 final String name) throws IOException, GeneralSecurityException {
    verification.addVerification(
            name, ocspClient, crl,
            LtvVerification.CertificateOption.WHOLE_CHAIN,
            LtvVerification.Level.OCSP_CRL,
            LtvVerification.CertificateInclusion.NO);
}


推荐答案

你的代码的主要原因并不总是支持LTV的PDF不会添加与OCSP响应签名相关的验证信息。

The main reason why your code does not always LTV-enable PDFs is that it does not add validation information related to OCSP response signatures.

它不会添加CRL签名的验证信息,无论是。由于CRL通常由签署者证书的颁发者证书签名,并且由于该签发者证书的验证信息已经在主签名的上下文中添加,因此LTV启用通常不会因为缺少CRL签名验证信息而失败。因此,如果您只能使用CRL,那么您的代码可能确实已经启用了LTV启用PDF。

这个答案(特别是它的使用自己的实用程序类的方法一节)我创建了一个实用程序类 Adob​​eLtvEnabling 用于iText 5允许LTV启用PDF,主要使用iText 5本身的零碎。与您的代码相反,它确实为OCSP响应签名(以及CRL签名)添加了验证信息。

In the context of this answer (in particular its section "An approach using an own utility class") I created an utility class AdobeLtvEnabling for iText 5 allowing to LTV-enable PDFs, mostly using bits and pieces found in iText 5 itself. In contrast to your code it does add validation information for OCSP response signatures (and also for CRL signatures).

在这里,您可以找到该类的端口到iText 7。

Here you can find the port of that class to iText 7.

此实用程序类捆绑LTV所需的代码,以便在签名的PDF文档中启用签名。代码段主要取自现有的iText代码。此类未设计为派生自 LtvVerification 的主要原因是该类所需的变量和方法是 private 。由于该类最初是为iText 5编写的,因此可能会在其中找到一些iText-5-isms ...

This utility class bundles the code required for LTV enabling the signatures in a signed PDF document. The code pieces mostly have been taken from existing iText code. The main reason why this class has not been designed to derive from LtvVerification is that required variables and methods from that class are private. As the class originally has been written for iText 5, some iText-5-isms probably can be found in it...

public class AdobeLtvEnabling {
    /**
     * Use this constructor with a {@link PdfDocument} in append mode. Otherwise
     * the existing signatures will be damaged.
     */
    public AdobeLtvEnabling(PdfDocument pdfDocument) {
        this.pdfDocument = pdfDocument;
    }

    /**
     * Call this method to have LTV information added to the {@link PdfDocument}
     * given in the constructor.
     */
    public void enable(IOcspClient ocspClient, ICrlClient crlClient) throws OperatorException, GeneralSecurityException, IOException, StreamParsingException, OCSPException {
        SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);

        List<String> names = signatureUtil.getSignatureNames();
        for (String name : names) {
            PdfPKCS7 pdfPKCS7 = signatureUtil.verifySignature(name, BouncyCastleProvider.PROVIDER_NAME);
            PdfSignature sig = signatureUtil.getSignature(name);
            List<X509Certificate> certificatesToCheck = new ArrayList<>();
            certificatesToCheck.add(pdfPKCS7.getSigningCertificate());
            while (!certificatesToCheck.isEmpty()) {
                X509Certificate certificate = certificatesToCheck.remove(0);
                addLtvForChain(certificate, ocspClient, crlClient, getSignatureHashKey(sig));
            }
        }

        outputDss();
    }

    //
    // the actual LTV enabling methods
    //
    void addLtvForChain(X509Certificate certificate, IOcspClient ocspClient, ICrlClient crlClient, PdfName key) throws GeneralSecurityException, IOException, StreamParsingException, OperatorCreationException, OCSPException {
        ValidationData validationData = new ValidationData();

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

        validated.put(key, validationData);
    }

    void outputDss() throws IOException {
        PdfDictionary dss = new PdfDictionary();
        PdfDictionary vrim = new PdfDictionary();
        PdfArray ocsps = new PdfArray();
        PdfArray crls = new PdfArray();
        PdfArray certs = new PdfArray();

        PdfCatalog catalog = pdfDocument.getCatalog();
        if (pdfDocument.getPdfVersion().compareTo(PdfVersion.PDF_2_0) < 0) {
            catalog.addDeveloperExtension(PdfDeveloperExtension.ESIC_1_7_EXTENSIONLEVEL5);
            catalog.addDeveloperExtension(new PdfDeveloperExtension(PdfName.ADBE, new PdfName("1.7"), 8));
        }

        for (PdfName vkey : validated.keySet()) {
            PdfArray ocsp = new PdfArray();
            PdfArray crl = new PdfArray();
            PdfArray cert = new PdfArray();
            PdfDictionary vri = new PdfDictionary();
            for (byte[] b : validated.get(vkey).crls) {
                PdfStream ps = new PdfStream(b);
                ps.setCompressionLevel(CompressionConstants.DEFAULT_COMPRESSION);
                ps.makeIndirect(pdfDocument);
                crl.add(ps);
                crls.add(ps);
                crls.setModified();
            }
            for (byte[] b : validated.get(vkey).ocsps) {
                b = buildOCSPResponse(b);
                PdfStream ps = new PdfStream(b);
                ps.setCompressionLevel(CompressionConstants.DEFAULT_COMPRESSION);
                ps.makeIndirect(pdfDocument);
                ocsp.add(ps);
                ocsps.add(ps);
                ocsps.setModified();
            }
            for (byte[] b : validated.get(vkey).certs) {
                PdfStream ps = new PdfStream(b);
                ps.setCompressionLevel(CompressionConstants.DEFAULT_COMPRESSION);
                ps.makeIndirect(pdfDocument);
                cert.add(ps);
                certs.add(ps);
                certs.setModified();
            }
            if (ocsp.size() > 0) {
                ocsp.makeIndirect(pdfDocument);
                vri.put(PdfName.OCSP, ocsp);
            }
            if (crl.size() > 0) {
                crl.makeIndirect(pdfDocument);
                vri.put(PdfName.CRL, crl);
            }
            if (cert.size() > 0) {
                cert.makeIndirect(pdfDocument);
                vri.put(PdfName.Cert, cert);
            }
            vri.put(PdfName.TU, new PdfDate().getPdfObject());
            vri.makeIndirect(pdfDocument);
            vrim.put(vkey, vri);
        }
        vrim.makeIndirect(pdfDocument);
        vrim.setModified();
        dss.put(PdfName.VRI, vrim);
        if (ocsps.size() > 0) {
            ocsps.makeIndirect(pdfDocument);
            dss.put(PdfName.OCSPs, ocsps);
        }
        if (crls.size() > 0) {
            crls.makeIndirect(pdfDocument);
            dss.put(PdfName.CRLs, crls);
        }
        if (certs.size() > 0) {
            certs.makeIndirect(pdfDocument);
            dss.put(PdfName.Certs, certs);
        }

        dss.makeIndirect(pdfDocument);
        dss.setModified();
        catalog.put(PdfName.DSS, dss);
    }

    //
    // VRI signature hash key calculation
    //
    static PdfName getCrlHashKey(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(convertToHex(octetHash));
        return octetName;
    }

    static PdfName getOcspHashKey(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(convertToHex(octetHash));
        return octetName;
    }

    static PdfName getSignatureHashKey(PdfSignature sig) throws NoSuchAlgorithmException, IOException {
        PdfString contents = sig.getContents();
        byte[] bc = PdfEncodings.convertToBytes(contents.getValue(), null);
        if (PdfName.ETSI_RFC3161.equals(sig.getSubFilter())) {
            try (   ASN1InputStream din = new ASN1InputStream(new ByteArrayInputStream(bc)) ) {
                ASN1Primitive pkcs = din.readObject();
                bc = pkcs.getEncoded();
            }
        }
        byte[] bt = hashBytesSha1(bc);
        return new PdfName(convertToHex(bt));
    }

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

    static String convertToHex(byte[] bytes) {
        ByteBuffer buf = new ByteBuffer();
        for (byte b : bytes) {
            buf.appendHex(b);
        }
        return PdfEncodings.convertToString(buf.toByteArray(), null).toUpperCase();
    }

    //
    // OCSP response helpers
    //
    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 byte[] buildOCSPResponse(byte[] BasicOCSPResponse) throws IOException {
        DEROctetString doctet = new DEROctetString(BasicOCSPResponse);
        ASN1EncodableVector v2 = new ASN1EncodableVector();
        v2.add(OCSPObjectIdentifiers.id_pkix_ocsp_basic);
        v2.add(doctet);
        ASN1Enumerated den = new ASN1Enumerated(0);
        ASN1EncodableVector v3 = new ASN1EncodableVector();
        v3.add(den);
        v3.add(new DERTaggedObject(true, 0, new DERSequence(v2)));            
        DERSequence seq = new DERSequence(v3);
        return seq.getEncoded();
    }

    //
    // X509 certificate related helpers
    //
    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 PdfException(PdfException.InvalidHttpResponse1).setMessageParams(con.getResponseCode());
            }
            InputStream inp = (InputStream) con.getContent();
            X509CertParser parser = new X509CertParser();
            parser.engineInit(new ByteArrayInputStream(StreamUtil.inputStreamToArray(inp)));
            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");
    }

    //
    // inner class
    //
    static class ValidationData {
        final List<byte[]> crls = new ArrayList<byte[]>();
        final List<byte[]> ocsps = new ArrayList<byte[]>();
        final List<byte[]> certs = new ArrayList<byte[]>();
    }

    //
    // member variables
    //
    final PdfDocument pdfDocument;

    final Map<PdfName,ValidationData> validated = new HashMap<PdfName,ValidationData>();
}

Adob​​eLtvEnabling.java

您可以使用 Adob​​eLtvEnabling 这样的类:

try (   PdfReader pdfReader = new PdfReader(SOURCE);
        PdfWriter pdfWriter = new PdfWriter(TARGET);
        PdfDocument pdfDocument = new PdfDocument(pdfReader, pdfWriter,
                new StampingProperties().preserveEncryption().useAppendMode())) {
    AdobeLtvEnabling adobeLtvEnabling = new AdobeLtvEnabling(pdfDocument);
    IOcspClient ocsp = new OcspClientBouncyCastle(null);
    ICrlClient crl = new CrlClientOnline();
    adobeLtvEnabling.enable(ocsp, crl);
}

MakeLtvEnabled 测试 testLtvEnableSignWithoutLtv

因为这段代码基本上从引用的答案中移植了iText 5代码,它还继承了答案中列出的限制:

As this code essentially is ported from the iText 5 code from the referenced answer, it also inherits the limitations listed in that answer:

代码在一些简化限制下工作,特别是:

The code works under some simplifying restrictions, in particular:


  • 签名时间戳被忽略,

  • 检索到的CRL被认为是直接和完整的,

  • 假设完整的证书链可以使用AIA条目构建。

您可以相应地改进代码如果您无法接受这些限制。

You can improve the code accordingly if these restrictions are not acceptable for you.

这篇关于iText7 LtvVerification.addVerification无法启用LTV的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆