问题描述
该方法接收 pdf 文档作为应签名的字节数组、要签名的证书和 TSA 客户端,如果有错误,则返回签名文档作为字节数组或 null.现在它返回签名的 pdf 文档,但它没有启用 LTV.签名文档必须启用 LTV.如何使返回的文档启用 LTV?如有任何建议,我将不胜感激.
The method receives pdf document as byte array that should be signed, certificate to sign with and TSA client and it returns signed document as byte array or null if there was error. Now it returns signed pdf document but it isnt LTV enabled.Signed document must be LTV enabled. How do I make the document being returned LTV enabled? I'll be very grateful for any suggestions.
public byte[] Sign(byte[] document, X509Certificate2 certificate, ITSAClient tsaClient)
{
byte[] signedDocument = null;
IExternalSignature signature = new X509Certificate2Signature(certificate, "SHA-1");
Org.BouncyCastle.X509.X509CertificateParser cp = new Org.BouncyCastle.X509.X509CertificateParser();
Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[] { cp.ReadCertificate(certificate.RawData) };
PdfReader reader = new PdfReader(document);
MemoryStream ms = new MemoryStream();
PdfStamper st = PdfStamper.CreateSignature(reader, ms, ' ');
PdfSignatureAppearance sap = st.SignatureAppearance;
sap.CertificationLevel = PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED;
sap.SignatureCreator = "NAME";
sap.Reason = "REASON";
sap.Contact = "CONTACT";
sap.Location = "LOCATION";
sap.SignDate = DateTime.Now;
RectangleF rectangle = new RectangleF(400.98139f, 54.88828f, 530, 84.88828f);
sap.Layer2Font = iTextSharp.text.FontFactory.GetFont(BaseFont.TIMES_ROMAN, BaseFont.CP1257, 7f);
sap.Layer2Font.Color = iTextSharp.text.BaseColor.RED;
sap.Layer2Text = string.Format("Signed for testing: {0}", DateTime.Now.ToString("dd.MM.yyyy."));
sap.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION;
sap.SetVisibleSignature(new iTextSharp.text.Rectangle(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height), 1, null);
MakeSignature.SignDetached(sap, signature, chain, null, null, tsaClient, 0, CryptoStandard.CMS);
st.Close();
ms.Flush();
signedDocument = ms.ToArray();
ms.Close();
reader.Close();
return signedDocument;
}
推荐答案
通常,您不能期望签名创建步骤返回支持 LTV 的签名.
In general you cannot expect the signature creation step to return a LTV-enabled signature.
Leonard Rosenthol(Adobe 的主要 PDF 专家)在 iText 上评论了 2013 年初的邮件列表指出,虽然签名容器本身可能已经包含启用 LTV 的签名所需的所有信息,但这种情况非常罕见,而且并非总是可行.
Leonard Rosenthol (Adobe's prime PDF Guru) remarked on the iText mailing list in early 2013 that while it is possible to have the signature container itself already contain all the information required for a LTV enabled signature, this is very uncommon and not always possible.
(也有例外,例如 Swisscom 签名服务生成签名容器,其中包含启用 LTV 的集成 PDF 签名所需的所有额外信息.)
(There are exceptions, e.g. there was a Swisscom signing service generating signature containers with all the extra information required for a LTV enabled integrated PDF signature.)
因此,通常您必须在第二步中添加所有缺失的信息.
Thus, in general you'll have to add all the missing information in a second step.
另一方面,这样的第二步意味着这样的第二次通过可能会干扰CertificationLevel = CERTIFIED_NO_CHANGES_ALLOWED
的签名——当前的PDF规范要求即使对于这样的认证级别增量更新也是如果它们只包含签名验证信息,则允许,但我还没有看到 Adobe Reader 在这种情况下不会抱怨.因此,您可能需要放宽 LTV 启用的认证级别.
Such a second step, on the other hand, means that such a second pass may interfere with signing with CertificationLevel = CERTIFIED_NO_CHANGES_ALLOWED
- the current PDF specification requires that even for such a certification level incremental updates are allowed if they contain only signature validation information but I have not yet seen Adobe Reader not complain about in such a case. So you might have to loosen your certification level for LTV enabling.
对于执行此第二步的 iText 5/Java 和 iText 7/Java 帮助程序类,可以在 this answer (iText 5) 中找到 和 这个答案 (iText 7).
For iText 5 / Java and iText 7 / Java helper classes that execute this second step can be found in this answer (iText 5) and this answer (iText 7).
我已将 iText 5 的 Java 帮助程序类移植到 C#:
I have ported the Java helper class for iText 5 to C#:
using iTextSharp.text;
using iTextSharp.text.error_messages;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.security;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Ocsp;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Ocsp;
using Org.BouncyCastle.X509;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
[...]
class AdobeLtvEnabling
{
/**
* Use this constructor with a {@link PdfStamper} in append mode. Otherwise
* the existing signatures will be damaged.
*/
public AdobeLtvEnabling(PdfStamper pdfStamper)
{
this.pdfStamper = pdfStamper;
}
/**
* Call this method to have LTV information added to the {@link PdfStamper}
* given in the constructor.
*/
public void enable(IOcspClient ocspClient, ICrlClient crlClient)
{
AcroFields fields = pdfStamper.AcroFields;
bool encrypted = pdfStamper.Reader.IsEncrypted();
List<String> names = fields.GetSignatureNames();
foreach (String name in names)
{
PdfPKCS7 pdfPKCS7 = fields.VerifySignature(name);
PdfDictionary signatureDictionary = fields.GetSignatureDictionary(name);
X509Certificate certificate = pdfPKCS7.SigningCertificate;
addLtvForChain(certificate, ocspClient, crlClient, getSignatureHashKey(signatureDictionary, encrypted));
}
outputDss();
}
//
// the actual LTV enabling methods
//
void addLtvForChain(X509Certificate certificate, IOcspClient ocspClient, ICrlClient crlClient, PdfName key)
{
if (seenCertificates.Contains(certificate))
return;
seenCertificates.Add(certificate);
ValidationData validationData = new ValidationData();
while (certificate != null)
{
Console.WriteLine(certificate.SubjectDN);
X509Certificate issuer = getIssuerCertificate(certificate);
validationData.certs.Add(certificate.GetEncoded());
byte[] ocspResponse = ocspClient.GetEncoded(certificate, issuer, null);
if (ocspResponse != null)
{
Console.WriteLine(" with OCSP response");
validationData.ocsps.Add(ocspResponse);
X509Certificate ocspSigner = getOcspSignerCertificate(ocspResponse);
if (ocspSigner != null)
{
Console.WriteLine(" signed by {0}
", ocspSigner.SubjectDN);
}
addLtvForChain(ocspSigner, ocspClient, crlClient, getOcspHashKey(ocspResponse));
}
else
{
ICollection<byte[]> crl = crlClient.GetEncoded(certificate, null);
if (crl != null && crl.Count > 0)
{
Console.WriteLine(" with {0} CRLs
", crl.Count);
foreach (byte[] crlBytes in crl)
{
validationData.crls.Add(crlBytes);
addLtvForChain(null, ocspClient, crlClient, getCrlHashKey(crlBytes));
}
}
}
certificate = issuer;
}
validated[key] = validationData;
}
void outputDss()
{
PdfWriter writer = pdfStamper.Writer;
PdfReader reader = pdfStamper.Reader;
PdfDictionary dss = new PdfDictionary();
PdfDictionary vrim = new PdfDictionary();
PdfArray ocsps = new PdfArray();
PdfArray crls = new PdfArray();
PdfArray certs = new PdfArray();
writer.AddDeveloperExtension(PdfDeveloperExtension.ESIC_1_7_EXTENSIONLEVEL5);
writer.AddDeveloperExtension(new PdfDeveloperExtension(PdfName.ADBE, new PdfName("1.7"), 8));
PdfDictionary catalog = reader.Catalog;
pdfStamper.MarkUsed(catalog);
foreach (PdfName vkey in validated.Keys)
{
PdfArray ocsp = new PdfArray();
PdfArray crl = new PdfArray();
PdfArray cert = new PdfArray();
PdfDictionary vri = new PdfDictionary();
foreach (byte[] b in validated[vkey].crls)
{
PdfStream ps = new PdfStream(b);
ps.FlateCompress();
PdfIndirectReference iref = writer.AddToBody(ps, false).IndirectReference;
crl.Add(iref);
crls.Add(iref);
}
foreach (byte[] b in validated[vkey].ocsps)
{
PdfStream ps = new PdfStream(buildOCSPResponse(b));
ps.FlateCompress();
PdfIndirectReference iref = writer.AddToBody(ps, false).IndirectReference;
ocsp.Add(iref);
ocsps.Add(iref);
}
foreach (byte[] b in validated[vkey].certs)
{
PdfStream ps = new PdfStream(b);
ps.FlateCompress();
PdfIndirectReference iref = writer.AddToBody(ps, false).IndirectReference;
cert.Add(iref);
certs.Add(iref);
}
if (ocsp.Length > 0)
vri.Put(PdfName.OCSP, writer.AddToBody(ocsp, false).IndirectReference);
if (crl.Length > 0)
vri.Put(PdfName.CRL, writer.AddToBody(crl, false).IndirectReference);
if (cert.Length > 0)
vri.Put(PdfName.CERT, writer.AddToBody(cert, false).IndirectReference);
vri.Put(PdfName.TU, new PdfDate());
vrim.Put(vkey, writer.AddToBody(vri, false).IndirectReference);
}
dss.Put(PdfName.VRI, writer.AddToBody(vrim, false).IndirectReference);
if (ocsps.Length > 0)
dss.Put(PdfName.OCSPS, writer.AddToBody(ocsps, false).IndirectReference);
if (crls.Length > 0)
dss.Put(PdfName.CRLS, writer.AddToBody(crls, false).IndirectReference);
if (certs.Length > 0)
dss.Put(PdfName.CERTS, writer.AddToBody(certs, false).IndirectReference);
catalog.Put(PdfName.DSS, writer.AddToBody(dss, false).IndirectReference);
}
//
// VRI signature hash key calculation
//
static PdfName getCrlHashKey(byte[] crlBytes)
{
X509Crl crl = new X509Crl(CertificateList.GetInstance(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 PdfName getOcspHashKey(byte[] basicResponseBytes)
{
BasicOcspResponse basicResponse = BasicOcspResponse.GetInstance(Asn1Sequence.GetInstance(basicResponseBytes));
byte[] signatureBytes = basicResponse.Signature.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 getSignatureHashKey(PdfDictionary dic, bool encrypted)
{
PdfString contents = dic.GetAsString(PdfName.CONTENTS);
byte[] bc = contents.GetOriginalBytes();
if (PdfName.ETSI_RFC3161.Equals(PdfReader.GetPdfObject(dic.Get(PdfName.SUBFILTER))))
{
using (Asn1InputStream din = new Asn1InputStream(bc))
{
Asn1Object pkcs = din.ReadObject();
bc = pkcs.GetEncoded();
}
}
byte[] bt = hashBytesSha1(bc);
return new PdfName(Utilities.ConvertToHex(bt));
}
static byte[] hashBytesSha1(byte[] b)
{
SHA1 sha = new SHA1CryptoServiceProvider();
return sha.ComputeHash(b);
}
//
// OCSP response helpers
//
static X509Certificate getOcspSignerCertificate(byte[] basicResponseBytes)
{
BasicOcspResponse borRaw = BasicOcspResponse.GetInstance(Asn1Sequence.GetInstance(basicResponseBytes));
BasicOcspResp bor = new BasicOcspResp(borRaw);
foreach (X509Certificate x509Certificate in bor.GetCerts())
{
if (bor.Verify(x509Certificate.GetPublicKey()))
return x509Certificate;
}
return null;
}
static byte[] buildOCSPResponse(byte[] BasicOCSPResponse)
{
DerOctetString doctet = new DerOctetString(BasicOCSPResponse);
Asn1EncodableVector v2 = new Asn1EncodableVector();
v2.Add(OcspObjectIdentifiers.PkixOcspBasic);
v2.Add(doctet);
DerEnumerated den = new DerEnumerated(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)
{
String url = getCACURL(certificate);
if (url != null && url.Length > 0)
{
HttpWebRequest con = (HttpWebRequest)WebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)con.GetResponse();
if (response.StatusCode != HttpStatusCode.OK)
throw new IOException(MessageLocalization.GetComposedMessage("invalid.http.response.1", (int)response.StatusCode));
//Get Response
Stream inp = response.GetResponseStream();
byte[] buf = new byte[1024];
MemoryStream bout = new MemoryStream();
while (true)
{
int n = inp.Read(buf, 0, buf.Length);
if (n <= 0)
break;
bout.Write(buf, 0, n);
}
inp.Close();
var cert2 = new System.Security.Cryptography.X509Certificates.X509Certificate2(bout.ToArray());
return new X509Certificate(X509CertificateStructure.GetInstance(cert2.GetRawCertData()));
}
try
{
certificate.Verify(certificate.GetPublicKey());
return null;
}
catch (Exception e)
{
}
foreach (X509Certificate candidate in extraCertificates)
{
try
{
certificate.Verify(candidate.GetPublicKey());
return candidate;
}
catch (Exception e)
{
}
}
return null;
}
static String getCACURL(X509Certificate certificate)
{
try
{
Asn1Object obj = getExtensionValue(certificate, X509Extensions.AuthorityInfoAccess.Id);
if (obj == null)
{
return null;
}
Asn1Sequence AccessDescriptions = (Asn1Sequence)obj;
for (int i = 0; i < AccessDescriptions.Count; i++)
{
Asn1Sequence AccessDescription = (Asn1Sequence)AccessDescriptions[i];
if (AccessDescription.Count != 2)
{
continue;
}
else
{
if ((AccessDescription[0] is DerObjectIdentifier) && ((DerObjectIdentifier)AccessDescription[0]).Id.Equals("1.3.6.1.5.5.7.48.2"))
{
String AccessLocation = getStringFromGeneralName((Asn1Object)AccessDescription[1]);
return AccessLocation == null ? "" : AccessLocation;
}
}
}
}
catch
{
}
return null;
}
static Asn1Object getExtensionValue(X509Certificate certificate, String oid)
{
byte[] bytes = certificate.GetExtensionValue(new DerObjectIdentifier(oid)).GetDerEncoded();
if (bytes == null) {
return null;
}
Asn1InputStream aIn = new Asn1InputStream(new MemoryStream(bytes));
Asn1OctetString octs = (Asn1OctetString)aIn.ReadObject();
aIn = new Asn1InputStream(new MemoryStream(octs.GetOctets()));
return aIn.ReadObject();
}
private static String getStringFromGeneralName(Asn1Object names)
{
Asn1TaggedObject taggedObject = (Asn1TaggedObject) names;
return Encoding.GetEncoding(1252).GetString(Asn1OctetString.GetInstance(taggedObject, false).GetOctets());
}
//
// inner class
//
class ValidationData
{
public IList<byte[]> crls = new List<byte[]>();
public IList<byte[]> ocsps = new List<byte[]>();
public IList<byte[]> certs = new List<byte[]>();
}
//
// member variables
//
PdfStamper pdfStamper;
ISet<X509Certificate> seenCertificates = new HashSet<X509Certificate>();
IDictionary<PdfName, ValidationData> validated = new Dictionary<PdfName, ValidationData>();
public static List<X509Certificate> extraCertificates = new List<X509Certificate>();
}
你通常像这样使用那个类
You generally use that class like this
PdfReader reader = new PdfReader(signedDocument);
FileStream os = new FileStream(ENABLED_PDF, FileMode.Create);
PdfStamper pdfStamper = new PdfStamper(reader, os, (char)0, true);
AdobeLtvEnabling adobeLtvEnabling = new AdobeLtvEnabling(pdfStamper);
IOcspClient ocsp = new OcspClientBouncyCastle();
ICrlClient crl = new CrlClientOnline();
adobeLtvEnabling.enable(ocsp, crl);
pdfStamper.Close();
原来的Java类有这样的限制
The original Java class has the limitation that
假设可以使用 AIA 条目构建完整的证书链.
这意味着有问题的每个(非根)证书都包含一个用于下载其颁发者证书的 URL.
This means that every (non-root) certificate in question contains an URL for downloading its issuer certificate.
情况并非总是如此.为了解决这个限制,我添加了一个 public static List extraCertificates
,如果某些证书中没有颁发者证书 URL,则可以将额外的证书放入其中作为颁发者证书候选者进行测试.在调用 enable
之前,您必须将这些额外的证书添加为 BouncyCastle X509Certificate
对象.
This is not always the case. To get around that limitation here, I added a public static List extraCertificates
into which one can put additional certificates which are tested as issuer certificate candidates if there is no issuer certificate URL in some certificates. Before calling enable
you have to add those extra certificates as BouncyCastle X509Certificate
objects.
这篇关于我想用 ITextSharp 签署 pdf 文档并返回 ltv pdf 启用文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!