org.signserver.module.pdfsigner.PDFSignerUnitTest.java Source code

Java tutorial

Introduction

Here is the source code for org.signserver.module.pdfsigner.PDFSignerUnitTest.java

Source

/*************************************************************************
 *                                                                       *
 *  SignServer: The OpenSource Automated Signing Server                  *
 *                                                                       *
 *  This software is free software; you can redistribute it and/or       *
 *  modify it under the terms of the GNU Lesser General Public           *
 *  License as published by the Free Software Foundation; either         *
 *  version 2.1 of the License, or any later version.                    *
 *                                                                       *
 *  See terms of license at gnu.org.                                     *
 *                                                                       *
 *************************************************************************/
package org.signserver.module.pdfsigner;

import com.lowagie.text.DocumentException;
import com.lowagie.text.exceptions.BadPasswordException;
import com.lowagie.text.pdf.*;
import java.io.*;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.cert.*;
import java.util.*;

import junit.framework.TestCase;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.CRLReason;
import org.bouncycastle.cert.X509CRLHolder;
import org.bouncycastle.cert.X509v2CRLBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CRLConverter;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.signserver.common.*;
import org.signserver.common.util.PathUtil;
import org.signserver.ejb.interfaces.IGlobalConfigurationSession;
import org.signserver.ejb.interfaces.IWorkerSession;
import org.signserver.server.cryptotokens.ICryptoToken;
import org.signserver.test.utils.builders.CertBuilder;
import org.signserver.test.utils.builders.CertBuilderException;
import org.signserver.test.utils.builders.CertExt;
import org.signserver.test.utils.builders.CryptoUtils;
import org.signserver.test.utils.mock.GlobalConfigurationSessionMock;
import org.signserver.test.utils.mock.MockedCryptoToken;
import org.signserver.test.utils.mock.WorkerSessionMock;

/**
 * Unit tests for PDFSigner.
 *
 * This tests uses a mockup and does not require an running application
 * server. Tests that require that can be placed among the system tests.
 *
 * @author Markus Kils
 * @version $Id: PDFSignerUnitTest.java 5977 2015-03-27 10:30:50Z netmackan $
 */
public class PDFSignerUnitTest extends TestCase {

    /** Logger for this class. */
    public static final Logger LOG = Logger.getLogger(PDFSignerUnitTest.class);

    /** Worker7897: Default algorithms, default hashing setting. */
    private static final int WORKER1 = 7897;
    private static final int WORKER2 = 7898;

    private static final String NAME = "NAME";
    private static final String AUTHTYPE = "AUTHTYPE";

    private static final String CRYPTOTOKEN_CLASSNAME = "org.signserver.server.cryptotokens.HardCodedCryptoToken";
    private final String SAMPLE_OWNER123_PASSWORD = "owner123";
    private final String SAMPLE_USER_AAA_PASSWORD = "user\u00e5\u00e4\u00f6";
    private final String SAMPLE_OPEN123_PASSWORD = "open123";

    private final String ILLEGAL_DIGEST_FOR_DSA_MESSAGE = "Only SHA1 is permitted as digest algorithm for DSA public/private keys";

    private IGlobalConfigurationSession.IRemote globalConfig;
    private IWorkerSession.IRemote workerSession;

    private File sampleOk;
    private File sampleRestricted;

    private File sample;
    private File sampleOpen123;
    private File sampleOpen123Owner123;
    private File sampleOwner123;
    private File sampleUseraao;
    private File sampleCertifiedSigningAllowed;
    private File sampleCertifiedNoChangesAllowed;
    private File sampleCertifiedFormFillingAllowed;
    private File sampleSigned;
    //    private File sampleLowprintingOwner123;

    private JcaX509CertificateConverter converter = new JcaX509CertificateConverter();

    public PDFSignerUnitTest() throws FileNotFoundException {
        SignServerUtil.installBCProvider();
        File home = PathUtil.getAppHome();
        sampleOk = new File(home, "res/test/ok.pdf");
        sampleRestricted = new File(home, "res/test/sample-restricted.pdf");
        sample = new File(home, "res/test/pdf/sample.pdf");
        sampleOpen123 = new File(home, "res/test/pdf/sample-open123.pdf");
        sampleOpen123Owner123 = new File(home, "res/test/pdf/sample-open123-owner123.pdf");
        sampleOwner123 = new File(home, "res/test/pdf/sample-owner123.pdf");
        sampleUseraao = new File(home, "res/test/pdf/sample-useraao.pdf");
        sampleCertifiedSigningAllowed = new File(home, "res/test/pdf/sample-certified-signingallowed.pdf");
        sampleCertifiedNoChangesAllowed = new File(home, "res/test/pdf/sample-certified-nochangesallowed.pdf");
        sampleCertifiedFormFillingAllowed = new File(home, "res/test/pdf/sample-certified-formfillingallowed.pdf");
        sampleSigned = new File(home, "res/test/pdf/sample-signed.pdf");
        //        sampleLowprintingOwner123 = new File(home, "res/test/pdf/sample-lowprinting-owner123.pdf");
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();

        setupWorkers();
    }

    @Override
    protected void tearDown() throws Exception {
        super.tearDown();
    }

    /**
     * Test signing of a simple PDF. Mostly to test that the mockup and PDF 
     * signing works before doing other tests that are expected to fail.
     * @throws Exception in case of error
     */
    public void test01signOk() throws Exception {
        byte[] data = readFile(sampleOk);

        final GenericSignRequest request = new GenericSignRequest(100, data);

        final GenericSignResponse response = (GenericSignResponse) workerSession.process(WORKER1, request,
                new RequestContext());
        assertEquals("requestId", 100, response.getRequestID());

        Certificate signercert = response.getSignerCertificate();
        assertNotNull(signercert);
    }

    /**
     * Tries to sign a PDF with document restrictions. As no password is 
     * supplied it throws an IllegalRequestException.
     * @throws Exception in case of error
     */
    public void test02SignWithRestrictionsNoPasswordSupplied() throws Exception {
        try {
            workerSession.process(WORKER1, new GenericSignRequest(200, readFile(sampleRestricted)),
                    new RequestContext());
            fail("Should have thrown exception");
        } catch (IllegalRequestException ignored) {
            // OK
        }

        try {
            workerSession.process(WORKER1, new GenericSignRequest(200, readFile(sampleOpen123)),
                    new RequestContext());
            fail("Should have thrown exception");
        } catch (IllegalRequestException ignored) {
            // OK
        }

        try {
            workerSession.process(WORKER1, new GenericSignRequest(200, readFile(sampleOpen123Owner123)),
                    new RequestContext());
            fail("Should have thrown exception");
        } catch (IllegalRequestException ignored) {
            // OK
        }

        try {
            workerSession.process(WORKER1, new GenericSignRequest(200, readFile(sampleOwner123)),
                    new RequestContext());
            fail("Should have thrown exception");
        } catch (IllegalRequestException ignored) {
            // OK
        }
    }

    /**
     * Tries to sign a PDF with document restrictions. As the correct passwords 
     * are supplied it should succeed.
     */
    public void test02SignWithRestrictionsPasswordSupplied() throws Exception {
        signProtectedPDF(sampleOpen123, "open123");
        signProtectedPDF(sampleOwner123, SAMPLE_OWNER123_PASSWORD);
        signProtectedPDF(sampleOpen123Owner123, "owner123");
        signProtectedPDF(sample, null);
        signProtectedPDF(sample, "");
        signProtectedPDF(sampleUseraao, SAMPLE_USER_AAA_PASSWORD);
    }

    /**
     * Tests the REJECT_PERMISSIONS with different values to see that the 
     * signer rejects documents with permissions not allowed.
     */
    public void test03RejectingPermissions() throws Exception {

        // First test without any constraints
        signProtectedPDF(sampleUseraao, SAMPLE_USER_AAA_PASSWORD);

        // Test with empty list of constraints
        workerSession.setWorkerProperty(WORKER1, "REJECT_PERMISSIONS", "");
        workerSession.reloadConfiguration(WORKER1);
        signProtectedPDF(sampleUseraao, SAMPLE_USER_AAA_PASSWORD);

        // Test with unknown permission
        workerSession.setWorkerProperty(WORKER1, "REJECT_PERMISSIONS", "_NON_EXISTING_PERMISSION_");
        workerSession.reloadConfiguration(WORKER1);
        signProtectedPDF(sampleUseraao, SAMPLE_USER_AAA_PASSWORD);

        // Test with document containing an not allowed permission
        workerSession.setWorkerProperty(WORKER1, "REJECT_PERMISSIONS", "ALLOW_PRINTING");
        workerSession.reloadConfiguration(WORKER1);
        try {
            signProtectedPDF(sampleUseraao, SAMPLE_USER_AAA_PASSWORD);
            fail("Should have thrown exception");
        } catch (IllegalRequestException ok) {
            LOG.debug("OK: " + ok.getMessage());
        }

        // Test with document containing two not allowed permissions
        workerSession.setWorkerProperty(WORKER1, "REJECT_PERMISSIONS", "ALLOW_PRINTING,ALLOW_COPY");
        workerSession.reloadConfiguration(WORKER1);
        try {
            signProtectedPDF(sampleUseraao, SAMPLE_USER_AAA_PASSWORD);
            fail("Should have thrown exception");
        } catch (IllegalRequestException ok) {
            LOG.debug("OK: " + ok.getMessage());
        }

        // Test with document containing one not allowed permission and 
        // not the other disallowed permission
        workerSession.setWorkerProperty(WORKER1, "REJECT_PERMISSIONS", "ALLOW_COPY,ALLOW_MODIFY_CONTENTS");
        workerSession.reloadConfiguration(WORKER1);
        try {
            signProtectedPDF(sampleOwner123, SAMPLE_OWNER123_PASSWORD);
            fail("Should have thrown exception");
        } catch (IllegalRequestException ok) {
            LOG.debug("OK: " + ok.getMessage());
        }

        // Test with document not contianing any permissions and thus implicitly 
        // allows everything
        workerSession.setWorkerProperty(WORKER1, "REJECT_PERMISSIONS", "ALLOW_PRINTING");
        workerSession.reloadConfiguration(WORKER1);
        try {
            signProtectedPDF(sampleOk, null);
            fail("Should have thrown exception");
        } catch (IllegalRequestException ok) {
            LOG.debug("OK: " + ok.getMessage());
        }

        // Test a document where only low-res printing is allowed
        /* TODO: When found such a document
        workerSession.setWorkerProperty(WORKER1, "REJECT_PERMISSIONS", "ALLOW_PRINTING");
        workerSession.reloadConfiguration(WORKER1);
        signProtectedPDF(sampleLowprintingOwner123, SAMPLE_OWNER123_PASSWORD);
        workerSession.setWorkerProperty(WORKER1, "REJECT_PERMISSIONS", "ALLOW_DEGRADED_PRINTING");
        workerSession.reloadConfiguration(WORKER1);
        try {
        signProtectedPDF(sampleLowprintingOwner123, SAMPLE_OWNER123_PASSWORD);
        fail("Should have thrown exception");
        } catch (IllegalRequestException ok) {
        LOG.debug("OK: " + ok.getMessage());
        }*/
    }

