Szenario 8.3: Zeitstempel (Timestamp)

Kategorie: Digitale Signaturen
Komplexität: ⭐⭐⭐ (Mittel)
Voraussetzungen: Signatur vorhanden, TSA-Zugang
Geschätzte Zeit: 10-15 Minuten


Beschreibung

Dieses Szenario beschreibt das Hinzufügen von Zeitstempeln zu digitalen Signaturen (RFC 3161). Zeitstempel beweisen, dass eine Signatur zu einem bestimmten Zeitpunkt existierte und ermöglichen:

  • Langzeit-Validierung - Signatur bleibt nach Zertifikatsablauf gültig
  • Nachweisbarkeit - Exakter Signaturzeitpunkt belegt
  • Compliance - Für eIDAS qualifizierte Signaturen erforderlich

Konzept:

  • Signatur-Hash wird an Timestamp Authority (TSA) gesendet
  • TSA signiert Hash mit Zeitangabe
  • Timestamp-Token wird an Signatur angehängt

Workflow

flowchart LR SIG[Signatur] --> HASH[Hash der Signatur] HASH --> REQ[TSA Request] REQ -->|HTTP POST| TSA[Timestamp Authority] TSA --> TOKEN[Timestamp Token] TOKEN --> ATTACH[An Signatur anhängen] ATTACH --> OUTPUT[Signatur mit Timestamp] style TSA fill:#e8f5e9 style OUTPUT fill:#e3f2fd


Code-Beispiel: RFC 3161 Timestamp Request

using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
 
using var ctx = PqCryptoContext.Initialize();
 
// Signatur laden
var signature = File.ReadAllBytes("document.sig");
 
// Hash der Signatur berechnen
var signatureHash = SHA256.HashData(signature);
 
// Timestamp Request erstellen (RFC 3161)
var tsRequest = new Rfc3161TimestampRequest(
    messageHash: signatureHash,
    hashAlgorithm: HashAlgorithmName.SHA256,
    requestedPolicyId: null,  // Default Policy
    nonce: GenerateNonce(),
    requestSignerCertificates: true
);
 
// Request an TSA senden
using var http = new HttpClient();
var content = new ByteArrayContent(tsRequest.Encode());
content.Headers.ContentType = new MediaTypeHeaderValue("application/timestamp-query");
 
var response = await http.PostAsync("http://timestamp.digicert.com", content);
var tsResponseBytes = await response.Content.ReadAsByteArrayAsync();
 
// Response parsen
var tsResponse = Rfc3161TimestampToken.Decode(tsResponseBytes, out int bytesConsumed);
 
// Validieren
if (!tsResponse.VerifySignatureForHash(signatureHash, HashAlgorithmName.SHA256, out var signerCert, null))
{
    throw new CryptographicException("Timestamp-Signatur ungültig");
}
 
// Token speichern
File.WriteAllBytes("document.sig.tst", tsResponse.AsSignedCms().Encode());
 
Console.WriteLine("Timestamp erhalten:");
Console.WriteLine($"  Zeit: {tsResponse.TokenInfo.Timestamp}");
Console.WriteLine($"  TSA: {signerCert.Subject}");
Console.WriteLine($"  Policy: {tsResponse.TokenInfo.PolicyId}");

Code-Beispiel: Timestamp zu CMS-Signatur hinzufügen

public class TimestampService
{
    private readonly string _tsaUrl;
    private readonly HttpClient _http;
 
    public TimestampService(string tsaUrl)
    {
        _tsaUrl = tsaUrl;
        _http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
    }
 
    public async Task<SignedCms> AddTimestamp(SignedCms signedCms)
    {
        foreach (var signerInfo in signedCms.SignerInfos)
        {
            // Signatur-Hash für Timestamp
            var signatureHash = SHA256.HashData(signerInfo.GetSignature());
 
            // Timestamp Request
            var tsRequest = new Rfc3161TimestampRequest(
                signatureHash,
                HashAlgorithmName.SHA256,
                requestedPolicyId: null,
                nonce: GenerateNonce(),
                requestSignerCertificates: true
            );
 
            // An TSA senden
            var content = new ByteArrayContent(tsRequest.Encode());
            content.Headers.ContentType = new MediaTypeHeaderValue("application/timestamp-query");
 
            var response = await _http.PostAsync(_tsaUrl, content);
            response.EnsureSuccessStatusCode();
 
            var tsResponseBytes = await response.Content.ReadAsByteArrayAsync();
            var tsToken = Rfc3161TimestampToken.Decode(tsResponseBytes, out _);
 
            // Als Unsigned Attribute hinzufügen
            var timestampAttr = new AsnEncodedData(
                new Oid("1.2.840.113549.1.9.16.2.14"),  // id-aa-timeStampToken
                tsToken.AsSignedCms().Encode()
            );
 
            signerInfo.AddUnsignedAttribute(timestampAttr);
        }
 
        return signedCms;
    }
 
    private byte[] GenerateNonce()
    {
        var nonce = new byte[8];
        RandomNumberGenerator.Fill(nonce);
        return nonce;
    }
}

Timestamp-Token validieren

public class TimestampValidator
{
    public TimestampValidationResult Validate(
        byte[] timestampToken,
        byte[] originalSignature,
        X509Certificate2Collection? trustedTsaCerts = null)
    {
        var result = new TimestampValidationResult();
 
        try
        {
            var tsToken = Rfc3161TimestampToken.Decode(timestampToken, out _);
            result.Timestamp = tsToken.TokenInfo.Timestamp;
            result.PolicyId = tsToken.TokenInfo.PolicyId?.Value;
 
            // Hash prüfen
            var expectedHash = SHA256.HashData(originalSignature);
            if (!tsToken.TokenInfo.GetMessageHash().ReadOnlySpan.SequenceEqual(expectedHash))
            {
                result.IsValid = false;
                result.Error = "Hash stimmt nicht überein";
                return result;
            }
 
            // Signatur prüfen
            if (!tsToken.VerifySignatureForHash(expectedHash, HashAlgorithmName.SHA256, out var tsaCert, trustedTsaCerts))
            {
                result.IsValid = false;
                result.Error = "TSA-Signatur ungültig";
                return result;
            }
 
            result.TsaCertificate = tsaCert;
 
            // TSA-Zertifikat-Chain prüfen
            var chain = new X509Chain();
            chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
 
            if (trustedTsaCerts != null)
            {
                chain.ChainPolicy.CustomTrustStore.AddRange(trustedTsaCerts);
                chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
            }
 
            result.IsValid = chain.Build(tsaCert);
            if (!result.IsValid)
            {
                result.Error = "TSA-Zertifikatskette ungültig";
            }
        }
        catch (Exception ex)
        {
            result.IsValid = false;
            result.Error = ex.Message;
        }
 
        return result;
    }
}
 
public class TimestampValidationResult
{
    public bool IsValid { get; set; }
    public DateTimeOffset? Timestamp { get; set; }
    public string? PolicyId { get; set; }
    public X509Certificate2? TsaCertificate { get; set; }
    public string? Error { get; set; }
}

Timestamp Authorities (TSAs)

Anbieter URL Qualifiziert (eIDAS)
DigiCert http://timestamp.digicert.com Nein
Sectigo http://timestamp.sectigo.com Nein
GlobalSign http://timestamp.globalsign.com Nein
D-TRUST http://zeitstempel.2.1.3.bundesdruckerei.de Ja
SwissSign http://tsa.swisscom.com/tsa Ja
Bundesdruckerei http://ts.2.bundesdruckerei.de Ja

eIDAS: Für qualifizierte elektronische Signaturen ist ein qualifizierter TSA erforderlich.


Langzeit-Archivierung (LTA)

public class LongTermArchive
{
    public async Task ExtendValidation(
        SignedCms signedCms,
        string tsaUrl,
        X509Certificate2Collection validationData)
    {
        // 1. Validierungsdaten einbetten (Zertifikate, CRLs, OCSP)
        AddValidationData(signedCms, validationData);
 
        // 2. Archive Timestamp hinzufügen
        var timestampService = new TimestampService(tsaUrl);
        await timestampService.AddArchiveTimestamp(signedCms);
 
        // Dieser Prozess kann periodisch wiederholt werden,
        // um die Signatur langfristig validierbar zu halten
    }
}

Branchenspezifische Anforderungen

Anwendung Timestamp erforderlich? Qualifiziert?
Code Signing Pflicht Nein
eIDAS QES Pflicht Ja
PDF/A-Archivierung Empfohlen Je nach Anforderung
S/MIME Optional Nein
Healthcare Pflicht Je nach Land

Verwandte Szenarien

Beziehung Szenario Beschreibung
Voraussetzung 8.1 Dokument signieren Signatur erstellen
Voraussetzung 8.2 Code signieren Code-Signatur
Nächster Schritt 8.4 Signatur verifizieren Validierung

« ← 8.2 Code signieren | ↑ Signaturen-Übersicht | 8.4 Signatur verifizieren → »


Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional

Zuletzt geändert: den 29.01.2026 um 15:13