Cryptologie : OpenSSL et Delphi

Importer des clés RSA OpenSSL avec Indy

Marion Candau

Le but de cet article est de montrer qu'il est tout à fait possible d'utiliser une bibliothèque de chiffrement comme OpenSSL avec Delphi grâce aux composants fournis par Indy. Le but que nous nous fixons est d'importer les clés RSA contenues dans un fichier au format PEM afin de pouvoir les lire ou les utiliser par la suite.

- Titre : Cryptologie : OpenSSL et Delphi

- Auteur : Marion Candau

- Parution : 24 octobre 2017

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2017 Marion Candau. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Charger la bibliothèque OpenSSL

- III. Charger les fichiers PEM

- IV. Importer une clé publique RSA

- V. Importer une clé publique RSA à partir d'un certificat X509

- VI. Importer une clé privée RSA (chiffrée ou non)

- VII. Conclusion

I - Introduction

OpenSSL est une bibliothèque de chiffrement open-source implémentant TLS (Transport Layer Security) et son prédécesseur SSL (Secure Sockets Layer). Elle est massivement utilisée par les sites Web en HTTPS. Certains composants Indy utilisent OpenSSL comme TIdServerIOHandlerSSLOpenSSL ou TidSSLIOHandlerSocketOpenSSL.

Indy utilise la version 1.0.2 d'OpenSSL qui n'est pas la dernière version. TIdServerIOHandlerSSLOpenSSL d'Indy permet d'activer une connexion SSL/TLS avec un autre composant Indy, mais il ne donne pas accès aux clés de manière directe.

Dans cet article, vous allez utiliser les fichiers IdSSLOpenSSLHeaders et IdSSLOpenSSL d'Indy. Pour iOS, vous aurez aussi besoin de IdSSLOpenSSLHeaders_Static.

Nous nous plaçons dans un scénario où un utilisateur possède un fichier .pem qui peut être un certificat x509, une clé publique RSA, une clé privée RSA ou une clé privée RSA chiffrée. À partir d'un certificat x509, il peut extraire la clé publique avec cette commande openssl, où cert.pem est un certificat x509 et cle.pem une clé publique RSA :

openssl x509 -in cert.pem -pubkey -noout > cle.pem

Il peut aussi extraire la clé publique à partir de la clé privée, ainsi que créer un certificat à partir d'une clé privée.

Trois cas vont être traités :

- une clé publique ;

- un certificat ;

- une clé privée (chiffrée ou non).

II - Charger la bibliothèque OpenSSL

Pour utiliser la bibliothèque OpenSSL sous Windows, OSX ou iOS, il faut qu'elle soit installée sur votre machine. Embarcadero explique comment faire ici.

Pour Android, il faut distinguer deux cas :

- si vous avez une version inférieure à 6.0, vous n'avez rien à faire, car OpenSSL est inclus par défaut ;

- si vous avez une version supérieure ou égale 6.0, il vous faut récupérer des fichiers libcrypto.so* et libssl.so* (par exemple, libcrypto.so.1.0.0) et les déployer dans ./assets/internal pour qu'ils soient accessibles via GetDocumentsPath.

Vous pouvez en trouver des versions ici : https://indy.fulgan.com/SSL/

Ensuite, il vous faut charger la bibliothèque dans votre programme. Les fonctions dont vous allez avoir besoin sont dans la partie crypto d'OpenSSL. Vous n'avez pas besoin de charger la bibliothèque sous iOS, car elle est liée statiquement au programme.

function TFromOpenSSL.LoadSSLCryptoLibrary: HMODULE;
begin
{$IFDEF MSWINDOWS}
Result := SafeLoadLibrary('libeay32.dll');
{$ELSE}
{$IFDEF ANDROID}
Result := LoadLibrary('libcrypto.so');
{$ELSE}
{$IFNDEF IOS}
Result := LoadLibrary(PChar('libcrypto.dylib'));
{$ENDIF}
{$ENDIF}
{$ENDIF}
end;

Cette fonction est utilisée dans le chargement de toute la bibliothèque OpenSSL. Certaines fonctions que vous allez utiliser par la suite ne sont pas chargées dans IdSSLOpenSSLHeaders, donc vous devez indiquer qu'elles se trouvent dans le FSSLModule.