    /**
     * Tests the property SET_PERMISSIONS by setting different values and make 
     * sure they end up in the signed PDF. Also tests that when not setting 
     * the property the original permissions remain.
     */
    public void test04SetPermissions() throws Exception {

        doTestSetPermissions(WORKER1, sampleOwner123, SAMPLE_OWNER123_PASSWORD, null,
                Arrays.asList("ALLOW_PRINTING", "ALLOW_MODIFY_CONTENTS", "ALLOW_COPY", "ALLOW_MODIFY_ANNOTATIONS",
                        "ALLOW_FILL_IN", "ALLOW_SCREENREADERS", "ALLOW_ASSEMBLY", "ALLOW_DEGRADED_PRINTING"));
        doTestSetPermissions(WORKER1, sampleOwner123, SAMPLE_OWNER123_PASSWORD, null,
                Arrays.asList("ALLOW_PRINTING", "ALLOW_MODIFY_CONTENTS", "ALLOW_COPY", "ALLOW_MODIFY_ANNOTATIONS",
                        "ALLOW_FILL_IN", "ALLOW_ASSEMBLY", "ALLOW_DEGRADED_PRINTING"));
        doTestSetPermissions(WORKER1, sampleOwner123, SAMPLE_OWNER123_PASSWORD, null,
                Arrays.asList("ALLOW_MODIFY_CONTENTS", "ALLOW_COPY", "ALLOW_MODIFY_ANNOTATIONS", "ALLOW_FILL_IN",
                        "ALLOW_ASSEMBLY", "ALLOW_DEGRADED_PRINTING"));
        doTestSetPermissions(WORKER1, sampleOwner123, SAMPLE_OWNER123_PASSWORD, null, Arrays.asList("ALLOW_COPY",
                "ALLOW_MODIFY_ANNOTATIONS", "ALLOW_FILL_IN", "ALLOW_ASSEMBLY", "ALLOW_DEGRADED_PRINTING"));
        doTestSetPermissions(WORKER1, sampleOwner123, SAMPLE_OWNER123_PASSWORD, null, Arrays.asList("ALLOW_COPY",
                "ALLOW_MODIFY_ANNOTATIONS", "ALLOW_FILL_IN", "ALLOW_DEGRADED_PRINTING"));
        doTestSetPermissions(WORKER1, sampleOwner123, SAMPLE_OWNER123_PASSWORD, null,
                Arrays.asList("ALLOW_COPY", "ALLOW_FILL_IN", "ALLOW_DEGRADED_PRINTING"));
        doTestSetPermissions(WORKER1, sampleOwner123, SAMPLE_OWNER123_PASSWORD, null,
                Arrays.asList("ALLOW_FILL_IN", "ALLOW_DEGRADED_PRINTING"));
        doTestSetPermissions(WORKER1, sampleOwner123, SAMPLE_OWNER123_PASSWORD, null,
                Arrays.asList("ALLOW_FILL_IN"));
        doTestSetPermissions(WORKER1, sampleOwner123, SAMPLE_OWNER123_PASSWORD, null, new LinkedList<String>());

        // Without SET_PERMISSIONS the original permissions should remain
        // The sampleOwner123 originally has: ALLOW_FILL_IN,ALLOW_MODIFY_ANNOTATIONS,ALLOW_MODIFY_CONTENTS
        workerSession.removeWorkerProperty(WORKER1, "SET_PERMISSIONS");
        workerSession.reloadConfiguration(WORKER1);
        Set<String> expected = new HashSet<String>(
                Arrays.asList("ALLOW_FILL_IN", "ALLOW_MODIFY_ANNOTATIONS", "ALLOW_MODIFY_CONTENTS"));
        Permissions actual = getPermissions(signProtectedPDF(sampleOwner123, SAMPLE_OWNER123_PASSWORD),
                SAMPLE_OWNER123_PASSWORD.getBytes("ISO-8859-1"));
        assertEquals(expected, actual.asSet());
    }

    /** Tests the property SET_PERMISSIONS by setting different values and make 
     * sure they end up in the signed PDF. Also tests that when not setting 
     * the property the original permissions remain.
     * This time for documents without owner password set or with both user 
     * and owner passwords.
     */
    public void test04SetPermissionsWithoutOwner() throws Exception {
        doTestSetPermissions(WORKER1, sample, null, null,
                Arrays.asList("ALLOW_PRINTING", "ALLOW_MODIFY_CONTENTS", "ALLOW_COPY", "ALLOW_MODIFY_ANNOTATIONS",
                        "ALLOW_FILL_IN", "ALLOW_SCREENREADERS", "ALLOW_ASSEMBLY", "ALLOW_DEGRADED_PRINTING"));
        doTestSetPermissions(WORKER1, sampleOpen123, null, SAMPLE_OPEN123_PASSWORD,
                Arrays.asList("ALLOW_PRINTING", "ALLOW_MODIFY_CONTENTS", "ALLOW_COPY", "ALLOW_MODIFY_ANNOTATIONS",
                        "ALLOW_FILL_IN", "ALLOW_ASSEMBLY", "ALLOW_DEGRADED_PRINTING"));
        doTestSetPermissions(WORKER1, sampleOpen123Owner123, SAMPLE_OWNER123_PASSWORD, SAMPLE_OPEN123_PASSWORD,
                Arrays.asList("ALLOW_MODIFY_CONTENTS", "ALLOW_COPY", "ALLOW_MODIFY_ANNOTATIONS", "ALLOW_FILL_IN",
                        "ALLOW_ASSEMBLY", "ALLOW_DEGRADED_PRINTING"));
    }

    private void doTestSetPermissions(int workerId, File pdf, String ownerPassword, String userPassword,
            Collection<String> permissions) throws Exception {
        Set<String> expected = new HashSet<String>(permissions);
        workerSession.setWorkerProperty(workerId, "SET_PERMISSIONS", toString(expected, ","));
        workerSession.reloadConfiguration(workerId);
        String password = ownerPassword == null ? userPassword : ownerPassword;
        byte[] pdfbytes = signProtectedPDF(pdf, password);
        Permissions actual = getPermissions(pdfbytes,
                userPassword == null ? (ownerPassword == null ? null : ownerPassword.getBytes("ISO-8859-1"))
                        : userPassword.getBytes("ISO-8859-1"));
        assertEquals(expected, actual.asSet());

        // Check that user password hasn't become the owner password (unless it already were)
        if (ownerPassword != null && userPassword != null && !ownerPassword.equals(userPassword)) {
            assertUserNotOwnerPassword(pdfbytes, userPassword);
        }

        // Check that the document is protected by an permissions password
        PdfReader reader = new PdfReader(pdfbytes,
                userPassword == null ? null : userPassword.getBytes("ISO-8859-1"));
        assertFalse("Should not be openned with full permissions", reader.isOpenedWithFullPermissions());
    }

    private byte[] doTestRemovePermissions(int workerId, File pdf, String ownerPassword, String userPassword,
            Collection<String> removePermissions, Collection<String> expected) throws Exception {
        Set<String> expectedSet = new HashSet<String>(expected);
        workerSession.setWorkerProperty(workerId, "REMOVE_PERMISSIONS", toString(removePermissions, ","));
        workerSession.reloadConfiguration(workerId);
        byte[] pdfbytes = signProtectedPDF(pdf, ownerPassword == null ? userPassword : ownerPassword);
        Permissions actual = getPermissions(pdfbytes,
                ownerPassword == null ? (userPassword == null ? null : userPassword.getBytes("ISO-8859-1"))
                        : ownerPassword.getBytes("ISO-8859-1"));
        assertEquals(expectedSet, actual.asSet());

        // Check that user password hasn't become the owner password (unless it already were)
        if (ownerPassword != null && userPassword != null && !ownerPassword.equals(userPassword)) {
            assertUserNotOwnerPassword(pdfbytes, userPassword);
        }

        // If some permissions are removed, check that the document is protected by an permissions password
        if (!removePermissions.isEmpty()) {
            PdfReader reader = new PdfReader(pdfbytes,
                    userPassword == null ? null : userPassword.getBytes("ISO-8859-1"));
            assertFalse("Should not be openned with full permissions", reader.isOpenedWithFullPermissions());
        }

        return pdfbytes;
    }

    private static String toString(Collection<String> collection, String separator) {
        StringBuilder buff = new StringBuilder();
        for (String s : collection) {
            buff.append(s).append(separator);
        }
        return buff.toString();
    }

