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.
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.
source to share
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.
source to share