如何使用iText添加PAdES-LTV [英] how can I add PAdES-LTV using iText

查看:376
本文介绍了如何使用iText添加PAdES-LTV的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在没有LTV格式的已签名PDF文档中启用LTV。我在所有情况下都找到了相同的例子,如链接如何启用LTV以获取时间戳签名启用iText LTV - 如何添加更多CRL?
,它定义了获得预期结果的程序。碰巧我没有工作,它没有给我任何错误但是我没有添加LTV。



在执行以下操作时有些想法代码不会给我任何错误但是我不添加LTV。



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

  public void addLtv(String src,String dest,OcspClient ocsp,CrlClient crl,TSAClient tsa)
抛出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


解决方案

正如本评论所述


我想要的是Adobe LTV-启用


该任务与PAdES相关较少(即使使用了PAdES中引入的机制),但专注于Adobe专有签名配置文件,LTV已启用签名



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


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


(有关详细信息和背景,请参阅此回答



因此,实现 LTV启用示例签名的方法涉及一些试验和错误,以及我无法保证Adobe将接受此代码的输出为即将启用LTV。即使是现有的iText 5签名API也不够用。该任务的框,因为(事实证明)Adobe需要某些其他可选结构,iText代码不会创建。解决这个问题的最简单方法是在两个方面更新iText类 LtvVerification ,所以我将在这里描述。或者,可以使用Java反射或复制并调整相当多的代码;如果您无法更新iText,如下所示,您将不得不选择一种此类替代方法。



LTV启用签名PDF的签名



此部分显示了LTV启用文档的代码添加和更改,例如OP的示例PDF sign_without_LTV.pdf



使用iText的 LtvVerification 类的方法



这是原始代码利用iText签名API中的 LtvVerification 类。不幸的是,必须将该功能添加到该类中。



修补 LtvVerification



iText 5 LtvVerification 类仅提供接受签名字段名称的 addVerification 方法。对于未绑定到表单字段的签名,我们还需要这些方法的功能,例如,用于OCSP响应签名。为此,我添加了该方法的以下重载:

  public boolean addVerification(PdfName signatureHash,Collection< byte []> ocsps ,Collection< byte []> crls,Collection< byte []> certs)抛出IOException,GeneralSecurityException {
if(used)
抛出新的IllegalStateException(MessageLocalization.getComposedMessage(verify.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);
返回true;
}

此外,最终VRI词典中的a(根据规范可选)时间条目是必须的。因此,我在 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 ---添加行
vri.put(PdfName.TU,new PdfDate());
// ^ ---添加行
vrim.put(vkey,writer.addToBody(vri,false).getIndirectReference());
...



一些低级辅助方法



需要一些在安全原语上运行的辅助方法。这些方法大部分都是从现有的iText类中收集的(因为它们是私有的,因此无法使用)或从代码中派生出来:

  static X509Certificate getOc​​spSignerCertificate(byte [] basicResponseBytes)抛出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;
}

返回null;
}

static PdfName getOc​​spSignatureKey(byte [] basicResponseBytes)抛出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)抛出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)抛出IOException,StreamParsingException {
String url = getCACURL(certificate);
if(url!= null&& url.length()> 0){
HttpURLConnection con =(HttpURLConnection)new URL(url).openConnection();
if(con.getResponseCode()/ 100!= 2){
抛出新的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();
}
返回null;
}

static String getCACURL(X509Certificate certificate){
ASN1Primitive obj;
try {
obj = getExtensionValue(certificate,Extension.authorityInfoAccess.getId());
if(obj == null){
返回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 {
返回AccessLocation;
}
}
}
}
} catch(IOException e){
返回null;
}
返回null;
}

static ASN1Primitive getExtensionValue(X509Certificate certificate,String oid)抛出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.getOc​​tets()));
返回aIn.readObject();
}

static String getStringFromGeneralName(ASN1Primitive names)抛出IOException {
ASN1TaggedObject taggedObject =(ASN1TaggedObject)name;
返回new String(ASN1OctetString.getInstance(taggedObject,false).getOc​​tets(),ISO-8859-1);
}

static byte [] hashBytesSha1(byte [] b)抛出NoSuchAlgorithmException {
MessageDigest sh = MessageDigest.getInstance(SHA1);
返回sh.digest(b);
}

