I am wondering how we can use ITEXT7 to extract image info associated to digital signatures. I know there have been similar questions asked in the past, but they were mostly around ITEXT5, which is quite different from the ITEXT7 after all the updates and modifications to the software.
You can extract the image from a signature appearance using low-level API.
Complete Java code:
private void saveImageFromSignature(PdfDocument document, String fieldName) throws IOException {
PdfAcroForm acroForm = PdfAcroForm.getAcroForm(document, false);
PdfDictionary xObject = acroForm.getField(name)
.getWidgets()
.get(0)
.getNormalAppearanceObject()
.getAsDictionary(PdfName.Resources)
.getAsDictionary(PdfName.XObject)
.getAsStream(new PdfName("FRM"))
.getAsDictionary(PdfName.Resources)
.getAsDictionary(PdfName.XObject);
PdfStream stream = xObject.getAsStream(new PdfName("Im1"));
PdfImageXObject image = new PdfImageXObject(stream);
BufferedImage result = createImageFromBytes(image.getImageBytes());
//pdf allows using masked image in the signature appearance
PdfStream maskStream = (PdfStream) stream.getAsStream(PdfName.SMask);
if (maskStream != null) {
PdfImageXObject maskImage = new PdfImageXObject(maskStream);
BufferedImage maskBimage = createImageFromBytes(maskImage.getImageBytes());
String fileMask = String.format(getOutputFolder() + "/file_mask_%d.%s",
image.getPdfObject().getIndirectReference().getObjNumber(),
image.identifyImageFileExtension());
ImageIO.write(maskBimage,
image.identifyImageFileExtension(),
new File(fileMask));
//the mask defines an alfa channel
Image transpImg = transformToTransperency(maskBimage);
result = applyTransperency(result, transpImg);
}
String filenameComp = String.format(getOutputFolder() + "/file_comp_%d.%s",
image.getPdfObject().getIndirectReference().getObjNumber(),
image.identifyImageFileExtension());
ImageIO.write(result,
image.identifyImageFileExtension(),
new File(filenameComp));
document.close();
}
private Image transformToTransperency(BufferedImage bi) {
ImageFilter filter = new RGBImageFilter() {
#Override
public int filterRGB(int x, int y, int rgb) {
return (rgb << 8) & 0xFF000000;
}
};
ImageProducer ip = new FilteredImageSource(bi.getSource(), filter);
return Toolkit.getDefaultToolkit().createImage(ip);
}
private BufferedImage applyTransperency(BufferedImage bi, Image mask) {
BufferedImage dest = new BufferedImage(
bi.getWidth(), bi.getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = dest.createGraphics();
g2.drawImage(bi, 0, 0, null);
AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.DST_IN, 1.0F);
g2.setComposite(ac);
g2.drawImage(mask, 0, 0, null);
g2.dispose();
return dest;
}
Upd: This works for a very limited number of cases. Thanks for #mkl.
First of all, thank you for the proposals which personally guided me.
After several tries, here is the code that worked for me:
public void extract(String inputFilename, String fieldName) throws IOException {
try (PdfDocument document = new PdfDocument(new PdfReader(inputFilename))){
PdfAcroForm acroForm = PdfAcroForm.getAcroForm(document, false);
final PdfFormField signatorySignature1 = acroForm.getField(fieldName);
final PdfDictionary appearanceDic = signatorySignature1.getPdfObject().getAsDictionary(PdfName.AP);
final PdfStream normalAppearance = appearanceDic.getAsStream(PdfName.N);
final PdfDictionary ressourceDic = normalAppearance.getAsDictionary(PdfName.Resources);
PdfResources resources = new PdfResources(ressourceDic);
final ImageRenderInfo imageRenderInfo = extractImageRenderInfo(normalAppearance.getBytes(), resources);
Files.write(
Path.of(inputFilename + "_" + fieldName + "_" + System.currentTimeMillis() + ".png"),
imageRenderInfo.getImage().getImageBytes());
} catch (Exception e) {
e.printStackTrace();
}
}
public ImageRenderInfo extractImageRenderInfo(byte[] contentBytes, PdfResources pdfResource) {
MyLocationExtractionStrategy strategy = new MyLocationExtractionStrategy();
PdfCanvasProcessor parser = new PdfCanvasProcessor(strategy, new HashMap<>());
parser.processContent(contentBytes, pdfResource);
return strategy.getImageRenderInfo();
}
class MyLocationExtractionStrategy implements ILocationExtractionStrategy {
private ImageRenderInfo imageRenderInfo;
#Override public Collection<IPdfTextLocation> getResultantLocations() {
return null;
}
#Override public void eventOccurred(IEventData iEventData, EventType eventType) {
if (eventType.equals(EventType.RENDER_IMAGE)) {
imageRenderInfo = (ImageRenderInfo)iEventData;
}
}
#Override public Set<EventType> getSupportedEvents() {
return null;
}
public ImageRenderInfo getImageRenderInfo() {
return this.imageRenderInfo;
}
}
I am executiong sql queries from different data sources. And I am getting the results as dynamic objects like following.
private IEnumerable<IDictionary<string, object>> GetDataFromPostgresql()
{
var dataList = new List<IDictionary<string, object>>();
IDictionary<string, object> obj = new ExpandoObject();
obj.Add("first_name", "john");
obj.Add("last_name", "smith");
obj.Add("age", 25);
dataList.Add(new Dictionary<string, object>(obj));
return dataList;
}
private IEnumerable<IDictionary<string, object>> GetDataFromOracle()
{
var dataList = new List<IDictionary<string, object>>();
IDictionary<string, object> obj = new ExpandoObject();
obj.Add("FIRST_NAME", "john");
obj.Add("LAST_NAME", "smith");
obj.Add("AGE", 25);
dataList.Add(new Dictionary<string, object>(obj));
return dataList;
}
private IEnumerable<IDictionary<string, object>> GetDataFromMssql()
{
var dataList = new List<IDictionary<string, object>>();
IDictionary<string, object> obj = new ExpandoObject();
obj.Add("FirstName", "john");
obj.Add("LastName", "smith");
obj.Add("Age", 25);
dataList.Add(new Dictionary<string, object>(obj));
return dataList;
}
I am using these data collections in my asp.net core controller.
[HttpGet]
[Route("")]
public IActionResult Get()
{
var dataList = GetDataFromPostgresql();
return Ok(dataList);
}
But I want to serialize responses a standart camel case format.
{ "firstName": "john", "lastName": "smith", "age": 25 }
So how can I do this?
PascalCase means it has a capitalized first letter, but if this kind of outcome that you are after, { "firstName": "john", "lastName": "smith", "age": 25 }, that's called camelCase.
I am not familiar with nowadays asp.net-core, but I am sure the RegExp would be the same as this JavaScript one.
function toUpperCase(raw, letter) {
return letter.toUpperCase();
}
function asCamelCase(str) {
// put an underscore between camel cases
return str.replace(/([a-z])([A-Z])/g, '$1_$2')
// make the whole string lower case
.toLowerCase()
// capitalize every letter after the underscore
.replace(/_+([^_]|$)/g, toUpperCase);
}
Above example would produce (try by copying and pasting it in your browser console) always firstName, given the following inputs:
asCamelCase("FirstName"); // firstName
asCamelCase("FIRST_NAME"); // firstName
asCamelCase("first_name"); // firstName
I hope this helped 👋
Try to use below code to change the key in Dictionary to CamelCase
[HttpGet]
[Route("")]
public IActionResult Get()
{
var dataList = GetDataFromPostgresql();
var newDataList = new List<IDictionary<string, object>>();
foreach (var data in dataList)
{
var newData = new Dictionary<string, object>();
foreach (var key in data.Keys)
{
var newKey = "";
var value = data[key];
var index = key.IndexOf('_');
if(index < 0)//for GetDataFromMssql()
{
newKey = Char.ToLowerInvariant(key[0]) + key.Substring(1);
}else
{
newKey = key.ToLowerInvariant().Replace("_", string.Empty).Replace(" ", string.Empty);
newKey = newKey.Replace(newKey[index], Char.ToUpperInvariant(newKey[index]));
}
newData.Add(newKey, value);
}
newDataList.Add(newData);
}
return Ok(newDataList);
}
If you have complex value like "first_name_hello_test", you could use below workaround:
[HttpGet]
[Route("")]
public IActionResult Get()
{
var dataList = GetDataFromPostgresql();
var newDataList = new List<IDictionary<string, object>>();
foreach (var data in dataList)
{
var newData = new Dictionary<string, object>();
foreach (var key in data.Keys)
{
var newKey = "";
var value = data[key];
var indexList = new List<int>();
var index = key.IndexOf('_');
if (index < 0)
{
newKey = Char.ToLowerInvariant(key[0]) + key.Substring(1);
}else
{
while (index >= 0)
{
indexList.Add(index);
index = key.IndexOf('_', index + 1);
}
newKey = key.ToLowerInvariant();
foreach(var i in indexList)
{
newKey = newKey.Replace(newKey[i+1], Char.ToUpperInvariant(newKey[i+1]));
}
newKey = newKey.Replace("_", string.Empty).Replace(" ", string.Empty);
}
newData.Add(newKey, value);
}
newDataList.Add(newData);
}
return Ok(newDataList);
}
My code is here; I was selecting PDF files from a SD card or internal phone memory using the showFileChooser() method then from onActivityResult(), the files go to mergePdfFiles(View view) method, and from there the files go to createPdf(String[] srcs).
Here is the complete code of my activity:
//Below is onCreate();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mergedpdf);
adapter = new ArrayAdapter<>(this,
android.R.layout.simple_list_item_1, locations);
// public void fileSelected(File file ) {
// locations.add(file.toString());
// adapter.notifyDataSetChanged();
// }
NewText = (TextView)findViewById(R.id.textView);
AdView mAdView = findViewById(R.id.adView);
AdRequest adRequest = new AdRequest.Builder().build();
mAdView.loadAd(adRequest);
listView = (ListView)findViewById(R.id.list_items);
btn = (ImageView) findViewById(R.id.imageView8);
btnconvert = (ImageView) findViewById(R.id.imageView11);
btnconvert.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// btTag=((Button)v).getTag().toString();
try {
createPdf( locations);
} catch (DocumentException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//Toast.makeText(Mergedpdf.this, "button clicked", Toast.LENGTH_SHORT).show();
}
});
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// btTag=((Button)v).getTag().toString();
showFileChooser();
NewText.setText("Below Files are Selected");
}
});
}
//this is mergedfiles();
public void mergePdfFiles(View view){
Toast.makeText(Mergedpdf.this, "merge function", Toast.LENGTH_SHORT).show();
try {String[] srcs= new String[locations.size()];
for(int i = 0;i<locations.size();i++) {
srcs[i] = locations.get(i);
}
// String[] srcs = {txt1.getText().toString(), txt2.getText().toString()};
createPdf(srcs);
}catch (Exception e){e.printStackTrace();}
}
//This method create merged pdf file
public void createPdf (String[] srcs) {
try {
// Create document object
File docsFolder = new File(Environment.getExternalStorageDirectory() + "/Merged-PDFFiles");
if (!docsFolder.exists()) {
docsFolder.mkdir();
Log.i(TAG, "Created a new directory for PDF");
}
Date date = new Date();
#SuppressLint("SimpleDateFormat") final String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(date);
Document document = new Document();
// pdfCopy = new File(docsFolder.getAbsolutePath(),pdf+"Images.pdf");
// Document document = new Document();
//PdfWriter.getInstance(document, output);
// Create pdf copy object to copy current document to the output mergedresult file
PdfCopy copy = new PdfCopy(document, new FileOutputStream(docsFolder + "/" + timeStamp +"combine.pdf"));
Toast.makeText(Mergedpdf.this, "merged pdf saved", Toast.LENGTH_SHORT).show();
// Open the document
document.open();
PdfReader pr;
int n;
for (int i = 0; i < srcs.length; i++) {
// Create pdf reader object to read each input pdf file
pr = new PdfReader(srcs[i].toString());
// Get the number of pages of the pdf file
n = pr.getNumberOfPages();
for (int page = 1; page <= n; page++) {
// Import all pages from the file to PdfCopy
copy.addPage(copy.getImportedPage(pr, page));
}
}
document.close(); // close the document
} catch (Exception e) {
e.printStackTrace();
}
}
//below is showfilechooser(); method
private void showFileChooser () {
Log.e("AA", "bttag=" + btTag);
String folderPath = Environment.getExternalStorageDirectory() + "/";
Intent intent = new Intent();
intent.setAction(Intent.ACTION_GET_CONTENT);
Uri myUri = Uri.parse(folderPath);
intent.setDataAndType(myUri, "application/pdf");
Intent intentChooser = Intent.createChooser(intent, "Select a file");
startActivityForResult(intentChooser,PICKFILE_RESULT_CODE);
}
//below is onActivityResult(); method
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (data != null) {
if (requestCode == PICKFILE_RESULT_CODE) {
if (resultCode == RESULT_OK) {
String FilePath = data.getData().getPath();
locations.add(FilePath);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.listview,locations);
listView.setAdapter(adapter);
}
}
}
}
//And my declared variables before oncreate().
public com.itextpdf.text.Document Document;
public PdfCopy Copy;
public ByteArrayOutputStream ms;
TextView NewText;
private TextView txt1;
private Button bt1, bt2,bt3;
private Handler handler;
ListView listView;
ArrayList<String> locations = new ArrayList<>();
ArrayAdapter<String> adapter;
ImageView btn, btnconvert, btn3;
private static final String TAG = "PdfCreator";
private final int PICKFILE_RESULT_CODE=10;
private String btTag = "";
So i came across an interesting problem. So I know that in itext/itextsharp when you are signing has a setting on the PdfSignatureAppearance that you can set the CertificationLevel, and that works as expected, but recently i came across the problem where I am signing a document where the first signature in that document is signed wit PdfSignatureAppearance.CERTIFIED_FORM_FILLING, thus allowing me to add/sign signatures aftwards using (PdfSignatureAppearance.NOT_CERTIFIED) which act as approval signatures.
So the reason I am asking the question is that i came across a problem where i have a document with existing signature fields in the document, I sign the first signature with PdfSignatureAppearance.CERTIFIED_FORM_FILLING and the signatures after that with PdfSignatureAppearance.NOT_CERTIFIED, but when i sign the second signature field, the first signature field becomes invalid.
This document I am using was created using FoxitPro, but if I do the exact same thing in Adobe DC Pro it works as expected.
Any advice would be appreciated.
Here is the code i use for signing a pdf document, its only the signer class so other objects and references to this class will be missing.
public byte[] Sign(SignRequest request)
{
request.Document = SaveDocumentChanges(new SaveRequest
{
Document = request.Document,
FormFields = request.FormFields,
SigningBlocks = request.SigningBlocks
}, request.Information);
return SignDocument(request.Certificate, request.Information, request.SigningBlocks, request.SignatureImages, request.Document, request.IsFinalSignature);
}
private byte[] AddFormFields(List<FormField> formFields, byte[] document)
{
for (int i = 0; i < formFields.Count; i++)
{
using (MemoryStream outputStream = new MemoryStream())
{
using (PdfReader reader = new PdfReader(document))
{
using (PdfStamper stamper = CreatePdfStamper(reader, outputStream, false))
{
if (!DoesFormFieldExist(reader, formFields[i].Name))
{
CreateFormField(reader, stamper, formFields[i]);
}
else
{
UpdateFormField(stamper, formFields[i]);
}
}
}
document = outputStream.ToArray();
}
}
return document;
}
private byte[] AddMetaData(SigningInformation information, byte[] document)
{
if (information.CustomProperties != null && information.CustomProperties.Any())
{
using (MemoryStream outputStream = new MemoryStream())
{
using (PdfReader reader = new PdfReader(document))
{
using (PdfStamper stamper = CreatePdfStamper(reader, outputStream, false))
{
Dictionary<string, string> currentProperties = reader.Info;
foreach (string key in information.CustomProperties.Keys)
{
AddMetaDataAddDictionaryValue(currentProperties, key, information.CustomProperties[key]);
}
AddMetaDataAddDictionaryValue(currentProperties, "Producer", "Signisure");
AddMetaDataAddDictionaryValue(currentProperties, "Creator", "Signisure");
AddMetaDataAddDictionaryValue(currentProperties, "Author", "Signisure");
stamper.MoreInfo = currentProperties;
}
}
return outputStream.ToArray();
}
}
return document;
}
private void AddMetaDataAddDictionaryValue(Dictionary<string, string> dictionary, string key, string value)
{
if (dictionary.ContainsKey(key))
{
dictionary[key] = value;
}
else
{
dictionary.Add(key, value);
}
}
private void AddMetaDataAddDictionaryValue(PdfDictionary dictionary, PdfName key, PdfObject value)
{
if (!dictionary.Keys.Contains(key))
{
dictionary.Put(key, value);
}
}
private byte[] AddSignatureFields(List<SigningBlock> signingBlocks, byte[] document)
{
for (int i = 0; i < signingBlocks.Count; i++)
{
using (MemoryStream outputStream = new MemoryStream())
{
using (PdfReader reader = new PdfReader(document))
{
using (PdfStamper stamper = CreatePdfStamper(reader, outputStream, false))
{
if (!DoesSignatureFieldExist(reader, signingBlocks[i].Name))
{
CreateSignatureField(stamper, signingBlocks[i]);
}
}
}
document = outputStream.ToArray();
}
}
return document;
}
private void CreateFormField(PdfReader reader, PdfStamper stamper, FormField formField)
{
TextField field = new TextField(stamper.Writer, new Rectangle(formField.X, formField.Y, formField.X + formField.Width, formField.Y + formField.Height), formField.Name);
field.Text = formField.Value;
field.Font = BaseFont.CreateFont(BaseFont.TIMES_ROMAN, BaseFont.CP1252, BaseFont.EMBEDDED);
field.FontSize = 12;
field.Options = TextField.READ_ONLY;
stamper.AddAnnotation(field.GetTextField(), formField.Page);
}
private PdfSignatureAppearance CreatePdfAppearance(PdfStamper stamper, SigningInformation information, string location, bool certify)
{
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION;
CreatePdfAppearanceCertifyDocument(appearance, certify);
if (information != null)
{
appearance.Location = location != String.Empty ? String.Format("{0} ({1})", location, information.IPAddress) : information.IPAddress;
appearance.Reason = information.SignatoryReason;
appearance.Contact = String.Format("{0} ({1})", information.Signatory, information.SignatoryEmail);
}
return appearance;
}
private void CreatePdfAppearanceCertifyDocument(PdfSignatureAppearance appearance, bool certify)
{
if (certify)
{
appearance.CertificationLevel = PdfSignatureAppearance.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS;
}
else
{
appearance.CertificationLevel = PdfSignatureAppearance.NOT_CERTIFIED;
}
}
private PdfStamper CreatePdfStamper(PdfReader reader, MemoryStream outputStream, bool isSignature)
{
if (CreatePdfStamperIsPDFADocument(reader))
{
if (isSignature)
{
return PdfAStamper.CreateSignature(reader, outputStream, _pdfVersion, null, true, PdfAConformanceLevel.PDF_A_1A);
}
else
{
return new PdfAStamper(reader, outputStream, _pdfVersion, true, PdfAConformanceLevel.PDF_A_1A);
}
}
else
{
if (isSignature)
{
return PdfStamper.CreateSignature(reader, outputStream, _pdfVersion, null, true);
}
else
{
return new PdfStamper(reader, outputStream, _pdfVersion, true);
}
}
}
private bool CreatePdfStamperIsPDFADocument(PdfReader reader)
{
if (reader.Metadata != null && reader.Metadata.Length > 0)
{
IXmpMeta xmpMeta = XmpMetaParser.Parse(reader.Metadata, null);
IXmpProperty pdfaidConformance = xmpMeta.GetProperty(XmpConst.NS_PDFA_ID, "pdfaid:conformance");
IXmpProperty pdfaidPart = xmpMeta.GetProperty(XmpConst.NS_PDFA_ID, "pdfaid:part");
if (pdfaidConformance == null || pdfaidPart == null)
{
return false;
}
return true;
}
return false;
}
private void CreateSignatureField(PdfStamper stamper, SigningBlock signingBlock)
{
if (signingBlock == null)
{
return;
}
PdfFormField signatureField = PdfFormField.CreateSignature(stamper.Writer);
signatureField.SetWidget(new Rectangle(signingBlock.X, signingBlock.Y, signingBlock.X + signingBlock.Width, signingBlock.Y + signingBlock.Height), null);
signatureField.Flags = PdfAnnotation.FLAGS_PRINT;
signatureField.FieldName = signingBlock.Name;
signatureField.Page = signingBlock.Page;
signatureField.Put(PdfName.CONTENTS, new PdfString(String.Empty));
CreateSignatureFieldAddLockProperties(signatureField, signingBlock, stamper);
stamper.AddAnnotation(signatureField, signingBlock.Page);
}
private void CreateSignatureFieldAddLockProperties(PdfFormField signatureField, SigningBlock signingBlock, PdfStamper stamper)
{
if (signingBlock.LinkedFormFields != null && signingBlock.LinkedFormFields.Count > 0)
{
PdfSigLockDictionary lockDictionary = new PdfSigLockDictionary(PdfSigLockDictionary.LockAction.INCLUDE, signingBlock.LinkedFormFields.ToArray());
signatureField.Put(PdfName.LOCK, stamper.Writer.AddToBody(lockDictionary).IndirectReference);
}
}
private bool DoesFormFieldExist(PdfReader reader, string formFieldName)
{
if (String.IsNullOrWhiteSpace(formFieldName))
{
return false;
}
return reader.AcroFields.Fields.Where(vp => vp.Key == formFieldName).Any();
}
private bool DoesSignatureFieldExist(PdfReader reader, string signatureFieldName)
{
if (String.IsNullOrWhiteSpace(signatureFieldName))
{
return false;
}
return reader.AcroFields.DoesSignatureFieldExist(signatureFieldName);
}
private AcroFields.FieldPosition GetAcroFieldByName(PdfStamper stamper, string signatureBlockName)
{
return stamper.AcroFields.GetFieldPositions(signatureBlockName).FirstOrDefault();
}
private List<string> GetAllSignatureFieldNames(PdfReader reader)
{
List<string> signatureFields = new List<string>();
signatureFields.AddRange(reader.AcroFields.GetBlankSignatureNames());
signatureFields.AddRange(reader.AcroFields.GetSignatureNames());
return signatureFields;
}
private void GetDocumentFormFieldsBuildFormFields(List<FormField> formFields, PdfReader reader, PdfStamper stamper)
{
List<string> signatureFields = GetAllSignatureFieldNames(reader);
foreach (KeyValuePair<string, AcroFields.Item> field in reader.AcroFields.Fields)
{
string fieldName = field.Key.ToString();
if (!signatureFields.Where(signatureFieldName => signatureFieldName == fieldName).Any())
{
string fieldValue = reader.AcroFields.GetField(field.Key.ToString());
AcroFields.FieldPosition formFieldPosition = GetAcroFieldByName(stamper, fieldName);
formFields.Add(GetDocumentFormFieldsBuildFormFieldsCreateField(formFieldPosition, fieldName, fieldValue));
}
}
}
private FormField GetDocumentFormFieldsBuildFormFieldsCreateField(AcroFields.FieldPosition formFieldPosition, string fieldName, string fieldValue)
{
return new FormField
{
Height = (int)formFieldPosition.position.Height,
Name = fieldName,
Page = formFieldPosition.page,
Width = (int)formFieldPosition.position.Width,
X = (int)formFieldPosition.position.Left,
Y = (int)formFieldPosition.position.Top,
Value = fieldValue
};
}
private void GetDocumentSignatureBlocksBuildSignatureBlocks(List<SigningBlock> signatureBlocks, List<string> signatureBlockNames, PdfReader reader, PdfStamper stamper, bool isSigned)
{
foreach (string signatureBlockName in signatureBlockNames)
{
AcroFields.FieldPosition signatureFieldPosition = GetAcroFieldByName(stamper, signatureBlockName);
signatureBlocks.Add(GetDocumentSignatureBlocksBuildSignatureBlocksCreateBlock(signatureFieldPosition, signatureBlockName, isSigned));
}
}
private SigningBlock GetDocumentSignatureBlocksBuildSignatureBlocksCreateBlock(AcroFields.FieldPosition signatureFieldPosition, string fieldName, bool isSigned)
{
return new SigningBlock
{
Height = (int)signatureFieldPosition.position.Height,
Name = fieldName,
Page = signatureFieldPosition.page,
Width = (int)signatureFieldPosition.position.Width,
X = (int)signatureFieldPosition.position.Left,
Y = (int)signatureFieldPosition.position.Top,
IsSigned = isSigned
};
}
private string GetFormFieldValueForName(PdfStamper stamper, string formFieldName)
{
AcroFields formFields = stamper.AcroFields;
return formFields.GetField(formFieldName);
}
private byte[] GetSignatureImage(List<MemberItemSignature> signatureImages, string signingBlockName)
{
MemberItemSignature signature = signingBlockName.Contains("Initial") ? signatureImages.Where(image => image.Use == SignatureUses.Initial).FirstOrDefault() : signatureImages.Where(image => image.Use == SignatureUses.Signature).FirstOrDefault();
if (signature != null)
{
return signature.Image;
}
return null;
}
private byte[] SaveDocumentChanges(SaveRequest request, SigningInformation information)
{
request.Document = AddMetaData(information, request.Document);
request.Document = AddFormFields(request.FormFields, request.Document);
request.Document = AddSignatureFields(request.SigningBlocks, request.Document);
return request.Document;
}
private byte[] SignDocument(Certificate certificate, SigningInformation information, List<SigningBlock> signingBlocks, List<MemberItemSignature> signatureImages, byte[] document, bool isFinalSignature)
{
for (int i = 0; i < signingBlocks.Count; i++)
{
document = SignDocumentSignSignatureField(certificate, information, signingBlocks[i], signatureImages, document, true);
}
if (isFinalSignature)
{
return SignDocumentLTVVerification(certificate, document);
}
return document;
}
private byte[] SignDocumentLTVVerification(Certificate certificate, byte[] document)
{
using (MemoryStream outputStream = new MemoryStream())
{
using (PdfReader reader = new PdfReader(document))
{
using (PdfStamper stamper = PdfStamper.CreateSignature(reader, outputStream, '\0', null, true))
{
SignDocumentSigningBlockAddLTVVerification(stamper, certificate);
}
}
return outputStream.ToArray();
}
}
private void SignDocumentSigningBlock(SigningComponents components, SigningInformation information, SigningBlock block, PdfSignatureAppearance appearance, PdfStamper stamper, byte[] signatureImage)
{
appearance.SetVisibleSignature(block.Name);
SignDocumentSigningBlockWithImage(signatureImage, appearance);
SignDocumentSigningBlockWithText(appearance, information, appearance.SignDate);
if (components.Certificate != null)
{
using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)components.Certificate.PrivateKey)
{
PrivateKeySignature privateKeySignature = SignDocumentSigningBlockBuildDigestSigningMethod(information, rsa) as PrivateKeySignature;
SignatureHelper.Sign(appearance, privateKeySignature, components.CertificateChain, new List<ICrlClient> { components.CrlClient }, components.OcspClient, components.TimeStampingAuthority, Int32.Parse(_settingManager["DocumentSigningEstimatedDigestSize"]), CryptoStandard.CMS, SignDocumentSigningBlockCreateMetaData(information));
}
}
else
{
HSMExternalSignature hsmExternalSignature = SignDocumentSigningBlockBuildDigestSigningMethod(information, null) as HSMExternalSignature;
SignatureHelper.Sign(appearance, hsmExternalSignature, components.TimeStampingAuthority, Int32.Parse(_settingManager["DocumentSigningEstimatedDigestSize"]), CryptoStandard.CMS, SignDocumentSigningBlockCreateMetaData(information));
}
}
private void SignDocumentSigningBlockAddLTVVerification(PdfStamper stamper, Certificate certificate)
{
SigningComponents components = new SigningComponents(_settingManager, certificate);
LtvVerification ltvVerification = stamper.LtvVerification;
List<string> signatureFieldNames = stamper.AcroFields.GetSignatureNames();
PdfPKCS7 pkcs7 = stamper.AcroFields.VerifySignature(signatureFieldNames.Last());
if (pkcs7.IsTsp)
{
bool validationAddedSuccessfully = ltvVerification.AddVerification(signatureFieldNames.Last(), components.OcspClient, components.CrlClient, LtvVerification.CertificateOption.SIGNING_CERTIFICATE, LtvVerification.Level.OCSP_CRL, LtvVerification.CertificateInclusion.YES);
}
else
{
foreach (string name in stamper.AcroFields.GetSignatureNames())
{
bool validationAddedSuccessfully = ltvVerification.AddVerification(name, components.OcspClient, components.CrlClient, LtvVerification.CertificateOption.WHOLE_CHAIN, LtvVerification.Level.OCSP_CRL, LtvVerification.CertificateInclusion.YES);
}
}
ltvVerification.Merge();
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
LtvTimestamp.Timestamp(appearance, components.TimeStampingAuthority, null);
}
private IExternalSignature SignDocumentSigningBlockBuildDigestSigningMethod(SigningInformation information, RSACryptoServiceProvider rsaCryptoProvider)
{
if (information.CertificateUse == CertificateUse.SignisureCertificate || rsaCryptoProvider == null)
{
return new HSMExternalSignature(_hsmService, _settingManager["DocumentSigningEncryptionHashAlgorithm"]);
}
else
{
return new PrivateKeySignature(DotNetUtilities.GetRsaKeyPair(rsaCryptoProvider).Private, _settingManager["DocumentSigningEncryptionHashAlgorithm"]);
}
}
private PdfDictionary SignDocumentSigningBlockCreateMetaData(SigningInformation information)
{
PdfDictionary signatureDictionary = new PdfDictionary();
if (!String.IsNullOrWhiteSpace(information.IdentificationInformation))
{
AddMetaDataAddDictionaryValue(signatureDictionary, new PdfName(".Signisure.IdentificationInformation"), new PdfString(information.IdentificationInformation));
}
if (!String.IsNullOrWhiteSpace(information.JuristicEntity))
{
AddMetaDataAddDictionaryValue(signatureDictionary, new PdfName(".Signisure.JuristicEntity"), new PdfString(information.JuristicEntity));
}
if (!String.IsNullOrWhiteSpace(information.Capacity))
{
AddMetaDataAddDictionaryValue(signatureDictionary, new PdfName(".Signisure.Capacity"), new PdfString(information.Capacity));
}
return signatureDictionary;
}
private void SignDocumentSigningBlockWithImage(byte[] signatureImage, PdfSignatureAppearance appearance)
{
if (signatureImage != null && signatureImage.Length > 0)
{
Image signatureImageInstance = Image.GetInstance(ImageHelper.FlattenImage(signatureImage));
appearance.Image = signatureImageInstance;
appearance.SignatureGraphic = signatureImageInstance;
}
}
private void SignDocumentSigningBlockWithText(PdfSignatureAppearance appearance, SigningInformation information, DateTime timestampDateTime)
{
BaseFont verdana = BaseFont.CreateFont(AssemblyDirectory + "\\Content\\Fonts\\Verdana\\Verdana.ttf", BaseFont.CP1252, BaseFont.EMBEDDED);
BaseFont helvetica = BaseFont.CreateFont(AssemblyDirectory + "\\Content\\Fonts\\Helvetica\\Helvetica.ttf", BaseFont.CP1252, BaseFont.EMBEDDED);
BaseFont comicSans = BaseFont.CreateFont(AssemblyDirectory + "\\Content\\Fonts\\ComicSans\\ComicSans.ttf", BaseFont.CP1252, BaseFont.EMBEDDED);
appearance.Layer2Text = SignDocumentSigningBlockWithTextBuildText(appearance, information, timestampDateTime);
appearance.Layer2Font = new Font(verdana);
}
private string SignDocumentSigningBlockWithTextBuildText(PdfSignatureAppearance appearance, SigningInformation information, DateTime timestampDateTime)
{
return String.Format("Signee: {0}\nSign date: {1}\nLocation: {2}\nReason: {3}", information.Signatory, timestampDateTime.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss zzz"), appearance.Location, appearance.Reason);
}
private byte[] SignDocumentSignSignatureField(Certificate certificate, SigningInformation information, SigningBlock signingBlock, List<MemberItemSignature> signatureImages, byte[] document, bool isVisible)
{
SigningComponents components = new SigningComponents(_settingManager, certificate);
using (MemoryStream outputStream = new MemoryStream())
{
using (PdfReader reader = new PdfReader(document))
{
using (PdfStamper stamper = CreatePdfStamper(reader, outputStream, true))
{
PdfSignatureAppearance appearance = CreatePdfAppearance(stamper, information, SignDocumentSignSignatureFieldBuildLocation(stamper, signingBlock), false);
SignDocumentSigningBlock(components, information, signingBlock, appearance, stamper, GetSignatureImage(signatureImages, signingBlock.Name));
}
}
return outputStream.ToArray();
}
}
private string SignDocumentSignSignatureFieldBuildLocation(PdfStamper stamper, SigningBlock signingBlock)
{
StringBuilder builder = new StringBuilder();
for (int index = 0; index < signingBlock.LinkedFormFields.Count; index++)
{
builder.Append(GetFormFieldValueForName(stamper, signingBlock.LinkedFormFields[index]));
if (index + 1 < signingBlock.LinkedFormFields.Count)
{
builder.Append(", ");
}
}
return builder.ToString();
}
private void UpdateFormField(PdfStamper stamper, FormField formField)
{
AcroFields formFields = stamper.AcroFields;
if (formField.Value != null && GetFormFieldValueForName(stamper, formField.Name) != formField.Value)
{
formFields.SetField(formField.Name, formField.Value);
formFields.SetFieldProperty(formField.Name, "setfflags", PdfFormField.FF_READ_ONLY, null);
}
}
}
}
Short answer:
If you sign a document with a certification signature with level PdfSignatureAppearance.CERTIFIED_FORM_FILLING, then you can add as many extra approval signatures as you want without breaking the original (or preceding) signatures.
Longer answer:
Nobody will believe you if you write:
I am trying to sign a document with PdfSignatureAppearance.CERTIFIED_FORM_FILLING first, that should allow me to add more signature fields, edit form fields or sign other signature fields, but instead it is invalidating my subsequent signature signing.
What you're saying here is wrong.
However, maybe you're not signing the PDF correctly. Allow me to repeat my short answer:
If you sign a document with a certification signature with level PdfSignatureAppearance.CERTIFIED_FORM_FILLING, then you can add as many extra approval signatures as you want without breaking the original (or preceding) signatures.
I am highlighting two concepts. A PDF can have at most one certification signature (aka author signature) and this signature should be the first signature in the document. A PDF can have several approval signatures (aka recipient signatures).
Maybe you are signing the document using a certification signature (I assume this is true because you talk about the certification level). And maybe you are trying to add a second certification signature. Obviously, this would break the signatures in the PDF because the PDF specification only allows a single certification signature.
Maybe you're problem is solved by adding approval signatures instead of certification signatures.