[C] Client/Serveur SSL/TLS multiplateformes avec OpenSSL

03
juil.
2012
  • Google Plus
  • LinkedIn
  • Viadeo
Posted by: Yann C.  /   Category: Administration réseaux et systèmes / Cryptographie / Cryptologie / OS / Programmation & Développement / Projets & outils / Windows   /   Pas de commentaire

Au cours de la plupart des développements actuels de logiciels exploitant les réseaux, la sécurité de ces échanges est primordiale. Une des solutions les plus employée de part sa facilité d’intégration, sa standardisation et qui a fait ses preuves est une encapsulation via le protocole SSL/TLS.

SSL (Secure Socket Layer) / TLS (Transport Layer Security) est un protocole permettant de relier des systèmes informatiques entre eux d’une manière sécurisée. Garantissant l’intégrité des échanges, la confidentialité et l’authentification des données, ce protocole est un standard en termes de protocole sécurisé. Datant de de 1994, SSL se découpe en 3 versions (1.0 non-utilisée, 2.0 jugée à présent obsolète et insécurisée et 3.0 extrêmement déployée). Après une standardisation du protocole par l’IETF en 2001, SSL à changé de nom pour TLS qui lui aussi se découpe en 3 version (1.0 très déployée, 1.1 et 1.2 qui étendent ses fonctionnalités).

SSL/TLS se découpe en 4 sous-protocoles:

  • Handshake : la négociation des paramètres de sécurité en tant que tel.
  • Change Cipher Spec : la validation de la négociation préalable, pour vérifier que les deux partis se sont bien accordés quant à la clé maîtresse générée, aux algorithmes à employer etc.
  • Alert : protocole informatif de l’état de la liaison.
  • Record : protocole d’acheminement des données de la communication. Ce protocole peut encapsuler tout autre protocole et le rend ainsi sécurisé.

SSL/TLS évolue à l’inter-couche transport/application de la pile OSI. Il est utilisé avec d’autres protocoles standardisés tels que HTTP, FTP ou encore SMPT en leur ajoutant un « S » final. Pour les puristes, « HTTPS », « FTPS » ou « STMPS » ne sont pas des protocoles en tant que tel. Ce sont des encapsulations de protocoles insécurisés (HTTP) dans une couche SSL/TLS ; d’où leur mise en italique dans la figure suivante.

Pile OSI et placement du protocole SSL/TLS

Pile OSI et placement du protocole SSL/TLS

Ce protocole se fonde initialement sur un mode de transport TCP (pouvant être UDP pour le DTLS, WTLS…) et s’implémente dans des développements logiciels par le biais d’API dont certaines des plus connues sont:

  • OpenSSL : l’API de référence.
  • GnuTLS : alternative à OpenSSL
  • yaSSL : version destinée au monde de l’embarqué
  • MatrixSSL : cible également le monde de l’embarqué

Au sein de cet article, il vous est présenté une implémentation simple d’un client et d’un serveur exploitant le protocole SSL/TLS pour communiquer via l’API OpenSSL. Ce client/serveur est multi-plateformes Windows/Linux.

Fonctionnalité du serveur :

  • Se met en écoute sur un port défini
  • Permet d’utiliser les versions SSL2.0, SSL3.0n SSL2.0 & 3.0, TLS1.0
  • Permet de charger un certificat contenant une clé publique à partir d’un fichier, idem concernant la clé privée.
  • Permet d’utiliser un certificat contenant une clé publique et une clé privée codés en dur dans l’application, au format PEM (encodage base64 du format DER).
  • Permet de générer un nouveau certificat et une nouvelle clé publique dynamiquement à chaque lancement du serveur.
  • Le serveur vérifie la correspondance entre le certificat et la clé privée avant de se mettre en écoute.
  • Le serveur patiente jusqu’à la connexion d’un client. Il affiche les détails du potentiel certificat envoyé par le client (facultatif) et attend la réception d’un message via le protocole « record« .
  • Lorsqu’un message arrive, celui-ci est directement retourné au client (ping-pong) puis le serveur attend une nouvelle connexion.
