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