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