我尝试通过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?
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)
局部变量的值
+ 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的本地缓存有效,而不是嵌入的缓存。
更新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格式。但是从中检索的CRLhttp://cavn.vn/new/CA2.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公钥基础结构证书和证书吊销列表(CRL)配置文件)第4.2.1.13节(CRL分发点),它必须采用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的设置中,因此应固定此设置。你应该相应地通知他们。
不幸的是,即使他们做出 React (开始时尚不清楚),也可能需要花费相当长的时间才能解决PKI设置问题。
因此,你还应该更改代码以也能够处理下载的PEM格式CRL。你可以通过创建自己的ICrlClient
实现(如果需要的话)将检索到的CRL转换为DER格式来实现,例如,通过派生CrlClientOnline
并覆盖GetEncoded(X509Certificate, String)
一个版本,该版本在通过base
方法检索CRL之后将对其进行检查byte[]
并在必要时进行转换。
谢谢@mkl。我按照您的建议将PEM转换为DER并成功签名,但是结果文件不符合我的期望。请参阅我的UPDATE 2,并给我一些解释。
您
CustomCrlClientOnline
不会从PEM转换为DER,而只是删除PEM的页眉和页脚行。为了转换为DER,至少需要对该字符串进行Base64解码,且不包含页眉或页脚。感谢您的大力支持。现在,它可以完美运行了。我为任何需要的人更新了我的最终代码。
很好,现在就可以使用。顺便说一句,在堆栈溢出时,通常不会将解决方案添加为问题文本的一部分,而是作为答案。此外,您最终可以通过单击问题左上方的对勾,将问题的答案之一标记为可接受的答案。