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