(如 MakeLtvEnabled



它们尚未优化,当然可以使它们更高效,更优雅。



添加LTV信息



根据这些添加和帮助,可以使用此方法添加启用LTV的签名所需的LTV信息 makeLtvEnabled

  public void makeLtvEnabled(PdfStamper stp,OcspClient ocspClient,CrlClient crlClient)抛出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);
列表< 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();
X509证书证书= 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证书,OcspClient ocspClient,CrlClient crlClient,VriAdder vriAdder,
BiConsumer< PdfName,X509Certificate> moreSignersAndCertificates)抛出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());
X509证书颁发者= getIssuerCertificate(证书);
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 = getOc​​spSignerCertificate(ocspResponse);
if(ocspSigner!= null){
System.out.printf(由%s \ n签名,ocspSigner.getSubjectX500Principal()。getName());
}
moreSignersAndCertificates.accept(getOc​​spSignatureKey(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类,此方法从iText的签名API获取上述方法中的所需代码和 LtvVerification 类并合并所有int o一个新的实用工具类。此类可以LTV启用文档而无需修补的iText版本。



Adob​​eLtvEnabling



此类将上面的代码和一些 LtvVerification 代码组合到LTV启用文档的实用程序类中。



不幸的是,在此处复制它会将消息大小推到超出堆栈溢出的30000字符限制。你可以从github检索代码,但是:


Adob​​eLtvEnabling.java




使用示例



对于 INPUT_PDF 的签名PDF和a结果输出流 RESULT_STREAM 你可以像这样使用上面的类:

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

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

pdfStamper.close();

MakeLtvEnabled 测试方法 testV3



限制



由于此实用程序类仅重新打包代码从第一种方法来看,同样的限制适用。



幕后花絮



如开头所述,所有Adobe告诉我们LTV已启用签名配置文件是


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


中但它们并没有告诉我们他们对嵌入信息的准确程度在文件中。



起初我只收集了所有这些信息,并确保将其添加到适用的文档安全商店词典中PDF版本( Certs OCSPs CRL )。



但是即使验证文件所需的所有信息(减去根证书)都包含在中,Adobe Acrobat也不会将文件视为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 BASELINE签名,甚至会使用 VRI 词典反对建议


  2. Adob​​e Acrobat期望每个 VRI 词典都包含 TU 条目,记录相应 VRI 词典的创建时间。 (可能 TS 也会这样做,我没有测试过。)



    根据规范,ISO 32000-2和ETSI EN 319 142-1,使用这些 TU 条目纯粹是可选。对于PAdES签名,甚至会使用 TU TS 条目进行推荐!


因此,默认情况下,应用程序根据PDF规范添加的LTV信息不会导致Adobe Acrobat报告的LTV启用签名,这一点也就不足为奇了。



PS



显然,我必须为Adobe Acrobat中的某些证书​​添加信任才能让它考虑上述结果OP的文档LTV已启用的代码。我选择了根证书CA RAIZ NACIONAL - COSTA RICA v2。


I am trying to enable LTV in an already signed PDF document without LTV format. I have found the same example in all cases as described in the links How to enable LTV for a timestamp signature, iText LTV enabled - how to add more CRLs? , which, defines what is the procedure to obtain the expected result. It happens that I'm not working, it does not give me any error but I do not add the LTV.

Some idea of why at the time of executing the following code does not give me any error but nevertheless I do not add the LTV.

This is the method with which I am trying to add the 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);
}

versions that I am working with:

  • itext: 5.5.11
  • java: 8

解决方案

As it turned out in this comment

i want is Adobe LTV-enable

the task is less PAdES related (even though mechanisms introduced in PAdES are used) but focused on an Adobe proprietary signature profile, "LTV enabled" signatures.

Unfortunately, this proprietary signature profile is not properly specified. All Adobe tells us is

LTV enabled means that all information necessary to validate the file (minus root certs) is contained within.

(for details and backgrounds read this answer)

Thus, implementing a way to LTV enable the example signature involved some trial and error, and I cannot guarantee Adobe will accept the outputs of this code as "LTV enabled" in Adobe Acrobat versions to come.

Furthermore, the current iText 5 signature APIs do not suffice out of the box for the task because (as it turned out) Adobe requires certain otherwise optional structures which the iText code does not create. The most simple way to fix this was to update the iText class LtvVerification in two aspects, so I'll describe that way here. Alternatively one could have used Java reflection or copied and tweaked quite a bit of code; if you cannot update iText as shown below, you'll have to chose one such alternative approach.

