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