Development

Introduction

This section describes the details of how to develop your own plug-ins for the SignServer API. It goes through most of the necessary interfaces to get going.

See also doc/DEVELOP.txt.

Custom modules

Custom code can be included in the SignServer build process by placing the projects under the modules directory and adhere to the modules contract.

First, the property modules.build.includes in res/modules-project.properties defines which projects that should be included in the build process. All projects with a folder name starting with SignServer-Module-*, SignServer-ejb* or SignServer-war are included. Other patterns can be included by setting a property extra.modules.build.includes.

Second, in the project folder there should be an Ant script called build.xml.

Third, if the project should be called it should define the properties:

  • modulename: The name of the module. Will be used as prefix to the enable-property. Example: module.pdfsigner (the enable property will then be module.pdfsigner.enabled)
  • clean.available: True if the "clean" target should be called when the rest of SignServer is getting cleaned.
  • dist-module.available: True if the "dist-module" target should be called when SignServer is getting built

Example:

<property name="modulename" value="module.pdfsigner"/>
<property name="clean.available" value="true"/>
<property name="dist-module.available" value="true"/>

Forth, the dist-module target should take care of moving all distributables to the ../../lib directory and if the module should be deployed as part of the signserver.ear file a module descriptor should be placed in ../../mods-available. The name of the descriptor file should be priority_modulename.properties where priority is the order in which the module should be built and modulename is the name as defined by the property with the same name.

The priorities are basically like this:

  • 10: Essential core modules (like signserverejb and signservercommon)
  • 20: Other core modules (like web services)
  • 30: Other core modules (like web components etc)
  • 40: Other modules (like signers/validators etc)

The module descriptor can contain:

  • module.name: Same as the modulename property
  • module.type: The type of the module. "lib" if the module should be put in signserver.ear/lib, "ejb" if it is an EJB module or "war" if it is and WAR module.
  • to.root: Comma separated list of files to move to the root of signserver.ear. Basedir is SIGNSERVER_HOME and all files should be taken from lib/ (not modules/ as it is not available in the binary distribution).
  • to.lib: Same as to.root but for jar files to be moved to signserver.ear/lib/.
  • module.ejb: Only if module.type is "ear" or "war". The name of the enterprise module file (example: signserver.war).
  • module.web.web-uri: Only if module.type is "war". The name to put in the web-uri part of the application.xml for this enterprise module.
  • module.web.context-root: Only if module.type is "war". The name to put in the context-root part of the application.xml for this enterprise module.
  • postprocess.files: Optionally. Comma separated list of property name prefixes for each file to postprocess (example: postprocess.file1)
  • postprocess.file1.src: Only if postprocess.files specified. Path of file to postprocess (relative to SIGNSERVER_HOME/lib/, example: SignServer-ejb-SignServerWS.jar)
  • postprocess.file1.includes: Only if postprocess.files. Ant includes pattern with files in the jar to postprocess (example: META-INF/ejb-jar.xml)
  • postprocess.file1.dest: Only if postprocess.files specified. Location to put the resulting JAR in the final EAR. Default "" (the root) but should be set to "lib/" for library JARs.

Example (mods-available/40_module.pdfsigner.properties):

module.name=module.pdfsigner
module.type=lib
to.root=
to.lib=lib/ext/1.6/bcprov-jdk.jar,lib/ext/1.6/bctsp-jdk.jar,lib/ext/cert-cvc.jar,lib/ext/commons-collections-3.2.2.jar,lib/ext/commons-io-1.4.jar,lib/ext/commons-lang-2.6.jar,lib/ext/commons-logging-1.1.1.jar,lib/ext/ejbca-util.jar,lib/ext/log4j-1.2.14.jar,lib/ext/module/pdfsigner/itext/itext.jar,lib/SignServer-Common.jar,lib/SignServer-ejb-interfaces.jar,lib/SignServer-Module-PDFSigner.jar

Example (mods-available/20_signserverws.properties):

