Scenarij 8.3: Časovni žig (Timestamp)

Kategorija: Digitalni podpisi
Kompleksnost: ⭐⭐⭐ (Srednja)
Predpogoji: Podpis na voljo, dostop do TSA
Predviden čas: 10-15 minut


Opis

Ta scenarij opisuje dodajanje časovnih žigov digitalnim podpisom (RFC 3161). Časovni žigi dokazujejo, da je podpis obstajal v določenem trenutku in omogočajo:

  • Dolgoročna validacija - Podpis ostane veljaven po poteku certifikata
  • Dokazljivost - Natančen čas podpisa je dokazan
  • Skladnost - Za kvalificirane podpise eIDAS obvezno

Koncept:

  • Zgoščena vrednost podpisa se pošlje organu za časovne žige (TSA)
  • TSA podpiše zgoščeno vrednost s časovno oznako
  • Žeton časovnega žiga se pripne podpisu

Potek dela

flowchart LR SIG[Podpis] --> HASH[Zgoščena vrednost podpisa] HASH --> REQ[TSA zahteva] REQ -->|HTTP POST| TSA[Organ za časovne žige] TSA --> TOKEN[Žeton časovnega žiga] TOKEN --> ATTACH[Pripenjanje k podpisu] ATTACH --> OUTPUT[Podpis s časovnim žigom] style TSA fill:#e8f5e9 style OUTPUT fill:#e3f2fd


Primer kode: RFC 3161 zahteva za časovni žig

using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
 
using var ctx = PqCryptoContext.Initialize();
 
// Nalaganje podpisa
var signature = File.ReadAllBytes("document.sig");
 
// Izračun zgoščene vrednosti podpisa
var signatureHash = SHA256.HashData(signature);
 
// Ustvarjanje zahteve za časovni žig (RFC 3161)
var tsRequest = new Rfc3161TimestampRequest(
    messageHash: signatureHash,
    hashAlgorithm: HashAlgorithmName.SHA256,
    requestedPolicyId: null,  // Privzeta politika
    nonce: GenerateNonce(),
    requestSignerCertificates: true
);
 
// Pošiljanje zahteve TSA
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();
 
// Razčlenjevanje odgovora
var tsResponse = Rfc3161TimestampToken.Decode(tsResponseBytes, out int bytesConsumed);
 
// Validacija
if (!tsResponse.VerifySignatureForHash(signatureHash, HashAlgorithmName.SHA256, out var signerCert, null))
{
    throw new CryptographicException("Podpis časovnega žiga neveljaven");
}
 
// Shranjevanje žetona
File.WriteAllBytes("document.sig.tst", tsResponse.AsSignedCms().Encode());
 
Console.WriteLine("Časovni žig prejet:");
Console.WriteLine($"  Čas: {tsResponse.TokenInfo.Timestamp}");
Console.WriteLine($"  TSA: {signerCert.Subject}");
Console.WriteLine($"  Politika: {tsResponse.TokenInfo.PolicyId}");

Primer kode: Dodajanje časovnega žiga CMS podpisu

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)
        {
            // Zgoščena vrednost podpisa za časovni žig
            var signatureHash = SHA256.HashData(signerInfo.GetSignature());
 
            // Zahteva za časovni žig
            var tsRequest = new Rfc3161TimestampRequest(
                signatureHash,
                HashAlgorithmName.SHA256,
                requestedPolicyId: null,
                nonce: GenerateNonce(),
                requestSignerCertificates: true
            );
 
            // Pošiljanje TSA
            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 _);
 
            // Dodajanje kot nepodpisan atribut
            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;
    }
}

Validacija žetona časovnega žiga

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;
 
            // Preverjanje zgoščene vrednosti
            var expectedHash = SHA256.HashData(originalSignature);
            if (!tsToken.TokenInfo.GetMessageHash().ReadOnlySpan.SequenceEqual(expectedHash))
            {
                result.IsValid = false;
                result.Error = "Zgoščena vrednost se ne ujema";
                return result;
            }
 
            // Preverjanje podpisa
            if (!tsToken.VerifySignatureForHash(expectedHash, HashAlgorithmName.SHA256, out var tsaCert, trustedTsaCerts))
            {
                result.IsValid = false;
                result.Error = "TSA podpis neveljaven";
                return result;
            }
 
            result.TsaCertificate = tsaCert;
 
            // Preverjanje verige certifikatov TSA
            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 = "Veriga certifikatov TSA neveljavna";
            }
        }
        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; }
}

Organi za časovne žige (TSA)

eIDAS: Za kvalificirane elektronske podpise je potreben kvalificiran TSA.


Dolgoročno arhiviranje (LTA)

public class LongTermArchive
{
    public async Task ExtendValidation(
        SignedCms signedCms,
        string tsaUrl,
        X509Certificate2Collection validationData)
    {
        // 1. Vgraditev validacijskih podatkov (certifikati, CRL-ji, OCSP)
        AddValidationData(signedCms, validationData);
 
        // 2. Dodajanje arhivskega časovnega žiga
        var timestampService = new TimestampService(tsaUrl);
        await timestampService.AddArchiveTimestamp(signedCms);
 
        // Ta proces se lahko periodično ponavlja,
        // da podpis ostane dolgoročno preverljiv
    }
}

Panožne zahteve

Uporaba Časovni žig obvezen? Kvalificiran?
Podpisovanje kode Obvezno Ne
eIDAS QES Obvezno Da
PDF/A arhiviranje Priporočeno Odvisno od zahtev
S/MIME Opcijsko Ne
Zdravstvo Obvezno Odvisno od države

Povezani scenariji

Povezava Scenarij Opis
Predpogoj 8.1 Podpisovanje dokumentov Ustvarjanje podpisa
Predpogoj 8.2 Podpisovanje kode Podpis kode
Naslednji korak 8.4 Verifikacija podpisa Validacija

« ← 8.2 Podpisovanje kode | ↑ Pregled podpisov | 8.4 Verifikacija podpisa → »


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

Zuletzt geändert: dne 30.01.2026 ob 06:42