💡 Pełny działający przykład dostępny na GitHubie:
digital-signing-certificate-validity-dotnet
Problem zgodności, którego nikt nie widzi, dopóki nie przyjdzie audytor
Usługa podpisywania działa przez trzy lata bez błędów. Dokumenty są wysyłane, odbiorcy je akceptują, w logach nie widać żadnych problemów. Wtedy walidator kontrahenta oznacza partię jako nieprawidłową, a dochodzenie wykazuje dwie przyczyny: podpisy zostały wykonane przy użyciu SHA‑1 oraz przez ostatnie cztery miesiące certyfikat był nieważny.
Oba błędy były ciche w momencie podpisywania. To właśnie zmienia GroupDocs.Signature 26.9.
Wymuszanie ważności certyfikatu jest nowym domyślnym zachowaniem dla .NET digital signing: certyfikat poza okresem ważności jest odrzucany, a nie używany. Wraz z nim przychodzą dwa towarzyszące elementy – SHA‑256 jako domyślny skrót PDF oraz LogLevel, który w końcu filtruje – i razem przenoszą trzy klasy błędów z odbiorcy z powrotem do nadawcy, gdzie nadal można je naprawić.
Dlaczego cichy sukces jest kosztownym wynikiem
Podpisywanie jest nietypowe, ponieważ strona popełniająca błąd nie jest tą, która go odkrywa. Nieprawidłowa faktura nie działa w twoim własnym systemie; nieprawidłowy podpis nie działa w systemie kogoś innego, tygodnie później, bez żadnej diagnostyki, którą mógłbyś odczytać.
Ta asymetria tłumaczy, dlaczego „API zwróciło sukces” nie jest tutaj użyteczną gwarancją. Stare domyślne ustawienia optymalizowały pod kątem nieprzerywania wywołującego, a koszt spadał na odbiorcę i ostatecznie na osobę, która musiała ponownie podpisać i wysłać setki dokumentów.
Zmiana 1: Wygasłe certyfikaty są odrzucane
Główna zmiana. Sign teraz rzuca GroupDocsSignatureException, gdy ważność certyfikatu zakończyła się lub jeszcze nie rozpoczęła, i nic nie jest zapisywane na dysku.
try
{
signature.Sign(outputPath, options);
return true;
}
catch (GroupDocsSignatureException ex)
{
Console.WriteLine($" Rejected: {ex.Message}");
return false;
}
Komunikat podaje nazwę certyfikatu i właściwość, która by to umożliwiła, więc operator czytający linię logu może podjąć działanie bez otwierania dokumentacji. Dla potoku, który aktualizuje się do 26.9 i zaczyna zgłaszać błędy, to prawie zawsze jest przyczyną – a prawidłową reakcją jest odnowienie, nie tłumienie.
Gdy naprawdę potrzebujesz starego zachowania, istnieje jedna właściwość:
var options = new DigitalSignOptions(certificate)
{
Password = certificatePassword,
AllowExpired = true
};
Dokument zostaje podpisany, a ostrzeżenie trafia do loggera. Walidatory nadal odrzucą wynik, ponieważ AllowExpired reguluje to, co biblioteka dopuszcza, a nie to, co certyfikat „warto”. Towarzysząca flaga AllowNotYetValid obejmuje drugą stronę okna i jest celowo niezależna: zezwolenie na wygasły certyfikat nie zezwala cicho na certyfikat z przyszłą datą.
Zmiana 2: SHA‑256 jako domyślne
Podpisy cyfrowe PDF są teraz zapisywane z użyciem SHA‑256 w formacie adbe.pkcs7.detached, którego obecnie oczekują walidatory. Wcześniejsze wersje używały SHA‑1.
var options = new DigitalSignOptions(certificate)
{
Password = certificatePassword,
HashAlgorithm = HashAlgorithm.Sha256,
Reason = "Approved",
Location = "Head office"
};
Ustawienie tej właściwości explicite jest potrzebne tylko wtedy, gdy trzeba pójść dalej – Sha384 lub Sha512, gdy polityka tego wymaga – lub aby pozostać przy Sha1 dla walidatora, który nie obsługuje nic innego. Znacznik czasu dodany do podpisu używa tego samego skrótu.
Weryfikacja zmieniła się w tej samej wersji i w tym samym kierunku: DigitalVerifyOptions bez kryteriów wcześniej był prawie nicnierobnym, a teraz wykonuje pełną kryptograficzną kontrolę, więc dokument zmieniony po podpisaniu jest zgłaszany jako nieprawidłowy.
Zmiana 3: LogLevel naprawdę filtruje
SignatureSettings od dawna akceptuje logger. Przed wersją 26.9 poziom był ignorowany, więc każda wiadomość docierała niezależnie i większość usług wyłączała logowanie, zamiast tonąć w śladach.
Przykład pokazuje różnicę w sposób mierzalny, podpisując ten sam dokument trzy razy przy użyciu liczącego loggera:
var levels = new Dictionary<string, LogLevel>
{
["None"] = LogLevel.None,
["Warning | Error"] = LogLevel.Warning | LogLevel.Error,
["All"] = LogLevel.All
};
None generuje zero komunikatów, Warning | Error zachowuje jedyne ostrzeżenie podniesione przez dozwolony wygasły certyfikat, a All dodaje ślad przy każdym kroku. Sam licznik loggera jest punktem integracji dla twojego własnego stosu:
public void Warning(string message)
{
Warnings++;
WarningMessages.Add(message);
}
Zaimplementuj te trzy metody w Serilog, NLog lub Application Insights, a diagnostyka biblioteki trafi tam, gdzie logi reszty twojej usługi.
Czy poziom logowania zmienia, jakie wyjątki otrzymuję?
Nie, i warto to wyraźnie zaznaczyć, ponieważ oba pojęcia wydają się powiązane. LogLevel filtruje to, co dociera do ILogger. Wyjątki są rzucane do twojego kodu niezależnie: wygasły certyfikat bez AllowExpired nadal rzuca przy LogLevel.None, a twój blok catch zachowuje się identycznie. Diagnostyka i przepływ sterowania to oddzielne kanały, co czyni bezpiecznym uruchamianie produkcji na Warning | Error.
Odrzucenie jest tańsze, niż się wydaje
Zastrzeżenie wobec twardego zatrzymania jest operacyjne: nocna partia, która kiedyś kończyła się pomyślnie, teraz kończy się o 02:00 i ktoś zostaje powiadomiony. To realny koszt i wciąż jest mniejszy. Odrzucona partia to jedno powiadomienie, jedno odnowienie i jedno ponowne uruchomienie, wszystko we własnych systemach. Partia podpisana wygasłym certyfikatem zostaje wykryta przez odbiorcę, co oznacza wątek wsparcia, ponowne wydanie każdego dotkniętego dokumentu i niezręczną rozmowę o tym, jak długo to trwało.
Przykład czyni awarię konkretną, a nie teoretyczną: podpisuje z zamiarem użycia wygasłego certyfikatu, łapie wyjątek i wypisuje komunikat, tak abyś mógł zobaczyć dokładnie, co znajdą się w twoich logach przed wdrożeniem w produkcji. Zalecam uruchomienie tej metody przeciwko własnemu magazynowi certyfikatów przed zaplanowaniem podbicia wersji.
Co zrobić przed aktualizacją
Trzy kontrole, w kolejności od najbardziej prawdopodobnych problemów.
Sprawdź daty wygaśnięcia certyfikatów we wszystkich ścieżkach podpisywania, w tym tych uruchamianych co miesiąc lub kwartał – to właśnie tam wygasły certyfikat ukrywa się najdłużej. Następnie przeszukaj kod pod kątem HashAlgorithm: jeśli nic go nie ustawia, twoje skróty zmienią się z SHA‑1 na SHA‑256 po aktualizacji, co jest ulepszeniem, które warto odnotować w notatce wydania. Na koniec świadomie zdecyduj o poziomie logowania. Uczciwym domyślnym dla usługi jest Warning | Error; All służy do odtworzenia konkretnego problemu, a None oznacza rezygnację z jedynego sygnału informującego, że podpis został wykonany z użyciem zwolnienia.
Weryfikacja zmieniła się w tym samym kierunku
Łatwo to przeoczyć, ponieważ kod wywołujący nie musi się zmieniać. DigitalVerifyOptions bez ustawionych kryteriów wcześniej był prawie nicnierobny: porównywał podane kryteria, a gdy ich nie było, miał mało do powiedzenia. Od wersji 26.9 to samo wywołanie wykonuje pełną kryptograficzną kontrolę każdego cyfrowego podpisu PDF.
Dla usługi weryfikującej przychodzące dokumenty jest to cicha aktualizacja z „istnieje tutaj podpis” na „ten podpis pasuje do tej treści”. Warto to wiedzieć, zanim zobaczysz dokument, który zaczyna nie przechodzić weryfikacji, a który w zeszłym miesiącu przeszedł pomyślnie: dokument prawdopodobnie został zmieniony, a starsza kontrola po prostu tego nie wykrywała.
Certyfikaty w przykładzie
Jedna rzecz warta skopiowania, a nie kodu: przykład nie zawiera prywatnego klucza. TestCertificates.cs tworzy trzy samopodpisane pliki PFX w pamięci w czasie działania – ważny, wygasły w zeszłym roku, ważny od przyszłego roku – więc demonstracja działa niezależnie od bieżącej daty i nie zawiera żadnych wrażliwych danych w repozytorium.
Ten wzorzec warto przyjąć w własnych zestawach testów. Zatwierdzony testowy certyfikat w końcu wygaśnie, a gdy tak się stanie, awaria wygląda dokładnie tak, jak błąd, który to wydanie ma ujawnić.
Wnioski
Trzy zmiany, jeden kierunek: awarie, które wcześniej pojawiały się u odbiorcy, teraz pojawiają się u nadawcy. Odnów certyfikat zamiast sięgać po AllowExpired, niech domyślnym będzie SHA‑256, weryfikuj przychodzące dokumenty kryptograficznie i wybierz poziom logowania, zanim go potrzebujesz. Przykład uruchamia wszystkie sześć zachowań w jednym przebiegu, w tym odrzucenie, więc aktualizację można przećwiczyć w kilka minut.