module.name=signserverws
module.type=ejb
module.ejb=SignServer-ejb-SignServerWS.jar
to.root=lib/SignServer-ejb-SignServerWS.jar
to.lib=lib/ext/1.6/bcprov-jdk.jar,lib/ext/1.6/bctsp-jdk.jar,lib/ext/cert-cvc.jar,lib/ext/commons-collections-3.2.2.jar,lib/ext/commons-io-1.4.jar,lib/ext/commons-lang-2.6.jar,lib/ext/commons-logging-1.1.1.jar,lib/ext/ejbca-util.jar,lib/ext/log4j-1.2.14.jar,lib/SignServer-Common.jar,lib/SignServer-ejb-interfaces.jar
postprocess.files=postprocess.file1
postprocess.file1.src=SignServer-ejb-SignServerWS.jar
postprocess.file1.includes=META-INF/ejb-jar.xml

Custom sub modules

In the same way as described in the previous chapter about custom modules, it is also possible to have custom sub modules. This is done by creating a folder under 'modules' with a name starting with 'mod-' and include a build.xml as in the example below. It would then be possible to include any number of additional modules in this folder.

Sample modules/mod-mysigners/build.xml:

<project name="MySigner Modules" default="dist-module" basedir=".">
    <description>Builds sub modules in mysigner</description>
    <property name="platform.project.dir" location="../../"/>
    <property name="signserver.home" location="../../"/>
    <property file="build.properties"/>
    <property file="${platform.project.dir}/conf/signserver_deploy.properties"/>
    <property file="${platform.project.dir}/signserver_deploy.properties"/>
    
    <!-- Clean selected modules in this sub module -->
    <target name="-do-clean-module">
        <echo>Cleaning ${ant.project.name}</echo>
        <subant target="-do-clean-module" failonerror="true">
            <fileset dir="." includes="${modules.clean.includes}"/>
         </subant>
    </target>
    <target name="clean" depends="-do-clean-module"/>
    
    <!-- Build selected modules in this sub module -->
    <target name="-do-module">
        <echo>Building ${ant.project.name}</echo>
        <subant target="-do-module" verbose="false">
            <fileset dir="." includes="${modules.build.includes}"/>
        </subant>
    </target>
    <target name="dist-module" depends="-do-module"/>
    
    <!-- Build selected systemtests in this sub module -->
    <target name="-do-test-module">    
        <echo>Building systemtests for ${ant.project.name}</echo>
        <subant target="-do-test-module" verbose="false">
            <fileset dir="." includes="${build.systemtest.includes}" excludes="${build.systemtest.excludes}"/>
        </subant>
    </target>
    <target name="build-systemtests" depends="-do-test-module"/>
    
    <!-- Run tests in this sub module -->
    <target name="test">
        <echo>Running tests for ${ant.project.name}</echo>
        <subant target="test">
            <fileset dir="." includes="${tests.modules.includes}"/>
        </subant>
    </target>
</project>

Custom CLI

The SignServer AdminCLI or ClientCLI can be extended by putting additional JAR files with custom implementations of the ClientCommandFactory or AdminCommandFactory interfaces on the classpath. The implementation classes should be listed in the JAR files under META-INF/services in a file with the full class name of the interface. See SignServer-Client-ValidationCLI as an example.

Plugin development

The main component in the SignServer is the Worker from which most other components inherits. To get a better overview of how the different component types relate to one and another see illustration 1 in the Overview section.

