sgcSign 2026.9.0 ist ein großes Release. Das meiste davon geht auf Kundenwünsche zurück, und es betrifft drei Bereiche: zu wissen, mit welchem Zertifikat Sie gleich signieren, eine Signatur zu erzeugen, die ein Prüfprogramm auch in zehn Jahren noch akzeptiert, und eine Signatur gegen etwas anderes als sich selbst zu prüfen.
Dieser Beitrag geht die neuen Funktionen durch und zeigt zu jeder den Delphi-Code. Am Ende folgt ein kurzer Abschnitt über Signaturen aus früheren Versionen, die neu erstellt werden sollten.
Zertifikatslisten, aus denen Sie wählen können
Das Auflisten von Zertifikaten lieferte bisher eine Liste von Anzeigenamen zurück, was für eine Combobox genügt, für eine Entscheidung aber nicht. Zwei Karten derselben Zertifizierungsstelle, ausgestellt auf dieselbe Person, sehen in dieser Liste identisch aus.
Die Auflistung enthält jetzt den SHA-1-Fingerabdruck, die Steuernummer, die Seriennummer, den Aussteller und die Gültigkeitsdaten, und sie funktioniert für den Windows-Zertifikatspeicher, ein PKCS#11-Token und eine PFX-Datei auf dieselbe Weise. Abgelaufene Zertifikate und Zertifikate ohne privaten Schlüssel lassen sich herausfiltern. Der Fingerabdruck geht direkt an SelectCertificateByThumbprint, sodass genau das Zertifikat signiert, das der Benutzer ausgewählt hat.
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;
Der Aufruf ohne Parameter bleibt unverändert, bestehender Code funktioniert also weiter.
Karten mit mehreren Slots ohne PIN erfassen
Eine qualifizierte Signaturkarte enthält oft mehr als ein Zertifikat, jedes hinter einer eigenen PIN. Polnische Karten sind der häufigste Fall, eine Certum-Karte mit zwei Profilen oder eine PWPW Sigillum-Karte mit drei Containern. Den Benutzer nach drei PINs zu fragen, nur um ihm eine Liste zu zeigen, ist keine brauchbare Oberfläche.
Ein PKCS#11-Token lässt sich jetzt ganz ohne Anmeldung erfassen. TokenSlotCount meldet, wie viele Slots tatsächlich ein Token enthalten, also den Bereich, der sich zu adressieren lohnt, und jeder Eintrag vermerkt den Slot und das Token-Label, aus dem er stammt. So muss die richtige PIN erst dann abgefragt werden, wenn dieses Zertifikat ausgewählt wird.
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;
Das Zertifikat finden, das Ihres ausgestellt hat
Langzeit-Signaturprofile brauchen das ausstellende Zertifikat, und die meisten qualifizierten Signaturkarten tragen nur Ihr eigenes. Zwei neue Aufrufe an jedem Key-Provider finden es: GetIssuerCertificate liefert das Zertifikat, das jenes ausgestellt hat, mit dem Sie signieren, und GetCertificateChain liefert den gesamten Pfad darüber. Die Zuordnung wird kryptografisch geprüft und nicht über den Namen, sodass eine Zertifizierungsstelle, die ihren Signaturschlüssel gewechselt hat, nicht mit ihrer Vorgängerin verwechselt wird.
Wo gesucht wird, ist eine Entscheidung und daher eine Eigenschaft. Standardmäßig wird der Windows-Zertifikatspeicher durchsucht, iluLocalStore durchsucht PEM- oder DER-Dateien, die Sie mit Ihrer Anwendung ausliefern, und iluAIA lädt das Zertifikat von der Adresse herunter, die in Ihrem eigenen steht. Letzteres ist standardmäßig ausgeschaltet, weil es auf das Netzwerk zugreift.
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;
Zwei neue PAdES-Profile
spPAdESBasicT signiert mit eingebettetem Zeitstempel und ohne Sperrinformationen, also genau das, was Sie brauchen, wenn die Signatur nur belegen muss, wann sie erstellt wurde. spPAdESDocumentArchive geht den anderen Weg und legt auf das Langzeitprofil zusätzlich einen Archiv-Zeitstempel, der das gesamte Dokument einschließlich seiner Sperrinformationen abdeckt. So bleibt die Datei prüfbar, auch wenn der eigene Gültigkeitszeitraum des ersten Zeitstempels abgelaufen ist.
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;
Zertifikate melden alles, was sie enthalten
Subject und Issuer meldeten bisher die sieben Attribute, die der Parser kannte, und verwarfen den Rest. Sie melden jetzt jedes Attribut im Zertifikat, die Postanschrift wird in lesbare Zeilen decodiert, und jedes Attribut lässt sich über seine OID auslesen.
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;
Signierte Zeitstempelanfragen
Manche qualifizierten Zeitstempeldienste, insbesondere die polnischen, beantworten keine einfache RFC 3161-Anfrage. Sie erwarten, dass die Anfrage selbst in ein CMS SignedData verpackt und signiert wird. Das ist jetzt eine Eigenschaft und nichts mehr, was Sie von Hand bauen müssen.
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;
Die Standardform entspricht einer Anfrage, die der Zeitstempeldienst PWPW Sigillum akzeptiert. Bestehender Code sendet weiterhin eine einfache Anfrage, es ändert sich nichts, solange Sie RequestFormat nicht setzen.
Authenticode-Cross-Zertifikate
Die Signatur eines Kernelmodus-Treibers muss über ein Cross-Zertifikat zur Microsoft Code Verification Root führen, und genau das bettet signtool /ac ein. sgcSign kann jetzt auf dieselbe Weise zusätzliche Zertifikate einbetten.
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;
Sie fließen in jede verschachtelte Signatur ein, und wenn Sie nichts hinzufügen, bleibt die Signatur Byte für Byte dieselbe wie bisher. Der Signaturserver nimmt ein Feld add_certs entgegen, die Kommandozeile eine mehrfach verwendbare Option --add-cert.
Prüfung mit Vertrauensankern
Das ist die wichtigste Änderung dieses Releases. Bisher entnahm der Prüfer das Signaturzertifikat dem Dokument, das er gerade prüfte, und bestätigte, dass dieser Schlüssel dieses Dokument signiert hatte. Das belegt nur, dass derjenige, der das Dokument geschrieben hat, auch die Signatur darin erzeugt hat, mehr nicht. Jeder kann ein Dokument erzeugen, das diese Prüfung besteht.
Der Prüfung lassen sich jetzt Vertrauensanker übergeben, und sie baut die Zertifikatskette auf und prüft sie gegen diese Anker. Ein Anker wird über den SHA-256-Fingerabdruck zugeordnet oder dadurch, dass die Prüfung unter seinem eigenen Schlüssel gelingt, niemals über den Namen.
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;
Ein Prüfer ohne Anker liefert dasselbe Ergebnis wie bisher, beim Upgrade geht also nichts kaputt. Eines ändert sich allerdings: Der Bericht nach ETSI TS 119 102-2 meldet für eine Signatur, die nie zu einem Anker verkettet wurde, nicht mehr total-passed, sondern indeterminate mit NO_CERTIFICATE_CHAIN_FOUND. Berichte aus früheren Versionen müssen neu erzeugt werden.
Ein HTTP-Transport, mit Proxys
Die Bibliothek stellt an mehreren Stellen Netzwerkanfragen: der Zeitstempel-Client, die Clients für OCSP und Sperrlisten, der Download der EU-Vertrauensliste und die Cloud-Key-Provider. Jeder hatte seine eigene Vorstellung davon, wie eine solche Anfrage auszusehen hat. Sie teilen sich jetzt einen einzigen Transport mit einer Eigenschaft 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;
Der Proxy kann der systemweite sein, gar keiner, eine ausdrücklich angegebene Adresse oder die benutzerbezogene Einstellung, die über WPAD oder ein PAC-Skript aufgelöst wird, so wie es der Browser macht. Jede Einstellung ist so vorbelegt, wie sich diese Anfragen bisher verhalten haben. Für ein Gateway, das sich mit diesen Einstellungen nicht beschreiben lässt, ersetzt ein neues Ereignis OnHTTPRequest den Transport vollständig.
Kleinere Dinge, die man kennen sollte
OCSP-Nonces. Die Sperrabfrage enthält jetzt eine zufällige Nonce aus dem kryptografischen Generator des Systems, und die Antwort wird dagegen geprüft. Eine Antwort, die keine Nonce zurückgibt, wird weiterhin akzeptiert, weil RFC 6960 vorproduzierte Antworten erlaubt, eine Antwort mit einer abweichenden Nonce wird dagegen abgelehnt. NonceEnabled schaltet die Erweiterung für einen Responder ab, der sie nicht annimmt.
Pivot-Pinning der EU-Vertrauensliste. Eine neue Eigenschaft RequirePinnedPivot entscheidet, ob die Liste der Vertrauenslisten zu einem der gepinnten Pivot-Fingerabdrücke aus dem Amtsblatt verkettet sein muss, LOTLPivotPinned und LastPivotFingerprint melden das Ergebnis. Die Prüfung existierte und wurde von nirgendwo aufgerufen. Die mitgelieferten gepinnten Konstanten sind weiterhin die dokumentierten Platzhalter, es wird also ein Fehlschlag gemeldet, bis Sie echte Fingerabdrücke eintragen und die Eigenschaft einschalten.
Ein Hashverfahren Ihrer Wahl. CAdES und PKCS#11 erhalten beide eine Eigenschaft HashAlgorithm, die auf SHA-256 vorbelegt ist, damit bestehender Code dieselben Bytes erzeugt. CAdES schrieb bisher SHA-256 als Literal in jeden Digest-Algorithmus, und PKCS#11 wählte seinen DigestInfo-Header nach der Länge dessen, was ihm übergeben wurde, sodass sich kein anderes Hashverfahren ausdrücken ließ. Eine Karte kann jetzt wie gewünscht mit SHA-1, SHA-256, SHA-384 oder SHA-512 signieren.
ASiC mit Signatur-Callback. Eine neue Überladung von BuildCAdES nimmt einen Callback entgegen statt fertiger Signatur-Bytes. Sie erzeugt zuerst META-INF/ASiCManifest.xml, übergibt genau diese Bytes an Ihren Callback und legt das Zurückgelieferte als META-INF/signature.p7s ab. Nur in dieser Reihenfolge kann die Signatur das Manifest abdecken. Bei ASiC-S, das kein Manifest enthält, erhält der Callback das Datendokument selbst. GetCAdESSignedData liefert dieselben Bytes für Aufrufer, die zwei ausdrückliche Schritte bevorzugen.
Cloud-KMS-Zertifikate. AWS KMS und Google Cloud KMS erhalten SetCertificate und SetCertificateFromFile, passend zu dem Paar, das HashiCorp Vault bereits hatte. Beide Dienste geben nur einen nackten öffentlichen Schlüssel heraus, und bisher gab es keine Möglichkeit, einem der beiden Provider mitzuteilen, welches X.509-Zertifikat dazugehört.
Ein Zeitstempel von dem Rechner, der die Kommandozeile ausführt. Die Kommandozeile sgcsign erhält --tsa-direct, womit der Zeitstempeldienst direkt angefragt wird statt über den sgcSign Server. Geben Sie die Option zusammen mit --tsa an.
Der Signaturserver
Die Serverseite hat ihre eigene Liste bekommen. Eine Authenticode-Signatur kann jetzt mehr als zwei verschachtelte Signaturen mit jeweils einem anderen Zertifikat tragen, über eine geordnete Liste hash_algorithms wie sha1,sha256,sha384 oder eine geordnete Liste providers wie certA:sha256,certB:sha1, in beiden Fällen bis zu vier Einträge. Das ist für den Fall gedacht, dass eine Datei ausgeliefert wird, die von einem auslaufenden Zertifikat und von seinem Nachfolger signiert ist. Jedes Zertifikat wird gegen die Berechtigungen des API-Schlüssels geprüft, bevor überhaupt signiert wird.
Windows-Katalogdateien lassen sich signieren: Der Upload-Endpunkt akzeptiert catalog als Format und signiert eine vorhandene .cat-Datei der Art, die makecat erzeugt, sodass ein Treiberpaket genauso signiert wird wie ein Programm.
Ein neuer Endpunkt /api/v1/sign/raw signiert einen bereits berechneten Digest und liefert nur den Signaturwert zurück, ohne PKCS#7-Hülle, ohne signierte Attribute und ohne Zeitstempel. Genau das verlangt signtool über seinen /dlib-Callback. Da er jeden übergebenen Digest signiert, ist er standardmäßig ausgeschaltet und wird mit allow_raw_sign einzeln pro Provider eingeschaltet.
API-Schlüssel und die Benutzer, die sie anlegen, sind jetzt pro Projekt isoliert, ein Projektadministrator verwaltet die Schlüssel in seinem eigenen Projekt, und Schlüssel lassen sich aktivieren und deaktivieren statt sie nur einmalig zu widerrufen. Das Ratenlimit und das Tageskontingent je Schlüssel können nach dem Anlegen bearbeitet werden. Eine neue Einstellung SessionAbsoluteMaxMin begrenzt die Gesamtlebensdauer einer Administratorsitzung standardmäßig auf zwölf Stunden, weil bisher jede authentifizierte Anfrage den Ablauf ohne Obergrenze nach hinten verschob. Das Audit-Protokoll lässt sich nach Client-Adresse filtern, in der Konsole und im CSV-Export, wobei eine unvollständige Adresse von links her passt. Und neue Einstellungen für weitergeleitete Header, standardmäßig ausgeschaltet, ermitteln die echte Client-Adresse, wenn der Server hinter einem Reverse Proxy läuft. Sie werden nur dann geglaubt, wenn die Verbindung von einem gelisteten vertrauenswürdigen Proxy kommt.
Signaturen, die Sie neu erstellen sollten
Drei Fehler in früheren Versionen erzeugten Dateien, die strukturell falsch sind, und ein Upgrade repariert keine Datei, die bereits geschrieben ist. Wenn einer dieser Punkte auf das zutrifft, was Sie signiert haben, signieren Sie es mit 2026.9.0 erneut.
- Alles, was mit einem EC-Schlüssel signiert wurde. Jede PAdES-, CAdES-, Authenticode-, NuGet- und RFC 3161-Signatur, die mit einem EC-Schlüssel erstellt wurde, war ungültig. Die Standards verlangen, dass der Signaturwert innerhalb einer CMS-Struktur ein DER-ECDSA-Sig-Value ist, die Key-Provider erzeugen die rohe Aneinanderreihung von r und s, und nichts wandelte das auf dem Weg dorthin um. P-256, P-384 und P-521 funktionieren jetzt alle. Die Pfade für XAdES und XML-DSig waren korrekt und bleiben bewusst unangetastet.
- Jede CAdES-Signatur und alles, was mit einer Karte, einem USB-Token oder einem Cloud-Schlüssel signiert wurde.
SignDatahatte keinen festgeschriebenen Vertrag, und die Provider waren sich uneinig, ob das Argument die Daten sind oder ein Digest davon. Eine CAdES-Signatur war deshalb RSA über einen Hash eines Hashes. Das Argument sind jetzt die Rohbytes, und der Provider hasht sie mit dem bei ihm konfigurierten Hashverfahren. - PAdES-Dateien, die signiert wurden, nachdem ein anderer Signierer denselben Key-Provider verwendet hatte.
Profile.HashAlgorithmwurde einmal im Konstruktor geschrieben und danach nie wieder gelesen. Ein PDF, das nach einem Facturae- oder SAF-T-Signierer signiert wurde, konnte deshalb RSA über einen SHA-1-Digest sein, innerhalb einer Signatur, die SHA-256 deklariert.
Die Prüfung hat sich in dieselbe Richtung verändert. Die Authenticode-Prüfung prüfte nie eine Signatur, sie berechnete den Datei-Hash neu und verglich ihn mit dem in der Signatur. Um eine Datei zu fälschen, die sgcSign als gültig signiert bezeichnete, brauchte es deshalb keinen privaten Schlüssel. Sperrantworten und Zeitstempel-Token wurden eingebettet, ohne geprüft zu werden. Die EU-Vertrauensliste wurde heruntergeladen und verwendet, ohne überhaupt etwas zu prüfen. All das führt jetzt die Prüfung durch, die der Name nahelegt, und die vollständige Darstellung zu jedem einzelnen Punkt steht im Changelog.
So bekommen Sie es
sgcSign 2026.9.0 ist ab sofort verfügbar, mit vollständigem Quellcode und einem Jahr Updates, für Delphi 7 bis Delphi 13 Florence, die entsprechenden C++ Builder-Versionen und .NET.
Produktseite · Testversion herunterladen · Changelog
Fragen oder Feedback? Nehmen Sie Kontakt auf, Sie erhalten eine Antwort von den Leuten, die den Code geschrieben haben.
