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