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.
Related
This question already has an answer here:
Embed user-specific data into an authenticode signed installer on download
(1 answer)
Closed 1 year ago.
I've an EXE which is code signed and saved on the server. When any user wants to download the EXE, I've to embed a user specific information into the EXE which I get to know when the download request comes from the user.
Constraints:
I can't rebuild EXE.
I can't resign EXE.
Whatever I do, I have to make sure the integrity of content and signature remains intact.
I came to know that it is possible to store custom data in the signature field from this blog post signing. This blog mentioned three ways:
Padding/stuffing
Unauthenticated attributes of signature
Additional certificate field of signature
The first technique mentioned over there is padding. In this technique we can leverage the unsed bytes in the last 8 bytes of the signature as signature always takes up space which is in multiles of 8.
So let's say if signature is of 60 bytes then 64 bytes will be allocated which is the nearest multiple of 8. Signature will use 60 bytes and we can use remaining 4 bytes to stuff our custom data.
I observed a couple of signed EXEs saw that we can indeed get 1 to 7 bytes:
git-bash.exe - 2 byte
cmtrace.exe - 3 bytes
chrome.exe - 0 bytes
iisexpress.exe - 0 bytes
iexplore.exe - 2 bytes
But as you can see above in some cases it can be zero as well. The C# .NET Core code below tells me the starting position of signature and length of signature in any EXE:
public static byte[] ExtractPadding(string filePath, out long signatureStartLocation, out int signatureLengthInUse)
{
signatureLengthInUse = 0;
using (var file = new PortableExecutable(filePath))
{
var dosHeader = file.GetDosHeader();
var peHeader = file.GetPEHeader(dosHeader);
var signatureLocation = peHeader.DataDirectories[ImageDataDirectoryEntry.IMAGE_DIRECTORY_ENTRY_SECURITY];
signatureStartLocation = signatureLocation.VirtualAddress;
using (var signatureData = file.ReadDataDirectory(signatureLocation))
{
using (var reader = new BinaryReader(signatureData))
{
var winCertLength = reader.ReadUInt32();
var winCertRevision = reader.ReadUInt16();
var winCertType = reader.ReadUInt16();
if (winCertRevision != 0x200 && winCertRevision != 0x100)
{
return null;
}
if (winCertType != 0x0002)
{
return null;
}
using (var memoryStream = new MemoryStream())
{
int read;
Span<byte> buffer = stackalloc byte[0x400];
while ((read = reader.Read(buffer)) > 0)
{
memoryStream.Write(buffer.Slice(0, read));
}
var winCertificate = memoryStream.ToArray();
var signer = new SignedCms();
signer.Decode(winCertificate);
var roundTrip = signer.Encode();
var sizeDifference = winCertificate.Length - roundTrip.Length;
var difference = new byte[sizeDifference];
signatureLengthInUse = roundTrip.Length;
Buffer.BlockCopy(winCertificate, roundTrip.Length, difference, 0, difference.Length);
return difference;
}
}
}
}
}
For my use case 7 bytes of space is very limiting as I need to pass data little more than that. Can someone help me in this if there is any other way or extension of the current approach which can help me write more data in signature field without invalidating the EXE or signature?
Say you find a piece of the file that can contain some random bytes and isn't affected by the signature, and you stuff your unique identifier in there... what's to stop any end-user from doing the exact same thing, essentially defeating your copy-protection? That's the whole point of code signing - to stop people from messing with a file in this exact manner.
If you "can't" (which I suspect is more like "don't want to") re-build and re-sign the file for each unique user, you can't embed your unique identifier into it. The fact that an arbitrary blog post from half a decade ago notes this technique doesn't mean that it works today, or that it will continue to work in the future, or that Microsoft won't flag your EXE as malicious. If you want to colour outside the lines, be prepared to be burned.
I try to sign a pdf file using my smartcard (USB token) but encounter "Document has been altered or corrupted since it was signed" error when I open the signed pdf file in Adobe. The error is not so descriptive and I'm not sure where to look at because the code seems good to me but apparently it's not..
The code that I use is:
var signer = smartCardManager.getSigner("myTokenPassword");
var toBeSignedHash = GetHashOfPdf(File.ReadAllBytes(#"xxx\pdf.pdf"), cert.asX509Certificate2().RawData, "dsa", null, false);
var signature = signer.sign(toBeSignedHash);
var signedPdf = EmbedSignature(cert.getBytes(), signature);
File.WriteAllBytes(#"xxx\signedpdf.pdf", signedPdf);
public byte[] GetHashOfPdf(byte[] unsignedFile, byte[] userCertificate, string signatureFieldName, List<float> location, bool append)
{
byte[] result = null;
var chain = new List<Org.BouncyCastle.X509.X509Certificate>
{
Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(new X509Certificate2(userCertificate))
};
Org.BouncyCastle.X509.X509Certificate certificate = chain.ElementAt(0);
using (PdfReader reader = new PdfReader(unsignedFile))
{
using (var os = new MemoryStream())
{
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0', null, append);
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(0,0,0,0), 1, signatureFieldName);
appearance.Certificate = certificate;
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, 8192);
Stream data = appearance.GetRangeStream();
byte[] hash = DigestAlgorithms.Digest(data, "SHA256");
var signatureContainer = new PdfPKCS7(null, chain, "SHA256", false);
byte[] signatureHash = signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
result = DigestAlgorithms.Digest(new MemoryStream(signatureHash), "SHA256");
this.hash = hash;
this.os = os.ToArray();
File.WriteAllBytes(#"xxx\temp.pdf", this.os);
}
}
return result;
}
public byte[] EmbedSignature(byte[] publicCert, byte[] sign)
{
var chain = new List<Org.BouncyCastle.X509.X509Certificate>
{
Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(new X509Certificate2(publicCert))
};
var signatureContainer = new PdfPKCS7(null, chain, "SHA256", false);
using (var reader = new PdfReader(this.os))
{
using (var os2 = new MemoryStream())
{
signatureContainer.SetExternalDigest(sign, null, "RSA");
byte[] encodedSignature = signatureContainer.GetEncodedPKCS7(this.hash, null, null, null, CryptoStandard.CMS);
IExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSignature);
MakeSignature.SignDeferred(reader, "dsa", os2, external);
return os2.ToArray();
}
}
}
The pdf file that I try to sign is this.
Temp pdf file that is created after adding signature fields is this.
Signed pdf file is this.
Base64 format of the hash that is signed is: klh6CGp7DUzayt62Eusiqjr1BFCcTZT4XdgnMBq7QeY=
Base64 format of the signature is: Uam/J6W0YX99rVP4M9mL9Lg9l6YzC2yiR4OtJ18AH1PtBVaNPteT3oPS7SUc+6ak2LfijgJ6j1RgdLamodDPKl/0E90kbBenry+/g1Ttd1bpO8lqTn1PWJU2TxeGHwyRyaFBOUga2AxpErIHrwxfuKCBcodB7wvAqRjso0jovnyP/4DluyOPm97QWh4na0S+rtUWOdqVmKGOuGJ3sBXuk019ewpvFcqWBX4Mvz7IKV56wcxQVQuJLCiyXsMXoazwyDCvdteaDz05K25IVwgEEjwLrppnc/7Ue9a9KVadFFzXWXfia7ndmUCgyd70r/Z+Oviu9MIAZL8GuTpkD7fJeA==
I use hex encoding of byte arrays here. Your base64 encoded hash
klh6CGp7DUzayt62Eusiqjr1BFCcTZT4XdgnMBq7QeY=
in hex encoding is equal to
92587A086A7B0D4CDACADEB612EB22AA3AF504509C4D94F85DD827301ABB41E6
###In short
Your code hashes the signed attributes twice. Simply don't hash the bytes returned by signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS) in GetHashOfPdf but instead use the authenticated attribute bytes themselves as return value.
###In detail
Analyzing the signature in your example PDF it turns out that
indeed the hash of the signed attributes is
92587A086A7B0D4CDACADEB612EB22AA3AF504509C4D94F85DD827301ABB41E6
but the hash in the RSA encrypted DigestInfo object of the signature is
1DC7CAA50D88243327A9D928D5FB4F1A61CBEFF9E947D393DDA705BD61B67F25
which turns out to be the hash of the before mentioned hash of the signed attributes.
Thus, your
var signature = signer.sign(toBeSignedHash);
call appears to hash the toBeSignedHash value again.
The most simple fix would be to replace
byte[] signatureHash = signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
result = DigestAlgorithms.Digest(new MemoryStream(signatureHash), "SHA256");
by
result = signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
in GetHashOfPdf to have only signer.sign do the hashing.
###Analyzing such issues
In a comment you asked
how did you figure all this out :)?
Well, yours is not the first question with a customized iText signing process resulting in errors or at least unwanted profiles.
In the course of the analysis of those questions the first step usually is to extract the embedded signature container and inspect it in an ASN.1 viewer.
In case of your PDF the main result of that inspection was that the signature as such looked ok and that your signed attributes don't contain any variable data.
If there had been some variable data (e.g. a signing time attribute) in them, a candidate for the cause of the issue would have been that you build the signed attributes twice, once explicitly in GetHashOfPdf, once implicitly in EmbedSignature, with different values for the variable data. But as mentioned above, this was not the case.
The next step here was to actually check the hashes involved. Checking the document hash is simple, one calculates the signed byte range hash and compares with the value of the MessageDigest signed attribute, cf. the ExtractHash test testSotnSignedpdf (in Java).
The result for your PDF turned out to be ok.
The following step was to inspect the signature container more thoroughly. In this context I once started to write some checks but did not get very far, cf. the SignatureAnalyzer class. I extended it a bit for the test of the hash of the signed attributes making use of the signature algorithm you used, the old RSASSA-PKCS1-v1_5: In contrast to many other signature algorithms, this one allows to trivially extract the signed hash.
Here the result for your PDF turned out not to be ok, the hash of the signed attributes differed from the signed hash.
There are two often seen causes for a mismatch here,
either the signed attributes are signed with a wrong encoding (it must be the regular DER encoding, not some arbitrary BER encoding and in particular not an encoding with the implicit tag the value stored in the signature has --- even larger players do this wrong sometimes, e.g. Docusign, cf. DSS-1343)
or the hash was somehow transformed during signing (e.g. the hash is base64 encoded or hashed again).
As it turned out, the latter was the case here, the hash was hashed again.
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.
I am trying to sing a pdf using a remote web service which returns a XML signature that consists of PKCS#1 signature with end users certificate.
I need to use this signature to sign pdf via IText deferred signing because the web service works asynchronously.
All the IText examples uses PKCS#7 message format but I am not sure what should I do for XML signature.
Code That Adds Empty Signature Field and Gets Signable Bytes of Pdf
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);
MakeSignature.SignExternalContainer(appearance, external, 8192);
byte[] array = SHA256Managed.Create().ComputeHash(appearance.GetRangeStream());
return Convert.ToBase64String(array);
}
}
}
Code That Opens Temp Pdf And Embeds The Received Signature
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 returned XML Signature from web service:
<SignatureValue Id="Signature-xxx-SIG">
RMFbYIigsrjYQEc4PCoHMMg8vwz/hYrCjj0IR+835BZZ/TsTMHZhMVnu2KQZi1UL
dWMeuhTxagBmjdBSzGiy1OEdH5r0FM77Of4Zz99o/aAhYqr3qpOETGgNn9GHlphL
5FSPYbNsq2rDHA8FsNdqNdb6qJUZYsfYvuhJaUMstBXeL/dLIT46t7ySCQ7CGjE5
mpD1AG8M+TVWf4ut5tucZuZV9PMQB3tyoarQD5RoUv872RzB5IorcIhLnf+O6E6o
EF0HuGitRhN9bbPgdLaUma5MNjKCaeQTpCXp3KXwi8VoQGd5fpUBZbAKtMpKeCts
RAOk1O4uk/4poic4IGPhDw==
</SignatureValue>
<KeyInfo>
<KeyValue>
<RSAKeyValue>
<Modulus>
AI5T0zOBBD4i7Cb1v8wDL0+By8i1h2U0HDFQ73/iNBkM9Gd7mUU0i2A9wJTeiktS
IeBU/JLzqVp7vK857dZlrlT9qiH2cNQufh+q2MpNk7wtPcDACVedVkBUNkIgoXBy
g4cGmAYoWBsD2zDXiZXukStjUWws+/xCU0hADIelFONr141zRHindfq86QrDTC7q
bHBtDT7dJckWzaDPHf3Xlej+U/A1x/8kd504VZaFQfAPYBOgGPY918G1HjBtlajR
nyrl10LuV708IHZtAmKmEfdZOeq//9OGZZLh2nJ86b5Fa6XPFhxzLx/ugBS/8GHt
iVYeJOlfHXRl5w2k2Vv/ssU=
</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</KeyValue>
<X509Data>
<X509Certificate>
MIIGpTCCBY2gAwIBAgIRAJvlsG1D+P++OcU8W8D8FnkwDQYJKoZIhvcNAQELBQAw
bzELMAkGA1UEBhMCVFIxKDAmBgNVBAoMH0VsZWt0cm9uaWsgQmlsZ2kgR3V2ZW5s
aWdpIEEuUy4xNjA0BgNVBAMMLVRFU1QgVHVya2NlbGwgTW9iaWwgTkVTIEhpem1l
dCBTYWdsYXlpY2lzaSBTMjAeFw0xNzA4MjUxMjQ3MjFaFw0xODA4MjUxMjQ3MjFa
MEoxCzAJBgNVBAYTAlRSMREwDwYDVQQKDAhGaXJlIExMVDEUMBIGA1UEBRMLNzY1
NDM0NTY3NjUxEjAQBgNVBAMMCU1lcnQgSW5hbjCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAI5T0zOBBD4i7Cb1v8wDL0+By8i1h2U0HDFQ73/iNBkM9Gd7
mUU0i2A9wJTeiktSIeBU/JLzqVp7vK857dZlrlT9qiH2cNQufh+q2MpNk7wtPcDA
CVedVkBUNkIgoXByg4cGmAYoWBsD2zDXiZXukStjUWws+/xCU0hADIelFONr141z
RHindfq86QrDTC7qbHBtDT7dJckWzaDPHf3Xlej+U/A1x/8kd504VZaFQfAPYBOg
GPY918G1HjBtlajRnyrl10LuV708IHZtAmKmEfdZOeq//9OGZZLh2nJ86b5Fa6XP
FhxzLx/ugBS/8GHtiVYeJOlfHXRl5w2k2Vv/ssUCAwEAAaOCA18wggNbMEIGCCsG
AQUFBwEBBDYwNDAyBggrBgEFBQcwAYYmaHR0cDovL3Rlc3RvY3NwMi5lLWd1dmVu
LmNvbS9vY3NwLnh1ZGEwHwYDVR0jBBgwFoAUT9gSazAfQrnZruIq9+dJFZ7E9OUw
ggFyBgNVHSAEggFpMIIBZTCBsQYGYIYYAwABMIGmMDYGCCsGAQUFBwIBFipodHRw
Oi8vd3d3LmUtZ3V2ZW4uY29tL2RvY3VtZW50cy9ORVNVRS5wZGYwbAYIKwYBBQUH
AgIwYBpeQnUgc2VydGlmaWthLCA1MDcwIHNhecSxbMSxIEVsZWt0cm9uaWsgxLBt
emEgS2FudW51bmEgZ8O2cmUgbml0ZWxpa2xpIGVsZWt0cm9uaWsgc2VydGlmaWth
ZMSxcjCBrgYJYIYYAwABAQEDMIGgMDcGCCsGAQUFBwIBFitodHRwOi8vd3d3LmUt
Z3V2ZW4uY29tL2RvY3VtZW50cy9NS05FU0kucGRmMGUGCCsGAQUFBwICMFkaV0J1
IHNlcnRpZmlrYSwgTUtORVNJIGthcHNhbcSxbmRhIHlhecSxbmxhbm3EscWfIGJp
ciBuaXRlbGlrbGkgZWxla3Ryb25payBzZXJ0aWZpa2FkxLFyLjBdBgNVHR8EVjBU
MFKgUKBOhkxodHRwOi8vdGVzdHNpbC5lLWd1dmVuLmNvbS9FbGVrdHJvbmlrQmls
Z2lHdXZlbmxpZ2lBU01LTkVTSS1TMi9MYXRlc3RDUkwuY3JsMA4GA1UdDwEB/wQE
AwIGwDCBgwYIKwYBBQUHAQMEdzB1MAgGBgQAjkYBATBpBgtghhgBPQABp04BAQxa
QnUgc2VydGlmaWthLCA1MDcwIHNheWlsaSBFbGVrdHJvbmlrIEltemEgS2FudW51
bmEgZ8O2cmUgbml0ZWxpa2xpIGVsZWt0cm9uaWsgc2VydGlmaWthZGlyMFAGA1Ud
CQRJMEcwFAYIKwYBBQUHCQIxCAQGQW5rYXJhMB0GCCsGAQUFBwkBMREYDzE5Nzgx
MjMxMjAwMDAwWjAQBggrBgEFBQcJBDEEBAJUUjAYBgNVHREEETAPgQ1maXJlQGZp
cmUuY29tMB0GA1UdDgQWBBTYUEJX62lhEzkZLSrTdSIdE9n8KzANBgkqhkiG9w0B
AQsFAAOCAQEAV/MY/Tp7n7eG7/tWJW+XlDgIPQK1nAFgvZHm+DodDJ8Bn7CPWmv8
EBmcNxx5PYMoG7y54E6+KzVyjdPBu5o0YtWyKlLKnH1St+EtptOmt29VFAODjalb
Q0An9361DxIDRHbMnQOaJiRTwMlIhED5VDa3OTUhBr7bNlVkp3N5Vs2RuoSdNypR
fq4D/cCpakVugsFyPk4zhWkGhTS5DlL7c5KYqCnU4ugpC33IRJGB05PSdjICeX7Y
N0oAdhzF+5QZEwePjgrDb+ROVpXYaGVMWICA8N2tOXuqdDYoGAzj1YemnPqrSn8a
2iwqbWnFujwep5w5C/TCFVaQWa4NGncMOw==
</X509Certificate>
</X509Data>
</KeyInfo>
When I use the returned PKCS#1 signature with above code, the signature verification fails with "Error encountered while BER decoding".
Since the BlankSignatureContainer created with ADBE_PKCS7_DETACHED subFilter, I think this is normal. However, I am not sure what filter and subFilter I should use for PKCS#1? Or Should / Can I create PKCS#7 message from PKCS#1 and user's certificate and use this format instead?
EDIT 1:
I can also retrieve the end users certificate before requesting the signature.
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Body>
<ns1:MSS_ProfileQueryResponse xmlns:ns1="/mobilesignature/validation/soap">
<MSS_ProfileResp MinorVersion="1" MajorVersion="1" xmlns:ns2="http://www.w3.org/2000/09/xmldsig#" xmlns:ns3="http://www.w3.org/2001/04/xmlenc#" xmlns:ns4="http://www.w3.org/2003/05/soap-envelope" xmlns:ns5="http://uri.etsi.org/TS102204/v1.1.2#">
<ns5:AP_Info Instant="2017-09-16T04:54:43.260Z" AP_PWD="turkcell123" AP_TransID="_1371012883260" AP_ID="http://turkcell.com.tr"/>
<ns5:MSSP_Info Instant="2017-09-16T13:33:36.316+02:00">
<ns5:MSSP_ID/>
</ns5:MSSP_Info>
<ns5:SignatureProfile>
<ns5:mssURI>http://MobileSignature/profile2</ns5:mssURI>
<ns5:CertIssuerDN> Mobil NES Hizmet Saglayicisi S2</ns5:CertIssuerDN>
<ns5:CertSerialNumber>71408272140747005781907792964830346324</ns5:CertSerialNumber>
<ns5:CertHash>
<ns5:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ns5:DigestValue>e1yKlaPIU95phxxYvrUsmSQDpKqKU60/b+a8BLw1wNM=</ns5:DigestValue>
</ns5:CertHash>
<ns5:msspUrl>http://localhost</ns5:msspUrl>
<ns5:certIssuerDN-DER>MG8xCzAJBgNVBAYTAlRSMSgwJgYDVQQKDB9FbGVrdHJvbmlrIEJpbGdpIEd1dmVubGlnaSBBLlMuMTYwNAYDVQQDDC1URVNUIFR1cmtjZWxsIE1vYmlsIE5FUyBIaXptZXQgU2FnbGF5aWNpc2kgUzI=</ns5:certIssuerDN-DER>
</ns5:SignatureProfile>
<ns5:Status>
<ns5:StatusCode Value="100"/>
<ns5:StatusMessage>REQUEST_OK</ns5:StatusMessage>
</ns5:Status>
</MSS_ProfileResp>
</ns1:MSS_ProfileQueryResponse>
</soap:Body>
</soap:Envelope>
EDIT 2:
I have updated my signing code to construct a CMS. However, the resulting hash value to be signed is 77 bytes. The web service excepts 32 bytes SHA256 hashed data.
public static byte[] GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName, byte[] x509Signature)
{
if (File.Exists(tempPdf))
File.Delete(tempPdf);
var chain = new List<Org.BouncyCastle.X509.X509Certificate>
{
Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(new X509Certificate2(x509Signature))
};
Org.BouncyCastle.X509.X509Certificate certificate = chain.ElementAt(0);
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);
appearance.Certificate = chain[0];
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, 8192);
Stream data = appearance.GetRangeStream();
byte[] hash = DigestAlgorithms.Digest(data, "SHA256");
var signature = new PdfPKCS7(null, chain, "SHA256", false);
byte[] signatureHash = signature.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
_signature = signature;
_apperance = appearance;
_hash = hash;
_signatureHash = signatureHash;
return signatureHash;
}
}
}
public static void EmbedSignature(string tempPdf, string signedPdf, string signatureFieldName, byte[] signedBytes)
{
using (PdfReader reader = new PdfReader(tempPdf))
{
using (FileStream os = File.OpenWrite(signedPdf))
{
_signature.SetExternalDigest(signedBytes, null, "RSA");
byte[] encodedSignature = _signature.GetEncodedPKCS7(_hash, null, null, null, CryptoStandard.CMS);
IExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSignature);
MakeSignature.SignDeferred(reader, signatureFieldName, os, external);
}
}
}
(This answer summarizes the evolution of working code in the course of comments and edits to the question.)
Which option does PDF support
The PDF format (the commonly supported ISO 32000-1:2008) specified embedded signatures using either naked PKCS#1 signatures or full PKCS#7/CMS signature containers, cf. section 12.8.3 "Signature Interoperability", in particular
section 12.8.3.2 "PKCS#1 Signatures" and
section 12.8.3.3 "PKCS#7 Signatures as used in ISO 32000".
Newer standards, like the ETSI PAdES standards and the new ISO 32000-2:2017, forbid or deprecate the former option. For a new solution that shall not be outdated already on the day it is published, therefore, one should go for the latter choice.
Knowing the certificate beforehand
I am trying to sing a pdf using a remote web service which returns a XML signature that consists of PKCS#1 signature with end users certificate.
If that were all the functionality of the web service, there'd be a problem: Both when embedding a naked PKCS#1 signature and when constructing a PKCS#7 signature container, one needs to know the end entity certificate before calling the service to create a signature for a hash because that certificate or a reference to it must be embedded in the signed PDF data or the signed CMS attributes.
(Strictly speaking very basic CMS signature container do not require this but all signature profiles to take seriously do.)
Fortunately it turned out (edit 1) that one
can access to another service "This service enables Application Providers to request end user’s certificate serial number and certificate hash which can be used in constructing the data to be signed."
The code
The OP found iText/Java code for implementing the functionality to sign a PDF with an embedded PKCS#7/CMS signature container based on a signing service as described above (edit 2).
However, the resulting hash value to be signed is 77 bytes. The web service excepts 32 bytes SHA256 hashed data.
As it turned out, though, those 77 bytes were not the hash of the signed attributes:
public static byte[] GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName, byte[] x509Signature)
{
[...]
byte[] signatureHash = signature.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
[...]
return signatureHash;
}
They were the signed attributes bytes. Thus, these bytes merely needed to be hashed to produce the hash to send to the signing service for creating a signature.
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, ...