6d230bd4d0f3d273c4ffcacddc12ac727b2b38b4
[claws.git] / src / common / ssl_certificate.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2007 Colin Leroy <colin@colino.net> 
4  * and the Claws Mail team
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see <http://www.gnu.org/licenses/>.
18  * 
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #endif
24
25 #if (defined(USE_OPENSSL) || defined (USE_GNUTLS))
26 #if USE_OPENSSL
27 #include <openssl/ssl.h>
28 #else
29 #include <gnutls/gnutls.h>
30 #include <gnutls/x509.h>
31 #include <sys/stat.h>
32 #include <unistd.h>
33 #include <string.h>
34 #endif
35 #include <sys/types.h>
36 #include <sys/socket.h>
37 #include <netinet/in.h>
38 #include <stdio.h>
39 #include <netdb.h>
40 #include <glib.h>
41 #include <glib/gi18n.h>
42
43 #ifdef G_OS_WIN32
44 #include "winsock2.h"
45 #endif
46 #include "ssl_certificate.h"
47 #include "utils.h"
48 #include "log.h"
49 #include "socket.h"
50 #include "hooks.h"
51 #include "defs.h"
52
53 static GHashTable *warned_expired = NULL;
54
55 gboolean prefs_common_unsafe_ssl_certs(void);
56
57 static gchar *get_certificate_path(const gchar *host, const gchar *port, const gchar *fp)
58 {
59         if (fp != NULL && prefs_common_unsafe_ssl_certs())
60                 return g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
61                           "certs", G_DIR_SEPARATOR_S,
62                           host, ".", port, ".", fp, ".cert", NULL);
63         else 
64                 return g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
65                           "certs", G_DIR_SEPARATOR_S,
66                           host, ".", port, ".cert", NULL);
67 }
68
69 #if USE_OPENSSL
70 static SSLCertificate *ssl_certificate_new_lookup(X509 *x509_cert, gchar *host, gushort port, gboolean lookup);
71 #else
72 static SSLCertificate *ssl_certificate_new_lookup(gnutls_x509_crt x509_cert, gchar *host, gushort port, gboolean lookup);
73 #endif
74 #if USE_OPENSSL
75 /* from Courier */
76 time_t asn1toTime(ASN1_TIME *asn1Time)
77 {
78         struct tm tm;
79         int offset;
80
81         if (asn1Time == NULL || asn1Time->length < 13)
82                 return 0;
83
84         memset(&tm, 0, sizeof(tm));
85
86 #define N2(n)   ((asn1Time->data[n]-'0')*10 + asn1Time->data[(n)+1]-'0')
87
88 #define CPY(f,n) (tm.f=N2(n))
89
90         CPY(tm_year,0);
91
92         if(tm.tm_year < 50)
93                 tm.tm_year += 100; /* Sux */
94
95         CPY(tm_mon, 2);
96         --tm.tm_mon;
97         CPY(tm_mday, 4);
98         CPY(tm_hour, 6);
99         CPY(tm_min, 8);
100         CPY(tm_sec, 10);
101
102         offset=0;
103
104         if (asn1Time->data[12] != 'Z')
105         {
106                 if (asn1Time->length < 17)
107                         return 0;
108
109                 offset=N2(13)*3600+N2(15)*60;
110
111                 if (asn1Time->data[12] == '-')
112                         offset= -offset;
113         }
114
115 #undef N2
116 #undef CPY
117
118         return mktime(&tm)-offset;
119 }
120 #endif
121
122 static char * get_fqdn(char *host)
123 {
124 #ifdef INET6
125         gint gai_err;
126         struct addrinfo hints, *res;
127 #else
128         struct hostent *hp;
129 #endif
130
131         if (host == NULL || strlen(host) == 0)
132                 return g_strdup("");
133 #ifdef INET6
134         memset(&hints, 0, sizeof(hints));
135         hints.ai_flags = AI_CANONNAME;
136         hints.ai_family = AF_UNSPEC;
137         hints.ai_socktype = SOCK_STREAM;
138         hints.ai_protocol = IPPROTO_TCP;
139
140         gai_err = getaddrinfo(host, NULL, &hints, &res);
141         if (gai_err != 0) {
142                 g_warning("getaddrinfo for %s failed: %s\n",
143                           host, gai_strerror(gai_err));
144                 return g_strdup(host);
145         }
146         if (res != NULL) {
147                 if (res->ai_canonname && strlen(res->ai_canonname)) {
148                         gchar *fqdn = g_strdup(res->ai_canonname);
149                         freeaddrinfo(res);
150                         return fqdn;
151                 } else {
152                         freeaddrinfo(res);
153                         return g_strdup(host);
154                 }
155         } else {
156                 return g_strdup(host);
157         }
158 #else
159         hp = my_gethostbyname(host);
160         if (hp == NULL)
161                 return g_strdup(host); /*caller should free*/
162         else 
163                 return g_strdup(hp->h_name);
164 #endif
165 }
166
167 char * readable_fingerprint(unsigned char *src, int len) 
168 {
169         int i=0;
170         char * ret;
171         
172         if (src == NULL)
173                 return NULL;
174         ret = g_strdup("");
175         while (i < len) {
176                 char *tmp2;
177                 if(i>0)
178                         tmp2 = g_strdup_printf("%s:%02X", ret, src[i]);
179                 else
180                         tmp2 = g_strdup_printf("%02X", src[i]);
181                 g_free(ret);
182                 ret = g_strdup(tmp2);
183                 g_free(tmp2);
184                 i++;
185         }
186         return ret;
187 }
188
189 #if USE_GNUTLS
190 static gnutls_x509_crt x509_crt_copy(gnutls_x509_crt src)
191 {
192     int ret;
193     size_t size;
194     gnutls_datum tmp;
195     gnutls_x509_crt dest;
196     
197     if (gnutls_x509_crt_init(&dest) != 0) {
198         g_warning("couldn't gnutls_x509_crt_init\n");
199         return NULL;
200     }
201
202     if (gnutls_x509_crt_export(src, GNUTLS_X509_FMT_DER, NULL, &size) 
203         != GNUTLS_E_SHORT_MEMORY_BUFFER) {
204         g_warning("couldn't gnutls_x509_crt_export to get size\n");
205         gnutls_x509_crt_deinit(dest);
206         return NULL;
207     }
208
209     tmp.data = malloc(size);
210     memset(tmp.data, 0, size);
211     ret = gnutls_x509_crt_export(src, GNUTLS_X509_FMT_DER, tmp.data, &size);
212     if (ret == 0) {
213         tmp.size = size;
214         ret = gnutls_x509_crt_import(dest, &tmp, GNUTLS_X509_FMT_DER);
215         if (ret) {
216                 g_warning("couldn't gnutls_x509_crt_import for real (%d %s)\n", ret, gnutls_strerror(ret));
217                 gnutls_x509_crt_deinit(dest);
218                 dest = NULL;
219         }
220     } else {
221         g_warning("couldn't gnutls_x509_crt_export for real (%d %s)\n", ret, gnutls_strerror(ret));
222         gnutls_x509_crt_deinit(dest);
223         dest = NULL;
224     }
225
226     free(tmp.data);
227     return dest;
228 }
229 #endif
230
231 #if USE_OPENSSL
232 static SSLCertificate *ssl_certificate_new_lookup(X509 *x509_cert, gchar *host, gushort port, gboolean lookup)
233 #else
234 static SSLCertificate *ssl_certificate_new_lookup(gnutls_x509_crt x509_cert, gchar *host, gushort port, gboolean lookup)
235 #endif
236 {
237         SSLCertificate *cert = g_new0(SSLCertificate, 1);
238         unsigned int n;
239         unsigned char md[128];  
240
241         if (host == NULL || x509_cert == NULL) {
242                 ssl_certificate_destroy(cert);
243                 return NULL;
244         }
245 #if USE_OPENSSL
246         cert->x509_cert = X509_dup(x509_cert);
247 #else
248         cert->x509_cert = x509_crt_copy(x509_cert);
249         cert->status = (guint)-1;
250 #endif
251         if (lookup)
252                 cert->host = get_fqdn(host);
253         else
254                 cert->host = g_strdup(host);
255         cert->port = port;
256         
257         /* fingerprint */
258 #if USE_OPENSSL
259         X509_digest(cert->x509_cert, EVP_md5(), md, &n);
260         cert->fingerprint = readable_fingerprint(md, (int)n);
261 #else
262         gnutls_x509_crt_get_fingerprint(cert->x509_cert, GNUTLS_DIG_MD5, md, &n);
263         cert->fingerprint = readable_fingerprint(md, (int)n);
264 #endif
265         return cert;
266 }
267
268 #ifdef USE_GNUTLS
269 static void i2d_X509_fp(FILE *fp, gnutls_x509_crt x509_cert)
270 {
271         char output[10*1024];
272         size_t cert_size = 10*1024;
273         int r;
274         
275         if ((r = gnutls_x509_crt_export(x509_cert, GNUTLS_X509_FMT_DER, output, &cert_size)) < 0) {
276                 g_warning("couldn't export cert %s (%d)\n", gnutls_strerror(r), cert_size);
277                 return;
278         }
279         debug_print("writing %zd bytes\n",cert_size);
280         if (fwrite(&output, 1, cert_size, fp) < cert_size) {
281                 g_warning("failed to write cert\n");
282         }
283 }
284 static gnutls_x509_crt d2i_X509_fp(FILE *fp, int unused)
285 {
286         gnutls_x509_crt cert = NULL;
287         gnutls_datum tmp;
288         struct stat s;
289         int r;
290         if (fstat(fileno(fp), &s) < 0) {
291                 perror("fstat");
292                 return NULL;
293         }
294         tmp.data = malloc(s.st_size);
295         memset(tmp.data, 0, s.st_size);
296         tmp.size = s.st_size;
297         if (fread (tmp.data, 1, s.st_size, fp) < s.st_size) {
298                 perror("fread");
299                 return NULL;
300         }
301
302         gnutls_x509_crt_init(&cert);
303         if ((r = gnutls_x509_crt_import(cert, &tmp, GNUTLS_X509_FMT_DER)) < 0) {
304                 g_warning("import failed: %s\n", gnutls_strerror(r));
305                 gnutls_x509_crt_deinit(cert);
306                 cert = NULL;
307         }
308         debug_print("got cert! %p\n", cert);
309         return cert;
310 }
311 #endif
312
313 static void ssl_certificate_save (SSLCertificate *cert)
314 {
315         gchar *file, *port;
316         FILE *fp;
317
318         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
319                           "certs", G_DIR_SEPARATOR_S, NULL);
320         
321         if (!is_dir_exist(file))
322                 make_dir_hier(file);
323         g_free(file);
324
325         port = g_strdup_printf("%d", cert->port);
326         file = get_certificate_path(cert->host, port, cert->fingerprint);
327
328         g_free(port);
329         fp = g_fopen(file, "wb");
330         if (fp == NULL) {
331                 g_free(file);
332                 debug_print("Can't save certificate !\n");
333                 return;
334         }
335         i2d_X509_fp(fp, cert->x509_cert);
336         g_free(file);
337         fclose(fp);
338
339 }
340
341 void ssl_certificate_destroy(SSLCertificate *cert) 
342 {
343         if (cert == NULL)
344                 return;
345
346         if (cert->x509_cert)
347 #if USE_OPENSSL
348                 X509_free(cert->x509_cert);
349 #else
350                 gnutls_x509_crt_deinit(cert->x509_cert);
351 #endif
352         g_free(cert->host);
353         g_free(cert->fingerprint);
354         g_free(cert);
355         cert = NULL;
356 }
357
358 void ssl_certificate_delete_from_disk(SSLCertificate *cert)
359 {
360         gchar *buf;
361         gchar *file;
362         buf = g_strdup_printf("%d", cert->port);
363         file = get_certificate_path(cert->host, buf, cert->fingerprint);
364         claws_unlink (file);
365         g_free(file);
366         g_free(buf);
367 }
368
369 SSLCertificate *ssl_certificate_find (gchar *host, gushort port, const gchar *fingerprint)
370 {
371         return ssl_certificate_find_lookup (host, port, fingerprint, TRUE);
372 }
373
374 SSLCertificate *ssl_certificate_find_lookup (gchar *host, gushort port, const gchar *fingerprint, gboolean lookup)
375 {
376         gchar *file = NULL;
377         gchar *buf;
378         gchar *fqdn_host;
379         SSLCertificate *cert = NULL;
380 #if USE_OPENSSL
381         X509 *tmp_x509;
382 #else
383         gnutls_x509_crt tmp_x509;
384 #endif
385         FILE *fp = NULL;
386         gboolean must_rename = FALSE;
387
388         if (lookup)
389                 fqdn_host = get_fqdn(host);
390         else
391                 fqdn_host = g_strdup(host);
392
393         buf = g_strdup_printf("%d", port);
394         
395         if (fingerprint != NULL) {
396                 file = get_certificate_path(fqdn_host, buf, fingerprint);
397                 fp = g_fopen(file, "rb");
398         }
399         if (fp == NULL) {
400                 /* see if we have the old one */
401                 debug_print("didn't get %s\n", file);
402                 g_free(file);
403                 file = get_certificate_path(fqdn_host, buf, NULL);
404                 fp = g_fopen(file, "rb");
405
406                 if (fp) {
407                         debug_print("got %s\n", file);
408                         must_rename = (fingerprint != NULL);
409                 }
410         } else {
411                 debug_print("got %s first try\n", file);
412         }
413         if (fp == NULL) {
414                 g_free(file);
415                 g_free(fqdn_host);
416                 g_free(buf);
417                 return NULL;
418         }
419         
420         if ((tmp_x509 = d2i_X509_fp(fp, 0)) != NULL) {
421                 cert = ssl_certificate_new_lookup(tmp_x509, fqdn_host, port, lookup);
422                 debug_print("got cert %p\n", cert);
423 #if USE_OPENSSL
424                 X509_free(tmp_x509);
425 #else
426                 gnutls_x509_crt_deinit(tmp_x509);
427 #endif
428         }
429         fclose(fp);
430         g_free(file);
431         
432         if (must_rename) {
433                 gchar *old = get_certificate_path(fqdn_host, buf, NULL);
434                 gchar *new = get_certificate_path(fqdn_host, buf, fingerprint);
435                 if (strcmp(old, new))
436                         move_file(old, new, TRUE);
437                 g_free(old);
438                 g_free(new);
439         }
440         g_free(buf);
441         g_free(fqdn_host);
442
443         return cert;
444 }
445
446 static gboolean ssl_certificate_compare (SSLCertificate *cert_a, SSLCertificate *cert_b)
447 {
448 #ifdef USE_OPENSSL
449         if (cert_a == NULL || cert_b == NULL)
450                 return FALSE;
451         else if (!X509_cmp(cert_a->x509_cert, cert_b->x509_cert))
452                 return TRUE;
453         else
454                 return FALSE;
455 #else
456         char *output_a;
457         char *output_b;
458         size_t cert_size_a = 0, cert_size_b = 0;
459         int r;
460
461         if (cert_a == NULL || cert_b == NULL)
462                 return FALSE;
463
464         if ((r = gnutls_x509_crt_export(cert_a->x509_cert, GNUTLS_X509_FMT_DER, NULL, &cert_size_a)) 
465             != GNUTLS_E_SHORT_MEMORY_BUFFER) {
466                 g_warning("couldn't gnutls_x509_crt_export to get size a %s\n", gnutls_strerror(r));
467                 return FALSE;
468         }
469
470         if ((r = gnutls_x509_crt_export(cert_b->x509_cert, GNUTLS_X509_FMT_DER, NULL, &cert_size_b))
471             != GNUTLS_E_SHORT_MEMORY_BUFFER) {
472                 g_warning("couldn't gnutls_x509_crt_export to get size b %s\n", gnutls_strerror(r));
473                 return FALSE;
474         }
475
476         output_a = malloc(cert_size_a);
477         output_b = malloc(cert_size_b);
478         if ((r = gnutls_x509_crt_export(cert_a->x509_cert, GNUTLS_X509_FMT_DER, output_a, &cert_size_a)) < 0) {
479                 g_warning("couldn't gnutls_x509_crt_export a %s\n", gnutls_strerror(r));
480                 return FALSE;
481         }
482         if ((r = gnutls_x509_crt_export(cert_b->x509_cert, GNUTLS_X509_FMT_DER, output_b, &cert_size_b)) < 0) {
483                 g_warning("couldn't gnutls_x509_crt_export b %s\n", gnutls_strerror(r));
484                 return FALSE;
485         }
486         if (cert_size_a != cert_size_b) {
487                 g_warning("size differ %d %d\n", cert_size_a, cert_size_b);
488                 return FALSE;
489         }
490         if (memcmp(output_a, output_b, cert_size_a)) {
491                 g_warning("contents differ\n");
492                 return FALSE;
493         }
494         
495         return TRUE;
496 #endif
497 }
498
499 #if USE_OPENSSL
500 char *ssl_certificate_check_signer (X509 *cert) 
501 {
502         X509_STORE_CTX store_ctx;
503         X509_STORE *store;
504         char *err_msg = NULL;
505
506         store = X509_STORE_new();
507         if (store == NULL) {
508                 g_print("Can't create X509_STORE\n");
509                 return NULL;
510         }
511         if (!X509_STORE_set_default_paths(store)) {
512                 X509_STORE_free (store);
513                 return g_strdup(_("Couldn't load X509 default paths"));
514         }
515         
516         X509_STORE_CTX_init (&store_ctx, store, cert, NULL);
517
518         if(!X509_verify_cert (&store_ctx)) {
519                 err_msg = g_strdup(X509_verify_cert_error_string(
520                                         X509_STORE_CTX_get_error(&store_ctx)));
521                 debug_print("Can't check signer: %s\n", err_msg);
522                 X509_STORE_CTX_cleanup (&store_ctx);
523                 X509_STORE_free (store);
524                 return err_msg;
525                         
526         }
527         X509_STORE_CTX_cleanup (&store_ctx);
528         X509_STORE_free (store);
529         return NULL;
530 }
531 #else
532 char *ssl_certificate_check_signer (gnutls_x509_crt cert, guint status) 
533 {
534         if (status == (guint)-1)
535                 return g_strdup(_("Uncheckable"));
536
537         if (status & GNUTLS_CERT_INVALID) {
538                 if (gnutls_x509_crt_check_issuer(cert, cert))
539                         return g_strdup(_("Self-signed certificate"));
540         }
541         if (status & GNUTLS_CERT_REVOKED)
542                 return g_strdup(_("Revoked certificate"));
543         if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
544                 return g_strdup(_("No certificate issuer found"));
545         if (status & GNUTLS_CERT_SIGNER_NOT_CA)
546                 return g_strdup(_("Certificate issuer is not a CA"));
547
548
549         return NULL;
550 }
551 #endif
552
553 #if USE_OPENSSL
554 gboolean ssl_certificate_check (X509 *x509_cert, gchar *fqdn, gchar *host, gushort port)
555 #else
556 gboolean ssl_certificate_check (gnutls_x509_crt x509_cert, guint status, gchar *fqdn, gchar *host, gushort port)
557 #endif
558 {
559         SSLCertificate *current_cert = NULL;
560         SSLCertificate *known_cert;
561         SSLCertHookData cert_hook_data;
562         gchar *fqdn_host = NULL;        
563         gchar *fingerprint;
564         unsigned int n;
565         unsigned char md[128];  
566
567         if (fqdn)
568                 fqdn_host = g_strdup(fqdn);
569         else if (host)
570                 fqdn_host = get_fqdn(host);
571         else {
572                 g_warning("no host!\n");
573                 return FALSE;
574         }
575                 
576         current_cert = ssl_certificate_new_lookup(x509_cert, fqdn_host, port, FALSE);
577         
578         if (current_cert == NULL) {
579                 debug_print("Buggy certificate !\n");
580                 g_free(fqdn_host);
581                 return FALSE;
582         }
583
584 #if USE_GNUTLS
585         current_cert->status = status;
586 #endif
587         /* fingerprint */
588 #if USE_OPENSSL
589         X509_digest(x509_cert, EVP_md5(), md, &n);
590         fingerprint = readable_fingerprint(md, (int)n);
591 #else
592         n = 128;
593         gnutls_x509_crt_get_fingerprint(x509_cert, GNUTLS_DIG_MD5, md, &n);
594         fingerprint = readable_fingerprint(md, (int)n);
595 #endif
596
597         known_cert = ssl_certificate_find_lookup (fqdn_host, port, fingerprint, FALSE);
598
599         g_free(fingerprint);
600         g_free(fqdn_host);
601
602         if (known_cert == NULL) {
603                 cert_hook_data.cert = current_cert;
604                 cert_hook_data.old_cert = NULL;
605                 cert_hook_data.expired = FALSE;
606                 cert_hook_data.accept = FALSE;
607                 
608                 hooks_invoke(SSLCERT_ASK_HOOKLIST, &cert_hook_data);
609                 
610                 if (!cert_hook_data.accept) {
611                         ssl_certificate_destroy(current_cert);
612                         return FALSE;
613                 } else {
614                         ssl_certificate_save(current_cert);
615                         ssl_certificate_destroy(current_cert);
616                         return TRUE;
617                 }
618         } else if (!ssl_certificate_compare (current_cert, known_cert)) {
619                 cert_hook_data.cert = current_cert;
620                 cert_hook_data.old_cert = known_cert;
621                 cert_hook_data.expired = FALSE;
622                 cert_hook_data.accept = FALSE;
623                 
624                 hooks_invoke(SSLCERT_ASK_HOOKLIST, &cert_hook_data);
625
626                 if (!cert_hook_data.accept) {
627                         ssl_certificate_destroy(current_cert);
628                         ssl_certificate_destroy(known_cert);
629                         return FALSE;
630                 } else {
631                         ssl_certificate_save(current_cert);
632                         ssl_certificate_destroy(current_cert);
633                         ssl_certificate_destroy(known_cert);
634                         return TRUE;
635                 }
636 #if USE_OPENSSL
637         } else if (asn1toTime(X509_get_notAfter(current_cert->x509_cert)) < time(NULL)) {
638 #else
639         } else if (gnutls_x509_crt_get_expiration_time(current_cert->x509_cert) < time(NULL)) {
640 #endif
641                 gchar *tmp = g_strdup_printf("%s:%d", current_cert->host, current_cert->port);
642                 
643                 if (warned_expired == NULL)
644                         warned_expired = g_hash_table_new(g_str_hash, g_str_equal);
645                 
646                 if (g_hash_table_lookup(warned_expired, tmp)) {
647                         g_free(tmp);
648                         ssl_certificate_destroy(current_cert);
649                         ssl_certificate_destroy(known_cert);
650                         return TRUE;
651                 }
652                         
653                 cert_hook_data.cert = current_cert;
654                 cert_hook_data.old_cert = NULL;
655                 cert_hook_data.expired = TRUE;
656                 cert_hook_data.accept = FALSE;
657                 
658                 hooks_invoke(SSLCERT_ASK_HOOKLIST, &cert_hook_data);
659
660                 if (!cert_hook_data.accept) {
661                         g_free(tmp);
662                         ssl_certificate_destroy(current_cert);
663                         ssl_certificate_destroy(known_cert);
664                         return FALSE;
665                 } else {
666                         g_hash_table_insert(warned_expired, tmp, GINT_TO_POINTER(1));
667                         ssl_certificate_destroy(current_cert);
668                         ssl_certificate_destroy(known_cert);
669                         return TRUE;
670                 }
671         }
672
673         ssl_certificate_destroy(current_cert);
674         ssl_certificate_destroy(known_cert);
675         return TRUE;
676 }
677
678 #endif /* USE_OPENSSL */