sgcSign 2026.9.0 è una versione importante. Nasce in gran parte dalle richieste dei clienti e interviene su tre fronti: sapere con quale certificato stai per firmare, costruire una firma che un validatore accetterà ancora fra dieci anni, e verificare una firma rispetto a qualcosa di diverso da se stessa.
Questo articolo illustra le nuove funzionalità con il codice Delphi per ciascuna di esse. Alla fine c'è anche una breve sezione sulle firme prodotte dalle versioni precedenti che andrebbero rifatte.
Elenchi di certificati tra cui scegliere
L'enumerazione dei certificati restituiva un elenco di nomi visualizzati, sufficiente per riempire una combo box ma non per prendere una decisione. Due carte della stessa autorità, intestate alla stessa persona, in quell'elenco appaiono identiche.
Ora l'enumerazione riporta l'impronta SHA-1, l'identificativo fiscale, il numero di serie, l'emittente e le date di validità, e funziona allo stesso modo per l'archivio certificati di Windows, un token PKCS#11 e un file PFX. I certificati scaduti e quelli privi di chiave privata possono essere esclusi. L'impronta si passa direttamente a SelectCertificateByThumbprint, così il certificato scelto dall'utente è esattamente quello che firma.
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;
La chiamata senza parametri è invariata, quindi il codice esistente continua a funzionare.
Carte multi-slot inventariate senza PIN
Una carta di firma qualificata contiene spesso più di un certificato, ciascuno protetto dal proprio PIN. Il caso più frequente sono le carte polacche, una carta Certum con due profili o una carta PWPW Sigillum con tre contenitori. Chiedere all'utente tre PIN solo per mostrargli un elenco non è un'interfaccia praticabile.
Ora un token PKCS#11 può essere inventariato senza effettuare alcun login. TokenSlotCount indica quanti slot contengono effettivamente un token, cioè l'intervallo che vale la pena interrogare, e ogni voce registra lo slot e l'etichetta del token da cui proviene, così il PIN corretto viene richiesto solo quando quel certificato è quello scelto.
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;
Trovare il certificato che ha emesso il tuo
I profili di firma a lungo termine richiedono il certificato emittente, e la maggior parte delle carte di firma qualificata contiene soltanto il tuo. Due nuove chiamate, presenti su ogni key provider, lo individuano: GetIssuerCertificate restituisce il certificato che ha emesso quello con cui stai firmando, e GetCertificateChain restituisce l'intero percorso superiore. La corrispondenza viene controllata a livello crittografico anziché per nome, così un'autorità che ha rinnovato la propria chiave di firma non viene confusa con la precedente.
Dove cercare è una decisione, quindi è una proprietà. Per impostazione predefinita viene consultato l'archivio certificati di Windows, iluLocalStore cerca nei file PEM o DER che distribuisci con la tua applicazione, e iluAIA scarica il certificato dall'indirizzo contenuto nel tuo, opzione disattivata per impostazione predefinita perché accede alla rete.
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;
Due nuovi profili PAdES
spPAdESBasicT firma con una marca temporale incorporata e senza dati di revoca, ed è quello che serve quando la firma deve soltanto dimostrare quando è stata apposta. spPAdESDocumentArchive va nella direzione opposta e aggiunge una marca temporale di archivio sopra il profilo a lungo termine, coprendo l'intero documento compresi i suoi dati di revoca, così il file resta verificabile anche dopo che è trascorsa la finestra di validità della prima marca temporale.
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;
I certificati riportano tutto quello che contengono
Il soggetto e l'emittente riportavano i sette attributi riconosciuti dal parser e scartavano il resto. Ora riportano ogni attributo presente nel certificato, l'indirizzo postale viene decodificato in righe leggibili e qualsiasi attributo può essere letto tramite il suo 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;
Richieste di marca temporale firmate
Alcune autorità di marcatura temporale qualificate, in particolare quelle polacche, non rispondono a una semplice richiesta RFC 3161. Vogliono che la richiesta stessa sia incapsulata in un CMS SignedData e firmata. Ora è una proprietà, non più qualcosa da costruire a mano.
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;
La forma predefinita corrisponde a una richiesta accettata dall'autorità di marcatura temporale PWPW Sigillum. Il codice esistente continua a inviare una richiesta semplice, non cambia nulla finché non imposti RequestFormat.
Cross-certificate Authenticode
La firma di un driver in modalità kernel deve risalire alla Microsoft Code Verification Root attraverso un cross-certificate, che è ciò che signtool /ac incorpora. Ora sgcSign può incorporare certificati aggiuntivi allo stesso modo.
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;
Confluiscono in ogni firma annidata e, se non se ne aggiunge nessuno, la firma resta byte per byte quella di prima. Il server di firma accetta un campo add_certs e la CLI un'opzione --add-cert ripetibile.
Verifica con le trust anchor
È la modifica più importante della versione. Fino a oggi il verificatore prendeva il certificato di firma dal documento che stava controllando e confermava che quella chiave aveva firmato quel documento. Questo dimostra che chi ha scritto il documento ha scritto anche la firma che contiene, e nulla di più. Chiunque può produrre un documento che supera il controllo.
Ora alla verifica si possono fornire delle trust anchor, e la catena di certificati viene costruita e controllata rispetto a esse. Una anchor viene riconosciuta tramite l'impronta SHA-256 o verificando sotto la sua stessa chiave, mai per nome.
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;
Un verificatore senza anchor restituisce lo stesso verdetto di prima, quindi l'aggiornamento non rompe nulla. Una cosa però cambia: il report ETSI TS 119 102-2 non riporta più total-passed per una firma che non è mai stata collegata a una anchor, riporta indeterminate con NO_CERTIFICATE_CHAIN_FOUND. I report salvati con le versioni precedenti vanno rigenerati.
Un unico trasporto HTTP, con i proxy
La libreria effettua richieste di rete da più punti: il client di marcatura temporale, i client OCSP e delle liste di revoca, il download della trust list UE e i key provider cloud. Ciascuno aveva una propria idea di come effettuarle. Ora condividono un unico trasporto con un'unica proprietà 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;
Il proxy può essere quello di sistema, nessuno, un indirizzo esplicito, oppure l'impostazione per utente risolta tramite WPAD o uno script PAC, che è quello che fa il browser. Ogni impostazione ha come valore predefinito il comportamento che quelle richieste avevano prima. Per un gateway che queste impostazioni non riescono a descrivere, un nuovo evento OnHTTPRequest sostituisce integralmente il trasporto.
Altre novità che vale la pena conoscere
Nonce OCSP. La richiesta di revoca ora contiene un nonce casuale prodotto dal generatore crittografico di sistema e la risposta viene controllata rispetto a esso. Una risposta che non riporta alcun nonce viene comunque accettata, perché RFC 6960 consente risposte pre-prodotte, mentre una che riporta un nonce diverso viene rifiutata. NonceEnabled disattiva l'estensione per un responder che non la accetta.
Pinning dei pivot della trust list UE. Una nuova proprietà RequirePinnedPivot stabilisce se l'elenco delle trust list debba risalire a una delle impronte pivot della Gazzetta Ufficiale registrate, con LOTLPivotPinned e LastPivotFingerprint a riportare l'esito. Il controllo esisteva e non veniva richiamato da nessuna parte. Le costanti registrate che vengono distribuite sono ancora i segnaposto documentati, quindi viene segnalato un mancato riscontro finché non inserisci le impronte reali e attivi la proprietà.
Un digest a tua scelta. CAdES e PKCS#11 ricevono entrambi una proprietà HashAlgorithm, con valore predefinito SHA-256 in modo che il codice esistente produca gli stessi byte. CAdES scriveva SHA-256 come valore letterale in ogni algoritmo di digest, e PKCS#11 sceglieva la propria intestazione DigestInfo in base alla lunghezza di ciò che riceveva, quindi non era possibile esprimere nessun altro digest. Ora una carta può firmare con SHA-1, SHA-256, SHA-384 o SHA-512, come richiesto.
ASiC con una callback di firma. Un nuovo overload di BuildCAdES accetta una callback al posto dei byte di firma già pronti. Costruisce prima META-INF/ASiCManifest.xml, passa esattamente quei byte alla tua callback e memorizza ciò che riceve indietro come META-INF/signature.p7s, che è l'unico ordine in cui la firma può coprire il manifest. Per ASiC-S, che non contiene alcun manifest, la callback riceve il documento di dati stesso. GetCAdESSignedData restituisce gli stessi byte per chi preferisce due passaggi espliciti.
Certificati per le KMS cloud. AWS KMS e Google Cloud KMS ricevono SetCertificate e SetCertificateFromFile, la stessa coppia che HashiCorp Vault aveva già. Entrambi i servizi restituiscono soltanto una chiave pubblica, e prima non c'era modo di indicare a nessuno dei due provider quale certificato X.509 le appartiene.
Una marca temporale dalla macchina che esegue la CLI. La riga di comando sgcsign guadagna --tsa-direct, che interroga direttamente l'autorità di marcatura temporale invece di passare per il sgcSign Server. Va passata insieme a --tsa.
Il server di firma
Anche il lato server ha il suo elenco di novità. Una firma Authenticode può ora contenere più di due firme annidate con un certificato diverso per ciascuna, tramite un elenco ordinato hash_algorithms come sha1,sha256,sha384 oppure un elenco ordinato providers come certA:sha256,certB:sha1, fino a quattro voci in entrambi i casi. Serve per distribuire un unico file firmato sia da un certificato in scadenza sia dal suo sostituto. Ogni certificato viene controllato rispetto ai permessi della chiave API prima che inizi qualsiasi operazione di firma.
I file di catalogo di Windows possono essere firmati: l'endpoint di upload accetta catalog come formato e firma un file .cat esistente del tipo prodotto da makecat, così un pacchetto di driver si firma esattamente come si firma un programma.
Un nuovo endpoint /api/v1/sign/raw firma un digest che hai già calcolato e restituisce soltanto il valore della firma, senza involucro PKCS#7, senza attributi firmati e senza marca temporale. È esattamente quello che signtool richiede tramite la sua callback /dlib. Poiché firma qualunque digest gli venga passato, è disattivato per impostazione predefinita e si attiva un provider alla volta con allow_raw_sign.
Le chiavi API e gli utenti che le creano sono ora isolati per progetto, un amministratore di progetto gestisce le chiavi del proprio progetto, e le chiavi possono essere abilitate e disabilitate invece di essere soltanto revocate in modo definitivo. Il limite di frequenza e la quota giornaliera per chiave si possono modificare dopo la creazione della chiave. Una nuova impostazione SessionAbsoluteMaxMin limita la durata complessiva di una sessione di amministratore a dodici ore per impostazione predefinita, perché prima ogni richiesta autenticata spostava in avanti la scadenza senza alcun tetto. Il log di audit si può filtrare per indirizzo del client, sia nella console sia nell'esportazione CSV, con un indirizzo parziale che corrisponde a partire da sinistra. E nuove impostazioni per gli header inoltrati, disattivate per impostazione predefinita, recuperano l'indirizzo reale del client quando il server è dietro un reverse proxy, considerate attendibili solo quando la connessione arriva da un proxy elencato tra quelli fidati.
Firme che dovresti rifare
Tre difetti delle versioni precedenti producevano file strutturalmente errati, e l'aggiornamento non ripara un file già scritto. Se uno di questi casi descrive ciò che hai firmato, firmalo di nuovo con la 2026.9.0.
- Qualsiasi cosa firmata con una chiave EC. Tutte le firme PAdES, CAdES, Authenticode, NuGet e RFC 3161 prodotte con una chiave EC erano non valide. Gli standard richiedono che il valore della firma all'interno di una struttura CMS sia un DER ECDSA-Sig-Value, i key provider producono la concatenazione grezza di r e s, e nulla la convertiva lungo il percorso. Ora P-256, P-384 e P-521 funzionano tutte. I percorsi XAdES e XML-DSig erano corretti e sono stati deliberatamente lasciati invariati.
- Tutte le firme CAdES, e qualsiasi cosa firmata con una carta, un token USB o una chiave cloud.
SignDatanon aveva un contratto scritto e i provider non concordavano sul fatto che il suo argomento fossero i dati oppure un loro digest, quindi una firma CAdES era RSA su un hash di un hash. Ora l'argomento sono i byte grezzi e il provider li sottopone a hash con il digest configurato. - File PAdES firmati dopo che un altro firmatario aveva usato lo stesso key provider.
Profile.HashAlgorithmveniva scritto una sola volta nel costruttore e non veniva mai più letto, quindi un PDF firmato dopo un firmatario Facturae o SAF-T poteva essere RSA su un digest SHA-1 all'interno di una firma che dichiarava SHA-256.
La verifica è cambiata nella stessa direzione. La verifica Authenticode non controllava mai una firma, ricalcolava l'hash del file e lo confrontava con quello contenuto nella firma, quindi per falsificare un file che sgcSign dichiarava firmato validamente non serviva alcuna chiave privata. Le risposte di revoca e i token di marcatura temporale venivano incorporati senza essere verificati. La trust list UE veniva scaricata e usata senza verificare assolutamente nulla. Ora tutti questi passaggi eseguono il controllo che il loro nome lascia intendere, e il resoconto completo di ciascuno è nel changelog.
Come ottenerlo
sgcSign 2026.9.0 è disponibile da subito, con il codice sorgente completo e un anno di aggiornamenti, per Delphi 7 fino a Delphi 13 Florence, le corrispondenti versioni di C++ Builder e .NET.
Pagina del prodotto · Scarica la versione di prova · Changelog
Domande o commenti? Contattaci, riceverai una risposta dalle persone che hanno scritto il codice.