Most workers work in the same way but with different interfaces to implement but for all of them should the following steps be performed.

  • Create a custom class implementing the specified interface. There usually exists a base class implementing the most basic function to simply the implementation even further. If it exists it's recommended to inherit it.
  • You can define your own properties that the worker can use for its configuration.
  • Make sure the custom class is available to the application server
  • Redeploy the SignServer.
  • Register the worker in the application by setting a property WORKER<id>.CLASSPATH with a global scope in the global configuration. (Also make sure to set it's crypto tokens class-path, see separate section).
  • Reload the service with the CLI reload command.

Implementing Workers

The ISigner Interface

A Signer is a component used to perform some form of cryptographic processing of requested data and to create a custom signer class it should implement the org.signserver.server.signers.ISigner interface. There exists a BaseSigner that can be inherited taking care of some of the functionality. If the BaseSigner is inherited the only method that needs to be implemented is 'processData() '.

There exists a DummySigner implementation that is used for demonstration purposes.

The ITimedService Interface

There are two kinds of timed services, singleton or non-singleton. A singleton service is only run at one of the nodes at the time while non-singleton services are run at all nodes simultaneously. If a singleton service fails to run on one of the nodes will one of the other nodes take over the service automatically.

If a service should be singleton or not is determined by a standard property SINGLETON defined in the ServiceConfig class.

Other basic properties used to configure all services are: ACTIVE when set to "TRUE" means that the service is active and should be run. INTERVAL defining the interval in seconds of how often the service should be run. INTERVALMS defining the interval in milliseconds of how often the service should be run. CRON used instead of INTERVAL or INTERVALMS to specify on a calendar basis.

To create a custom timed service class it should implement the org.signserver.server.timedservices.ITimedService interface. There exists a BaseTimedService that can be inherited taking care of most of the basic functionality. If the BaseTimedService is inherited the the only method that needs to be implemented is the 'work()' method.

The work method that needs to be implemented is described here:

/**
 * Method that should do the actual work and should
 * be implemented by all services. The method is run
 * at a periodical interval defined in getNextInterval.
 *
 * @throws ServiceExecutionFailedException if execution of a service failed
 */
public void work() throws ServiceExecutionFailedException;

There exists a DummyTimedService implementation that is used for demonstration purposes.

IValidationService Interface

Just as the other worker plug-ins have the validator service a base class taking care of most of the common methods and the only method that needs to be implemented is the 'validate' method below. But for most applications should the DefaultValidationService work. What is probably more interesting is to develop a custom IValidator used to integrate the default validation service against different certificate status repositories. See section called 'Other Customizations' for details of how to implement a Validator.

/**
 * Method used to check the validation of a certificate
 *
 * @param validationRequest
 * @return a ValidateResponse
 * @throws IllegalRequestException if data in the request didn't conform with the specification.
 * @throws CryptoTokenOfflineException if the crypto token isn't online.
 * @throws SignServerException for general failure exception during validation
 * @see org.signserver.validationservice.common.ValidateRequest
 * @see org.signserver.validationservice.common.ValidateResponse
 */
ValidateResponse validate(ValidateRequest validationRequest) throws IllegalRequestException, CryptoTokenOfflineException, SignServerException;

Implementing Crypto Tokens

The ICryptoToken Interface

  • A custom crypto token needs to implement the interface org.signserver.server.cryptotokens.ICryptoToken. See P12CryptoToken for an example implementation.
  • You can define own properties for a crypto token in the same way as for workers. The properties are sent to the crypto token upon initialization.
  • Make sure the custom class is available to the application server
  • Redeploy the SignServer.
  • Register the crypto token to a worker in the application by setting a property WORKER<id>.CRYPTOTOKEN.CLASSPATH with a global scope in the global configuration. (Also make sure to set it's crypto tokens class-path, see next section).
  • Reload the service with the CLI reload command.

The ICryptoToken interface have the following methods that needs to be implemented:

public interface ICryptoToken {
	public static final int PURPOSE_SIGN = 1;
	public static final int PURPOSE_DECRYPT = 2;

	public static final int PROVIDERUSAGE_SIGN    = 1;
	public static final int PROVIDERUSAGE_DECRYPT = 2;

   /**
    * Method called after creation of instance.
    *
    */
	public abstract void init(Properties props) throws CryptoTokenInitializationFailureException;

	/**
	 *  Method that returns the current status of the crypto token.
	 *
	 *  Should return one of the SignerStatus.STATUS_.. values
	 */
	public abstract int getCryptoTokenStatus();

    /**
     * Method used to activate SignTokens when connected after being off-line.
     *
     * @param authenticationcode used to unlock crypto token, i.e PIN for smartcard HSMs
     * @throws CryptoTokenOfflineException if SignToken is not available or connected.
     * @throws CryptoTokenAuthenticationFailureException with error message if authentication to crypto token fail.
     */
    public abstract void activate(String authenticationcode) throws CryptoTokenAuthenticationFailureException, CryptoTokenOfflineException;

    /**
     * Method used to deactivate crypto tokens.
     * Used to set a crypto token too off-line status and to reset the HSMs authorization code.
     *
     * @return true if deactivation was successful.
     */
    public abstract boolean deactivate();

    /** Returns the private key (if possible) of token.
    *
    * @param purpose should one of the PURPOSE_... constants
    * @throws CryptoTokenOfflineException if CryptoToken is not available or connected.
    * @return PrivateKey object
    */
    public abstract PrivateKey getPrivateKey(int purpose) throws CryptoTokenOfflineException;

    /** Returns the public key (if possible) of token.
    *
    * @param purpose should one of the PURPOSE_... constants
    * @throws CryptoTokenOfflineException if CryptoToken is not available or connected.
    * @return PublicKey object
    */
    public abstract PublicKey getPublicKey(int purpose) throws CryptoTokenOfflineException;


    /** Returns the signature Provider that should be used to sign things with
     *  the PrivateKey object returned by this crypto device implementation.
     *  @param providerUsage should be one if the ICryptoToken.PROVIDERUSAGE_ constants
     *  specifying the usage of the private key.
     * @return String the name of the Provider
     */
    public abstract String getProvider(int providerUsage);

    /**
     * Method returning the crypto tokens certificate if it's included in the token.
     * This method should only be implemented by soft crypto tokens which have the certificate
     * included in the key store.
     *
     * All other crypto tokens should return 'null' and let the signer fetch the certificate from database.
     *
     */

    public abstract Certificate getCertificate(int purpose) throws CryptoTokenOfflineException;


    /**
     * Method returning the crypto tokens certificate chain if it's included in the token.
     * This method should only be implemented by soft crypto tokens which have the certificates
     * included in the key store.
     *
     * All other crypto tokens should return 'null' and let the signer fetch the certificate from database.
     *
     */

    public abstract Collection<Certificate> getCertificateChain(int purpose) throws CryptoTokenOfflineException;

	/**
	 * Method used to tell the crypto token to create a certificate request using its crypto token.
	 */
	public ICertReqData genCertificateRequest(ISignerCertReqInfo info) throws CryptoTokenOfflineException;

	/**
	 * Method used to remove a key in the signer that shouldn't be used any more
	 * @param purpose on of ICryptoToken.PURPOSE_ constants
	 * @return true if removal was successful.
	 */
	public boolean destroyKey(int purpose);
}

Sample Workers and Components

Note
This is a SignServer Enterprise Edition (EE) feature.

There is a module containing sample and skeleton implementation of almost all the different kinds of workers as well as different components.

The source of the module is located in SIGNSERVER_HOME/modules/mod-enterprise/SignServer-Module-Sample/.

The following implementations are available:

  • SkeletonTimedService, HelloTimedService
  • SkeletonWorker, HelloWorker
  • SkeletonDispatcher, StatisticalDispatcher
  • SkeletonSigner, TextSigner
  • SkeletonValidator, TextValidator
  • SkeletonArchiver, FileArchiver
  • SkeletonAccounter, GlobalConfigAccounter
  • SkeletonAuthorizer, ReversedNameAuthorizer

Other Customizations

The IValidator Interface

A Validator is used in the DefaultValidationService to connect to different kinds of certificate status repositories, such as CRL, OCSP, XKMS, database etc. It contains two methods 'validate' used for the actual certificate validation and 'testConnection' used by health check related functionality to check that the connection to the underlying validator resource is alright.

/**
 * Main method of a Validation Service responsible for validating certificates.
 *
 * Important a validator also have to support to check the revocation status of the
 * involved CA certificates and should only return Validation object with status REVOKED or VALID
 * If the validator doesn't support the given issuer it must return null.
 *
 *
 * @param cert the certificate to validate.
 * @return a Validation object or null if the certificate couldn't be looked up in this validator.
 * @throws IllegalRequestException if data in the request didn't conform with the specification.
 * @throws CryptoTokenOfflineException if the crypto token isn't online.
 * @throws SignServerException for general failure exception during validation.
 */
Validation validate(ICertificate cert) throws IllegalRequestException, CryptoTokenOfflineException, SignServerException;






/**
 * Optional method used to test the connection to a specific underlying validator implementation.
 *
 * @throws ConnectException if connection to underlying validator implementation failed.
 * @throws SignServerException for general failure exception during validation.
 */
void testConnection() throws ConnectException, SignServerException;

The IAuthorizer Interface

It's possible to integrate the authorization of processable requests with external authorizations applications. All that is needed is a class implementing the IAuthorizer interface containing two methods, 'init' and 'isAuthorized'.

To register that the customized authorizer should be used by a worker, all that's needed to be done is to set the property AUTHTYPE to the class path of the authorizer implementation.

public interface IAuthorizer {

	/**
	 * Method called by the worker upon first call to the authenticator after instantiation.
	 *
	 * @param workerId ID of worker.
	 * @param config active worker configuration of worker
	 * @param em the SignServer EntityManager
	 * @throws SignServerException if unexpected error occurred during initialization.
	 */
	void init(int workerId, WorkerConfig config, EntityManager em) throws SignServerException;

	/**
	 *
	 * Main method determining if the requester is authorized to process the data in the request.
	 *
	 * @param request the request data sent to the worker to process.
	 * @param requestContext containing the optional clientCert client certificate or remote IP of the user, may also contain customly defined data.
	 * @throws SignServerException if unexpected error occurred during authorization.
	 * @throws IllegalRequestException if the requester isn't authorized or couldn't be authenticated for some other reason.
	 */
	void isAuthorized(ProcessRequest request, RequestContext requestContext) throws IllegalRequestException, SignServerException;
}

The Archiving API

Custom ArchiverS can be implemented by implementing the Archiver interface. See org.signserver.server.archive.Archiver.

Archiving API

Using the Global Configuration Store

The global configuration store is a memory bank that workers can use to store data used in ongoing operations. The data can be either node (i.e. only read by the current node) or global scoped.

To access the global configuration store use the getGlobalConfigurationSession() method from the BaseWorker (inherited by most of the base component implementations). The returned GlobalConfigurationSession have the following methods that can be used (the other ones should be avoided)

/**
* Method setting a global configuration property. For node. prefix will the
node ID be appended.
* @param scope, one of the GlobalConfiguration.SCOPE_ constants
* @param key of the property should not have any scope prefix, never null
* @param value the value, never null.
*/
public void setProperty( java.lang.String scope,java.lang.String
key,java.lang.String value ) ;
/**
* Method used to remove a property from the global configuration.
* @param scope, one of the GlobalConfiguration.SCOPE_ constants
* @param key of the property should start with either glob. or node., never
null
* @return true if removal was successful, otherwise false.
*/
public boolean removeProperty( java.lang.String scope,java.lang.String key )
;
/**
* Method that returns all the global properties with Global Scope and Node
scopes properties for this node.
* @return A GlobalConfiguration Object, nevel null
*/
public org.signserver.common.GlobalConfiguration getGlobalConfiguration( ) ;

The getGlobalConfiguration returns a GlobalConfiguration and have a method String getProperty(String scope, String property) that can be used. The value of the property can be user-defined as long as it is guaranteed to be unique over the entire application. Reserved values are all property keys starting with "WORKER".

Testing

There exists some test scripts used to test that the SignServer functions correctly. They are described here.

Automatic JUnit Tests

Automatic JUnit tests are in the different projects and system tests in SignServer-Test-System.

Important: For the SignServer test suite to run successful the following properties should be set in signserver_deploy.properties:

  • signserverws.enabled=true
  • validationws.enabled=true
  • adminws.enabled=true
  • timestampclient.enabled=true
  • validationclient.enabled=true
  • signingandvalidationapi.enabled=true
  • clientcli.enabled=true
  • includemodulesinbuild=true
  • useclusterclassloader=true
  • clusterclassloader.useclassversions=true
  • healthcheck.maintenancefile=/path/to/signserver/maintenance.properties (where /path/to/signserver is the path where SignServer is located)

For the Database CLI tests to run successfully you will need to put the JDBC driver for your database as lib/ext/jdbc/jdbc.jar.

To run the test suite do the following:

  • Set the environment variable SIGNSERVER_HOME
  • Make sure SignServer is deployed and the application server is running
  • Do 'ant test:run'
  • A protocol is generated in the directory 'bin/junit'

A single system test can be run with "ant test:runone -Dtest.runone=ClassName" where ClassName is replaced with the test class to run.

A single test can also be run the same way from any of the other projects that has tests by executing the command from their project directory.

If SignServer is configured to run without database some tests needs to be excluded by running like this:

bin/ant test:run -Dexcludes="**/ArchivingCLITest*,**/Base64DatabaseArchiverTest*,**/OldDatabaseArchiverTest*,**/ArchiveTest*,**/AuditLogCLITest*,**/DatabaseCLITest*,**/VerifyLogCommandTest*"

System tests with HSM

To run system tests with an HSM create an properties file in SIGNSERVER_HOME called test-config.properties containing the PKCS#11 configuration including the key alias for an existing RSA key-pair:

test.p11.sharedlibrary=/opt/ETcpsdk/lib/linux-x86_64/libcryptoki.so
test.p11.slot=1
test.p11.pin=foo123
test.p11.existingkey1=mykey001

The tests can be run with:

ant test:p11:run test:report

Random testing

SignServer-Test-Random is a tool for random testing of SignServer. Each test suite has different requirements configured workers in the different worker groups and how many threads that can be used etc. See the source code for further details.

Building and running

Build from modules/SignServer-Test-Random/ using:

ant jar

It can then be run from SIGNSERVER_HOME using:

bin/randomtest

Usage

usage: randomtest <options>
Random testing tool
 -randomseed <arg>     Optional. Seed to initialize the pseudo random
                       generator with.
 -testsuite <arg>      Test suite to run. Any of [signWhileUpdatingConfig,
                       signAndCountSignings, signWhileRenewing].
 -threadgroup1 <arg>   Number of threads in group 1.
 -threadgroup2 <arg>   Number of threads in group 2.
 -timelimit <arg>      Optional. Only run for the specified time (in
                       milliseconds).
 -workergroup1 <arg>   First group of workers. Comma separated list of
                       workerId/workerType.
 -workergroup2 <arg>   Second group of workers. Comma separated list of
                       workerId/workerType
 -workergroup3 <arg>   Third group of workers. Comma separated list of
                       workerId/workerType

Sample usages:
a) randomtest -testsuite signWhileUpdatingConfig -workergroup1
5678/xml,5679/tsa,5680/xml -threadgroup1 4 -workergroup2
5677/xml,5678/xml,5679/tsa -threadgroup2 3 -timelimit 30000

b) randomtest -testsuite signAndCountSignings -workergroup1
5678/xml,5679/tsa,5680/xml -threadgroup1 10 -timelimit 30000