Code source du serveur :
/** SSL/TLS Server
* SSL/TLS server demonstration. This source code is cross-plateforme Windows and Linux.
* Compile under Linux with : g++ main.cpp -Wall -lssl -lcrypto -o main
* Certificat and private key to protect transaction can be used from :
* - External(s) file(s), created with command : openssl req -x509 -nodes -newkey rsa:2048 -keyout server.pem -out server.pem
* - Internal uniq hardcoded certificat and private key, equal into each server instance
* - Randomly generated certificat and private key, best solution to used dynamic keying material at each server lauching.
* Usage :
* # run the server on port 1337 for SSLv2&3 protocol with internals key and certificat
* $ [./]server[.exe] 1337
* # run the server on port 1337 for TLSv1 protocol with key and certificat in server.pem file
* $ [./]server[.exe] 1337 1 server.pem server.pem
* @author x@s
*/
 
#define DEFAULT_PORT 443
 
#ifdef __unix__ // __unix__ is usually defined by compilers targeting Unix systems
# include <unistd.h>
# include <sys/socket.h>
# include <arpa/inet.h>
# include <resolv.h>
# define SOCKLEN_T socklen_t
# define CLOSESOCKET close
#elif defined _WIN32 // _Win32 is usually defined by compilers targeting 32 or 64 bit Windows systems
# include <windows.h>
# include <winsock2.h>
# define SOCKLEN_T int
# define CLOSESOCKET closesocket
#endif
 
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <malloc.h>
#include <string.h>
 
#include <openssl/crypto.h>
#include <openssl/x509v3.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/bio.h>
 
#ifdef _WIN32
WSADATA wsa; // Winsock data
#endif
 
/**
* printUsage function who describe the utilisation of this script.
* @param char* bin : the name of the current binary.
*/
void printHeader(char* bin){
printf("[?] Usage : %s <port> [<method> <server_cert> <server_private_key>]\n", bin);
printf("[?] With <method> :\n");
printf("\t1 :\tTLS v1\n");
printf("\t2 :\tSSL v2 (deprecated so disabled)\n");
printf("\t3 :\tSSL v3\n");
printf("\t4 :\tSSL v2 & v3 (default)\n");
return;
}
 
/**
* makeServerSocket function who create a traditionnal server socket, bind it and listen to it.
* @param int port : the port to listen
* @return int socket : the socket number created
*/
int makeServerSocket(int port){
int sock;
struct sockaddr_in addr;
#ifdef _WIN32
WSAStartup(MAKEWORD(2,0),&wsa);
#endif
sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if(bind(sock, (struct sockaddr*)&addr, sizeof(addr)) != 0){
perror("[-] Can't bind port on indicated port...");
abort();
}
if(listen(sock, 10) != 0){
perror("[-] Can't listening on indicated port...");
abort();
}
printf("[+] Server listening on the %d port...\n", port);
return sock;
}
 
/**
* callbackGeneratingKey called during internal dynamic key generation.
* A callback function may be used to provide feedback about the
* progress of the key generation. If callback is not NULL, it will
* be called as follows:
* - While a random prime number is generated, it is called as
* described in BN_generate_prime(3).
* - When the n-th randomly generated prime is rejected as not
* suitable for the key, callback(2, n, cb_arg) is called.
* - When a random p has been found with p-1 relatively prime to e,
* it is called as callback(3, 0, cb_arg).
* The process is then repeated for prime q with callback(3, 1, cb_arg).
* @param int p : callback random prime flag
* @param int n : n-th randomly generation
* @param void *arg : argument for the callback passed from initial call
*/
static void callbackGeneratingKey(int p, int n, void *arg){
char c='B';
if (p == 0) c = '.'; // generating key...
if (p == 1) c = '+'; // near the end of generation...
if (p == 2) c = '*'; // rejecting current random generation...
if (p == 3) c = '\n'; // key generated
fputc(c, stderr); // print generation state
}
 
