add port to certificate checker
[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 "alertpanel.h"
30 #include "utils.h"
31 #include "intl.h"
32 #include "prefs_common.h"
33
34 static void ssl_certificate_destroy(SSLCertificate *cert);
35 static char *ssl_certificate_check_signer (X509 *cert); 
36
37 static char * readable_fingerprint(unsigned char *src, int len) 
38 {
39         int i=0;
40         char * ret;
41         
42         if (src == NULL)
43                 return NULL;
44         ret = g_strdup("");
45         while (i < len) {
46                 char *tmp2;
47                 if(i>0)
48                         tmp2 = g_strdup_printf("%s:%02X", ret, src[i]);
49                 else
50                         tmp2 = g_strdup_printf("%02X", src[i]);
51                 g_free(ret);
52                 ret = g_strdup(tmp2);
53                 g_free(tmp2);
54                 i++;
55         }
56         return ret;
57 }
58
59 SSLCertificate *ssl_certificate_new(X509 *x509_cert, gchar *host, gushort port)
60 {
61         SSLCertificate *cert = g_new0(SSLCertificate, 1);
62         
63         if (host == NULL || x509_cert == NULL) {
64                 ssl_certificate_destroy(cert);
65                 return NULL;
66         }
67
68         cert->x509_cert = X509_dup(x509_cert);
69         cert->host = g_strdup(host);
70         cert->port = port;
71         return cert;
72 }
73
74 static void ssl_certificate_save (SSLCertificate *cert)
75 {
76         gchar *file, *port;
77         FILE *fp;
78
79         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
80                           "certs", G_DIR_SEPARATOR_S, NULL);
81         
82         if (!is_dir_exist(file))
83                 make_dir_hier(file);
84         g_free(file);
85
86         port = g_strdup_printf("%d", cert->port);
87         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
88                           "certs", G_DIR_SEPARATOR_S,
89                           cert->host, ".", port, ".cert", NULL);
90
91         g_free(port);
92         fp = fopen(file, "w");
93         if (fp == NULL) {
94                 g_free(file);
95                 alertpanel_error(_("Can't save certificate !"));
96                 return;
97         }
98         i2d_X509_fp(fp, cert->x509_cert);
99         g_free(file);
100         fclose(fp);
101
102 }
103
104 static char* ssl_certificate_to_string(SSLCertificate *cert)
105 {
106         char *ret, buf[100];
107         char *issuer_commonname, *issuer_location, *issuer_organization;
108         char *subject_commonname, *subject_location, *subject_organization;
109         char *fingerprint, *sig_status;
110         unsigned int n;
111         unsigned char md[EVP_MAX_MD_SIZE];      
112         
113         /* issuer */    
114          X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
115                                         NID_commonName, buf, 100);
116          issuer_commonname = g_strdup(buf);
117          X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
118                                         NID_localityName, buf, 100);
119          issuer_location = g_strdup(buf);
120          X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
121                                         NID_countryName, buf, 100);
122          issuer_location = g_strconcat(issuer_location,", ",buf, NULL);
123          X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
124                                         NID_organizationName, buf, 100);
125          issuer_organization = g_strdup(buf);
126
127         /* issuer */    
128          X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
129                                         NID_commonName, buf, 100);
130          subject_commonname = g_strdup(buf);
131          X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
132                                         NID_localityName, buf, 100);
133          subject_location = g_strdup(buf);
134          X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
135                                         NID_countryName, buf, 100);
136          subject_location = g_strconcat(subject_location,", ",buf, NULL);
137          X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
138                                         NID_organizationName, buf, 100);
139          subject_organization = g_strdup(buf);
140                  
141         /* fingerprint */
142         X509_digest(cert->x509_cert, EVP_md5(), md, &n);
143         fingerprint = readable_fingerprint(md, (int)n);
144
145         /* signature */
146         sig_status = ssl_certificate_check_signer(cert->x509_cert);
147
148         ret = g_strdup_printf(_("  Owner: %s (%s) in %s\n  Signed by: %s (%s) in %s\n  Fingerprint: %s\n  Signature status: %s"),
149                                 subject_commonname, subject_organization, subject_location, 
150                                 issuer_commonname, issuer_organization, issuer_location, 
151                                 fingerprint,
152                                 (sig_status==NULL ? "correct":sig_status));
153
154         if (issuer_commonname)
155                 g_free(issuer_commonname);
156         if (issuer_location)
157                 g_free(issuer_location);
158         if (issuer_organization)
159                 g_free(issuer_organization);
160         if (subject_commonname)
161                 g_free(subject_commonname);
162         if (subject_location)
163                 g_free(subject_location);
164         if (subject_organization)
165                 g_free(subject_organization);
166         if (fingerprint)
167                 g_free(fingerprint);
168         if (sig_status)
169                 g_free(sig_status);
170         return ret;
171 }
172         
173 void ssl_certificate_destroy(SSLCertificate *cert) 
174 {
175         g_return_if_fail(cert != NULL);
176         if (cert->x509_cert)
177                 X509_free(cert->x509_cert);
178         if (cert->host) 
179                 g_free(cert->host);
180         g_free(cert);
181         cert = NULL;
182 }
183
184 static SSLCertificate *ssl_certificate_find (gchar *host, gushort port)
185 {
186         gchar *file;
187         gchar *buf;
188         SSLCertificate *cert = NULL;
189         X509 *tmp_x509;
190         FILE *fp;
191         
192         buf = g_strdup_printf("%d", port);
193         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
194                           "certs", G_DIR_SEPARATOR_S,
195                           host, ".", buf, ".cert", NULL);
196
197         g_free(buf);
198         fp = fopen(file, "r");
199         if (fp == NULL) {
200                 g_free(file);
201                 return NULL;
202         }
203         
204         
205         if ((tmp_x509 = d2i_X509_fp(fp, 0)) != NULL) {
206                 cert = ssl_certificate_new(tmp_x509, host, port);
207                 X509_free(tmp_x509);
208         }
209         fclose(fp);
210         g_free(file);
211         
212         return cert;
213 }
214
215 static gboolean ssl_certificate_compare (SSLCertificate *cert_a, SSLCertificate *cert_b)
216 {
217         if (cert_a == NULL || cert_b == NULL)
218                 return FALSE;
219         else if (!X509_cmp(cert_a->x509_cert, cert_b->x509_cert))
220                 return TRUE;    
221         else
222                 return FALSE;
223 }
224
225 static char *ssl_certificate_check_signer (X509 *cert) 
226 {
227         X509_STORE_CTX store_ctx;
228         X509_STORE *store;
229         int ok = 0;
230         char *cert_file = NULL;
231         char *err_msg = NULL;
232
233         store = X509_STORE_new();
234         if (store == NULL) {
235                 printf("Can't create X509_STORE\n");
236                 return NULL;
237         }
238         if (X509_STORE_set_default_paths(store)) 
239                 ok++;
240         if (X509_STORE_load_locations(store, cert_file, NULL))
241                 ok++;
242
243         if (ok == 0) {
244                 X509_STORE_free (store);
245                 return g_strdup(_("Can't load X509 default paths"));
246         }
247         
248         X509_STORE_CTX_init (&store_ctx, store, cert, NULL);
249         ok = X509_verify_cert (&store_ctx);
250         
251         if (ok == 0) {
252                 err_msg = g_strdup(X509_verify_cert_error_string(
253                                         X509_STORE_CTX_get_error(&store_ctx)));
254                 debug_print("Can't check signer: %s\n", err_msg);
255                 X509_STORE_CTX_cleanup (&store_ctx);
256                 X509_STORE_free (store);
257                 return err_msg;
258                         
259         }
260         X509_STORE_CTX_cleanup (&store_ctx);
261         X509_STORE_free (store);
262         return NULL;
263 }
264
265 gboolean ssl_certificate_check (X509 *x509_cert, gchar *host, gushort port)
266 {
267         SSLCertificate *current_cert = ssl_certificate_new(x509_cert, host, port);
268         SSLCertificate *known_cert;
269
270         if (current_cert == NULL) {
271                 debug_print("Buggy certificate !\n");
272                 return FALSE;
273         }
274
275         known_cert = ssl_certificate_find (host, port);
276
277         if (known_cert == NULL) {
278                 gint val;
279                 gchar *err_msg, *cur_cert_str;
280                 
281                 cur_cert_str = ssl_certificate_to_string(current_cert);
282                 
283                 err_msg = g_strdup_printf(_("%s presented an unknown SSL certificate:\n%s"),
284                                           host,
285                                           cur_cert_str);
286                 g_free (cur_cert_str);
287
288                 if (prefs_common.no_recv_err_panel) {
289                         log_error(_("%s\n\nMail won't be retrieved on this account until you save the certificate.\n(Uncheck the \"%s\" preference).\n"),
290                                         err_msg,
291                                         _("Don't popup error dialog on receive error"));
292                         g_free(err_msg);
293                         return FALSE;
294                 }
295                  
296                 val = alertpanel(_("Warning"),
297                                err_msg,
298                                _("Accept and save"), _("Cancel connection"), NULL);
299                 g_free(err_msg);
300
301                 switch (val) {
302                         case G_ALERTALTERNATE:
303                                 ssl_certificate_destroy(current_cert);
304                                 return FALSE;
305                         default:
306                                 ssl_certificate_save(current_cert);
307                                 ssl_certificate_destroy(current_cert);
308                                 return TRUE;
309                 }
310         }
311         else if (!ssl_certificate_compare (current_cert, known_cert)) {
312                 gint val;
313                 gchar *err_msg, *known_cert_str, *cur_cert_str, *sig_status;
314                 
315                 sig_status = ssl_certificate_check_signer(x509_cert);
316
317                 known_cert_str = ssl_certificate_to_string(known_cert);
318                 cur_cert_str = ssl_certificate_to_string(current_cert);
319                 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."),
320                                           host,
321                                           known_cert_str,
322                                           cur_cert_str);
323                 g_free (cur_cert_str);
324                 g_free (known_cert_str);
325                 if (sig_status)
326                         g_free (sig_status);
327
328                 if (prefs_common.no_recv_err_panel) {
329                         log_error(_("%s\n\nMail won't be retrieved on this account until you save the certificate.\n(Uncheck the \"%s\" preference).\n"),
330                                         err_msg,
331                                         _("Don't popup error dialog on receive error"));
332                         g_free(err_msg);
333                         return FALSE;
334                 }
335
336                 val = alertpanel(_("Warning"),
337                                err_msg,
338                                _("Accept and save"), _("Cancel connection"), NULL);
339                 g_free(err_msg);
340
341                 switch (val) {
342                         case G_ALERTALTERNATE:
343                                 ssl_certificate_destroy(current_cert);
344                                 ssl_certificate_destroy(known_cert);
345                                 return FALSE;
346                         default:
347                                 ssl_certificate_save(current_cert);
348                                 ssl_certificate_destroy(current_cert);
349                                 ssl_certificate_destroy(known_cert);
350                                 return TRUE;
351                 }
352         }
353
354         ssl_certificate_destroy(current_cert);
355         ssl_certificate_destroy(known_cert);
356         return TRUE;
357 }
358
359 #endif /* USE_SSL */