Titre: Protection d'application, un système de licence simple (12/11/2008 Par rfr)
Introduction
Lorsqu'un développeur ou une société fournit un logiciel de sa propre conception, il ou elle veut souvent le protéger à l'aide de clés. Le principe est simple: pour pouvoir utiliser le logiciel, l'utilisateur devra fournir une clé sous forme de numéro, de texte ou de fichier de licence.

De tels systèmes existent « out-of-the-box », comme FLEXlm par exemple, mais ces systèmes s'avèrent souvent coûteux et de plus, il existe sur internet des solutions génériques pour détourner ces protections (dans le cas de FLEXlm, le remplacement d'une simple DLL suffit parfois, testé et approuvé par votre humble serviteur ...)

Le but de ce petit article est de vous expliquer comment et « simplement » vous pouvez implémenter votre propre système de vérification de licence en utilisant la librairie OpenSSL <www.openssl.org> (lié statiquement bien sûr, sinon ce serait trop simple ;)).
Théorie
Le principe théorique est simple et très connu. Il s'agit du principe de la signature numérique, basée sur la cryptographie à clé publique, clé privée.
Petit rappel:

Soit une fonction de hashage HASH(x) = x' tel qui n'existe pas de fonction IHASH tel que

IHASH(x') = x,


Soit

-un algorithme cryptographique à clé privée, clé publique ENC,
-une clé publique Ppub
-une clé privée Ppriv

Tel que

Il n'existe pas de fonction F tel que F(Ppub) = Ppriv
et ENC(Ppriv, x) = x'
et ENC(Ppub, x') = x

La signature numérique sig d'un message msg s'exprime sous la forme:

ENC(Ppriv, HASH(msg)) = sig


En français, cela donne que la signature numérique d'un message est le résultat de l'encryptage du résultat d'une fonction de hashage par un algorithme à clé publique clé publique/clé privée au moyen de la clé privée.

Il est dès lors évident que la vérification de ce type de signature est aisé, il suffit de vérifier que:

ENC(Ppub, sig) = HASH(msg)


Notre système va utiliser ces principes pour signer un fichier de licence qui contient du texte décrivant les droits octroyés par la licence. Par exemple, on peut imaginer un fichier de licence contenant:

ExpirationDate: 31/08/2010
LicenceOwner: Joe Bar
Fonctions: FUNC1, FUNC2, FUNC3


Comme vous pouvez le constater, il s'agit de simple lignes de texte contenant un ensemble de clés/valeurs.

Evidemment, nous ne pouvons utiliser ce fichier comme licence car il suffirait de le modifier pour obtenir d'autres fonctionnalités ou supprimer la date d'expiration.

Pour empêcher la modification de ce fichier, nous allons le signer au moyen d'une clé privée que nous aurons préalablement générée et que nous garderons secrète. Ensuite, nous ajouterons à la fin du fichier de licence final une ligne contenant la signature du fichier.

Example:


ExpirationDate: 31/08/2010
LicenceOwner: Joe Bar
Fonctions: FUNC1, FUNC2, FUNC3
Sig: DnQSMdzq1d89o0gYgoSZXBHr167sCvMjzV1p8w4hJriU7VdrLTaGtc
faF4DVuExWCfq5wHY/NP8J6gj3TK+OMPviyPQH4nR4mBFkiNfiklKWgfuzZQQLf+ayB2nDzD67NwFLzAL4hpJ/5bi5IooKTQCciJ0OE+eAfLrIRaWsSmk=


La valeur de la clé Sig est simplement la valeur en Base64 de la signature numérique du fichier original au moyen des algorithmes Sha-512 (hashage) et RSA (encryptage).
Donc, pour vérifier que notre fichier de licence est correct et n'a pas été modifié, il suffit de lire la valeur de la clé Sig dans le fichier de licence et de vérifier que cette signature correspond bien au contenu du fichier original (çad toutes les données avant la clé Sig). Et ceci au moyen de la clé publique liée à notre clé privée.
Le Programme 'licence'
Description

Le programme que nous allons écrire se base donc sur les principes théoriques énoncés plus haut.

Dans sa forme, il permet de générer un fichier de licence signé à partir d'un fichier de licence non signé et de vérifier une licence (signée bien évidement ...).
Pour le compiler sous linux, il suffira de lancer la commande suivante:

gcc -o licence licence.c /path/to/libcrypto.a -ldl

ou:
/path/to/libcrypto.a est le chemin vers la libraire statique libcrypto.a


Utilisation

Pour signer un fichier de licence:

# ./licence -s licence.txt licence-signed.txt


Le fichier licence-signed.txt contiendra votre fichier de licence signé.
Pour vérifier qu'une licence est bien valide:

# ./licence -v licence-signed.txt


Note pour les développeurs

Les variable privkey_data et pubkey_data du fichier licence.c contiennent respectivement les clés privée et publique RSA générée au format PEM à l'aide d'openssl (voir la documentation d'OpenSSL).

Note: Il est évident que si vous vous basez sur ce code, votre application ne devra JAMAIS contenir la clé privée ... et aussi que vous ne devrez pas réutiliser les clés contenues dans ce programme mais bien générer votre propre paire de clé à l'aide du programme openssl:

openssl genrsa -out privkey.pem 1024
openssl rsa -in privkey.pem -out pubkey.pem -pubout


Code

licence.c:
  1. #include <stdio.h> 
  2. #include <string.h> 
  3. #include <openssl/pem.h> 
  4. #include <openssl/err.h> 
  5. #include <openssl/evp.h> 
  6. char *privkey_data = 
  7. "-----BEGIN RSA PRIVATE KEY-----\n" 
  8. "MIICXgIBAAKBgQD3kEfHz/jRBjXqyZNt6TaAIDDhvQzQntG1TgLXV7wRhv8Vfjew\n" 
  9. "e8df0d84jFNCP2Pw4GF1jdGolXUTrlGTPcj62wabiVt5r0waw/BHRrepkx1I84UM\n" 
  10. "j5gCO13zEU1EwuY3IdharIBQO58sbd4yjwLz5Ngw1bSp1VLhSjKVlXIjsQIDAQAB\n" 
  11. "AoGAHwoXBeV8g/CsZ/C4LGq2K6BdoKAMcEfJcVsDni5g5S2w4+f8YYNTfx8YRsXJ\n" 
  12. "VqAODtCDR7LtW57JnBsaHUT84r/amlpWmaEmYzb/efFMOsGB9n1STBYDyJjQmhCB\n" 
  13. "5V9gam/UQ8iyw5QR2PyL8FpSfvDLwNcw8K4Ph40zqp5CggECQQD/qDwr3Bej16mr\n" 
  14. "1z+ovN77Q8w44/V3q53e8QgPvKJxoe0NsXgSOdCVjadrOdrAknRRaVwCxJOdoMvf\n" 
  15. "Hvd4m1uRAkEA9+VEUxfjKJWMT8AFoVbKSjBo1+PvnEYJbq0R2leX10VKyaN8WnP5\n" 
  16. "5+KxJQIXXrEDEl4cyyfroPYrVBvRJY72IQJBAKD7G2B2xz/5BbLZ6BDlVPccutS7\n" 
  17. "3g5Lty3x0iSuoA8zaiRsMnIvi4MQXnJrTK5jyfCVikWH6HeiD53gTu3XzbECQQDF\n" 
  18. "H2jPcoVONm1W1WkkvLErgYc9daGJ8R97BODXcSPrKMypvvkZrHOi817OAPW4dKXu\n" 
  19. "qyvWWK5EVrxpq50Kcr5BAkEA8ZQhEGscDcLRsfiamg7fDiRugrWzLTrGJOf3CVr3\n" 
  20. "0uyWAvOpjF7frSH7r3NpaaJDUSk6w3Kf1tXic7UFjuN4+A==\n" 
  21. "-----END RSA PRIVATE KEY-----\n"
  22. char *pubkey_data = 
  23. "-----BEGIN PUBLIC KEY-----\n" 
  24. "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD3kEfHz/jRBjXqyZNt6TaAIDDh\n" 
  25. "vQzQntG1TgLXV7wRhv8Vfjewe8df0d84jFNCP2Pw4GF1jdGolXUTrlGTPcj62wab\n" 
  26. "iVt5r0waw/BHRrepkx1I84UMj5gCO13zEU1EwuY3IdharIBQO58sbd4yjwLz5Ngw\n" 
  27. "1bSp1VLhSjKVlXIjsQIDAQAB\n" 
  28. "-----END PUBLIC KEY-----\n"
  29. unsigned char* sign_buffer(const void *buffer, unsigned int data_len, unsigned int *sig_len) { 
  30. EVP_PKEY *pubkey = NULL; 
  31. EVP_PKEY *privkey = NULL; 
  32. unsigned int sign_buf_size = 0
  33. unsigned char *sign; 
  34. ERR_load_crypto_strings(); 
  35. BIO *bio_pubkey = BIO_new_mem_buf((void *) pubkey_data, strlen(pubkey_data)); 
  36. BIO *bio_privkey = BIO_new_mem_buf((void *) privkey_data, strlen(privkey_data)); 
  37. if (!PEM_read_bio_PrivateKey(bio_privkey, &privkey, NULL, NULL)) { 
  38. printf("load error\n"); 
  39. ERR_print_errors_fp(stdout); 
  40. exit(1); 
  41. if (!PEM_read_bio_PUBKEY(bio_pubkey, &pubkey, NULL, NULL)) { 
  42. printf("load pubkey error\n"); 
  43. ERR_print_errors_fp(stdout); 
  44. exit(1); 
  45. BIO_free(bio_pubkey); 
  46. BIO_free(bio_privkey); 
  47. OpenSSL_add_all_algorithms(); 
  48. EVP_MD_CTX ctx; 
  49. EVP_MD_CTX_init(&ctx); 
  50.     sign_buf_size = EVP_PKEY_size(privkey); 
  51.     *sig_len = sign_buf_size; 
  52.     sign = (unsigned char *) malloc(sign_buf_size); 
  53. EVP_SignInit(&ctx, EVP_sha512()); 
  54. EVP_SignUpdate(&ctx, (const void *) buffer, data_len); 
  55. if (!EVP_SignFinal(&ctx, sign, sig_len, privkey)) { 
  56. printf("sign error\n"); 
  57. ERR_print_errors_fp(stdout); 
  58. exit(1); 
  59. EVP_VerifyInit(&ctx, EVP_sha512()); 
  60. EVP_VerifyUpdate(&ctx, (const void *) buffer, data_len); 
  61. if (!EVP_VerifyFinal(&ctx, sign, *sig_len, pubkey)) { 
  62.                 printf("verification error\n"); 
  63.                 ERR_print_errors_fp(stdout); 
  64.                 exit(1); 
  65. return sign; 
  66. void make_license(char *in_file, char *out_file) { 
  67. BIO *in = BIO_new_file(in_file, "r"); 
  68. if (!in) { 
  69. fprintf(stderr, "cannot read file %s\n", in_file); 
  70. exit(1); 
  71. BIO *out = BIO_new_file(out_file, "w"); 
  72. if (!out) { 
  73. fprintf(stderr, "cannot create file %s\n", out_file); 
  74. exit(1); 
  75. unsigned char in_buffer[4096]; 
  76. unsigned int in_buffer_len = 0
  77. in_buffer_len = BIO_read(in, (void *) in_buffer, 4096); 
  78. BIO *b64 = BIO_new(BIO_f_base64()); 
  79. BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); 
  80. b64 = BIO_push(b64, out); 
  81. unsigned int sig_len = 0
  82. unsigned char *signature = sign_buffer((void *) in_buffer, in_buffer_len, &sig_len); 
  83. BIO_write(out, (const void *) in_buffer, in_buffer_len); 
  84. BIO_write(out, "Sig: "5); 
  85. BIO_flush(out); 
  86. BIO_write(b64, signature, sig_len); 
  87. BIO_flush(b64); 
  88. BIO_write(out, "\n"1); 
  89. BIO_flush(out); 
  90. BIO_flush(b64); 
  91. BIO_free(b64); 
  92. BIO_free(out); 
  93. BIO_free(in); 
  94. free(signature); 
  95. void verify_license(char *in_file) { 
  96. unsigned char in_buffer[4096]; 
  97. unsigned int in_buffer_len; 
  98. unsigned char *signature[256]; // Suitable for a 2048bits RSA Key 
  99. BIO *in = BIO_new_file(in_file, "r"); 
  100. if (!in) { 
  101. fprintf(stderr, "cannot read file %s\n", in_file); 
  102. in_buffer_len = BIO_read(in, (void *) in_buffer, 4096); 
  103. BIO_free(in); 
  104. int sig_start = -1
  105. unsigned int i=0
  106. for (; i < in_buffer_len - 5; i++) { 
  107. if (!strncmp("Sig: ",(char *) ((unsigned int) in_buffer)+i, 5)) { 
  108. sig_start = i+5
  109. if (sig_start == -1) { 
  110. printf("Signature not found\n"); 
  111. exit(1); 
  112. BIO *sig_in = BIO_new_mem_buf((void *) in_buffer+sig_start, in_buffer_len - sig_start); 
  113. BIO *b64 = BIO_new(BIO_f_base64()); 
  114. BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); 
  115. b64 = BIO_push(b64, sig_in); 
  116. unsigned int sig_len = BIO_read(b64, (void *) signature, 256); 
  117. BIO_free(b64); 
  118. BIO_free(sig_in); 
  119. in = BIO_new_mem_buf(pubkey_data, strlen(pubkey_data)); 
  120. EVP_PKEY *pubkey; 
  121. PEM_read_bio_PUBKEY(in, &pubkey, NULL, NULL); 
  122. BIO_free(in); 
  123. EVP_MD_CTX ctx; 
  124. EVP_MD_CTX_init(&ctx); 
  125. EVP_VerifyInit(&ctx, EVP_sha512()); 
  126. EVP_VerifyUpdate(&ctx, (const void *) in_buffer, sig_start-5); 
  127. if (!EVP_VerifyFinal(&ctx, (void *) signature, sig_len, pubkey)) { 
  128.                 printf("Invalid License File\n"); 
  129.                 exit(1); 
  130. else { 
  131. printf("License File OK\n"); 
  132. void usage(char *progname) { 
  133. printf("Usage:\n\t%s -s <infile> <outfile>\n\t%s -v <infile>\n", progname, progname); 
  134. int main(int argc, char **argv) { 
  135. if (argc < 3) { 
  136. usage(argv[0]); 
  137. exit(1); 
  138. if (!strcmp(argv[1], "-s")) { 
  139. if (argc < 4) { 
  140. usage(argv[0]); 
  141. exit(1); 
  142. make_license(argv[2], argv[3]); 
  143. else if (!strcmp(argv[1], "-v")) { 
  144. verify_license(argv[2]); 
  145. return 0
  146. }
Conclusion
Oh my god! Ce programme ne contient aucun commentaire! Et oui, c'est voulu ... En effet, c'est en comprenant les principes théoriques de ce programme et en parcourant le code (tout en vous aidant des pages de manuel d'openssl, donc man openssl_fonction) que vous pourrez comprendre et réutiliser ce code (il est plus simple à comprendre que vous ne l'imaginez ...).

En terme d'efficacité théorique ... ce principe est sans faille. En pratique, ce n'est guère le cas. Car une personne mal intentionnée pourrait, par exemple, modifier votre fichier exécutable et changer la clé publique par sa propre clé. Mais sachez que sans modification de l'exécutable (ou de son image mémoire pendant l'exécution), aucun hacker ne pourra générer de licence valide ou écrire un générateur de licence. Sans la clé privée, rien n'est possible.

Donc, la seule protection supplémentaire à apporter à la solution est de protéger votre exécutable contre la modification. Et pour cela, il existe beaucoup de solutions ... une simple recherche sur google de « exe packer », « exe protector », ... suffira à vous convaincre.
Retour