I have a signed PDF file. With this function, which makes use of iTextSharp library, I find the certificates p7m signatures:
private void GetSignature(string FileName)
{
AcroFields acroFields = new PdfReader(FileName).AcroFields;
List<string> names = acroFields.GetSignatureNames();
foreach (var name in names)
{
PdfDictionary dict = acroFields.GetSignatureDictionary(name);
PdfString contents = (PdfString)PdfReader.GetPdfObject(dict.Get(PdfName.CONTENTS));
byte[] PKCS7 = contents.GetOriginalBytes();
ByteArrayToFile(#"c:\signature\" + name + ".p7m", PKCS7);
}
}
Now... how can I extract images (bitmap) associated to the signatures? Is it possible?
Thanks, Luigi
In your sample documents the term signature applies threefold:
It contains digital signatures according to the PDF specification ISO 32000-1:2008.
The respective visualization contains a bitmap image of an handwritten signature.
The respective signature dictionary contains proprietary information by the software which added all the signature data to the PDF. Most likely these proprietary information contain the biometric data mentioned in the comments of the OP.
According to the manufacturer of the software creating these multi-level signatures, the handwritten signature seems like the major proof of identity. The digital one only serves to protect the document from changes; it does not necessarily reflect the identity of the person who signed manually but instead of the owner of the device on which that manual signature was created ("Please sign here that you got the parcel"):
Functions
Handwritten Signature Capturing - Forensically identifiable signatures on signature pads, payment terminals, the iPad or Android devices.
Signature Verification - Compare a handwritten signature against a pre-enrolled profile.
Control all steps in the signing process - Including positioning signature fields, filling out forms, adding annotations, adding attachments, and much more.
Protects the Integrity of Documents - By sealing them with a digital signature.
(xyzmo English website start page)
Concerning the extraction of all these information using iText...
The properties of the digital signature can easily be extracted and verified as already observed by the OP using the signature related methods of the AcroFields class.
The bitmap image of the handwritten signature can also be extracted fairly easily. The appearance stream of the signature form field dictionary merely paints that bitmap which is attached to the stream as a resource.
The data container containing the proprietary information can also be extracted as it is the value of just another key in the signature dictionary.
Unfortunately, though, the contents of that data container are packed into a XML fragment calling itself EncryptedSignatureDataContainer. Whether the payload data of this XML fragment can be properly decrypted and how it is to be interpreted is information to request from the xyzmo people themselves, and I have no idea if they consider that information public or not.
Thus, the most relevant information is the least accessible one.
PS Concerning the decryption of the encrypted biometric payload I found the following on the manufacturer's website:
The document contains a captured signature that has been encrypted (RSA 4096 + AES256). A person’s signature is encrypted immediately as it is captured by the signature pad, using the private key of a special certificate. This special certificate is selected by the company using the xyzmo suite, and is typically stored in a secure environment outside the company (bank safe, external notary, etc.). Thus, xyzmo itself has NO access to this certificate. For the encryption of signatures, the xyzmo suite just needs the public key of the certificate. It is only for decryption, and the extraction of signatures from a document, that the private key is required. Only specific people, to whom the company has granted access to this certificate, will be able to decrypt the profile using the PenAnalyst tool, which is provided as part of the suite.
(xyzmo English website Digital Signature Capture FAQ's)
Thus, to decrypt the biometric data you have to have access to the respective private key which is typically stored in a secure environment outside the company (bank safe, external notary, etc.). If you have that kind of access, we may continue talking about the format of those decrypted data... ;)
BTW, if anyone could simply retrieve the biometric data from the signed document, they could too easily be copied to other documents to fake a signature.
Extracting the bitmap image of the handwritten signature
As there was special interest in the extraction of the bitmap image of the handwritten signature, here a quick and dirty helper to extract the image of the signature. As already said, I do it in Java as I'm more at home there:
public class XyzmoSignatureDataExtractor
{
public XyzmoSignatureDataExtractor(PdfReader reader)
{
this.reader = reader;
}
public PdfImageObject extractImage(String signatureName) throws IOException
{
MyImageRenderListener listener = new MyImageRenderListener();
PdfDictionary sigFieldDic = reader.getAcroFields().getFieldItem(signatureName).getMerged(0);
PdfDictionary appearancesDic = sigFieldDic.getAsDict(PdfName.AP);
PdfStream normalAppearance = appearancesDic.getAsStream(PdfName.N);
PdfDictionary resourcesDic = normalAppearance.getAsDict(PdfName.RESOURCES);
PdfContentStreamProcessor processor = new PdfContentStreamProcessor(listener);
processor.processContent(ContentByteUtils.getContentBytesFromContentObject(normalAppearance), resourcesDic);
return listener.image;
}
class MyImageRenderListener implements RenderListener
{
public void beginTextBlock() { }
public void endTextBlock() { }
public void renderImage(ImageRenderInfo renderInfo)
{
try
{
image = renderInfo.getImage();
}
catch (IOException e)
{
throw new RuntimeException("Failure retrieving image", e);
}
}
public void renderText(TextRenderInfo renderInfo) { }
PdfImageObject image = null;
}
final PdfReader reader;
}
You use it like this:
PdfReader reader = new PdfReader(resourceStream);
XyzmoSignatureDataExtractor extractor = new XyzmoSignatureDataExtractor(reader);
AcroFields acroFields = reader.getAcroFields();
for (String name: acroFields.getSignatureNames())
{
System.out.printf("\nTesting signature '%s'.\n", name);
PdfImageObject image = extractor.extractImage(name);
OutputStream os = new FileOutputStream("target/test-outputs/SampleXyzmoSignature-image-" + name + "." + image.getFileType());
os.write(image.getImageAsBytes());
os.close();
PdfDictionary imageDictionary = image.getDictionary();
PRStream maskStream = (PRStream) imageDictionary.getAsStream(PdfName.SMASK);
if (maskStream != null)
{
PdfImageObject maskImage = new PdfImageObject(maskStream);
os = new FileOutputStream("target/test-outputs/SampleXyzmoSignature-image-" + name + "-mask." + maskImage.getFileType());
os.write(maskImage.getImageAsBytes());
os.close();
}
}
Warning: The class XyzmoSignatureDataExtractor really is a quick&dirty hack. Many assumptions are made, null-checks are left out, ...
Related
PDF document needs to signed with national digital identity.
National digital identity WebService provide facility to sign document, in my project I have integrated same.
Requesting Esign services give response in PKCS7(CMS) format.
I want to append same response in multiple locations, So i am creating multiple empty signature container post i receive Response from service.
I referred this article : Sign Pdf Using ITextSharp and XML Signature
But in given article we only one signing location is present but i have multiple signing locations.
I am using itext sharp Library.
Using MakeSignature.SignDeferred Method to append signature at multiple locations but it is showing PDF invalid.
Please find below response XML which i received from Webservice:
<?xml version="1.0" encoding="UTF-8"?>
<EsignResp errCode="NA" errMsg="NA" resCode="259A52453BE95D3A1071193995E062E3EAD796AD" status="1" ts="2019-03-18T14:26:59" txn="UKC:eSign:2998:20190318142602814">
<UserX509Certificate>--Usercerti in base64--</UserX509Certificate>
<Signatures>
<DocSignature error="" id="1" sigHashAlgorithm="SHA256">--Signature in base 64 in PKCS7(CMS)---</DocSignature>
</Signatures>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></CanonicalizationMethod>
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"></SignatureMethod>
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></Transform>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
<DigestValue>MrOfovytOIp/8qlEkgamrcyhGTSGTN5aS1P+08Fbwfk=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>BBexJyk47YaTdoDgXaFRCtJq1Gc3KsZNt48/I8X4TgNJ6gh2NI9Y5Y9Tc7bozrK/QRy1VYPOWYq5r/YdunjMQLmJJicyeqeqe2eD+TJ8oecpjCbmhPnDK2VgaJ2h00sfsfdsflIe/toKwAmV4PTBA1a5wkz77hj+HTkWXMkPEIsBUnBirVpHxe2bYaa7jcIIpWtJmqvcSurKTOeyFRa+AFWfwWHB/EzHJlDmgiMXzrNauxJ4HpphNaRU+bO5JdyzJs/8Zx4i6qwSEybkuprL3GdO9C7zMPiC98CTfO2dfUrbZWy1pSvwEqlVXQIfrkp+m2JRbFgT8EEIGfXUS+AJBPRwhY1Xsww==</SignatureValue>
<KeyInfo>
<KeyValue>
<RSAKeyValue>
<Modulus>0o9vohWZ3ztI9ea8D/zUEUBRq6c82BE7sFmr1hNMeuGSJQFf39ceesRtGUzlUYVWXcU23P8sVZ5419CHh7ApFzUXaLD72i/2d5FFI0n3iRlTQec9PEUHyrvOCVDpqBhbnrO/EHBqRluUQJTQUtMu5mhPNFV7IIJMTEAsUhCL9adZXXQK9NeK0foRr29Oq7VdEGfSeLzHIibpQmhNPh89oJXqu0cmbNSW4J4i2GmwHQpmsmHaSQcgh4mgVrykO64pAKXPreAPipDHQM1l/e5hilYlWfLHxhC5OdfdfdsbTCTcydQ218IVulFOFhdQt7xVV61TOmoTC2elhWbDqoLJBVU5mBfQ==</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</KeyValue>
<X509Data>
<X509SubjectName>CN=D-Random detail</X509SubjectName>
<X509Certificate>--public certificate of provider--- </X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
</EsignResp>
EDIT: As per latest communication, Web Service provide response for whatever hash is being provided from my end. They do not validate it. Hash is any 64 character string. Kindly let me know what are the possible ways by which i can use this to append PKCS7 signature on a PDF document.
Below Code for generating request :
if (System.IO.File.Exists(tempPdf))
System.IO.File.Delete(tempPdf);
using (PdfReader reader = new PdfReader(pdfReadServerPath))
{
using (FileStream os = System.IO.File.OpenWrite(tempPdf))
{
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0',null,true);
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SetVisibleSignature(new Rectangle(15, 15, 100, 100), 1, "sign1");
appearance.CertificationLevel = PdfSignatureAppearance.NOT_CERTIFIED;
AllPagesSignatureContainer external = new AllPagesSignatureContainer(appearance);
MakeSignature.SignExternalContainer(appearance, external, 8192);
Stream data = appearance.GetRangeStream();
Stream data = appearance.GetRangeStream();
byte[] hash = ReadFully(data); //Convert stream to byte
_signatureHash = hash;
}
}
//create sha256 message digest
using (SHA256.Create())
{
_signatureHash = SHA256.Create().ComputeHash(_signatureHash);
}
bool check = false;
string hexencodedDigest = null;
//create hex encoded sha256 message digest
hexencodedDigest = new BigInteger(1, _signatureHash).ToString(16);
hexencodedDigest = hexencodedDigest.ToUpper();
if (hexencodedDigest.Length == 64)
{
**Send this hexencoded hash to webservice**
}
Below code for appending signature:
//DLL Call
eSign2_1_Request_Response req_resp = new eSign2_1_Request_Response();
//// Response XML Digest process
string resp_xml = Request.Form["msg"].ToString();//signature response XML;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(resp_xml);
XmlElement EsignResp = xmlDoc.DocumentElement;
if (EsignResp.Attributes != null && EsignResp.Attributes["status"].Value != "1")
{
req_resp.WriteTextFileLog("errCode: " + EsignResp.Attributes["errCode"].Value + " & Error Message: " + EsignResp.Attributes["errMsg"].Value, "log", base_folder_path);
}
else
{
req_resp.WriteTextFileLog(resp_xml, "xml", base_folder_path + "\\" + file_withoutExtn + "_responseXML.txt");
//-------Continue to generate signed PDF by passing parameter to DLL
XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signatures");
string signature = nodeList[0].FirstChild.InnerText;
string signedPdf = #"D:\POC Hosted\TryNSDL\TryNSDL\wwwroot\TempPath\signedPdf.pdf";
string tempPdf = #"D:\POC Hosted\TryNSDL\TryNSDL\wwwroot\TempPath\tempPdf.pdf";
using (PdfReader reader = new PdfReader(tempPdf))
{
using (FileStream os = System.IO.File.OpenWrite(signedPdf))
{
byte[] encodedSignature = Convert.FromBase64String(signature);
IExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSignature);
MakeSignature.SignDeferred(reader, "sign1", os, external);
}
}
}
Code for Allsignature container:
public class AllPagesSignatureContainer : IExternalSignatureContainer
{
public AllPagesSignatureContainer(PdfSignatureAppearance appearance)
{
this.appearance = appearance;
}
public void ModifySigningDictionary(PdfDictionary signDic)
{
signDic.Put(PdfName.FILTER, PdfName.ADOBE_PPKMS);
signDic.Put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED);
PdfStamper stamper = appearance.Stamper;
PdfReader reader = stamper.Reader;
PdfDictionary xobject1 = new PdfDictionary();
PdfDictionary xobject2 = new PdfDictionary();
xobject1.Put(PdfName.N, appearance.GetAppearance().IndirectReference);
xobject2.Put(PdfName.AP, xobject1);
PdfIndirectReference PRef = stamper.Writer.PdfIndirectReference;
PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + reader.NumberOfPages) + " 0 R");
for (int i = 2; i < reader.NumberOfPages+1; i++)
{
var signatureField = PdfFormField.CreateSignature(stamper.Writer);
signatureField.Put(PdfName.T, new PdfString("ClientSignature_" + i.ToString()));
signatureField.Put(PdfName.V, PRefLiteral);
signatureField.Put(PdfName.F, new PdfNumber("132"));
signatureField.SetWidget(new Rectangle(15, 15, 100, 100), null);
signatureField.Put(PdfName.SUBTYPE, PdfName.WIDGET);
signatureField.Put(PdfName.AP, xobject1);
signatureField.SetPage();
Console.WriteLine(signatureField);
stamper.AddAnnotation(signatureField, i);
}
}
public byte[] Sign(Stream data)
{
return new byte[0];
}
PdfSignatureAppearance appearance;
}
I used append mode in create signature then signature doesn't come. Only empty signatures are visible in adobe reader : /Fileremoved/
If i try same without appendmode PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0'); and PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + 1 + 2 * (reader.NumberOfPages - 1)) + " 0 R");
then it works fine : /Fileremoved/, but it can be used for single signer only. Nd if we again try to use same pdf to Resign then old signatures becomes invalid. (obviously since append mode is not used.)
I guess For signing to work in append mode, Change is required in line of PdfLiteral - I have less idea about same how it actually works.
Signed file :/Fileremoved/
Input file: /Fileremoved/
A first quick look through your code revealed two major errors.
Hashing twice
You hash the document data twice (using different APIs for that... weird!):
Stream data = appearance.GetRangeStream();
byte[] hash = DigestAlgorithms.Digest(data, "SHA256");
[...]
_signatureHash = hash;// signatureHash;
}
}
[...]
using (SHA256.Create())
{
_signatureHash = SHA256.Create().ComputeHash(_signatureHash);
}
This is wrong, this makes no sense.
Injecting the wrong signature container
You say
Requesting Esign services give response in PKCS7(CMS) format.
But instead of using the CMS signature container from the result as such, you try to build an own CMS container, injecting the Esign response CMS container as if it was a mere signed hash:
XmlNodeList UserX509Certificate = xmlDoc.GetElementsByTagName("UserX509Certificate");
byte[] rawdat = Convert.FromBase64String(UserX509Certificate[0].InnerText);
var chain = new List<Org.BouncyCastle.X509.X509Certificate>
{
Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(new X509Certificate2(rawdat))
};
var signaturee = new PdfPKCS7(null, chain, "SHA256", false);
_signature = signaturee;
_signature.SetExternalDigest(Convert.FromBase64String(signature), null, "RSA");
byte[] encodedSignature = _signature.GetEncodedPKCS7(_hash, null, null, null, CryptoStandard.CMS);
According to your comments in the XML
<DocSignature error="" id="1" sigHashAlgorithm="SHA256">--Signature in base 64 in PKCS7(CMS)---</DocSignature>
this DocSignature element contains the CMS signature container.
Thus, remove the code segment above and instead put the content of the DocSignature element (don't forget to base64 decode) into the byte[] encodedSignature. Now you can inject it into the prepared signature as before:
IExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSignature);
MakeSignature.SignDeferred(reader, "sign1", os, external);
After you fixed the issues above, two more became apparent:
Using the wrong file mode
You open the stream to write to like this:
using (FileStream os = System.IO.File.OpenWrite(signedPdf))
File.OpenWrite is documented on learn.microsoft.com to be
equivalent to the FileStream(String, FileMode, FileAccess, FileShare) constructor overload with file mode set to OpenOrCreate, the access set to Write, and the share mode set to None.
The file mode OpenOrCreate in turn is documented to specify
that the operating system should open a file if it exists; otherwise, a new file should be created.
Thus, if there already is a file at the given location, that file remains and you start writing into it.
If the new file you create is longer than the old one, this is no problem, you eventually overwrite all the old file content and then the file grows to house the additional new content.
But if the new file you create is shorter than the old one, you have a problem: After the end of the new file there still is data from the old, longer file. Thus, your result is a hodgepodge of two files.
This happened in case of the example files you shared, your new content of "signedPdf.pdf" is only 175982 bytes long but there appears to have been some older file with that name which was 811986 bytes long. Thus, the "signedPdf.pdf" file you shared is 811986 bytes long, the first 175982 bytes containing the result of your operation, the rest data from some other file.
If you cut down your shared "signedPdf.pdf" file to its first 175982 bytes, the result looks much better!
To solve this issue you should use the file mode Create which is documented to be
equivalent to requesting that if the file does not exist, use CreateNew; otherwise, use Truncate.
using (FileStream os = new FileStream(signedPdf, FileMode.Create, FileAccess.Write, FileShare.None))
An issue with your signing service - identity not yet valid
As mentioned above, if you cut down your shared "signedPdf.pdf" file to its first 175982 bytes, the result looks much better! Unfortunately merely better, not yet good:
The reason for your "identity has expired or is not yet valid" becomes clearer by looking at the details:
I.e. the signing time claimed by the PDF is 09:47:59 UTC+1.
But looking at the certificate:
I.e. your certificate is valid not before 09:48:40 UTC+1.
Thus, the claimed signing time is more than half a minute before your user certificate became valid! This obviously cannot be accepted by a validator...
Apparently your signing service creates a short-time certificate for you on demand, valid from just then for half an hour. And the time at which you started creating the PDF signature is not in that interval.
I doubt they will change the design of the signing service for your requirements. Thus, you'll have to cheat a bit and use a signing time slightly in the future.
By default the signing time is set to the current by the PdfSignatureAppearance constructor, i.e. when this line executes:
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
Fortunately you can change this claimed signing time if you immediately use
appearance.SignDate = [some other date time];
The date time you should use here has to be shortly (I'd propose not more than 5 minutes) after the time you will call your signing service.
This of course implies that you cannot arbitrarily wait until executing that service call. As soon as you assigned the claimed signing time above, you are committed to have successfully called your signing service shortly before that claimed time!
Furthermore, if that signing service turns out to react only slowly or only after some retries, your software should definitively check the certificate in the signature container you retrieve from it and compare its validity interval with your claimed signing time. If the claimed signing time is not in that interval, start signing again!
Now it became apparent that the AllPagesSignatureContainer you used was designed for a very special use case and still had to be adapted to your use case.
Adapting the AllPagesSignatureContainer for append mode
The AllPagesSignatureContainer implementation essentially copied from this answer worked fine when not signing in append mode but when signing in append mode it failed.
This at first was plausible because that class has to predict the object number that will be used for the signature value. This prediction depends on the exact use case, and switching on append mode changes this use case considerably. Thus, my advice in a comment was
If you need append mode, try to replace the
PdfLiteral PRefLiteral = ...
line in the AllPagesSignatureContainer by
PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + reader.NumberOfPages) + " 0 R");
In my tests that worked but in your tests it still didn't. An analysis of your signed file turned up the cause: My test file was using cross reference tables while yours was using cross reference streams.
Adapting the AllPagesSignatureContainer for append mode and object streams
iText in append mode uses the compression features of the original file, i.e. in case of your file it creates an object stream as soon as storing an indirect object that allows storage in an object stream.
In case of your file iText reserved an object number for the object stream, and it did so between the time the AllPagesSignatureContainer predicted the signature value object number and the time the signature value actually was generated. Thus, in your file the actual signature value object number was higher than the predicted number by 1.
To solve this for PDFs with cross reference streams, therefore, one can simply replace the PdfLiteral PRefLiteral = ... line by
PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + reader.NumberOfPages + 1) + " 0 R");
i.e. by adding 1 to the originally predicted value. Unfortunately now the prediction is wrong for PDFs with cross reference tables...
A better way to fix this is to force iText to reserve an object number for the object stream for cross reference stream PDFs before predicting the signature value object number and then use the original prediction code. One way to do this is by creating and writing an indirect object right before the prediction, e.g. like this:
stamper.Writer.AddToBody(new PdfNull(), stamper.Writer.PdfIndirectReference, true);
PdfIndirectReference PRef = stamper.Writer.PdfIndirectReference;
PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + reader.NumberOfPages) + " 0 R");
The answer the AllPagesSignatureContainer implementation essentially was copied from has been updated accordingly.
I am using swisscom digital signature service and we have a test account. Well the service requires the hash code the pdf file . We send it with
DIGEST_VALUE=$(openssl dgst -binary -SHA256 $FILE | openssl enc -base64 -A)
and I get a PKCS#7 response. You can decode the my signature response by using this website https://certlogik.com/decoder/
and the signature content is http://not_need_anymore
I have the same problem as follow (because we use the same code)
Digital Signature (PKCS#7 - Deferred Signing) / The document has been altered or corrupted since the signature was applied
my response has been with sha256 crypted.
Well I am using iText with c# to sign the pdf file. I sign and I see some details (such as reason, location etc).
here is the method that creates a pdf file with a signature field
public static string GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName)
{
if (File.Exists(tempPdf))
File.Delete(tempPdf);
using (PdfReader reader = new PdfReader(unsignedPdf))
{
using (FileStream os = File.OpenWrite(tempPdf))
{
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SetVisibleSignature(new Rectangle(36, 748, 250, 400), 1, signatureFieldName);
//IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
PdfSignature external2 = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);//ADBE_PKCS7_SHA1);
//as pdf name I tried also PdfName.ETSI_RFC3161
//(ref => https://github.com/SCS-CBU-CED-IAM/itext-ais/blob/master/src/com/swisscom/ais/itext/PDF.java)
appearance.Reason = "For archive";
appearance.Location = "my loc";
appearance.SignDate = DateTime.Now;
appearance.Contact = "myemail#domain.ch";
appearance.CryptoDictionary = external2;
var level = reader.GetCertificationLevel();
// check: at most one certification per pdf is allowed
if (level != PdfSignatureAppearance.NOT_CERTIFIED)
throw new Exception("Could not apply -certlevel option. At most one certification per pdf is allowed, but source pdf contained already a certification.");
appearance.CertificationLevel = level;
MakeSignature.SignExternalContainer(appearance, external,30000);
byte[] array = SHA256Managed.Create().ComputeHash(appearance.GetRangeStream());
return Convert.ToBase64String(array);
}
}
}
Actualls I do not use what this method returns. Because it already creates a temp pdf file with signature field.
After that,I give the hash code of this pdf file and get PKCS#7 responde. and then using the following function, I am adding the signature to a pdf (it creates another pdf file).
public static void EmbedSignature(string tempPdf, string signedPdf,
string signatureFieldName, string signature)
{
byte[] signedBytes = Convert.FromBase64String(signature);
using (PdfReader reader = new PdfReader(tempPdf))
{
using (FileStream os = File.OpenWrite(signedPdf))
{
IExternalSignatureContainer external =
new MyExternalSignatureContainer(signedBytes);
MakeSignature.SignDeferred(reader, signatureFieldName, os, external);
}
}
}
the signature parameter in the method, I give p7s file content as follows
string signatureContent = File.ReadAllText(#"mypath\signed_cert.p7s");
signatureContent = signatureContent
.Replace("-----BEGIN PKCS7-----\n", "")
.Replace("-----END PKCS7-----\n","").Trim();
what am I missing or doing wrong?
In contrast to the regular detached signatures which sign the whole signed file, integrated signatures in PDFs sign (and can only sign) everything but the space set aside for the signature itself.
(For more backgrounds read this answer)
Thus, when you after preparing the PDF with a placeholder to embed the signature
give the hash code of this pdf file and get PKCS#7 responde
you hash too much because your hashing included the (then empty, i.e. filled with '0' characters) placeholder for the actual signature. The method GetBytesToSign instead only returns the hash over the signed byte ranges, i.e. everything but the placeholder:
byte[] array = SHA256Managed.Create().ComputeHash(appearance.GetRangeStream());
You have to either takes this value or similarly hash only everything but the placeholder for the signature.
Trying to validate PDF signature isn't working. The PDF were signed by Adobe Acrobat and then trying to verify it with the public key of the client certificate.
So I get the public key of the client certificate, hash the PDF and verify if the hash is equal to the pdf signature, but it fails.
HttpClientCertificate cert = request.ClientCertificate;
X509Certificate2 cert2 = new X509Certificate2(cert.Certificate);
PdfReader pdfreader = new PdfReader("path_to_file");
AcroFields fields = pdfreader.AcroFields;
AcroFields.Item item = fields.GetFieldItem("Signature1");
List<string> names = fields.GetSignatureNames();
foreach (string name in names){
PdfDictionary dict = fields.GetSignatureDictionary(name);
PdfPKCS7 pkcs7 = fields.VerifySignature(name);
Org.BouncyCastle.X509.X509Certificate pdfSign = pkcs7.SigningCertificate;
// Get its associated CSP and public key
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert2.PublicKey.Key;
// Hash the data
SHA256 sha256 = new SHA256Managed();
byte[] pdfBytes = System.IO.File.ReadAllBytes("path_to_pdf");
byte[] hash = sha256.ComputeHash(pdfBytes);
// Verify the signature with the hash
bool ok = csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), pdfSing.GetSignature());
}
First, to verify whether the signature correctly you can simply use the PdfPKCS7 object you already retrieved, more exactly its Verify method:
/**
* Verify the digest.
* #throws SignatureException on error
* #return <CODE>true</CODE> if the signature checks out, <CODE>false</CODE> otherwise
*/
virtual public bool Verify()
Thus, you can simply call
bool ok = pkcs7.Verify();
and ok is true only if the document hash matches the hash in the signature.
Concerning your attempt to calculate the document hash like this
byte[] pdfBytes = System.IO.File.ReadAllBytes("path_to_pdf");
byte[] hash = sha256.ComputeHash(pdfBytes);
This indeed gives you the hash value of the complete PDF.
For document types with integrated signatures like PDFs, though, this is not the hash of interest because the complete PDF obviously includes the integrated signature!
Thus, you have to find the space reserved for the signature in the PDF and ignore it during hash calculation, cf. this answer on Information Security Stack Exchange, in particular this image:
In case of multiple signatures you furthermore have to consider that the earlier signatures only sign a former revision of the PDF, so the hash is to be calculated only for a starting segment of the file, cf. this image from the answer referenced above:
The iText(Sharp) method PdfPKCS7.Verify() takes all this into account.
I have a set of questions:
What does a .p7s file contain? I read that it usually is an encrypted message sent to an e-mail, but in my case I have a file and a .p7s file on my hdd. The file is much larger than the .p7s, so I am wondering what is in it (and of course how to later use it).
2.this question occurs mostly because I have no naswer for 1. - If I have my public key in the form of a byte array, how can I verify the signature in C#? I found this on the internet, but again, it uses some text gotten from an e-mail and I honestly don't know what is happening:
public static bool Verify(byte[] signature, string text)
{
X509Certificate2 certificate = new X509Certificate2(#"D:\My-CV.docx.p7s");
if (signature == null)
throw new ArgumentNullException("signature");
if (certificate == null)
throw new ArgumentNullException("certificate");
//hash the text
// Methode 3 for Hashing
System.Security.Cryptography.SHA1 hash3 = System.Security.Cryptography.SHA1.Create();
System.Text.UnicodeEncoding encoder = new System.Text.UnicodeEncoding();
byte[] combined = encoder.GetBytes(text);
byte[] hash3byte = hash3.ComputeHash(combined);
//Adding the text from the email, to a contentInfo
ContentInfo content = new ContentInfo(hash3byte);
// decode the signature
SignedCms verifyCms = new SignedCms(content, true);
verifyCms.Decode(signature);
// verify it
try
{
verifyCms.CheckSignature(new X509Certificate2Collection(certificate), false);
return true;
}
catch (CryptographicException)
{
return false;
}
}
Can someone help me with this?
The .p7s file is containing the signature of your document. The content is not encrypted, but it's look like.
The document file is much larger, because it contains the date, the .p7s the signature.
To check the signature, you need the public part of the certificate with which the document was signed.
You can check this two posts to check the signature :
Check signature for x509 certificate
In C#, sign an xml with a x.509 certificate and check the signature
The last thing is to load the signature data, from the p7s file.
I make some search, and come back to you.
I receive an XML document as a string parameter in my method. The XML document is:
<Document>
<ZipContainer> Zip_File_In_Base64 </ZipContainer>
<X509Certificate> Certificate_In_Base64 </X509Certificate>
</Document>
From this string I extract the ZIP file in base64 format and X509Certificate2 certificate in base64 format. The ZIP file contains:
file describing the contents of the ZIP file as XML (file packageDescription.xml);
files with the contents of transmitted documents (for example, *.doc files);
files with content of detached digital signature (*.p7s files - detached digital signature);
From the archive should be extracted signature that signed documents (detached digital signature may be more than one). Detached digital signature are stored in files with .p7s extension. Each signature must be done to check its agreement with digital signature, with which the user is logged in to the portal.
The must consist of two steps:
See method certificateValidator() (see this method below): This is a of detached signature, contained in the .p7s files with their corresponding files that are signed, these *. P7s-files.
For example: a pair of related files: ZayavUL_3594c921f545406d9b8734bbe28bf894.doc_1.p7s and
ZayavUL_3594c921f545406d9b8734bbe28bf894.doc.
See method certificateValidator(): This verifies certificate from a file .p7s with a certificate that is extracted from the XML document input string.
Questions
The method signatureValidator (see this method below) is not currently used detached signature of the files .p7s. I did try, but without success. How do I properly verify the detached signature of .p7s file for its corresponding file?
In the method certificateValidator (see this method below) how do I verify the conformity of the certificate extracted from the .p7s file, with a certificate extracted from input string in Base64 format?
The line of code foreach (X509Certificate2 x509 in signCms.Certificates) { } ---> Certificates Collection always is empty. Why?
Input parameters
Dictionary <string, byte[]> dictP7SFiles (key - the name of the file *.p7s, value - array of bytes, representing *.p7s file)
Dictionary <string, byte[]> dictNotP7SFiles (key - the name of the file that is signed using detached signature from *.p7s file, value - array of bytes, representing file)
X509Certificate2 userCertX509 - certificate object, extracted from the input xml-document (where it has the format Base64)
Code
Here below are testing implementation of verification steps (see above this 2 steps):
private bool certificateValidator(Dictionary<string, byte[]> dictP7SFiles,
Dictionary<string, byte[]> dictNotP7SFiles, X509Certificate2 userCertX509)
{
bool isValid = false;
try
{
foreach (KeyValuePair<string, byte[]> pair in dictP7SFiles)
{
ContentInfo contentInfo = new ContentInfo(pair.Value);
SignedCms signCms = new SignedCms(contentInfo, true);
if (signCms.Certificates.Count != 0)
{
//Certificates Collection always is empty. Why?
foreach (X509Certificate2 x509 in signCms.Certificates)
{
if ((x509.SerialNumber != userCertX509.SerialNumber)
|| (x509.Thumbprint != userCertX509.Thumbprint))
{
isValid = false;
return isValid;
}
}
isValid = true;
return isValid;
}
}
}
catch (Exception ex)
{
//here process exception code
}
return isValid;
}
private bool signatureValidator(Dictionary<string, byte[]> dictP7SFiles,
Dictionary<string, byte[]> dictNotP7SFiles, X509Certificate2 userCertX509)
{
bool isValid = false;
try
{
byte[] data = dictP7SFiles["ZayavUL_3594c921f545406d9b8734bbe28bf894.doc"];
byte[] publicKey;
byte[] signature;
object hasher = SHA1.Create(); // Our chosen hashing algorithm.
// Generate a new key pair, then sign the data with it:
using (var publicPrivate = new RSACryptoServiceProvider())
{
signature = publicPrivate.SignData(data, hasher);
publicKey = publicPrivate.ExportCspBlob(false); // get public key
}
// Create a fresh RSA using just the public key, then test the signature.
using (var publicOnly = new RSACryptoServiceProvider())
{
publicOnly.ImportCspBlob(publicKey);
isValid = publicOnly.VerifyData(data, hasher, signature); // Return True
//isValid = ByteArrayCompare(dictP7SStreams["ZayavUL_3594c921f545406d9b8734bbe28bf894.doc_1.p7s"], signature);
byte[] p7sDetachedSignature = File.ReadAllBytes(#"D:\ZayavUL_3594c921f545406d9b8734bbe28bf894.doc_1.p7s");
isValid = ByteArrayCompare(p7sDetachedSignature, signature);
}
}
catch (Exception)
{
//here process exception code
}
return isValid;
}
The main thing you are doing wrong is to regenerate the CMS and signature. You should parse the CMS message, then indicate the external content during verification.
How do I properly verify the detached signature of .p7s file for its corresponding file?
Take a look at the following Java code on SO to see how to verify signatures; C# should use the same architecture and it should therefore work similarly.
In the method certificateValidator (see this method below) how do I verify the conformity of the certificate extracted from the .p7s file, with a certificate extracted from input string in Base64 format?
Decode the base 64 certificate and perform chain verification and validation of the certificate. How much validation you want to perform (e.g. checking the effective date) is up to you.
The line of code foreach (X509Certificate2 x509 in signCms.Certificates) { } ---> Certificates Collection always is empty. Why?
You simply didn't put one in during construction of the new CMS structure.
You certainly should not regenerate the signature either. Normally you would not have the private key during verification, and the algorithm may not match the one used within the CMS document. Furthermore, even if it would, signature generation is not always deterministic (you may get different signature values).