001/* 
002 * Copyright (C) 2016 Hobrasoft s.r.o.
003 *
004 * This program is free software: you can redistribute it and/or modify
005 * it under the terms of the GNU Affero General Public License as published by
006 * the Free Software Foundation, either version 3 of the License, or
007 * (at your option) any later version.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012 * GNU Affero General Public License for more details.
013 *
014 * You should have received a copy of the GNU Affero General Public License
015 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
016 */
017package cz.hobrasoft.pdfmu;
018
019import com.tngtech.java.junit.dataprovider.DataProvider;
020import com.tngtech.java.junit.dataprovider.DataProviderRunner;
021import com.tngtech.java.junit.dataprovider.UseDataProvider;
022import cz.hobrasoft.pdfmu.jackson.CertificateResult;
023import cz.hobrasoft.pdfmu.jackson.Inspect;
024import cz.hobrasoft.pdfmu.jackson.Signature;
025import cz.hobrasoft.pdfmu.jackson.SignatureMetadata;
026import cz.hobrasoft.pdfmu.operation.OperationException;
027import cz.hobrasoft.pdfmu.operation.OperationInspect;
028import java.io.File;
029import java.io.IOException;
030import java.text.DateFormat;
031import java.text.ParseException;
032import java.text.SimpleDateFormat;
033import java.util.ArrayList;
034import java.util.Arrays;
035import java.util.Date;
036import java.util.LinkedHashMap;
037import java.util.List;
038import java.util.Locale;
039import java.util.Map;
040import java.util.concurrent.TimeUnit;
041import org.junit.Assert;
042import org.junit.Rule;
043import org.junit.Test;
044import org.junit.contrib.java.lang.system.Assertion;
045import org.junit.contrib.java.lang.system.EnvironmentVariables;
046import org.junit.runner.RunWith;
047
048/**
049 * @author Filip Bártek
050 */
051@RunWith(DataProviderRunner.class)
052public class MainSignTest extends MainTest {
053
054    @Rule
055    public final EnvironmentVariables environmentVariables
056            = new EnvironmentVariables();
057
058    @Test
059    public void testNoInput() throws IOException {
060        String[] args = new String[]{
061            "sign"
062        };
063        exit.expectSystemExitWithStatus(14);
064        Main.main(args);
065        assert false;
066    }
067
068    @Test
069    public void testNoKeystore() throws IOException {
070        File inFile = BLANK_12_PDF.getFile(folder);
071        final File outFile = newFile("out.pdf", false);
072
073        String[] args = new String[]{
074            "sign",
075            inFile.getAbsolutePath(),
076            "--out",
077            outFile.getAbsolutePath()
078        };
079
080        exit.expectSystemExitWithStatus(41);
081        exit.checkAssertionAfterwards(new Assertion() {
082            @Override
083            public void checkAssertion() {
084                Assert.assertFalse(outFile.exists());
085            }
086        });
087        Main.main(args);
088        assert false;
089    }
090
091    @DataProvider
092    public static Object[][] dataProviderEmpty() {
093        return new Object[][]{
094            new Object[]{"empty.p12", null},
095            new Object[]{"empty.pfx", null},
096            new Object[]{"empty.jks", null},
097            new Object[]{"empty.jceks", "jceks"}
098        };
099    }
100
101    @Test
102    @UseDataProvider
103    public void testEmpty(String keystoreFileName, String keystoreType)
104            throws IOException {
105        File inFile = BLANK_12_PDF.getFile(folder);
106        File keystoreFile = new FileResource(keystoreFileName).getFile(folder);
107        final File outFile = newFile("out.pdf", false);
108
109        List<String> argsList = new ArrayList<>();
110        argsList.add("sign");
111        argsList.add(inFile.getAbsolutePath());
112        argsList.add("--out");
113        argsList.add(outFile.getAbsolutePath());
114        argsList.add("--keystore");
115        argsList.add(keystoreFile.getAbsolutePath());
116        if (keystoreType != null) {
117            argsList.add("--keystore-type");
118            argsList.add(keystoreType);
119        }
120
121        exit.expectSystemExitWithStatus(51);
122        exit.checkAssertionAfterwards(new Assertion() {
123            @Override
124            public void checkAssertion() {
125                Assert.assertFalse(outFile.exists());
126            }
127        });
128        Main.main(argsList.toArray(new String[]{}));
129        assert false;
130    }
131
132    private static void assertDateBetween(Date actual, Date begin, Date end) {
133        assert actual != null;
134        assert begin != null;
135        // The dates saved in PDF files only have second precision,
136        // while new Date() has millisecond precision.
137        // We compare with a 1 second delta to compensate
138        Assert.assertTrue(actual.getTime() - begin.getTime()
139                > TimeUnit.SECONDS.toMillis(-1));
140        assert end != null;
141        Assert.assertFalse(actual.after(end));
142    }
143
144    // Example content of the properties dictionary:
145    // Producer => iText® 5.5.6 ©2000-2015 iText Group NV (AGPL-version); modified using iText® 5.5.6 ©2000-2015 iText Group NV (AGPL-version)
146    // CreationDate => D:20160525204745+02'00'
147    // ModDate => D:20160601102240+02'00'
148    private static void assertDefaultPropertiesValid(
149            Map<String, String> properties, String expectedCreationDate,
150            Date modDateBegin, Date modDateEnd) throws ParseException {
151        Assert.assertNotNull(properties);
152        Assert.assertEquals(3, properties.size());
153        Assert.assertTrue(properties.containsKey("Producer"));
154        String expectedProducer = "iText® 5.5.6 ©2000-2015 iText Group NV (AGPL-version); modified using iText® 5.5.6 ©2000-2015 iText Group NV (AGPL-version)";
155        Assert.assertEquals(expectedProducer, properties.get("Producer"));
156        Assert.assertTrue(properties.containsKey("CreationDate"));
157        assert expectedCreationDate != null;
158        Assert.assertEquals(expectedCreationDate, properties.get("CreationDate"));
159        Assert.assertTrue(properties.containsKey("ModDate"));
160        DateFormat dateFormat = new SimpleDateFormat("'D:'yyyyMMddHHmmssX'''00'''", Locale.ROOT);
161        Date actualModDate = dateFormat.parse(properties.get("ModDate"));
162        assertDateBetween(actualModDate, modDateBegin, modDateEnd);
163    }
164
165    @Test
166    public void test1p12() throws IOException {
167        final PdfFileResource inFileResource = BLANK_12_PDF;
168        File inFile = inFileResource.getFile(folder);
169        File keystoreFile = new FileResource("1.p12").getFile(folder);
170        final File outFile = newFile("out.pdf", false);
171
172        List<String> argsList = new ArrayList<>();
173        argsList.add("sign");
174        argsList.add(inFile.getAbsolutePath());
175        argsList.add("--out");
176        argsList.add(outFile.getAbsolutePath());
177        argsList.add("--keystore");
178        argsList.add(keystoreFile.getAbsolutePath());
179
180        final Date modDateBegin = new Date();
181
182        exit.expectSystemExitWithStatus(0);
183        exit.checkAssertionAfterwards(new Assertion() {
184            @Override
185            public void checkAssertion() throws OperationException, IOException, ParseException {
186                Date modDateEnd = new Date();
187                Assert.assertTrue(outFile.exists());
188                Inspect inspect = OperationInspect.getInstance().execute(outFile);
189                assert inspect != null;
190                assert inFileResource.version != null;
191                Assert.assertEquals(inFileResource.version, inspect.version);
192                assertDefaultPropertiesValid(inspect.properties,
193                        inFileResource.creationDate, modDateBegin, modDateEnd);
194                Assert.assertNotNull(inspect.signatures);
195                Assert.assertEquals(1, inspect.signatures.nRevisions.intValue());
196                Assert.assertNotNull(inspect.signatures.signatures);
197                Assert.assertEquals(1, inspect.signatures.signatures.size());
198                {
199                    Signature signature = inspect.signatures.signatures.get(0);
200                    Assert.assertNotNull(signature);
201                    Assert.assertEquals("Signature1", signature.id);
202                    Assert.assertTrue(signature.coversWholeDocument);
203                    Assert.assertEquals(1, signature.revision.intValue());
204                    {
205                        SignatureMetadata metadata = signature.metadata;
206                        Assert.assertNotNull(metadata);
207                        Assert.assertNull(metadata.name);
208                        Assert.assertEquals("", metadata.reason);
209                        Assert.assertEquals("", metadata.location);
210                        Assert.assertNotNull(metadata.date);
211                        DateFormat dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.ROOT);
212                        // Example: "Wed Jun 01 11:22:38 CEST 2016"
213                        Date actualDate = dateFormat.parse(metadata.date);
214                        assertDateBetween(actualDate, modDateBegin, modDateEnd);
215                    }
216                    Assert.assertNotNull(signature.certificates);
217                    Assert.assertEquals(1, signature.certificates.size());
218                    {
219                        CertificateResult certificate = signature.certificates.get(0);
220                        Assert.assertNotNull(certificate);
221                        Assert.assertEquals("X.509", certificate.type);
222                        Assert.assertTrue(certificate.selfSigned);
223                        Map<String, List<String>> dn = new LinkedHashMap<>();
224                        dn.put("CN", Arrays.asList(new String[]{"CN1"}));
225                        dn.put("E", Arrays.asList(new String[]{"E1"}));
226                        dn.put("OU", Arrays.asList(new String[]{"OU1"}));
227                        dn.put("O", Arrays.asList(new String[]{"O1"}));
228                        dn.put("L", Arrays.asList(new String[]{"L1"}));
229                        dn.put("ST", Arrays.asList(new String[]{"ST1"}));
230                        dn.put("C", Arrays.asList(new String[]{"C1"}));
231                        Assert.assertEquals(dn, certificate.subject);
232                        Assert.assertEquals(dn, certificate.issuer);
233                    }
234                }
235            }
236        });
237        Main.main(argsList.toArray(new String[]{}));
238        assert false;
239    }
240
241    @Test
242    public void testKeyIncorrect() throws IOException {
243        final PdfFileResource inFileResource = BLANK_12_PDF;
244        File inFile = inFileResource.getFile(folder);
245        File keystoreFile = new FileResource("1.p12").getFile(folder);
246        final File outFile = newFile("out.pdf", false);
247
248        List<String> argsList = new ArrayList<>();
249        argsList.add("sign");
250        argsList.add(inFile.getAbsolutePath());
251        argsList.add("--out");
252        argsList.add(outFile.getAbsolutePath());
253        argsList.add("--keystore");
254        argsList.add(keystoreFile.getAbsolutePath());
255        argsList.add("--key-alias");
256        argsList.add("incorrect-alias");
257
258        exit.expectSystemExitWithStatus(52);
259        exit.checkAssertionAfterwards(new Assertion() {
260            @Override
261            public void checkAssertion() {
262                Assert.assertFalse(outFile.exists());
263            }
264        });
265        Main.main(argsList.toArray(new String[]{}));
266        assert false;
267    }
268
269    @Test
270    public void testKeyCorrect() throws IOException {
271        final PdfFileResource inFileResource = BLANK_12_PDF;
272        File inFile = inFileResource.getFile(folder);
273        File keystoreFile = new FileResource("1.p12").getFile(folder);
274        final File outFile = newFile("out.pdf", false);
275
276        List<String> argsList = new ArrayList<>();
277        argsList.add("sign");
278        argsList.add(inFile.getAbsolutePath());
279        argsList.add("--out");
280        argsList.add(outFile.getAbsolutePath());
281        argsList.add("--keystore");
282        argsList.add(keystoreFile.getAbsolutePath());
283        argsList.add("--key-alias");
284        argsList.add("cn1");
285
286        exit.expectSystemExitWithStatus(0);
287        exit.checkAssertionAfterwards(new Assertion() {
288            @Override
289            public void checkAssertion() {
290                Assert.assertTrue(outFile.exists());
291            }
292        });
293        Main.main(argsList.toArray(new String[]{}));
294        assert false;
295    }
296
297    @Test
298    public void testPasswordMissing() throws IOException {
299        final PdfFileResource inFileResource = BLANK_12_PDF;
300        File inFile = inFileResource.getFile(folder);
301        File keystoreFile = new FileResource("1-changeit.p12").getFile(folder);
302        final File outFile = newFile("out.pdf", false);
303
304        List<String> argsList = new ArrayList<>();
305        argsList.add("sign");
306        argsList.add(inFile.getAbsolutePath());
307        argsList.add("--out");
308        argsList.add(outFile.getAbsolutePath());
309        argsList.add("--keystore");
310        argsList.add(keystoreFile.getAbsolutePath());
311        argsList.add("--keystore-type");
312        argsList.add("pkcs12");
313
314        exit.expectSystemExitWithStatus(43);
315        exit.checkAssertionAfterwards(new Assertion() {
316            @Override
317            public void checkAssertion() {
318                Assert.assertFalse(outFile.exists());
319            }
320        });
321        Main.main(argsList.toArray(new String[]{}));
322        assert false;
323    }
324
325    @Test
326    public void testPasswordIncorrect() throws IOException {
327        final PdfFileResource inFileResource = BLANK_12_PDF;
328        File inFile = inFileResource.getFile(folder);
329        File keystoreFile = new FileResource("1-changeit.p12").getFile(folder);
330        final File outFile = newFile("out.pdf", false);
331
332        List<String> argsList = new ArrayList<>();
333        argsList.add("sign");
334        argsList.add(inFile.getAbsolutePath());
335        argsList.add("--out");
336        argsList.add(outFile.getAbsolutePath());
337        argsList.add("--keystore");
338        argsList.add(keystoreFile.getAbsolutePath());
339        argsList.add("--keystore-type");
340        argsList.add("pkcs12");
341        argsList.add("--keystore-password");
342        argsList.add("incorrect-password");
343
344        exit.expectSystemExitWithStatus(43);
345        exit.checkAssertionAfterwards(new Assertion() {
346            @Override
347            public void checkAssertion() {
348                Assert.assertFalse(outFile.exists());
349            }
350        });
351        Main.main(argsList.toArray(new String[]{}));
352        assert false;
353    }
354
355    @Test
356    public void testPasswordCmdlineSuccess() throws IOException {
357        final PdfFileResource inFileResource = BLANK_12_PDF;
358        File inFile = inFileResource.getFile(folder);
359        File keystoreFile = new FileResource("1-changeit.p12").getFile(folder);
360        final File outFile = newFile("out.pdf", false);
361
362        List<String> argsList = new ArrayList<>();
363        argsList.add("sign");
364        argsList.add(inFile.getAbsolutePath());
365        argsList.add("--out");
366        argsList.add(outFile.getAbsolutePath());
367        argsList.add("--keystore");
368        argsList.add(keystoreFile.getAbsolutePath());
369        argsList.add("--keystore-type");
370        argsList.add("pkcs12");
371        argsList.add("--keystore-password");
372        argsList.add("changeit");
373
374        exit.expectSystemExitWithStatus(0);
375        exit.checkAssertionAfterwards(new Assertion() {
376            @Override
377            public void checkAssertion() {
378                Assert.assertTrue(outFile.exists());
379            }
380        });
381        Main.main(argsList.toArray(new String[]{}));
382        assert false;
383    }
384
385    @Test
386    public void testPasswordEnvvarDefaultSuccess() throws IOException {
387        final PdfFileResource inFileResource = BLANK_12_PDF;
388        File inFile = inFileResource.getFile(folder);
389        File keystoreFile = new FileResource("1-changeit.p12").getFile(folder);
390        final File outFile = newFile("out.pdf", false);
391
392        List<String> argsList = new ArrayList<>();
393        argsList.add("sign");
394        argsList.add(inFile.getAbsolutePath());
395        argsList.add("--out");
396        argsList.add(outFile.getAbsolutePath());
397        argsList.add("--keystore");
398        argsList.add(keystoreFile.getAbsolutePath());
399        argsList.add("--keystore-type");
400        argsList.add("pkcs12");
401
402        environmentVariables.set("PDFMU_STOREPASS", "changeit");
403
404        exit.expectSystemExitWithStatus(0);
405        exit.checkAssertionAfterwards(new Assertion() {
406            @Override
407            public void checkAssertion() {
408                Assert.assertTrue(outFile.exists());
409            }
410        });
411        Main.main(argsList.toArray(new String[]{}));
412        assert false;
413    }
414
415    @Test
416    public void testPasswordEnvvarCustomSuccess() throws IOException {
417        final PdfFileResource inFileResource = BLANK_12_PDF;
418        File inFile = inFileResource.getFile(folder);
419        File keystoreFile = new FileResource("1-changeit.p12").getFile(folder);
420        final File outFile = newFile("out.pdf", false);
421
422        List<String> argsList = new ArrayList<>();
423        argsList.add("sign");
424        argsList.add(inFile.getAbsolutePath());
425        argsList.add("--out");
426        argsList.add(outFile.getAbsolutePath());
427        argsList.add("--keystore");
428        argsList.add(keystoreFile.getAbsolutePath());
429        argsList.add("--keystore-type");
430        argsList.add("pkcs12");
431        argsList.add("--keystore-password-envvar");
432        argsList.add("PDFMU_STOREPASS_CUSTOM_ENVVAR");
433
434        environmentVariables.set("PDFMU_STOREPASS_CUSTOM_ENVVAR", "changeit");
435
436        exit.expectSystemExitWithStatus(0);
437        exit.checkAssertionAfterwards(new Assertion() {
438            @Override
439            public void checkAssertion() {
440                Assert.assertTrue(outFile.exists());
441            }
442        });
443        Main.main(argsList.toArray(new String[]{}));
444        assert false;
445    }
446}