iText 7 - Problem when signing PDF document

0

I've tried to sign a PDF file by USB token with CRL distribution points as below:

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

The first URI failed to parse

  1. The first question is: why don't iText7 try to read next URI when the first one fails?

    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. I tried to modify above code to read next URI and when I use CRLVerifier to verify it as below code

    CRLVerifier crlVerifier = new CRLVerifier(null, null);
    IList<VerificationOK> verificationOks = crlVerifier.Verify(signCert, issuerCert, date);
    

    It verifies successfully.

    But when I sign the document with below code:

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

    It fails with exception "Unknown tag 13 encountered" in BouncyCastle.Crypto.dll.

    But when I try to sign the document by Adobe Reader with the same USB token, it succeeds.

Image link

Please tell me why and how I can fix it. Thanks

UPDATE 1

Modified code to read the next 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;
}

Full stack trace

>   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

Local variable's value

+       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

The problem is in URI: http://cavn.vn/new/CA2.crl

After use this code to get byte array from that 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);
    }
}

It's still OK. But when it runs into BouncyCastle.Crypto.dll!Org.BouncyCastle.Asn1.Asn1InputStream.BuildObject(int tag, int tagNo, int length) => fail here because wrong tagNo.

UPDATE 2

My new implementation for 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;
    }
}

When I open PDF file, in Revocation tab show valid with local cache of CRL instead of embedded one.

Result file

UPDATE 3 - Final code

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;
    }
}
c#
itext7
asked on Stack Overflow Dec 10, 2020 by duyan • edited Dec 14, 2020 by duyan

1 Answer

0

In short: iText assumes that the CRL to embed is in DER format. But the CRL retrieved from http://cavn.vn/new/CA2.crl is in PEM format. Thus, the attempt to parse its DER structure fails as you observe.

In Detail

The CRL file pointed to by the http URL in the CRL distribution points of your certificate is in PEM format:

-----BEGIN X509 CRL-----
MIMDuB0wgwO3BAIBATANBgkqhkiG9w0BAQUFADAzMQswCQYDVQQGEwJWTjEWMBQG
A1UEChMNTkFDRU5DT01NIFNDVDEMMAoGA1UEAxMDQ0EyFw0yMDA4MDgwODMwMjJa
...
rTh3AXJjJlSJinM/d0jO7o3JcKp2DGaD07DlObWTIQBf+oOs9SDDq+IZHMSolp51
5UE=
-----END X509 CRL-----

But according to RFC 5280 (Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile), section 4.2.1.13 (CRL Distribution Points), it must be in DER format:

   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].

Thus, the PKI of your CA is set up incorrectly.

What To Do

As the error is in the setup of your CA, this setup should be fixed. You should inform them accordingly.

Unfortunately, even if they react (which isn't clear to start with), it might take considerable time until the PKI setup is fixed.

Thus, you should also change your code to also be able to handle downloaded PEM format CRLs. You could do so by creating your own ICrlClient implementation that transforms the retrieved CRLs to DER format if need be, e.g. by deriving from CrlClientOnline and overriding GetEncoded(X509Certificate, String) by a version that after retrieving the CRLs via the base method checks each byte[] and transforms it if necessary.

answered on Stack Overflow Dec 11, 2020 by mkl

User contributions licensed under CC BY-SA 3.0