/**
* makekCert function who create the server certificat containing public key and
* the server private key signed (dynamic method).
* @param X509 **x509p : potential previous instance of X509 certificat
* @param EVP_PKEY **pkeyp : potential previous instance of private key
* @param int bits : length of the RSA key to generate (precaunized greater than or equal 2048b)
* @param int serial : long integer representing a serial number
* @param int days : number of valid days of the certificat
* @see Inpired from /demos/x509/mkcert.c file of OpenSSL library.
*/
void makekCert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int days){
X509 *x;
EVP_PKEY *pk;
RSA *rsa;
X509_NAME *name = NULL;
 
if((pkeyp == NULL) || (*pkeyp == NULL)){
if((pk = EVP_PKEY_new()) == NULL)
abort();
} else
pk= *pkeyp;
if((x509p == NULL) || (*x509p == NULL)){
if ((x = X509_new()) == NULL)
abort();
} else
x= *x509p;
 
// create RSA key
rsa = RSA_generate_key(bits, RSA_F4, callbackGeneratingKey, NULL);
if(!EVP_PKEY_assign_RSA(pk, rsa))
abort();
rsa = NULL;
 
X509_set_version(x, 2); // why not 3 ?
ASN1_INTEGER_set(X509_get_serialNumber(x), serial);
X509_gmtime_adj(X509_get_notBefore(x), 0); // define validation begin cert
X509_gmtime_adj(X509_get_notAfter(x), (long)60*60*24*days); // define validation end cert
X509_set_pubkey(x, pk); // define public key in cert
name = X509_get_subject_name(x);
 
// This function creates and adds the entry, working out the
// correct string type and performing checks on its length.
// Normally we'd check the return value for errors...
X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (const unsigned char*)"XX", -1, -1, 0); // useless if more anonymity needed
X509_NAME_add_entry_by_txt(name,"CN", MBSTRING_ASC, (const unsigned char*)"ASRAT", -1, -1, 0); // useless if more anonymity needed
 
// Its self signed so set the issuer name to be the same as the subject.
X509_set_issuer_name(x, name);
 
if(!X509_sign(x, pk, EVP_md5())) // secured more with sha1? md5/sha1? sha256?
abort();
 
*x509p = x;
*pkeyp = pk;
return;
}
 
/**
* initSSLContext function who initialize the SSL/TLS engine with right method/protocol
* @param int ctxMethod : the number coresponding to the method/protocol to use
* @return SSL_CTX *ctx : a pointer to the SSL context created
*/
SSL_CTX* initSSLContext(int ctxMethod){
const SSL_METHOD *method;
SSL_CTX *ctx;
 
SSL_library_init(); // initialize the SSL library
SSL_load_error_strings(); // bring in and register error messages
OpenSSL_add_all_algorithms(); // load usable algorithms
 
switch(ctxMethod){ // create new client-method instance
case 1 :
method = TLSv1_server_method();
printf("[+] Use TLSv1 method.\n");
break;
// SSLv2 isn't sure and is deprecated, so the latest OpenSSL version on Linux delete his implementation.
/*case 2 :
method = SSLv2_server_method();
printf("[+] Use SSLv2 method.\n");
break;*/
case 3 :
method = SSLv3_server_method();
printf("[+] Use SSLv3 method.\n");
break;
case 4 :
method = SSLv23_server_method();
printf("[+] Use SSLv2&3 method.\n");
break;
default :
method = SSLv23_server_method();
printf("[+] Use SSLv2&3 method.\n");
}
 
ctx = SSL_CTX_new(method); // create new context from selected method
if(ctx == NULL){
ERR_print_errors_fp(stderr);
abort();
}
return ctx;
}
 
