PDFBox 1.8 - Signing a PDF document using TSA (Autodesk Time Stamp Autority)

I am able to sign a PDF to PDF using PDFBOX 1.8.5 thanks to this excellent sample provided in PDFBOX.

https://github.com/apache/pdfbox/blob/1.8/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateSignature.java

When signing this example, use the local machine date / time (line 175):

// signature date required for a valid signature signature.setSignDate (Calendar.getInstance ());

This means that Acrobat Reader will not trust the signing date as if it were done using an external time stamp control (TSA).

Does anyone know how to use external TSA with PDFBOX?

Thank.

+3


source to share


1 answer


The CreateSignature

PDFBox sample version was enhanced in development version 2.0.0-SNAPSHOT, and it is also optional to include a timestamp from some TSAs.

The main difference is that after the CMS signature is created in the signature sign(InputStream)

container, the CMS is expanded in an additional method signTimeStamps(CMSSignedData)

to also have a signature timestamp:

public byte[] sign(InputStream content) throws IOException
{
    ...
        CMSSignedData signedData = gen.generate(processable, false);
        // vvv Additional call
        if (tsaClient != null)
        {
            signedData = signTimeStamps(signedData);
        }
        // ^^^ Additional call
        return signedData.getEncoded();
    ...
}

// vvv Additional helper methods
private CMSSignedData signTimeStamps(CMSSignedData signedData)
        throws IOException, TSPException
{
    SignerInformationStore signerStore = signedData.getSignerInfos();
    List<SignerInformation> newSigners = new ArrayList<SignerInformation>();

    for (SignerInformation signer : (Collection<SignerInformation>)signerStore.getSigners())
    {
        newSigners.add(signTimeStamp(signer));
    }

    return CMSSignedData.replaceSigners(signedData, new SignerInformationStore(newSigners));
}

private SignerInformation signTimeStamp(SignerInformation signer)
        throws IOException, TSPException
{
    AttributeTable unsignedAttributes = signer.getUnsignedAttributes();

    ASN1EncodableVector vector = new ASN1EncodableVector();
    if (unsignedAttributes != null)
    {
        vector = unsignedAttributes.toASN1EncodableVector();
    }

    byte[] token = tsaClient.getTimeStampToken(signer.getSignature());
    ASN1ObjectIdentifier oid = PKCSObjectIdentifiers.id_aa_signatureTimeStampToken;
    ASN1Encodable signatureTimeStamp = new Attribute(oid, new DERSet(byteToASN1Object(token)));

    vector.add(signatureTimeStamp);
    Attributes signedAttributes = new Attributes(vector);

    SignerInformation newSigner = SignerInformation.replaceUnsignedAttributes(
            signer, new AttributeTable(signedAttributes));

    if (newSigner == null)
    {
        return signer;
    }

    return newSigner;
}

private ASN1Object byteToASN1Object(byte[] data) throws IOException
{
    ASN1InputStream in = new ASN1InputStream(data);
    try
    {
        return in.readObject();
    }
    finally
    {
        in.close();
    }
}
// ^^^ Additional helper methods

      

( CreateSignature.java , version 2.0.0-SNAPSHOT)



Here tsaClient

is a new member variable CreateSignature

containing an instance tsaClient

interacting with the external TSA in question:

/**
 * Time Stamping Authority (TSA) Client [RFC 3161].
 * @author Vakhtang Koroghlishvili
 * @author John Hewson
 */
public class TSAClient
{
    private static final Log log = LogFactory.getLog(TSAClient.class);

    private final URL url;
    private final String username;
    private final String password;
    private final MessageDigest digest;

    public TSAClient(URL url, String username, String password, MessageDigest digest)
    {
        this.url = url;
        this.username = username;
        this.password = password;
        this.digest = digest;
    }

    public byte[] getTimeStampToken(byte[] messageImprint) throws IOException
    {
        digest.reset();
        byte[] hash = digest.digest(messageImprint);

        // 32-bit cryptographic nonce
        SecureRandom random = new SecureRandom();
        int nonce = random.nextInt();

        // generate TSA request
        TimeStampRequestGenerator tsaGenerator = new TimeStampRequestGenerator();
        tsaGenerator.setCertReq(true);
        ASN1ObjectIdentifier oid = getHashObjectIdentifier(digest.getAlgorithm());
        TimeStampRequest request = tsaGenerator.generate(oid, hash, BigInteger.valueOf(nonce));

        // get TSA response
        byte[] tsaResponse = getTSAResponse(request.getEncoded());

        TimeStampResponse response;
        try
        {
            response = new TimeStampResponse(tsaResponse);
            response.validate(request);
        }
        catch (TSPException e)
        {
            throw new IOException(e);
        }

        TimeStampToken token = response.getTimeStampToken();
        if (token == null)
        {
            throw new IOException("Response does not have a time stamp token");
        }

        return token.getEncoded();
    }

    // gets response data for the given encoded TimeStampRequest data
    // throws IOException if a connection to the TSA cannot be established
    private byte[] getTSAResponse(byte[] request) throws IOException
    {
        log.debug("Opening connection to TSA server");

        // todo: support proxy servers
        URLConnection connection = url.openConnection();
        connection.setDoOutput(true);
        connection.setDoInput(true);
        connection.setRequestProperty("Content-Type", "application/timestamp-query");

        log.debug("Established connection to TSA server");

        if (username != null && password != null)
        {
            if (!username.isEmpty() && !password.isEmpty())
            {
                connection.setRequestProperty(username, password);
            }
        }

        // read response
        OutputStream output = null;
        try
        {
            output = connection.getOutputStream();
            output.write(request);
        }
        finally
        {
            IOUtils.closeQuietly(output);
        }

        log.debug("Waiting for response from TSA server");

        InputStream input = null;
        byte[] response;
        try
        {
            input = connection.getInputStream();
            response = IOUtils.toByteArray(input);
        }
        finally
        {
            IOUtils.closeQuietly(input);
        }

        log.debug("Received response from TSA server");

        return response;
    }

    // returns the ASN.1 OID of the given hash algorithm
    private ASN1ObjectIdentifier getHashObjectIdentifier(String algorithm)
    {
        // TODO can bouncy castle or Java provide this information?
        if (algorithm.equals("MD2"))
        {
            return new ASN1ObjectIdentifier("1.2.840.113549.2.2");
        }
        else if (algorithm.equals("MD5"))
        {
            return new ASN1ObjectIdentifier("1.2.840.113549.2.5");
        }
        else if (algorithm.equals("SHA-1"))
        {
            return new ASN1ObjectIdentifier("1.3.14.3.2.26");
        }
        else if (algorithm.equals("SHA-224"))
        {
            return new ASN1ObjectIdentifier("2.16.840.1.101.3.4.2.4");
        }
        else if (algorithm.equals("SHA-256"))
        {
            return new ASN1ObjectIdentifier("2.16.840.1.101.3.4.2.1");
        }
        else if (algorithm.equals("SHA-394"))
        {
            return new ASN1ObjectIdentifier("2.16.840.1.101.3.4.2.2");
        }
        else if (algorithm.equals("SHA-512"))
        {
            return new ASN1ObjectIdentifier("2.16.840.1.101.3.4.2.3");
        }
        else
        {
            return new ASN1ObjectIdentifier(algorithm);
        }
    }
}

      

( TSAClient.java , version 2.0.0-SNAPSHOT)

Since these additions only depend on the version of BouncyCastle used, and not the PDFBox code, this code should also be easily fallback for use with PDFBox 1.8.x.

+3


source







All Articles