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
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;
}
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.
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)
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.
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;
}
}
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.
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.
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.
User contributions licensed under CC BY-SA 3.0