2005-11-02 [cleroy] 1.9.99cvs6
[claws.git] / src / common / ssl_certificate.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2005 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 SSLCertificate *ssl_certificate_new_lookup(X509 *x509_cert, gchar *host, gushort port, gboolean lookup);
38
39 static char * get_fqdn(char *host)
40 {
41         struct hostent *hp;
42
43         if (host == NULL || strlen(host) == 0)
44                 return g_strdup("");
45
46         hp = my_gethostbyname(host);
47         if (hp == NULL)
48                 return g_strdup(host); /*caller should free*/
49         else 
50                 return g_strdup(hp->h_name);
51 }
52
53 char * readable_fingerprint(unsigned char *src, int len) 
54 {
55         int i=0;
56         char * ret;
57         
58         if (src == NULL)
59                 return NULL;
60         ret = g_strdup("");
61         while (i < len) {
62                 char *tmp2;
63                 if(i>0)
64                         tmp2 = g_strdup_printf("%s:%02X", ret, src[i]);
65                 else
66                         tmp2 = g_strdup_printf("%02X", src[i]);
67                 g_free(ret);
68                 ret = g_strdup(tmp2);
69                 g_free(tmp2);
70                 i++;
71         }
72         return ret;
73 }
74
75 SSLCertificate *ssl_certificate_new(X509 *x509_cert, gchar *host, gushort port)
76 {
77         return ssl_certificate_new_lookup(x509_cert, host, port, TRUE);
78 }
79
80 static SSLCertificate *ssl_certificate_new_lookup(X509 *x509_cert, gchar *host, gushort port, gboolean lookup)
81 {
82         SSLCertificate *cert = g_new0(SSLCertificate, 1);
83         
84         if (host == NULL || x509_cert == NULL) {
85                 ssl_certificate_destroy(cert);
86                 return NULL;
87         }
88         cert->x509_cert = X509_dup(x509_cert);
89         if (lookup)
90                 cert->host = get_fqdn(host);
91         else
92                 cert->host = g_strdup(host);
93         cert->port = port;
94         return cert;
95 }
96
97 static void ssl_certificate_save (SSLCertificate *cert)
98 {
99         gchar *file, *port;
100         FILE *fp;
101
102         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
103                           "certs", G_DIR_SEPARATOR_S, NULL);
104         
105         if (!is_dir_exist(file))
106                 make_dir_hier(file);
107         g_free(file);
108
109         port = g_strdup_printf("%d", cert->port);
110         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
111                           "certs", G_DIR_SEPARATOR_S,
112                           cert->host, ".", port, ".cert", NULL);
113
114         g_free(port);
115         fp = g_fopen(file, "wb");
116         if (fp == NULL) {
117                 g_free(file);
118                 debug_print("Can't save certificate !\n");
119                 return;
120         }
121         i2d_X509_fp(fp, cert->x509_cert);
122         g_free(file);
123         fclose(fp);
124
125 }
126
127 char* ssl_certificate_to_string(SSLCertificate *cert)
128 {
129         char *ret, buf[100];
130         char *issuer_commonname, *issuer_location, *issuer_organization;
131         char *subject_commonname, *subject_location, *subject_organization;
132         char *fingerprint, *sig_status;
133         unsigned int n;
134         unsigned char md[EVP_MAX_MD_SIZE];      
135         
136         /* issuer */    
137         if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
138                                        NID_commonName, buf, 100) >= 0)
139                 issuer_commonname = g_strdup(buf);
140         else
141                 issuer_commonname = g_strdup(_("<not in certificate>"));
142         if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
143                                        NID_localityName, buf, 100) >= 0) {
144                 issuer_location = g_strdup(buf);
145                 if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
146                                        NID_countryName, buf, 100) >= 0)
147                         issuer_location = g_strconcat(issuer_location,", ",buf, NULL);
148         } else if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
149                                        NID_countryName, buf, 100) >= 0)
150                 issuer_location = g_strdup(buf);
151         else
152                 issuer_location = g_strdup(_("<not in certificate>"));
153
154         if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
155                                        NID_organizationName, buf, 100) >= 0)
156                 issuer_organization = g_strdup(buf);
157         else 
158                 issuer_organization = g_strdup(_("<not in certificate>"));
159          
160         /* subject */   
161         if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
162                                        NID_commonName, buf, 100) >= 0)
163                 subject_commonname = g_strdup(buf);
164         else
165                 subject_commonname = g_strdup(_("<not in certificate>"));
166         if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
167                                        NID_localityName, buf, 100) >= 0) {
168                 subject_location = g_strdup(buf);
169                 if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
170                                        NID_countryName, buf, 100) >= 0)
171                         subject_location = g_strconcat(subject_location,", ",buf, NULL);
172         } else if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
173                                        NID_countryName, buf, 100) >= 0)
174                 subject_location = g_strdup(buf);
175         else
176                 subject_location = g_strdup(_("<not in certificate>"));
177
178         if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
179                                        NID_organizationName, buf, 100) >= 0)
180                 subject_organization = g_strdup(buf);
181         else 
182                 subject_organization = g_strdup(_("<not in certificate>"));
183          
184         /* fingerprint */
185         X509_digest(cert->x509_cert, EVP_md5(), md, &n);
186         fingerprint = readable_fingerprint(md, (int)n);
187
188         /* signature */
189         sig_status = ssl_certificate_check_signer(cert->x509_cert);
190
191         ret = g_strdup_printf(_("  Owner: %s (%s) in %s\n  Signed by: %s (%s) in %s\n  Fingerprint: %s\n  Signature status: %s"),
192                                 subject_commonname, subject_organization, subject_location, 
193                                 issuer_commonname, issuer_organization, issuer_location, 
194                                 fingerprint,
195                                 (sig_status==NULL ? "correct":sig_status));
196
197         if (issuer_commonname)
198                 g_free(issuer_commonname);
199         if (issuer_location)
200                 g_free(issuer_location);
201         if (issuer_organization)
202                 g_free(issuer_organization);
203         if (subject_commonname)
204                 g_free(subject_commonname);
205         if (subject_location)
206                 g_free(subject_location);
207         if (subject_organization)
208                 g_free(subject_organization);
209         if (fingerprint)
210                 g_free(fingerprint);
211         if (sig_status)
212                 g_free(sig_status);
213         return ret;
214 }
215         
216 void ssl_certificate_destroy(SSLCertificate *cert) 
217 {
218         if (cert == NULL)
219                 return;
220
221         if (cert->x509_cert)
222                 X509_free(cert->x509_cert);
223         if (cert->host) 
224                 g_free(cert->host);
225         g_free(cert);
226         cert = NULL;
227 }
228
229 void ssl_certificate_delete_from_disk(SSLCertificate *cert)
230 {
231         gchar *buf;
232         gchar *file;
233         buf = g_strdup_printf("%d", cert->port);
234         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
235                           "certs", G_DIR_SEPARATOR_S,
236                           cert->host, ".", buf, ".cert", NULL);
237         g_unlink (file);
238         g_free(buf);
239         g_free(file);
240 }
241
242 SSLCertificate *ssl_certificate_find (gchar *host, gushort port)
243 {
244         return ssl_certificate_find_lookup (host, port, TRUE);
245 }
246
247 SSLCertificate *ssl_certificate_find_lookup (gchar *host, gushort port, gboolean lookup)
248 {
249         gchar *file;
250         gchar *buf;
251         gchar *fqdn_host;
252         SSLCertificate *cert = NULL;
253         X509 *tmp_x509;
254         FILE *fp;
255
256         if (lookup)
257                 fqdn_host = get_fqdn(host);
258         else
259                 fqdn_host = g_strdup(host);
260
261         buf = g_strdup_printf("%d", port);
262         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
263                           "certs", G_DIR_SEPARATOR_S,
264                           fqdn_host, ".", buf, ".cert", NULL);
265
266         g_free(buf);
267         fp = g_fopen(file, "rb");
268         if (fp == NULL) {
269                 g_free(file);
270                 g_free(fqdn_host);
271                 return NULL;
272         }
273         
274         
275         if ((tmp_x509 = d2i_X509_fp(fp, 0)) != NULL) {
276                 cert = ssl_certificate_new_lookup(tmp_x509, fqdn_host, port, lookup);
277                 X509_free(tmp_x509);
278         }
279         fclose(fp);
280         g_free(file);
281         g_free(fqdn_host);
282         
283         return cert;
284 }
285
286 static gboolean ssl_certificate_compare (SSLCertificate *cert_a, SSLCertificate *cert_b)
287 {
288         if (cert_a == NULL || cert_b == NULL)
289                 return FALSE;
290         else if (!X509_cmp(cert_a->x509_cert, cert_b->x509_cert))
291                 return TRUE;    
292         else
293                 return FALSE;
294 }
295
296 char *ssl_certificate_check_signer (X509 *cert) 
297 {
298         X509_STORE_CTX store_ctx;
299         X509_STORE *store;
300         char *err_msg = NULL;
301
302         store = X509_STORE_new();
303         if (store == NULL) {
304                 printf("Can't create X509_STORE\n");
305                 return NULL;
306         }
307         if (!X509_STORE_set_default_paths(store)) {
308                 X509_STORE_free (store);
309                 return g_strdup(_("Can't load X509 default paths"));
310         }
311         
312         X509_STORE_CTX_init (&store_ctx, store, cert, NULL);
313
314         if(!X509_verify_cert (&store_ctx)) {
315                 err_msg = g_strdup(X509_verify_cert_error_string(
316                                         X509_STORE_CTX_get_error(&store_ctx)));
317                 debug_print("Can't check signer: %s\n", err_msg);
318                 X509_STORE_CTX_cleanup (&store_ctx);
319                 X509_STORE_free (store);
320                 return err_msg;
321                         
322         }
323         X509_STORE_CTX_cleanup (&store_ctx);
324         X509_STORE_free (store);
325         return NULL;
326 }
327
328 gboolean ssl_certificate_check (X509 *x509_cert, gchar *host, gushort port)
329 {
330         SSLCertificate *current_cert = ssl_certificate_new(x509_cert, host, port);
331         SSLCertificate *known_cert;
332         SSLCertHookData cert_hook_data;
333         
334         if (current_cert == NULL) {
335                 debug_print("Buggy certificate !\n");
336                 return FALSE;
337         }
338
339         known_cert = ssl_certificate_find (host, port);
340
341         if (known_cert == NULL) {
342                 gchar *err_msg, *cur_cert_str, *sig_status;
343                 
344                 sig_status = ssl_certificate_check_signer(x509_cert);
345
346 #if 0 /* disabled pref for now */
347                 if (sig_status == NULL && !prefs_common.ssl_ask_unknown_valid) {
348                         /* trust and accept silently if hostnames match */
349                         char *buf; /* don't free buf ! */
350                         if (X509_NAME_get_text_by_NID(X509_get_subject_name(x509_cert), 
351                                        NID_commonName, buf, 100) >= 0)
352                                 if (!strcmp(buf, current_cert->host)) {
353                                         g_free(sig_status);
354                                         ssl_certificate_save(current_cert);
355                                         ssl_certificate_destroy(current_cert);
356                                         return TRUE;            
357                                 }
358                 }
359 #endif
360                 g_free(sig_status);
361
362                 cur_cert_str = ssl_certificate_to_string(current_cert);
363                 
364                 err_msg = g_strdup_printf(_("%s presented an unknown SSL certificate:\n%s"),
365                                           current_cert->host,
366                                           cur_cert_str);
367                 g_free (cur_cert_str);
368
369 #if 0 /* disabled for now */
370                 if (prefs_common.no_recv_err_panel) {
371                         log_error(_("%s\n\nMail won't be retrieved on this account until you save the certificate.\n(Uncheck the \"%s\" preference).\n"),
372                                         err_msg,
373                                         _("Don't popup error dialog on receive error"));
374                         g_free(err_msg);
375                         return FALSE;
376                 }
377 #endif
378                 cert_hook_data.cert = current_cert;
379                 cert_hook_data.old_cert = NULL;
380                 cert_hook_data.accept = FALSE;
381                 
382                 hooks_invoke(SSLCERT_ASK_HOOKLIST, &cert_hook_data);
383                 
384                 g_free(err_msg);
385
386                 if (!cert_hook_data.accept) {
387                         ssl_certificate_destroy(current_cert);
388                         return FALSE;
389                 } else {
390                         ssl_certificate_save(current_cert);
391                         ssl_certificate_destroy(current_cert);
392                         return TRUE;
393                 }
394         }
395         else if (!ssl_certificate_compare (current_cert, known_cert)) {
396                 gchar *err_msg, *known_cert_str, *cur_cert_str;
397                 
398                 known_cert_str = ssl_certificate_to_string(known_cert);
399                 cur_cert_str = ssl_certificate_to_string(current_cert);
400                 err_msg = g_strdup_printf(_("%s's SSL certificate changed !\nWe have saved this one:\n%s\n\nIt is now:\n%s\n\nThis could mean the server answering is not the known one."),
401                                           current_cert->host,
402                                           known_cert_str,
403                                           cur_cert_str);
404                 g_free (cur_cert_str);
405                 g_free (known_cert_str);
406
407 #if 0
408                 if (prefs_common.no_recv_err_panel) {
409                         log_error(_("%s\n\nMail won't be retrieved on this account until you save the certificate.\n(Uncheck the \"%s\" preference).\n"),
410                                         err_msg,
411                                         _("Don't popup error dialog on receive error"));
412                         g_free(err_msg);
413                         return FALSE;
414                 }
415 #endif
416                 cert_hook_data.cert = current_cert;
417                 cert_hook_data.old_cert = known_cert;
418                 cert_hook_data.accept = FALSE;
419                 
420                 hooks_invoke(SSLCERT_ASK_HOOKLIST, &cert_hook_data);
421                 
422                 g_free(err_msg);
423
424                 if (!cert_hook_data.accept) {
425                         ssl_certificate_destroy(current_cert);
426                         ssl_certificate_destroy(known_cert);
427                         return FALSE;
428                 } else {
429                         ssl_certificate_save(current_cert);
430                         ssl_certificate_destroy(current_cert);
431                         ssl_certificate_destroy(known_cert);
432                         return TRUE;
433                 }
434         }
435
436         ssl_certificate_destroy(current_cert);
437         ssl_certificate_destroy(known_cert);
438         return TRUE;
439 }
440
441 #endif /* USE_OPENSSL */