    /**
     * Tests the REMOVE_PERMISSIONS property by setting different values for 
     * what to remove and check that they were removed from the signed PDF.
     */
    public void test04RemovePermissions() throws Exception {
        // The sampleOwner123 originally has: ALLOW_FILL_IN,ALLOW_MODIFY_ANNOTATIONS,ALLOW_MODIFY_CONTENTS
        doTestRemovePermissions(WORKER1, sampleOwner123, SAMPLE_OWNER123_PASSWORD, null,
                Arrays.asList("ALLOW_FILL_IN"), Arrays.asList("ALLOW_MODIFY_ANNOTATIONS", "ALLOW_MODIFY_CONTENTS"));
        doTestRemovePermissions(WORKER1, sampleOwner123, SAMPLE_OWNER123_PASSWORD, null,
                Arrays.asList("ALLOW_MODIFY_ANNOTATIONS"), Arrays.asList("ALLOW_FILL_IN", "ALLOW_MODIFY_CONTENTS"));
        doTestRemovePermissions(WORKER1, sampleOwner123, SAMPLE_OWNER123_PASSWORD, null,
                Arrays.asList("ALLOW_MODIFY_CONTENTS"), Arrays.asList("ALLOW_FILL_IN", "ALLOW_MODIFY_ANNOTATIONS"));

        doTestRemovePermissions(WORKER1, sampleOwner123, SAMPLE_OWNER123_PASSWORD, null,
                Arrays.asList("ALLOW_MODIFY_ANNOTATIONS", "ALLOW_MODIFY_CONTENTS"), Arrays.asList("ALLOW_FILL_IN"));
        doTestRemovePermissions(WORKER1, sampleOwner123, SAMPLE_OWNER123_PASSWORD, null,
                Arrays.asList("ALLOW_FILL_IN", "ALLOW_MODIFY_CONTENTS"), Arrays.asList("ALLOW_MODIFY_ANNOTATIONS"));
        doTestRemovePermissions(WORKER1, sampleOwner123, SAMPLE_OWNER123_PASSWORD, null,
                Arrays.asList("ALLOW_FILL_IN", "ALLOW_MODIFY_ANNOTATIONS"), Arrays.asList("ALLOW_MODIFY_CONTENTS"));
    }

    public void test04RemovePermissionsWithoutOwner() throws Exception {

        // Removing any permissions should protected the document even 
        // if it did not contain the permission before but was unprotected
        // (unprotected means all permissions)
        Collection<String> anyPermissions = Arrays.asList("ALLOW_FILL_IN");

        // sample has Permissions(0)
        byte[] pdfbytes = doTestRemovePermissions(WORKER1, sample, null, null, anyPermissions,
                new LinkedList<String>());
        assertUserNotOwnerPassword(pdfbytes, null);

        // sampleOpen123 has Permissions(-1028)[ALLOW_FILL_IN, ALLOW_MODIFY_ANNOTATIONS, ALLOW_DEGRADED_PRINTING, ALLOW_SCREENREADERS, ALLOW_COPY, ALLOW_PRINTING, ALLOW_MODIFY_CONTENTS]
        Collection<String> anyPermissionsRemoved = Arrays.asList("ALLOW_MODIFY_ANNOTATIONS",
                "ALLOW_DEGRADED_PRINTING", "ALLOW_SCREENREADERS", "ALLOW_COPY", "ALLOW_PRINTING",
                "ALLOW_MODIFY_CONTENTS");
        pdfbytes = doTestRemovePermissions(WORKER1, sampleOpen123, null, SAMPLE_OPEN123_PASSWORD, anyPermissions,
                anyPermissionsRemoved);
        assertUserNotOwnerPassword(pdfbytes, SAMPLE_OPEN123_PASSWORD);
    }

    /**
     * Tests illegal configuration: specifying mutually exclusive properties.
     */
    public void test04SetAndRemovePermissions() throws Exception {
        workerSession.setWorkerProperty(WORKER1, "SET_PERMISSIONS", "ALLOW_COPY");
        workerSession.setWorkerProperty(WORKER1, "REMOVE_PERMISSIONS", "ALLOW_FILL_IN");
        workerSession.reloadConfiguration(WORKER1);
        try {
            signProtectedPDF(sampleOwner123, SAMPLE_OWNER123_PASSWORD);
            fail("Should have thrown exception");
        } catch (SignServerException ok) {
            LOG.debug("OK: " + ok.getMessage());
        }
    }

    /**
     * Tests that rejecting permissions still works even do we set permissions 
     * explicitly.
     */
    public void test05SetAndRejectPermissions() throws Exception {
        // Setting a permission we then reject. Not so clever :)
        workerSession.setWorkerProperty(WORKER1, "SET_PERMISSIONS", "ALLOW_MODIFY_CONTENTS,ALLOW_COPY");
        workerSession.setWorkerProperty(WORKER1, "REJECT_PERMISSIONS", "ALLOW_COPY");
        workerSession.reloadConfiguration(WORKER1);
        try {
            signProtectedPDF(sampleOwner123, SAMPLE_OWNER123_PASSWORD);
            fail("Should have thrown exception");
        } catch (IllegalRequestException ok) {
            LOG.debug("OK: " + ok.getMessage());
        }
    }

    /**
     * Tests that even do we remove some permission we will still check for 
     * permissions to reject. But if we remove all rejected the document is ok.
     */
    public void test06RemoveAndRejectPermissions() throws Exception {
        // Remove a permissions but still the document contains a permission we reject
        workerSession.setWorkerProperty(WORKER1, "REMOVE_PERMISSIONS", "ALLOW_MODIFY_CONTENTS");
        workerSession.setWorkerProperty(WORKER1, "REJECT_PERMISSIONS", "ALLOW_FILL_IN");
        workerSession.reloadConfiguration(WORKER1);
        try {
            signProtectedPDF(sampleOwner123, SAMPLE_OWNER123_PASSWORD);
            fail("Should have thrown exception");
        } catch (IllegalRequestException ok) {
            LOG.debug("OK: " + ok.getMessage());
        }

        // Remove the permission we reject
        workerSession.setWorkerProperty(WORKER1, "REMOVE_PERMISSIONS", "ALLOW_FILL_IN");
        workerSession.reloadConfiguration(WORKER1);
        signProtectedPDF(sampleOwner123, SAMPLE_OWNER123_PASSWORD);
    }

    /**
     * Tests that it is possible also to change the permissions on a document 
     * not previously protected by any password.
     */
    public void test07ChangePermissionOfUnprotectedDocument() throws Exception {
        doTestSetPermissions(WORKER1, sampleOk, null, null,
                Arrays.asList("ALLOW_FILL_IN", "ALLOW_DEGRADED_PRINTING"));
    }

    /**
     * Test helper method for asserting that a certain owner password is really 
     * set.
     */
    public void test08assertOwnerPassword() throws Exception {
        try {
            assertOwnerPassword(readFile(sampleOpen123Owner123), "open123");
            fail("Should have thrown exception as it was not openned with owner password");
        } catch (IOException ok) { // NOPMD
            // OK
        }
        try {
            assertOwnerPassword(readFile(sampleOk), "open123a");
            fail("Should have thrown exception as the password was not needed");
        } catch (IOException ok) { // NOPMD
            // OK
        }
        assertOwnerPassword(readFile(sampleOpen123Owner123), SAMPLE_OWNER123_PASSWORD);
    }

    /**
     * Tests the worker property SET_OWNERPASSWORD with documents containing 
     * different password types.
     */
    public void test09SetOwnerPassword() throws Exception {
        // Set owner password on a document that does not have any password
        String ownerPassword1 = "newownerpassword%%_1";
        workerSession.setWorkerProperty(WORKER1, "SET_OWNERPASSWORD", ownerPassword1);
        workerSession.reloadConfiguration(WORKER1);
        byte[] pdf1 = signProtectedPDF(sampleOk, null);
        assertOwnerPassword(pdf1, ownerPassword1);

        // Set owner password on a document that already has a user password
        // The user password should still be the same
        String ownerPassword2 = "newownerpassword%%_2";
        workerSession.setWorkerProperty(WORKER1, "SET_OWNERPASSWORD", ownerPassword2);
        workerSession.reloadConfiguration(WORKER1);
        byte[] pdf2 = signProtectedPDF(sampleOpen123, "open123");
        assertOwnerPassword(pdf2, ownerPassword2);
        assertUserPassword(pdf2, "open123");

        // Set owner password on a document that already has a user and owner password
        // The user password should still be the same
        String ownerPassword3 = "newownerpassword%%_3";
        workerSession.setWorkerProperty(WORKER1, "SET_OWNERPASSWORD", ownerPassword3);
        workerSession.reloadConfiguration(WORKER1);
        byte[] pdf3 = signProtectedPDF(sampleOpen123Owner123, "owner123");
        assertOwnerPassword(pdf3, ownerPassword3);
        assertUserPassword(pdf3, "open123");

        // Set owner password on a document that already has an owner password
        // The user password should still not be needed
        String ownerPassword4 = "newownerpassword%%_4";
        workerSession.setWorkerProperty(WORKER1, "SET_OWNERPASSWORD", ownerPassword4);
        workerSession.reloadConfiguration(WORKER1);
        byte[] pdf4 = signProtectedPDF(sampleOwner123, "owner123");
        assertOwnerPassword(pdf4, ownerPassword4);
        assertUserPassword(pdf4, "");
    }

    /**
     * Tests that it is possible to sign a certified document which allows 
     * signing and not one the does not.
     */
    public void test10SignCertifiedDocument() throws Exception {
        signPDF(sampleCertifiedSigningAllowed);
        try {
            signPDF(sampleCertifiedNoChangesAllowed);
            fail("Should not be possible to sign a certified document with NO_CHANGES_ALLOWED");
        } catch (IllegalRequestException ok) {
            LOG.debug("ok: " + ok.getMessage());
        }
        try {
            signPDF(sampleCertifiedFormFillingAllowed);
            fail("Should not be possible to sign a certified document with FORM_FILLING");
        } catch (IllegalRequestException ok) {
            LOG.debug("ok: " + ok.getMessage());
        }
    }

    /**
     * Tests that it is possible to certify a document that already is signed.
     */
    public void test11CertifySignedDocument() throws Exception {
        workerSession.setWorkerProperty(WORKER1, "CERTIFICATION_LEVEL", "FORM_FILLING");
        workerSession.reloadConfiguration(WORKER1);
        signPDF(sampleSigned);

        workerSession.setWorkerProperty(WORKER1, "CERTIFICATION_LEVEL", "FORM_FILLING_AND_ANNOTATIONS");
        workerSession.reloadConfiguration(WORKER1);
        signPDF(sampleSigned);

        workerSession.setWorkerProperty(WORKER1, "CERTIFICATION_LEVEL", "NO_CHANGES_ALLOWED");
        workerSession.reloadConfiguration(WORKER1);
        signPDF(sampleSigned);
    }

