Publi

Conexión segura a un servidor web (HTTPS) en C usando openSSL

Bueno, el código no tiene mucha más explicación. El objetivo es poder conectar a un servidor web a través de un protocolo seguro y recibir datos, pero también que podamos ver si hay algún problema con el certificado, verificar el algoritmo de cifrado y los datos del certificado, verificar los certificados intermedios, etc.

Si ignoramos SSL, lo que tenemos que hacer es conectar con un servidor a través de un puerto determinado (como estamos en web, sería el puerto 80, por defecto), y mandarle una serie de datos:

GET / HTTP/1.1[CRLF]
Host: openssl.org[CRLF]
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:37.0) Gecko/20100101 Firefox/37.0[CRLF]
[CRLF]

Por ejemplo. Siendo CRLF un retorno de carro (CR, o \r) y un salto de línea (LF, o \n). Después de la primera línea donde indicamos método URI y protocolo vendrán una serie de cabeceras que completarán la petición: elementos relativos al cliente como el navegador, el host al que estamos conectando (al final accedemos a una IP, pero puede que en esa IP haya muchas webs dentro, aquí podemos especificarla), información que aceptamos (Accept/Accept-Language/Accept-Encoding…), capacidades del cliente (Connection…), cookies, dirección desde la que has llegado, opciones de caché o cabeceras personalizadas, entre otras. Todo eso, seguido de dos CRLF, indicando que no vamos a dar más información.

El servidor nos responderá con unas cabeceras como estas:

HTTP/1.1 200 OK[CRLF]
Date: Fri,28 Aug 2015 10:10:10[CRLF]
Server: Apache/2.4 PHP/5.4[CRLF]
Content-Type: text/html[CRLF]
[CRLF]

(Pero igual que el navegador puede decir muchas cosas, los servidores también pueden contar su vida). Y tras terminar las cabeceras con dos [CRLF] como antes decíamos, nos mandarán la web en cuestión.

Cuando terminan de enviarnos la página, puede que la conexión se cierre porque ya no hay nada más que decir, o puede que se quede abierta un tiempo prudencial (por si queremos pedir otra cosa. Esto suele ser el Keep-Alive, que también lo vemos en las cabeceras, y seguro que si hemos curioseado con Apache alguna vez lo hemos visto por ahí).

Introduciendo SSL, muy rápidamente

El objetivo es que la conexión esté cifrada punto a punto, es decir, que sólo el origen pueda enviar y sólo el destino pueda recibir no importando los saltos que dé la conexión (porque da muchos), ni quién mire entre medias (man-in-the-middle) tanto si tu conexión es compartida, como si tenemos un vecino espiando la conexión WiFi (que siempre pensamos que a quién le va a interesar, pero el día menos pensado nos han robado los datos). Así que hacemos que tanto el origen (navegador) como el destino (servidor) cifren los datos que envían y que éstos sólo puedan ser descifrados por la otra parte (por lo que nuestro vecino se aburrirá sin saber qué se cuece por la red).

Pero claro, yo me puedo conectar a un servidor sin conocerlo, ¿cómo sé la clave de cifrado? Al empezar la conexión, tanto el navegador como el servidor intercambian claves de cifrado para llevar a cabo el proceso, de modo que la persona que está en medio, sólo podría ver un saludo entre los servidores (sabrá que estamos conectando de forma segura, pero no podrá ver el contenido).

Además, ¿quién me garantiza a mi que mi vecino no ha interceptado la señal y está actuando él de servidor o haciendo de proxy? Es decir, yo me conecto con mi vecino que hace de servidor y él se conecta con el servidor de destino curioseando mis mensajes. Ahí entran los datos del certificado (¿con quién estoy conectando? Bueno, se puede falsificar) y las autoridades certificadoras (CA), son certificados que tenemos en nuestro equipo de gente “de confianza”. Estas autoridades firman los certificados y dan fe de que el certificado original es de quien dice ser, y si la firma no está bien, se detectarán los certificados como no válidos. Nos dejará hacer la conexión segura si queremos, pero podremos saber que “ahí pasa algo”.

