Signature numérique
Faire signer un PDF par signature numérique : le SDK certifie le PDF avec un certificat, puis l'envoie au signataire. C'est le mode de signature recommandé.
Prérequis
- Le module de signature de votre langage :
SecureExchanges.Signature.NetCoreen .NET 8 (SecureExchanges.Signatureen .NET Framework),secure-exchanges-sdk[signature]en Python ; il est inclus dans le paquet PHP. Voir Choisir son paquet. - Un navigateur Chromium sur la machine qui certifie : le SDK le pilote sans interface.
- .NET : voir Docker et Linux pour un serveur Linux ou un conteneur.
- Python :
playwright install chromium, une fois par machine (environ 150 Mo). - PHP : Chrome ou Chromium installé ; hors des emplacements habituels, donnez son chemin dans
SE_CHROME_PATH.
- Vos identifiants chargés : voir Identifiants et environnements.
Les six étapes
- Lire le PDF et calculer son empreinte SHA-512. Elle désigne le fichier dans les étapes suivantes.
- Placer les zones à signer avec
ZoneBuilder: signature, date, initiales, champs. Voir Zones de signature. - Associer les zones au signataire, pour ce fichier.
- Obtenir le jeton de certification du document avec
GetCertificationToken. - Certifier le PDF avec
CertifyPkiPdfdePkiFileHelper. - Envoyer le PDF certifié avec
MultiRecipientMessage, en le passant dansCertifiedPdfs.
Exemple complet
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using SecureExchangesSDK.Helpers;
using SecureExchangesSDK.Models;
using SecureExchangesSDK.Models.Args;
using SecureExchangesSDK.Models.Entity;
using SecureExchangesSDK.Models.JSON;
using SecureExchangesSDK.Models.Transport;
using SecureExchangesSDK.SecureExchanges;
using SecureExchangesSignatureSDK.Helpers;
using SecureExchangesSignatureSDK.Models.Args;
internal static class CertificationPki
{
private static async Task EnvoyerPourSignature(string cheminPdf, string signataire)
{
// Vos identifiants viennent de votre coffre de secrets, jamais du code.
Guid serial = LireSecret("SecureExchanges:Serial");
Guid apiUser = LireSecret("SecureExchanges:ApiUser");
Guid apiPassword = LireSecret("SecureExchanges:ApiPassword");
var endpoint = new Uri("https://www.secure-exchanges.com/_api/0217/0217.asmx");
// 1. Lire le PDF et calculer son empreinte SHA-512 : elle désigne le fichier dans les étapes suivantes.
byte[] pdf = File.ReadAllBytes(cheminPdf);
string sha512 = CryptoHelper.GetSHA512HashOfBytes(pdf);
// 2. Placer les zones à signer (voir « Zones de signature »).
SignZoneDetails[] zones;
using (var zoneBuilder = new ZoneBuilder(pdf, isPki: true))
{
zones = new[]
{
zoneBuilder.CreateFieldSignZone(page: 1, fieldName: "signature1", x: 60, y: 400, width: 200, height: 50),
zoneBuilder.CreateFieldDateSignZone(page: 1, fieldName: "date1", x: 60, y: 500, width: 100, height: 25),
};
}
// 3. Associer les zones au signataire.
var zonesParSignataire = new List<RecipientZoneDefinition>
{
new RecipientZoneDefinition
{
Email = signataire,
ZonesDefByFile = new[]
{
new FileZoneDefinition
{
UniqueName = sha512,
IsPKIFile = true,
ZonesDef = new SignZoneDefinition { Mode = "define", Zones = zones }
}
}
}
};
// 4. Obtenir le jeton de certification du document.
var jeton = SignHelper.GetCertificationToken(new GetCertificationTokenSDKArgs(
endpoint, serial, apiUser, apiPassword, sha512, CertificationTokenType.CertifyDocument, "fr-CA"));
if (jeton.Status != 200)
throw new InvalidOperationException($"Jeton de certification refusé, statut {jeton.Status}");
// 5. Certifier le PDF. PkiFileHelper pilote un navigateur sans interface : le libérer après usage.
CertifiedPdfContainer pdfCertifie;
await using (var pki = new PkiFileHelper(isPreview: false, culture: "fr-CA"))
{
pdfCertifie = await pki.CertifyPkiPdf(new CertifyPdfArgs
{
File = new FileArgs(pdf, Path.GetFileName(cheminPdf), "application/pdf"),
Recipients = new List<string> { signataire },
CertifyData = jeton.CertificationData,
RecipientZoneDefinitions = zonesParSignataire
});
}
// 6. Envoyer le PDF certifié au signataire.
var args = new MutliRecipientArgs(
endPointUri: endpoint, serial: serial, user: apiUser, psw: apiPassword,
recipients: new List<RecipientInfo> { new RecipientInfo { Email = signataire } },
message: "Veuillez signer le document ci-joint.",
subject: "Document à signer",
password: null, filesList: null,
sendMode: SendMethodEnum.onlyEmail, getBackHtml: false,
showSubject: true, getNotify: true, cultureID: "fr-CA",
maximumOpenTime: 5, minutesExpiration: 10080);
args.CertifiedPdfs = new List<CertifiedPdfContainer> { pdfCertifie };
args.OwnerDontNeedToSign = true; // sinon vous signez aussi, en dernier : false par défaut en .NET
var response = MessageHelper.MultiRecipientMessage(args);
if (response.Status != 200)
throw new InvalidOperationException($"Envoi refusé, statut {response.Status}");
}
private static Guid LireSecret(string nom)
{
// À remplacer : lecture dans votre gestionnaire de secrets (Azure Key Vault, etc.).
throw new NotImplementedException(nom);
}
}
Note
En .NET Framework (paquet SecureExchanges.Signature), PkiFileHelper se libère avec using plutôt qu'avec await using.
À savoir
| Point | Détail |
|---|---|
| Le même PDF à chaque étape | L'empreinte SHA-512 désigne le fichier : ne le modifiez pas entre son calcul et la certification. |
| Préproduction | Passez isPreview à true dans votre configuration de test, avec vos identifiants de préproduction. Il doit suivre le même environnement que vos autres appels : true avec la configuration de préproduction, false en production. |
| Champs déjà présents dans le PDF | Avec DetectExistingFields (true par défaut), ils sont détectés ; DefineRecipients permet de les répartir entre les signataires. |
| Ressources | PkiFileHelper lance un navigateur : libérez-le dès la certification faite, comme dans l'exemple. |
| Plusieurs signataires | Ajoutez une définition de zones par signataire, et tous les signataires dans la liste des destinataires, dans l'ordre de signature : voir Plusieurs signataires. |
| Options sans PKI | DoNotEncryptPDF et DoNotAppendCertificateToFile sont obsolètes et n'ont pas à être utilisées. Voir Passer à la signature numérique. |
Plusieurs signataires
Les signatures se font l'une après l'autre, dans l'ordre de la liste des destinataires :
- Le premier signataire reçoit le courriel dès l'envoi.
- Chaque signataire suivant le reçoit quand le précédent a signé.
- Quand tout est signé, les signataires et le propriétaire de la licence reçoivent les documents signés.
Il n'y a pas d'envoi en parallèle : l'ordre de la liste est l'ordre de signature. Pour aviser des personnes qui ne signent pas, voir Destinataires en copie.
Le propriétaire de la licence
Le propriétaire de la licence qui envoie le message est toujours ajouté en dernier au circuit. OwnerDontNeedToSign décide s'il doit signer :
| Valeur | Effet |
|---|---|
| true | Il n'a pas à signer : il reçoit seulement les documents signés à la fin. |
| false | Il signe en dernier, après tous les autres. Il lui faut alors ses propres zones, sinon l'envoi est refusé. |
Attention
La valeur par défaut n'est pas la même partout : false en .NET, true en Python et en PHP. Fixez-la toujours explicitement, comme dans l'exemple.
Options de signature
SignatureOptions, de type SignOptions, règle trois aspects du circuit : les modes de signature offerts, un fichier que le signataire doit joindre, et des destinataires en copie qui ne signent pas. Il n'a d'effet que sur un envoi avec des documents à signer. L'exemple reprend l'étape 6 de l'exemple complet :
using System;
using System.Collections.Generic;
using SecureExchangesSDK.Helpers;
using SecureExchangesSDK.Models;
using SecureExchangesSDK.Models.Args;
using SecureExchangesSDK.Models.Entity;
using SecureExchangesSDK.Models.Enum;
using SecureExchangesSDK.Models.JSON;
using SecureExchangesSDK.Models.Transport;
using SecureExchangesSDK.SecureExchanges;
internal static class OptionsSignature
{
// Étape 6 de l'exemple complet, avec les options de signature : pdfCertifie vient de l'étape 5.
private static void EnvoyerAvecOptions(CertifiedPdfContainer pdfCertifie, string signataire,
string enCopie, string enCopieCachee)
{
// Vos identifiants viennent de votre coffre de secrets, jamais du code.
Guid serial = LireSecret("SecureExchanges:Serial");
Guid apiUser = LireSecret("SecureExchanges:ApiUser");
Guid apiPassword = LireSecret("SecureExchanges:ApiPassword");
var endpoint = new Uri("https://www.secure-exchanges.com/_api/0217/0217.asmx");
var options = new SignOptions
{
// Mode permis pour la signature et les initiales : ici, tracer seulement.
SignatureRestriction = SignatureModeRestriction.Trace,
// Le signataire doit joindre un fichier pour terminer : un seul signataire dans ce cas.
UploadFileOptions = new SignOptionsFileUpload
{
UploadMode = ReplyFileUploadMode.UploadMandatory,
AuthorizedExtensions = new[] { ".pdf", ".jpg", ".png" },
Messages = new List<ReplyOptionsMessage>
{
new ReplyOptionsMessage { CultureID = "fr-CA", Message = "Joignez l'annexe B remplie." },
new ReplyOptionsMessage { CultureID = "en-CA", Message = "Attach the completed Schedule B." }
},
NoEditor = true
},
// Avisés dès l'envoi, ils reçoivent les documents signés à la fin, sans signer.
CCRecipients = new List<Recipient> { new Recipient { Email = enCopie } },
// Comme les précédents, mais masqués aux signataires.
BCCRecipients = new List<Recipient> { new Recipient { Email = enCopieCachee } }
};
var args = new MutliRecipientArgs(
endPointUri: endpoint, serial: serial, user: apiUser, psw: apiPassword,
recipients: new List<RecipientInfo> { new RecipientInfo { Email = signataire } },
message: "Veuillez signer le document ci-joint, puis y joindre l'annexe B remplie.",
subject: "Document à signer",
password: null, filesList: null,
sendMode: SendMethodEnum.onlyEmail, getBackHtml: false,
showSubject: true, getNotify: true, cultureID: "fr-CA",
maximumOpenTime: 5, minutesExpiration: 10080);
args.CertifiedPdfs = new List<CertifiedPdfContainer> { pdfCertifie };
args.SignatureOptions = options;
args.OwnerDontNeedToSign = true; // sinon vous signez aussi, en dernier : false par défaut en .NET
var response = MessageHelper.MultiRecipientMessage(args);
if (response.Status != 200)
throw new InvalidOperationException($"Envoi refusé, statut {response.Status}");
}
private static Guid LireSecret(string nom)
{
// À remplacer : lecture dans votre gestionnaire de secrets (Azure Key Vault, etc.).
throw new NotImplementedException(nom);
}
}
Modes de signature permis
SignatureRestriction désigne les modes que le signataire peut employer pour sa signature et ses initiales. Le sceau, toujours une image, n'est pas visé. La restriction vaut pour tous les signataires du circuit.
| Valeur | Mode offert |
|---|---|
NoRestriction |
Tous les modes. C'est la valeur par défaut. |
Text |
Saisir son nom au clavier : il est rendu en écriture cursive. |
Trace |
Tracer sa signature à la souris ou au doigt. |
Picture |
Téléverser une image de sa signature, en PNG ou en JPEG, de 500 Ko au plus. |
Les valeurs se combinent avec l'opérateur | : Text et Trace ensemble offrent la saisie et le tracé.
En PHP, combinez leurs ->value avec l'opérateur | : SignatureModeRestriction::TEXT->value | SignatureModeRestriction::TRACE->value offre la saisie et le tracé. Une valeur qui ne correspond à aucun mode est refusée avant l'envoi.
Fichier à joindre
UploadFileOptions reçoit un SignOptionsFileUpload, qui demande au signataire de joindre des fichiers à ses documents signés.
| Propriété | Rôle |
|---|---|
UploadMode |
NoUpload, la valeur par défaut : aucun fichier ne peut être joint. UploadOptional : le signataire peut en joindre. UploadMandatory : il ne peut pas terminer sans en joindre au moins un. |
AuthorizedExtensions |
Extensions permises, avec le point, par exemple .pdf. Un fichier d'une autre extension est refusé ; les documents signés ne sont pas visés. Sans liste, toute extension est permise. |
Messages |
La consigne affichée au signataire : un ReplyOptionsMessage par culture. Seule s'affiche la consigne dont la culture est exactement celle de la page, fr-CA ou en-CA : fournissez les deux. Écrivez du texte simple. |
NoEditor |
true : le signataire ne peut pas écrire de message, il joint seulement des fichiers. |
NoReply |
Sans effet en signature. |
Messages accepte aussi des dictionnaires en Python, et des tableaux en PHP, avec les clés CultureID et Message.
Important
UploadOptional et UploadMandatory exigent un seul signataire. Avec plusieurs destinataires, l'envoi est refusé.
Destinataires en copie
CCRecipients et BCCRecipients ajoutent des destinataires qui ne signent pas :
- Dès l'envoi, ils sont avisés que des documents sont à signer. Ils ne peuvent pas les signer.
- Quand tout est signé, ils reçoivent les documents signés.
Ceux de BCCRecipients sont masqués : les signataires ne les voient pas, et le courriel final ne les nomme pas.
Chaque destinataire en copie a une adresse courriel, un numéro de téléphone, ou les deux. Avec un numéro seulement, il est joint par SMS ; avec les deux, il reçoit le courriel et un code par SMS.
En .NET, ce sont des Recipient de l'espace de noms SecureExchangesSDK.SecureExchanges : seuls Email et Phone comptent.
En Python, ce sont des Recipient, exportés par secure_exchanges_sdk.models.entity.
En PHP, ce sont des RecipientInfo, comme pour les signataires.
Erreurs fréquentes
| Symptôme | Cause probable | Solution |
|---|---|---|
| Échec au lancement du navigateur | Chromium absent ou dépendances système manquantes | Voir Docker et Linux. |
| Envoi refusé : « Not all recipients have fields defined for all files » | OwnerDontNeedToSign à false, le défaut en .NET, sans zones pour le propriétaire |
Passez-le à true, ou ajoutez ses zones. |
| Envoi refusé, message commençant par « The signature file upload mode » | UploadOptional ou UploadMandatory avec plusieurs signataires |
Gardez un seul signataire, ou passez UploadMode à NoUpload. |
| Jeton de certification refusé | Identifiants, environnement ou licence | Voir Codes de statut et Licence et quotas. |
| Les zones n'apparaissent pas | Zones associées à un autre fichier ou à une autre adresse | Vérifiez l'empreinte SHA-512 et l'adresse du signataire. |
Et ensuite
- Zones de signature
- Journaux : suivre l'ouverture et la signature
- Azure Functions