【问题标题】:iText 7 - Problem when signing PDF documentiText 7 - 签署 PDF 文档时出现问题
【发布时间】:2021-03-21 17:38:47
【问题描述】:

我尝试通过 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 解析失败

  1. 第一个问题是:为什么 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;
    }
    
  2. 我尝试修改上面的代码以读取下一个 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”失败。

    但是当我尝试通过 Adob​​e Reader 使用相同的 USB 令牌对文档进行签名时,它成功了。

请告诉我为什么以及如何解决它。谢谢

更新 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

我对@9​​87654337@ 的新实现

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

【问题讨论】:

  • “第一个问题是:为什么 iText7 在第一个失败时不尝试读取下一个 URI?” - 很难猜出为什么。测试所有提供的 URL 显然是有意义的。
  • “在 BouncyCastle.Crypto.dll 中出现异常“遇到未知标签 13”失败。” - 由于我们没有您的 U 盘,我们几乎无法重现问题。因此,请确保您提供所有可以提供的信息。特别是完整的堆栈跟踪和 BC 试图在那里解析的确切数据。
  • 我已经为您的调试更新了更多信息。请帮我找出原因。非常感谢。
  • 好的,根据您原始问题中的信息,我应该已经看到了问题,但是额外的信息让我眼前一亮。请参阅下面的答案。

标签: c# itext7


【解决方案1】:

简而言之: 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 5280Internet X.509 公钥基础结构证书和证书撤销列表 (CRL) 配置文件),section 4.2.1.13CRL 分发点) ,必须是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 派生并通过在通过base 方法检索CRL 后检查每个byte[] 并在必要时对其进行转换的版本覆盖GetEncoded(X509Certificate, String)

【讨论】:

  • 谢谢@mkl。我按照您的建议将 PEM 转换为 DER 并成功签名,但结果文件不符合我的预期。请查看我的 UPDATE 2 并给我一些解释。
  • 您的CustomCrlClientOnline 不会从 PEM 转换为 DER,它只会删除 PEM 的页眉和页脚行。要转换为 DER,至少需要对该字符串进行 Base64 解码,无需页眉或页脚。
  • 感谢您的大力支持。它现在完美运行。我为任何需要的人更新了我的最终代码。
  • 现在可以正常使用了。顺便说一句,在堆栈溢出时,通常不会将分辨率添加为问题文本的一部分,而是添加为答案。此外,您最终可以通过单击左上角的勾号将问题的答案之一标记为已接受的答案。
猜你喜欢
  • 1970-01-01
  • 2021-03-09
  • 1970-01-01
  • 2011-11-17
  • 1970-01-01
  • 2018-05-27
  • 1970-01-01
  • 1970-01-01
  • 2016-01-21
相关资源
最近更新 更多