c) randomtest -testsuite signWhileRenewing -workergroup1 300/xml
-workergroup2 301/xml,302/xml -threadgroup1 5 -workergroup3 309/renew
-timelimit 20000

Available worker types:
- workerType can be any of [xml, tsa, renew]

Test suite: signAndCountSignings
- Signs documents with the workers from group 1 with the number of threads
defined for group 1
- Pauses signings and counts performed signings a compares to the key
usage counter value
- Notice that it is assumed that all workers use the same key-pair

Test suite: signWhileUpdatingConfig
- Signs documents with the workers from group 1 with the number of threads
defined for group 1
- Increases a counter in the configuration of group 2
- Notice that the size of thread group 2 must be equal to the number of
workers in group 2

Test suite: signWhileRenewing
- Signs documents with the workers from group 1 with the number of threads
defined for group 1
- Renews signers from group 2 using the one renewal worker in group 3
- Notice that group 3 should only include one renewal worker

Stress testing

SignServer-Test-Performance is a tool for performance testing of SignServer. Currently it contains only one test suite for testing time stamping. See the source code for further details.

Building and running

Build from modules/SignServer-Test-Performance/ using:

ant jar

It can then be run from SIGNSERVER_HOME using:

bin/stresstest

Usage

usage: stresstest <options>
Performance testing tool
 -data <arg>            Input data to be used with the
                        DocumentSigner/Validator1 testsuites using an
                        XMLSigner.
 -infile <arg>          Input file used for DocumentSigner/Validator1
                        testsuites.
 -maxwaittime <arg>     Maximum number of milliseconds for a thread to
                        wait until issuing the next time stamp.
                        Default=100
 -processurl <arg>      URL to process servlet (for the
                        DocumentSigner/Validator1 test suites).
 -statoutputdir <arg>   Optional. Directory to output statistics to. If
                        set, each threads creates a file in this directory
                        to output its response times to. The directory
                        must exist.
 -testsuite <arg>       Test suite to run. Any of [TimeStamp1,
                        DocumentSigner1, DocumentValidator1].
 -threads <arg>         Number of threads requesting time stamps.
 -timelimit <arg>       Optional. Only run for the specified time (in
                        milliseconds).
 -tsaurl <arg>          URL to timestamp worker to use.
 -userprefix <arg>      Prefix for usernames.
 -usersuffixmax <arg>   Highest suffix for usernames in form of an integer
                        value (inclusive).
 -usersuffixmin <arg>   Lowest suffix for usernames in form of an integer
                        value (inclusive).
 -warmuptime <arg>      Don't count number of signings and response times
                        until after this time (in milliseconds). Default=0
                        (no warmup time).
 -worker <arg>          Worker name or ID to use (with the
                        DocumentSigner/Validator1 test suites).
 -workerurl <arg>       URL to worker servlet (for the
                        DocumentSigner/Validator1 test suites).

Sample usages:
a) stresstest -testsuite TimeStamp1 -threads 4 -tsaurl
http://localhost:8080/signserver/tsa?workerId=1
b) stresstest -testsuite TimeStamp1 -threads 4 -maxwaittime 100
-statoutputdir ./statistics/ -tsaurl
http://localhost:8080/signserver/tsa?workerId=1
c) stresstest -testsuite DocumentSigner1 -threads 4 -processurl
http://localhost:8080/signserver/process -worker PDFSigner -infile
test.pdf
d) stresstest -testsuite DocumentSigner1 -threads 4 -processurl
http://localhost:8080/signserver/process -worker XMLSigner -data "<root/>"
e) stresstest -testsuite DocumentSigner1 -threads 4 -processurl
http://localhost:8080/signserver/process -worker XMLSigner -data "<root/>"
-userprefix user -usersuffixmin 1 -usersuffixmax 50
f) stresstest -testsuite DocumentValidator1 -threads 4 -processurl
http://localhost:8080/signserver/process -worker DemoXMLValidator -infile
signed.xml

When finished (after time limit expires, or process is stopped using control+c) a statistic overview is printed containing number of signings, and statistics (average, minimum, and maximum) for response times.