LTV enabling the signatures of a signed PDF

This section shows the code additions and changes with which one can LTV enable documents like the OP's example PDF sign_without_LTV.pdf.

An approach using iText's LtvVerification class

This is the original code which makes use of the LtvVerification class from iText's signature API. Unfortunately for this a functionality has to be added to that class.

Patching LtvVerification

The iText 5 LtvVerification class only offers addVerification methods accepting a signature field name. We need the functionality of these methods also for signatures not bound to a form field, e.g. for OCSP response signatures. For this I added the following overload of that method:

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

Furthermore, a (per the specification optional) time entry in the final VRI dictionaries is required. Thus, I added the a line in the outputDss method as follows:

...
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());
...

Some low level helper methods

Some helper methods operating on security primitives is required. These methods mostly have been collected from existing iText classes (which could not be used as is because they are private) or derived from code there:

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);
}

(as in MakeLtvEnabled)

They aren't optimized yet, one certainly can make them more performant and more elegant.

Adding LTV information

Based on these additions and helpers one can add the LTV information required for LTV enabled signatures with this method 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 as makeLtvEnabledV2)

Example usage

For a signed PDF at INPUT_PDF and a result output stream RESULT_STREAM you can use the method above like this:

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 test method testV2)

Limitations

The methods above only work under some simplifying restrictions, in particular:

  • signature time stamps are ignored,
  • retrieved CRLs are assumed to be direct and complete,
  • the complete certificate chains are assumed to be buildable using AIA entries.

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

An approach using an own utility class

To avoid having to patch an iText class, this approach takes the required code from the methods above and the LtvVerification class from iText's signature API and merges all into a new utility class. This class can LTV enable a document without requiring a patched iText version.

The AdobeLtvEnabling class

This class combines the code above and some LtvVerification code into a utility class for LTV enabling documents.

Unfortunately copying it here pushes the message size beyond the 30000 character limit of stack overflow. You can retrieve the code from github, though:

AdobeLtvEnabling.java

Example usage

For a signed PDF at INPUT_PDF and a result output stream RESULT_STREAM you can use the class above like this:

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 test method testV3)

Limitations

As this utility class merely repackages the code from the first approach, the same limitations apply.

Behind the scenes

As mentioned at the start, all Adobe tells us about the "LTV enabled" signature profile is that

LTV enabled means that all information necessary to validate the file (minus root certs) is contained within

but they don't tell us how exactly they expect the information to be embedded within the file.

At first I merely collected all this information and made sure it was added to the applicable Document Security Store dictionaries of the PDF (Certs, OCSPs, and CRLs).

But even though all information necessary to validate the file (minus root certs) was contained within, Adobe Acrobat did not consider the file "LTV enabled".

I then LTV enabled the document using Adobe Acrobat and analyzed the differences. As it turned out, the following extra data also were necessary:

  1. For the signature of each OCSP response Adobe Acrobat requires the presence of a respective VRI dictionary. In the example PDF of the OP this VRI dictionary does not need to contain any certificates, CRLs, or OCSP responses at all, but the VRI dictionary needs to be there.

    In contrast this is not necessary for the signatures of CRLs. This looks a bit arbitrary.

    According to the specifications, both ISO 32000-2 and ETSI EN 319 142-1, the use of these VRI dictionaries is purely optional. For PAdES BASELINE signatures there even is a recommendation against using VRI dictionaries!

  2. Adobe Acrobat expects the VRI dictionaries to each contain a TU entry documenting the creation time of the respective VRI dictionary. (Probably a TS would also do, I have not tested that).

    According to the specifications, both ISO 32000-2 and ETSI EN 319 142-1, the use of these TU entries is purely optional. For PAdES signatures there even is a recommendation against using TU or TS entries!

Thus, it is not surprising that by default LTV information added by applications according to the PDF specifications do not result in "LTV enabled" signatures as reported by Adobe Acrobat.

PS

Obviously I had to add trust for some certificate in Adobe Acrobat to get it to consider the result of the above code for the OP's document "LTV enabled" at all. I chose the root certificate "CA RAIZ NACIONAL - COSTA RICA v2".

这篇关于如何使用iText添加PAdES-LTV的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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