iText 7 - 签署 PDF 文档时出现问题
iText 7 - Problem when signing PDF document
我已尝试通过 USB 令牌使用 CRL 分发点对 PDF 文件进行签名,如下所示:
URI = ldap:///CN=CA2,CN=www,CN=CDP,CN=Public%20Key%20Services,CN=Services,CN=Configuration,DC=cavn,DC=vn?certificateRevocationList?base?objectClass=cRLDistributionPoint
URI = http://cavn.vn/new/CA2.crl
URI = http://www.cavn.vn/new/CA2.crl
第一个 URI 解析失败
第一个问题是:为什么 iText7 在第一个 URI 失败时不尝试读取下一个 URI?
public static String GetCRLURL(X509Certificate certificate) {
Asn1Object obj;
try {
obj = GetExtensionValue(certificate, X509Extensions.CrlDistributionPoints.Id);
}
catch (System.IO.IOException) {
obj = (Asn1Object)null;
}
if (obj == null) {
return null;
}
CrlDistPoint dist = CrlDistPoint.GetInstance(obj);
DistributionPoint[] dists = dist.GetDistributionPoints();
foreach (DistributionPoint p in dists) {
DistributionPointName distributionPointName = p.DistributionPointName;
if (DistributionPointName.FullName != distributionPointName.PointType) {
continue;
}
GeneralNames generalNames = (GeneralNames)distributionPointName.Name;
GeneralName[] names = generalNames.GetNames();
foreach (GeneralName name in names) {
if (name.TagNo != GeneralName.UniformResourceIdentifier) {
continue;
}
DerIA5String derStr = DerIA5String.GetInstance((Asn1TaggedObject)name.ToAsn1Object(), false);
**//Here iText7 always return the first URI. Why?**
return derStr.GetString();
}
}
return null;
}
我尝试修改上面的代码以读取下一个 URI,当我使用 CRLVerifier
验证它时,如下代码
CRLVerifier crlVerifier = new CRLVerifier(null, null);
IList<VerificationOK> verificationOks = crlVerifier.Verify(signCert, issuerCert, date);
验证成功
但是当我用下面的代码签署文档时:
void Sign(string srcFile, SysCert.X509Certificate2 signerCert, BCCert.X509Certificate[] arrBCChain, string tsaURL, string tsaUserName, string tsaPassword)
{
PdfReader pdfReader = null;
PdfDocument pdfDocument = null;
FileStream outfileStream = null;
string tempFileName = string.Empty;
try
{
pdfReader = new PdfReader(srcFile);
pdfDocument = new PdfDocument(pdfReader);
PdfPage lastPage = pdfDocument.GetLastPage();
int pageNumber = pdfDocument.GetPageNumber(lastPage);
iText.Kernel.Geom.Rectangle mediaBox = lastPage.GetMediaBox();
pdfDocument.Close();
pdfReader.Close();
pdfReader = new PdfReader(srcFile);
tempFileName = Path.Combine(Path.GetDirectoryName(srcFile), $"{Path.GetFileNameWithoutExtension(srcFile)}_Signed_{DateTime.Now.ToString("yyyyMMdd_HHmmss")}.pdf");
outfileStream = new FileStream(tempFileName, FileMode.Create);
StampingProperties stampingProperties = new StampingProperties();
stampingProperties.UseAppendMode();
PdfSigner pdfSigner = new PdfSigner(pdfReader, outfileStream, stampingProperties);
PdfSignatureAppearance signatureAppearance = pdfSigner.GetSignatureAppearance();
pdfDocument = pdfSigner.GetDocument();
SignatureUtil signUtil = new SignatureUtil(pdfDocument);
IList<String> sigNames = signUtil.GetSignatureNames();
string lastSignatureName = sigNames.LastOrDefault();
PdfAcroForm acroForm = PdfAcroForm.GetAcroForm(pdfDocument, false);
iText.Kernel.Geom.Rectangle rect = null;
if (acroForm != null && !string.IsNullOrEmpty(lastSignatureName))
{
PdfFormField pdfFormField = acroForm.GetField(lastSignatureName);
PdfArray pdfArray = pdfFormField.GetWidgets().First().GetRectangle();
rect = new iText.Kernel.Geom.Rectangle(pdfArray.ToFloatArray()[0] + 150 + 5, mediaBox.GetY(), 150, 10);
}
else
{
rect = new iText.Kernel.Geom.Rectangle(mediaBox.GetX(), mediaBox.GetY(), 150, 10);
}
string signerName = arrBCChain[0].SubjectDN.GetValueList(X509Name.CN)[0].ToString();
signatureAppearance.SetRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION);
signatureAppearance.SetLayer2Text("Signed by " + signerName);
PdfFont font = PdfFontFactory.CreateFont(Path.Combine(Application.StartupPath, "VietnameseFonts", "vuTimesBold.ttf"), PdfEncodings.IDENTITY_H, true);
signatureAppearance.SetLayer2Font(font);
signatureAppearance.SetLayer2FontSize(5);
signatureAppearance.SetLayer2FontColor(ColorConstants.BLACK);
signatureAppearance.SetPageRect(rect);
signatureAppearance.SetPageNumber(pageNumber);
pdfSigner.SetFieldName(TwofishCryptEngine.Encrypt("MZH_METIT_Signature_" + DateTime.Now.ToString("yyyyMMdd_HHmmss")));
IExternalSignature externalSignature = new AsymmetricAlgorithmSignature((RSACryptoServiceProvider)signerCert.PrivateKey, DigestAlgorithms.SHA256);
IOcspClient ocspClient = new OcspClientBouncyCastle(null);
ICrlClient crlClient = new CrlClientOnline(arrBCChain);
List<ICrlClient> lstCRL = new List<ICrlClient>() { crlClient };
ITSAClient tsaClient = null;
if (string.IsNullOrWhiteSpace(tsaUserName))
tsaClient = new TSAClientBouncyCastle(tsaURL);
else
tsaClient = new TSAClientBouncyCastle(tsaURL, tsaUserName, tsaPassword);
pdfSigner.SignDetached(externalSignature, arrBCChain, lstCRL, ocspClient, tsaClient, 0, PdfSigner.CryptoStandard.CMS);
pdfReader.Close();
outfileStream.Close();
if (File.Exists(srcFile))
File.Delete(srcFile);
if (File.Exists(tempFileName))
File.Move(tempFileName, srcFile);
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (pdfDocument != null && !pdfDocument.IsClosed())
pdfDocument.Close();
if (pdfReader != null)
pdfReader.Close();
if (outfileStream != null)
outfileStream.Close();
if (File.Exists(tempFileName))
File.Delete(tempFileName);
}
}
在 BouncyCastle.Crypto.dll 中出现异常“遇到未知标记 13”失败。
但是当我尝试使用相同的 USB 令牌通过 Adobe Reader 签署文档时,它成功了。
请告诉我为什么以及如何修复它。谢谢
更新 1
修改代码以读取下一个 URI
public static String GetCRLURL(X509Certificate certificate)
{
Asn1Object obj;
try
{
obj = GetExtensionValue(certificate, X509Extensions.CrlDistributionPoints.Id);
}
catch (System.IO.IOException)
{
obj = (Asn1Object)null;
}
if (obj == null)
{
return null;
}
CrlDistPoint dist = CrlDistPoint.GetInstance(obj);
DistributionPoint[] dists = dist.GetDistributionPoints();
foreach (DistributionPoint p in dists)
{
DistributionPointName distributionPointName = p.DistributionPointName;
if (DistributionPointName.FullName != distributionPointName.PointType)
{
continue;
}
GeneralNames generalNames = (GeneralNames)distributionPointName.Name;
GeneralName[] names = generalNames.GetNames();
foreach (GeneralName name in names)
{
if (name.TagNo != GeneralName.UniformResourceIdentifier)
{
continue;
}
//Hack by AnND: 07 - 12 - 2020: try to parse URL until get valid URL
try
{
DerIA5String derStr = DerIA5String.GetInstance((Asn1TaggedObject)name.ToAsn1Object(), false);
string url = derStr.GetString();
X509Crl x509Crl = GetCRL(url);
return url;
}
catch
{
}
//End hack
}
}
return null;
}
完整堆栈跟踪
> BouncyCastle.Crypto.dll!Org.BouncyCastle.Asn1.Asn1InputStream.BuildObject(int tag, int tagNo, int length) (IL=0x0087, Native=0x0B31AD68+0x1D6)
BouncyCastle.Crypto.dll!Org.BouncyCastle.Asn1.Asn1InputStream.ReadObject() (IL≈0x00FB, Native=0x0B31A5F8+0x392)
itext.sign.dll!iText.Signatures.PdfPKCS7.GetAuthenticatedAttributeSet(byte[] secondDigest, System.Collections.Generic.ICollection<byte[]> ocsp, System.Collections.Generic.ICollection<byte[]> crlBytes, iText.Signatures.PdfSigner.CryptoStandard sigtype) (IL≈0x0169, Native=0x14768008+0x64C)
itext.sign.dll!iText.Signatures.PdfPKCS7.GetAuthenticatedAttributeBytes(byte[] secondDigest, iText.Signatures.PdfSigner.CryptoStandard sigtype, System.Collections.Generic.ICollection<byte[]> ocsp, System.Collections.Generic.ICollection<byte[]> crlBytes) (IL≈0x0000, Native=0x14767F60+0x46)
itext.sign.dll!iText.Signatures.PdfSigner.SignDetached(iText.Signatures.IExternalSignature externalSignature, Org.BouncyCastle.X509.X509Certificate[] chain, System.Collections.Generic.ICollection<iText.Signatures.ICrlClient> crlList, iText.Signatures.IOcspClient ocspClient, iText.Signatures.ITSAClient tsaClient, int estimatedSize, iText.Signatures.PdfSigner.CryptoStandard sigtype, Org.BouncyCastle.Asn1.Esf.SignaturePolicyIdentifier signaturePolicy) (IL≈0x01F5, Native=0x14674510+0x70A)
itext.sign.dll!iText.Signatures.PdfSigner.SignDetached(iText.Signatures.IExternalSignature externalSignature, Org.BouncyCastle.X509.X509Certificate[] chain, System.Collections.Generic.ICollection<iText.Signatures.ICrlClient> crlList, iText.Signatures.IOcspClient ocspClient, iText.Signatures.ITSAClient tsaClient, int estimatedSize, iText.Signatures.PdfSigner.CryptoStandard sigtype) (IL=0x0012, Native=0x14673F88+0x3C)
METIT.exe!METIT.SignDocument.Sign(string srcFile, System.Security.Cryptography.X509Certificates.X509Certificate2 signerCert, Org.BouncyCastle.X509.X509Certificate[] arrBCChain, string tsaURL, string tsaUserName, string tsaPassword) (IL=0x0290, Native=0x0D203A40+0x920)
METIT.exe!METIT.SignDocument.btnSign_Click(object sender, System.EventArgs e) (IL=0x0793, Native=0x0B2B7838+0x166F)
System.Windows.Forms.dll!System.Windows.Forms.Control.OnClick(System.EventArgs e) (IL=0x0021, Native=0x0B8E47F0+0x87)
System.Windows.Forms.dll!System.Windows.Forms.Button.OnClick(System.EventArgs e) (IL=0x0035, Native=0x0B8E4680+0x78)
System.Windows.Forms.dll!System.Windows.Forms.Button.OnMouseUp(System.Windows.Forms.MouseEventArgs mevent) (IL=0x007E, Native=0x0B8E4190+0x177)
System.Windows.Forms.dll!System.Windows.Forms.Control.WmMouseUp(ref System.Windows.Forms.Message m, System.Windows.Forms.MouseButtons button, int clicks) (IL=0x0189, Native=0x0B8E34E0+0x59C)
System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m) (IL=0x04AC, Native=0x07932360+0x7B2)
System.Windows.Forms.dll!System.Windows.Forms.ButtonBase.WndProc(ref System.Windows.Forms.Message m) (IL=0x00DB, Native=0x0B3F9698+0x1E8)
System.Windows.Forms.dll!System.Windows.Forms.Button.WndProc(ref System.Windows.Forms.Message m) (IL=0x0044, Native=0x0B3F95D0+0xB0)
System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref System.Windows.Forms.Message m) (IL=0x000C, Native=0x07931F18+0x2E)
System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m) (IL=0x009A, Native=0x07931DA8+0x123)
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.Callback(System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam) (IL=0x002D, Native=0x07931B90+0xA1)
[Managed to Native Transition]
System.Windows.Forms.dll!System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(System.IntPtr dwComponentID, int reason, int pvLoopData) (IL≈0x0177, Native=0x0B29CDA8+0x49D)
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(int reason, System.Windows.Forms.ApplicationContext context) (IL≈0x01FA, Native=0x094BADD8+0x550)
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoop(int reason, System.Windows.Forms.ApplicationContext context) (IL=0x001C, Native=0x094BA928+0x5D)
System.Windows.Forms.dll!System.Windows.Forms.Application.Run(System.Windows.Forms.Form mainForm) (IL=0x0011, Native=0x0B3F9238+0x4F)
METIT.exe!METIT.METITControlForm.Main() (IL=0x0114, Native=0x056CD658+0x29B)
Debuging image
局部变量的值
+ this {Org.BouncyCastle.Asn1.Asn1InputStream} Org.BouncyCastle.Asn1.Asn1InputStream
tag 0x0000002D int
tagNo 0x0000000D int
length 0x0000002D int
isConstructed true bool
+ defIn {Org.BouncyCastle.Asn1.DefiniteLengthInputStream} Org.BouncyCastle.Asn1.DefiniteLengthInputStream
问题出在 URI 中:http://cavn.vn/new/CA2.crl
使用此代码从该 URI 获取字节数组后
IList<byte[]> ar = new List<byte[]>();
foreach (Uri urlt in urllist) {
try {
LOGGER.Info("Checking CRL: " + urlt);
Stream inp = SignUtils.GetHttpResponse(urlt);
byte[] buf = new byte[1024];
MemoryStream bout = new MemoryStream();
while (true) {
int n = inp.JRead(buf, 0, buf.Length);
if (n <= 0) {
break;
}
bout.Write(buf, 0, n);
}
inp.Dispose();
ar.Add(bout.ToArray());
LOGGER.Info("Added CRL found at: " + urlt);
}
catch (Exception e) {
LOGGER.Info("Skipped CRL: " + e.Message + " for " + urlt);
}
}
还是可以的。但是当它遇到 BouncyCastle.Crypto.dll!Org.BouncyCastle.Asn1.Asn1InputStream.BuildObject(int tag, int tagNo, int length)
=> 在这里失败,因为错误的 tagNo.
更新 2
我对 CrlClientOnline
的新实现
public class CustomCrlClientOnline : CrlClientOnline
{
public CustomCrlClientOnline(X509Certificate[] chain) : base(chain)
{
}
public override ICollection<byte[]> GetEncoded(X509Certificate checkCert, string url)
{
ICollection<byte[]> result = new List<byte[]>();
ICollection<byte[]> crls = base.GetEncoded(checkCert, url);
foreach (byte[] crl in crls)
{
string crlData = Encoding.UTF8.GetString(crl);
if (crlData.StartsWith("-----BEGIN"))
{
string[] array2 = Regex.Split(crlData, "\r\n|\r|\n");
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < array2.Length; i++)
{
if (!array2[i].StartsWith("-----BEGIN") && !array2[i].StartsWith("-----END"))
{
stringBuilder.Append(array2[i] + "\r\n");
}
}
string text = stringBuilder.ToString().Trim(new char[]
{
'\r',
'\n'
});
array2 = Regex.Split(text, "\r\n|\r|\n");
result.Add(Encoding.UTF8.GetBytes(text));
}
else
{
result.Add(crl);
}
}
return result;
}
}
当我打开 PDF 文件时,在吊销选项卡中显示有效的 CRL 本地缓存,而不是嵌入式 CRL。
更新 3 - 最终代码
public class CustomCrlClientOnline : CrlClientOnline
{
public CustomCrlClientOnline(X509Certificate[] chain) : base(chain)
{
}
public override ICollection<byte[]> GetEncoded(X509Certificate checkCert, string url)
{
ICollection<byte[]> result = new List<byte[]>();
ICollection<byte[]> crls = base.GetEncoded(checkCert, url);
foreach (byte[] crl in crls)
{
string crlString = Encoding.UTF8.GetString(crl);
if (crlString.StartsWith("-----BEGIN"))
{
string[] linesOfCRL = Regex.Split(crlString, "\r\n|\r|\n");
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < linesOfCRL.Length; i++)
{
if (!linesOfCRL[i].StartsWith("-----BEGIN") && !linesOfCRL[i].StartsWith("-----END"))
{
stringBuilder.Append(linesOfCRL[i] + "\r\n");
}
}
string derString = stringBuilder.ToString().Trim(new char[]
{
'\r',
'\n'
});
result.Add(Convert.FromBase64String(derString));
}
else
{
result.Add(crl);
}
}
return result;
}
}
简而言之: iText 假设要嵌入的 CRL 是 DER 格式。但是从 http://cavn.vn/new/CA2.crl
检索到的 CRL 是 PEM 格式的。因此,如您所见,解析其 DER 结构的尝试失败了。
详细
您证书的 CRL 分发点中的 http URL 指向的 CRL 文件是 PEM 格式:
-----BEGIN X509 CRL-----
MIMDuB0wgwO3BAIBATANBgkqhkiG9w0BAQUFADAzMQswCQYDVQQGEwJWTjEWMBQG
A1UEChMNTkFDRU5DT01NIFNDVDEMMAoGA1UEAxMDQ0EyFw0yMDA4MDgwODMwMjJa
...
rTh3AXJjJlSJinM/d0jO7o3JcKp2DGaD07DlObWTIQBf+oOs9SDDq+IZHMSolp51
5UE=
-----END X509 CRL-----
但是根据RFC 5280 (Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile), section 4.2.1.13 (CRL Distribution Points),必须是DER格式:
If the DistributionPointName contains a general name of type URI, the
following semantics MUST be assumed: the URI is a pointer to the
current CRL for the associated reasons and will be issued by the
associated cRLIssuer. When the HTTP or FTP URI scheme is used, the
URI MUST point to a single DER encoded CRL as specified in
[RFC2585].
因此,您的 CA 的 PKI 设置不正确。
做什么
由于您的 CA 设置中存在错误,因此应该修复此设置。你应该相应地通知他们。
不幸的是,即使他们做出反应(一开始并不清楚),也可能需要相当长的时间才能修复 PKI 设置。
因此,您还应该更改代码以也能够处理下载的 PEM 格式 CRL。您可以通过创建自己的 ICrlClient
实现来做到这一点,该实现在需要时将检索到的 CRL 转换为 DER 格式,例如通过从 CrlClientOnline
派生并覆盖 GetEncoded(X509Certificate, String)
的版本,在通过 base
方法检索 CRL 后检查每个 byte[]
并在必要时对其进行转换。
我已尝试通过 USB 令牌使用 CRL 分发点对 PDF 文件进行签名,如下所示:
URI = ldap:///CN=CA2,CN=www,CN=CDP,CN=Public%20Key%20Services,CN=Services,CN=Configuration,DC=cavn,DC=vn?certificateRevocationList?base?objectClass=cRLDistributionPoint
URI = http://cavn.vn/new/CA2.crl
URI = http://www.cavn.vn/new/CA2.crl
第一个 URI 解析失败
第一个问题是:为什么 iText7 在第一个 URI 失败时不尝试读取下一个 URI?
public static String GetCRLURL(X509Certificate certificate) { Asn1Object obj; try { obj = GetExtensionValue(certificate, X509Extensions.CrlDistributionPoints.Id); } catch (System.IO.IOException) { obj = (Asn1Object)null; } if (obj == null) { return null; } CrlDistPoint dist = CrlDistPoint.GetInstance(obj); DistributionPoint[] dists = dist.GetDistributionPoints(); foreach (DistributionPoint p in dists) { DistributionPointName distributionPointName = p.DistributionPointName; if (DistributionPointName.FullName != distributionPointName.PointType) { continue; } GeneralNames generalNames = (GeneralNames)distributionPointName.Name; GeneralName[] names = generalNames.GetNames(); foreach (GeneralName name in names) { if (name.TagNo != GeneralName.UniformResourceIdentifier) { continue; } DerIA5String derStr = DerIA5String.GetInstance((Asn1TaggedObject)name.ToAsn1Object(), false); **//Here iText7 always return the first URI. Why?** return derStr.GetString(); } } return null; }
我尝试修改上面的代码以读取下一个 URI,当我使用
CRLVerifier
验证它时,如下代码CRLVerifier crlVerifier = new CRLVerifier(null, null); IList<VerificationOK> verificationOks = crlVerifier.Verify(signCert, issuerCert, date);
验证成功
但是当我用下面的代码签署文档时:
void Sign(string srcFile, SysCert.X509Certificate2 signerCert, BCCert.X509Certificate[] arrBCChain, string tsaURL, string tsaUserName, string tsaPassword) { PdfReader pdfReader = null; PdfDocument pdfDocument = null; FileStream outfileStream = null; string tempFileName = string.Empty; try { pdfReader = new PdfReader(srcFile); pdfDocument = new PdfDocument(pdfReader); PdfPage lastPage = pdfDocument.GetLastPage(); int pageNumber = pdfDocument.GetPageNumber(lastPage); iText.Kernel.Geom.Rectangle mediaBox = lastPage.GetMediaBox(); pdfDocument.Close(); pdfReader.Close(); pdfReader = new PdfReader(srcFile); tempFileName = Path.Combine(Path.GetDirectoryName(srcFile), $"{Path.GetFileNameWithoutExtension(srcFile)}_Signed_{DateTime.Now.ToString("yyyyMMdd_HHmmss")}.pdf"); outfileStream = new FileStream(tempFileName, FileMode.Create); StampingProperties stampingProperties = new StampingProperties(); stampingProperties.UseAppendMode(); PdfSigner pdfSigner = new PdfSigner(pdfReader, outfileStream, stampingProperties); PdfSignatureAppearance signatureAppearance = pdfSigner.GetSignatureAppearance(); pdfDocument = pdfSigner.GetDocument(); SignatureUtil signUtil = new SignatureUtil(pdfDocument); IList<String> sigNames = signUtil.GetSignatureNames(); string lastSignatureName = sigNames.LastOrDefault(); PdfAcroForm acroForm = PdfAcroForm.GetAcroForm(pdfDocument, false); iText.Kernel.Geom.Rectangle rect = null; if (acroForm != null && !string.IsNullOrEmpty(lastSignatureName)) { PdfFormField pdfFormField = acroForm.GetField(lastSignatureName); PdfArray pdfArray = pdfFormField.GetWidgets().First().GetRectangle(); rect = new iText.Kernel.Geom.Rectangle(pdfArray.ToFloatArray()[0] + 150 + 5, mediaBox.GetY(), 150, 10); } else { rect = new iText.Kernel.Geom.Rectangle(mediaBox.GetX(), mediaBox.GetY(), 150, 10); } string signerName = arrBCChain[0].SubjectDN.GetValueList(X509Name.CN)[0].ToString(); signatureAppearance.SetRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION); signatureAppearance.SetLayer2Text("Signed by " + signerName); PdfFont font = PdfFontFactory.CreateFont(Path.Combine(Application.StartupPath, "VietnameseFonts", "vuTimesBold.ttf"), PdfEncodings.IDENTITY_H, true); signatureAppearance.SetLayer2Font(font); signatureAppearance.SetLayer2FontSize(5); signatureAppearance.SetLayer2FontColor(ColorConstants.BLACK); signatureAppearance.SetPageRect(rect); signatureAppearance.SetPageNumber(pageNumber); pdfSigner.SetFieldName(TwofishCryptEngine.Encrypt("MZH_METIT_Signature_" + DateTime.Now.ToString("yyyyMMdd_HHmmss"))); IExternalSignature externalSignature = new AsymmetricAlgorithmSignature((RSACryptoServiceProvider)signerCert.PrivateKey, DigestAlgorithms.SHA256); IOcspClient ocspClient = new OcspClientBouncyCastle(null); ICrlClient crlClient = new CrlClientOnline(arrBCChain); List<ICrlClient> lstCRL = new List<ICrlClient>() { crlClient }; ITSAClient tsaClient = null; if (string.IsNullOrWhiteSpace(tsaUserName)) tsaClient = new TSAClientBouncyCastle(tsaURL); else tsaClient = new TSAClientBouncyCastle(tsaURL, tsaUserName, tsaPassword); pdfSigner.SignDetached(externalSignature, arrBCChain, lstCRL, ocspClient, tsaClient, 0, PdfSigner.CryptoStandard.CMS); pdfReader.Close(); outfileStream.Close(); if (File.Exists(srcFile)) File.Delete(srcFile); if (File.Exists(tempFileName)) File.Move(tempFileName, srcFile); } catch (Exception ex) { throw ex; } finally { if (pdfDocument != null && !pdfDocument.IsClosed()) pdfDocument.Close(); if (pdfReader != null) pdfReader.Close(); if (outfileStream != null) outfileStream.Close(); if (File.Exists(tempFileName)) File.Delete(tempFileName); } }
在 BouncyCastle.Crypto.dll 中出现异常“遇到未知标记 13”失败。
但是当我尝试使用相同的 USB 令牌通过 Adobe Reader 签署文档时,它成功了。
请告诉我为什么以及如何修复它。谢谢
更新 1
修改代码以读取下一个 URI
public static String GetCRLURL(X509Certificate certificate)
{
Asn1Object obj;
try
{
obj = GetExtensionValue(certificate, X509Extensions.CrlDistributionPoints.Id);
}
catch (System.IO.IOException)
{
obj = (Asn1Object)null;
}
if (obj == null)
{
return null;
}
CrlDistPoint dist = CrlDistPoint.GetInstance(obj);
DistributionPoint[] dists = dist.GetDistributionPoints();
foreach (DistributionPoint p in dists)
{
DistributionPointName distributionPointName = p.DistributionPointName;
if (DistributionPointName.FullName != distributionPointName.PointType)
{
continue;
}
GeneralNames generalNames = (GeneralNames)distributionPointName.Name;
GeneralName[] names = generalNames.GetNames();
foreach (GeneralName name in names)
{
if (name.TagNo != GeneralName.UniformResourceIdentifier)
{
continue;
}
//Hack by AnND: 07 - 12 - 2020: try to parse URL until get valid URL
try
{
DerIA5String derStr = DerIA5String.GetInstance((Asn1TaggedObject)name.ToAsn1Object(), false);
string url = derStr.GetString();
X509Crl x509Crl = GetCRL(url);
return url;
}
catch
{
}
//End hack
}
}
return null;
}
完整堆栈跟踪
> BouncyCastle.Crypto.dll!Org.BouncyCastle.Asn1.Asn1InputStream.BuildObject(int tag, int tagNo, int length) (IL=0x0087, Native=0x0B31AD68+0x1D6)
BouncyCastle.Crypto.dll!Org.BouncyCastle.Asn1.Asn1InputStream.ReadObject() (IL≈0x00FB, Native=0x0B31A5F8+0x392)
itext.sign.dll!iText.Signatures.PdfPKCS7.GetAuthenticatedAttributeSet(byte[] secondDigest, System.Collections.Generic.ICollection<byte[]> ocsp, System.Collections.Generic.ICollection<byte[]> crlBytes, iText.Signatures.PdfSigner.CryptoStandard sigtype) (IL≈0x0169, Native=0x14768008+0x64C)
itext.sign.dll!iText.Signatures.PdfPKCS7.GetAuthenticatedAttributeBytes(byte[] secondDigest, iText.Signatures.PdfSigner.CryptoStandard sigtype, System.Collections.Generic.ICollection<byte[]> ocsp, System.Collections.Generic.ICollection<byte[]> crlBytes) (IL≈0x0000, Native=0x14767F60+0x46)
itext.sign.dll!iText.Signatures.PdfSigner.SignDetached(iText.Signatures.IExternalSignature externalSignature, Org.BouncyCastle.X509.X509Certificate[] chain, System.Collections.Generic.ICollection<iText.Signatures.ICrlClient> crlList, iText.Signatures.IOcspClient ocspClient, iText.Signatures.ITSAClient tsaClient, int estimatedSize, iText.Signatures.PdfSigner.CryptoStandard sigtype, Org.BouncyCastle.Asn1.Esf.SignaturePolicyIdentifier signaturePolicy) (IL≈0x01F5, Native=0x14674510+0x70A)
itext.sign.dll!iText.Signatures.PdfSigner.SignDetached(iText.Signatures.IExternalSignature externalSignature, Org.BouncyCastle.X509.X509Certificate[] chain, System.Collections.Generic.ICollection<iText.Signatures.ICrlClient> crlList, iText.Signatures.IOcspClient ocspClient, iText.Signatures.ITSAClient tsaClient, int estimatedSize, iText.Signatures.PdfSigner.CryptoStandard sigtype) (IL=0x0012, Native=0x14673F88+0x3C)
METIT.exe!METIT.SignDocument.Sign(string srcFile, System.Security.Cryptography.X509Certificates.X509Certificate2 signerCert, Org.BouncyCastle.X509.X509Certificate[] arrBCChain, string tsaURL, string tsaUserName, string tsaPassword) (IL=0x0290, Native=0x0D203A40+0x920)
METIT.exe!METIT.SignDocument.btnSign_Click(object sender, System.EventArgs e) (IL=0x0793, Native=0x0B2B7838+0x166F)
System.Windows.Forms.dll!System.Windows.Forms.Control.OnClick(System.EventArgs e) (IL=0x0021, Native=0x0B8E47F0+0x87)
System.Windows.Forms.dll!System.Windows.Forms.Button.OnClick(System.EventArgs e) (IL=0x0035, Native=0x0B8E4680+0x78)
System.Windows.Forms.dll!System.Windows.Forms.Button.OnMouseUp(System.Windows.Forms.MouseEventArgs mevent) (IL=0x007E, Native=0x0B8E4190+0x177)
System.Windows.Forms.dll!System.Windows.Forms.Control.WmMouseUp(ref System.Windows.Forms.Message m, System.Windows.Forms.MouseButtons button, int clicks) (IL=0x0189, Native=0x0B8E34E0+0x59C)
System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m) (IL=0x04AC, Native=0x07932360+0x7B2)
System.Windows.Forms.dll!System.Windows.Forms.ButtonBase.WndProc(ref System.Windows.Forms.Message m) (IL=0x00DB, Native=0x0B3F9698+0x1E8)
System.Windows.Forms.dll!System.Windows.Forms.Button.WndProc(ref System.Windows.Forms.Message m) (IL=0x0044, Native=0x0B3F95D0+0xB0)
System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref System.Windows.Forms.Message m) (IL=0x000C, Native=0x07931F18+0x2E)
System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m) (IL=0x009A, Native=0x07931DA8+0x123)
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.Callback(System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam) (IL=0x002D, Native=0x07931B90+0xA1)
[Managed to Native Transition]
System.Windows.Forms.dll!System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(System.IntPtr dwComponentID, int reason, int pvLoopData) (IL≈0x0177, Native=0x0B29CDA8+0x49D)
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(int reason, System.Windows.Forms.ApplicationContext context) (IL≈0x01FA, Native=0x094BADD8+0x550)
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoop(int reason, System.Windows.Forms.ApplicationContext context) (IL=0x001C, Native=0x094BA928+0x5D)
System.Windows.Forms.dll!System.Windows.Forms.Application.Run(System.Windows.Forms.Form mainForm) (IL=0x0011, Native=0x0B3F9238+0x4F)
METIT.exe!METIT.METITControlForm.Main() (IL=0x0114, Native=0x056CD658+0x29B)
Debuging image
局部变量的值
+ this {Org.BouncyCastle.Asn1.Asn1InputStream} Org.BouncyCastle.Asn1.Asn1InputStream
tag 0x0000002D int
tagNo 0x0000000D int
length 0x0000002D int
isConstructed true bool
+ defIn {Org.BouncyCastle.Asn1.DefiniteLengthInputStream} Org.BouncyCastle.Asn1.DefiniteLengthInputStream
问题出在 URI 中:http://cavn.vn/new/CA2.crl
使用此代码从该 URI 获取字节数组后
IList<byte[]> ar = new List<byte[]>();
foreach (Uri urlt in urllist) {
try {
LOGGER.Info("Checking CRL: " + urlt);
Stream inp = SignUtils.GetHttpResponse(urlt);
byte[] buf = new byte[1024];
MemoryStream bout = new MemoryStream();
while (true) {
int n = inp.JRead(buf, 0, buf.Length);
if (n <= 0) {
break;
}
bout.Write(buf, 0, n);
}
inp.Dispose();
ar.Add(bout.ToArray());
LOGGER.Info("Added CRL found at: " + urlt);
}
catch (Exception e) {
LOGGER.Info("Skipped CRL: " + e.Message + " for " + urlt);
}
}
还是可以的。但是当它遇到 BouncyCastle.Crypto.dll!Org.BouncyCastle.Asn1.Asn1InputStream.BuildObject(int tag, int tagNo, int length)
=> 在这里失败,因为错误的 tagNo.
更新 2
我对 CrlClientOnline
public class CustomCrlClientOnline : CrlClientOnline
{
public CustomCrlClientOnline(X509Certificate[] chain) : base(chain)
{
}
public override ICollection<byte[]> GetEncoded(X509Certificate checkCert, string url)
{
ICollection<byte[]> result = new List<byte[]>();
ICollection<byte[]> crls = base.GetEncoded(checkCert, url);
foreach (byte[] crl in crls)
{
string crlData = Encoding.UTF8.GetString(crl);
if (crlData.StartsWith("-----BEGIN"))
{
string[] array2 = Regex.Split(crlData, "\r\n|\r|\n");
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < array2.Length; i++)
{
if (!array2[i].StartsWith("-----BEGIN") && !array2[i].StartsWith("-----END"))
{
stringBuilder.Append(array2[i] + "\r\n");
}
}
string text = stringBuilder.ToString().Trim(new char[]
{
'\r',
'\n'
});
array2 = Regex.Split(text, "\r\n|\r|\n");
result.Add(Encoding.UTF8.GetBytes(text));
}
else
{
result.Add(crl);
}
}
return result;
}
}
当我打开 PDF 文件时,在吊销选项卡中显示有效的 CRL 本地缓存,而不是嵌入式 CRL。
更新 3 - 最终代码
public class CustomCrlClientOnline : CrlClientOnline
{
public CustomCrlClientOnline(X509Certificate[] chain) : base(chain)
{
}
public override ICollection<byte[]> GetEncoded(X509Certificate checkCert, string url)
{
ICollection<byte[]> result = new List<byte[]>();
ICollection<byte[]> crls = base.GetEncoded(checkCert, url);
foreach (byte[] crl in crls)
{
string crlString = Encoding.UTF8.GetString(crl);
if (crlString.StartsWith("-----BEGIN"))
{
string[] linesOfCRL = Regex.Split(crlString, "\r\n|\r|\n");
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < linesOfCRL.Length; i++)
{
if (!linesOfCRL[i].StartsWith("-----BEGIN") && !linesOfCRL[i].StartsWith("-----END"))
{
stringBuilder.Append(linesOfCRL[i] + "\r\n");
}
}
string derString = stringBuilder.ToString().Trim(new char[]
{
'\r',
'\n'
});
result.Add(Convert.FromBase64String(derString));
}
else
{
result.Add(crl);
}
}
return result;
}
}
简而言之: iText 假设要嵌入的 CRL 是 DER 格式。但是从 http://cavn.vn/new/CA2.crl
检索到的 CRL 是 PEM 格式的。因此,如您所见,解析其 DER 结构的尝试失败了。
详细
您证书的 CRL 分发点中的 http URL 指向的 CRL 文件是 PEM 格式:
-----BEGIN X509 CRL-----
MIMDuB0wgwO3BAIBATANBgkqhkiG9w0BAQUFADAzMQswCQYDVQQGEwJWTjEWMBQG
A1UEChMNTkFDRU5DT01NIFNDVDEMMAoGA1UEAxMDQ0EyFw0yMDA4MDgwODMwMjJa
...
rTh3AXJjJlSJinM/d0jO7o3JcKp2DGaD07DlObWTIQBf+oOs9SDDq+IZHMSolp51
5UE=
-----END X509 CRL-----
但是根据RFC 5280 (Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile), section 4.2.1.13 (CRL Distribution Points),必须是DER格式:
If the DistributionPointName contains a general name of type URI, the
following semantics MUST be assumed: the URI is a pointer to the
current CRL for the associated reasons and will be issued by the
associated cRLIssuer. When the HTTP or FTP URI scheme is used, the
URI MUST point to a single DER encoded CRL as specified in
[RFC2585].
因此,您的 CA 的 PKI 设置不正确。
做什么
由于您的 CA 设置中存在错误,因此应该修复此设置。你应该相应地通知他们。
不幸的是,即使他们做出反应(一开始并不清楚),也可能需要相当长的时间才能修复 PKI 设置。
因此,您还应该更改代码以也能够处理下载的 PEM 格式 CRL。您可以通过创建自己的 ICrlClient
实现来做到这一点,该实现在需要时将检索到的 CRL 转换为 DER 格式,例如通过从 CrlClientOnline
派生并覆盖 GetEncoded(X509Certificate, String)
的版本,在通过 base
方法检索 CRL 后检查每个 byte[]
并在必要时对其进行转换。