/**
* loadCertificates function who load private key and certificat from files.
* 3 mecanisms available :
* - loading certificate and private key from file(s)
* - use embed hardcoded certificate and private key in the PEM format
* - generate random and dynamic certificate and private key at each server's launch instance.
* @param SSL_CTX* ctx : the SSL/TLS context
* @param char *certFile : filename of the PEM certificat
* @param char *keyFile : filename of the PEM private key
*/
void loadCertificates(SSL_CTX* ctx, const char* certFile, const char* keyFile){
// The server private key in PEM format, if internals required
/*const char *keyBuffer = "-----BEGIN PRIVATE KEY-----\n"
"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDP1SC2T/+NW59H\n"
"CYF0mzkoFcObGUAkoK7mvemFk2P99FLcKbqYKZZDMLVBg+tLU12kuIefYrC4G8F7\n"
"K8WReTZ+ZBWI1h+gEBhilZ0O4+XXoww2tjVyuHNe5twSxOhRYvoPNSKMLPR70Oij\n"
"b4nHSyu0a7JHAWvEdpk7HIeWugKYbY8ss58iCmkWGcrop/od6SPW12W+ugAyDGD9\n"
"F1Otrmb+T3KQPadlPgGdNprvVXHjk+eS1RcwOsT630usogl1JqhoAT4ViQvxDP0J\n"
"LEffPvG2Iow2WoRtjLGfKqGinhtrLyuht5s3XBzm05kHYNVDc1vkWPvk4PuoIfTp\n"
"ezrxuMR5AgMBAAECggEADV6wlAnhbr6OKIu8ADxcGPANfVTKg5Cyr7VX6Hfq3tNw\n"
"4SjuEAvc1sWzY1uRL29VfttAHkjDBZUDhWDzfMBHeSoHGJ5tumZOq0jkqaiPiKe8\n"
"iWh/V7n18gz3610vdMzhOUk5x7q8n5p43Mq4GlIDpb+n4Fl/DUxz3xGex1t//z4v\n"
"W7U1j+dKxiZGaNz2dyVVM7eHaynvEE4QL8i4msjhmrFSItqjF/0M/CJ0oEPPb2VL\n"
"6GLSfqCcjBzt0Sy93gVNhxO+KjMpumB1a9omDxBkTO4HF4xoDojrtkgYXaUx3uKk\n"
"Gc35xLoOdkn/pNDDzGzQT+xWYOO6IBxJGj/INvnIAQKBgQDwdVsC02z0Pb4JrlcM\n"
"KRBGyJetxxQguiZ3TYMIGMMP/fQZn3uofmWxNGPbk20VmDXXtZFsCJG7zrFVOUe3\n"
"eXIPjE2ho80aPAMWeiPAMkivhj0OnPHTg5sof75uH5F9zPerw7kgcwZMFPZk/Za0\n"
"53gxjakIZo2mlrtaomZLD/U+2QKBgQDdQ/EQlMG5+sjGn6MQrqpzlIT+PYQ4OmFE\n"
"p8B6AKtwC1oVKkY/1dWVUQ33DqTbXv8i8zN2mplMaFM/6rJNcY4BhKwBm+pW5XuV\n"
"LHLMGGkubues3bCb2OHax8DOm/i6hDJ14cEORsZSA2Jt6qzxaQ9HrtCZy29S5FIg\n"
"cFGCLHNuoQKBgAIe5tiViMZ2rPBk6zueORiGuF+9+712JtSyiE9P+Jhxgu+e6nZH\n"
"9xmi/qZ3HGUuXHs0jL3JLY/ceM/pm2pQ1eKxOBYO3cY3dUeDeEE/sEhsBKnWVIOr\n"
"C3lF9yX9fUkAv8ZyCXXxzcJqBOpLGkMqL3Mwbqc2UFWBytE30XMkBuOxAoGBAI8l\n"
"qGzAwIBwpboShy2AwteZq1zMMaEq68i9+oEzs7X+Mh5lRiOAVPiQAsfmGnOuBsP2\n"
"sUG3DRxolgtQ7F+76lJDIgC8fSQQvR4qLm6qEEoxCANHPT3mV1/yQWOpdoY8hmTL\n"
"U9nHogBnHiPcYlygSnlmuJ/3BCONgTBpWeIsndVhAoGAOFpnITiCmUFc5AUaxglZ\n"
"fz4fC+Mt4SF4XGFUtL8feGN4XGXHU6lQVQqu1yaRpYjSTabq6V6LLvVOh1sb+qZw\n"
"sSB4hC5C+VjjIBScsaN0pytFdL0+FeRaGPVBUs/yBWzfhi6Lm9vE8ebE0fMxr7b5\n"
"gw4qJCTvXYDZ8ZOIwG4YRRs=\n"
"-----END PRIVATE KEY-----\n";
// The server certificat containing public key in PEM format
const char *certBuffer = "-----BEGIN CERTIFICATE-----\n"
"MIIDiTCCAnGgAwIBAgIJAK0drhMsLqg2MA0GCSqGSIb3DQEBBQUAMFsxCzAJBgNV\n"
"BAYTAlhYMQowCAYDVQQIDAFYMQowCAYDVQQHDAFYMQowCAYDVQQKDAFYMQowCAYD\n"
"VQQLDAFYMQowCAYDVQQDDAFYMRAwDgYJKoZIhvcNAQkBFgFYMB4XDTEyMDMwMTEz\n"
"NDcwM1oXDTEyMDMzMTEzNDcwM1owWzELMAkGA1UEBhMCWFgxCjAIBgNVBAgMAVgx\n"
"CjAIBgNVBAcMAVgxCjAIBgNVBAoMAVgxCjAIBgNVBAsMAVgxCjAIBgNVBAMMAVgx\n"
"EDAOBgkqhkiG9w0BCQEWAVgwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB\n"
"AQDP1SC2T/+NW59HCYF0mzkoFcObGUAkoK7mvemFk2P99FLcKbqYKZZDMLVBg+tL\n"
"U12kuIefYrC4G8F7K8WReTZ+ZBWI1h+gEBhilZ0O4+XXoww2tjVyuHNe5twSxOhR\n"
"YvoPNSKMLPR70Oijb4nHSyu0a7JHAWvEdpk7HIeWugKYbY8ss58iCmkWGcrop/od\n"
"6SPW12W+ugAyDGD9F1Otrmb+T3KQPadlPgGdNprvVXHjk+eS1RcwOsT630usogl1\n"
"JqhoAT4ViQvxDP0JLEffPvG2Iow2WoRtjLGfKqGinhtrLyuht5s3XBzm05kHYNVD\n"
"c1vkWPvk4PuoIfTpezrxuMR5AgMBAAGjUDBOMB0GA1UdDgQWBBRG76BYshU93k3q\n"
"hy6gIpMl/VUDhTAfBgNVHSMEGDAWgBRG76BYshU93k3qhy6gIpMl/VUDhTAMBgNV\n"
"HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBCGmmyVt9gRJ0fuWh9o5MnT70m\n"
"nwbt0fM3Z6AO/Gkc0fkc6H4pZ3tnEtubtXBBm24wMFfXutcXFAjZMk0OTCPj5U8I\n"
"0/yjk5zuBdgktIFUTjs4Os/Ct2wvIfIiOm/WeL3FZOWli/HOX1PqjbeF/HXN+069\n"
"31U++ajDzM0uDFGc7dEPTXTEuE7w81696n9PTF0PSLt3/xIOwkMx28Wykc9XKgAp\n"
"MztGxeEtyb32ib+zL7UhEyuDHnW4haC8QsjG1QLpESTMMASbRe6QxrYxuMFjkf+g\n"
"FMw9jUYsThZropV2gFipcltT63ncyk0/W8gj1zmF6QsC46r1MFPUfnc/I6dx\n"
"-----END CERTIFICATE-----\n";*/
X509 *cert = NULL;
EVP_PKEY *pkey = NULL;
// RSA *rsa = NULL; // if internal private key and certificat required
//BIO *cbio, *kbio; // if internal private key and certificat required
 
if(certFile == NULL || keyFile == NULL){
 
/*
// if internal certificat and private key required
printf("[*] Loading internal server's certificat and private key.\n");
cbio = BIO_new_mem_buf((void*)certBuffer, -1);
PEM_read_bio_X509(cbio, &cert, 0, NULL);
SSL_CTX_use_certificate(ctx, cert);
kbio = BIO_new_mem_buf((void*)keyBuffer, -1);
PEM_read_bio_RSAPrivateKey(kbio, &rsa, 0, NULL);
SSL_CTX_use_RSAPrivateKey(ctx, rsa);
*/
 
printf("[*] Generate random server's certificat and private key.\n");
makekCert(&cert, &pkey, 2048, 0, 0);
SSL_CTX_use_certificate(ctx, cert);
SSL_CTX_use_PrivateKey(ctx, pkey);
 
// set the local certificate from certFile if certFile specified
// set the private key from keyFile (may be the same as certFile) if specified
} else if(SSL_CTX_use_certificate_file(ctx, certFile, SSL_FILETYPE_PEM) <= 0 ||
SSL_CTX_use_RSAPrivateKey_file(ctx, keyFile, SSL_FILETYPE_PEM) <= 0){
ERR_print_errors_fp(stderr);
abort();
} else
printf("[*] Server's certificat and private key loaded from file.\n");
 
// verify private key match the public key into the certificate
if(!SSL_CTX_check_private_key(ctx)){
fprintf(stderr, "[-] Private key does not match the public certificate...\n");
abort();
} else
printf("[+] Server's private key match public certificat !\n");
return;
}
 
