Ce document a pour but de présenter la signature de données et le contrôle de cette signature à l'aide d'une paire de clés
asymétiques en utilisant l'API Windows
Ce document a pour but de présenter le principe de fonctionnement de l'API Windows concernant la cryptographie
en prenant l'exemple de la signature de données.
Le code a été écrit et testé avec Borland C++ Builder 6 Enterprise anglais.
Bien entendu, toute remarque constructive est la bienvenue.
Pour une description détaillée des fonctions et valeurs permises pour les paramètres, tout se trouve dans MSDN ,
rubrique Security / Cryptography. La description y est exhaustive, mais parfois complexe.
Les termes 'clé publique', clé privée', 'Certificat' ,'Autorité de Certification' doivent idéalement vous être,
si pas familiers, du moins connus. Si ce n'est pas le cas, je vous invite à vous documenter sur le sujet.
En très résumé, mais de façon à pouvoir comprendre la suite du tutoriel.
La clé privée est celle que vous gardez précieusement.
La clé publique est celle que vous pouvez distribuer.
L'Autorité de Certification est un organisme, une société, une personne, etc ...
à qui l'on accorde sa confiance pour certifier que la personne, la société est bien qui elle prétend être.
un Certificat est une clé publique, certifiée par une Autorité de Certification.
La clé que j'ai utilisé pour ces tests provient de TrustCenter.
Cette autorité de certification offre des clés valables pour une durée d'un an, gratuitement, à des fins d'évaluation.
Il en existe d'autres.
La paire de clés doit être enregistrée dans le magasin de certificats de Windows. Ce magasin est consultable dans
Internet Explorer / Outils / Options Internet / Contenu / Certificats. Lorsque vous sélectionnez un élément de la liste
'Personnel', et que vous cliquez sur le bouton Affichage, dans la fenêtre des propriétés qui apparaît, l'information
'Vous avez une clé privée qui correspond à ce certificat' doit être présent.
(cfr la zone en rouge dans la capture d'écran ci-dessous.)
1. Principe
La signature de données, dans son principe, est très simple : c'est la création d'un 'marquage'
à partir des données et de la clé privée.
Le moindre changement des données génère un marquage tout à fait différent.
Ce qui permet de garantir que les données transmises
n'ont pas été altérées. La signature permet aussi d'authentifier la personne qui envoie les données.
Le contrôle de la signature, est, quant à lui effectué grâce à la clé publique.
Il permet de vérifier que la signature a bien été faite par la clé privée qui lui correspond, mais ne permet en
aucun cas de régénérer cette signature. Seul le possesseur de la clé privée peut le faire.
La signature peut être liée aux données, ou séparée de ces données. On parle dans ce second cas, de signature détachée.
2. Préparation du projet
La première chose est de lancer en ligne de commande, dans le répertoire System de Windows la commande suivante
IMPLIB crypt32.lib crpt32.dll pour créer une librairie d'importation.
Pour une application à mettre en production, le mieux serait de faire un chargement dynamique des fonctions dont
on aura besoin car la version de cette DLL varie entre les différents Windows, mais ceci sort du cadre de ce tutoriel.
Après avoir créé un nouveau répertoire pour le projet, on y recopiera ce fichier crypt32.lib.
Il faut ensuite créer un nouveau projet et lui ajouter la librairie.
Ajoutez la ligne suivante, avant le premier include.
#define CRYPT_SIGN_MESSAGE_PARA_HAS_CMS_FIELDS
Les 2 defines suivants nous faciliteront la tâche.
Le premier correspond aux types d'encodage des certificats reconnus par l'API.
Le second est le nom du magasin de certificats dans lequel les clés se trouvent.
Il s'agit ici d'un magasin de certificats système, le plus couramment utilisé.
Le projet de test que l'ai utilisé est téléchargeable ici.
3. Principe
Il faut d'abord ouvrir le magasin de certificat.
Ensuite, il faut obtenir un handle sur la clé privée.
Il faut ensuite signer les données et récupérer la signature ainsi créée.
Un point très important est qu'à aucun moment, on ne manipule directement la clé.
L'utilisation de la clé se fait toujours à l'aide de 'handle' sur les clés.
CertFondCertificateInStore renvoie une structure de type PCCERT_CONTEXT en cas de succès, NULL sinon.
Les paramètres sont :
le handle du magasin de certificats dans lequel la recherche va se faire
le type d'encodage des certificats
0 dans la plupart des cas, ce paramètre permet de configurer des options en fonction du type de recherche
le type de recherche que l'on va effectuer ( nous allons chercher le certificat sur la
base du Nom repris dans le sujet du certificat
le critère de recherche (ici, le nom repris dans le champ certificat)
le dernier paramètre permet d'effectuer la recherche à partir d'un certificat donné.
NULL indique que c'est le début de la recherche. Ce paramètre est utile quand on recherche
un certificat sur base d'un critère qui n'est pas un identifiant unique.
Dans un but de simplicité, Nous supposerons que le certificat identifié par LFE@home.be est unique et
possède une clé privée associée.
Après utilisation de cette structure, il faut la libérer par :
CertFreeCertificateContext(CertSignerContext);
3.3. Signer les données
Pour signer des données, il faut
initialiser une structure contenant les paramètres de la signature
appeler une première fois la fonction de signature pour
déterminer la longueur du buffer qui contiendra les données après signature
Appeler une seconde fois la fonction de signature pour effectuer la signature proprement dite
La première étape est de déclarer et d'initialiser une structure de type CRYPT_SIGN_MESSAGE.
cbSize est la taille de la structure.
dwMsgEncondingType est le type d'encodage des certificats.
HashAlgortihm est le type de hashage utilisé pour créer la signature.
pSigningCert est le handle du certificat utilisé pour signer les données.
cMsgCert est le nombre d'eléments repris dans le paramètre rgpMsgCert.
rgpMsgCert est un tableau de pointeurs sur des structures de type PCCERT_CONTEXT.
Il s'agit du (des) certificat(s) à joindre aux données.
Il existe d'autres eléments dans cette structure, mais ils ne sont pas utilisé dans ce cas-ci.
Dans l'exemple présenté ici, nous ne joindrons que le certificat de la personne qui signe le message.
Pour la seconde étape, il faut savoir que lors de la signature, on peut, en une seule manipulation, signer plusieurs buffers de données.
Pour cela, il faut remplir 2 tableaux.
pbToBeSigned, contient les pointeurs sur les buffers de données à signer.
cbToBeSigned, contient les longueurs de ces buffers.
CryptSignMessage effectue la signature proprement dite.
Cette fonction renvoie true en cas de succès, false, sinon.
Les paramètres de la fonction sont
un pointeur sur la structure de type CRYPT_SIGN_MESSAGE.
un booléen qui vaut true si la signature est détachée, false sinon.
le nombre d'eléments des 2 tableaux contenant respectivement les pointeurs sur les buffers à signer,
et les longueurs de ces buffers.
le tableau pbToBeSigned
le tableau cbToBeSigned
le pointeur sur le buffer recevant la signature
la longueur des données signées.
Les 2 appels successifs de cryptSignMessage s'expliquent par le fait qu'il faut connaître la
longueur de la signature, pour pouvoir allouer le buffer.
le premier appel de la fonction avec NULL comme pointeur de retour fait effectuer
le calcul de la longueur.
le second appel, avec un pointeur non NULL, signe les données.
En cas d'échec de la signature, en plus de la valeur de retour à false, le pointeur de retour pointe sur
une chaîne vide, et la longueur des données est 0.
En complément de la valeur de retour, la fonction GetLastError() permet de récupérer un status d'erreur
un tout petit peu plus explicite.
3.3.2. Signature jointe, certificat non joint
Pour ne pas joindre de certificat, il faut juste modifier ces 2 lignes de code
Pour détacher la signature, l'appel à CryptSignMessage se fait de la façon suivante :
lOk = CryptSignMessage(&SignMsgParam,
true,
1,
(constBYTE **)rgpbToBeSigned,
rgcbToBeSigned,
NULL,
&cbSignedMsgBlob);
et
lOk = CryptSignMessage(&SignMsgParam,
true,
1,
(constBYTE **)rgpbToBeSigned,
rgcbToBeSigned,
cSignedMsg,
&cbSignedMsgBlob);
Il est important que les 2 fonctions soient appelées de la même façon,
pour que le calcul de longueur soit toujours correct
3.3.4. Signature détachée, certificat non joint
Pour cela, il faut combiner les 2 points précédents
3.4. Récupérer la signature
Nous voilà donc avec un buffer contenant les données signées. Cette signature est une suite de données binaires,
avec une longueur associée.
On peut soit les écrire telles quelles dans un fichier, soit les encoder en Base64 dans le but de les envoyer
par mail, etc ....
4. Vérifier la signature
A nouveau, il faut ouvrir le magasin de certificat.
Cette fois, il ne faut pas obtenir de handle sur le certificat.
Il y a 2 cas :
le certificat est joint à la signature, il suffit alors de contrôler que le message est bien conforme
le certificat n'est pas joint. Dans ce cas, il faut spécifier la façon de le retrouver.
Pour des raisons de simplicité, nous supposerons que le certificat est déjà dans le magasin de certificat
de l'utilisateur.
4.1. Vérifier une signature jointe, certificat joint
CRYPT_VERIFY_MESSAGE_PARA VerifParams;
DWORD cbSignedData, cbData;
char *pbSignedData, *pbData;
// récupérer les données signées et la longueur de ces données
memset(&VerifParams, 0x00, sizeof(CRYPT_VERIFY_MESSAGE_PARA));
VerifParams.cbSize = sizeof(CRYPT_VERIFY_MESSAGE_PARA);
VerifParams.dwMsgAndCertEncodingType = CRYPT_TYPE;
// Get The output buffer size
lOk = CryptVerifyMessageSignature(&VerifParams,
0,
pbSignedData,
cbSignedData,
NULL,
&cbData,
NULL);
pbData = (char*)malloc(cbData);
memset(pbData, 0x00, cbData);
// Verify the Message Signature
lOk = CryptVerifyMessageSignature(&VerifParams,
0,
pbSignedData,
cbSgnedData,
pbData,
&cbData,
NULL);
4.2. Vérifier une signature jointe, certificat non joint
Si le certificat n'est pas joint, les données signées contiennent malgré tout certaines informations permettant
de l'identifier : l'émetteur et le n° de série du certificat.
La structure de type CRYPT_VERIFY_MESSAGE_PARA doit avoir 2 éléments supplémentaires.
L'élément pvGetArg de la structure est le premier paramètre de la fonction.
Le paramètre pSignerId contient l'identification de l'émetteur et le n° de série du certificat. hMsgCertStore correspond au magasin de certificat contenu dans le message signé.
La fonction suivante recherche le certificat dans le magasin de certificat spécifié par pvGetArg.
Une signature détachée est uniquement le 'marquage' des données, par opposition au mécanisme de signature jointe,
ou les données sont jointes à la signature.
Dans ce cas, il faut fournir les données ayant été signées.
CryptVerifyDetachedSignature prend les paramètres suivants :
une structure de type CRYPT_VERIFY_MESSAGE_PARA.
le n° d'ordre de la signature à vérifier.
la signature
la longueur de la signature
le nombre d'éléments des 2 paramètres suivants
un premier tableau pbToBeSigned contient les pointeurs sur les buffers de données à signer
un second tableau cbToBeSigned contient les longueurs des buffers de données à signer
.
un pointeur sur un pointeur de type PCCERT_CONTEXT, si l'on désire utiliser le certificat
ayant servi à la signature pour d'autres opérations, NULL sinon.
(Attention, dans ce cas, il ne faudra pas oublier de le libérer par un appel à
CertFreeCertificatContext).
La valeur de retour est true si la signature est vérifiée, false sinon.
4.4. Vérifier une signature détachée, certificat non joint
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur.
La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de
l'autorisation de l'auteur.
Responsable bénévole de la rubrique C : Arnaud Feltz (buchs) - Contacter par EMail :