Qualifed Timestamping in C# .NET (Native Implementation)
Learn how to request and verify qualified RFC 3161 timestamps in .NET 6+ / .NET 8 / .NET 9 using the native Rfc3161TimestampRequest class without third-party dependencies like BouncyCastle.
- .NET 5 / .NET 6 / .NET 8 / .NET 9 SDK installed.
- Namespace requirement:
System.Security.Cryptography.Pkcs(built-in .NET assembly). - TSA Certificate Chain (for offline verification): Download TSAChain.zip.
Step 1: Request a Timestamp Token (.tsr)
Compute the SHA-256 hash of your file, build an Rfc3161TimestampRequest, and send it via HTTP POST with the application/timestamp-query content header.
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
using System.Threading.Tasks;
public class TimestampClient
{
public static async Task RequestTimestampAsync(string filePath, string outputTsrPath, string tsaUrl)
{
// 1. Compute SHA-256 hash of the target file
byte[] fileBytes = await File.ReadAllBytesAsync(filePath);
byte[] hash = SHA256.HashData(fileBytes);
// 2. Build RFC 3161 timestamp request
Rfc3161TimestampRequest request = Rfc3161TimestampRequest.CreateFromHash(
hash,
HashAlgorithmName.SHA256,
requestSignerCertificates: true
);
// 3. Send HTTP POST request to the TSA server
using HttpClient client = new HttpClient();
ByteArrayContent content = new ByteArrayContent(request.Encode());
content.Headers.ContentType = new MediaTypeHeaderValue("application/timestamp-query");
HttpResponseMessage response = await client.PostAsync(tsaUrl, content);
response.EnsureSuccessStatusCode();
// 4. Save timestamp response token (.tsr)
byte[] tsrBytes = await response.Content.ReadAsByteArrayAsync();
await File.WriteAllBytesAsync(outputTsrPath, tsrBytes);
Console.WriteLine($"[SUCCESS] Timestamp saved to: {outputTsrPath}");
}
}
Step 2: Verify the Timestamp Token (.tsr) in C#
Validate the received .tsr response against the original file hash and ensure the TSA signing certificate matches your trusted certificate authority chain.
using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
public class TimestampVerifier
{
public static bool VerifyTimestamp(string filePath, string tsrPath)
{
byte[] fileBytes = File.ReadAllBytes(filePath);
byte[] hash = SHA256.HashData(fileBytes);
byte[] tsrBytes = File.ReadAllBytes(tsrPath);
// Parse RFC 3161 Response
Rfc3161TimestampToken token = Rfc3161TimestampToken.ReadIfTimestampToken(
tsrBytes,
out _,
out _
);
if (token == null)
{
Console.WriteLine("[ERROR] Invalid timestamp token format.");
return false;
}
// Verify hash integrity and timestamp signature
bool isHashValid = token.VerifyHash(hash, HashAlgorithmName.SHA256, out _);
if (isHashValid)
{
DateTimeOffset timestampTime = token.AsSignedCms().SignerInfos[0].SigningTime;
Console.WriteLine($"[SUCCESS] Timestamp is valid! Signed at (UTC): {timestampTime.UtcDateTime}");
return true;
}
Console.WriteLine("[ERROR] Hash mismatch or invalid signature.");
return false;
}
}
Complete Program Execution
Run the full workflow directly in your .NET console application:
string fileToSign = "document.pdf";
string tsrOutput = "document.pdf.tsr";
string tsaServerUrl = "https://tsaexample.com/myserver.aspx";
// 1. Request Timestamp
await TimestampClient.RequestTimestampAsync(fileToSign, tsrOutput, tsaServerUrl);
// 2. Verify Timestamp
bool isValid = TimestampVerifier.VerifyTimestamp(fileToSign, tsrOutput);
Console.WriteLine($"Verification Result: {isValid}");
Why use native .NET over BouncyCastle? Starting with .NET 5,
System.Security.Cryptography.Pkcs provides built-in support for RFC 3161 timestamping, avoiding third-party dependency vulnerabilities, reducing library bloat, and offering better memory efficiency via Span<T> and ReadOnlySpan<T>.