/**
* showCerts function who catch and print out certificate's data from the client.
* @param SSL* ssl : the SSL/TLS connection
*/
void showCerts(SSL* ssl){
X509 *cert;
char *subject, *issuer;
 
cert = SSL_get_peer_certificate(ssl); // get the client's certificate
if(cert != NULL){
subject = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); // get certificate's subject
issuer = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); // get certificate's issuer
 
printf("[+] Client certificates :\n");
printf("\tSubject: %s\n", subject);
printf("\tIssuer: %s\n", issuer);
 
free(subject); // free the malloc'ed string
free(issuer); // free the malloc'ed string
X509_free(cert); // free the malloc'ed certificate copy
}
else
printf("[-] No client's certificates\n");
return;
}
 
/**
* routine function who treat the content of data received and reply to the client.
* this function is threadable and his context sharedable.
* @param SSL* ssl : the SSL/TLS connection
*/
void routine(SSL* ssl){
char buf[1024], reply[1024];
int sock, bytes;
const char* echo = "Enchante %s, je suis ServerName.\n";
 
if(SSL_accept(ssl) == -1) // accept SSL/TLS connection
ERR_print_errors_fp(stderr);
else{
printf("[+] Cipher used : %s\n", SSL_get_cipher(ssl));
showCerts(ssl); // get any client certificates
bytes = SSL_read(ssl, buf, sizeof(buf)); // read data from client request
if(bytes > 0){
buf[bytes] = 0;
printf("[+] Client data received : %s\n", buf);
sprintf(reply, echo, buf); // construct response
SSL_write(ssl, reply, strlen(reply)); // send response
} else {
switch(SSL_get_error(ssl, bytes)){
case SSL_ERROR_ZERO_RETURN :
printf("SSL_ERROR_ZERO_RETURN : ");
break;
case SSL_ERROR_NONE :
printf("SSL_ERROR_NONE : ");
break;
case SSL_ERROR_SSL:
printf("SSL_ERROR_SSL : ");
break;
}
ERR_print_errors_fp(stderr);
}
 
}
sock = SSL_get_fd(ssl); // get traditionnal socket connection from SSL connection
SSL_shutdown(ssl);
SSL_free(ssl); // release SSL connection state
CLOSESOCKET(sock); // close socket
}
 
