栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

如何使用iText添加PAdES-LTV

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

如何使用iText添加PAdES-LTV

事实证明,

我要启用Adobe LTV

该任务与PAdES的相关性较小(即使使用了PAdES中引入的机制),但重点是Adobe专有签名配置文件 “启用LTV”签名

不幸的是,此专有签名配置文件未正确指定。Adobe告诉我们的只是

启用LTV意味着其中包含验证文件所需的所有信息(减去根证书)。

因此,实现 LTV 方式 将使 示例签名涉及一些试验和错误,并且我不能保证Adobe会接受此代码的输出作为Adobe
Acrobat版本中的“启用LTV”。

此外,当前的iText
5签名API不足以完成任务,因为(事实证明)Adobe需要某些其他可选结构,但iText代码不会创建这些结构(但请参见下面的PPS)。解决此问题的最简单方法是

LtvVerification
从两个方面更新iText类,因此我将在此处进行描述。或者,可以使用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词典中(根据规范可选)需要输入时间(但请参见下面的PPS)。因此,我在

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 linevri.put(PdfName.TU, new PdfDate());// ^--- added linevrim.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.getEnpred();    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.getEnpred();    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.getEnpred());        byte[] ocspResponse = ocspClient.getEnpred(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 %sn", ocspSigner.getSubjectX500Principal().getName()); } moreSignersAndCertificates.accept(getOcspSignatureKey(ocspResponse), ocspSigner);        } else {Collection<byte[]> crl = crlClient.getEnpred(certificate, null);if (crl != null && !crl.isEmpty()) {    System.out.printf("  with %s CRLsn", 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的适用的“文档安全性商店”字典( CertsOCSPCRL
)中。

但是,即使其中 包含验证文件所需的所有信息(减去根证书) ,Adobe Acrobat也不认为文件“已启用LTV”。

然后,我LTV使用Adobe Acrobat启用了文档并分析了差异。事实证明,以下额外数据也是必要的(但请参见下面的PPS):

  1. 对于每个OCSP响应的签名,Adobe Acrobat要求存在相应的 VRI 词典。在OP的示例PDF中,此VRI词典根本不需要包含任何证书,CRL或OCSP响应,但是 VRI 词典必须在此。

相反,对于CRL的签名而言,这 不是 必需的。这看起来有些武断。

根据ISO 32000-2和ETSI EN 319 142-1的规范,这些 VRI 词典的使用完全是 可选的 。对于PAdES
baseLINE签名,甚至建议 不要 使用 VRI 词典!

  1. Adobe Acrobat希望 VRI 词典每个都包含一个 TU 条目,该条目记录相应 VRI 词典的创建时间。(也许 TS 也可以,我还没有测试过)。

根据ISO 32000-2和ETSI EN 319 142-1的规范,这些 TU 条目的使用纯粹是 可选的 。对于PAdES签名,甚至建议
不要 使用 TUTS 条目!

因此,毫不奇怪,默认情况下,由应用程序根据PDF规范添加的LTV信息不会导致Adobe Acrobat报告的“启用LTV”签名。

聚苯乙烯

显然,我不得不添加对Adobe Acrobat中某些证书的信任,以使其完全考虑上述OP文档“ LTV enabled”文档的代码结果。我选择了根证书“ CARAIZ NACIONAL-COSTA RICA v2”。

PPS(2020-03-02)

显然,与此同时,在测试LTV启用状态时,Adobe Acrobat不再需要 VRI 词典(更不用说 TU
时间戳)来考虑DSS中的吊销信息了

因此,很可能可以简化上述解决方案。



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/495632.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号