Itext7 PDF Sign Problem - The document has been altered or corrupted since the signature was applied - pdf

I have a program that signs pdf's via smartcard. Right now there is a new card that I need to integrate, but there is an error with the signature of the PDF (The document has been altered or corrupted since the signature was applied).
Error
I thought this is strange since there is no such error with the other cards..
The only difference between the cards is that this new one I use iaik to get the sign hash and not direct APDU commands, so, I'm in doubt if the signing problem is related with my implementation of the IAIK or I just need to change the way of signing with Itext 7 on this particular card.
Here is some code:
public byte[] signPDFDocument(byte[] pdfData, String requestKey, String requestIV, UserCertificates uc, String xLocation, String yLocation, String height, String width, String pageToSign) throws IOException{
int numPageToSign = Integer.parseInt(pageToSign);
InputStream inputStream = new ByteArrayInputStream(pdfData);
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
PdfReader reader = new PdfReader(inputStream);
PdfSigner pdfSigner = new PdfSigner(reader, outStream, new StampingProperties());
//check page to sign not outOfBounds
if(numPageToSign > pdfSigner.getDocument().getNumberOfPages() || numPageToSign <= 0)
{
numPageToSign = 1;
}
//Certs Chain
Certificate[] certChain = new Certificate[1];
certChain[0] = uc.getCert();
// Create the signature appearance
PdfFont font = PdfFontFactory.createFont(FontConstants.TIMES_ROMAN);
PdfFont fontBold = PdfFontFactory.createFont(FontConstants.TIMES_BOLD);
Rectangle rect = new Rectangle(Integer.parseInt(xLocation), Integer.parseInt(yLocation), Integer.parseInt(width), Integer.parseInt(height));
PdfSignatureAppearance signatureAppearance = pdfSigner.getSignatureAppearance();
signatureAppearance
// Specify if the appearance before field is signed will be used
// as a background for the signed field. The "false" value is the default value.
.setReuseAppearance(false)
.setPageRect(rect)
.setPageNumber(numPageToSign)
.setReasonCaption("")
.setLocationCaption("");
pdfSigner.setFieldName("signature");
PdfFormXObject n0 = signatureAppearance.getLayer0();
PdfCanvas n0Canvas = new PdfCanvas(n0, pdfSigner.getDocument());
PdfFormXObject n2 = signatureAppearance.getLayer2();
Canvas n2Canvas = new Canvas(n2, pdfSigner.getDocument());
CertificateInfo.X500Name x500name = CertificateInfo.getSubjectFields((X509Certificate)certChain[0]);
String name = null;
if (x500name != null) {
name = x500name.getField("CN");
if (name == null)
name = x500name.getField("E");
}
//Signature
Text first = new Text("Assinado por: ").setFont(font).setFontSize(9);
Text second = new Text(name).setFontSize(8).setFont(fontBold);
Paragraph paragraph = new Paragraph().add(first).add(second);
paragraph.setMarginBottom(0.07f);
n2Canvas.add(paragraph);
//Date
Text date = new Text("Data: ").setFont(font).setFontSize(9);
Text date2 = new Text(new SimpleDateFormat("dd-MM-yyyy HH:mm Z").format(Calendar.getInstance().getTime())).setFont(fontBold).setFontSize(8);
paragraph = new Paragraph().add(date).add(date2);
n2Canvas.add(paragraph);
n2Canvas.close();
IExternalDigest digest = new BouncyCastleDigest();
IExternalSignature externalSignature = new SmartCardSignaturePDF();
// IExternalSignature externalSignature = new PrivateKeySignature(pk,"SHA-256",p.getName());
// OCSPVerifier ocspVerifier = new OCSPVerifier(null, null);
// IOcspClient ocspClient = new OcspClientBouncyCastle(ocspVerifier);
try {
pdfSigner.signDetached(digest, externalSignature, certChain, null, null, null, 0, CryptoStandard.CADES);
} catch (IOException | GeneralSecurityException e) {
}
return outStream.toByteArray();
}
And the External Signature
public class SmartCardSignaturePDF implements IExternalSignature{
#Override
public String getHashAlgorithm() {
return DigestAlgorithms.SHA256;
}
#Override
public String getEncryptionAlgorithm() {
return "RSA";
}
#Override
public byte[] sign(byte[] message) throws GeneralSecurityException {
try {
return signData(MessageDigest.getInstance("SHA-256").digest(message));
} catch (HardwareException | SignatureException e) {
}
return null;
}
}
And the code for the signature from the card
private byte[] signData(byte[] input_data) throws HardwareException {
//Key Mechanism, Label and ID
Mechanism KeyPairGenerationMechanism = Mechanism.get(PKCS11Constants.CKM_RSA_PKCS);
char [] signTemplate = "Sign".toCharArray();
byte[] id = "Sign".getBytes();
//Private Key Template
RSAPrivateKey rsaPrivateKeyTemplate = new RSAPrivateKey();
//rsaPrivate
rsaPrivateKeyTemplate.getToken().setBooleanValue(Boolean.TRUE);
rsaPrivateKeyTemplate.getId().setByteArrayValue(id);
rsaPrivateKeyTemplate.getLabel().setCharArrayValue(signTemplate);
//Public Key Template
RSAPublicKey rsaPublicKeyTemplate = new RSAPublicKey();
//rsaPublicKeyTemplate.getVerify().setBooleanValue(value);
rsaPublicKeyTemplate.getToken().setBooleanValue(Boolean.TRUE);
rsaPublicKeyTemplate.getId().setByteArrayValue(id);
rsaPublicKeyTemplate.getLabel().setCharArrayValue(signTemplate);
try {
this.cegerCardsession.findObjectsInit(rsaPrivateKeyTemplate);
PKCS11Object[] key = this.cegerCardsession.findObjects(1024);
if(key.length == 0)
{
KeyPairGenerationMechanism = Mechanism.get(PKCS11Constants.CKM_RSA_PKCS_KEY_PAIR_GEN);
KeyPair keyPair = this.cegerCardsession.generateKeyPair(KeyPairGenerationMechanism, rsaPublicKeyTemplate, rsaPrivateKeyTemplate);
KeyPairGenerationMechanism = Mechanism.get(PKCS11Constants.CKM_RSA_PKCS);
this.cegerCardsession.signInit(KeyPairGenerationMechanism, keyPair.getPrivateKey());
}
else
{
PrivateKey PKey = new PrivateKey();
PKey = (PrivateKey) key[0];
this.cegerCardsession.signInit(KeyPairGenerationMechanism, PKey);
}
byte[] signedData = this.cegerCardsession.sign(input_data);
return signedData;
} catch (TokenException e) {
throw new HardwareException(e.getMessage(), e.getCause());
}
}
Also, a link with a signed pdf file and the original one
Pdf's
I'm completly stuck for several days now and I have no idea on how to fix this :S

