What Is New in sgcSign 2026.9.0

· Releases
sgcSign 2026.9.0, digital signature components for Delphi and C++ Builder

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.

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.