/**
* main function who coordinate the socket and SSL connection creation, then receive and emit data to and from the client.
*/
int main(int argc, char **argv){
int sock, ctxMethod, port;
SSL_CTX *ctx;
const char *certFile, *keyFile;
 
if(argc != 2 && argc != 5){
printHeader(argv[0]);
exit(0);
}
 
port = (atoi(argv[1]) > 0 && atoi(argv[1]) < 65535) ? atoi(argv[1]) : DEFAULT_PORT;
ctxMethod = (argc >= 3) ? atoi(argv[2]) : 4; // SSLv2, SSLv3, SSLv2&3 or TLSv1
ctx = initSSLContext(ctxMethod); // load SSL library and dependances
certFile = (argc >= 4) ? argv[3] : NULL;
keyFile = (argc >= 5) ? argv[4] : NULL;
 
loadCertificates(ctx, certFile, keyFile); // load certificats and keys
 
sock = makeServerSocket(port); // make a classic server socket
 
while(42){
struct sockaddr_in addr;
SSL *ssl;
SOCKLEN_T len = sizeof(addr);
int client = accept(sock, (struct sockaddr*)&addr, &len); // accept connection of client
printf("[+] Connection [%s:%d]\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
ssl = SSL_new(ctx); // get new SSL state with context
SSL_set_fd(ssl, client); // set traditionnal socket to SSL
routine(ssl); // apply routine to the socket's content
}
 
CLOSESOCKET(sock); // close socket
#ifdef _WIN32
WSACleanup(); // Windows's Winsock clean
#endif
SSL_CTX_free(ctx); // release SSL's context
return 0;
}

Fonctionnalités du client :

Se connecte à un serveur sur un port défini
Utilise la version du protocole au choix entre SSL2.0, SSL3.0n SSL2.0 & 3.0, TLS1.0
Envoi un message au serveur et attend le retour de celui-ci.
Code source du client :

/** SSL/TLS Client
* SSL/TLS client demonstration. This source code is cross-plateforme Windows and Linux.
* Compile under Linux with : g++ main.cpp -Wall -lssl -lcrypto -o main
* Usage :
* # run the client to 127.0.0.1 on port 1337 for SSLv2&3 protocol
* $ [./]client[.exe] 127.0.0.1 1337
* # run the client to 127.0.0.1 on port 1337 for TLSv1 protocol
* $ [./]client[.exe] 127.0.0.1 1337 1
* @author x@s
*/
 
#define DEFAULT_PORT 443
 
#ifdef __unix__ // __unix__ is usually defined by compilers targeting Unix systems
# include <unistd.h>
# include <sys/socket.h>
# include <resolv.h>
# include <netdb.h>
# define SOCKLEN_T socklen_t
# define CLOSESOCKET close
#elif defined _WIN32 // _Win32 is usually defined by compilers targeting 32 or 64 bit Windows systems
# include <windows.h>
# include <winsock2.h>
# define SOCKLEN_T int
# define CLOSESOCKET closesocket
#endif
 
#include <stdio.h>
#include <errno.h>
#include <malloc.h>
#include <string.h>
 
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
 
#ifdef _WIN32
WSADATA wsa; // Winsock data
#endif
 
/**
* printUsage function who describe the utilisation of this script.
* @param char* bin : the name of the current binary.
*/
void printHeader(char* bin){
printf("[?] Usage : %s <hostname> <port> [<method>]\n", bin);
printf("[?] With optional <method> :\n");
printf("\t1 :\tTLS v1\n");
printf("\t2 :\tSSL v2 (deprecated so disabled)\n");
printf("\t3 :\tSSL v3\n");
printf("\t4 :\tSSL v2 & v3 (default)\n");
return;
}
 
/**
* makeClientSocket function who create a traditionnal client socket to the hostname throught the port.
* @param char* hostname : the target to connect to
* @param int port : the port to connect throught
* @return int socket ; the socket number created
*/
int makeClientSocket(const char *hostname, int port){
int sock;
struct hostent *host;
struct sockaddr_in addr;
#ifdef _WIN32
WSAStartup(MAKEWORD(2,0),&wsa);
#endif
if((host = gethostbyname(hostname)) == NULL ){
perror(hostname);
abort();
}
sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = *(long*)(host->h_addr);
if(connect(sock, (struct sockaddr*)&addr, sizeof(addr)) != 0){
CLOSESOCKET(sock);
perror(hostname);
abort();
}
return sock;
}
 
/**
* initSSLContext function who initialize the SSL/TLS engine with right method/protocol
* @param int ctxMethod : the number coresponding to the method/protocol to use
* @return SSL_CTX *ctx ; a pointer to the SSL context created
*/
SSL_CTX* initSSLContext(int ctxMethod){
const SSL_METHOD *method;
SSL_CTX *ctx;
 
SSL_library_init(); // initialize the SSL library
SSL_load_error_strings(); // bring in and register error messages
OpenSSL_add_all_algorithms(); // load usable algorithms
 
switch(ctxMethod){ // create new client-method instance
case 1 :
method = TLSv1_client_method();
printf("[+] Use TLSv1 method.\n");
break;
// SSLv2 isn't sure and is deprecated, so the latest OpenSSL version delete his implementation.
/*case 2 :
method = SSLv2_client_method();
printf("[+] Use SSLv2 method.\n");
break;*/
case 3 :
method = SSLv3_client_method();
printf("[+] Use SSLv3 method.\n");
break;
case 4 :
method = SSLv23_client_method();
printf("[+] Use SSLv2&3 method.\n");
break;
default :
method = SSLv23_client_method();
printf("[+] Use SSLv2&3 method.\n");
}
 
ctx = SSL_CTX_new(method); // create new context from selected method
if(ctx == NULL){
ERR_print_errors_fp(stderr);
abort();
}
return ctx;
}
 
/**
* showCerts function who catch and print out certificat's data from the server
* @param SSL* ssl : the SSL/TLS connection
*/
void showCerts(SSL* ssl){
X509 *cert;
char *subject, *issuer;
 
cert = SSL_get_peer_certificate(ssl); // get the server's certificate
if(cert != NULL){
subject = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); // get certificat's subject
issuer = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); // get certificat's issuer
 
printf("[+] Server certificates :\n");
printf("\tSubject: %s\n", subject);
printf("\tIssuer: %s\n", issuer);
 
free(subject); // free the malloc'ed string
free(issuer); // free the malloc'ed string
X509_free(cert); // free the malloc'ed certificate copy
if(SSL_get_verify_result(ssl) == X509_V_OK) // check certificat's trust
printf("[+] Server certificates X509 is trust!\n");
else
printf("[-] Server certificates X509 is not trust...\n");
}
else
printf("[-] No server's certificates\n");
return;
}
 