    /**
     * Tests that it is possible to sign an already signed document.
     */
    public void test12SignSignedDocument() throws Exception {
        signPDF(sampleSigned);
    }

    /**
     * Tests that it is not possible to certify an already certified document.
     */
    public void test13CertifyCertifiedDocument() throws Exception {
        workerSession.setWorkerProperty(WORKER1, "CERTIFICATION_LEVEL", "FORM_FILLING");
        workerSession.reloadConfiguration(WORKER1);
        try {
            signPDF(sampleCertifiedNoChangesAllowed);
            fail("Should not be possible to certify a certified document");
        } catch (IllegalRequestException ok) {
            LOG.debug("ok: " + ok.getMessage());
        }
        try {
            signPDF(sampleCertifiedFormFillingAllowed);
            fail("Should not be possible to sign a certified document");
        } catch (IllegalRequestException ok) {
            LOG.debug("ok: " + ok.getMessage());
        }
        try {
            signPDF(sampleCertifiedSigningAllowed);
            fail("Should not be possible to sign a certified document");
        } catch (IllegalRequestException ok) {
            LOG.debug("ok: " + ok.getMessage());
        }
    }

    /**
     * Tests that our assumption that an increase of n bytes in a certificate 
     * does not lead to more than an increase of n bytes in the PKCS#7 structure.
     *
     * This should never fail unless we upgrade BouncyCastle and the behavior 
     * changes.
     */
    public void test14EstimatedP7Size_increaseCertSize() throws Exception {
        final int somethingLargeEnough = 31000;
        KeyPair issuerKeyPair = CryptoUtils.generateRSA(1024);
        KeyPair signerKeyPair = CryptoUtils.generateRSA(1024);
        PrivateKey signerPrivKey = signerKeyPair.getPrivate();
        byte[] extensionBytes = new byte[0];
        int referenceIssuerCertSize;
        int referenceSize;
        int actualP7Size;

        // Create initial certificates
        Certificate issuerCert = converter.getCertificate(new CertBuilder()
                .setIssuerPrivateKey(issuerKeyPair.getPrivate()).setSubjectPublicKey(issuerKeyPair.getPublic())
                .setSubject("CN=Issuer1").setIssuer("CN=Issuer1")
                .addExtension(
                        new CertExt(new ASN1ObjectIdentifier("1.2.3.4"), false, new DERBitString(extensionBytes)))
                .build());
        referenceIssuerCertSize = issuerCert.getEncoded().length;
        Certificate signerCert = converter.getCertificate(new CertBuilder()
                .setIssuerPrivateKey(issuerKeyPair.getPrivate()).setSubjectPublicKey(signerKeyPair.getPublic())
                .setSubject("CN=Signer").setIssuer("CN=Issuer1").build());

        // We will only variate the issuer certificate size
        // so the other parameters are not important for this test
        CRL[] crlList = new CRL[0];
        MockedTSAClient tsc = new MockedTSAClient(1234);
        byte[] ocsp = "OOOOOOOO".getBytes();

        // Test 1: First test is the reference test
        Certificate[] certChain = new Certificate[] { signerCert, issuerCert };
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        referenceSize = actualP7Size;
        LOG.debug("referenceSize: " + actualP7Size);
        LOG.debug("referenceIssuerCertSize: " + referenceIssuerCertSize);

        // Test 2: Increase the size of the certificate with 1 byte and test
        // that the final P7 does not increases with more than 1 byte
        extensionBytes = new byte[1];
        issuerCert = converter.getCertificate(new CertBuilder().setIssuerPrivateKey(issuerKeyPair.getPrivate())
                .setSubjectPublicKey(issuerKeyPair.getPublic()).setSubject("CN=Issuer1").setIssuer("CN=Issuer1")
                .addExtension(
                        new CertExt(new ASN1ObjectIdentifier("1.2.3.4"), false, new DERBitString(extensionBytes)))
                .build());
        certChain = new Certificate[] { signerCert, issuerCert };
        if (issuerCert.getEncoded().length != referenceIssuerCertSize + 1) {
            throw new Exception("The test should have increased the certificate size by 1 byte");
        }
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        assertEquals("new size 1 byte larger", referenceSize + 1, actualP7Size);

        // Test 2: Increase the size of the certificate with 37 bytes and test
        // that the final P7 does not increases with more than 37 bytes
        extensionBytes = new byte[37];
        issuerCert = converter.getCertificate(new CertBuilder().setIssuerPrivateKey(issuerKeyPair.getPrivate())
                .setSubjectPublicKey(issuerKeyPair.getPublic()).setSubject("CN=Issuer1").setIssuer("CN=Issuer1")
                .addExtension(
                        new CertExt(new ASN1ObjectIdentifier("1.2.3.4"), false, new DERBitString(extensionBytes)))
                .build());
        certChain = new Certificate[] { signerCert, issuerCert };
        if (issuerCert.getEncoded().length != referenceIssuerCertSize + 37) {
            throw new Exception("The test should have increased the certificate size by 37 bytes but was: "
                    + issuerCert.getEncoded().length);
        }
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        assertEquals("new size 37 bytes larger", referenceSize + 37, actualP7Size);

        // Test 2: Increase the size of the certificate with at least 10000 bytes and test
        // that the final P7 does not increases more than the certificate
        // (it turned out that increasing the certificate with 10000 bytes actually made it even larger, 
        //  however that is not important in this case)
        extensionBytes = new byte[10000];
        issuerCert = converter.getCertificate(new CertBuilder().setIssuerPrivateKey(issuerKeyPair.getPrivate())
                .setSubjectPublicKey(issuerKeyPair.getPublic()).setSubject("CN=Issuer1").setIssuer("CN=Issuer1")
                .addExtension(
                        new CertExt(new ASN1ObjectIdentifier("1.2.3.4"), false, new DERBitString(extensionBytes)))
                .build());
        certChain = new Certificate[] { signerCert, issuerCert };
        int certIncrease = issuerCert.getEncoded().length - referenceIssuerCertSize;
        LOG.debug("increased certificate size with: " + certIncrease);
        if (certIncrease < 10000) {
            throw new Exception("The test should have increased the certificate with at least 10000 bytes but was: "
                    + issuerCert.getEncoded().length);
        }
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        assertEquals("new larger size", referenceSize + certIncrease, actualP7Size);
        referenceSize = actualP7Size;

        // Test 3: Increase the size of the certificate with at least 30123 bytes and test
        // that the final P7 does not increases more than the certificate
        extensionBytes = new byte[30123];
        issuerCert = converter.getCertificate(new CertBuilder().setIssuerPrivateKey(issuerKeyPair.getPrivate())
                .setSubjectPublicKey(issuerKeyPair.getPublic()).setSubject("CN=Issuer1").setIssuer("CN=Issuer1")
                .addExtension(
                        new CertExt(new ASN1ObjectIdentifier("1.2.3.4"), false, new DERBitString(extensionBytes)))
                .build());
        certChain = new Certificate[] { signerCert, issuerCert };
        certIncrease = issuerCert.getEncoded().length - referenceIssuerCertSize;
        LOG.debug("increased certificate size with: " + certIncrease);
        if (certIncrease < 30123) {
            throw new Exception("The test should have increased the certificate with at least 30123 bytes but was: "
                    + issuerCert.getEncoded().length);
        }
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        // It turns out that the P7 might use less size than the increase in the certificate
        assertTrue("new larger size", referenceSize + certIncrease >= actualP7Size);
        //referenceSize = actualP7Size;        
    }

    /**
     * Tests that our assumption that an increase of n bytes in a time-stamp response 
     * does not lead to more than an increase of n bytes in the PKCS#7 structure.
     *
     * This should never fail unless we upgrade BouncyCastle and the behavior 
     * changes.
     */
    public void test14EstimatedP7Size_increaseTSRSize() throws Exception {
        final int somethingLargeEnough = 31000;
        KeyPair signerKeyPair = CryptoUtils.generateRSA(1024);
        PrivateKey signerPrivKey = signerKeyPair.getPrivate();
        int referenceTSRSize;
        int referenceSize;
        int actualP7Size;

        // We will only variate the Time-stamp response size
        // so the other parameters are not important for this test
        Certificate[] certChain = new Certificate[] { converter.getCertificate(new CertBuilder().build()) };
        CRL[] crlList = new CRL[0];
        byte[] ocsp = "OOOOOOOO".getBytes();

        // Test 1: First test is the reference test
        MockedTSAClient tsc = new MockedTSAClient(0);

        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        referenceSize = actualP7Size;
        referenceTSRSize = tsc.getTimeStampToken().length;
        LOG.debug("referenceSize: " + actualP7Size);
        LOG.debug("referenceTSRSize: " + referenceTSRSize);

        // Test 2: Increase the size of the TSR with 1 byte and test
        // that the final P7 does not increases with more than 1 byte
        tsc = new MockedTSAClient(1);
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        assertEquals("new size 1 byte larger", referenceSize + 1, actualP7Size);

        // Test 2: Increase the size of the certificate with 37 bytes and test
        // that the final P7 does not increases with more than 37 bytes
        tsc = new MockedTSAClient(37);
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        assertEquals("new size 37 bytes larger", referenceSize + 37, actualP7Size);

        // Test 2: Increase the size of the certificate with at least 10000 bytes and test
        // that the final P7 does not increases more than the certificate
        // (it turned out that increasing the TSR with 10000 bytes actually made it even larger, 
        //  however that is not important in this case)
        tsc = new MockedTSAClient(10000);
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        int tsrIncrease = tsc.getTokenSizeEstimate() - referenceTSRSize;
        LOG.debug("increased certificate size with: " + tsrIncrease);
        // It turns out that the P7 might use less size than the increase in the TSR size
        assertTrue("new larger size", referenceSize + tsrIncrease >= actualP7Size);
        referenceSize = actualP7Size;

        // Test 3: Increase the size of the certificate with at least 30123 bytes and test
        // that the final P7 does not increases more than the certificate
        tsc = new MockedTSAClient(30123);
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        tsrIncrease = tsc.getTokenSizeEstimate() - referenceTSRSize;
        // It turns out that the P7 might use less size than the increase in the TSR size
        assertTrue("new larger size", referenceSize + tsrIncrease >= actualP7Size);
        //referenceSize = actualP7Size;        
    }

