Check SSL certificates
[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
33 static void ssl_certificate_destroy(SSLCertificate *cert);
34
35 static char * convert_fingerprint(char *src) 
36 {
37         int i=0;
38         char * ret;
39         
40         if (src == NULL)
41                 return NULL;
42         ret = g_strdup("");
43         while (src[i] != '\0') {
44                 char *tmp2;
45                 if(i>0)
46                         tmp2 = g_strdup_printf("%s:%02X", ret, src[i]);
47                 else
48                         tmp2 = g_strdup_printf("%02X", src[i]);
49                 ret = g_strdup(tmp2);
50                 g_free(tmp2);
51                 i++;
52         }
53         return ret;
54 }
55
56 SSLCertificate *ssl_certificate_new(gchar *host, gchar *issuer, gchar *subject, gchar *md)
57 {
58         SSLCertificate *cert = g_new0(SSLCertificate, 1);
59         
60         if (host == NULL || issuer == NULL || subject == NULL || md == NULL) {
61                 ssl_certificate_destroy(cert);
62                 return NULL;
63         }
64
65         cert->host = g_strdup(host);
66         cert->issuer = g_strdup(issuer);
67         cert->subject = g_strdup(subject);
68         cert->fingerprint = g_strdup(md);
69         return cert;
70 }
71
72 static void ssl_certificate_save (SSLCertificate *cert)
73 {
74         gchar *file;
75         FILE *fp;
76         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
77                           "certs", G_DIR_SEPARATOR_S, NULL);
78         
79         if (!is_dir_exist(file))
80                 make_dir_hier(file);
81         g_free(file);
82
83         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
84                           "certs", G_DIR_SEPARATOR_S,
85                           cert->host, ".cert", NULL);
86
87         fp = fopen(file, "w");
88         if (fp == NULL) {
89                 g_free(file);
90                 alertpanel_error(_("Can't save certificate !"));
91                 return;
92         }
93         fputs("issuer=", fp);
94         fputs(cert->issuer, fp);
95         fputs("\nsubject=", fp);
96         fputs(cert->subject, fp);
97         fputs("\nfingerprint=", fp);
98         fputs(cert->fingerprint, fp);
99         fputs("\n", fp);
100         fclose(fp);
101 }
102
103 static char* ssl_certificate_to_string(SSLCertificate *cert)
104 {
105         char *ret;
106         ret = g_strdup_printf("  Issuer: %s\n  Subject: %s\n  Fingerprint: %s",
107                                 cert->issuer,
108                                 cert->subject,
109                                 cert->fingerprint);
110         return ret;
111 }
112         
113 void ssl_certificate_destroy(SSLCertificate *cert) 
114 {
115         g_return_if_fail(cert != NULL);
116         if (cert->host) 
117                 g_free(cert->host);
118         if (cert->issuer)
119                 g_free(cert->issuer);
120         if (cert->subject)
121                 g_free(cert->subject);
122         if (cert->fingerprint)
123                 g_free(cert->fingerprint);
124         g_free(cert);
125         cert = NULL;
126 }
127
128 static SSLCertificate *ssl_certificate_find (gchar *host)
129 {
130         gchar *file;
131         gchar buf[1024], *subject, *issuer, *fingerprint;
132         SSLCertificate *cert;
133         FILE *fp;
134         
135         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
136                           "certs", G_DIR_SEPARATOR_S,
137                           host, ".cert", NULL);
138
139         fp = fopen(file, "r");
140         if (fp == NULL) {
141                 g_free(file);
142                 return NULL;
143         }
144         
145         while( fgets( buf, sizeof( buf ), fp ) != NULL ) {
146                 if (!strncmp(buf, "subject=", 8)) {
147                         subject = g_strdup((char *)buf +8);
148                         g_strdelimit(subject, "\r\n", '\0');
149                 }
150                 else if (!strncmp(buf, "issuer=", 7)) {
151                         issuer = g_strdup((char *)buf +7);
152                         g_strdelimit(issuer, "\r\n", '\0');
153                 }
154                 else if (!strncmp(buf, "fingerprint=", 12)) {
155                         fingerprint = g_strdup((char *)buf +12);
156                         g_strdelimit(fingerprint, "\r\n", '\0');
157                 }
158         }
159         fclose (fp);
160         if (subject && issuer && fingerprint) {
161                 cert = ssl_certificate_new(host, issuer, subject, fingerprint);
162         }
163         
164         g_free(file);
165         
166         if (subject)
167                 g_free(subject);
168         if (issuer)
169                 g_free(issuer);
170         if (fingerprint)
171                 g_free(fingerprint);
172
173         return cert;
174 }
175
176 static gboolean ssl_certificate_compare (SSLCertificate *cert_a, SSLCertificate *cert_b)
177 {
178         if (!strcmp(cert_a->issuer, cert_b->issuer)
179         &&  !strcmp(cert_a->subject, cert_b->subject)
180         &&  !strcmp(cert_a->fingerprint, cert_b->fingerprint))
181                 return TRUE;    
182         else
183                 return FALSE;
184 }
185
186 static char *ssl_certificate_check_signer (X509 *cert) 
187 {
188         X509_STORE_CTX store_ctx;
189         X509_STORE *store;
190         int ok = 0;
191         char *cert_file = NULL;
192         char *err_msg = NULL;
193
194         store = X509_STORE_new();
195         if (store == NULL) {
196                 printf("Can't create X509_STORE\n");
197                 return FALSE;
198         }
199         if (X509_STORE_set_default_paths(store)) 
200                 ok++;
201         if (X509_STORE_load_locations(store, cert_file, NULL))
202                 ok++;
203
204         if (ok == 0) {
205                 X509_STORE_free (store);
206                 return g_strdup(_("Can't load X509 default paths"));
207         }
208         
209         X509_STORE_CTX_init (&store_ctx, store, cert, NULL);
210         ok = X509_verify_cert (&store_ctx);
211         
212         if (ok == 0) {
213                 err_msg = g_strdup(X509_verify_cert_error_string(
214                                         X509_STORE_CTX_get_error(&store_ctx)));
215                 debug_print("Can't check signer: %s\n", err_msg);
216                 X509_STORE_CTX_cleanup (&store_ctx);
217                 X509_STORE_free (store);
218                 return err_msg;
219                         
220         }
221         X509_STORE_CTX_cleanup (&store_ctx);
222         X509_STORE_free (store);
223         return NULL;
224 }
225
226 gboolean ssl_certificate_check (X509 *x509_cert, gchar *host, gchar *issuer, 
227                                 gchar *subject, gchar *md) 
228 {
229         char *readable_md = convert_fingerprint(md);
230         SSLCertificate *current_cert = ssl_certificate_new(host, issuer, subject, readable_md);
231         SSLCertificate *known_cert;
232
233         if (current_cert == NULL) {
234                 debug_print("Buggy certificate !\n");
235                 debug_print("host: %s\n", host);
236                 debug_print("issuer: %s\n", issuer);
237                 debug_print("subject: %s\n", subject);
238                 debug_print("md: %s\n", readable_md);
239                 if (readable_md)
240                         g_free(readable_md);
241                 return FALSE;
242         }
243
244         if (readable_md)
245                 g_free(readable_md);
246
247         known_cert = ssl_certificate_find (host);
248
249         if (known_cert == NULL) {
250                 gint val;
251                 gchar *err_msg, *cur_cert_str;
252                 gchar *sig_status = NULL;
253                 
254                 cur_cert_str = ssl_certificate_to_string(current_cert);
255                 
256                 sig_status = ssl_certificate_check_signer(x509_cert);
257
258                 err_msg = g_strdup_printf(_("The SSL certificate presented by %s is unknown.\nPresented certificate is:\n%s\n\n%s%s"),
259                                           host,
260                                           cur_cert_str,
261                                           (sig_status == NULL)?"The presented certificate signature is correct.":"The presented certificate signature is not correct: ",
262                                           (sig_status == NULL)?"":sig_status);
263                 g_free (cur_cert_str);
264                 g_free (sig_status);
265  
266                 val = alertpanel(_("Warning"),
267                                err_msg,
268                                _("Accept and save"), _("Cancel connection"), NULL);
269                 g_free(err_msg);
270
271                 switch (val) {
272                         case G_ALERTALTERNATE:
273                                 ssl_certificate_destroy(current_cert);
274                                 return FALSE;
275                         default:
276                                 ssl_certificate_save(current_cert);
277                                 ssl_certificate_destroy(current_cert);
278                                 return TRUE;
279                 }
280         }
281         else if (!ssl_certificate_compare (current_cert, known_cert)) {
282                 gint val;
283                 gchar *err_msg, *known_cert_str, *cur_cert_str, *sig_status;
284                 
285                 sig_status = ssl_certificate_check_signer(x509_cert);
286
287                 known_cert_str = ssl_certificate_to_string(known_cert);
288                 cur_cert_str = ssl_certificate_to_string(current_cert);
289                 err_msg = g_strdup_printf(_("The SSL certificate presented by %s differs from the known one.\nKnown certificate is:\n%s\nPresented certificate is:\n%s\n\n%s%s"),
290                                           host,
291                                           known_cert_str,
292                                           cur_cert_str,
293                                           (sig_status == NULL)?"The presented certificate signature is correct.":"The presented certificate signature is not correct: ",
294                                           (sig_status == NULL)?"":sig_status);
295                 g_free (cur_cert_str);
296                 g_free (known_cert_str);
297                 g_free (sig_status);
298
299                 val = alertpanel(_("Warning"),
300                                err_msg,
301                                _("Accept and save"), _("Cancel connection"), NULL);
302                 g_free(err_msg);
303
304                 switch (val) {
305                         case G_ALERTALTERNATE:
306                                 ssl_certificate_destroy(current_cert);
307                                 ssl_certificate_destroy(known_cert);
308                                 return FALSE;
309                         default:
310                                 ssl_certificate_save(current_cert);
311                                 ssl_certificate_destroy(current_cert);
312                                 ssl_certificate_destroy(known_cert);
313                                 return TRUE;
314                 }
315         }
316
317         ssl_certificate_destroy(current_cert);
318         ssl_certificate_destroy(known_cert);
319         return TRUE;
320 }
321
322 #endif /* USE_SSL */