pdfbox - document getting corrupted after adding signed attributes - pdfbox

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.

Related

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

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

How to convert MailAttachment to MimeKit Attachment

We have to rework how we're sending emails since we are using Amazon SES. In the past, we were using smtp but can't do that in this case. The class that needs updated has taken in a MailMessage object and used smtp to send it. So I'm trying to rework the method to be able to continue to accept the MailMessage object and convert it to a MimeKit MimeMessage. For the most part it's working fine except when it comes to attachments. In the code I have, the attachment gets added and sent, however, when trying to open it appears it's corrupted or something. In my test case I attached a csv file. I could not open it in excel after receiving the email.
public class EmailAbstraction
{
public virtual void Send(MailMessage mailMessage)
{
sendMessage(mailMessage);
}
private static void sendMessage(MailMessage mailMessage)
{
using (var client = new AmazonSimpleEmailServiceClient(AwsConstants.SESAWSKey, AwsConstants.SESAWSSecret, AwsConstants.RegionEndpoint))
{
foreach (var to in mailMessage.To)
{
using (var messageStream = new MemoryStream())
{
var newMessage = new MimeMessage();
var builder = new BodyBuilder
{
HtmlBody = mailMessage.Body
};
newMessage.From.Add(mailMessage.From == null
? new MailboxAddress(EmailConstants.DefaultFromEmailDisplayName, EmailConstants.DefaultFromEmailAddress)
: new MailboxAddress(mailMessage.From.Address));
newMessage.To.Add(new MailboxAddress(to.DisplayName, to.Address));
newMessage.Subject = mailMessage.Subject;
foreach (var attachment in mailMessage.Attachments)
{
builder.Attachments.Add(attachment.Name, attachment.ContentStream);
}
newMessage.Body = builder.ToMessageBody();
newMessage.WriteTo(messageStream);
var request = new SendRawEmailRequest
{
RawMessage = new RawMessage { Data = messageStream }
};
client.SendRawEmail(request);
}
}
}
}
}
And in my test app, I have this.
internal class Program
{
private static void Main(string[] args)
{
var s = GetFileStream();
var m = new MailMessage();
var sender = new MailAddress("info#ourwebsite.com", "info");
m.From = sender;
m.Sender = sender;
m.Body = "test email";
m.Subject = "test subject";
m.To.Add(myemail);
m.Attachments.Add(new Attachment(s, "test-file.csv"));
new EmailAbstraction().Send(m);
}
private static MemoryStream GetFileStream()
{
var stream = new MemoryStream();
var fileStream = File.Open(#"C:\Users\dev\Desktop\test-file.csv", FileMode.Open);
fileStream.CopyTo(stream);
fileStream.Close();
return stream;
}
}
This is just copied from the MimeKit source code:
static MimePart GetMimePart (System.Net.Mail.AttachmentBase item)
{
var mimeType = item.ContentType.ToString ();
var contentType = ContentType.Parse (mimeType);
var attachment = item as System.Net.Mail.Attachment;
MimePart part;
if (contentType.MediaType.Equals ("text", StringComparison.OrdinalIgnoreCase))
part = new TextPart (contentType);
else
part = new MimePart (contentType);
if (attachment != null) {
var disposition = attachment.ContentDisposition.ToString ();
part.ContentDisposition = ContentDisposition.Parse (disposition);
}
switch (item.TransferEncoding) {
case System.Net.Mime.TransferEncoding.QuotedPrintable:
part.ContentTransferEncoding = ContentEncoding.QuotedPrintable;
break;
case System.Net.Mime.TransferEncoding.Base64:
part.ContentTransferEncoding = ContentEncoding.Base64;
break;
case System.Net.Mime.TransferEncoding.SevenBit:
part.ContentTransferEncoding = ContentEncoding.SevenBit;
break;
//case System.Net.Mime.TransferEncoding.EightBit:
// part.ContentTransferEncoding = ContentEncoding.EightBit;
// break;
}
if (item.ContentId != null)
part.ContentId = item.ContentId;
var stream = new MemoryStream ();
item.ContentStream.CopyTo (stream);
stream.Position = 0;
part.Content = new MimeContent (stream);
return part;
}

noTS LTV to PDF document. Error Signature defined. Must be closed in PdfSignatureAppearance

I would like to sign and add LTV to yet not signed PDF.
I've copied coding from iText examples and qustions
http://developers.itextpdf.com/question/how-enable-ltv-timestamp-signature
Basic part of main method:
Security.addProvider(new BouncyCastleProvider());
EncryptPDF pdf = new EncryptPDF();
OCSPVerifier ocspVerifier = new OCSPVerifier(null, null);
OcspClient ocsp = new OcspClientBouncyCastle(ocspVerifier);
pdf.signPDF(file_src, file_temp, ocsp);
pdf.addLtvNoTS(file_temp, file_dest, ocsp);
Method signPDF has no problem with signing (or now commented encrypting). Temp file is OK.
private void signPDF(String src, String dest, OcspClient ocsp) {
try {
PdfReader reader = new PdfReader(src);
KeyStore ks = KeyStore.getInstance("pkcs12", "BC");
ks.load(new FileInputStream(cert2_src), keystore_password.toCharArray());
String alias = null;
Enumeration<String> en = ks.aliases();
while(en.hasMoreElements()) {
alias = en.nextElement();
System.out.println("alias name: " + alias);
}
PrivateKey pk = (PrivateKey) ks.getKey(alias, key_password.toCharArray());
Certificate[] chain = ks.getCertificateChain(alias);
PdfStamper stamper = PdfStamper.createSignature(reader, new FileOutputStream(dest), '\0', null, true);
// appearance
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setImage(Image.getInstance(res_src));
appearance.setVisibleSignature(new Rectangle(172, 132, 572, 232), 1, "SignatureField");
// Certificate cert = getPublicCertificate(cert_src);
// stamper.setEncryption(new Certificate[]{cert},
// new int[]{PdfWriter.ALLOW_PRINTING}, PdfWriter.ENCRYPTION_AES_128 ); //| PdfWriter.DO_NOT_ENCRYPT_METADATA
// digital signature
ExternalSignature es = new PrivateKeySignature(pk, "SHA-256", "BC");
ExternalDigest digest = new BouncyCastleDigest();
MakeSignature.signDetached(appearance, digest, es, chain, null, ocsp, null, 0, CryptoStandard.CMS);
} catch (FileNotFoundException e){
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
In method addLtvNoTS I'm getting error "Signature defined. Must be closed in PdfSignatureAppearance." on last line stamper.close(). I'm not able to figure it out. Please, help.
private void addLtvNoTS(String src, String dest, OcspClient ocsp)
throws IOException, DocumentException, GeneralSecurityException {
PdfReader r = new PdfReader(src);
FileOutputStream fos = new FileOutputStream(dest);
PdfStamper stamper = PdfStamper.createSignature(r, fos, '\0', null, true);
LtvVerification v = stamper.getLtvVerification();
AcroFields fields = stamper.getAcroFields();
ArrayList<String> names = fields.getSignatureNames();
String sigName = names.get(names.size() - 1);
PdfPKCS7 pkcs7 = fields.verifySignature(sigName);
v.addVerification(sigName, ocsp, null,
LtvVerification.CertificateOption.WHOLE_CHAIN,
LtvVerification.Level.OCSP,
LtvVerification.CertificateInclusion.NO);
stamper.close();
}
Your addLtvNoTS method seems to first have been a copy of the addLtv method in the article which you then incompletely edited to become a copy of the addLtvNoTS method there.
In particular your addLtvNoTS method still contains this line from the article addLtv method
PdfStamper stamper = PdfStamper.createSignature(r, fos, '\0', null, true);
while in the article addLtvNoTS method the corresponding line is this:
PdfStamper stp = new PdfStamper(r, fos, '\0', true);
Thus, you create a PdfStamper for signing or time stamping (so iText eventually complains about you doing neither) while you should create one which is not.

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 LTV enabled - how to add more CRLs?

I need to make a signed pdf LTV enabled. Signing certificate has a chain with three levels (root / public / personal). I know that it is necessary to add OCSP and CRL of certificates in pdf (except root).
Can I use for it basic LtvVerification.addVerification() method?
If I add in one run two CRLs, in the result PDF is only a second. If i change order, is there again a second.
If I add the CRL in two runs, it will end the same way - in pdf remains CRL added as a second.
I thought the "add" will not overwrite the previous state..
How to properly use the method LtvVerification.merge()? Before/after adding first/second/both CRL?
Or i can use only alternative method LtvVerification.addVerification(String signatureName, Collection ocsps, Collection crls, Collection certs)?
Thank you very much for the tips.
Source code:
public void addLtv(String src, String dest) throws IOException, DocumentException, GeneralSecurityException
{
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
PdfReader r = new PdfReader(src);
System.out.println("Source file: " + src);
FileOutputStream fos = new FileOutputStream(dest);
PdfStamper stp = new PdfStamper(r, fos, '\0', true);
LtvVerification v = stp.getLtvVerification();
AcroFields fields = stp.getAcroFields();
ArrayList<String> names = fields.getSignatureNames();
String sigName = names.get(names.size() - 1);
System.out.println("found signature: " + sigName);
PdfPKCS7 pkcs7 = fields.verifySignature(sigName);
//add LTV
OcspClient ocsp = new OcspClientBouncyCastle();
CrlClient crlClient1 = new CrlClientOnline("http://www.postsignum.cz/crl/psrootqca2.crl");
ArrayList<CrlClient> crllist = new ArrayList<CrlClient>();
crllist.add(crlClient1);
CrlClient crlClient2 = new CrlClientOnline("http://www.postsignum.cz/crl/pspublicca2.crl");
crllist.add(crlClient2);
System.out.println("crllist.size=" + crllist.size());
if (pkcs7.isTsp())
{
for (CrlClient crlclient : crllist)
{
if (v.addVerification(sigName, new OcspClientBouncyCastle(), crlclient,
LtvVerification.CertificateOption.SIGNING_CERTIFICATE,
LtvVerification.Level.CRL,
LtvVerification.CertificateInclusion.NO)) {
System.out.println("crl " + crlclient.toString() + " added to timestamp");
}
}
} else{
for (String name : names)
{
for (int i = 0; i < crllist.size(); i++) {
if (v.addVerification(name, ocsp, crllist.get(i),
LtvVerification.CertificateOption.WHOLE_CHAIN,
LtvVerification.Level.CRL,
LtvVerification.CertificateInclusion.NO)) {
System.out.println("crl " + crllist.get(i).toString() + " added to " + name);
}
if (i > 0) {
System.out.println("found verification, merge");
v.merge();
}
}
}
}
stp.close();
}
If you want to provide multiple CRLs to LtvVerification.addVerification, you do not call that method once for each CRL but instead once with all CRLs.
For this CrlClientOnline also accepts multiple URLs:
/**
* Creates a CrlClientOnline instance using one or more URLs.
*/
public CrlClientOnline(String... crls)
Thus, by using this constructor instead we simplify and fix your code to
PdfReader r = new PdfReader(src);
FileOutputStream fos = new FileOutputStream(dest);
PdfStamper stp = new PdfStamper(r, fos, '\0', true);
LtvVerification v = stp.getLtvVerification();
AcroFields fields = stp.getAcroFields();
ArrayList<String> names = fields.getSignatureNames();
String sigName = names.get(names.size() - 1);
System.out.println("found signature: " + sigName);
PdfPKCS7 pkcs7 = fields.verifySignature(sigName);
//add LTV
OcspClient ocsp = new OcspClientBouncyCastle();
CrlClient crlClient = new CrlClientOnline("http://www.postsignum.cz/crl/psrootqca2.crl", "http://www.postsignum.cz/crl/pspublicca2.crl");
if (pkcs7.isTsp())
{
if (v.addVerification(sigName, new OcspClientBouncyCastle(), crlClient,
LtvVerification.CertificateOption.SIGNING_CERTIFICATE,
LtvVerification.Level.CRL,
LtvVerification.CertificateInclusion.NO))
{
System.out.println("crl " + crlClient.toString() + " added to timestamp");
}
}
else
{
for (String name : names)
{
if (v.addVerification(name, ocsp, crlClient,
LtvVerification.CertificateOption.WHOLE_CHAIN,
LtvVerification.Level.CRL,
LtvVerification.CertificateInclusion.NO))
{
System.out.println("crl " + crlClient.toString() + " added to " + name);
}
}
}
stp.close();
(AddLtvCrls.java, method addLtvFixed)
Applying it to your sample file, we get:
For some background, LtvVerification.addVerification stores the information it has as the validation information required for the signature in question. Calling it multiple times results in the information only from the last attempt to count.
Calling LtvVerification.merge does not help either here as it merely merges validation information required for different signatures from older revisions into the new validation related information section.