Related

pdfbox - document getting corrupted after adding signed attributes

I am trying to digitally sign document using pdfbox. I am using filter as FILTER_ADOBE_PPKLITE and subfilter as SUBFILTER_ETSI_CADES_DETACHED. For ETSI_CADES_Detached, a signing attribute is needs to be added. I am fetching signed hash and certificates from CSC> But after adding signing attribute, it is making the document corrupted. Sharing the screenshot for the reference error image.
Seems like hash is getting chagned. Sharing code for the reference.
PDDocument document = PDDocument.load(inputStream);
outFile = File.createTempFile("signedFIle", ".pdf");
Certificate[] certificateChain = retrieveCertificates(requestId, providerId, credentialId, accessToken);//Retrieve certificates from CSC.
setCertificateChain(certificateChain);
// sign
FileOutputStream output = new FileOutputStream(outFile);
IOUtils.copy(inputStream, output);
// create signature dictionary
PDSignature signature = new PDSignature();
// signature.setType(COSName.SIG);
// PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(null);
int accessPermissions = SigUtils.getMDPPermission(document);
if (accessPermissions == 1)
{
throw new IllegalStateException("No changes to the document are permitted due to DocMDP transform parameters dictionary");
}
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ETSI_CADES_DETACHED);
signature.setName("Test Name");
// signature.setLocation("Bucharest, RO");
// signature.setReason("PDFBox Signing");
signature.setSignDate(Calendar.getInstance());
Rectangle2D humanRect = new Rectangle2D.Float(location.getLeft(), location.getBottom(), location.getRight(), location.getTop());
PDRectangle rect = createSignatureRectangle(document, humanRect);
SignatureOptions signatureOptions = new SignatureOptions();
signatureOptions.setVisualSignature(createVisualSignatureTemplate(document, 0, rect, signature));
signatureOptions.setPage(0);
document.addSignature(signature, signatureOptions);
ExternalSigningSupport externalSigning =
document.saveIncrementalForExternalSigning(output);
InputStream content = externalSigning.getContent();
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
X509Certificate cert = (X509Certificate) certificateChain[0];
gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));
MessageDigest digest = MessageDigest.getInstance("SHA-256");
// Use a buffer to read the input stream in chunks
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = content.read(buffer)) != -1) {
digest.update(buffer, 0, bytesRead);
}
byte[] hashBytes = digest.digest();
ESSCertIDv2 certid = new ESSCertIDv2(
new AlgorithmIdentifier(new ASN1ObjectIdentifier("*****")),
MessageDigest.getInstance("SHA-256").digest(cert.getEncoded())
);
SigningCertificateV2 sigcert = new SigningCertificateV2(certid);
final DERSet attrValues = new DERSet(sigcert);
Attribute attr = new Attribute(PKCSObjectIdentifiers.id_aa_signingCertificateV2, attrValues);
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(attr);
AttributeTable atttributeTable = new AttributeTable(v);
//Create a standard attribute table from the passed in parameters - certhash
CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator(atttributeTable);
final byte[] signedHash = signHash(requestId, providerId, accessToken, hashBytes); //Retrieve signed hash from CSC.
ContentSigner nonSigner = new ContentSigner() {
#Override
public byte[] getSignature() {
return signedHash;
}
#Override
public OutputStream getOutputStream() {
return new ByteArrayOutputStream();
}
#Override
public AlgorithmIdentifier getAlgorithmIdentifier() {
return new DefaultSignatureAlgorithmIdentifierFinder().find( "SHA256WithRSA" );
}
};
org.bouncycastle.asn1.x509.Certificate cert2 = org.bouncycastle.asn1.x509.Certificate.getInstance(ASN1Primitive.fromByteArray(cert.getEncoded()));
JcaSignerInfoGeneratorBuilder sigb = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build());
// RevocationInfoResponse revocationInfoResponse = sealingService.getRevocationInfo(requestId, accessToken, revocationInfoRequest);
sigb.setSignedAttributeGenerator(attrGen);
// sigb.setDirectSignature( true );
gen.addSignerInfoGenerator(sigb.build(nonSigner, new X509CertificateHolder(cert2)));
CMSTypedData msg = new CMSProcessableInputStream( inputStream);
CMSSignedData signedData = gen.generate((CMSTypedData)msg, false);
byte[] cmsSignature = signedData.getEncoded();
inputStream.close();
externalSigning.setSignature(cmsSignature);
IOUtils.closeQuietly(signatureOptions);
return new FileInputStream(outFile);
If I use subfilter as SUBFILTER_ADBE_PKCS7_DETACHED and don't add addtibutesTable, then it works fine. But for SUBFILTER_ETSI_CADES_DETACHED, attributes needs to be added.

