Co nowego w sgcSign 2026.9.0

· Wydania
sgcSign 2026.9.0, komponenty podpisu cyfrowego dla Delphi i C++ Builder

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.

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.