Las autoridades certificadoras que podemos utilizar cuestan dinero, al final son empresas que miran tus datos y hacen de “notario” con esa información (y los notarios son caros). Dependiendo del empeño que pongan en verificar tu identidad son más o menos caros (entre otras cosas). También podemos crearnos nosotros un certificado y firmarnos con él, pero como no somos una autoridad de confianza, el navegador se quejará igual. Aunque si confiamos en nosotros mismos, podemos instalar nuestro certificado en nuestro ordenador y decirle que somos de confianza, pero si ahora llamo a un amigo para que se meta en mi web segura, para él, no somos de confianza.

Bueno, para terminar con esta parte, decir que es una intro super rápida en tres párrafos, falta mucho por decir y hay un montón de cosas entre medias y que, la teoría es muy bonita, pero yo siempre digo una cosa: “Si un dato se puede interceptar, échate a temblar”, o lo que es lo mismo, si alguien puede tener tu información, aunque sea codificada, tarde o temprano, con ganas e ilusión, podrá descifrarla. De hecho muy a menudo se descubren formas de descifrar estas conexiones, extraer las claves, etc.

A lo que vamos

Pero bueno, queremos hacer más o menos eso, en C, como los valientes, aunque utilizando la biblioteca OpenSSL (seré valiente, pero implementar todas las tecnologías necesarias desde cero es demasiado). Ahora, desde OpenSSL, me gustaría conocer si la validación del certificado es buena (pueden fallar mil cosas si miramos la documentación), de todas formas, nuestro cliente es un valiente y termina lo que ha venido a hacer sin que le importe nuestro vecino de las WiFis.