Extract images of signatures contained in a pdf file with iText7

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;
}
}

Signed pdf with selfsigned digital id in PdfBox is blank

I'm trying to sign existing pdf document with pdfbox. My class implements SignatureInterface interface, uses bouncycastle as provider and selfsigned cert stored in .jks file. But in output I get a blank page. What's wrong with my code?
public class CreateSignature implements SignatureInterface {
private PrivateKey privateKey;
private Certificate[] cert;
private CreateSignature(KeyStore keyStore, String certPassword) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {
privateKey = (PrivateKey) keyStore.getKey("1", certPassword.toCharArray()); // "1" - alias default name
cert = keyStore.getCertificateChain("1");
}
#Override
public byte[] sign(InputStream inputStream) throws IOException {
byte[] c = IOUtils.toByteArray(inputStream);
List<Certificate> certList = new ArrayList<>();
certList.add(cert[0]);
try {
Store certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
org.bouncycastle.asn1.x509.Certificate certificate = org.bouncycastle.asn1.x509.Certificate.getInstance(ASN1Primitive.fromByteArray(cert[0].getEncoded()));
ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(privateKey);
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(sha1Signer, new X509CertificateHolder(certificate)));
gen.addCertificates(certs);
CMSTypedData msg = new CMSProcessableByteArray(c);
CMSSignedData signedData = gen.generate(msg,false);
return signedData.getEncoded();
} catch (CertificateEncodingException | OperatorCreationException | CMSException e) {
throw new RuntimeException(e);
}
}
public static byte[] signPdfDocument(PDDocument document) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException {
String keystorePassword = AppVars.getPdfCertificationKeystorePass();
String adobeDigitalIDPassword = AppVars.getPdfCertificationAdobePass();
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(CreateSignature.class.getResourceAsStream("/pdf/certificate/SelfSignedCert.jks"), keystorePassword.toCharArray());
CreateSignature singing = new CreateSignature(ks, adobeDigitalIDPassword);
return singing.doSign(document);
}
private byte[] doSign(PDDocument document) throws IOException {
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setName("name");
signature.setLocation("location");
signature.setReason("reason");
signature.setSignDate(Calendar.getInstance());
document.addSignature(signature, this);
ByteArrayOutputStream out = new ByteArrayOutputStream();
document.saveIncremental(out);
document.close();
return out.toByteArray();
}
}
As discussed in the comments - the cause was that the document was built in the application itself after loading, and not loaded from a PDF file (or stream). The incremental signing would not identify these changes as such, so the result was the PDF at the time of loading, plus a signature that was not shown, probably becaus the document did not even have a page. What gave it away was that the signed PDF was much smaller than the unsigned PDF.
The solution is
Create your PDF, create your content
Save it
Close it
Reload the PDF and sign it.

