2006-12-06 [colin] 2.6.1cvs8
[claws.git] / src / common / ssl_certificate.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2006 Hiroyuki Yamamoto
4  * This file Copyright (C) 2002-2005 Colin Leroy <colin@colino.net>
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 2 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, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #endif
24
25 #if USE_OPENSSL
26
27 #include <openssl/ssl.h>
28 #include <glib.h>
29 #include <glib/gi18n.h>
30
31 #include "ssl_certificate.h"
32 #include "utils.h"
33 #include "log.h"
34 #include "socket.h"
35 #include "hooks.h"
36
37 static GHashTable *warned_expired = NULL;
38
39 static SSLCertificate *ssl_certificate_new_lookup(X509 *x509_cert, gchar *host, gushort port, gboolean lookup);
40
41 /* from Courier */
42 time_t asn1toTime(ASN1_TIME *asn1Time)
43 {
44         struct tm tm;
45         int offset;
46
47         if (asn1Time == NULL || asn1Time->length < 13)
48                 return 0;
49
50         memset(&tm, 0, sizeof(tm));
51
52 #define N2(n)   ((asn1Time->data[n]-'0')*10 + asn1Time->data[(n)+1]-'0')
53
54 #define CPY(f,n) (tm.f=N2(n))
55
56         CPY(tm_year,0);
57
58         if(tm.tm_year < 50)
59                 tm.tm_year += 100; /* Sux */
60
61         CPY(tm_mon, 2);
62         --tm.tm_mon;
63         CPY(tm_mday, 4);
64         CPY(tm_hour, 6);
65         CPY(tm_min, 8);
66         CPY(tm_sec, 10);
67
68         offset=0;
69
70         if (asn1Time->data[12] != 'Z')
71         {
72                 if (asn1Time->length < 17)
73                         return 0;
74
75                 offset=N2(13)*3600+N2(15)*60;
76
77                 if (asn1Time->data[12] == '-')
78                         offset= -offset;
79         }
80
81 #undef N2
82 #undef CPY
83
84         return mktime(&tm)-offset;
85 }
86
87 static char * get_fqdn(char *host)
88 {
89         struct hostent *hp;
90
91         if (host == NULL || strlen(host) == 0)
92                 return g_strdup("");
93
94         hp = my_gethostbyname(host);
95         if (hp == NULL)
96                 return g_strdup(host); /*caller should free*/
97         else 
98                 return g_strdup(hp->h_name);
99 }
100
101 char * readable_fingerprint(unsigned char *src, int len) 
102 {
103         int i=0;
104         char * ret;
105         
106         if (src == NULL)
107                 return NULL;
108         ret = g_strdup("");
109         while (i < len) {
110                 char *tmp2;
111                 if(i>0)
112                         tmp2 = g_strdup_printf("%s:%02X", ret, src[i]);
113                 else
114                         tmp2 = g_strdup_printf("%02X", src[i]);
115                 g_free(ret);
116                 ret = g_strdup(tmp2);
117                 g_free(tmp2);
118                 i++;
119         }
120         return ret;
121 }
122
123 SSLCertificate *ssl_certificate_new(X509 *x509_cert, gchar *host, gushort port)
124 {
125         return ssl_certificate_new_lookup(x509_cert, host, port, TRUE);
126 }
127
128 static SSLCertificate *ssl_certificate_new_lookup(X509 *x509_cert, gchar *host, gushort port, gboolean lookup)
129 {
130         SSLCertificate *cert = g_new0(SSLCertificate, 1);
131         unsigned int n;
132         unsigned char md[EVP_MAX_MD_SIZE];      
133         
134         if (host == NULL || x509_cert == NULL) {
135                 ssl_certificate_destroy(cert);
136                 return NULL;
137         }
138         cert->x509_cert = X509_dup(x509_cert);
139         if (lookup)
140                 cert->host = get_fqdn(host);
141         else
142                 cert->host = g_strdup(host);
143         cert->port = port;
144         
145         /* fingerprint */
146         X509_digest(cert->x509_cert, EVP_md5(), md, &n);
147         cert->fingerprint = readable_fingerprint(md, (int)n);
148
149         return cert;
150 }
151
152 static void ssl_certificate_save (SSLCertificate *cert)
153 {
154         gchar *file, *port;
155         FILE *fp;
156
157         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
158                           "certs", G_DIR_SEPARATOR_S, NULL);
159         
160         if (!is_dir_exist(file))
161                 make_dir_hier(file);
162         g_free(file);
163
164         port = g_strdup_printf("%d", cert->port);
165         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
166                           "certs", G_DIR_SEPARATOR_S,
167                           cert->host, ".", port, ".", cert->fingerprint, ".cert", NULL);
168
169         g_free(port);
170         fp = g_fopen(file, "wb");
171         if (fp == NULL) {
172                 g_free(file);
173                 debug_print("Can't save certificate !\n");
174                 return;
175         }
176         i2d_X509_fp(fp, cert->x509_cert);
177         g_free(file);
178         fclose(fp);
179
180 }
181
182 char* ssl_certificate_to_string(SSLCertificate *cert)
183 {
184         char *ret, buf[100];
185         char *issuer_commonname, *issuer_location, *issuer_organization;
186         char *subject_commonname, *subject_location, *subject_organization;
187         char *fingerprint, *sig_status;
188         unsigned int n;
189         unsigned char md[EVP_MAX_MD_SIZE];      
190         
191         /* issuer */    
192         if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
193                                        NID_commonName, buf, 100) >= 0)
194                 issuer_commonname = g_strdup(buf);
195         else
196                 issuer_commonname = g_strdup(_("<not in certificate>"));
197         if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
198                                        NID_localityName, buf, 100) >= 0) {
199                 issuer_location = g_strdup(buf);
200                 if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
201                                        NID_countryName, buf, 100) >= 0)
202                         issuer_location = g_strconcat(issuer_location,", ",buf, NULL);
203         } else if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
204                                        NID_countryName, buf, 100) >= 0)
205                 issuer_location = g_strdup(buf);
206         else
207                 issuer_location = g_strdup(_("<not in certificate>"));
208
209         if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
210                                        NID_organizationName, buf, 100) >= 0)
211                 issuer_organization = g_strdup(buf);
212         else 
213                 issuer_organization = g_strdup(_("<not in certificate>"));
214          
215         /* subject */   
216         if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
217                                        NID_commonName, buf, 100) >= 0)
218                 subject_commonname = g_strdup(buf);
219         else
220                 subject_commonname = g_strdup(_("<not in certificate>"));
221         if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
222                                        NID_localityName, buf, 100) >= 0) {
223                 subject_location = g_strdup(buf);
224                 if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
225                                        NID_countryName, buf, 100) >= 0)
226                         subject_location = g_strconcat(subject_location,", ",buf, NULL);
227         } else if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
228                                        NID_countryName, buf, 100) >= 0)
229                 subject_location = g_strdup(buf);
230         else
231                 subject_location = g_strdup(_("<not in certificate>"));
232
233         if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
234                                        NID_organizationName, buf, 100) >= 0)
235                 subject_organization = g_strdup(buf);
236         else 
237                 subject_organization = g_strdup(_("<not in certificate>"));
238          
239         /* fingerprint */
240         X509_digest(cert->x509_cert, EVP_md5(), md, &n);
241         fingerprint = readable_fingerprint(md, (int)n);
242
243         /* signature */
244         sig_status = ssl_certificate_check_signer(cert->x509_cert);
245
246         ret = g_strdup_printf(_("  Owner: %s (%s) in %s\n  Signed by: %s (%s) in %s\n  Fingerprint: %s\n  Signature status: %s"),
247                                 subject_commonname, subject_organization, subject_location, 
248                                 issuer_commonname, issuer_organization, issuer_location, 
249                                 fingerprint,
250                                 (sig_status==NULL ? "correct":sig_status));
251
252         g_free(issuer_commonname);
253         g_free(issuer_location);
254         g_free(issuer_organization);
255         g_free(subject_commonname);
256         g_free(subject_location);
257         g_free(subject_organization);
258         g_free(fingerprint);
259         g_free(sig_status);
260         return ret;
261 }
262         
263 void ssl_certificate_destroy(SSLCertificate *cert) 
264 {
265         if (cert == NULL)
266                 return;
267
268         if (cert->x509_cert)
269                 X509_free(cert->x509_cert);
270         g_free(cert->host);
271         g_free(cert->fingerprint);
272         g_free(cert);
273         cert = NULL;
274 }
275
276 void ssl_certificate_delete_from_disk(SSLCertificate *cert)
277 {
278         gchar *buf;
279         gchar *file;
280         buf = g_strdup_printf("%d", cert->port);
281         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
282                           "certs", G_DIR_SEPARATOR_S,
283                           cert->host, ".", buf, ".", cert->fingerprint, ".cert", NULL);
284         g_unlink (file);
285         g_free(file);
286         g_free(buf);
287 }
288
289 SSLCertificate *ssl_certificate_find (gchar *host, gushort port, const gchar *fingerprint)
290 {
291         return ssl_certificate_find_lookup (host, port, fingerprint, TRUE);
292 }
293
294 SSLCertificate *ssl_certificate_find_lookup (gchar *host, gushort port, const gchar *fingerprint, gboolean lookup)
295 {
296         gchar *file = NULL;
297         gchar *buf;
298         gchar *fqdn_host;
299         SSLCertificate *cert = NULL;
300         X509 *tmp_x509;
301         FILE *fp = NULL;
302         gboolean must_rename = FALSE;
303
304         if (lookup)
305                 fqdn_host = get_fqdn(host);
306         else
307                 fqdn_host = g_strdup(host);
308
309         buf = g_strdup_printf("%d", port);
310         
311         if (fingerprint != NULL) {
312                 file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
313                           "certs", G_DIR_SEPARATOR_S,
314                           fqdn_host, ".", buf, ".", fingerprint, ".cert", NULL);
315                 fp = g_fopen(file, "rb");
316         }
317         if (fp == NULL) {
318                 /* see if we have the old one */
319                 g_free(file);
320                 file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
321                           "certs", G_DIR_SEPARATOR_S,
322                           fqdn_host, ".", buf, ".cert", NULL);
323                 fp = g_fopen(file, "rb");
324
325                 if (fp)
326                         must_rename = (fingerprint != NULL);
327         }
328         if (fp == NULL) {
329                 g_free(file);
330                 g_free(fqdn_host);
331                 g_free(buf);
332                 return NULL;
333         }
334         
335         if ((tmp_x509 = d2i_X509_fp(fp, 0)) != NULL) {
336                 cert = ssl_certificate_new_lookup(tmp_x509, fqdn_host, port, lookup);
337                 X509_free(tmp_x509);
338         }
339         fclose(fp);
340         g_free(file);
341         
342         if (must_rename) {
343                 gchar *old = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
344                           "certs", G_DIR_SEPARATOR_S,
345                           fqdn_host, ".", buf, ".cert", NULL);
346                 gchar *new = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
347                           "certs", G_DIR_SEPARATOR_S,
348                           fqdn_host, ".", buf, ".", fingerprint, ".cert", NULL);
349                 move_file(old, new, TRUE);
350                 g_free(old);
351                 g_free(new);
352         }
353         g_free(buf);
354         g_free(fqdn_host);
355
356         return cert;
357 }
358
359 static gboolean ssl_certificate_compare (SSLCertificate *cert_a, SSLCertificate *cert_b)
360 {
361         if (cert_a == NULL || cert_b == NULL)
362                 return FALSE;
363         else if (!X509_cmp(cert_a->x509_cert, cert_b->x509_cert))
364                 return TRUE;
365         else
366                 return FALSE;
367 }
368
369 char *ssl_certificate_check_signer (X509 *cert) 
370 {
371         X509_STORE_CTX store_ctx;
372         X509_STORE *store;
373         char *err_msg = NULL;
374
375         store = X509_STORE_new();
376         if (store == NULL) {
377                 printf("Can't create X509_STORE\n");
378                 return NULL;
379         }
380         if (!X509_STORE_set_default_paths(store)) {
381                 X509_STORE_free (store);
382                 return g_strdup(_("Couldn't load X509 default paths"));
383         }
384         
385         X509_STORE_CTX_init (&store_ctx, store, cert, NULL);
386
387         if(!X509_verify_cert (&store_ctx)) {
388                 err_msg = g_strdup(X509_verify_cert_error_string(
389                                         X509_STORE_CTX_get_error(&store_ctx)));
390                 debug_print("Can't check signer: %s\n", err_msg);
391                 X509_STORE_CTX_cleanup (&store_ctx);
392                 X509_STORE_free (store);
393                 return err_msg;
394                         
395         }
396         X509_STORE_CTX_cleanup (&store_ctx);
397         X509_STORE_free (store);
398         return NULL;
399 }
400
401 gboolean ssl_certificate_check (X509 *x509_cert, gchar *fqdn, gchar *host, gushort port)
402 {
403         SSLCertificate *current_cert = NULL;
404         SSLCertificate *known_cert;
405         SSLCertHookData cert_hook_data;
406         gchar *fqdn_host = NULL;        
407         gchar *fingerprint;
408         unsigned int n;
409         unsigned char md[EVP_MAX_MD_SIZE];      
410
411         if (fqdn)
412                 fqdn_host = g_strdup(fqdn);
413         else if (host)
414                 fqdn_host = get_fqdn(host);
415         else {
416                 g_warning("no host!\n");
417                 return FALSE;
418         }
419                 
420         current_cert = ssl_certificate_new_lookup(x509_cert, fqdn_host, port, FALSE);
421         
422         if (current_cert == NULL) {
423                 debug_print("Buggy certificate !\n");
424                 g_free(fqdn_host);
425                 return FALSE;
426         }
427
428         /* fingerprint */
429         X509_digest(x509_cert, EVP_md5(), md, &n);
430         fingerprint = readable_fingerprint(md, (int)n);
431
432         known_cert = ssl_certificate_find_lookup (fqdn_host, port, fingerprint, FALSE);
433
434         g_free(fingerprint);
435         g_free(fqdn_host);
436
437         if (known_cert == NULL) {
438                 cert_hook_data.cert = current_cert;
439                 cert_hook_data.old_cert = NULL;
440                 cert_hook_data.expired = FALSE;
441                 cert_hook_data.accept = FALSE;
442                 
443                 hooks_invoke(SSLCERT_ASK_HOOKLIST, &cert_hook_data);
444                 
445                 if (!cert_hook_data.accept) {
446                         ssl_certificate_destroy(current_cert);
447                         return FALSE;
448                 } else {
449                         ssl_certificate_save(current_cert);
450                         ssl_certificate_destroy(current_cert);
451                         return TRUE;
452                 }
453         } else if (!ssl_certificate_compare (current_cert, known_cert)) {
454                 cert_hook_data.cert = current_cert;
455                 cert_hook_data.old_cert = known_cert;
456                 cert_hook_data.expired = FALSE;
457                 cert_hook_data.accept = FALSE;
458                 
459                 hooks_invoke(SSLCERT_ASK_HOOKLIST, &cert_hook_data);
460
461                 if (!cert_hook_data.accept) {
462                         ssl_certificate_destroy(current_cert);
463                         ssl_certificate_destroy(known_cert);
464                         return FALSE;
465                 } else {
466                         ssl_certificate_save(current_cert);
467                         ssl_certificate_destroy(current_cert);
468                         ssl_certificate_destroy(known_cert);
469                         return TRUE;
470                 }
471         } else if (asn1toTime(X509_get_notAfter(current_cert->x509_cert)) < time(NULL)) {
472                 gchar *tmp = g_strdup_printf("%s:%d", current_cert->host, current_cert->port);
473                 
474                 if (warned_expired == NULL)
475                         warned_expired = g_hash_table_new(g_str_hash, g_str_equal);
476                 
477                 if (g_hash_table_lookup(warned_expired, tmp)) {
478                         g_free(tmp);
479                         ssl_certificate_destroy(current_cert);
480                         ssl_certificate_destroy(known_cert);
481                         return TRUE;
482                 }
483                         
484                 cert_hook_data.cert = current_cert;
485                 cert_hook_data.old_cert = NULL;
486                 cert_hook_data.expired = TRUE;
487                 cert_hook_data.accept = FALSE;
488                 
489                 hooks_invoke(SSLCERT_ASK_HOOKLIST, &cert_hook_data);
490
491                 if (!cert_hook_data.accept) {
492                         g_free(tmp);
493                         ssl_certificate_destroy(current_cert);
494                         ssl_certificate_destroy(known_cert);
495                         return FALSE;
496                 } else {
497                         g_hash_table_insert(warned_expired, tmp, GINT_TO_POINTER(1));
498                         ssl_certificate_destroy(current_cert);
499                         ssl_certificate_destroy(known_cert);
500                         return TRUE;
501                 }
502         }
503
504         ssl_certificate_destroy(current_cert);
505         ssl_certificate_destroy(known_cert);
506         return TRUE;
507 }
508
509 #endif /* USE_OPENSSL */