    /**
     * Tests that our assumption that an increase of n bytes in a certificate 
     * does not lead to more than an increase of n+X bytes in the PKCS#7 structure 
     * where X seems to be 1 extra byte that could be needed.
     *
     * This should never fail unless we upgrade BouncyCastle and the behavior 
     * changes.
     */
    public void test14EstimatedP7Size_increaseCRLSize() throws Exception {
        final int extraSpace = 1;

        final int somethingLargeEnough = 31000;
        KeyPair signerKeyPair = CryptoUtils.generateRSA(1024);
        PrivateKey signerPrivKey = signerKeyPair.getPrivate();
        byte[] extensionBytes;
        final int referenceCRLSize;
        int referenceSize;
        int actualP7Size;

        // Create initial certificates
        Certificate[] certChain = new Certificate[] { converter.getCertificate(new CertBuilder().build()) };

        // We will only variate the issuer certificate size
        // so the other parameters are not important for this test
        MockedTSAClient tsc = new MockedTSAClient(1234);
        byte[] ocsp = "OOOOOOOO".getBytes();

        // Test 1: First test is the reference test
        CRL[] crlList = new CRL[] { createCRL(signerPrivKey, new byte[0]) };
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        referenceSize = actualP7Size;
        referenceCRLSize = ((X509CRL) crlList[0]).getEncoded().length;
        LOG.debug("referenceSize: " + actualP7Size);
        LOG.debug("referenceCRLSize: " + referenceCRLSize);

        // Test 2: Increase the size of the CRL with 1 byte and test
        // that the final P7 does not increases with more than 1 byte
        extensionBytes = new byte[1];
        crlList = new CRL[] { createCRL(signerPrivKey, extensionBytes) };
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        assertEquals("new size 1 byte larger", referenceSize + 1, actualP7Size);

        // Test 2: Increase the size of the certificate with 37 bytes and test
        // that the final P7 does not increases with more than 37 bytes
        extensionBytes = new byte[37];
        crlList = new CRL[] { createCRL(signerPrivKey, extensionBytes) };
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        assertTrue("new size 37 bytes larger", actualP7Size <= referenceSize + 37 + extraSpace);

        // Test 2: Increase the size of the certificate with at least 10000 bytes and test
        // that the final P7 does not increases more than the certificate
        // (it turned out that increasing the certificate with 10000 bytes actually made it even larger, 
        //  however that is not important in this case)
        extensionBytes = new byte[10000];
        crlList = new CRL[] { createCRL(signerPrivKey, extensionBytes) };
        int certIncrease = ((X509CRL) crlList[0]).getEncoded().length - referenceCRLSize;
        LOG.debug("increased CRL size with: " + certIncrease);
        if (certIncrease < 10000) {
            throw new Exception("The test should have increased the certificate with at least 10000 bytes but was: "
                    + certIncrease);
        }
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        assertEquals("new larger size", referenceSize + certIncrease, actualP7Size);
        referenceSize = actualP7Size;

        // Test 3: Increase the size of the certificate with at least 30123 bytes and test
        // that the final P7 does not increases more than the certificate
        extensionBytes = new byte[30123];
        crlList = new CRL[] { createCRL(signerPrivKey, extensionBytes) };
        certIncrease = ((X509CRL) crlList[0]).getEncoded().length - referenceCRLSize;
        LOG.debug("increased CRL size with: " + certIncrease);
        if (certIncrease < 30123) {
            throw new Exception("The test should have increased the certificate with at least 30123 bytes but was: "
                    + certIncrease);
        }
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        // It turns out that the P7 might use less size than the increase in the certificate
        assertTrue("new larger size", referenceSize + certIncrease >= actualP7Size);
        //referenceSize = actualP7Size;        
    }

    private X509CRL createCRL(PrivateKey caCrlPrivKey, byte[] data) throws Exception {
        X509v2CRLBuilder crlGen = new X509v2CRLBuilder(new X500Name("CN=CRL Issuer"), new Date());
        crlGen.addCRLEntry(BigInteger.ONE, new Date(), CRLReason.privilegeWithdrawn);
        crlGen.addExtension(new ASN1ObjectIdentifier("1.2.3.4"), false, new DERBitString(data));
        X509CRLHolder crl = crlGen.build(new JcaContentSignerBuilder("SHA1withRSA").build(caCrlPrivKey));
        return new JcaX509CRLConverter().getCRL(crl);
    }

    private int sumCertSizes(Certificate[] certs) throws CertificateEncodingException {
        return sumCertSizes(certs, 0);
    }

    private int sumCertSizes(Certificate[] certs, int offset) throws CertificateEncodingException {
        int result = 0;
        for (int i = offset; i < certs.length; i++) {
            result += certs[i].getEncoded().length;
        }
        return result;
    }

    private int sumCRLSizes(CRL[] crls) throws CRLException {
        return sumCRLSizes(crls, 0);
    }

    private int sumCRLSizes(CRL[] crls, int offset) throws CRLException {
        int result = 0;
        for (int i = offset; i < crls.length; i++) {
            result += ((X509CRL) crls[i]).getEncoded().length;
        }
        return result;
    }

    /**
     * Tests that our assumption that adding an additional certificate does not
     * increase the size with more than the size of the certificate.
     *
     * This should never fail unless we upgrade BouncyCastle and the behavior 
     * changes.
     */
    public void test14EstimatedP7Size_increaseNumCerts() throws Exception {
        final int somethingLargeEnough = 31000;
        KeyPair issuerKeyPair = CryptoUtils.generateRSA(1024);
        KeyPair signerKeyPair = CryptoUtils.generateRSA(1024);
        PrivateKey signerPrivKey = signerKeyPair.getPrivate();
        byte[] extensionBytes = new byte[0];
        int referenceIssuerCertSize;
        int referenceSize;
        int actualP7Size;

        // Create initial certificates
        Certificate signerCert = converter.getCertificate(new CertBuilder()
                .setIssuerPrivateKey(issuerKeyPair.getPrivate()).setSubjectPublicKey(signerKeyPair.getPublic())
                .setSubject("CN=Signer").setIssuer("CN=Issuer1").build());
        Certificate[] allCerts = new Certificate[50];
        allCerts[0] = signerCert;
        for (int i = 1; i < 50; i++) {
            allCerts[i] = converter.getCertificate(new CertBuilder().setIssuerPrivateKey(issuerKeyPair.getPrivate())
                    .setSubjectPublicKey(issuerKeyPair.getPublic()).setSubject("CN=Issuer" + i)
                    .setIssuer("CN=Issuer" + i).addExtension(new CertExt(new ASN1ObjectIdentifier("1.2.3.4"), false,
                            new DERBitString(extensionBytes)))
                    .build());
        }

        // We will only variate the number of issuer certificates
        // so the other parameters are not important for this test
        CRL[] crlList = new CRL[0];
        MockedTSAClient tsc = new MockedTSAClient(1234);
        byte[] ocsp = "OOOOOOOO".getBytes();

        // Test 1: First test is the reference test
        Certificate[] certChain = new Certificate[2];
        System.arraycopy(allCerts, 0, certChain, 0, 2); // 2 certificates
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        LOG.debug("actualP7Size=" + actualP7Size + ", sumCertSizes=" + sumCertSizes(certChain));
        referenceSize = actualP7Size;
        referenceIssuerCertSize = sumCertSizes(certChain);
        LOG.debug("referenceSize: " + actualP7Size);
        LOG.debug("referenceIssuerCertSize: " + referenceIssuerCertSize);

        // Test 2: Increase the size of the certificate chain with 1 and test
        // that the final P7 does not increases with more than the size of the certificate
        certChain = new Certificate[3];
        System.arraycopy(allCerts, 0, certChain, 0, 3); // 3 certificates
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        int diff = actualP7Size - referenceSize - sumCertSizes(certChain, 2);
        LOG.debug("actualP7Size=" + actualP7Size + ", sumCertSizes=" + sumCertSizes(certChain));
        assertEquals("no extra added for each certificate", 0, diff);

        // Test 3: Increase the size of the certificate chain with 2 and test
        // that the final P7 does not increases with more than the size of the certificates
        certChain = new Certificate[4];
        System.arraycopy(allCerts, 0, certChain, 0, 4); // 4 certificates
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        diff = actualP7Size - referenceSize - sumCertSizes(certChain, 2);
        LOG.debug("actualP7Size=" + actualP7Size + ", sumCertSizes=" + sumCertSizes(certChain));
        assertEquals("no extra added for each certificate", 0, diff);

        // Test 4: Increase the size of the certificate chain with 49 and test
        // that the final P7 does not increases with more than the size of the certificates
        certChain = new Certificate[50];
        System.arraycopy(allCerts, 0, certChain, 0, 50); // 50 certificates
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        diff = actualP7Size - referenceSize - sumCertSizes(certChain, 2);
        LOG.debug("actualP7Size=" + actualP7Size + ", sumCertSizes=" + sumCertSizes(certChain));
        assertEquals("no extra added for each certificate", 0, diff);
    }

