The PDF Document API allows you to use a timestamp for the signature. The following code sample project shows how to create a custom timestamp client based on the Bouncy Castle C# API.
Files to Review
More Examples
- Use a Custom Signer Class to Apply Signatures to a PDF Document
- Use the Azure Key Vault API to Sign a PDF document
- Validate Document Signatures
Does this example address your development requirements/objectives?
(you will be redirected to DevExpress.com to submit your response)
Example Code
C#using DevExpress.Pdf;
using DevExpress.Office.DigitalSignatures;
using DevExpress.Office.Tsp;
using Org.BouncyCastle.Crypto.Digests;
using System;
using System.IO;
using System.Diagnostics;
namespace CustomTsaClient
{
static class Program
{
static void Main(string[] args)
{
using (var signer = new PdfDocumentSigner(@"Document.pdf"))
{
//Create a custom timestamp client instance:
ITsaClient tsaClient = new BouncyCastleTsaClient(new Uri(@"https://freetsa.org/tsr"), new Sha256Digest(), new System.Net.Http.HttpClient());
//Create a PKCS#7 signature:
Pkcs7Signer pkcs7Signature = new Pkcs7Signer(@"testcert.pfx", "123", HashAlgorithmType.SHA256, tsaClient);
//Apply the signature to the form field:
var signatureBuilder = new PdfSignatureBuilder(pkcs7Signature, "Sign");
//Specify image data and signer information:
signatureBuilder.SetImageData(File.ReadAllBytes("JaneCooper.jpg"));
signatureBuilder.Location = "United Kingdom";
//Sign and save the document:
signer.SaveDocument("SignedDocument.pdf", signatureBuilder);
}
Process.Start(new ProcessStartInfo("SignedDocument.pdf") { UseShellExecute = true }); return;
}
}
}
C#using DevExpress.Office.Tsp;
using Org.BouncyCastle.Asn1.Cmp;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Tsp;
using System;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
public class BouncyCastleTsaClient : ITsaClient
{
readonly Uri tsaServerURI;
readonly IDigest hashCalculator;
readonly HttpClient httpClient;
public BouncyCastleTsaClient(Uri tsaServerURI, IDigest hashCalculator, HttpClient httpClient)
{
this.tsaServerURI = tsaServerURI;
this.hashCalculator = hashCalculator;
this.httpClient = httpClient;
}
byte[] CalculateDigest(Stream stream)
{
byte[] buffer = new byte[81920];
hashCalculator.Reset();
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
hashCalculator.BlockUpdate(buffer, 0, bytesRead);
byte[] result = new byte[hashCalculator.GetDigestSize()];
hashCalculator.DoFinal(result, 0);
return result;
}
public byte[] GenerateTimeStamp(Stream stream)
{
//Generate a timestamp request:
TimeStampRequestGenerator tsqGenerator = new TimeStampRequestGenerator();
tsqGenerator.SetCertReq(true);
BigInteger nonce;
using (RandomNumberGenerator generator = RandomNumberGenerator.Create())
{
byte[] nonceValue = new byte[10];
generator.GetBytes(nonceValue);
nonce = new BigInteger(nonceValue);
}
string algorithmOid = DigestUtilities.GetObjectIdentifier(hashCalculator.AlgorithmName).Id;
TimeStampRequest request = tsqGenerator.Generate(algorithmOid, CalculateDigest(stream), nonce);
byte[] requestBytes = request.GetEncoded();
//Send the request to a server:
HttpClient httpClient = new HttpClient();
using var content = new ByteArrayContent(requestBytes);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/timestamp-query");
//Get a responce from the server:
using HttpResponseMessage responseMessage = httpClient.PostAsync(tsaServerURI, content).Result;
if (!responseMessage.IsSuccessStatusCode)
{
throw new Exception($"TimeStamp request to the \"{tsaServerURI}\" failed with status code {responseMessage.StatusCode}.");
}
byte[] responseBytes = responseMessage.Content.ReadAsByteArrayAsync().Result;
//Read the response:
TimeStampResponse response = new TimeStampResponse(responseBytes);
response.Validate(request);
PkiFailureInfo failure = response.GetFailInfo();
//Throw an exception if the responce returned an error:
if (failure != null)
throw new Exception($"TimeStamp request to the \"{tsaServerURI}\" failed.");
TimeStampToken token = response.TimeStampToken;
//Throw an exception if the responce doesn't contain the timestamp:
if (token == null)
throw new Exception($"TimeStamp request to the \"{tsaServerURI}\" failed.");
return token.GetEncoded();
}
public byte[] GenerateTimeStamp(byte[] digest, string digestAlgorithmOID)
{
//Generate a timestamp request:
TimeStampRequestGenerator tsqGenerator = new TimeStampRequestGenerator();
tsqGenerator.SetCertReq(true);
BigInteger nonce;
using (RandomNumberGenerator generator = RandomNumberGenerator.Create())
{
byte[] nonceValue = new byte[10];
generator.GetBytes(nonceValue);
nonce = new BigInteger(nonceValue);
}
string algorithmOid = DigestUtilities.GetObjectIdentifier(hashCalculator.AlgorithmName).Id;
TimeStampRequest request = tsqGenerator.Generate(algorithmOid, digest, nonce);
byte[] requestBytes = request.GetEncoded();
//Send the request to a server:
HttpClient httpClient = new HttpClient();
using var content = new ByteArrayContent(requestBytes);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/timestamp-query");
//Get a responce from the server:
using HttpResponseMessage responseMessage = httpClient.PostAsync(tsaServerURI, content).Result;
if (!responseMessage.IsSuccessStatusCode)
{
throw new Exception($"TimeStamp request to the \"{tsaServerURI}\" failed with status code {responseMessage.StatusCode}.");
}
byte[] responseBytes = responseMessage.Content.ReadAsByteArrayAsync().Result;
//Read the response:
TimeStampResponse response = new TimeStampResponse(responseBytes);
response.Validate(request);
PkiFailureInfo failure = response.GetFailInfo();
//Throw an exception if the responce returned an error:
if (failure != null)
throw new Exception($"TimeStamp request to the \"{tsaServerURI}\" failed.");
TimeStampToken token = response.TimeStampToken;
//Throw an exception if the responce doesn't contain the timestamp:
if (token == null)
throw new Exception($"TimeStamp request to the \"{tsaServerURI}\" failed.");
return token.GetEncoded();
}
}