-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathCreateVisibleSignature.java
More file actions
376 lines (336 loc) · 13.6 KB
/
CreateVisibleSignature.java
File metadata and controls
376 lines (336 loc) · 13.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
package PDFSignature;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Calendar;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.ExternalSigningSupport;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSigProperties;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSignDesigner;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
import org.apache.pdfbox.util.Hex;
/**
* This is an example for visual signing a pdf.
*
* @see CreateSignature
* @author Vakhtang Koroghlishvili
*/
public class CreateVisibleSignature extends CreateSignatureBase {
private SignatureOptions signatureOptions;
private PDVisibleSignDesigner visibleSignDesigner;
private final PDVisibleSigProperties visibleSignatureProperties = new PDVisibleSigProperties();
private boolean lateExternalSigning = false;
public boolean isLateExternalSigning() {
return lateExternalSigning;
}
/**
* Set late external signing. Enable this if you want to activate the demo
* code where the signature is kept and added in an extra step without using
* PDFBox methods. This is disabled by default.
*
* @param lateExternalSigning
*/
public void setLateExternalSigning(boolean lateExternalSigning) {
this.lateExternalSigning = lateExternalSigning;
}
public void setVisibleSignDesigner(String filename, int x, int y, int zoomPercent, FileInputStream imageStream,
int page) throws IOException {
visibleSignDesigner = new PDVisibleSignDesigner(filename, imageStream, page);
visibleSignDesigner.xAxis(x).yAxis(y).zoom(zoomPercent).adjustForRotation();
}
public void setVisibleSignatureProperties(String name, String location, String reason, int preferredSize, int page,
boolean visualSignEnabled) throws IOException {
visibleSignatureProperties.signerName(name).signerLocation(location).signatureReason(reason)
.preferredSize(preferredSize).page(page).visualSignEnabled(visualSignEnabled)
.setPdVisibleSignature(visibleSignDesigner);
}
/**
* Initialize the signature creator with a keystore (pkcs12) and pin that
* should be used for the signature.
*
* @param keystore
* is a pkcs12 keystore.
* @param pin
* is the pin for the keystore / private key
* @throws KeyStoreException
* if the keystore has not been initialized (loaded)
* @throws NoSuchAlgorithmException
* if the algorithm for recovering the key cannot be found
* @throws UnrecoverableKeyException
* if the given password is wrong
* @throws CertificateException
* if the certificate is not valid as signing time
* @throws IOException
* if no certificate could be found
*/
public CreateVisibleSignature(KeyStore keystore, char[] pin) throws KeyStoreException, UnrecoverableKeyException,
NoSuchAlgorithmException, IOException, CertificateException {
super(keystore, pin);
}
/**
* Sign pdf file and create new file that ends with "_signed.pdf".
*
* @param inputFile
* The source pdf document file.
* @param signedFile
* The file to be signed.
* @param tsaClient
* optional TSA client
* @throws IOException
*/
public void signPDF(File inputFile, File signedFile, TSAClient tsaClient) throws IOException {
this.signPDF(inputFile, signedFile, tsaClient, null);
}
/**
* Sign pdf file and create new file that ends with "_signed.pdf".
*
* @param inputFile
* The source pdf document file.
* @param signedFile
* The file to be signed.
* @param tsaClient
* optional TSA client
* @param signatureFieldName
* optional name of an existing (unsigned) signature field
* @throws IOException
*/
public void signPDF(File inputFile, File signedFile, TSAClient tsaClient, String signatureFieldName)
throws IOException {
setTsaClient(tsaClient);
if (inputFile == null || !inputFile.exists()) {
throw new IOException("Document for signing does not exist");
}
// creating output document and prepare the IO streams.
FileOutputStream fos = new FileOutputStream(signedFile);
try (PDDocument doc = PDDocument.load(inputFile)) {
int accessPermissions = getMDPPermission(doc);
if (accessPermissions == 1) {
throw new IllegalStateException(
"No changes to the document are permitted due to DocMDP transform parameters dictionary");
}
// Note that PDFBox has a bug that visual signing on certified files
// with permission 2
// doesn't work properly, see PDFBOX-3699. As long as this issue is
// open, you may want to
// be careful with such files.
PDSignature signature;
// sign a PDF with an existing empty signature, as created by the
// CreateEmptySignatureForm example.
signature = findExistingSignature(doc, signatureFieldName);
if (signature == null) {
// create signature dictionary
signature = new PDSignature();
}
// Optional: certify
if (accessPermissions == 0) {
setMDPPermission(doc, signature, 2);
}
PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm();
if (acroForm != null && acroForm.getNeedAppearances()) {
// PDFBOX-3738 NeedAppearances true results in visible signature
// becoming invisible
// with Adobe Reader
if (acroForm.getFields().isEmpty()) {
// we can safely delete it if there are no fields
acroForm.getCOSObject().removeItem(COSName.NEED_APPEARANCES);
// note that if you've set MDP permissions, the removal of
// this item
// may result in Adobe Reader claiming that the document has
// been changed.
// and/or that field content won't be displayed properly.
// ==> decide what you prefer and adjust your code
// accordingly.
} else {
System.out.println("/NeedAppearances is set, signature may be ignored by Adobe Reader");
}
}
// default filter
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
// subfilter for basic and PAdES Part 2 signatures
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
if (visibleSignatureProperties != null) {
// this builds the signature structures in a separate document
visibleSignatureProperties.buildSignature();
signature.setName(visibleSignatureProperties.getSignerName());
signature.setLocation(visibleSignatureProperties.getSignerLocation());
signature.setReason(visibleSignatureProperties.getSignatureReason());
}
// the signing date, needed for valid signature
signature.setSignDate(Calendar.getInstance());
// do not set SignatureInterface instance, if external signing used
SignatureInterface signatureInterface = isExternalSigning() ? null : this;
// register signature dictionary and sign interface
if (visibleSignatureProperties != null && visibleSignatureProperties.isVisualSignEnabled()) {
signatureOptions = new SignatureOptions();
signatureOptions.setVisualSignature(visibleSignatureProperties.getVisibleSignature());
signatureOptions.setPage(visibleSignatureProperties.getPage() - 1);
doc.addSignature(signature, signatureInterface, signatureOptions);
} else {
doc.addSignature(signature, signatureInterface);
}
if (isExternalSigning()) {
System.out.println("Signing externally " + signedFile.getName());
ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(fos);
// invoke external signature service
byte[] cmsSignature = sign(externalSigning.getContent());
// Explanation of late external signing (off by default):
// If you want to add the signature in a separate step, then set
// an empty byte array
// and call signature.getByteRange() and remember the offset
// signature.getByteRange()[1]+1.
// you can write the ascii hex signature at a later time even if
// you don't have this
// PDDocument object anymore, with classic java file random
// access methods.
// If you can't remember the offset value from ByteRange because
// your context has changed,
// then open the file with PDFBox, find the field with
// findExistingSignature() or
// PODDocument.getLastSignatureDictionary() and get the
// ByteRange from there.
// Close the file and then write the signature as explained
// earlier in this comment.
if (isLateExternalSigning()) {
// this saves the file with a 0 signature
externalSigning.setSignature(new byte[0]);
// remember the offset (add 1 because of "<")
int offset = signature.getByteRange()[1] + 1;
// now write the signature at the correct offset without any
// PDFBox methods
try (RandomAccessFile raf = new RandomAccessFile(signedFile, "rw")) {
raf.seek(offset);
raf.write(Hex.getBytes(cmsSignature));
}
} else {
// set signature bytes received from the service and save
// the file
externalSigning.setSignature(cmsSignature);
}
} else {
// write incremental (only for signing purpose)
doc.saveIncremental(fos);
}
}
// Do not close signatureOptions before saving, because some COSStream
// objects within
// are transferred to the signed document.
// Do not allow signatureOptions get out of scope before saving, because
// then the COSDocument
// in signature options might by closed by gc, which would close
// COSStream objects prematurely.
// See https://issues.apache.org/jira/browse/PDFBOX-3743
IOUtils.closeQuietly(signatureOptions);
}
// Find an existing signature (assumed to be empty). You will usually not
// need this.
private PDSignature findExistingSignature(PDDocument doc, String sigFieldName) {
PDSignature signature = null;
PDSignatureField signatureField;
PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm();
if (acroForm != null) {
signatureField = (PDSignatureField) acroForm.getField(sigFieldName);
if (signatureField != null) {
// retrieve signature dictionary
signature = signatureField.getSignature();
if (signature == null) {
signature = new PDSignature();
// after solving PDFBOX-3524
// signatureField.setValue(signature)
// until then:
signatureField.getCOSObject().setItem(COSName.V, signature);
} else {
throw new IllegalStateException("The signature field " + sigFieldName + " is already signed.");
}
}
}
return signature;
}
/**
* Arguments are [0] key store [1] pin [2] document that will be signed [3]
* image of visible signature
*
* @param args
* @throws java.security.KeyStoreException
* @throws java.security.cert.CertificateException
* @throws java.io.IOException
* @throws java.security.NoSuchAlgorithmException
* @throws java.security.UnrecoverableKeyException
*/
public static void main(String[] args) throws KeyStoreException, CertificateException, IOException,
NoSuchAlgorithmException, UnrecoverableKeyException {
// generate with
// keytool -storepass 123456 -storetype PKCS12 -keystore file.p12
// -genkey -alias client -keyalg RSA
if (args.length < 4) {
usage();
System.exit(1);
}
String tsaUrl = null;
// External signing is needed if you are using an external signing
// service, e.g. to sign
// several files at once.
boolean externalSig = false;
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-tsa")) {
i++;
if (i >= args.length) {
usage();
System.exit(1);
}
tsaUrl = args[i];
}
if (args[i].equals("-e")) {
externalSig = true;
}
}
File ksFile = new File(args[0]);
KeyStore keystore = KeyStore.getInstance("PKCS12");
char[] pin = args[1].toCharArray();
keystore.load(new FileInputStream(ksFile), pin);
// TSA client
TSAClient tsaClient = null;
if (tsaUrl != null) {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
tsaClient = new TSAClient(new URL(tsaUrl), null, null, digest);
}
File documentFile = new File(args[2]);
CreateVisibleSignature signing = new CreateVisibleSignature(keystore, pin.clone());
File signedDocumentFile;
int page;
try (FileInputStream imageStream = new FileInputStream(args[3])) {
String name = documentFile.getName();
String substring = name.substring(0, name.lastIndexOf('.'));
signedDocumentFile = new File(documentFile.getParent(), substring + "_signed.pdf");
// page is 1-based here
page = 1;
signing.setVisibleSignDesigner(args[2], 0, 0, -50, imageStream, page);
}
signing.setVisibleSignatureProperties("name", "location", "Security", 0, page, true);
signing.setExternalSigning(externalSig);
signing.signPDF(documentFile, signedDocumentFile, tsaClient);
}
/**
* This will print the usage for this program.
*/
private static void usage() {
System.err.println("Usage: java " + CreateVisibleSignature.class.getName()
+ " <pkcs12-keystore-file> <pin> <input-pdf> <sign-image>\n" + "" + "options:\n"
+ " -tsa <url> sign timestamp using the given TSA server\n"
+ " -e sign using external signature creation scenario");
}
}