    /**
     * Tests that our assumption that adding an additional CRL does not
     * increase the size with more than the size of the CRL.
     *
     * This should never fail unless we upgrade BouncyCastle and the behavior 
     * changes.
     */
    public void test14EstimatedP7Size_increaseNumCRLs() throws Exception {
        final int somethingLargeEnough = 31000;
        KeyPair signerKeyPair = CryptoUtils.generateRSA(1024);
        PrivateKey signerPrivKey = signerKeyPair.getPrivate();
        int referenceCRLSize;
        int referenceSize;
        int actualP7Size;
        CRL[] crlList;

        // Create initial certificates
        Certificate[] certChain = new Certificate[] { converter.getCertificate(new CertBuilder().build()) };
        CRL[] allCRLs = new CRL[10];

        for (int i = 0; i < 10; i++) {
            allCRLs[i] = createCRL(signerPrivKey, "CCCCCCCCCCCCC".getBytes());
        }

        // We will only variate the number of CRLs
        // so the other parameters are not important for this test
        MockedTSAClient tsc = new MockedTSAClient(1234);
        byte[] ocsp = "OOOOOOOO".getBytes();

        // Test 1: First test is the reference test
        crlList = new CRL[1];
        System.arraycopy(allCRLs, 0, crlList, 0, 1); // 1 CRL
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        LOG.debug("actualP7Size=" + actualP7Size + ", sumCertSizes=" + sumCRLSizes(crlList));
        referenceSize = actualP7Size;
        referenceCRLSize = sumCRLSizes(crlList);
        LOG.debug("referenceSize: " + actualP7Size);
        LOG.debug("referenceIssuerCertSize: " + referenceCRLSize);

        // Test 2: Increase the size of the certificate chain with 1 and test
        // that the final P7 does not increases with more than the size of the certificate
        crlList = new CRL[2];
        System.arraycopy(allCRLs, 0, crlList, 0, 2); // 2 CRLs
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        int diff = actualP7Size - referenceSize - sumCRLSizes(crlList, 1);
        LOG.debug("actualP7Size=" + actualP7Size + ", sumCertSizes=" + sumCRLSizes(crlList));
        assertEquals("no extra added for each certificate", 0, diff);

        // Test 3: Increase the size of the certificate chain with 2 and test
        // that the final P7 does not increases with more than the size of the certificates
        crlList = new CRL[3];
        System.arraycopy(allCRLs, 0, crlList, 0, 3); // 3 CRLs
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        diff = actualP7Size - referenceSize - sumCRLSizes(crlList, 1);
        LOG.debug("actualP7Size=" + actualP7Size + ", sumCertSizes=" + sumCRLSizes(crlList));
        assertEquals("no extra added for each certificate", 0, diff);

        // Test 4: Increase the size of the certificate chain with 49 and test
        // that the final P7 does not increases with more than the size of the certificates
        crlList = new CRL[10];
        System.arraycopy(allCRLs, 0, crlList, 0, 10); // 10 CRLs
        actualP7Size = getActualP7Size(signerPrivKey, somethingLargeEnough, certChain, crlList, ocsp, tsc);
        diff = actualP7Size - referenceSize - sumCRLSizes(crlList, 1);
        LOG.debug("actualP7Size=" + actualP7Size + ", sumCertSizes=" + sumCRLSizes(crlList));
        assertEquals("no extra added for each certificate", 0, diff);
    }

    /**
     * Test that the estimated value is within correct bounds when using different input values.
     * 
     * Tests a few different combinations. 
     * 
     * TODO: Randomized stress testing would be good for this feature.
     * 
     * The most important thing is that we don't estimate a too low value. 
     * 
     * Second thing is to not make a too large estimate. What quality of the 
     * estimate we require is defined by the maxDiff constant. We can't calculate 
     * the size of the TS response (as it is performed after we construct the 
     * signature structure) so in this test only values under 7168 (the 
     * default estimate) are considered.
     * 
     */
    public void test14calculateEstimatedSignatureSize() throws Exception {

        final int estimatedIntialTSResponseSize = 7168; // The value we assume for the TS response siz
        final int maxDiff = estimatedIntialTSResponseSize + 10000; // How far from the actual value we allow the algorithm to be

        KeyPair issuerKeyPair = CryptoUtils.generateRSA(1024);
        KeyPair signerKeyPair = CryptoUtils.generateRSA(1024);
        byte[] extensionBytes;

        Certificate signerCert = converter.getCertificate(new CertBuilder()
                .setIssuerPrivateKey(issuerKeyPair.getPrivate()).setSubjectPublicKey(signerKeyPair.getPublic())
                .setSubject("CN=Signer").setIssuer("CN=Issuer1").build());
        Certificate issuerCert;
        Certificate[] certChain;
        CRL[] crlList;
        MockedTSAClient tsc;
        byte[] ocsp;

        // Subject, 0 extra bytes TS, 0 bytes OCSP, 0 CRLs (0 extra bytes)
        certChain = new Certificate[] { signerCert };
        tsc = new MockedTSAClient(0);
        ocsp = null;
        crlList = new CRL[0];
        assertEstimateCloseEnough(signerKeyPair.getPrivate(), certChain, tsc, ocsp, crlList, maxDiff);

        // Subject, Issuer(4123 extra bytes), 0 extra bytes TS, 0 bytes OCSP, 0 CRLs (0 extra bytes)
        extensionBytes = new byte[4123];
        issuerCert = converter.getCertificate(new CertBuilder().setIssuerPrivateKey(issuerKeyPair.getPrivate())
                .setSubjectPublicKey(issuerKeyPair.getPublic()).setSubject("CN=Issuer1").setIssuer("CN=Issuer1")
                .addExtension(
                        new CertExt(new ASN1ObjectIdentifier("1.2.3.4"), false, new DERBitString(extensionBytes)))
                .build());
        certChain = new Certificate[] { signerCert, issuerCert };
        crlList = new CRL[0];
        tsc = new MockedTSAClient(0);
        ocsp = null;
        assertEstimateCloseEnough(signerKeyPair.getPrivate(), certChain, tsc, ocsp, crlList, maxDiff);

        // Subject, Issuer(17173 extra bytes), Issuer2 (123 extra bytes), 0 extra bytes TS, 0 bytes OCSP, 0 CRLs (0 extra bytes)
        extensionBytes = new byte[17173];
        issuerCert = converter.getCertificate(new CertBuilder().setIssuerPrivateKey(issuerKeyPair.getPrivate())
                .setSubjectPublicKey(issuerKeyPair.getPublic()).setSubject("CN=Issuer1").setIssuer("CN=Issuer1")
                .addExtension(
                        new CertExt(new ASN1ObjectIdentifier("1.2.3.4"), false, new DERBitString(extensionBytes)))
                .build());
        X509Certificate issuerCert2 = converter.getCertificate(new CertBuilder()
                .setIssuerPrivateKey(issuerKeyPair.getPrivate()).setSubjectPublicKey(issuerKeyPair.getPublic())
                .setSubject("CN=Issuer1").setIssuer("CN=Issuer1")
                .addExtension(
                        new CertExt(new ASN1ObjectIdentifier("1.2.3.4"), false, new DERBitString(new byte[123])))
                .build());
        certChain = new Certificate[] { signerCert, issuerCert, issuerCert2 };
        crlList = new CRL[0];
        tsc = new MockedTSAClient(0);
        ocsp = null;
        assertEstimateCloseEnough(signerKeyPair.getPrivate(), certChain, tsc, ocsp, crlList, maxDiff);

        // Subject, Issuer(4123 extra bytes), 3178 extra bytes TS, 0 bytes OCSP, 0 CRLs (0 extra bytes)
        extensionBytes = new byte[4123];
        issuerCert = converter.getCertificate(new CertBuilder().setIssuerPrivateKey(issuerKeyPair.getPrivate())
                .setSubjectPublicKey(issuerKeyPair.getPublic()).setSubject("CN=Issuer1").setIssuer("CN=Issuer1")
                .addExtension(
                        new CertExt(new ASN1ObjectIdentifier("1.2.3.4"), false, new DERBitString(extensionBytes)))
                .build());
        certChain = new Certificate[] { signerCert, issuerCert };
        crlList = new CRL[0];
        tsc = new MockedTSAClient(3178);
        ocsp = null;
        assertEstimateCloseEnough(signerKeyPair.getPrivate(), certChain, tsc, ocsp, crlList, maxDiff);

        // Subject, Issuer(17173 extra bytes), Issuer2 (123 extra bytes), 3178 extra bytes TS, 0 bytes OCSP, 0 CRLs (0 extra bytes)
        extensionBytes = new byte[17173];
        issuerCert = converter.getCertificate(new CertBuilder().setIssuerPrivateKey(issuerKeyPair.getPrivate())
                .setSubjectPublicKey(issuerKeyPair.getPublic()).setSubject("CN=Issuer1").setIssuer("CN=Issuer1")
                .addExtension(
                        new CertExt(new ASN1ObjectIdentifier("1.2.3.4"), false, new DERBitString(extensionBytes)))
                .build());
        issuerCert2 = converter.getCertificate(new CertBuilder().setIssuerPrivateKey(issuerKeyPair.getPrivate())
                .setSubjectPublicKey(issuerKeyPair.getPublic()).setSubject("CN=Issuer1").setIssuer("CN=Issuer1")
                .addExtension(
                        new CertExt(new ASN1ObjectIdentifier("1.2.3.4"), false, new DERBitString(new byte[123])))
                .build());
        certChain = new Certificate[] { signerCert, issuerCert, issuerCert2 };
        crlList = new CRL[0];
        tsc = new MockedTSAClient(3178);
        ocsp = null; //"OOOOOOOO".getBytes();
        assertEstimateCloseEnough(signerKeyPair.getPrivate(), certChain, tsc, ocsp, crlList, maxDiff);

        // Subject, Issuer(17173 extra bytes), Issuer2 (123 extra bytes), 3178 extra bytes TS, 1 bytes OCSP, 0 CRLs (0 extra bytes)
        //extensionBytes =
        //issuerCert =
        //issuerCert2 =
        //certChain =
        crlList = new CRL[0];
        tsc = new MockedTSAClient(3178);
        ocsp = new byte[1];
        assertEstimateCloseEnough(signerKeyPair.getPrivate(), certChain, tsc, ocsp, crlList, maxDiff);

        // Subject, Issuer(17173 extra bytes), Issuer2 (123 extra bytes), 3178 extra bytes TS, 1304 bytes OCSP, 0 CRLs (0 extra bytes)
        //extensionBytes =
        //issuerCert =
        //issuerCert2 = 
        //certChain = 
        //crlList = new CRL[0];
        tsc = new MockedTSAClient(3178);
        ocsp = new byte[1304];
        assertEstimateCloseEnough(signerKeyPair.getPrivate(), certChain, tsc, ocsp, crlList, maxDiff);

        // Subject, Issuer(17173 extra bytes), Issuer2 (123 extra bytes), 3178 extra bytes TS, 10102 bytes OCSP, 0 CRLs (0 extra bytes)
        //extensionBytes = 
        //issuerCert = 
        //issuerCert2 = 
        //certChain = 
        //crlList =
        tsc = new MockedTSAClient(3178);
        ocsp = new byte[10102];
        assertEstimateCloseEnough(signerKeyPair.getPrivate(), certChain, tsc, ocsp, crlList, maxDiff);

        // Subject, Issuer(0 extra bytes), 0 extra bytes TS, 0 bytes OCSP, 1 CRLs (0 extra bytes)
        extensionBytes = new byte[0];
        issuerCert = converter.getCertificate(new CertBuilder().setIssuerPrivateKey(issuerKeyPair.getPrivate())
                .setSubjectPublicKey(issuerKeyPair.getPublic()).setSubject("CN=Issuer1").setIssuer("CN=Issuer1")
                .addExtension(
                        new CertExt(new ASN1ObjectIdentifier("1.2.3.4"), false, new DERBitString(extensionBytes)))
                .build());
        certChain = new Certificate[] { signerCert, issuerCert };
        crlList = new CRL[] { createCRL(signerKeyPair.getPrivate(), new byte[0]) };
        tsc = new MockedTSAClient(0);
        ocsp = null;
        assertEstimateCloseEnough(signerKeyPair.getPrivate(), certChain, tsc, ocsp, crlList, maxDiff);

        // Subject, Issuer(17000 extra bytes), 0 extra bytes TS, 0 bytes OCSP, 1 CRLs (5432 extra bytes)
        extensionBytes = new byte[17000];
        issuerCert = converter.getCertificate(new CertBuilder().setIssuerPrivateKey(issuerKeyPair.getPrivate())
                .setSubjectPublicKey(issuerKeyPair.getPublic()).setSubject("CN=Issuer1").setIssuer("CN=Issuer1")
                .addExtension(
                        new CertExt(new ASN1ObjectIdentifier("1.2.3.4"), false, new DERBitString(extensionBytes)))
                .build());
        certChain = new Certificate[] { signerCert, issuerCert };
        crlList = new CRL[] { createCRL(signerKeyPair.getPrivate(), new byte[5432]) };
        tsc = new MockedTSAClient(0);
        ocsp = null;
        assertEstimateCloseEnough(signerKeyPair.getPrivate(), certChain, tsc, ocsp, crlList, maxDiff);

        // Subject, Issuer(17000 extra bytes), 0 extra bytes TS, 0 bytes OCSP, 2 CRLs (5432 extra bytes, 5076 extra bytes)
        extensionBytes = new byte[17000];
        issuerCert = converter.getCertificate(new CertBuilder().setIssuerPrivateKey(issuerKeyPair.getPrivate())
                .setSubjectPublicKey(issuerKeyPair.getPublic()).setSubject("CN=Issuer1").setIssuer("CN=Issuer1")
                .addExtension(
                        new CertExt(new ASN1ObjectIdentifier("1.2.3.4"), false, new DERBitString(extensionBytes)))
                .build());
        certChain = new Certificate[] { signerCert, issuerCert };
        crlList = new CRL[] { createCRL(signerKeyPair.getPrivate(), new byte[5432]),
                createCRL(signerKeyPair.getPrivate(), new byte[5076]) };
        tsc = new MockedTSAClient(0);
        ocsp = null;
        assertEstimateCloseEnough(signerKeyPair.getPrivate(), certChain, tsc, ocsp, crlList, maxDiff);
    }