/**
* main function who coordinate the socket and SSL connection creation, then receive and emit data to and from the server.
*/
int main(int argc, char **argv){
int sock, bytes, ctxMethod, port;
SSL_CTX *ctx;
SSL *ssl;
char buf[1024];
char *hostname;
 
if(argc != 3 && argc != 4){
printHeader(argv[0]);
exit(0);
}
 
hostname = argv[1];
port = (atoi(argv[2]) > 0 && atoi(argv[2]) < 65535) ? atoi(argv[2]) : DEFAULT_PORT;
ctxMethod = (argc == 4) ? atoi(argv[3]) : 4; // SSLv2, SSLv3, SSLv2&3 or TLSv1
ctx = initSSLContext(ctxMethod); // load SSL library and dependances
sock = makeClientSocket(hostname, port); // make a classic socket to the hostname throught the port
ssl = SSL_new(ctx); // create new SSL connection state
 
SSL_set_fd(ssl, sock); // attach the socket descriptor
 
if(SSL_connect(ssl) == -1) // make the SSL connection
ERR_print_errors_fp(stderr);
else{
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); // if the server suddenly wants a new handshake,
// OpenSSL handles it in the background. Without this
// option, any read or write operation will return an
// error if the server wants a new handshake.
 
char msg[] = "ClientName";
printf("[+] Cipher used : %s\n", SSL_get_cipher(ssl));
showCerts(ssl); // get any certificats
SSL_write(ssl, msg, strlen(msg)); // encrypt and send message
bytes = SSL_read(ssl, buf, sizeof(buf)); // get response and decrypt content
buf[bytes] = 0;
printf("[+] Server data received : %s\n", buf);
SSL_shutdown(ssl);
SSL_free(ssl); // release SSL connection state
 
}
CLOSESOCKET(sock); // close socket
#ifdef _WIN32
WSACleanup(); // Windows's Winsock clean
#endif
SSL_CTX_free(ctx); // release SSL's context
return 0;
}

