/
C2_12_LockFields.java
279 lines (247 loc) · 13.2 KB
/
C2_12_LockFields.java
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
/*
This file is part of the iText (R) project.
Copyright (c) 1998-2019 iText Group NV
Authors: iText Software.
For more information, please contact iText Software at this address:
sales@itextpdf.com
*/
/*
* This class is part of the white paper entitled
* "Digital Signatures for PDF documents"
* written by Bruno Lowagie
*
* For more info, go to: http://itextpdf.com/learn
*/
package com.itextpdf.samples.signatures.chapter02;
import com.itextpdf.forms.PdfAcroForm;
import com.itextpdf.forms.PdfSigFieldLock;
import com.itextpdf.forms.fields.PdfFormField;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.StampingProperties;
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.property.UnitValue;
import com.itextpdf.layout.renderer.CellRenderer;
import com.itextpdf.layout.renderer.DrawContext;
import com.itextpdf.samples.SignatureTest;
import com.itextpdf.signatures.*;
import com.itextpdf.test.annotations.type.SampleTest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import static org.junit.Assert.fail;
@Category(SampleTest.class)
public class C2_12_LockFields extends SignatureTest {
public static final String FORM = "./target/test/resources/signatures/chapter02/form_lock.pdf";
public static final String ALICE = "./src/test/resources/encryption/alice";
public static final String BOB = "./src/test/resources/encryption/bob";
public static final String CAROL = "./src/test/resources/encryption/carol";
public static final String DAVE = "./src/test/resources/encryption/dave";
public static final String KEYSTORE = "./src/test/resources/encryption/ks";
public static final char[] PASSWORD = "password".toCharArray();
public static final String DEST = "./target/test/resources/signatures/chapter02/step_%s_signed_by_%s.pdf";
public static void main(String[] args) throws IOException, GeneralSecurityException {
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
C2_12_LockFields app = new C2_12_LockFields();
app.createForm();
app.certify(ALICE, FORM, "sig1", String.format(DEST, 1, "alice"));
app.fillOutAndSign(BOB, String.format(DEST, 1, "alice"), "sig2", "approved_bob", "Read and Approved by Bob", String.format(DEST, 2, "alice_and_bob"));
app.fillOutAndSign(CAROL, String.format(DEST, 2, "alice_and_bob"), "sig3", "approved_carol", "Read and Approved by Carol", String.format(DEST, 3, "alice_bob_and_carol"));
app.fillOutAndSign(DAVE, String.format(DEST, 3, "alice_bob_and_carol"), "sig4", "approved_dave", "Read and Approved by Dave", String.format(DEST, 4, "alice_bob_carol_and_dave"));
app.fillOut(String.format(DEST, 2, "alice_and_bob"), String.format(DEST, 5, "alice_and_bob_broken_by_chuck"), "approved_bob", "Changed by Chuck");
app.fillOut(String.format(DEST, 4, "alice_bob_carol_and_dave"), String.format(DEST, 6, "dave_broken_by_chuck"), "approved_carol", "Changed by Chuck");
}
public void createForm() throws IOException {
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(FORM));
Document doc = new Document(pdfDoc);
Table table = new Table(UnitValue.createPercentArray(1)).useAllAvailableWidth();
table.addCell("Written by Alice");
table.addCell(createSignatureFieldCell("sig1", null));
table.addCell("For approval by Bob");
table.addCell(createTextFieldCell("approved_bob"));
PdfSigFieldLock lock = new PdfSigFieldLock().setFieldLock(PdfSigFieldLock.LockAction.INCLUDE, "sig1", "approved_bob", "sig2");
table.addCell(createSignatureFieldCell("sig2", lock));
table.addCell("For approval by Carol");
table.addCell(createTextFieldCell("approved_carol"));
lock = new PdfSigFieldLock().setFieldLock(PdfSigFieldLock.LockAction.EXCLUDE, "approved_dave", "sig4");
table.addCell(createSignatureFieldCell("sig3", lock));
table.addCell("For approval by Dave");
table.addCell(createTextFieldCell("approved_dave"));
lock = new PdfSigFieldLock().setDocumentPermissions(PdfSigFieldLock.LockPermissions.NO_CHANGES_ALLOWED);
table.addCell(createSignatureFieldCell("sig4", lock));
doc.add(table);
doc.close();
}
public void certify(String keystore, String src, String name, String dest)
throws GeneralSecurityException, IOException {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(new FileInputStream(keystore), PASSWORD);
String alias = ks.aliases().nextElement();
PrivateKey pk = (PrivateKey) ks.getKey(alias, PASSWORD);
Certificate[] chain = ks.getCertificateChain(alias);
PdfReader reader = new PdfReader(src);
PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), true);
signer.setFieldName(name);
signer.setCertificationLevel(PdfSigner.CERTIFIED_FORM_FILLING);
PdfAcroForm form = PdfAcroForm.getAcroForm(signer.getDocument(), true);
form.getField(name).setReadOnly(true);
PrivateKeySignature pks = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, "BC");
IExternalDigest digest = new BouncyCastleDigest();
signer.signDetached(digest, pks, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
}
public void fillOutAndSign(String keystore, String src, String name, String fname, String value, String dest)
throws GeneralSecurityException, IOException {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(new FileInputStream(keystore), PASSWORD);
String alias = ks.aliases().nextElement();
PrivateKey pk = (PrivateKey) ks.getKey(alias, PASSWORD);
Certificate[] chain = ks.getCertificateChain(alias);
// Creating the reader and the signer
PdfReader reader = new PdfReader(src);
PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), true);
PdfAcroForm form = PdfAcroForm.getAcroForm(signer.getDocument(), true);
form.getField(fname).setValue(value);
form.getField(name).setReadOnly(true);
form.getField(fname).setReadOnly(true);
// Setting signer options
signer.setFieldName(name);
// Creating the signature
PrivateKeySignature pks = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, "BC");
IExternalDigest digest = new BouncyCastleDigest();
signer.signDetached(digest, pks, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
}
public void fillOut(String src, String dest, String name, String value) throws IOException {
PdfDocument pdfDoc = new PdfDocument(new PdfReader(src), new PdfWriter(dest), new StampingProperties().useAppendMode());
PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true);
form.getField(name).setValue(value);
pdfDoc.close();
}
public void sign(String keystore, String src, String name, String dest)
throws GeneralSecurityException, IOException {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(new FileInputStream(keystore), PASSWORD);
String alias = ks.aliases().nextElement();
PrivateKey pk = (PrivateKey) ks.getKey(alias, PASSWORD);
Certificate[] chain = ks.getCertificateChain(alias);
// Creating the reader and the signer
PdfReader reader = new PdfReader(src);
PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), true);
// Setting signer options
signer.setFieldName(name);
// Creating the signature
PrivateKeySignature pks = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, "BC");
IExternalDigest digest = new BouncyCastleDigest();
signer.signDetached(digest, pks, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
}
protected Cell createTextFieldCell(String name) {
Cell cell = new Cell();
cell.setHeight(20);
cell.setNextRenderer(new TextFieldCellRenderer(cell, name));
return cell;
}
protected Cell createSignatureFieldCell(String name, PdfSigFieldLock lock) throws IOException {
Cell cell = new Cell();
cell.setHeight(50);
cell.setNextRenderer(new SignatureFieldCellRenderer(cell, name, lock));
return cell;
}
class TextFieldCellRenderer extends CellRenderer {
public String name;
public TextFieldCellRenderer(Cell modelElement, String name) {
super(modelElement);
this.name = name;
}
@Override
public void draw(DrawContext drawContext) {
super.draw(drawContext);
PdfFormField field = PdfFormField.createText(drawContext.getDocument(), getOccupiedAreaBBox(), name);
PdfAcroForm.getAcroForm(drawContext.getDocument(), true).addField(field);
}
}
class SignatureFieldCellRenderer extends CellRenderer {
public String name;
public PdfSigFieldLock lock;
public SignatureFieldCellRenderer(Cell modelElement, String name, PdfSigFieldLock lock) {
super(modelElement);
this.name = name;
this.lock = lock;
}
@Override
public void draw(DrawContext drawContext) {
super.draw(drawContext);
PdfFormField field = PdfFormField.createSignature(drawContext.getDocument(), getOccupiedAreaBBox());
field.setFieldName(name);
if (lock != null) {
field.put(PdfName.Lock, lock.makeIndirect(drawContext.getDocument()).getPdfObject());
}
field.getWidgets().get(0).setFlag(PdfAnnotation.PRINT);
field.getWidgets().get(0).setHighlightMode(PdfAnnotation.HIGHLIGHT_INVERT);
PdfAcroForm.getAcroForm(drawContext.getDocument(), true).addField(field);
}
}
@Test
public void runTest() throws IOException, InterruptedException, GeneralSecurityException {
new File("./target/test/resources/signatures/chapter02/").mkdirs();
C2_12_LockFields.main(null);
String[] resultFiles = new String[]{"step_1_signed_by_alice.pdf", "step_2_signed_by_alice_and_bob.pdf", "step_3_signed_by_alice_bob_and_carol.pdf",
// TODO: DEVSIX-1623 for some reason Acrobat recognizes last signature in this document as invalid,
// it happens only if LockPermissions is set to NO_CHANGES_ALLOWED for the last signature form field.
// It's still unclear, whether it's iText messes up the document or it's Acrobat bug.
"step_4_signed_by_alice_bob_carol_and_dave.pdf",
// TODO: iText doesn't recognize invalidated signatures in those files,
// because we don't check changes in new revisions against old signatures (permissions, certifications, content changes),
// however signatures themselves are not broken.
"step_5_signed_by_alice_and_bob_broken_by_chuck.pdf", "step_6_signed_by_dave_broken_by_chuck.pdf"};
String destPath = String.format(outPath, "chapter02");
String comparePath = String.format(cmpPath, "chapter02");
String[] errors = new String[resultFiles.length];
boolean error = false;
HashMap<Integer, List<Rectangle>> ignoredAreas = new HashMap<Integer, List<Rectangle>>() {
{
put(1, Arrays.asList(new Rectangle(55, 425, 287, 380)));
}
};
for (int i = 0; i < resultFiles.length; i++) {
String resultFile = resultFiles[i];
String fileErrors = checkForErrors(destPath + resultFile, comparePath + "cmp_" + resultFile, destPath, ignoredAreas);
if (fileErrors != null) {
errors[i] = fileErrors;
error = true;
}
}
if (error) {
fail(accumulateErrors(errors));
}
}
@Override
protected void initKeyStoreForVerification(KeyStore ks) throws IOException, NoSuchAlgorithmException, CertificateException, KeyStoreException {
super.initKeyStoreForVerification(ks);
ks.setCertificateEntry("alice", loadCertificateFromKeyStore(ALICE, PASSWORD));
ks.setCertificateEntry("bob", loadCertificateFromKeyStore(BOB, PASSWORD));
ks.setCertificateEntry("carol", loadCertificateFromKeyStore(CAROL, PASSWORD));
ks.setCertificateEntry("dave", loadCertificateFromKeyStore(DAVE, PASSWORD));
}
}