Queremos también información del certificado, que nos diga con quién estamos conectando, la validez del certificado (fecha de emisión y de caducidad o NotBefore y NotAfter) y autoridad certificadora que lo emite (puede que en lugar de una autoridad, haya una cadena de firmas: A (autoridad gorda) firma a B (autoridad intermedia) que firma a C (el servidor), pero como A deposita su confianza en B y B la deposita en C, podemos decir que C es de confianza. Aunque también sabemos cuántos niveles hay y podemos establecer que pasados dos niveles ya no confiamos (en ese caso, si hubiera un D, diríamos que no confiamos en él).

Para eso y mucho más he hecho este ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
/**
*************************************************************
* @file myssl.c
* @brief SSL Client HTTP Connection example getting information
*     and verifying certificates.
* These are just some SSL notes
*
* @author Gaspar Fernández <blakeyed@totaki.com>
* @version 0.1
* @date 17 abr 2015
*
* To compile:
*  $ gcc -o myssl myssl.c -lcrypto -lssl
*
*************************************************************/


#include <time.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdlib.h>
#include <errno.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <sys/select.h>
#include <sys/time.h>

/** Where to look for the Certificate Authorities */
#define CAPATH "/etc/ssl/certs"
/** 16Kb SSL_read block size */
#define BUFFERSIZE 16384
/** For temporary strings */
#define STRBUFFERSIZE 256
/** User agent string  */
#define USERAGENT "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:37.0) Gecko/20100101 Firefox/37.0"
/** CRLF  */
#define CRLF "\r\n"

/* Useful Structure. Just to have everything in one place and avoid
 passing lot of arguments to the functions. */


/** sslc handles SSL and SSL_CTX and socket */
typedef struct
{
  /** socket handler  */
  int skt;
  /** error, if any  */
  int err;
  /** SSL handler  */
  SSL* ssl;
  /** SSL Context  */
  SSL_CTX* ctx;
} Sslc;

/* First some useful stuff */

/**
 * Transforms ASN1 time sring to time_t (except milliseconds and time zone)
 * Ideas from: http://stackoverflow.com/questions/10975542/asn1-time-conversion
 *
 * @param time    SSL ASN1_TIME pointer
 * @param tmt     time_t pointer to write to
 *
 * @return int 0 if OK, <0 if anything goes wrong
 */

int ASN1_TIME_to_time_t(ASN1_TIME* time, time_t *tmt);

/**
 * Extract a substring from origin into buffer, updating starting
 * value to call in chain. Used by ASN1_TIME_to_time_t to extract
 * substrings easyly
 *
 * @param buffer  Where to write to
 * @param origin  Original string
 * @param from    Where to start from.
 *                           Updated to the last position after end.
 * @param size    Characters to extract.
 *
 * @return char* reference to buffer
 */

char* join(char* buffer, const char* origin, size_t *from, size_t size);

/**
 * Check certificate validity
 *
 * @param       certificate Certificate to check
 *
 * @return      error code (0 is OK. See x509_vfy.h for
 *              constants (X509_V_ERR_UNABLE_*). You can also
 *              use X509_verify_cert_error_string(long int) to
 *              see the error string
 */

long int check_certificate_validity(X509* certificate);

/**
 * Creates a basic TCP client connection to a server on a port.
 * Uses simple sockets
 *
 * @param h       Our structure. Only the socket will be used
 * @param server  Where to connect
 * @param port    The port to use (443 for HTTPS)
 *
 * @return int       (0 if OK, else fail)
 */

int TCP_Connection(Sslc* h, char* server, int port);

/**
 * Uses select to test if there is anything waiting to be read.
 *
 * @param h       Our structure. Only the socket will be used
 * @param timeout Timeout before giving up
 *
 * @return (0 timeout, 1 data waiting, <0 fail)
 */

int TCP_select(Sslc* h, double timeout);

/**
 * SSL initialization and handshake
 *
 * @param h       Our structure.
 *
 * @return (0 if OK, else fail)
 */

int SSL_Connection(Sslc* h);

/**
 * SSL send. To be called instead of send. It will send data through
 * the socket and decode information, or even perform a handshake
 * if needed.
 *
 * @param h       Our structure.
 * @param msg     Message to send

 * @return (0 if OK, else fail)
 */

int SSL_send(Sslc* h, char* msg);

/**
 * SSL recv. To be called instead of recs. It will read the socket
 * and decode information, or even perform a handshake if needed
 *
 * @param h       Our structure.
 * @param data    Data to be read (caution a pointer by
 *                           reference that must be freed manually

 * @return (0 if OK, else fail)
 */

int SSL_recv(Sslc* h, char** data);

/**
 * Prints out SSL information: SSL Version, cipher used and certificate
 * information.
 *
 * @param h       Our structure.
 *
 * @return void
 */

void SSL_print_info(Sslc* h);

/**
 * Prints out certificate information. Run throught the entries, print the
 * not before and not after information and verify the certificate.
 *
 * @param cert       The certificate to check
 *
 * @return void
 */

void SSL_print_certificate_info(X509* cert);

/**
 * Gets cipher description in a string
 * Please free the resulting string, don't do it like me ;)
 *
 * @param cipher       Cipher
 *
 * @return String with the description
 */

char *SSL_cipher_description(SSL_CIPHER* cipher);

/**
 * Gets a string with the time_t into a string
 *
 * @param buffer      Buffer to write to
 * @param bufsize     Total buffer size
 * @param format      Date/Time format (@see strftime())
 * @param tim         Time
 *
 * @return buffer
 */

char *time_t_to_str(char *buffer, size_t bufsize, const char* format, time_t *tim);

/**
 * Prints ASN1_TIME on screen
 *
 * @param asn1time     Time to write
 * @param pre_string   String to write before the date
 * @param dateformat   Date format (@see strftime())
 *
 * @return void
 */

void print_time(ASN1_TIME* asn1time, char* pre_string, char* dateformat);


/**
 * Prints program usage
 *
 * @param executable   Program executable (argv[0])
 *
 * @return void
 */

void print_usage(char* executable);

/**
 * Prints a tragic error and exit
 *
 * @param msg   Error text
 *
 * @return void
 */

void panic(char *msg);

int main(int argc, char *argv[])
{
  Sslc sslc;
  char *response;
  char *server = argv[1];
  int port;
  char *httpquerytemplate ="GET / HTTP/1.1" CRLF "Host: %s" CRLF "User-Agent: %s" CRLF CRLF;
  char httpquery[1024];

  /* What will be sent to the server */
  sprintf (httpquery, httpquerytemplate, server, USERAGENT);

  if (argc<2)
    print_usage(argv[0]);

  if (argc>2)
    port = atoi (argv[2]);
  else
    port = 443;         /* default https port */

  if (TCP_Connection(&sslc, server, port)<0)
    panic ("Couldn't connect host");

  if (SSL_Connection(&sslc)<0)
    panic ("Couldn't stablish secure connection");

  if (SSL_send(&sslc, httpquery)<0)
    panic ("Couldn't send anything to the server");

  SSL_print_info(&sslc);

  if (SSL_recv(&sslc, &response)<0)
    panic ("Couldn't receive the message");

  printf ("Received %lu bytes\n", strlen(response));

  free(response);

  return EXIT_SUCCESS;
}

char* join(char* buffer, const char* origin, size_t *from, size_t size)
{
  size_t i=0;
  while (i<size)
    {
      buffer[i++] = origin[(*from)++];
    }
  buffer[i] = '\0';
  return buffer;
}

int ASN1_TIME_to_time_t(ASN1_TIME* time, time_t *tmt)
{
  const char* data = time->data;
  size_t p = 0;
  char buf[5];
  struct tm t;
  int temp;
  memset(&t, 0, sizeof(t));
  size_t datalen = strlen(data);

  if (time->type == V_ASN1_UTCTIME) {/* two digit year */
    /* error checking YYMMDDHH at least */
    if (datalen<8)
      return -1;
    t.tm_year = atoi (join(buf, data, &p, 2));
    if (t.tm_year<70)
      t.tm_year += 100;
    datalen = strlen(data+2);
  } else if (time->type == V_ASN1_GENERALIZEDTIME) {/* four digit year */
    /* error checking YYYYMMDDHH at least*/
    if (datalen<10)
      return -1;

    t.tm_year = atoi (join(buf, data, &p, 4));
    t.tm_year -= 1900;
    datalen = strlen(data+4);
  }

  /* the year is out of datalen. Now datalen is fixed */

  t.tm_mon = atoi (join(buf, data, &p, 2))-1; /* January is 0 for time_t */
  t.tm_mday= atoi (join(buf, data, &p, 2));
  t.tm_hour= atoi (join(buf, data, &p, 2));

  if (datalen<8)
    return !(*tmt = mktime(&t));
  t.tm_min = atoi (join(buf, data, &p, 2));

  if (datalen<10)
    return !(*tmt = mktime(&t));
  t.tm_sec = atoi (join(buf, data, &p, 2));

  /* Ignore millisecnds and time zone */
  return !(*tmt = mktime(&t));
}

long int check_certificate_validity(X509* certificate)
{
  int status;
  X509_STORE_CTX *ctx;
  ctx = X509_STORE_CTX_new();
  X509_STORE *store = X509_STORE_new();
  X509_STORE_load_locations(store, NULL, CAPATH);
  X509_STORE_add_cert(store, certificate);

  X509_STORE_CTX_init(ctx, store, certificate, NULL);

  status = X509_verify_cert(ctx);

  return ctx->error;
}

int TCP_Connection(Sslc* h, char* server, int port)
{
  h->err = 0;
  struct hostent *host = gethostbyname (server);
  struct sockaddr_in addr;

  if (host == NULL)
    return -1;          /* Couldn't get host */

  h->skt = socket (AF_INET, SOCK_STREAM, 0);
  if (h->skt < 0)
    {
      return -2;
    }
  else
    {
      /* fill in address data */
      addr.sin_family = AF_INET;
      addr.sin_port = htons (port);
      addr.sin_addr = *((struct in_addr *) host->h_addr);
      bzero (&(addr.sin_zero), 8);

      /* connect */
      h->err = connect (h->skt, (struct sockaddr *) &addr, sizeof (struct sockaddr));
      if (h->err == -1)
        {
      return -3;
        }
    }
  return 0;
}

int SSL_Connection(Sslc* h)
{
  SSL_library_init();       /* not reentrant! */
  SSL_load_error_strings();

  /* try SSL methods (TLSv1.2 ... SSLv3 and SSLv2 (caution! SSLv2 and SSLv3 are deprecated!) */
  /* you could use TLSv1_client_method(), TLSv1_2_client_method()... */
  h->ctx = SSL_CTX_new(SSLv23_client_method());
  if (h->ctx == NULL)
    return -3;          /* Context not created */

  /* SSL will fail if cerificate can't be validated */
  /* h->ctx->verify_mode = 1; */

  h->ssl = SSL_new (h->ctx);
  if (h->ssl == NULL)
    return -4;          /* SSL struct not created */

  if (SSL_set_fd(h->ssl, h->skt) == 0)
    return -5;          /* Couldn't bind SSL with our connection */

  if (SSL_CTX_load_verify_locations(h->ctx, NULL, CAPATH) == 0)
    return -6;          /* Couldn't load verify locations */

  /* Verify depth. How many certs from the chain to verify */
  /* -1 to verify all certificates. */
  printf ("VDepth: %d\n", SSL_get_verify_depth(h->ssl));

  if (SSL_connect (h->ssl) < 1)
    return -7;          /* Couldn't finish SSL handshake */

  /* Get certificate information */
  return 0;
}

int SSL_send(Sslc* h, char* msg)
{
  int bytes = SSL_write(h->ssl, msg, strlen(msg));
  if (bytes<1)
    h->err = bytes;

  return bytes;
}

int TCP_select(Sslc* h, double timeout)
{
  fd_set fds;
  FD_ZERO(&fds);
  FD_SET(h->skt, &fds);
  fd_set *rset=&fds;
  fd_set *wset=NULL;

  struct timeval tv;
  tv.tv_sec = (int)(timeout);
  tv.tv_usec = (int)((timeout - (int)(timeout)) * 1000000.0);

  int ret = select(h->skt+1, rset, wset, NULL, &tv);
  return ret;
}

int SSL_recv(Sslc* h, char** data)
{
  size_t bytes, totalbytes = 0;
  char buffer[BUFFERSIZE];
  int sel;
  size_t allocated = BUFFERSIZE;
  *data = malloc (sizeof(char) * BUFFERSIZE);
  *data[0] = '\0';

  while (1)
    {
      sel = TCP_select(h, 1);
      if (sel<0)
    return -6;      /* select fail */
      else if (sel==0)
    {
      return totalbytes;
    }
      /* We must include terminator after reading */
      bytes = SSL_read (h->ssl, buffer, BUFFERSIZE -1);
      buffer[bytes] = '\0';

      if (bytes<1)
    return -7;

      if (totalbytes + bytes > allocated)
    {
      allocated+=BUFFERSIZE;
      *data = realloc(*data, allocated * sizeof(char));
    }
      strcat(*data, buffer);
      totalbytes+=bytes;
    }
  return totalbytes;
}

void SSL_print_info(Sslc* h)
{
  long vresult = SSL_get_verify_result (h->ssl);
  X509* cert;
  STACK_OF(X509) *chain;
  int i;

  printf ("Verify result: %ld (%s)\n", vresult, X509_verify_cert_error_string(vresult));
  printf ("Verify mode: %d\n", SSL_get_verify_mode(h->ssl)); /* If it's 1 SSL will fail if not verified */
  printf ("SSL version: %s\n", SSL_get_version(h->ssl));
  printf ("Cipher name : %s\n", SSL_get_cipher_name(h->ssl));
  printf ("Cipher version: %s\n", SSL_get_cipher_version(h->ssl));
  printf ("Cipher description: %s\n", SSL_cipher_description((SSL_CIPHER*)SSL_get_current_cipher(h->ssl)));
  printf ("--------------------------------------------\n");
  printf ("Certificate:\n");
  cert = SSL_get_peer_certificate(h->ssl);
  if (cert)
    {
      SSL_print_certificate_info(cert);
      /* X509_free(cert); */
    }
  else
    fprintf(stderr, "------ ERROR: Peer certificate not present!!\n");

  printf ("--------------------------------------------\n");
  printf ("The entire certificate chain: \n");
  chain = SSL_get_peer_cert_chain(h->ssl);
  if (chain)
    {
      printf ("Certificates on chain: %d\n", sk_X509_num(chain));
      for (i=0; i<sk_X509_num(chain); i++)
    {
      cert = sk_X509_value(chain, i);
      if (cert)
        {
          SSL_print_certificate_info(cert);
          /* Not a good idea to free, it's an internal pointer and may
             break something*/

          /* X509_free(cert); */
          printf ("            ·················\n");
        }
    }
    }
  else
    fprintf (stderr, "------- ERROR: Couldn't get certificate chain!!\n");
}

void SSL_print_certificate_info(X509* cert)
{
  X509_NAME *certname;
  X509_NAME_ENTRY* entry;
  char *s;
  char buffer[STRBUFFERSIZE];
  int i, n;

  certname = X509_get_subject_name(cert);
  for (i=0; i< X509_NAME_entry_count(certname); i++)
    {
      entry = X509_NAME_get_entry(certname, i);
      if (entry == NULL)    /* error test. May exit the loop */
    continue;

      /* extracted from X509_NAME_print_ex() */
      int n = OBJ_obj2nid(entry->object);
      if ((n == NID_undef) || ((s = (char*)OBJ_nid2sn(n)) == NULL))
    {
      i2t_ASN1_OBJECT(buffer, sizeof(buffer), entry->object);
      s = buffer;
    }
      printf ("%s = %s\n", s, entry->value->data);
      /* We must NOT free entries (they are internal pointers) */
    }
  print_time(X509_get_notBefore(cert), "Not Before", "%d/%m/%Y %H:%M:%S");
  print_time(X509_get_notAfter(cert),  "Not After", "%d/%m/%Y %H:%M:%S");
  printf ("Valid certificate?: %s\n", X509_verify_cert_error_string(check_certificate_validity(cert)));

  /* We must NOT free X509_get_subject_name() objects */
  /* X509_NAME_free(certname); */
}

char *time_t_to_str(char *buffer, size_t bufsize, const char* format, time_t *tim)
{
  struct tm _tm;
  gmtime_r(tim, &_tm);
  strftime(buffer, bufsize, format, &_tm);
  return buffer;
}

void print_time(ASN1_TIME* asn1time, char* pre_string, char* dateformat)
{
  time_t tim;
  char buffer[STRBUFFERSIZE];

  if (ASN1_TIME_to_time_t(asn1time, &tim)==0)
    printf ("%s: %s\n", pre_string, time_t_to_str(buffer, STRBUFFERSIZE, dateformat, &tim));
  else
    printf ("%s: (error)\n", pre_string);
}

char *SSL_cipher_description(SSL_CIPHER* cipher)
{
  char *tmp = malloc(sizeof(char)*STRBUFFERSIZE);
  tmp[0] = '\0';
  if (cipher)
    SSL_CIPHER_description(cipher, tmp, STRBUFFERSIZE);

  return tmp;
}

void print_usage(char *executable)
{
  fprintf(stderr, "You must specify a web server to connect through HTTPS and additionally a port:\n");
  fprintf(stderr, "  %s server [port]\n", executable);
  fprintf(stderr, "------------\n\n");
  exit(-1);
}

void panic(char *msg)
{
  fprintf (stderr, "Error: %s (errno %d, %s)\n", msg, errno, strerror(errno));
  /* Print SSL errors */
  ERR_print_errors_fp(stderr);
  exit(2);
}

Y además, lo he subido a github por lo que se podrá ir actualizando.

Para compilar:

$ gcc -o myssl myssql.c -lcrypto -lssl

y para ejecutar:

$ ./myssl servidor [puerto]

Alguna cosa curiosa en el código

Algo digno de mención en el código anterior es que para hacer lectura y escritura de mensajes ya no usaremos send() ni recv(), lo que usábamos de toda la vida para conectar con sockets, ahora usaremos SSL_read() y SSL_write() que se encargarán entre otras cosas de cifrar/descifrar, enviar/recibir la información y si hay algún problema con las claves o la negociación, intentar solucionarlo. Los sockets no los usaremos más (a no ser que queramos algo específico, y como mucho usarlos para hacer un select() porque los datos que pasan por ellos no tendrán mucho sentido para nosotros. Desde que llamamos a SSL_set_fd() podemos olvidarnos.

Muchas funciones de obtención de información de certificados: sk_X509_value(), X509_NAME_get_entry() devuelven un puntero, y como somos de C, todos los punteros que reservamos, deben ser liberados, sobre todo, si en la documentación encontramos cosas como X509_NAME_free(), pero no debe ser así. Sólo llamaremos a xxx_free() y hemos llamado previamente a xxx_new(), si no, se están devolviendo punteros internos que se están usando para nuestro programa.

No he incluido constantes de error ni nada, es sólo una prueba de concepto que quiero incorporar en una pequeña biblioteca que tengo en desarrollo.

¡A jugar!
Foto: AshtonPal (Flickr CC-by)

También podría interesarte....

Leave a Reply