L’objectif de ce client/serveur est de disposer d’un code source d’exemple fonctionnel, cross-plateformes, épuré, documenté et clair quand à la mise en place d’une connexion SSL/TLS en C pour un quelconque développement futur.

La création du certificat et de la clé privée du serveur peut se faire avec la ligne de commande suivante (résultat dans un unique fichier) :

openssl req -x509 -nodes -newkey rsa:2048 -keyout server.pem -out server.pem

Compilation sous environnements Linux:

g++ main.cpp -Wall -lssl -lcrypto -o main

Sous Windows, OpenSSL doit être installé avec les fichiers d’en-têtes disponibles dans les includes de l’IDE. Le linkerde l’IDE utilisé doit utiliser les bibliothèques statiques d’OpenSSL (libcrypto.a et libssl.a) ou bien les DLL d’OpenSSL (libeay32.dll et libssl32.dll) doivent être présentes au côté du binaire.
Utilisation du serveur:

# Usage :
# run the server on port 1337 for SSLv2&3 protocol with internal randomly generated key and certificate
$ [./]server[.exe] 1337
# run the server on port 1337 for TLSv1 protocol with key and certificate in server.pem file
$ [./]server[.exe] 1337 1 server.pem server.pem

Utilisation du client:

# Usage :
# run the client to 127.0.0.1 on port 1337 for SSLv2&3 protocol
$ [./]client[.exe] 127.0.0.1 1337
# run the client to 127.0.0.1 on port 1337 for TLSv1 protocol
$ [./]client[.exe] 127.0.0.1 1337 1

L’ensemble des sources et binaires (Gcc et Code::Blocks) sont disponibles dans l’archive suivante [Client/Server OpenSSL]. La version utilisée d’OpenSSL au temps de développement était la 1.0.0e.

  • Google Plus
  • LinkedIn
  • Viadeo
Yann C.

About the Author : Yann C.

Consultant en sécurité informatique et s’exerçant dans ce domaine depuis le début des années 2000 en autodidacte par passion, plaisir et perspectives, il maintient le portail ASafety pour présenter des articles, des projets personnels, des recherches et développements, ainsi que des « advisory » de vulnérabilités décelées notamment au cours de pentest.