PDF setting that does not allow changes after signing

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.

itext signing pdf

I'm currently developing an ERP application which uses iText for creating and signing PDF files. The idea is that the app can generate a PDF file with a bill, then use PdfStamper or any other class to sign it with a digital signature. Here's my code:
CREATING AND EDITING THE BILL
File f1 = null;
f1 = new File("myFilePath");
f1.delete();
if ((f1 != null) && (f1.createNewFile()))
{
//Here I call the procedure that creates the bill
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(f1));
PdfFormField field = PdfFormField.createSignature(writer);
PdfSigner pdfSigner = new PdfSigner();
pdfSigner.setFileIn(f1.getAbsolutePath());
pdfSigner.setFileKey("myKeyPath.pfx");
pdfSigner.setFileKeyPassword("myPassword");
pdfSigner.setFileOut("myOutputPath");
if (pdfGenerator.factura(writer, pdfSigner, document))
{
//Here I show the File to the user
}
}
else
{
//Here I show an error
}
PROCEDURE "factura"
public boolean factura (PdfWriter writer, PdfSigner signer, Document document) throws NullPointerException
{
try
{
//Here I set a PdfPageEvent before attaching it to the PdfWriter
writer.setPageEvent(myPdfPageEvent);
document.open();
//Here I manipulate the Document to generate the bill
signer.signPdf();
document.close();
return true;
}
//catch 4 or 5 different types of exceptions and return false if needed
}
CLASS PdfSigner
public class PdfSigner
{
private String fileKey = null;
private String fileKeyPassword = null;
private String fileIn = null;
private String fileOut = null;
public PdfSigner() {}
public boolean signPdf() throws IOException, DocumentException, Exception
{
if (fileKey == null || fileKeyPassword == null || fileIn == null || fileOut == null) return false;
try
{
KeyStore ks = KeyStore.getInstance("pkcs12");
ks.load(new FileInputStream(fileKey), fileKeyPassword.toCharArray());
String alias = (String) ks.aliases().nextElement();
PrivateKey key = (PrivateKey) ks.getKey(alias, fileKeyPassword.toCharArray());
Certificate[] chain = ks.getCertificateChain(alias);
//BOOOOOM!
PdfReader pdfReader = new PdfReader((new File(fileIn)).getAbsolutePath());
FileOutputStream outputFile = new FileOutputStream(fileOut);
PdfStamper pdfStamper = PdfStamper.createSignature(pdfReader, outputFile, '?');
PdfSignatureAppearance sap = pdfStamper.getSignatureAppearance();
sap.setCrypto(key, chain, null, PdfSignatureAppearance.SELF_SIGNED);
sap.setVisibleSignature(new Rectangle(10, 10, 50, 30), 1, "sign_rbl");
pdfStamper.close();
return true;
}
catch (Exception key)
{
throw new Exception(key);
}
}
//getters and setters
}
Well, this is wrong, but I don't know where does it fail. If I try to run it, it usually throw an exception in the line that I've marked with a "BOOOOM!", before setting the PdfReader. But if I try to sign it outside the procedure "factura", after closing the document, the exception is usually thrown almost in the last line, when I close the PdfStamper. In both cases, the cause is always the same: "PDF header signature not found."
Anyone has an idea of what's happening? I am 99% sure the paths I'm giving to the program are right, and the digital signature and password are also right...
Thanks
PS: I swear I've tried to find a solution among the multiple answers in this page, but none of them proved to be of any use to me