sgcSign 2026.9.0 to duże wydanie. Większość zmian powstała na życzenie klientów i dotyczy trzech obszarów: wiedzy o tym, którym certyfikatem zaraz podpiszesz, budowania podpisu, który walidator zaakceptuje także za dziesięć lat, oraz weryfikowania podpisu względem czegoś innego niż on sam.
Ten wpis omawia nowe funkcje wraz z kodem Delphi dla każdej z nich. Na końcu znajduje się też krótka sekcja o podpisach złożonych we wcześniejszych wersjach, które warto złożyć ponownie.
Listy certyfikatów, z których można wybierać
Wyliczanie certyfikatów zwracało dotąd listę nazw wyświetlanych, co wystarcza do wypełnienia listy rozwijanej, ale nie do podjęcia decyzji. Dwie karty od tego samego urzędu, wydane tej samej osobie, wyglądają na takiej liście identycznie.
Wyliczanie zwraca teraz odcisk palca SHA-1, identyfikator podatkowy, numer seryjny, wystawcę i daty ważności, a działa tak samo dla magazynu certyfikatów Windows, tokena PKCS#11 i pliku PFX. Certyfikaty wygasłe oraz certyfikaty bez klucza prywatnego można odfiltrować. Odcisk palca trafia prosto do SelectCertificateByThumbprint, więc certyfikat wskazany przez użytkownika jest tym, który podpisuje.
uses
sgcSign_KeyProvider_WinCertStore, sgcSign_X509, sgcSign_Types;
var
oProvider: TsgcWindowsCertStoreProvider;
oList: TsgcX509CertificateList;
i: Integer;
begin
oProvider := TsgcWindowsCertStoreProvider.Create(nil);
Try
// only certificates that are still valid and hold a private key
oList := oProvider.EnumerateCertificateList([cfNotExpired, cfPrivateKey]);
Try
for i := 0 to oList.Count - 1 do
Memo1.Lines.Add(Format('%s | %s | %s | %s .. %s | %s',
[oList[i].Subject, oList[i].NIF, oList[i].SerialNumber,
DateToStr(oList[i].NotBefore), DateToStr(oList[i].NotAfter),
oList[i].Thumbprint]));
// and sign with exactly the one that was chosen
oProvider.SelectCertificateByThumbprint(oList[0].Thumbprint);
Finally
oList.Free;
End;
Finally
oProvider.Free;
End;
end;
Wywołanie bez parametrów pozostaje niezmienione, więc istniejący kod działa dalej.
Inwentaryzacja kart wieloslotowych bez PIN-u
Karta podpisu kwalifikowanego często zawiera więcej niż jeden certyfikat, każdy za własnym PIN-em. Typowym przypadkiem są karty polskie, karta Certum z dwoma profilami albo karta PWPW Sigillum z trzema kontenerami. Proszenie użytkownika o trzy PIN-y tylko po to, by pokazać mu listę, nie jest praktycznym rozwiązaniem.
Token PKCS#11 można teraz zinwentaryzować w ogóle bez logowania. TokenSlotCount podaje, ile slotów rzeczywiście zawiera token, czyli jaki zakres warto przeglądać, a każdy wpis zapisuje slot i etykietę tokena, z którego pochodzi, dzięki czemu o właściwy PIN można poprosić dopiero wtedy, gdy dany certyfikat zostanie wybrany.
uses
sgcSign_KeyProvider_PKCS11, sgcSign_X509, sgcSign_Types;
var
oPKCS11: TsgcPKCS11Provider;
oList: TsgcX509CertificateList;
i: Integer;
begin
oPKCS11 := TsgcPKCS11Provider.Create(nil);
Try
oPKCS11.LibraryPath := 'C:\Windows\System32\cryptoCertum3PKCS.dll';
ShowMessage(Format('%d slots hold a token', [oPKCS11.TokenSlotCount]));
// walks every slot that holds a token, never logs in, never needs a PIN
oList := oPKCS11.EnumerateCertificateListAllSlots([cfNotExpired]);
Try
for i := 0 to oList.Count - 1 do
Memo1.Lines.Add(Format('slot %d (%s): %s',
[oList[i].SlotIndex, oList[i].TokenLabel, oList[i].Subject]));
Finally
oList.Free;
End;
Finally
oPKCS11.Free;
End;
end;
Odnajdywanie certyfikatu, który wydał Twój
Profile podpisu długoterminowego wymagają certyfikatu wystawcy, a większość kart podpisu kwalifikowanego zawiera wyłącznie Twój własny. Znajdują go dwa nowe wywołania dostępne w każdym dostawcy kluczy: GetIssuerCertificate zwraca certyfikat, który wydał ten używany do podpisu, a GetCertificateChain zwraca całą ścieżkę powyżej niego. Dopasowanie sprawdzane jest kryptograficznie, a nie po nazwie, więc urząd, który wymienił swój klucz podpisujący, nie zostanie pomylony ze swoim poprzednikiem.
To, gdzie szukać, jest decyzją, więc jest właściwością. Domyślnie przeszukiwany jest magazyn certyfikatów Windows, iluLocalStore przeszukuje pliki PEM lub DER dostarczane razem z aplikacją, a iluAIA pobiera certyfikat spod adresu zapisanego w Twoim własnym, co jest domyślnie wyłączone, ponieważ wymaga sięgnięcia do sieci.
uses
sgcSign_Classes, sgcSign_X509;
var
vIssuerDER: TBytes;
oChain: TsgcCertificateChain;
begin
oProvider.IssuerLookup := [iluSystemStore, iluLocalStore];
oProvider.IssuerFiles.Add('certs\ca-intermediate.pem');
oProvider.IssuerFiles.Add('certs\ca-root.pem');
vIssuerDER := oProvider.GetIssuerCertificate;
oChain := oProvider.GetCertificateChain;
end;
Dwa nowe profile PAdES
spPAdESBasicT podpisuje z osadzonym znacznikiem czasu i bez danych o unieważnieniu, czyli dokładnie tak, jak chcesz, gdy podpis ma tylko dowodzić, kiedy powstał. spPAdESDocumentArchive idzie w drugą stronę i dokłada archiwalny znacznik czasu na wierzchu profilu długoterminowego, obejmując cały dokument wraz z danymi o unieważnieniu, dzięki czemu plik pozostaje weryfikowalny po upływie okresu ważności pierwszego znacznika czasu.
uses
sgcSign_PAdES, sgcSign_Types;
var
oPAdES: TsgcPAdESSigner;
begin
oPAdES := TsgcPAdESSigner.Create(nil);
Try
oPAdES.KeyProvider := oProvider;
oPAdES.Profile.Profile := spPAdESDocumentArchive;
oPAdES.TSAClient := TSAClient1;
oPAdES.OCSPClient := OCSPClient1;
// revocation lists you supply yourself, for an authority whose CRL is
// issued by its own root. IssuerCertificate is resolved automatically
// when it is left empty and OCSPClient is assigned
oPAdES.CRLFiles.Add('crl\ca-intermediate.crl');
oPAdES.SignPDFFile('contract.pdf', 'contract-signed.pdf');
Finally
oPAdES.Free;
End;
end;
Certyfikaty ujawniają wszystko, co zawierają
Podmiot i wystawca zwracali dotąd siedem atrybutów, które rozpoznawał parser, a resztę pomijali. Teraz zwracają każdy atrybut zapisany w certyfikacie, adres pocztowy jest dekodowany do czytelnych wierszy, a dowolny atrybut można odczytać po jego OID.
uses
sgcSign_X509;
var
i: Integer;
begin
// by OID: organizationIdentifier
ShowMessage(oCert.GetSubjectAttribute('2.5.4.97'));
// or the full list, in the order the certificate declares it
for i := 0 to oCert.SubjectAttributeCount - 1 do
Memo1.Lines.Add(oCert.SubjectAttributeOID[i] + ' = ' +
oCert.SubjectAttributeValue[i]);
end;
Podpisane żądania znacznika czasu
Niektóre kwalifikowane urzędy znacznika czasu, w szczególności polskie, nie odpowiadają na zwykłe żądanie RFC 3161. Wymagają, aby samo żądanie było opakowane w CMS SignedData i podpisane. Teraz jest to właściwość, a nie coś, co budujesz ręcznie.
uses
sgcSign_TSA;
begin
oTSA.URL := 'https://tsa.example.com';
oTSA.RequestFormat := trfCMS; // plain RFC 3161 is still the default
oTSA.KeyProvider := oProvider; // trfCMS needs one
// authorities differ in what they expect inside the wrapper
oTSA.SignOptions.IncludeSignedAttributes := True;
// and the exact bytes are available when an authority needs checking
oTSA.OnBeforeSendRequest := DoBeforeSendRequest;
oTSA.OnAfterReceiveResponse := DoAfterReceiveResponse;
end;
Domyślna postać odpowiada żądaniu akceptowanemu przez urząd znacznika czasu PWPW Sigillum. Istniejący kod nadal wysyła zwykłe żądanie, nic się nie zmienia, dopóki nie ustawisz RequestFormat.
Certyfikaty krzyżowe Authenticode
Podpis sterownika trybu jądra musi tworzyć ścieżkę do Microsoft Code Verification Root poprzez certyfikat krzyżowy, czyli dokładnie to, co osadza signtool /ac. sgcSign potrafi teraz osadzać dodatkowe certyfikaty w ten sam sposób.
uses
sgcSign_Authenticode;
var
oSigner: TsgcAuthenticodeSigner;
begin
oSigner := TsgcAuthenticodeSigner.Create(nil);
Try
oSigner.KeyProvider := oProvider;
oSigner.AddCertificateFromFile('MSCV-VSClass3.cer');
// or from bytes you already hold
// oSigner.AddCertificate(vCrossCertDER);
oSigner.SignFile('driver.sys', 'driver-signed.sys');
Finally
oSigner.Free;
End;
end;
Trafiają one do każdego zagnieżdżonego podpisu, a jeśli nic nie dodasz, podpis pozostaje bajt w bajt taki sam jak wcześniej. Serwer podpisujący przyjmuje pole add_certs, a wiersz poleceń powtarzalną opcję --add-cert.
Weryfikacja z kotwicami zaufania
To najważniejsza zmiana w tym wydaniu. Do tej pory weryfikator pobierał certyfikat podpisujący z badanego dokumentu i potwierdzał, że ten klucz podpisał ten dokument. Dowodzi to jedynie, że ten, kto napisał dokument, napisał również zawarty w nim podpis, i nic ponadto. Każdy może wytworzyć dokument, który przejdzie taką weryfikację.
Weryfikacji można teraz przekazać kotwice zaufania, a ona buduje i sprawdza względem nich ścieżkę certyfikacji. Kotwica jest dopasowywana po odcisku palca SHA-256 albo przez weryfikację jej własnym kluczem, nigdy po nazwie.
uses
sgcSign_Verifier, sgcSign_Types;
var
oVerifier: TsgcSignatureVerifier;
begin
oVerifier := TsgcSignatureVerifier.Create(nil);
Try
oVerifier.TrustedCertificates.Add('certs\qualified-root.pem');
// or a Windows certificate store by name
oVerifier.TrustedCertificateStore := 'ROOT';
oVerifier.RequireTrustedChain := True;
oVerifier.CheckKeyUsage := True;
oVerifier.RequireCompleteRevocationCheck := True;
if oVerifier.Verify(vSignedXML) = vsValid then
ShowMessage('signed, and chained to a root you trust');
Finally
oVerifier.Free;
End;
end;
Weryfikator bez kotwicy zwraca ten sam werdykt co wcześniej, więc aktualizacja niczego nie psuje. Jedno się jednak zmienia: raport ETSI TS 119 102-2 nie podaje już total-passed dla podpisu, którego nigdy nie powiązano ścieżką z kotwicą, tylko indeterminate z NO_CERTIFICATE_CHAIN_FOUND. Raporty zapisane we wcześniejszych wersjach trzeba wygenerować ponownie.
Jeden transport HTTP, z obsługą proxy
Biblioteka wykonuje żądania sieciowe z kilku miejsc: z klienta znacznika czasu, z klientów OCSP i list unieważnień, przy pobieraniu unijnej listy zaufania oraz z chmurowych dostawców kluczy. Każde z nich miało własny pomysł na to, jak takie żądanie wykonać. Teraz wszystkie korzystają z jednego transportu i jednej właściwości HTTPOptions.
uses
sgcSign_WinHTTP;
begin
oTSA.HTTPOptions.Proxy.Mode := pxCustom; // pxSystem is the default
oTSA.HTTPOptions.Proxy.URL := 'proxy.corp.local:8080';
oTSA.HTTPOptions.Proxy.Username := 'user';
oTSA.HTTPOptions.Proxy.Password := 'secret';
// the certificate presented when the gateway asks for client authentication
oTSA.HTTPOptions.ClientCertificate.StoreName := 'MY';
oTSA.HTTPOptions.ClientCertificate.Thumbprint := 'a1b2c3...';
oTSA.HTTPOptions.MinTLSVersion := tlsTLS1_2;
end;
Proxy może być tym ustawionym dla całej maszyny, może go nie być wcale, może to być jawny adres albo ustawienie danego użytkownika rozwiązywane przez WPAD lub skrypt PAC, czyli tak, jak robi to przeglądarka. Każde ustawienie ma domyślnie taką wartość, jaką te żądania miały wcześniej. Dla bramy, której tymi ustawieniami nie da się opisać, nowe zdarzenie OnHTTPRequest zastępuje transport w całości.
Drobiazgi, o których warto wiedzieć
Wartości nonce w OCSP. Żądanie o unieważnienie zawiera teraz losową wartość nonce z systemowego generatora kryptograficznego, a odpowiedź jest z nią porównywana. Odpowiedź, która nie odsyła żadnej wartości nonce, jest nadal akceptowana, ponieważ RFC 6960 dopuszcza odpowiedzi przygotowane z wyprzedzeniem, ale odpowiedź odsyłająca inną wartość zostaje odrzucona. NonceEnabled wyłącza to rozszerzenie dla respondera, który go nie przyjmuje.
Przypinanie punktów zwrotnych unijnej listy zaufania. Nowa właściwość RequirePinnedPivot decyduje o tym, czy lista list zaufanych musi tworzyć ścieżkę do jednego z przypiętych odcisków palca punktów zwrotnych z Dziennika Urzędowego, a LOTLPivotPinned i LastPivotFingerprint raportują wynik. Sprawdzenie istniało, ale nie było nigdzie wywoływane. Dostarczane przypięte stałe są nadal udokumentowanymi wartościami zastępczymi, więc brak dopasowania będzie zgłaszany dopóty, dopóki nie wpiszesz prawdziwych odcisków palca i nie włączysz tej właściwości.
Skrót, który wybierasz sam. CAdES i PKCS#11 zyskują właściwość HashAlgorithm, domyślnie SHA-256, więc istniejący kod wytwarza te same bajty. CAdES wpisywał dotąd SHA-256 do każdego algorytmu skrótu jako wartość literalną, a PKCS#11 dobierał nagłówek DigestInfo na podstawie długości otrzymanych danych, przez co nie dało się wyrazić żadnego innego skrótu. Karta może teraz podpisywać algorytmem SHA-1, SHA-256, SHA-384 lub SHA-512, zgodnie z żądaniem.
ASiC z funkcją zwrotną podpisu. Nowe przeciążenie BuildCAdES przyjmuje funkcję zwrotną zamiast gotowych bajtów podpisu. Najpierw buduje META-INF/ASiCManifest.xml, przekazuje dokładnie te bajty do Twojej funkcji zwrotnej i zapisuje to, co wróci, jako META-INF/signature.p7s, a jest to jedyna kolejność, w której podpis może objąć manifest. W przypadku ASiC-S, który nie zawiera manifestu, funkcja zwrotna otrzymuje sam dokument z danymi. GetCAdESSignedData zwraca te same bajty dla tych, którzy wolą dwa jawne kroki.
Certyfikaty w chmurowym KMS. AWS KMS i Google Cloud KMS zyskują SetCertificate oraz SetCertificateFromFile, czyli parę, którą HashiCorp Vault miał już wcześniej. Obie usługi wydają wyłącznie surowy klucz publiczny, a wcześniej nie było sposobu, by wskazać któremukolwiek z tych dostawców, który certyfikat X.509 do niego należy.
Znacznik czasu z maszyny, na której działa wiersz poleceń. Wiersz poleceń sgcsign zyskuje --tsa-direct, który odpytuje urząd znacznika czasu bezpośrednio, zamiast przechodzić przez sgcSign Server. Podaj go razem z --tsa.
Serwer podpisujący
Strona serwerowa dostała własną listę zmian. Podpis Authenticode może teraz zawierać więcej niż dwa zagnieżdżone podpisy, każdy z innym certyfikatem, dzięki uporządkowanej liście hash_algorithms, na przykład sha1,sha256,sha384, albo uporządkowanej liście providers, na przykład certA:sha256,certB:sha1, w obu przypadkach do czterech pozycji. Służy to dostarczaniu jednego pliku podpisanego wygasającym certyfikatem oraz jego następcą. Każdy certyfikat jest sprawdzany względem uprawnień klucza API, zanim rozpocznie się jakiekolwiek podpisywanie.
Można podpisywać pliki katalogowe Windows: punkt końcowy przesyłania przyjmuje catalog jako format i podpisuje istniejący plik .cat tego rodzaju, jaki wytwarza makecat, więc pakiet sterownika podpisuje się tak samo jak program.
Nowy punkt końcowy /api/v1/sign/raw podpisuje skrót, który już obliczyłeś, i zwraca wyłącznie wartość podpisu, bez opakowania PKCS#7, bez podpisanych atrybutów i bez znacznika czasu. Dokładnie o to prosi signtool przez swoje wywołanie zwrotne /dlib. Ponieważ podpisze każdy przekazany mu skrót, jest domyślnie wyłączony i włącza się go osobno dla każdego dostawcy za pomocą allow_raw_sign.
Klucze API oraz użytkownicy, którzy je tworzą, są teraz odizolowani w obrębie projektu, administrator projektu zarządza kluczami we własnym projekcie, a klucze można włączać i wyłączać, zamiast jedynie nieodwracalnie je unieważniać. Limit szybkości i dzienny limit dla klucza można edytować po jego utworzeniu. Nowe ustawienie SessionAbsoluteMaxMin ogranicza całkowity czas trwania sesji administratora, domyślnie do dwunastu godzin, ponieważ dotąd każde uwierzytelnione żądanie przesuwało termin wygaśnięcia w przód bez żadnego górnego limitu. Dziennik audytu można filtrować po adresie klienta, zarówno w konsoli, jak i w eksporcie CSV, przy czym częściowy adres dopasowywany jest od lewej strony. Są też nowe ustawienia nagłówków przekazywanych, domyślnie wyłączone, które odtwarzają rzeczywisty adres klienta, gdy serwer działa za odwrotnym proxy, i są uznawane tylko wtedy, gdy połączenie przychodzi od proxy z listy zaufanych.
Podpisy, które warto złożyć ponownie
Trzy usterki we wcześniejszych wersjach wytwarzały pliki błędne strukturalnie, a aktualizacja nie naprawia pliku, który został już zapisany. Jeśli którykolwiek z poniższych opisów pasuje do tego, co podpisałeś, podpisz to ponownie w wersji 2026.9.0.
- Wszystko, co podpisano kluczem EC. Każdy podpis PAdES, CAdES, Authenticode, NuGet i RFC 3161 złożony kluczem EC był nieprawidłowy. Standardy wymagają, aby wartość podpisu wewnątrz struktury CMS była strukturą DER ECDSA-Sig-Value, dostawcy kluczy wytwarzają surowe złączenie r i s, a nic nie przekształcało tego po drodze. P-256, P-384 i P-521 działają teraz poprawnie. Ścieżki XAdES i XML-DSig były prawidłowe i celowo pozostawiono je bez zmian.
- Każdy podpis CAdES oraz wszystko, co podpisano kartą, tokenem USB lub kluczem w chmurze.
SignDatanie miał spisanego kontraktu, a dostawcy różnili się co do tego, czy jego argumentem są dane, czy ich skrót, więc podpis CAdES był operacją RSA na skrócie ze skrótu. Argumentem są teraz surowe bajty, a dostawca wylicza z nich skrót skonfigurowanym algorytmem. - Pliki PAdES podpisane po tym, jak inny komponent podpisujący użył tego samego dostawcy kluczy.
Profile.HashAlgorithmbył zapisywany raz w konstruktorze i nigdy więcej odczytywany, więc plik PDF podpisany po komponencie Facturae lub SAF-T mógł być operacją RSA na skrócie SHA-1 wewnątrz podpisu deklarującego SHA-256.
Weryfikacja zmieniła się w tym samym kierunku. Weryfikacja Authenticode nigdy nie sprawdzała podpisu, tylko przeliczała skrót pliku i porównywała go z tym zapisanym w podpisie, więc sfałszowanie pliku, który sgcSign uznawał za poprawnie podpisany, nie wymagało żadnego klucza prywatnego. Odpowiedzi o unieważnieniu i tokeny znacznika czasu były osadzane bez weryfikacji. Unijna lista zaufania była pobierana i używana bez jakiejkolwiek weryfikacji. Wszystkie te operacje wykonują teraz sprawdzenie, które sugeruje ich nazwa, a pełny opis każdej z nich znajduje się w dzienniku zmian.
Jak zacząć
sgcSign 2026.9.0 jest już dostępny, z pełnym kodem źródłowym i rocznym okresem aktualizacji, dla Delphi 7 do Delphi 13 Florence, odpowiadających im wersji C++ Builder oraz .NET.
Strona produktu · Pobierz wersję próbną · Dziennik zmian
Pytania lub uwagi? Skontaktuj się z nami, odpowiedź otrzymasz od osób, które napisały ten kod.
