com.lowagie.text.pdf.PdfSignatureAppearance Maven / Gradle / Ivy
/*
* $Id: PdfSignatureAppearance.java 4065 2009-09-16 23:09:11Z psoares33 $
*
* Copyright 2004-2006 by Paulo Soares.
*
* The contents of this file are subject to the Mozilla Public License Version 1.1
* (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the License.
*
* The Original Code is 'iText, a free JAVA-PDF library'.
*
* The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
* the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
* All Rights Reserved.
* Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
* are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
*
* Contributor(s): all the names of the contributors are added in the source code
* where applicable.
*
* Alternatively, the contents of this file may be used under the terms of the
* LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
* provisions of LGPL are applicable instead of those above. If you wish to
* allow use of your version of this file only under the terms of the LGPL
* License and not to allow others to use your version of this file under
* the MPL, indicate your decision by deleting the provisions above and
* replace them with the notice and other provisions required by the LGPL.
* If you do not delete the provisions above, a recipient may use your version
* of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MPL as stated above or under the terms of the GNU
* Library General Public License as published by the Free Software Foundation;
* either version 2 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
* details.
*
* If you didn't download this code from the following link, you should check if
* you aren't using an obsolete version:
* https://github.com/LibrePDF/OpenPDF
*/
package com.lowagie.text.pdf;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.security.PrivateKey;
import java.security.cert.CRL;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import com.lowagie.text.Chunk;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.Font;
import com.lowagie.text.Image;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Phrase;
import com.lowagie.text.Rectangle;
import com.lowagie.text.error_messages.MessageLocalization;
import com.lowagie.text.ExceptionConverter;
/**
* This class takes care of the cryptographic options and appearances that form
* a signature.
*/
public class PdfSignatureAppearance {
/**
* The rendering mode is just the description
*/
public static final int SignatureRenderDescription = 0;
/**
* The rendering mode is the name of the signer and the description
*/
public static final int SignatureRenderNameAndDescription = 1;
/**
* The rendering mode is an image and the description
*/
public static final int SignatureRenderGraphicAndDescription = 2;
/**
* The self signed filter.
*/
public static final PdfName SELF_SIGNED = PdfName.ADOBE_PPKLITE;
/**
* The VeriSign filter.
*/
public static final PdfName VERISIGN_SIGNED = PdfName.VERISIGN_PPKVS;
/**
* The Windows Certificate Security.
*/
public static final PdfName WINCER_SIGNED = PdfName.ADOBE_PPKMS;
public static final int NOT_CERTIFIED = 0;
public static final int CERTIFIED_NO_CHANGES_ALLOWED = 1;
public static final int CERTIFIED_FORM_FILLING = 2;
public static final int CERTIFIED_FORM_FILLING_AND_ANNOTATIONS = 3;
private static final float TOP_SECTION = 0.3f;
private static final float MARGIN = 2;
private Rectangle rect;
private Rectangle pageRect;
private final PdfTemplate[] app = new PdfTemplate[5];
private PdfTemplate frm;
private final PdfStamperImp writer;
private String layer2Text;
private String reason;
private String location;
private Calendar signDate;
private String provider;
private int page = 1;
private String fieldName;
private PrivateKey privKey;
private CRL[] crlList;
private PdfName filter;
private boolean newField;
private ByteBuffer sigout;
private OutputStream originalout;
private File tempFile;
private PdfDictionary cryptoDictionary;
private PdfStamper stamper;
private boolean preClosed = false;
private PdfSigGenericPKCS sigStandard;
private long[] range;
private RandomAccessFile raf;
private byte[] bout;
private int boutLen;
private byte[] externalDigest;
private byte[] externalRSAdata;
private String digestEncryptionAlgorithm;
private Map exclusionLocations;
private Certificate[] certChain;
// ******************************************************************************
PdfSignatureAppearance(PdfStamperImp writer) {
this.writer = writer;
signDate = new GregorianCalendar();
fieldName = getNewSigName();
}
private int render = SignatureRenderDescription;
/**
* Gets the rendering mode for this signature.
*
* @return the rendering mode for this signature
*/
public int getRender() {
return render;
}
/**
* Sets the rendering mode for this signature. The rendering modes can be the
* constants SignatureRenderDescription
,
* SignatureRenderNameAndDescription
or
* SignatureRenderGraphicAndDescription
. The two last modes
* should be used with Acrobat 6 layer type.
*
* @param render
* the render mode
*/
public void setRender(int render) {
this.render = render;
}
private Image signatureGraphic = null;
/**
* Gets the Image object to render.
*
* @return the image
*/
public Image getSignatureGraphic() {
return signatureGraphic;
}
/**
* Sets the Image object to render when Render is set to
* SignatureRenderGraphicAndDescription
*
* @param signatureGraphic
* image rendered. If null
the mode is defaulted to
* SignatureRenderDescription
*/
public void setSignatureGraphic(Image signatureGraphic) {
this.signatureGraphic = signatureGraphic;
}
/**
* Sets the signature text identifying the signer.
*
* @param text
* the signature text identifying the signer. If null
or
* not set a standard description will be used
*/
public void setLayer2Text(String text) {
layer2Text = text;
}
/**
* Gets the signature text identifying the signer if set by setLayer2Text().
*
* @return the signature text identifying the signer
*/
public String getLayer2Text() {
return layer2Text;
}
/**
* Sets the text identifying the signature status.
*
* @param text
* the text identifying the signature status. If null
or
* not set the description "Signature Not Verified" will be used
*/
public void setLayer4Text(String text) {
layer4Text = text;
}
/**
* Gets the text identifying the signature status if set by setLayer4Text().
*
* @return the text identifying the signature status
*/
public String getLayer4Text() {
return layer4Text;
}
/**
* Gets the rectangle representing the signature dimensions.
*
* @return the rectangle representing the signature dimensions. It may be
* null
or have zero width or height for invisible
* signatures
*/
public Rectangle getRect() {
return rect;
}
/**
* Gets the visibility status of the signature.
*
* @return the visibility status of the signature
*/
public boolean isInvisible() {
return (rect == null || rect.getWidth() == 0 || rect.getHeight() == 0);
}
/**
* Sets the cryptographic parameters.
*
* @param privKey
* the private key
* @param certificate
* the certificate
* @param crl
* the certificate revocation list. It may be null
* @param filter
* the cryptographic filter type. It can be SELF_SIGNED,
* VERISIGN_SIGNED or WINCER_SIGNED
*/
public void setCrypto(PrivateKey privKey, X509Certificate certificate,
CRL crl, PdfName filter) {
this.privKey = privKey;
if (certificate == null)
throw new IllegalArgumentException("Null certificate not allowed");
this.certChain = new Certificate[1];
this.certChain[0] = certificate;
if (crl != null) {
this.crlList = new CRL[1];
this.crlList[0] = crl;
}
this.filter = filter;
}
/**
* Sets the cryptographic parameters.
* @param privKey the private key
* @param certChain the certification chain
* @param crlList the crl list
* @param filter the PdfName
*/
public void setCrypto(PrivateKey privKey, Certificate[] certChain, CRL[] crlList, PdfName filter) {
this.privKey = privKey;
this.certChain = certChain;
this.crlList = crlList;
this.filter = filter;
}
// OJO... Modificacion de
// flopez-------------------------------------------------
public void setVisibleSignature(Rectangle pageRect, int page) {
setVisibleSignature(pageRect, page, getNewSigName());
}
// ******************************************************************************
/**
* Sets the signature to be visible. It creates a new visible signature field.
*
* @param pageRect
* the position and dimension of the field in the page
* @param page
* the page to place the field. The fist page is 1
* @param fieldName
* the field name or null
to generate automatically a
* new field name
*/
public void setVisibleSignature(Rectangle pageRect, int page, String fieldName) {
if (fieldName != null) {
if (fieldName.indexOf('.') >= 0)
throw new IllegalArgumentException(
MessageLocalization
.getComposedMessage("field.names.cannot.contain.a.dot"));
AcroFields af = writer.getAcroFields();
AcroFields.Item item = af.getFieldItem(fieldName);
if (item != null)
throw new IllegalArgumentException(
MessageLocalization.getComposedMessage(
"the.field.1.already.exists", fieldName));
this.fieldName = fieldName;
}
// OJO... Modificacion de
// flopez--------------------------------------------------
// if (page < 1 || page > writer.reader.getNumberOfPages())
if (page < 0 || page > writer.reader.getNumberOfPages())
throw new IllegalArgumentException(
MessageLocalization.getComposedMessage("invalid.page.number.1", page));
// ******************************************************************************
this.pageRect = new Rectangle(pageRect);
this.pageRect.normalize();
rect = new Rectangle(this.pageRect.getWidth(), this.pageRect.getHeight());
this.page = page;
newField = true;
}
/**
* Sets the signature to be visible. An empty signature field with the same
* name must already exist.
*
* @param fieldName
* the existing empty signature field name
*/
public void setVisibleSignature(String fieldName) {
AcroFields af = writer.getAcroFields();
AcroFields.Item item = af.getFieldItem(fieldName);
if (item == null)
throw new IllegalArgumentException(
MessageLocalization.getComposedMessage("the.field.1.does.not.exist",
fieldName));
PdfDictionary merged = item.getMerged(0);
if (!PdfName.SIG.equals(PdfReader.getPdfObject(merged.get(PdfName.FT))))
throw new IllegalArgumentException(
MessageLocalization.getComposedMessage(
"the.field.1.is.not.a.signature.field", fieldName));
this.fieldName = fieldName;
PdfArray r = merged.getAsArray(PdfName.RECT);
float llx = r.getAsNumber(0).floatValue();
float lly = r.getAsNumber(1).floatValue();
float urx = r.getAsNumber(2).floatValue();
float ury = r.getAsNumber(3).floatValue();
pageRect = new Rectangle(llx, lly, urx, ury);
pageRect.normalize();
page = item.getPage(0);
int rotation = writer.reader.getPageRotation(page);
Rectangle pageSize = writer.reader.getPageSizeWithRotation(page);
switch (rotation) {
case 90:
pageRect = new Rectangle(pageRect.getBottom(), pageSize.getTop()
- pageRect.getLeft(), pageRect.getTop(), pageSize.getTop()
- pageRect.getRight());
break;
case 180:
pageRect = new Rectangle(pageSize.getRight() - pageRect.getLeft(),
pageSize.getTop() - pageRect.getBottom(), pageSize.getRight()
- pageRect.getRight(), pageSize.getTop() - pageRect.getTop());
break;
case 270:
pageRect = new Rectangle(pageSize.getRight() - pageRect.getBottom(),
pageRect.getLeft(), pageSize.getRight() - pageRect.getTop(),
pageRect.getRight());
break;
}
if (rotation != 0)
pageRect.normalize();
rect = new Rectangle(this.pageRect.getWidth(), this.pageRect.getHeight());
}
/**
* Gets a template layer to create a signature appearance. The layers can go
* from 0 to 4.
*
* Consult PPKAppearances.pdf for further details.
*
* @param layer
* the layer
* @return a template
*/
public PdfTemplate getLayer(int layer) {
if (layer < 0 || layer >= app.length)
return null;
PdfTemplate t = app[layer];
if (t == null) {
t = app[layer] = new PdfTemplate(writer);
t.setBoundingBox(rect);
writer.addDirectTemplateSimple(t, new PdfName("n" + layer));
}
return t;
}
/**
* Gets the template that aggregates all appearance layers. This corresponds
* to the /FRM resource.
*
* Consult PPKAppearances.pdf for further details.
*
* @return the template that aggregates all appearance layers
*/
public PdfTemplate getTopLayer() {
if (frm == null) {
frm = new PdfTemplate(writer);
frm.setBoundingBox(rect);
writer.addDirectTemplateSimple(frm, new PdfName("FRM"));
}
return frm;
}
/**
* Gets the main appearance layer.
*
* Consult PPKAppearances.pdf for further details.
*
* @return the main appearance layer
* @throws DocumentException
* on error
*/
public PdfTemplate getAppearance() throws DocumentException {
if (isInvisible()) {
PdfTemplate t = new PdfTemplate(writer);
t.setBoundingBox(new Rectangle(0, 0));
writer.addDirectTemplateSimple(t, null);
return t;
}
if (app[0] == null) {
PdfTemplate t = app[0] = new PdfTemplate(writer);
t.setBoundingBox(new Rectangle(100, 100));
writer.addDirectTemplateSimple(t, new PdfName("n0"));
t.setLiteral("% DSBlank\n");
}
if (app[1] == null && !acro6Layers) {
PdfTemplate t = app[1] = new PdfTemplate(writer);
t.setBoundingBox(new Rectangle(100, 100));
writer.addDirectTemplateSimple(t, new PdfName("n1"));
t.setLiteral(questionMark);
}
if (app[2] == null) {
String text;
if (layer2Text == null) {
StringBuilder buf = new StringBuilder();
buf.append("Digitally signed by ")
.append(PdfPKCS7.getSubjectFields((X509Certificate) certChain[0]).getField("CN"))
.append('\n');
SimpleDateFormat sd = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z");
buf.append("Date: ").append(sd.format(signDate.getTime()));
if (reason != null)
buf.append('\n').append("Reason: ").append(reason);
if (location != null)
buf.append('\n').append("Location: ").append(location);
text = buf.toString();
} else
text = layer2Text;
PdfTemplate t = app[2] = new PdfTemplate(writer);
t.setBoundingBox(rect);
writer.addDirectTemplateSimple(t, new PdfName("n2"));
if (image != null) {
if (imageScale == 0) {
t.addImage(image, rect.getWidth(), 0, 0, rect.getHeight(), 0, 0);
} else {
float usableScale = imageScale;
if (imageScale < 0)
usableScale = Math.min(rect.getWidth() / image.getWidth(),
rect.getHeight() / image.getHeight());
float w = image.getWidth() * usableScale;
float h = image.getHeight() * usableScale;
float x = (rect.getWidth() - w) / 2;
float y = (rect.getHeight() - h) / 2;
t.addImage(image, w, 0, 0, h, x, y);
}
}
Font font;
if (layer2Font == null)
font = new Font();
else
font = new Font(layer2Font);
float size = font.getSize();
Rectangle dataRect = null;
Rectangle signatureRect = null;
if (render == SignatureRenderNameAndDescription
|| (render == SignatureRenderGraphicAndDescription && this.signatureGraphic != null)) {
// origin is the bottom-left
signatureRect = new Rectangle(MARGIN, MARGIN, rect.getWidth() / 2
- MARGIN, rect.getHeight() - MARGIN);
dataRect = new Rectangle(rect.getWidth() / 2 + MARGIN / 2, MARGIN,
rect.getWidth() - MARGIN / 2, rect.getHeight() - MARGIN);
if (rect.getHeight() > rect.getWidth()) {
signatureRect = new Rectangle(MARGIN, rect.getHeight() / 2,
rect.getWidth() - MARGIN, rect.getHeight());
dataRect = new Rectangle(MARGIN, MARGIN, rect.getWidth() - MARGIN,
rect.getHeight() / 2 - MARGIN);
}
} else {
dataRect = new Rectangle(MARGIN, MARGIN, rect.getWidth() - MARGIN,
rect.getHeight() * (1 - TOP_SECTION) - MARGIN);
}
if (render == SignatureRenderNameAndDescription) {
String signedBy = PdfPKCS7.getSubjectFields((X509Certificate) certChain[0]).getField("CN");
Rectangle sr2 = new Rectangle(signatureRect.getWidth() - MARGIN,
signatureRect.getHeight() - MARGIN);
float signedSize = fitText(font, signedBy, sr2, -1, runDirection);
ColumnText ct2 = new ColumnText(t);
ct2.setRunDirection(runDirection);
ct2.setSimpleColumn(new Phrase(signedBy, font),
signatureRect.getLeft(), signatureRect.getBottom(),
signatureRect.getRight(), signatureRect.getTop(), signedSize,
Element.ALIGN_LEFT);
ct2.go();
} else if (render == SignatureRenderGraphicAndDescription) {
ColumnText ct2 = new ColumnText(t);
ct2.setRunDirection(runDirection);
ct2.setSimpleColumn(signatureRect.getLeft(), signatureRect.getBottom(),
signatureRect.getRight(), signatureRect.getTop(), 0,
Element.ALIGN_RIGHT);
Image im = Image.getInstance(signatureGraphic);
im.scaleToFit(signatureRect.getWidth(), signatureRect.getHeight());
Paragraph p = new Paragraph();
// must calculate the point to draw from to make image appear in middle
// of column
float x = 0;
// experimentation found this magic number to counteract Adobe's
// signature graphic, which
// offsets the y co-ordinate by 15 units
float y = -im.getScaledHeight() + 15;
x = x + (signatureRect.getWidth() - im.getScaledWidth()) / 2;
y = y - (signatureRect.getHeight() - im.getScaledHeight()) / 2;
p.add(new Chunk(im, x
+ (signatureRect.getWidth() - im.getScaledWidth()) / 2, y, false));
ct2.addElement(p);
ct2.go();
}
if (size <= 0) {
Rectangle sr = new Rectangle(dataRect.getWidth(), dataRect.getHeight());
size = fitText(font, text, sr, 12, runDirection);
}
ColumnText ct = new ColumnText(t);
ct.setRunDirection(runDirection);
ct.setSimpleColumn(new Phrase(text, font), dataRect.getLeft(),
dataRect.getBottom(), dataRect.getRight(), dataRect.getTop(), size,
Element.ALIGN_LEFT);
ct.go();
}
if (app[3] == null && !acro6Layers) {
PdfTemplate t = app[3] = new PdfTemplate(writer);
t.setBoundingBox(new Rectangle(100, 100));
writer.addDirectTemplateSimple(t, new PdfName("n3"));
t.setLiteral("% DSBlank\n");
}
if (app[4] == null && !acro6Layers) {
PdfTemplate t = app[4] = new PdfTemplate(writer);
t.setBoundingBox(new Rectangle(0, rect.getHeight() * (1 - TOP_SECTION),
rect.getRight(), rect.getTop()));
writer.addDirectTemplateSimple(t, new PdfName("n4"));
Font font;
if (layer2Font == null)
font = new Font();
else
font = new Font(layer2Font);
float size = font.getSize();
String text = "Signature Not Verified";
if (layer4Text != null)
text = layer4Text;
Rectangle sr = new Rectangle(rect.getWidth() - 2 * MARGIN,
rect.getHeight() * TOP_SECTION - 2 * MARGIN);
size = fitText(font, text, sr, 15, runDirection);
ColumnText ct = new ColumnText(t);
ct.setRunDirection(runDirection);
ct.setSimpleColumn(new Phrase(text, font), MARGIN, 0, rect.getWidth()
- MARGIN, rect.getHeight() - MARGIN, size, Element.ALIGN_LEFT);
ct.go();
}
int rotation = writer.reader.getPageRotation(page);
Rectangle rotated = new Rectangle(rect);
int n = rotation;
while (n > 0) {
rotated = rotated.rotate();
n -= 90;
}
if (frm == null) {
frm = new PdfTemplate(writer);
frm.setBoundingBox(rotated);
writer.addDirectTemplateSimple(frm, new PdfName("FRM"));
float scale = Math.min(rect.getWidth(), rect.getHeight()) * 0.9f;
float x = (rect.getWidth() - scale) / 2;
float y = (rect.getHeight() - scale) / 2;
scale /= 100;
if (rotation == 90)
frm.concatCTM(0, 1, -1, 0, rect.getHeight(), 0);
else if (rotation == 180)
frm.concatCTM(-1, 0, 0, -1, rect.getWidth(), rect.getHeight());
else if (rotation == 270)
frm.concatCTM(0, -1, 1, 0, 0, rect.getWidth());
frm.addTemplate(app[0], 0, 0);
if (!acro6Layers)
frm.addTemplate(app[1], scale, 0, 0, scale, x, y);
frm.addTemplate(app[2], 0, 0);
if (!acro6Layers) {
frm.addTemplate(app[3], scale, 0, 0, scale, x, y);
frm.addTemplate(app[4], 0, 0);
}
}
PdfTemplate napp = new PdfTemplate(writer);
napp.setBoundingBox(rotated);
writer.addDirectTemplateSimple(napp, null);
napp.addTemplate(frm, 0, 0);
return napp;
}
/**
* Fits the text to some rectangle adjusting the font size as needed.
*
* @param font
* the font to use
* @param text
* the text
* @param rect
* the rectangle where the text must fit
* @param maxFontSize
* the maximum font size
* @param runDirection
* the run direction
* @return the calculated font size that makes the text fit
*/
public static float fitText(Font font, String text, Rectangle rect,
float maxFontSize, int runDirection) {
try {
ColumnText ct = null;
int status = 0;
if (maxFontSize <= 0) {
int cr = 0;
int lf = 0;
char[] t = text.toCharArray();
for (char c : t) {
if (c == '\n')
++lf;
else if (c == '\r')
++cr;
}
int minLines = Math.max(cr, lf) + 1;
maxFontSize = Math.abs(rect.getHeight()) / minLines - 0.001f;
}
font.setSize(maxFontSize);
Phrase ph = new Phrase(text, font);
ct = new ColumnText(null);
ct.setSimpleColumn(ph, rect.getLeft(), rect.getBottom(), rect.getRight(),
rect.getTop(), maxFontSize, Element.ALIGN_LEFT);
ct.setRunDirection(runDirection);
status = ct.go(true);
if ((status & ColumnText.NO_MORE_TEXT) != 0)
return maxFontSize;
float precision = 0.1f;
float min = 0;
float max = maxFontSize;
float size = maxFontSize;
for (int k = 0; k < 50; ++k) { // just in case it doesn't converge
size = (min + max) / 2;
ct = new ColumnText(null);
font.setSize(size);
ct.setSimpleColumn(new Phrase(text, font), rect.getLeft(),
rect.getBottom(), rect.getRight(), rect.getTop(), size,
Element.ALIGN_LEFT);
ct.setRunDirection(runDirection);
status = ct.go(true);
if ((status & ColumnText.NO_MORE_TEXT) != 0) {
if (max - min < size * precision)
return size;
min = size;
} else
max = size;
}
return size;
} catch (Exception e) {
throw new ExceptionConverter(e);
}
}
/**
* Sets the digest/signature to an external calculated value.
*
* @param digest
* the digest. This is the actual signature
* @param RSAdata
* the extra data that goes into the data tag in PKCS#7
* @param digestEncryptionAlgorithm
* the encryption algorithm. It may must be null
if the
* digest
is also null
. If the
* digest
is not null
then it may be "RSA"
* or "DSA"
*/
public void setExternalDigest(byte[] digest, byte[] RSAdata,
String digestEncryptionAlgorithm) {
externalDigest = digest;
externalRSAdata = RSAdata;
this.digestEncryptionAlgorithm = digestEncryptionAlgorithm;
}
/**
* Gets the signing reason.
*
* @return the signing reason
*/
public String getReason() {
return this.reason;
}
/**
* Sets the signing reason.
*
* @param reason
* the signing reason
*/
public void setReason(String reason) {
this.reason = reason;
}
/**
* Gets the signing location.
*
* @return the signing location
*/
public String getLocation() {
return this.location;
}
/**
* Sets the signing location.
*
* @param location
* the signing location
*/
public void setLocation(String location) {
this.location = location;
}
/**
* Returns the Cryptographic Service Provider that will sign the document.
*
* @return provider the name of the provider, for example "SUN", or
* null
to use the default provider.
*/
public String getProvider() {
return this.provider;
}
/**
* Sets the Cryptographic Service Provider that will sign the document.
*
* @param provider
* the name of the provider, for example "SUN", or null
* to use the default provider.
*/
public void setProvider(String provider) {
this.provider = provider;
}
/**
* Gets the private key.
*
* @return the private key
*/
public java.security.PrivateKey getPrivKey() {
return privKey;
}
/**
* Gets the certificate revocation list.
*
* @return the certificate revocation list
*/
public java.security.cert.CRL[] getCrlList() {
return this.crlList;
}
/**
* Gets the filter used to sign the document.
*
* @return the filter used to sign the document
*/
public com.lowagie.text.pdf.PdfName getFilter() {
return filter;
}
/**
* Checks if a new field was created.
*
* @return true
if a new field was created, false
if
* signing an existing field or if the signature is invisible
*/
public boolean isNewField() {
return this.newField;
}
/**
* Gets the page number of the field.
*
* @return the page number of the field
*/
public int getPage() {
return page;
}
/**
* Gets the field name.
*
* @return the field name
*/
public java.lang.String getFieldName() {
return fieldName;
}
/**
* Gets the rectangle that represent the position and dimension of the
* signature in the page.
*
* @return the rectangle that represent the position and dimension of the
* signature in the page
*/
public com.lowagie.text.Rectangle getPageRect() {
return pageRect;
}
/**
* Gets the signature date.
*
* @return the signature date
*/
public java.util.Calendar getSignDate() {
return signDate;
}
/**
* Sets the signature date.
*
* @param signDate
* the signature date
*/
public void setSignDate(java.util.Calendar signDate) {
this.signDate = signDate;
}
com.lowagie.text.pdf.ByteBuffer getSigout() {
return sigout;
}
void setSigout(com.lowagie.text.pdf.ByteBuffer sigout) {
this.sigout = sigout;
}
java.io.OutputStream getOriginalout() {
return originalout;
}
void setOriginalout(java.io.OutputStream originalout) {
this.originalout = originalout;
}
/**
* Gets the temporary file.
*
* @return the temporary file or null
is the document is created
* in memory
*/
public java.io.File getTempFile() {
return tempFile;
}
void setTempFile(java.io.File tempFile) {
this.tempFile = tempFile;
}
/**
* Gets a new signature fied name that doesn't clash with any existing name.
*
* @return a new signature fied name
*/
public final String getNewSigName() {
AcroFields af = writer.getAcroFields();
String name = "Signature";
int step = 0;
boolean found = false;
while (!found) {
++step;
String n1 = name + step;
if (af.getFieldItem(n1) != null)
continue;
n1 += ".";
found = true;
for (String fn : af.getAllFields().keySet()) {
if (fn.startsWith(n1)) {
found = false;
break;
}
}
}
name += step;
return name;
}
/**
* This is the first method to be called when using external signatures. The
* general sequence is: preClose(), getDocumentBytes() and close().
*
* If calling preClose() dont't call PdfStamper.close().
*
* No external signatures are allowed if this method is called.
*
* @throws IOException
* on error
* @throws DocumentException
* on error
*/
public void preClose() throws IOException, DocumentException {
preClose(null);
}
/**
* This is the first method to be called when using external signatures. The
* general sequence is: preClose(), getDocumentBytes() and close().
*
* If calling preClose() dont't call PdfStamper.close().
*
* If using an external signature exclusionSizes
must contain at
* least the PdfName.CONTENTS
key with the size that it will take
* in the document. Note that due to the hex string coding this size should be
* byte_size*2+2.
*
* @param exclusionSizes
* a HashMap
with names and sizes to be excluded in the
* signature calculation. The key is a PdfName
and the
* value an Integer
. At least the
* PdfName.CONTENTS
must be present
* @throws IOException
* on error
* @throws DocumentException
* on error
*/
public void preClose(Map exclusionSizes) throws IOException,
DocumentException {
if (preClosed)
throw new DocumentException(
MessageLocalization.getComposedMessage("document.already.pre.closed"));
preClosed = true;
AcroFields af = writer.getAcroFields();
String name = getFieldName();
boolean fieldExists = !(isInvisible() || isNewField());
PdfIndirectReference refSig = writer.getPdfIndirectReference();
writer.setSigFlags(3);
if (fieldExists) {
PdfDictionary widget = af.getFieldItem(name).getWidget(0);
writer.markUsed(widget);
widget.put(PdfName.P, writer.getPageReference(getPage()));
widget.put(PdfName.V, refSig);
PdfObject obj = PdfReader.getPdfObjectRelease(widget.get(PdfName.F));
int flags = 0;
if (obj != null && obj.isNumber())
flags = ((PdfNumber) obj).intValue();
flags |= PdfAnnotation.FLAGS_LOCKED;
widget.put(PdfName.F, new PdfNumber(flags));
PdfDictionary ap = new PdfDictionary();
ap.put(PdfName.N, getAppearance().getIndirectReference());
widget.put(PdfName.AP, ap);
} else {
PdfFormField sigField = PdfFormField.createSignature(writer);
sigField.setFieldName(name);
sigField.put(PdfName.V, refSig);
sigField.setFlags(PdfAnnotation.FLAGS_PRINT | PdfAnnotation.FLAGS_LOCKED);
int pagen = getPage();
// OJO... Modificacion de
// flopez-----------------------------------------------------
// if (!isInvisible())
// sigField.setWidget(getPageRect(), null);
// else
// sigField.setWidget(new Rectangle(0, 0), null);
if ((!isInvisible()) && (pagen == 0)) { // Si pagina en cero tonces firma
// en todas las paginas
int pages = writer.reader.getNumberOfPages();
for (int i = 1; i <= pages; i++) {
PdfFormField field = PdfFormField.createEmpty(writer);
this.page = i;
pagen = i;
field.setWidget(getPageRect(), null);
field.setAppearance(PdfAnnotation.APPEARANCE_NORMAL, getAppearance());
field.setPlaceInPage(i);
field.setPage(i);
field.setFlags(PdfAnnotation.FLAGS_PRINT);
sigField.addKid(field);
field = null;
}
} else if (!isInvisible()) // Si es una pagina especifica
sigField.setWidget(getPageRect(), null);
else
sigField.setWidget(new Rectangle(0, 0), null);
// ******************************************************************************
sigField.setAppearance(PdfAnnotation.APPEARANCE_NORMAL, getAppearance());
sigField.setPage(pagen);
writer.addAnnotation(sigField, pagen);
}
exclusionLocations = new HashMap<>();
if (cryptoDictionary == null) {
if (PdfName.ADOBE_PPKLITE.equals(getFilter()))
sigStandard = new PdfSigGenericPKCS.PPKLite(getProvider());
else if (PdfName.ADOBE_PPKMS.equals(getFilter()))
sigStandard = new PdfSigGenericPKCS.PPKMS(getProvider());
else if (PdfName.VERISIGN_PPKVS.equals(getFilter()))
sigStandard = new PdfSigGenericPKCS.VeriSign(getProvider());
else
throw new IllegalArgumentException(
MessageLocalization.getComposedMessage("unknown.filter.1",
getFilter()));
sigStandard.setExternalDigest(externalDigest, externalRSAdata,
digestEncryptionAlgorithm);
if (getReason() != null)
sigStandard.setReason(getReason());
if (getLocation() != null)
sigStandard.setLocation(getLocation());
if (getContact() != null)
sigStandard.setContact(getContact());
sigStandard.put(PdfName.M, new PdfDate(getSignDate()));
sigStandard.setSignInfo(getPrivKey(), certChain, crlList);
// ******************************************************************************
PdfString contents = (PdfString) sigStandard.get(PdfName.CONTENTS);
PdfLiteral lit = new PdfLiteral(
(contents.toString().length() + (PdfName.ADOBE_PPKLITE
.equals(getFilter()) ? 0 : 64)) * 2 + 2);
exclusionLocations.put(PdfName.CONTENTS, lit);
sigStandard.put(PdfName.CONTENTS, lit);
lit = new PdfLiteral(80);
exclusionLocations.put(PdfName.BYTERANGE, lit);
sigStandard.put(PdfName.BYTERANGE, lit);
if (certificationLevel > 0) {
addDocMDP(sigStandard);
}
if (signatureEvent != null)
signatureEvent.getSignatureDictionary(sigStandard);
writer.addToBody(sigStandard, refSig, false);
} else {
PdfLiteral lit = new PdfLiteral(80);
exclusionLocations.put(PdfName.BYTERANGE, lit);
cryptoDictionary.put(PdfName.BYTERANGE, lit);
for (Object o : exclusionSizes.entrySet()) {
Map.Entry entry = (Map.Entry) o;
PdfName key = (PdfName) entry.getKey();
Integer v = (Integer) entry.getValue();
lit = new PdfLiteral(v);
exclusionLocations.put(key, lit);
cryptoDictionary.put(key, lit);
}
if (certificationLevel > 0)
addDocMDP(cryptoDictionary);
if (signatureEvent != null)
signatureEvent.getSignatureDictionary(cryptoDictionary);
writer.addToBody(cryptoDictionary, refSig, false);
}
if (certificationLevel > 0) {
// add DocMDP entry to root
PdfDictionary docmdp = new PdfDictionary();
docmdp.put(new PdfName("DocMDP"), refSig);
writer.reader.getCatalog().put(new PdfName("Perms"), docmdp);
}
writer.close(stamper.getInfoDictionary());
range = new long[exclusionLocations.size() * 2];
long byteRangePosition = exclusionLocations
.get(PdfName.BYTERANGE).getPosition();
exclusionLocations.remove(PdfName.BYTERANGE);
int idx = 1;
for (Object o : exclusionLocations.values()) {
PdfLiteral lit = (PdfLiteral) o;
long n = lit.getPosition();
range[idx++] = n;
range[idx++] = lit.getPosLength() + n;
}
Arrays.sort(range, 1, range.length - 1);
for (int k = 3; k < range.length - 2; k += 2)
range[k] -= range[k - 1];
if (tempFile == null) {
bout = sigout.getBuffer();
boutLen = sigout.size();
range[range.length - 1] = boutLen - range[range.length - 2];
ByteBuffer bf = new ByteBuffer();
bf.append('[');
for (long i : range) bf.append(i).append(' ');
bf.append(']');
System.arraycopy(bf.getBuffer(), 0, bout, (int) byteRangePosition, bf.size());
} else {
try {
raf = new RandomAccessFile(tempFile, "rw");
long boutL = raf.length();
range[range.length - 1] = boutL - range[range.length - 2];
ByteBuffer bf = new ByteBuffer();
bf.append('[');
for (long i : range) bf.append(i).append(' ');
bf.append(']');
raf.seek(byteRangePosition);
raf.write(bf.getBuffer(), 0, bf.size());
} catch (IOException e) {
try {
raf.close();
} catch (Exception ee) {
}
try {
tempFile.delete();
} catch (Exception ee) {
}
throw e;
}
}
}
/**
* This is the last method to be called when using external signatures. The
* general sequence is: preClose(), getDocumentBytes() and close().
*
* update
is a PdfDictionary
that must have exactly
* the same keys as the ones provided in {@link PdfSignatureAppearance#preClose(Map)}.
*
* @param update
* a PdfDictionary
with the key/value that will fill the
* holes defined in {@link PdfSignatureAppearance#preClose(Map)}
* @throws DocumentException
* on error
* @throws IOException
* on error
*/
public void close(PdfDictionary update) throws IOException, DocumentException {
try {
if (!preClosed)
throw new DocumentException(
MessageLocalization
.getComposedMessage("preclose.must.be.called.first"));
ByteBuffer bf = new ByteBuffer();
for (PdfName key : update.getKeys()) {
PdfObject obj = update.get(key);
PdfLiteral lit = exclusionLocations.get(key);
if (lit == null)
throw new IllegalArgumentException(
MessageLocalization.getComposedMessage(
"the.key.1.didn.t.reserve.space.in.preclose", key.toString()));
bf.reset();
obj.toPdf(null, bf);
if (bf.size() > lit.getPosLength())
throw new IllegalArgumentException(
MessageLocalization.getComposedMessage(
"the.key.1.is.too.big.is.2.reserved.3", key.toString(),
String.valueOf(bf.size()), String.valueOf(lit.getPosLength())));
if (tempFile == null)
System.arraycopy(bf.getBuffer(), 0, bout, (int) lit.getPosition(),
bf.size());
else {
raf.seek(lit.getPosition());
raf.write(bf.getBuffer(), 0, bf.size());
}
}
if (update.size() != exclusionLocations.size())
throw new IllegalArgumentException(
MessageLocalization
.getComposedMessage("the.update.dictionary.has.less.keys.than.required"));
if (tempFile == null) {
originalout.write(bout, 0, boutLen);
} else {
if (originalout != null) {
raf.seek(0);
long length = raf.length();
byte[] buf = new byte[8192];
while (length > 0) {
int r = raf.read(buf, 0, (int) Math.min(buf.length, length));
if (r < 0)
throw new EOFException(
MessageLocalization.getComposedMessage("unexpected.eof"));
originalout.write(buf, 0, r);
length -= r;
}
}
}
} finally {
if (tempFile != null) {
try {
raf.close();
} catch (Exception ee) {
}
if (originalout != null)
try {
tempFile.delete();
} catch (Exception ee) {
}
}
if (originalout != null)
try {
originalout.close();
} catch (Exception e) {
}
}
}
private void addDocMDP(PdfDictionary crypto) {
PdfDictionary reference = new PdfDictionary();
PdfDictionary transformParams = new PdfDictionary();
transformParams.put(PdfName.P, new PdfNumber(certificationLevel));
transformParams.put(PdfName.V, new PdfName("1.2"));
transformParams.put(PdfName.TYPE, PdfName.TRANSFORMPARAMS);
reference.put(PdfName.TRANSFORMMETHOD, PdfName.DOCMDP);
reference.put(PdfName.TYPE, PdfName.SIGREF);
reference.put(PdfName.TRANSFORMPARAMS, transformParams);
reference.put(new PdfName("DigestValue"), new PdfString("aa"));
PdfArray loc = new PdfArray();
loc.add(new PdfNumber(0));
loc.add(new PdfNumber(0));
reference.put(new PdfName("DigestLocation"), loc);
reference.put(new PdfName("DigestMethod"), new PdfName("MD5"));
reference.put(PdfName.DATA, writer.reader.getTrailer().get(PdfName.ROOT));
PdfArray types = new PdfArray();
types.add(reference);
crypto.put(PdfName.REFERENCE, types);
}
/**
* Gets the document bytes that are hashable when using external signatures.
* The general sequence is: preClose(), getRangeStream() and close().
*
*
* @return the document bytes that are hashable
*/
public InputStream getRangeStream() {
return new PdfSignatureAppearance.RangeStream(raf, bout, range);
}
/**
* Gets the user made signature dictionary. This is the dictionary at the /V
* key.
*
* @return the user made signature dictionary
*/
public com.lowagie.text.pdf.PdfDictionary getCryptoDictionary() {
return cryptoDictionary;
}
/**
* Sets a user made signature dictionary. This is the dictionary at the /V
* key.
*
* @param cryptoDictionary
* a user made signature dictionary
*/
public void setCryptoDictionary(
com.lowagie.text.pdf.PdfDictionary cryptoDictionary) {
this.cryptoDictionary = cryptoDictionary;
}
/**
* Gets the PdfStamper
associated with this instance.
*
* @return the PdfStamper
associated with this instance
*/
public com.lowagie.text.pdf.PdfStamper getStamper() {
return stamper;
}
void setStamper(com.lowagie.text.pdf.PdfStamper stamper) {
this.stamper = stamper;
}
/**
* Checks if the document is in the process of closing.
*
* @return true
if the document is in the process of closing,
* false
otherwise
*/
public boolean isPreClosed() {
return preClosed;
}
/**
* Gets the instance of the standard signature dictionary. This instance is
* only available after pre close.
*
* The main use is to insert external signatures.
*
* @return the instance of the standard signature dictionary
*/
public com.lowagie.text.pdf.PdfSigGenericPKCS getSigStandard() {
return sigStandard;
}
/**
* Gets the signing contact.
*
* @return the signing contact
*/
public String getContact() {
return this.contact;
}
/**
* Sets the signing contact.
*
* @param contact
* the signing contact
*/
public void setContact(String contact) {
this.contact = contact;
}
/**
* Gets the n2 and n4 layer font.
*
* @return the n2 and n4 layer font
*/
public Font getLayer2Font() {
return this.layer2Font;
}
/**
* Sets the n2 and n4 layer font. If the font size is zero, auto-fit will be
* used.
*
* @param layer2Font
* the n2 and n4 font
*/
public void setLayer2Font(Font layer2Font) {
this.layer2Font = layer2Font;
}
/**
* Gets the Acrobat 6.0 layer mode.
*
* @return the Acrobat 6.0 layer mode
*/
public boolean isAcro6Layers() {
return this.acro6Layers;
}
/**
* Acrobat 6.0 and higher recommends that only layer n2 and n4 be present.
* This method sets that mode.
*
* @param acro6Layers
* if true
only the layers n2 and n4 will be present
*/
public void setAcro6Layers(boolean acro6Layers) {
this.acro6Layers = acro6Layers;
}
/**
* Sets the run direction in the n2 and n4 layer.
*
* @param runDirection
* the run direction
*/
public void setRunDirection(int runDirection) {
if (runDirection < PdfWriter.RUN_DIRECTION_DEFAULT
|| runDirection > PdfWriter.RUN_DIRECTION_RTL)
throw new RuntimeException(MessageLocalization.getComposedMessage(
"invalid.run.direction.1", runDirection));
this.runDirection = runDirection;
}
/**
* Gets the run direction.
*
* @return the run direction
*/
public int getRunDirection() {
return runDirection;
}
/**
* Getter for property signatureEvent.
*
* @return Value of property signatureEvent.
*/
public SignatureEvent getSignatureEvent() {
return this.signatureEvent;
}
/**
* Sets the signature event to allow modification of the signature dictionary.
*
* @param signatureEvent
* the signature event
*/
public void setSignatureEvent(SignatureEvent signatureEvent) {
this.signatureEvent = signatureEvent;
}
/**
* Gets the background image for the layer 2.
*
* @return the background image for the layer 2
*/
public Image getImage() {
return this.image;
}
/**
* Sets the background image for the layer 2.
*
* @param image
* the background image for the layer 2
*/
public void setImage(Image image) {
this.image = image;
}
/**
* Gets the scaling to be applied to the background image.
*
* @return the scaling to be applied to the background image
*/
public float getImageScale() {
return this.imageScale;
}
/**
* Sets the scaling to be applied to the background image. If it's zero the
* image will fully fill the rectangle. If it's less than zero the image will
* fill the rectangle but will keep the proportions. If it's greater than zero
* that scaling will be applied. In any of the cases the image will always be
* centered. It's zero by default.
*
* @param imageScale
* the scaling to be applied to the background image
*/
public void setImageScale(float imageScale) {
this.imageScale = imageScale;
}
/**
* Commands to draw a yellow question mark in a stream content
*/
public static final String questionMark = "% DSUnknown\n" + "q\n" + "1 G\n"
+ "1 g\n" + "0.1 0 0 0.1 9 0 cm\n" + "0 J 0 j 4 M []0 d\n" + "1 i \n"
+ "0 g\n" + "313 292 m\n" + "313 404 325 453 432 529 c\n"
+ "478 561 504 597 504 645 c\n" + "504 736 440 760 391 760 c\n"
+ "286 760 271 681 265 626 c\n" + "265 625 l\n" + "100 625 l\n"
+ "100 828 253 898 381 898 c\n" + "451 898 679 878 679 650 c\n"
+ "679 555 628 499 538 435 c\n" + "488 399 467 376 467 292 c\n"
+ "313 292 l\n" + "h\n" + "308 214 170 -164 re\n" + "f\n" + "0.44 G\n"
+ "1.2 w\n" + "1 1 0.4 rg\n" + "287 318 m\n"
+ "287 430 299 479 406 555 c\n" + "451 587 478 623 478 671 c\n"
+ "478 762 414 786 365 786 c\n" + "260 786 245 707 239 652 c\n"
+ "239 651 l\n" + "74 651 l\n" + "74 854 227 924 355 924 c\n"
+ "425 924 653 904 653 676 c\n" + "653 581 602 525 512 461 c\n"
+ "462 425 441 402 441 318 c\n" + "287 318 l\n" + "h\n"
+ "282 240 170 -164 re\n" + "B\n" + "Q\n";
/**
* Holds value of property contact.
*/
private String contact;
/**
* Holds value of property layer2Font.
*/
private Font layer2Font;
/**
* Holds value of property layer4Text.
*/
private String layer4Text;
/**
* Holds value of property acro6Layers.
*/
private boolean acro6Layers;
/**
* Holds value of property runDirection.
*/
private int runDirection = PdfWriter.RUN_DIRECTION_NO_BIDI;
/**
* Holds value of property signatureEvent.
*/
private SignatureEvent signatureEvent;
/**
* Holds value of property image.
*/
private Image image;
/**
* Holds value of property imageScale.
*/
private float imageScale;
/**
*
*/
private static class RangeStream extends InputStream {
private final byte[] b = new byte[1];
private final RandomAccessFile raf;
private final byte[] bout;
private final long[] range;
private long rangePosition = 0;
private RangeStream(RandomAccessFile raf, byte[] bout, long[] range) {
this.raf = raf;
this.bout = bout;
this.range = range;
}
/**
* @see java.io.InputStream#read()
*/
@Override
public int read() throws IOException {
int n = read(b);
if (n != 1)
return -1;
return b[0] & 0xff;
}
/**
* @see java.io.InputStream#read(byte[], int, int)
*/
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0)
|| ((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
if (rangePosition >= range[range.length - 2] + range[range.length - 1]) {
return -1;
}
for (int k = 0; k < range.length; k += 2) {
long start = range[k];
long end = start;
if (range.length > k + 1) {
end = start + range[k + 1];
}
if (rangePosition < start) {
rangePosition = start;
}
if (rangePosition < end) {
int lenf = (int) Math.min(len, end - rangePosition);
if (raf == null) {
System.arraycopy(bout, (int) rangePosition, b, off, lenf);
}else {
raf.seek(rangePosition);
raf.readFully(b, off, lenf);
}
rangePosition += lenf;
return lenf;
}
}
return -1;
}
}
/**
* An interface to retrieve the signature dictionary for modification.
*/
public interface SignatureEvent {
/**
* Allows modification of the signature dictionary.
*
* @param sig
* the signature dictionary
*/
void getSignatureDictionary(PdfDictionary sig);
}
private int certificationLevel = NOT_CERTIFIED;
/**
* Gets the certified status of this document.
*
* @return the certified status
*/
public int getCertificationLevel() {
return this.certificationLevel;
}
/**
* Sets the document type to certified instead of simply signed.
*
* @param certificationLevel
* the values can be: NOT_CERTIFIED
,
* CERTIFIED_NO_CHANGES_ALLOWED
,
* CERTIFIED_FORM_FILLING
and
* CERTIFIED_FORM_FILLING_AND_ANNOTATIONS
*/
public void setCertificationLevel(int certificationLevel) {
this.certificationLevel = certificationLevel;
}
}