    private void assertEstimateCloseEnough(PrivateKey signerPrivKey, Certificate[] certChain, MockedTSAClient tsc,
            byte[] ocsp, CRL[] crlList, int maxDiff) throws Exception {
        final int largeEnoughSpace = 32000;
        PDFSigner instance = new PDFSigner();

        int estimate = instance.calculateEstimatedSignatureSize(certChain, tsc, ocsp, crlList);

        int actual = getActualP7Size(signerPrivKey, largeEnoughSpace, certChain, crlList, ocsp, tsc);
        LOG.debug("estimate: " + estimate + ", actual: " + actual);

        // Estimate should not be to small
        assertTrue("estimate (" + estimate + ") must be at least " + actual, estimate >= actual);

        // Should not be larger than maxDiff
        int diff = estimate - actual;
        LOG.debug("diff: " + diff);
        assertTrue("diff (" + diff + ") <= maxDiff (" + maxDiff + ")", diff <= maxDiff);
    }

    /**
     * As we might not be in control of an external TSA the size they return might
     * be different from call to call that means that there is always a chance of
     * us doing a wrong size estimate. 
     * This tests tests that if we got it wrong the first time the second time
     * we make an estimate which is larger than the actual size returned from the 
     * first try.
     */
    public void test14calculateEstimatedSignatureSize_resign() throws Exception {

        byte[] pdfbytes = readFile(sample);
        final KeyPair signerKeyPair = CryptoUtils.generateRSA(1024);
        final Certificate[] certChain = new Certificate[] { converter.getCertificate(new CertBuilder().build()) };
        final Certificate signerCertificate = certChain[0];

        // any small value
        assertCanSign(pdfbytes, signerKeyPair, certChain, signerCertificate, 10);

        // medium value 3072
        assertCanSign(pdfbytes, signerKeyPair, certChain, signerCertificate, 3070);

        // the initial value just by the TSA client
        assertCanSign(pdfbytes, signerKeyPair, certChain, signerCertificate, 7168);
        assertCanSign(pdfbytes, signerKeyPair, certChain, signerCertificate, 7168 + 32);

        // slightly larger
        assertCanSign(pdfbytes, signerKeyPair, certChain, signerCertificate, 7168 + 32 + 1);

        // a larger value
        assertCanSign(pdfbytes, signerKeyPair, certChain, signerCertificate, 10123);

        // a large value
        assertCanSign(pdfbytes, signerKeyPair, certChain, signerCertificate, 15000 * 2 + 456);
    }

    /**
     * Test that setting both TSA_URL and TSA_WORKER results in a config error.
     * 
     * @throws Exception
     */
    public void test15TSA_URLandTSA_WORKERbothNotAllowed() throws Exception {
        WorkerConfig workerConfig = new WorkerConfig();

        workerConfig.setProperty("NAME", "TestSigner100");
        workerConfig.setProperty("TSA_URL", "http://localhost:8080/signserver/tsa?workerName=TimeStampSigner");
        workerConfig.setProperty("TSA_WORKER", "TimeStampSigner2");

        final PDFSigner instance = new PDFSigner() {
            @Override
            public ICryptoToken getCryptoToken() throws SignServerException {
                return null;
            }
        };
        instance.init(WORKER1, workerConfig, null, null);

        final List<String> fatalErrors = instance.getFatalErrors();

        assertTrue("Should contain error", fatalErrors.contains(
                "Can not specify " + PDFSigner.TSA_URL + " and " + PDFSigner.TSA_WORKER + " at the same time."));
    }

    /**
     * Test that specifying an unknown hash algorithm gives
     * a configuration error.
     * 
     * @throws Exception
     */
    public void test16IllegalDigestAlgorithm() throws Exception {
        WorkerConfig workerConfig = new WorkerConfig();

        workerConfig.setProperty("NAME", "TestSigner100");
        workerConfig.setProperty("DIGESTALGORITHM", "IllegalHash");

        final PDFSigner instance = new PDFSigner() {
            @Override
            public ICryptoToken getCryptoToken() throws SignServerException {
                return null;
            }
        };
        instance.init(WORKER1, workerConfig, null, null);

        final List<String> fatalErrors = instance.getFatalErrors();

        assertTrue("Should contain error", fatalErrors.contains("Illegal digest algorithm: IllegalHash"));
    }

    /**
     * Test that setting a hash algorithm other than SHA1
     * gives an error when using DSA keys.
     * 
     * @throws Exception
     */
    public void test17OnlySHA1AcceptedForDSA() throws Exception {
        final MockedCryptoToken token = generateToken(true);
        final MockedPDFSigner instance = new MockedPDFSigner(token);

        final WorkerConfig workerConfig = new WorkerConfig();

        workerConfig.setProperty("NAME", "TestSignerDSA");
        workerConfig.setProperty("DIGESTALGORITHM", "SHA256");

        instance.init(WORKER2, workerConfig, null, null);

        final List<String> fatalErrors = instance.getFatalErrors();

        assertTrue("Should contain error", fatalErrors.contains(ILLEGAL_DIGEST_FOR_DSA_MESSAGE));
    }

    /**
     * Test that explicitly setting SHA1 for DSA keys works.
     * 
     * @throws Exception
     */
    public void test18SHA1acceptedForDSA() throws Exception {
        final MockedCryptoToken token = generateToken(true);
        final MockedPDFSigner instance = new MockedPDFSigner(token);

        final WorkerConfig workerConfig = new WorkerConfig();

        workerConfig.setProperty("NAME", "TestSignerDSA");
        workerConfig.setProperty("DIGESTALGORITHM", "SHA1");

        instance.init(WORKER2, workerConfig, null, null);

        final List<String> fatalErrors = instance.getFatalErrors();

        assertFalse("Should not contain error", fatalErrors.contains(ILLEGAL_DIGEST_FOR_DSA_MESSAGE));
    }

    /**
     * Test that setting the hash algorithm to SHA256
     * is accepted for RSA keys.
     * 
     * @throws Exception
     */
    public void test19SHA256AcceptedForRSA() throws Exception {
        final MockedCryptoToken token = generateToken(false);
        final MockedPDFSigner instance = new MockedPDFSigner(token);

        final WorkerConfig workerConfig = new WorkerConfig();

        workerConfig.setProperty("NAME", "TestSignerDSA");
        workerConfig.setProperty("DIGESTALGORITHM", "SHA256");

        instance.init(WORKER2, workerConfig, null, null);

        final List<String> fatalErrors = instance.getFatalErrors();

        assertFalse("Should not contain error", fatalErrors.contains(ILLEGAL_DIGEST_FOR_DSA_MESSAGE));
    }

    /**
     * Helper method creating a mocked token, using DSA or RSA keys.
     * 
     * @param useDSA True if DSA is to be used, otherwise RSA
     * @return Mocked crypto token
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws CertBuilderException
     * @throws CertificateException
     */
    private MockedCryptoToken generateToken(final boolean useDSA)
            throws NoSuchAlgorithmException, NoSuchProviderException, CertBuilderException, CertificateException {
        final KeyPair signerKeyPair = useDSA ? CryptoUtils.generateDSA(1024) : CryptoUtils.generateRSA(1024);
        final Certificate[] certChain = new Certificate[] { converter.getCertificate(new CertBuilder().build()) };
        final Certificate signerCertificate = certChain[0];
        final String provider = "BC";

        final MockedCryptoToken token = new MockedCryptoToken(signerKeyPair.getPrivate(), signerKeyPair.getPublic(),
                signerCertificate, Arrays.asList(certChain), provider);
        return token;
    }