function TFromOpenSSL.LoadOpenSSLLibrary: Boolean;
begin
{FSSLModule est le HMODULE privé de la classe dans lequel est chargé libcrypto}
if FSSLModule <> 0 then
Exit(True);
{$IFDEF ANDROID}
IdOpenSSLSetLibPath(System.IOUtils.TPath.GetDocumentsPath);
{$ENDIF}
Result := IdSSLOpenSSL.LoadOpenSSLLibrary;
if Result then
begin
{$IFNDEF IOS}
FSSLModule := LoadSSLCryptoLibrary;
if FSSLModule = 0 then
Exit(False);
{On indique où sont les fonctions non chargées dans IdSSLOpenSSLHeaders}
PEM_read_bio_PUBKEY := GetProcAddress(FSSLModule, PChar('PEM_read_bio_PUBKEY'));
X509_get_pubkey := GetProcAddress(FSSLModule, PChar('X509_get_pubkey'));
OpenSSL_add_all_algorithms;
OpenSSL_add_all_ciphers;
OpenSSL_add_all_digests;
ERR_load_crypto_strings;
{$ENDIF}
end;
end;

III - Charger les fichiers PEM

Vous allez à présent charger le contenu des fichiers PEM dans un PBIO :

function TFromOpenSSL.LoadPEMFile(filePath: string): PBio;
var
{$IFNDEF MSWINDOWS}
LEncoding: TEncoding;
LOffset: Integer;
{$ENDIF}
Buffer: TBytes;
Stream: TStream;
begin
Stream := TFileStream.Create(filePath, fmOpenRead or fmShareDenyWrite);
try
SetLength(Buffer, Stream.size);
Stream.ReadBuffer(Buffer[0], Stream.size);
{$IFNDEF MSWINDOWS}
{On traite les problèmes d'encodage de flux sur les plateformes différentes de Windows}
LEncoding := nil;
LOffset := TEncoding.GetBufferEncoding(Buffer, LEncoding);
Buffer := LEncoding.Convert(LEncoding, TEncoding.UTF8, Buffer, LOffset,
Length(Buffer) - LOffset);
{$ENDIF}
Result := BIO_new_mem_buf(Buffer, Length(Buffer));
finally
Stream.free;
end;
end;

IV - Importer une clé publique RSA

Un fichier au format PEM contenant une clé publique RSA commence par —-BEGIN PUBLIC KEY—- puis est suivi de la clé en Base64 et se termine par —-END PUBLIC KEY—-.

function TFromOpenSSL.FromOpenSSLPublicKey(filePath: string): pRSA;
var
KeyBuffer: PBIO;
pkey: PEVP_PKEY;
begin
KeyBuffer := LoadPEMFile(filePath);
if KeyBuffer = nil then
raise Exception.Create('Impossible de charger le buffer');
try
pkey := PEM_read_bio_PUBKEY(KeyBuffer, nil, nil, nil);
if not Assigned(pkey) then
raise Exception.Create('Impossible de charger la clé publique');
try
Result := EVP_PKEY_get1_RSA(pkey);
if not Assigned(Result) then
raise Exception.Create('Impossible de charger la clé publique RSA');
finally
EVP_PKEY_free(pkey);
end;
finally
BIO_free(KeyBuffer);
end;
end;

V - Importer une clé publique RSA à partir d'un certificat X509

Un fichier au format PEM contenant un certificat X509 commence par —-BEGIN CERTIFICATE—- puis est suivi de la clé en Base64 et se termine par —-END CERTIFICATE—-.

function TFromOpenSSL.FromOpenSSLCert(filePath: string): pRSA;
var
KeyBuffer: PBIO;
FX509: pX509;
Key: PEVP_PKEY;
begin
KeyBuffer := LoadPEMFile(Buffer, Length(Buffer));
if KeyBuffer = nil then
raise Exception.Create('Impossible de charger le buffer X509');
try
FX509 := PEM_read_bio_X509(KeyBuffer, nil, nil, nil);
if not Assigned(FX509) then
raise Exception.Create('Impossible de charger le certificat X509');
Key := X509_get_pubkey(FX509);
if not Assigned(Key) then
raise Exception.Create('Impossible de charger la clé publique X509');
try
Result := EVP_PKEY_get1_RSA(Key);
if not Assigned(Result) then
raise Exception.Create('Impossible de charger la clé publique RSA');
finally
EVP_PKEY_free(Key);
end;
finally
BIO_free(KeyBuffer);
end;
end;

VI - Importer une clé privée RSA (chiffrée ou non)

Un fichier au format PEM contenant un clé privée RSA commence par —-BEGIN PRIVATE KEY—- puis est suivi de la clé en Base64 et se termine par —-END PRIVATE KEY—-. Si la clé est chiffrée, alors le fichier au format PEM commence par —-BEGIN RSA PRIVATE KEY—- puis est suivi de Proc-Type: 4,ENCRYPTED. Ensuite, il y a des informations sur l'algorithme utilisé pour chiffrer la clé (par exemple AES-128-CBC) puis il y a la clé chiffrée, en Base64. Enfin, le fichier se termine par —-END RSA PRIVATE KEY—-. Dans ce dernier cas, la fonction doit avoir pour entrée le mot de passe, pwd, pour déchiffrer la clé. La fonction PEM_read_bio_RSAPrivateKey prend un PAnsiChar (sous Windows ou OSX) ou un PByte (sous les plateformes mobiles) comme quatrième argument. Cet argument peut contenir le mot de passe.

Vous devez par conséquent déclarer le type PReadKeyChar comme ceci :

{$IF (defined(MSWINDOWS) or defined(MACOS)) and (not defined(IOS))}
ReadKeyChar = AnsiChar;
{$ELSE}
ReadKeyChar = Byte;
{$ENDIF}
PReadKeyChar = ^ReadKeyChar;

La méthode FromOpenSSLPrivateKey est définie comme suit :

function TFromOpenSSL.FromOpenSSLPrivateKey(filePath: string;
pwd: String): pRSA;
var
KeyBuffer: PBio;
p: PReadKeyChar;
I: Integer;
begin
KeyBuffer := LoadPEMFile(filePath);
if KeyBuffer = nil then
raise Exception.Create('Impossible de charger le buffer');
try
if pwd <> '' then
begin
p := GetMemory((pwd.Length + 1) * SizeOf(Char));
for I := 0 to pwd.Length - 1 do
p[I] := ReadKeyChar(pwd.Chars[I]);
p[pwd.Length] := ReadKeyChar(#0);
end
else
p := nil;
try
Result := PEM_read_bio_RSAPrivateKey(KeyBuffer, nil, nil, p);
if not Assigned(Result) then
raise Exception.Create('Impossible de charger la clé privée RSA');
finally
{On efface le mot de passe}
FillChar(p, SizeOf(p), 0);
FreeMem(p);
end;
finally
BIO_free(KeyBuffer);
end;

end;

Vous trouverez le code source complet de la classe ici.

VII - Conclusion

Les fonctions proposées seront appliquées pour extraire les clés RSA des fichiers afin qu'elles soient utilisées pour chiffrer, ou pour signer manuellement ou à l'aide d'une bibliothèque comme TMS Cryptography Pack.

Il est ainsi possible d'utiliser OpenSSL avec Delphi par le biais d'Indy. Cependant, il vous faut garder à l'esprit que beaucoup de vulnérabilités ont été trouvées dans OpenSSL et que de nombreux algorithmes utilisés par lui sont obsolètes, notamment l'algorithme utilisé dans le format PEM pour dériver une clé d'un mot de passe, car il utilise l'algorithme MD5. Il vous faut donc être vigilant et suivre de bonnes pratiques en cryptographie, notamment concernant les longueurs de clés minimales recommandées que vous pouvez retrouver sur https://www.keylength.com/fr/compare/

Je remercie Andnotor pour ses conseils, ainsi que YYYYYY pour la relecture orthographique.

Ce tutoriel a été mis en forme par gvasseur58.

Marion Candau (MVP Embarcadero) est actuellement ingénieure en cryptographie chez Cyberens. Elle a soutenu une thèse dont le titre est : Codes correcteurs d'erreurs convolutifs non commutatifs. Parmi ses centres d'intérêts, on notera le football féminin qu'elle pratique et soutient avec enthousiasme.