我正在尝试在一个已经签名的PDF文档中启用LTV,而不使用LTV格式。我在所有情况下都发现了相同的示例,如链接“如何为时间戳签名启用LTV”,iText LTV enabled-如何添加更多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);
}
我正在使用的版本:
正如这篇评论所显示的
我想要的是Adobe LTV使能
该任务与PAdES相关较少(即使使用了PAdES中引入的机制),但侧重于Adobe专有签名配置文件“启用LTV”的签名。
不幸的是,此专有签名配置文件没有正确指定。Adobe告诉我们的是
启用LTV意味着验证文件(减去根证书)所需的所有信息都包含在中。
(有关详细信息和背景,请阅读此答案)
因此,实现LTV启用的方式涉及一些尝试和错误的示例签名,我不能保证Adobe将在未来的Adobe Acrobat版本中接受此代码的输出为"LTV启用"。
此外,当前的iText 5签名API不足以完成这项任务,因为(事实证明)Adobe需要某些iText代码无法创建的可选结构(但请参见下面的PPS)。解决这个问题最简单的方法是从两个方面更新iText类LtvVerification
,因此我将在这里描述这种方法。或者可以使用Java反射或复制和调整相当多的代码;如果无法按如下所示更新iText,则必须选择一种这样的替代方法。
本节显示了代码添加和更改,通过这些代码可以启用LTV文档,如OP的示例PDFsign\u而不使用LTV。pdf
。
这是使用iText签名API中的LtvVerification
类的原始代码。不幸的是,必须向该类添加一个功能。
iText 5LtvVerification
类仅提供接受签名字段名的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 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);
}
(如在MakeLetvEn的)
它们还没有优化,当然可以使它们更具性能和优雅。
基于这些添加和帮助,可以使用此方法添加启用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);
}
说明:作为使能,使能V2
对于一个签名的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
)
上述方法仅在某些简化限制条件下有效,特别是:
如果您不能接受这些限制,您可以相应地改进代码。
为了避免修补iText类,这种方法从上面的方法中获取所需的代码,并从iText的签名API中获取LtvVerification
类,并将所有内容合并到一个新的实用程序类中。此类可以在不需要修补的iText版本的情况下启用LTV文档。
此类将上面的代码和一些LTV验证
代码组合成一个用于LTV启用文档的实用程序类。
不幸的是,在这里复制它会使消息大小超过堆栈溢出30000个字符的限制。但是,您可以从github检索代码:
爱慕nabling.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(证书、OCSP和CRL)的适用文档安全存储字典中。
但是,尽管验证文件(减去根证书)所需的所有信息都包含在其中,但Adobe AcROAT没有考虑文件“启用LTV”。
然后,我使用AdobeAcrobat启用了LTV,并分析了差异。事实证明,以下额外数据也是必要的(但请参见下面的PPS):
>
对于每个OCSP响应的签名,Adobe Acrobat要求存在相应的VRI字典。在OP的示例PDF中,这个VRI字典根本不需要包含任何证书、CRL或OCSP响应,但是VRI字典需要在那里。
相比之下,CRL的签名不需要这样做。这看起来有点武断。
根据ISO 32000-2和ETSI EN 319 142-1的规范,使用这些VRI字典是完全可选的。对于PADES基线签名,甚至建议不要使用VRI字典!
Adobe Acrobat希望每个VRI字典都包含一个TU条目,记录各自VRI字典的创建时间。(可能TS也可以,我还没有测试过)。
根据ISO 32000-2和ETSI EN 319 142-1规范,这些TU条目的使用完全是可选的。对于PAdES签名,甚至建议不要使用TU或TS条目!
因此,应用程序根据PDF规范添加的默认LTV信息不会导致Adobe Acrobat报告的“启用LTV”签名也就不足为奇了。
显然,我必须增加对Adobe AcROBAT中一些证书的信任,使其能够考虑上述代码对OP文档“启用LTV”的结果。我选择了根证书“CA RAIZ National-COSTA RICA v2”。
与此同时,在测试LTV启用状态时,Adobe Acrobat显然不再需要VRI字典(更不用说TU时间戳)来考虑DSS中的撤销信息,请参阅此答案的“DSS中的可选元素”部分。
因此,上述解决方案很可能可以稍微简化。
问题内容: 我试图在没有LTV格式的已签名PDF文档中启用LTV。在所有情况下,我都找到了相同的示例,如链接中所述。如何为时间戳签名启用LTV,启用iText LTV-如何添加更多CRL?,它定义了获得预期结果的过程。碰巧我没有工作,它没有给我任何错误,但是我没有添加LTV。 为什么在执行以下代码时不会给我任何错误,但是我不添加LTV的一些想法。 这是我尝试添加LTV的方法: 我正在使用的版本:
问题内容: 如何使用iText在每个页面上添加总页数? 问题答案: 使用伪页面计数将输出从a 处理为第一个。 从中创建一个,调用以获取实际的页数。 重新创建PDF输出,知道页数是多少,并相应地更改页脚。 这很麻烦,但是如果没有两遍方法,就没有简单的方法来知道页数。有关处理PDF的详细信息,请参见示例代码。
使用iTextSharp,您可以通过将事件附加到PDF来向PDF添加页眉/页脚,如本SO答案中所述:https://stackoverflow.com/a/19004392 我怎样才能用 iText 7 做同样的事情? 这个链接有Java代码示例,但看起来不像它使用的页面事件。
问题内容: 我正在尝试使用itext java api将anchor(命名为destinations)添加到pdf。但是它不起作用。当我单击文本时,什么也没有发生。这就是我在做什么。 我究竟做错了什么?。任何帮助 问题答案: 试试这个。它为我工作。并会做魔术。
问题内容: 在我的pdf文件中,我需要有多个页眉和页脚。在页眉中,我希望标题标题位于左侧,而某些文本位于中心。 同样,在页脚中,我需要在左侧打印公司名称,在中心打印页码,并在右侧打印有关表格内容的一些信息。 我看过很多文章,但是我没有正确的想法来创建它,有人请帮助我提供一些示例代码片段。 问题答案: 页眉和页脚应使用“页面事件”添加。如果您需要一些示例,只需在官方网站上查找关键字header /
我正在使用iText 5.3.5创建一个pdf文档。现在,我正试图在文档的每一页上得到一个矩形,但我不太确定如何做到这一点。我试着在代码的末尾添加这个(我是在网上找到的): 但这只是在最后一页添加了矩形,这有点意义,因为我没有在任何地方使用pgCnt。我如何指定我想要页号pgCnt上的矩形,以便我可以在每一页上添加矩形? 希望我能解释清楚。提前感谢你的帮助。:)