~~NOTOC~~ ====== Scenarij 8.3: Vremenska oznaka (Timestamp) ====== **Kategorija:** [[.:start|Digitalni potpisi]] \\ **Složenost:** ⭐⭐⭐ (Srednja) \\ **Preduvjeti:** Postojeći potpis, pristup TSA \\ **Procijenjeno vrijeme:** 10-15 minuta ---- ===== Opis ===== Ovaj scenarij opisuje **dodavanje vremenskih oznaka** digitalnim potpisima (RFC 3161). Vremenske oznake dokazuju da je potpis postojao u određenom trenutku i omogućuju: * **Dugoročnu validaciju** - Potpis ostaje valjan nakon isteka certifikata * **Dokazivost** - Egzaktan trenutak potpisa je dokumentiran * **Usklađenost** - Obavezno za eIDAS kvalificirane potpise **Koncept:** * Hash potpisa se šalje Timestamp Authority (TSA) * TSA potpisuje hash s vremenskom oznakom * Timestamp-Token se prilaže potpisu ---- ===== Tijek rada ===== flowchart LR SIG[Potpis] --> HASH[Hash potpisa] HASH --> REQ[TSA Zahtjev] REQ -->|HTTP POST| TSA[Timestamp Authority] TSA --> TOKEN[Timestamp Token] TOKEN --> ATTACH[Prilaganje potpisu] ATTACH --> OUTPUT[Potpis s vremenskom oznakom] style TSA fill:#e8f5e9 style OUTPUT fill:#e3f2fd ---- ===== Primjer koda: 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(); // Učitavanje potpisa var signature = File.ReadAllBytes("document.sig"); // Izračun hasha potpisa var signatureHash = SHA256.HashData(signature); // Kreiranje Timestamp Requesta (RFC 3161) var tsRequest = new Rfc3161TimestampRequest( messageHash: signatureHash, hashAlgorithm: HashAlgorithmName.SHA256, requestedPolicyId: null, // Default Policy nonce: GenerateNonce(), requestSignerCertificates: true ); // Slanje zahtjeva TSA-i 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(); // Parsiranje odgovora var tsResponse = Rfc3161TimestampToken.Decode(tsResponseBytes, out int bytesConsumed); // Validacija if (!tsResponse.VerifySignatureForHash(signatureHash, HashAlgorithmName.SHA256, out var signerCert, null)) { throw new CryptographicException("Timestamp potpis nije valjan"); } // Spremanje tokena File.WriteAllBytes("document.sig.tst", tsResponse.AsSignedCms().Encode()); Console.WriteLine("Vremenska oznaka primljena:"); Console.WriteLine($" Vrijeme: {tsResponse.TokenInfo.Timestamp}"); Console.WriteLine($" TSA: {signerCert.Subject}"); Console.WriteLine($" Policy: {tsResponse.TokenInfo.PolicyId}"); ---- ===== Primjer koda: Dodavanje vremenske oznake CMS potpisu ===== 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 AddTimestamp(SignedCms signedCms) { foreach (var signerInfo in signedCms.SignerInfos) { // Hash potpisa za vremensku oznaku var signatureHash = SHA256.HashData(signerInfo.GetSignature()); // Timestamp Request var tsRequest = new Rfc3161TimestampRequest( signatureHash, HashAlgorithmName.SHA256, requestedPolicyId: null, nonce: GenerateNonce(), requestSignerCertificates: true ); // Slanje TSA-i 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 _); // Dodavanje kao Unsigned Attribute 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 Timestamp-Tokena ===== 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; // Provjera hasha var expectedHash = SHA256.HashData(originalSignature); if (!tsToken.TokenInfo.GetMessageHash().ReadOnlySpan.SequenceEqual(expectedHash)) { result.IsValid = false; result.Error = "Hash se ne podudara"; return result; } // Provjera potpisa if (!tsToken.VerifySignatureForHash(expectedHash, HashAlgorithmName.SHA256, out var tsaCert, trustedTsaCerts)) { result.IsValid = false; result.Error = "TSA potpis nije valjan"; return result; } result.TsaCertificate = tsaCert; // Provjera TSA lanca certifikata 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 lanac certifikata nije valjan"; } } 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) ===== ^ Pružatelj ^ URL ^ Kvalificirani (eIDAS) ^ | DigiCert | http://timestamp.digicert.com | Ne | | Sectigo | http://timestamp.sectigo.com | Ne | | GlobalSign | http://timestamp.globalsign.com | Ne | | D-TRUST | http://zeitstempel.2.1.3.bundesdruckerei.de | **Da** | | SwissSign | http://tsa.swisscom.com/tsa | **Da** | | Bundesdruckerei | http://ts.2.bundesdruckerei.de | **Da** | **eIDAS:** Za kvalificirane elektroničke potpise potreban je kvalificirani TSA. ---- ===== Dugoročno arhiviranje (LTA) ===== public class LongTermArchive { public async Task ExtendValidation( SignedCms signedCms, string tsaUrl, X509Certificate2Collection validationData) { // 1. Ugrađivanje validacijskih podataka (Certifikati, CRL-ovi, OCSP) AddValidationData(signedCms, validationData); // 2. Dodavanje Archive Timestampa var timestampService = new TimestampService(tsaUrl); await timestampService.AddArchiveTimestamp(signedCms); // Ovaj proces se može periodički ponavljati // kako bi potpis ostao dugoročno validiran } } ---- ===== Zahtjevi specifični za industriju ===== ^ Primjena ^ Vremenska oznaka obavezna? ^ Kvalificirana? ^ | **Code Signing** | Obavezno | Ne | | **eIDAS QES** | Obavezno | Da | | **PDF/A-Arhiviranje** | Preporučeno | Ovisno o zahtjevima | | **S/MIME** | Opcionalno | Ne | | **Zdravstvo** | Obavezno | Ovisno o zemlji | ---- ===== Povezani scenariji ===== ^ Povezanost ^ Scenarij ^ Opis ^ | **Preduvjet** | [[.:dokument_signieren|8.1 Potpisivanje dokumenata]] | Kreiranje potpisa | | **Preduvjet** | [[.:code_signieren|8.2 Potpisivanje koda]] | Code-Signature | | **Sljedeći korak** | [[.:signatur_verifizieren|8.4 Verifikacija potpisa]] | Validacija | ---- << [[.:code_signieren|← 8.2 Potpisivanje koda]] | [[.:start|↑ Pregled potpisa]] | [[.:signatur_verifizieren|8.4 Verifikacija potpisa →]] >> {{tag>scenarij potpis timestamp tsa rfc3161 dugoročno}} ---- //Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional//