1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package cz.hobrasoft.pdfmu.operation.signature;
18
19 import com.itextpdf.text.DocumentException;
20 import com.itextpdf.text.ExceptionConverter;
21 import com.itextpdf.text.pdf.PdfSignatureAppearance;
22 import com.itextpdf.text.pdf.PdfStamper;
23 import com.itextpdf.text.pdf.security.BouncyCastleDigest;
24 import com.itextpdf.text.pdf.security.CrlClient;
25 import com.itextpdf.text.pdf.security.DigestAlgorithms;
26 import com.itextpdf.text.pdf.security.ExternalDigest;
27 import com.itextpdf.text.pdf.security.ExternalSignature;
28 import com.itextpdf.text.pdf.security.MakeSignature;
29 import com.itextpdf.text.pdf.security.OcspClient;
30 import com.itextpdf.text.pdf.security.PrivateKeySignature;
31 import com.itextpdf.text.pdf.security.TSAClient;
32 import cz.hobrasoft.pdfmu.ExceptionMessagePattern;
33 import cz.hobrasoft.pdfmu.PdfmuUtils;
34 import static cz.hobrasoft.pdfmu.error.ErrorType.SIGNATURE_ADD_FAIL;
35 import static cz.hobrasoft.pdfmu.error.ErrorType.SIGNATURE_ADD_SIGNATURE_EXCEPTION;
36 import static cz.hobrasoft.pdfmu.error.ErrorType.SIGNATURE_ADD_TSA_BAD_CERTIFICATE;
37 import static cz.hobrasoft.pdfmu.error.ErrorType.SIGNATURE_ADD_TSA_HANDSHAKE_FAILURE;
38 import static cz.hobrasoft.pdfmu.error.ErrorType.SIGNATURE_ADD_TSA_LOGIN_FAIL;
39 import static cz.hobrasoft.pdfmu.error.ErrorType.SIGNATURE_ADD_TSA_SSL_FATAL_ALERT;
40 import static cz.hobrasoft.pdfmu.error.ErrorType.SIGNATURE_ADD_TSA_SSL_HANDSHAKE_EXCEPTION;
41 import static cz.hobrasoft.pdfmu.error.ErrorType.SIGNATURE_ADD_TSA_UNAUTHORIZED;
42 import static cz.hobrasoft.pdfmu.error.ErrorType.SIGNATURE_ADD_TSA_UNREACHABLE;
43 import static cz.hobrasoft.pdfmu.error.ErrorType.SIGNATURE_ADD_TSA_UNTRUSTED;
44 import static cz.hobrasoft.pdfmu.error.ErrorType.SIGNATURE_ADD_UNSUPPORTED_DIGEST_ALGORITHM;
45 import static cz.hobrasoft.pdfmu.error.ErrorType.SSL_TRUSTSTORE_EMPTY;
46 import static cz.hobrasoft.pdfmu.error.ErrorType.SSL_TRUSTSTORE_INCORRECT_TYPE;
47 import cz.hobrasoft.pdfmu.jackson.SignatureAdd;
48 import cz.hobrasoft.pdfmu.operation.Operation;
49 import cz.hobrasoft.pdfmu.operation.OperationCommon;
50 import cz.hobrasoft.pdfmu.operation.OperationException;
51 import cz.hobrasoft.pdfmu.operation.args.InOutPdfArgs;
52 import java.io.FileNotFoundException;
53 import java.io.IOException;
54 import java.net.SocketException;
55 import java.net.UnknownHostException;
56 import java.security.GeneralSecurityException;
57 import java.security.KeyStore;
58 import java.security.PrivateKey;
59 import java.security.Provider;
60 import java.security.Security;
61 import java.security.SignatureException;
62 import java.security.cert.Certificate;
63 import java.util.AbstractMap.SimpleEntry;
64 import java.util.ArrayList;
65 import java.util.Arrays;
66 import java.util.Collection;
67 import java.util.HashSet;
68 import java.util.Set;
69 import java.util.logging.Logger;
70 import javax.net.ssl.SSLException;
71 import javax.net.ssl.SSLHandshakeException;
72 import net.sourceforge.argparse4j.inf.Namespace;
73 import net.sourceforge.argparse4j.inf.Subparser;
74 import org.bouncycastle.jce.provider.BouncyCastleProvider;
75
76
77
78
79
80
81 public class OperationSignatureAdd extends OperationCommon {
82
83 private static final Logger logger = Logger.getLogger(OperationSignatureAdd.class.getName());
84
85 private final InOutPdfArgs inout = new InOutPdfArgs();
86
87 @Override
88 public Subparser configureSubparser(Subparser subparser) {
89 String help = "Add a digital signature to a PDF document";
90
91
92 subparser.help(help)
93 .description(help)
94 .defaultHelp(true);
95
96 inout.addArguments(subparser);
97 signatureParameters.addArguments(subparser);
98
99 return subparser;
100 }
101
102
103
104 private static final BouncyCastleProvider provider = new BouncyCastleProvider();
105
106 static {
107
108
109
110 Security.addProvider(provider);
111 }
112
113
114 private static final ExternalDigest externalDigest = new BouncyCastleDigest();
115
116
117
118
119
120 private final SignatureParameters signatureParameters = new SignatureParameters();
121
122 @Override
123 public void execute(Namespace namespace) throws OperationException {
124 inout.setFromNamespace(namespace);
125
126
127 signatureParameters.setFromNamespace(namespace);
128
129 writeResult(sign(inout, signatureParameters));
130 }
131
132 private static SignatureAdd sign(InOutPdfArgs inout,
133 SignatureParameters signatureParameters) throws OperationException {
134 SignatureAdd sa;
135 try {
136 inout.openSignature();
137 PdfStamper stp = inout.getPdfStamper();
138 sa = sign(stp, signatureParameters);
139 inout.close(true);
140 } finally {
141 inout.close(false);
142 }
143 return sa;
144 }
145
146
147 private static SignatureAdd sign(PdfStamper stp,
148 SignatureParameters signatureParameters) throws OperationException {
149
150 SignatureAppearanceParameters signatureAppearanceParameters = signatureParameters.appearance;
151 KeystoreParameters keystoreParameters = signatureParameters.keystore;
152 KeyParameters keyParameters = signatureParameters.key;
153 String digestAlgorithm = signatureParameters.digestAlgorithm;
154
155 TSAClient tsaClient = signatureParameters.timestamp.getTSAClient();
156 if (tsaClient != null) {
157 logger.info("Using a timestamp authority to attach a timestamp.");
158 } else {
159 logger.info("No timestamp authority was specified.");
160 }
161
162 MakeSignature.CryptoStandard sigtype = signatureParameters.format;
163
164 assert keystoreParameters != null;
165
166
167 KeyStore ks = keystoreParameters.loadKeystore();
168
169
170 keyParameters.fix(ks, keystoreParameters.getPassword());
171
172 return sign(stp, signatureAppearanceParameters, ks, keyParameters, digestAlgorithm, tsaClient, sigtype);
173 }
174
175
176 private static SignatureAdd sign(PdfStamper stp,
177 SignatureAppearanceParameters signatureAppearanceParameters,
178 KeyStore ks,
179 KeyParameters keyParameters,
180 String digestAlgorithm,
181 TSAClient tsaClient,
182 MakeSignature.CryptoStandard sigtype) throws OperationException {
183
184 PdfSignatureAppearance sap = signatureAppearanceParameters.getSignatureAppearance(stp);
185 assert sap != null;
186
187 return sign(sap, ks, keyParameters, digestAlgorithm, tsaClient, sigtype);
188 }
189
190
191 private static SignatureAdd sign(PdfSignatureAppearance sap,
192 KeyStore ks,
193 KeyParameters keyParameters,
194 String digestAlgorithm,
195 TSAClient tsaClient,
196 MakeSignature.CryptoStandard sigtype) throws OperationException {
197 assert keyParameters != null;
198 String alias = keyParameters.alias;
199 SignatureAdd sa = new SignatureAdd(alias);
200
201 PrivateKey pk = keyParameters.getPrivateKey(ks);
202 Certificate[] chain = keyParameters.getCertificateChain(ks);
203
204 Provider signatureProvider;
205 {
206 Provider ksProvider = ks.getProvider();
207
208
209
210
211 if ("SunMSCAPI".equals(ksProvider.getName())) {
212 signatureProvider = ksProvider;
213 } else {
214 signatureProvider = provider;
215 }
216 }
217
218 sign(sap, pk, digestAlgorithm, chain, tsaClient, sigtype, signatureProvider);
219
220 return sa;
221 }
222
223
224 private static void sign(PdfSignatureAppearance sap,
225 PrivateKey pk,
226 String digestAlgorithm,
227 Certificate[] chain,
228 TSAClient tsaClient,
229 MakeSignature.CryptoStandard sigtype,
230 Provider signatureProvider) throws OperationException {
231 assert digestAlgorithm != null;
232
233
234 logger.info(String.format("Digest algorithm: %s", digestAlgorithm));
235 if (DigestAlgorithms.getAllowedDigests(digestAlgorithm) == null) {
236 throw new OperationException(SIGNATURE_ADD_UNSUPPORTED_DIGEST_ALGORITHM,
237 PdfmuUtils.sortedMap(new SimpleEntry<String, Object>("digestAlgorithm", digestAlgorithm)));
238 }
239
240 logger.info(String.format("Signature security provider: %s", signatureProvider.getName()));
241 ExternalSignature externalSignature = new PrivateKeySignature(pk, digestAlgorithm, signatureProvider.getName());
242
243 sign(sap, externalSignature, chain, tsaClient, sigtype);
244 }
245
246
247 private static void sign(PdfSignatureAppearance sap,
248 ExternalSignature externalSignature,
249 Certificate[] chain,
250 TSAClient tsaClient,
251 MakeSignature.CryptoStandard sigtype) throws OperationException {
252
253 sign(sap, externalDigest, externalSignature, chain, tsaClient, sigtype);
254 }
255
256
257 private static void sign(PdfSignatureAppearance sap,
258 ExternalDigest externalDigest,
259 ExternalSignature externalSignature,
260 Certificate[] chain,
261 TSAClient tsaClient,
262 MakeSignature.CryptoStandard sigtype) throws OperationException {
263
264
265
266
267 Collection<CrlClient> crlList = null;
268
269
270
271 OcspClient ocspClient = null;
272
273
274
275
276 int estimatedSize = 0;
277
278 logger.info(String.format("Cryptographic standard (signature format): %s", sigtype));
279
280 try {
281 MakeSignature.signDetached(sap, externalDigest, externalSignature, chain, crlList, ocspClient, tsaClient, estimatedSize, sigtype);
282 } catch (ExceptionConverter ex) {
283 Exception exInner = ex.getException();
284 if (exInner instanceof IOException) {
285 if (exInner instanceof SSLHandshakeException) {
286 Set<ExceptionMessagePattern> patterns = new HashSet<>();
287
288
289 patterns.add(new ExceptionMessagePattern(
290 SIGNATURE_ADD_TSA_UNTRUSTED,
291 "sun\\.security\\.validator\\.ValidatorException: PKIX path building failed: sun\\.security\\.provider\\.certpath\\.SunCertPathBuilderException: unable to find valid certification path to requested target",
292 new ArrayList<String>()));
293
294
295 patterns.add(new ExceptionMessagePattern(
296 SIGNATURE_ADD_TSA_BAD_CERTIFICATE,
297 "Received fatal alert: bad_certificate",
298 new ArrayList<String>()));
299
300
301 patterns.add(new ExceptionMessagePattern(
302 SIGNATURE_ADD_TSA_HANDSHAKE_FAILURE,
303 "Received fatal alert: handshake_failure",
304 new ArrayList<String>()));
305
306 OperationException oe = null;
307 for (ExceptionMessagePattern p : patterns) {
308 oe = p.getOperationException(exInner);
309 if (oe != null) {
310 break;
311 }
312 }
313 if (oe == null) {
314 ExceptionMessagePattern emp = new ExceptionMessagePattern(
315 SIGNATURE_ADD_TSA_SSL_FATAL_ALERT,
316 "Received fatal alert: (?<alert>.*)",
317 Arrays.asList(new String[]{"alert"}));
318 oe = emp.getOperationException(exInner);
319
320 if (oe == null) {
321
322 oe = new OperationException(SIGNATURE_ADD_TSA_SSL_HANDSHAKE_EXCEPTION, exInner);
323 }
324 }
325 assert oe != null;
326 throw oe;
327 }
328
329 if (exInner instanceof SSLException) {
330 ExceptionMessagePattern emp = new ExceptionMessagePattern(
331 SSL_TRUSTSTORE_EMPTY,
332 "java\\.lang\\.RuntimeException: Unexpected error: java\\.security\\.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty",
333 new ArrayList<String>());
334 OperationException oe = emp.getOperationException(exInner);
335 if (oe != null) {
336 throw oe;
337 }
338 throw new OperationException(SIGNATURE_ADD_FAIL, exInner);
339 }
340
341 if (exInner instanceof UnknownHostException
342 || exInner instanceof FileNotFoundException) {
343 String host = exInner.getMessage();
344 throw new OperationException(SIGNATURE_ADD_TSA_UNREACHABLE,
345 exInner,
346 new SimpleEntry<String, Object>("host", host));
347 }
348
349 if (exInner instanceof SocketException) {
350 ExceptionMessagePattern emp = new ExceptionMessagePattern(
351 SSL_TRUSTSTORE_INCORRECT_TYPE,
352 "java\\.security\\.NoSuchAlgorithmException: Error constructing implementation \\(algorithm: (?<algorithm>.*), provider: (?<provider>.*), class: (?<class>.*)\\)",
353 Arrays.asList(new String[]{"algorithm", "provider", "class"}));
354 OperationException oe = emp.getOperationException(exInner);
355 if (oe != null) {
356 throw oe;
357 }
358 throw new OperationException(SIGNATURE_ADD_FAIL, exInner);
359 }
360
361 Set<ExceptionMessagePattern> patterns = new HashSet<>();
362
363
364
365 patterns.add(new ExceptionMessagePattern(
366 SIGNATURE_ADD_TSA_UNAUTHORIZED,
367 "Server returned HTTP response code: 401 for URL: (?<url>.*)",
368 Arrays.asList(new String[]{"url"})));
369
370
371 patterns.add(new ExceptionMessagePattern(
372 SIGNATURE_ADD_TSA_LOGIN_FAIL,
373 "Invalid TSA '(?<url>.*)' response, code (?<code>\\d+)",
374 Arrays.asList(new String[]{"url", "code"})));
375
376 patterns.add(new ExceptionMessagePattern(
377 SIGNATURE_ADD_FAIL,
378 "unknown tag (?<tag>\\d+) encountered",
379 Arrays.asList(new String[]{"tag"})));
380
381 OperationException oe = null;
382 for (ExceptionMessagePattern p : patterns) {
383 oe = p.getOperationException(exInner);
384 if (oe != null) {
385 break;
386 }
387 }
388 if (oe == null) {
389
390 oe = new OperationException(SIGNATURE_ADD_FAIL, exInner);
391 }
392 assert oe != null;
393 throw oe;
394 }
395 throw new OperationException(SIGNATURE_ADD_FAIL, exInner);
396 } catch (SignatureException ex) {
397 throw new OperationException(SIGNATURE_ADD_SIGNATURE_EXCEPTION, ex);
398 } catch (IOException | DocumentException | GeneralSecurityException ex) {
399 throw new OperationException(SIGNATURE_ADD_FAIL, ex);
400 } catch (NullPointerException ex) {
401
402 throw new OperationException(SIGNATURE_ADD_FAIL, ex);
403 }
404 logger.info("Document successfully signed.");
405 }
406
407 private static Operation instance = null;
408
409 public static Operation getInstance() {
410 if (instance == null) {
411 instance = new OperationSignatureAdd();
412 }
413 return instance;
414 }
415
416
417 private OperationSignatureAdd() {
418 super();
419 }
420
421 }