sgcSign 2026.9.0 is a big release. Most of it came out of customer requests, and it lands in three areas: knowing which certificate you are about to sign with, building a signature that a validator will still accept in ten years, and verifying a signature against something other than itself.
This post walks through the new features with the Delphi code for each one. There is also a short section at the end on signatures made by earlier versions that should be made again.
Certificate Lists You Can Choose From
Enumerating certificates used to hand back a list of display names, which is enough to fill a combo box and not enough to make a decision. Two cards from the same authority, issued to the same person, look identical in that list.
The enumeration now carries the SHA-1 thumbprint, the fiscal identifier, the serial number, the issuer and the validity dates, and it works the same way for the Windows certificate store, a PKCS#11 token and a PFX file. Expired certificates and certificates with no private key can be filtered out. The thumbprint goes straight into SelectCertificateByThumbprint, so the certificate a user picked is the certificate that signs.
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;
The call without parameters is unchanged, so existing code keeps working.
Multi-Slot Cards Inventoried Without a PIN
A qualified signature card often holds more than one certificate, each behind its own PIN. Polish cards are the common case, a Certum card with two profiles or a PWPW Sigillum card with three containers. Asking the user for three PINs just to show them a list is not a workable interface.
A PKCS#11 token can now be inventoried without logging in at all. TokenSlotCount reports how many slots actually hold a token, which is the range worth addressing, and every entry records the slot and token label it came from, so the right PIN can be asked for only when that certificate is the one chosen.
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;
Finding the Certificate That Issued Yours
Long-term signature profiles need the issuing certificate, and most qualified signature cards carry only your own. Two new calls on every key provider find it: GetIssuerCertificate returns the certificate that issued the one you are signing with, and GetCertificateChain returns the whole path above it. The match is checked cryptographically rather than by name, so an authority that has rolled its signing key is not confused with its predecessor.
Where to look is a decision, so it is a property. The Windows certificate store is searched by default, iluLocalStore searches PEM or DER files you ship with your application, and iluAIA downloads the certificate from the address inside your own, which is off by default because it reaches out to the network.
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;
Two New PAdES Profiles
spPAdESBasicT signs with an embedded timestamp and no revocation data, which is what you want when the signature only has to prove when it was made. spPAdESDocumentArchive goes the other way and adds an archive timestamp on top of the long-term profile, covering the whole document including its revocation data, so the file stays checkable after the first timestamp's own validity window has passed.
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;
Certificates Report Everything They Carry
The subject and the issuer used to report the seven attributes the parser recognised, and dropped the rest. They now report every attribute in the certificate, the postal address is decoded into readable lines, and any attribute can be read by its 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;
Signed Timestamp Requests
Some qualified timestamp authorities, the Polish ones in particular, will not answer a plain RFC 3161 request. They want the request itself wrapped in a CMS SignedData and signed. That is now a property rather than something you build by hand.
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;
The default shape matches a request accepted by the PWPW Sigillum timestamp authority. Existing code keeps sending a plain request, nothing changes unless you set RequestFormat.
Authenticode Cross-Certificates
A kernel-mode driver signature has to chain to the Microsoft Code Verification Root through a cross-certificate, which is what signtool /ac embeds. sgcSign can now embed extra certificates the same way.
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;
They flow into every nested signature, and adding nothing leaves the signature byte for byte what it was. The signing server takes an add_certs field and the CLI a repeatable --add-cert option.
Verification With Trust Anchors
This is the most important change in the release. Until now the verifier took the signing certificate out of the document it was checking and confirmed that key had signed that document. That proves whoever wrote the document also wrote the signature in it, and nothing more. Anyone can produce a document that passes.
Verification can now be given trust anchors, and it builds and checks the certificate chain against them. An anchor is matched by SHA-256 thumbprint or by verifying under its own key, never by name.
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;
A verifier with no anchor returns the verdict it returned before, so nothing breaks on upgrade. One thing does change: the ETSI TS 119 102-2 report no longer says total-passed for a signature that was never chained to an anchor, it says indeterminate with NO_CERTIFICATE_CHAIN_FOUND. Reports stored from earlier versions need regenerating.
One HTTP Transport, With Proxies
The library makes network requests from several places: the timestamp client, the OCSP and revocation list clients, the EU trust list download and the cloud key providers. They each had their own idea of how to make one. They now share a single transport with one HTTPOptions property.
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;
The proxy can be the machine-wide one, none at all, an explicit address, or the per-user setting resolved through WPAD or a PAC script, which is what the browser does. Every setting defaults to what those requests did before. For a gateway these settings cannot describe, a new OnHTTPRequest event replaces the transport entirely.
Smaller Things Worth Knowing
OCSP nonces. The revocation request now carries a random nonce from the system cryptographic generator and the response is checked against it. A response echoing no nonce is still accepted, because RFC 6960 allows pre-produced responses, but one echoing a different nonce is refused. NonceEnabled turns the extension off for a responder that will not take it.
EU trust list pivot pinning. A new RequirePinnedPivot property decides whether the list of trusted lists has to chain to one of the pinned Official Journal pivot fingerprints, with LOTLPivotPinned and LastPivotFingerprint reporting the outcome. The check existed and was called from nowhere. The pinned constants that ship are still the documented placeholders, so a miss is reported until you fill in real fingerprints and turn the property on.
A digest you choose. CAdES and PKCS#11 both gain a HashAlgorithm property, defaulting to SHA-256 so existing code produces the same bytes. CAdES used to write SHA-256 into every digest algorithm as a literal, and PKCS#11 picked its DigestInfo header from the length of what it was handed, so no other digest could be expressed. A card can now sign with SHA-1, SHA-256, SHA-384 or SHA-512 as asked.
ASiC with a signing callback. A new BuildCAdES overload takes a callback instead of finished signature bytes. It builds META-INF/ASiCManifest.xml first, hands those exact bytes to your callback and stores what comes back as META-INF/signature.p7s, which is the only order in which the signature can cover the manifest. For ASiC-S, which carries no manifest, the callback receives the data document itself. GetCAdESSignedData returns the same bytes for callers who prefer two explicit steps.
Cloud KMS certificates. AWS KMS and Google Cloud KMS gain SetCertificate and SetCertificateFromFile, matching the pair HashiCorp Vault already had. Both services hand out a bare public key, and there was previously no way to tell either provider which X.509 certificate belongs to it.
A timestamp from the machine that runs the CLI. The sgcsign command line gains --tsa-direct, which asks the timestamp authority directly instead of going through the sgcSign Server. Pass it together with --tsa.
The Signing Server
The server side got its own list. An Authenticode signature can now carry more than two nested signatures with a different certificate for each, through an ordered hash_algorithms list such as sha1,sha256,sha384 or an ordered providers list such as certA:sha256,certB:sha1, up to four entries either way. That is for shipping one file signed by an expiring certificate and its replacement. Every certificate is checked against the API key's permissions before any signing starts.
Windows catalog files can be signed: the upload endpoint accepts catalog as a format and signs an existing .cat file of the kind makecat produces, so a driver package is signed the same way a program is.
A new /api/v1/sign/raw endpoint signs a digest you have already computed and returns only the signature value, with no PKCS#7 wrapper, no signed attributes and no timestamp. That is exactly what signtool asks for through its /dlib callback. Because it will sign any digest handed to it, it is off by default and turned on one provider at a time with allow_raw_sign.
API keys and the users who create them are now isolated per project, a project admin manages the keys in their own project, and keys can be enabled and disabled rather than only revoked one way. The per-key rate limit and daily quota can be edited after the key is created. A new SessionAbsoluteMaxMin setting caps the total lifetime of an administrator session at twelve hours by default, because every authenticated request used to push the expiry forward with no ceiling. The audit log can be filtered by client address, in the console and in the CSV export, with a partial address matching from the left. And new forwarded-header settings, off by default, recover the real client address when the server runs behind a reverse proxy, believed only when the connection arrives from a listed trusted proxy.
Signatures You Should Make Again
Three defects in earlier versions produced files that are structurally wrong, and upgrading does not repair a file that is already written. If any of these describes what you signed, sign it again with 2026.9.0.
- Anything signed with an EC key. Every PAdES, CAdES, Authenticode, NuGet and RFC 3161 signature made with an EC key was invalid. The standards require the signature value inside a CMS structure to be a DER ECDSA-Sig-Value, the key providers produce the raw concatenation of r and s, and nothing converted it on the way in. P-256, P-384 and P-521 all work now. The XAdES and XML-DSig paths were correct and are deliberately left alone.
- Every CAdES signature, and anything signed with a card, a USB token or a cloud key.
SignDatahad no written contract and the providers disagreed about whether its argument is the data or a digest of it, so a CAdES signature was RSA over a hash of a hash. The argument is now the raw bytes and the provider hashes them with its configured digest. - PAdES files signed after another signer had used the same key provider.
Profile.HashAlgorithmwas written once in the constructor and never read again, so a PDF signed after a Facturae or SAF-T signer could be RSA over a SHA-1 digest inside a signature declaring SHA-256.
Verification changed in the same direction. Authenticode verification never checked a signature, it recomputed the file hash and compared it with the one in the signature, so forging a file sgcSign called validly signed needed no private key. Revocation responses and timestamp tokens were embedded without being verified. The EU trust list was downloaded and used without verifying anything at all. All of those now do the check their name implies, and the full account of each one is in the changelog.
Getting It
sgcSign 2026.9.0 is available now, with full source code and one year of updates, for Delphi 7 through Delphi 13 Florence, the matching C++ Builder versions, and .NET.
Product page · Download the trial · Changelog
Questions or feedback? Get in touch, you will get a reply from the people who wrote the code.
