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.