    /**
     * Mocked PDF signer using a mocked crypto token.
     */
    private class MockedPDFSigner extends PDFSigner {
        private final MockedCryptoToken mockedToken;

        public MockedPDFSigner(final MockedCryptoToken mockedToken) {
            this.mockedToken = mockedToken;
        }

        @Override
        public Certificate getSigningCertificate(final ProcessRequest request, final RequestContext context)
                throws CryptoTokenOfflineException {
            return mockedToken.getCertificate(ICryptoToken.PURPOSE_SIGN);
        }

        @Override
        public List<Certificate> getSigningCertificateChain(final ProcessRequest request,
                final RequestContext context) throws CryptoTokenOfflineException {
            return mockedToken.getCertificateChain(ICryptoToken.PURPOSE_SIGN);
        }

        @Override
        public ICryptoToken getCryptoToken() {
            return mockedToken;
        }
    }

    /**
     * Tests that we don't get an exception trying to sign a document with the 
     * given parameters.
     * The idea is that if the estimate is too small an retry is done so this 
     * should always succeed.
     */
    private void assertCanSign(final byte[] pdfbytes, final KeyPair signerKeyPair, final Certificate[] certChain,
            final Certificate signerCertificate, final int tsSize) throws Exception {

        final MockedTSAClient tsc = new MockedTSAClient(tsSize);
        final String provider = "BC";

        final MockedCryptoToken token = new MockedCryptoToken(signerKeyPair.getPrivate(), signerKeyPair.getPublic(),
                signerCertificate, Arrays.asList(certChain), provider);

        PDFSigner instance = new PDFSigner() {

            @Override
            protected TSAClient getTimeStampClient(String url, String username, String password) {
                return tsc;
            }

            @Override
            public Certificate getSigningCertificate(final ProcessRequest request, final RequestContext context)
                    throws CryptoTokenOfflineException {
                return signerCertificate;
            }

            @Override
            public List<Certificate> getSigningCertificateChain(final ProcessRequest request,
                    final RequestContext context) throws CryptoTokenOfflineException {
                return Arrays.asList(certChain);
            }

            @Override
            public ICryptoToken getCryptoToken() {
                return token;
            }

        };

        final WorkerConfig config = new WorkerConfig();
        config.setProperty("TSA_URL", "http://any-tsa.example.com");
        final PDFSignerParameters params = new PDFSignerParameters(1234, config);

        instance.setIncludeCertificateLevels(1);

        try {
            byte[] signedPdfbytes = instance.addSignatureToPDFDocument(
                    token.acquireCryptoInstance("any-alias", Collections.<String, Object>emptyMap(), null), params,
                    pdfbytes, null, 0, null, null);
            assertNotNull(signedPdfbytes);
        } catch (SignServerException ex) {
            LOG.debug("failed to sign", ex);
            fail(ex.getMessage());
        }

        if (!tsc.isCalled()) {
            throw new Exception("Test must be configured to use TSA otherwise we are not testing anything...");
        }
        LOG.debug("Private key used: " + token.getPrivateKeyCalls() + "\n");
    }

    /**
     * Create a signature with the given input.
     * @return The size of the produced PKCS#7 structure.
     */
    private int getActualP7Size(PrivateKey signerPrivKey, int estimate, Certificate[] certChain, CRL[] crlList,
            byte[] ocsp, MockedTSAClient tsc) throws Exception {
        PDFSigner instance = new PDFSigner();
        PdfReader reader = new PdfReader(readFile(sample), null);
        ByteArrayOutputStream fout = new ByteArrayOutputStream();
        PdfStamper stp = PdfStamper.createSignature(reader, fout, '\0', null, true);
        PdfSignatureAppearance sap = stp.getSignatureAppearance();
        sap.setCrypto(null, certChain, crlList, PdfSignatureAppearance.SELF_SIGNED);
        PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, new PdfName("adbe.pkcs7.detached"));
        dic.setReason("Reasons...");
        dic.setLocation("Location...");
        dic.setDate(new PdfDate(Calendar.getInstance()));
        sap.setCryptoDictionary(dic);
        byte[] encodedSig = instance.calculateSignature(
                new PdfPKCS7(signerPrivKey, certChain, crlList, "SHA1", null, false), estimate,
                MessageDigest.getInstance("SHA1"), Calendar.getInstance(), null, certChain, tsc, ocsp, sap);

        return encodedSig.length;
    }

    private byte[] signPDF(File file) throws Exception {
        return signProtectedPDF(file, null);
    }

    private byte[] signProtectedPDF(File file, String password) throws Exception {
        LOG.debug("Tests signing of " + file.getName() + " with password:");
        if (password == null) {
            LOG.debug("null");
        } else {
            LOG.debug("\"" + password + "\" " + Arrays.toString(password.toCharArray()));
        }

        RequestContext context = new RequestContext();
        RequestMetadata.getInstance(context).put(RequestContext.METADATA_PDFPASSWORD, password);

        final GenericSignResponse response = (GenericSignResponse) workerSession.process(WORKER1,
                new GenericSignRequest(200, readFile(file)), context);
        assertNotNull(response);
        return response.getProcessedData();
    }

    private void setupWorkers()
            throws NoSuchAlgorithmException, NoSuchProviderException, CertBuilderException, CertificateException {

        final GlobalConfigurationSessionMock globalMock = new GlobalConfigurationSessionMock();
        final WorkerSessionMock workerMock = new WorkerSessionMock(globalMock);
        globalConfig = globalMock;
        workerSession = workerMock;

        // WORKER1
        final MockedCryptoToken token = generateToken(false);
        {
            final int workerId = WORKER1;
            final WorkerConfig config = new WorkerConfig();
            config.setProperty(NAME, "TestPDFSigner1");
            config.setProperty(AUTHTYPE, "NOAUTH");

            workerMock.setupWorker(workerId, CRYPTOTOKEN_CLASSNAME, config, new PDFSigner() {
                @Override
                protected IGlobalConfigurationSession.IRemote getGlobalConfigurationSession() {
                    return globalConfig;
                }

                @Override
                public ICryptoToken getCryptoToken() {
                    return token;
                }
            });
            workerSession.reloadConfiguration(workerId);
        }
    }

    private byte[] readFile(File file) throws IOException {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        BufferedInputStream in = null;
        try {
            in = new BufferedInputStream(new FileInputStream(file));
            int b;
            while ((b = in.read()) != -1) {
                bout.write(b);
            }
        } finally {
            if (in != null) {
                in.close();
            }
        }
        return bout.toByteArray();
    }

    private Permissions getPermissions(byte[] pdfBytes, byte[] password) throws IOException {
        PdfReader reader = new PdfReader(pdfBytes, password);
        return Permissions.fromInt(reader.getPermissions());
    }

    /**
     * Asserts that the password really can be used as user password.
     */
    private static void assertUserPassword(byte[] pdfBytes, String password) throws IOException, DocumentException {
        // This will fail unless password is owner or user
        System.out.println("password: " + password);
        PdfReader reader = new PdfReader(pdfBytes, password.getBytes("ISO-8859-1"));
        reader.close();

        // Still if the document did not contain a password it would not have failed yet
        // Test that it really fails when specifying a wrong password
        boolean exceptionThrown = true;
        try {
            PdfReader reader2 = new PdfReader(pdfBytes,
                    "_ABSOLUTLEY_NOT_THE_RIGHT_PASSWORD_".getBytes("ISO-8859-1"));
            reader2.close();
            exceptionThrown = false;
        } catch (IOException ok) {
            LOG.debug(ok.getMessage());
        }
        if (!exceptionThrown) {
            throw new IOException("PDF did not require a password");
        }
    }

    /**
     * Asserts that the supplied password is user but not an owner password.
     */
    private static void assertUserNotOwnerPassword(byte[] pdfBytes, String password)
            throws IOException, DocumentException {
        // This will fail unless password is owner or user
        PdfReader reader;
        PdfStamper stp;
        try {
            reader = new PdfReader(pdfBytes, password == null ? null : password.getBytes("ISO-8859-1"));
        } catch (BadPasswordException ex) {
            fail("Not a valid password: " + ex.getMessage());
            return;
        }

        try {
            // This should fail if password is not owner
            ByteArrayOutputStream fout = new ByteArrayOutputStream();
            stp = PdfStamper.createSignature(reader, fout, '\0', null, false);
            stp.setEncryption(reader.computeUserPassword(),
                    password == null ? null : password.getBytes("ISO-8859-1"), 0, 1);
            fail("Password was an owner password");
        } catch (BadPasswordException ok) {
            LOG.debug("ok: " + ok.getMessage());
        }
    }

    /**
     * Asserts that the password really can be used as owner password.
     */
    private static void assertOwnerPassword(byte[] pdfBytes, String password)
            throws IOException, DocumentException {
        // This will fail unless password is owner or user
        PdfReader reader = new PdfReader(pdfBytes, password.getBytes("ISO-8859-1"));
        ByteArrayOutputStream fout = new ByteArrayOutputStream();
        PdfStamper stp = PdfStamper.createSignature(reader, fout, '\0', null, false);

        // This will fail unless password is owner
        stp.setEncryption(reader.computeUserPassword(), password.getBytes("ISO-8859-1"), 0, 1);

        // Still if the document did not contain a password it would not have failed yet
        // Test that it really fails when specifying a wrong password
        boolean exceptionThrown = true;
        try {
            PdfReader reader2 = new PdfReader(pdfBytes,
                    "_ABSOLUTLEY_NOT_THE_RIGHT_PASSWORD_".getBytes("ISO-8859-1"));
            reader2.close();
            exceptionThrown = false;
        } catch (IOException ok) {
            LOG.debug(ok.getMessage());
        }
        if (!exceptionThrown) {
            throw new IOException("PDF did not require a password");
        }
    }
}