sgcSign 2026.9.0 est une version majeure. L'essentiel provient de demandes de clients, et tout se concentre sur trois domaines : savoir avec quel certificat vous vous apprêtez à signer, construire une signature qu'un validateur acceptera encore dans dix ans, et vérifier une signature par rapport à autre chose qu'elle-même.
Cet article passe en revue les nouvelles fonctionnalités avec le code Delphi correspondant à chacune. Une courte section à la fin traite des signatures produites par les versions antérieures qui devraient être refaites.
Des listes de certificats dans lesquelles choisir
L'énumération des certificats renvoyait jusqu'ici une liste de noms d'affichage, ce qui suffit à remplir une liste déroulante mais pas à prendre une décision. Deux cartes de la même autorité, délivrées à la même personne, sont identiques dans cette liste.
L'énumération transporte désormais l'empreinte SHA-1, l'identifiant fiscal, le numéro de série, l'émetteur et les dates de validité, et elle fonctionne de la même manière pour le magasin de certificats Windows, un jeton PKCS#11 et un fichier PFX. Les certificats expirés et les certificats sans clé privée peuvent être écartés. L'empreinte se passe directement à SelectCertificateByThumbprint, de sorte que le certificat choisi par l'utilisateur est bien celui qui signe.
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;
L'appel sans paramètres est inchangé, le code existant continue donc de fonctionner.
Inventaire des cartes multi-slots sans code PIN
Une carte de signature qualifiée contient souvent plusieurs certificats, chacun protégé par son propre code PIN. Les cartes polonaises sont le cas le plus fréquent, une carte Certum avec deux profils ou une carte PWPW Sigillum avec trois conteneurs. Demander trois codes PIN à l'utilisateur juste pour lui afficher une liste n'est pas une interface viable.
Un jeton PKCS#11 peut désormais être inventorié sans se connecter du tout. TokenSlotCount indique combien de slots contiennent réellement un jeton, c'est-à-dire la plage qu'il vaut la peine de parcourir, et chaque entrée conserve le slot et l'étiquette du jeton dont elle provient, de sorte que le bon code PIN n'est demandé que lorsque ce certificat est celui qui a été retenu.
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;
Trouver le certificat qui a émis le vôtre
Les profils de signature à long terme ont besoin du certificat émetteur, et la plupart des cartes de signature qualifiée ne portent que le vôtre. Deux nouveaux appels, présents sur chaque fournisseur de clés, le trouvent : GetIssuerCertificate renvoie le certificat qui a émis celui avec lequel vous signez, et GetCertificateChain renvoie tout le chemin au-dessus. La correspondance est contrôlée cryptographiquement et non par le nom, ainsi une autorité qui a renouvelé sa clé de signature n'est pas confondue avec sa devancière.
L'endroit où chercher est une décision, c'est donc une propriété. Le magasin de certificats Windows est interrogé par défaut, iluLocalStore cherche dans les fichiers PEM ou DER que vous livrez avec votre application, et iluAIA télécharge le certificat depuis l'adresse inscrite dans le vôtre, ce qui est désactivé par défaut car cela suppose un accès au réseau.
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;
Deux nouveaux profils PAdES
spPAdESBasicT signe avec un horodatage intégré et sans données de révocation, ce qui correspond à ce que vous voulez quand la signature doit seulement prouver le moment où elle a été faite. spPAdESDocumentArchive va dans l'autre sens et ajoute un horodatage d'archivage par-dessus le profil à long terme, couvrant le document entier y compris ses données de révocation, de sorte que le fichier reste vérifiable une fois passée la période de validité du premier horodatage.
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;
Les certificats rapportent tout ce qu'ils contiennent
Le sujet et l'émetteur ne rapportaient que les sept attributs reconnus par l'analyseur, et laissaient tomber le reste. Ils rapportent désormais tous les attributs du certificat, l'adresse postale est décodée en lignes lisibles, et n'importe quel attribut peut être lu par son 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;
Demandes d'horodatage signées
Certaines autorités d'horodatage qualifiées, les polonaises en particulier, ne répondent pas à une simple requête RFC 3161. Elles veulent que la requête elle-même soit encapsulée dans un CMS SignedData et signée. C'est maintenant une propriété plutôt qu'une construction manuelle.
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 forme par défaut correspond à une requête acceptée par l'autorité d'horodatage PWPW Sigillum. Le code existant continue d'envoyer une requête simple, rien ne change tant que vous ne définissez pas RequestFormat.
Certificats croisés Authenticode
La signature d'un pilote en mode noyau doit chaîner jusqu'à la racine Microsoft Code Verification Root par l'intermédiaire d'un certificat croisé, ce que signtool /ac intègre. sgcSign sait désormais intégrer des certificats supplémentaires de la même manière.
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;
Ils se propagent dans chaque signature imbriquée, et n'en ajouter aucun laisse la signature octet pour octet telle qu'elle était. Le serveur de signature accepte un champ add_certs et la ligne de commande une option --add-cert répétable.
Vérification avec des ancres de confiance
C'est le changement le plus important de cette version. Jusqu'ici, le vérificateur extrayait le certificat de signature du document qu'il contrôlait et confirmait que cette clé avait signé ce document. Cela prouve que celui qui a écrit le document a aussi écrit la signature qu'il contient, et rien de plus. N'importe qui peut produire un document qui passe le contrôle.
La vérification peut désormais recevoir des ancres de confiance, et elle construit puis contrôle la chaîne de certificats par rapport à celles-ci. Une ancre est reconnue par son empreinte SHA-256 ou par une vérification sous sa propre clé, jamais par le nom.
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 vérificateur sans ancre renvoie le verdict qu'il renvoyait auparavant, rien ne casse donc à la mise à jour. Une chose change tout de même : le rapport ETSI TS 119 102-2 n'indique plus total-passed pour une signature qui n'a jamais été chaînée à une ancre, il indique indeterminate avec NO_CERTIFICATE_CHAIN_FOUND. Les rapports enregistrés par les versions antérieures doivent être régénérés.
Un seul transport HTTP, avec les proxys
La bibliothèque effectue des requêtes réseau depuis plusieurs endroits : le client d'horodatage, les clients OCSP et de listes de révocation, le téléchargement de la liste de confiance de l'UE et les fournisseurs de clés dans le cloud. Chacun avait sa propre idée de la façon d'en émettre une. Ils partagent désormais un transport unique avec une seule propriété 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;
Le proxy peut être celui de la machine, aucun proxy, une adresse explicite, ou le paramétrage propre à l'utilisateur résolu par WPAD ou un script PAC, ce que fait le navigateur. Chaque paramètre a pour valeur par défaut ce que faisaient ces requêtes auparavant. Pour une passerelle que ces paramètres ne savent pas décrire, un nouvel événement OnHTTPRequest remplace entièrement le transport.
Petites choses bonnes à savoir
Nonces OCSP. La requête de révocation transporte désormais un nonce aléatoire issu du générateur cryptographique du système et la réponse est contrôlée par rapport à celui-ci. Une réponse qui ne renvoie aucun nonce reste acceptée, car la RFC 6960 autorise les réponses pré-produites, mais une réponse qui en renvoie un différent est refusée. NonceEnabled désactive l'extension pour un répondeur qui ne l'accepte pas.
Épinglage des pivots de la liste de confiance de l'UE. Une nouvelle propriété RequirePinnedPivot détermine si la liste des listes de confiance doit chaîner vers l'une des empreintes de pivot épinglées du Journal officiel, LOTLPivotPinned et LastPivotFingerprint rapportant le résultat. Le contrôle existait et n'était appelé de nulle part. Les constantes épinglées livrées restent les valeurs de substitution documentées, un échec est donc signalé tant que vous n'avez pas renseigné de véritables empreintes et activé la propriété.
Une empreinte que vous choisissez. CAdES et PKCS#11 reçoivent tous deux une propriété HashAlgorithm, avec SHA-256 par défaut pour que le code existant produise les mêmes octets. CAdES écrivait SHA-256 en dur dans chaque algorithme d'empreinte, et PKCS#11 choisissait son en-tête DigestInfo d'après la longueur de ce qu'on lui passait, si bien qu'aucune autre empreinte ne pouvait être exprimée. Une carte peut maintenant signer avec SHA-1, SHA-256, SHA-384 ou SHA-512, comme demandé.
ASiC avec un rappel de signature. Une nouvelle surcharge de BuildCAdES prend un rappel au lieu d'octets de signature déjà produits. Elle construit d'abord META-INF/ASiCManifest.xml, transmet ces octets exacts à votre rappel et stocke ce qui en revient sous META-INF/signature.p7s, ce qui est le seul ordre dans lequel la signature peut couvrir le manifeste. Pour ASiC-S, qui ne comporte pas de manifeste, le rappel reçoit le document de données lui-même. GetCAdESSignedData renvoie les mêmes octets pour ceux qui préfèrent deux étapes explicites.
Certificats des KMS dans le cloud. AWS KMS et Google Cloud KMS reçoivent SetCertificate et SetCertificateFromFile, à l'image du duo dont HashiCorp Vault disposait déjà. Les deux services ne fournissent qu'une clé publique nue, et il n'existait jusqu'ici aucun moyen d'indiquer à l'un ou l'autre fournisseur quel certificat X.509 lui correspond.
Un horodatage depuis la machine qui exécute la ligne de commande. La ligne de commande sgcsign reçoit --tsa-direct, qui interroge directement l'autorité d'horodatage au lieu de passer par le sgcSign Server. Passez-la avec --tsa.
Le serveur de signature
Le côté serveur a eu sa propre liste. Une signature Authenticode peut désormais porter plus de deux signatures imbriquées avec un certificat différent pour chacune, au moyen d'une liste ordonnée hash_algorithms telle que sha1,sha256,sha384 ou d'une liste ordonnée providers telle que certA:sha256,certB:sha1, jusqu'à quatre entrées dans les deux cas. C'est ce qu'il faut pour livrer un même fichier signé par un certificat qui expire et par son remplaçant. Chaque certificat est contrôlé par rapport aux permissions de la clé API avant que la moindre signature ne commence.
Les fichiers catalogue Windows peuvent être signés : le point de terminaison d'envoi accepte catalog comme format et signe un fichier .cat existant du type de ceux que produit makecat, de sorte qu'un paquet de pilotes se signe comme un programme.
Un nouveau point de terminaison /api/v1/sign/raw signe une empreinte que vous avez déjà calculée et ne renvoie que la valeur de signature, sans enveloppe PKCS#7, sans attributs signés et sans horodatage. C'est exactement ce que demande signtool par son rappel /dlib. Comme il signe n'importe quelle empreinte qu'on lui transmet, il est désactivé par défaut et s'active fournisseur par fournisseur avec allow_raw_sign.
Les clés API et les utilisateurs qui les créent sont désormais isolés par projet, un administrateur de projet gère les clés de son propre projet, et les clés peuvent être activées et désactivées au lieu d'être seulement révoquées sans retour. La limite de débit et le quota quotidien propres à chaque clé peuvent être modifiés après la création de la clé. Un nouveau paramètre SessionAbsoluteMaxMin plafonne la durée de vie totale d'une session d'administrateur à douze heures par défaut, car chaque requête authentifiée repoussait auparavant l'expiration sans aucune limite. Le journal d'audit peut être filtré par adresse cliente, dans la console comme dans l'export CSV, une adresse partielle étant comparée à partir de la gauche. Et de nouveaux paramètres d'en-têtes transférés, désactivés par défaut, récupèrent l'adresse cliente réelle quand le serveur fonctionne derrière un proxy inverse, adresse qui n'est prise en compte que si la connexion provient d'un proxy de confiance déclaré.
Les signatures que vous devriez refaire
Trois défauts des versions antérieures ont produit des fichiers structurellement incorrects, et la mise à jour ne répare pas un fichier déjà écrit. Si l'un de ces cas correspond à ce que vous avez signé, signez-le de nouveau avec 2026.9.0.
- Tout ce qui a été signé avec une clé EC. Toutes les signatures PAdES, CAdES, Authenticode, NuGet et RFC 3161 réalisées avec une clé EC étaient invalides. Les normes exigent que la valeur de signature à l'intérieur d'une structure CMS soit une ECDSA-Sig-Value DER, les fournisseurs de clés produisent la concaténation brute de r et s, et rien ne la convertissait au passage. P-256, P-384 et P-521 fonctionnent tous désormais. Les chemins XAdES et XML-DSig étaient corrects et ont délibérément été laissés tels quels.
- Toutes les signatures CAdES, et tout ce qui a été signé avec une carte, un jeton USB ou une clé dans le cloud.
SignDatan'avait aucun contrat écrit et les fournisseurs n'étaient pas d'accord sur le fait que son argument soit les données ou une empreinte de celles-ci, de sorte qu'une signature CAdES était du RSA sur une empreinte d'empreinte. L'argument correspond maintenant aux octets bruts et le fournisseur les hache avec l'empreinte qu'il a configurée. - Les fichiers PAdES signés après qu'un autre signataire a utilisé le même fournisseur de clés.
Profile.HashAlgorithmétait écrit une seule fois dans le constructeur et n'était plus jamais relu, de sorte qu'un PDF signé après un signataire Facturae ou SAF-T pouvait être du RSA sur une empreinte SHA-1 à l'intérieur d'une signature déclarant SHA-256.
La vérification a évolué dans le même sens. La vérification Authenticode ne contrôlait jamais de signature, elle recalculait l'empreinte du fichier et la comparait à celle inscrite dans la signature, si bien que falsifier un fichier que sgcSign déclarait valablement signé ne demandait aucune clé privée. Les réponses de révocation et les jetons d'horodatage étaient intégrés sans être vérifiés. La liste de confiance de l'UE était téléchargée et utilisée sans aucune vérification. Tous ces contrôles font désormais ce que leur nom annonce, et le détail complet de chacun figure dans le journal des modifications.
Se la procurer
sgcSign 2026.9.0 est disponible dès maintenant, avec le code source complet et un an de mises à jour, pour Delphi 7 jusqu'à Delphi 13 Florence, les versions correspondantes de C++ Builder, et .NET.
Page du produit · Télécharger la version d'essai · Journal des modifications
Des questions ou des remarques ? Contactez-nous, vous recevrez une réponse des personnes qui ont écrit le code.
