08ad0ef2a2fb9a0e27dcf5a1df7d898f24b3b1a3
[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 SSLCertificate *ssl_certificate_new_lookup(X509 *x509_cert, gchar *host, gushort port, gboolean lookup);
38
39 /* from Courier */
40 time_t asn1toTime(ASN1_TIME *asn1Time)
41 {
42         struct tm tm;
43         int offset;
44
45         if (asn1Time == NULL || asn1Time->length < 13)
46                 return 0;
47
48         memset(&tm, 0, sizeof(tm));
49
50 #define N2(n)   ((asn1Time->data[n]-'0')*10 + asn1Time->data[(n)+1]-'0')
51
52 #define CPY(f,n) (tm.f=N2(n))
53
54         CPY(tm_year,0);
55
56         if(tm.tm_year < 50)
57                 tm.tm_year += 100; /* Sux */
58
59         CPY(tm_mon, 2);
60         --tm.tm_mon;
61         CPY(tm_mday, 4);
62         CPY(tm_hour, 6);
63         CPY(tm_min, 8);
64         CPY(tm_sec, 10);
65
66         offset=0;
67
68         if (asn1Time->data[12] != 'Z')
69         {
70                 if (asn1Time->length < 17)
71                         return 0;
72
73                 offset=N2(13)*3600+N2(15)*60;
74
75                 if (asn1Time->data[12] == '-')
76                         offset= -offset;
77         }
78
79 #undef N2
80 #undef CPY
81
82         return mktime(&tm)-offset;
83 }
84
85 static char * get_fqdn(char *host)
86 {
87         struct hostent *hp;
88
89         if (host == NULL || strlen(host) == 0)
90                 return g_strdup("");
91
92         hp = my_gethostbyname(host);
93         if (hp == NULL)
94                 return g_strdup(host); /*caller should free*/
95         else 
96                 return g_strdup(hp->h_name);
97 }
98
99 char * readable_fingerprint(unsigned char *src, int len) 
100 {
101         int i=0;
102         char * ret;
103         
104         if (src == NULL)
105                 return NULL;
106         ret = g_strdup("");
107         while (i < len) {
108                 char *tmp2;
109                 if(i>0)
110                         tmp2 = g_strdup_printf("%s:%02X", ret, src[i]);
111                 else
112                         tmp2 = g_strdup_printf("%02X", src[i]);
113                 g_free(ret);
114                 ret = g_strdup(tmp2);
115                 g_free(tmp2);
116                 i++;
117         }
118         return ret;
119 }
120
121 SSLCertificate *ssl_certificate_new(X509 *x509_cert, gchar *host, gushort port)
122 {
123         return ssl_certificate_new_lookup(x509_cert, host, port, TRUE);
124 }
125
126 static SSLCertificate *ssl_certificate_new_lookup(X509 *x509_cert, gchar *host, gushort port, gboolean lookup)
127 {
128         SSLCertificate *cert = g_new0(SSLCertificate, 1);
129         
130         if (host == NULL || x509_cert == NULL) {
131                 ssl_certificate_destroy(cert);
132                 return NULL;
133         }
134         cert->x509_cert = X509_dup(x509_cert);
135         if (lookup)
136                 cert->host = get_fqdn(host);
137         else
138                 cert->host = g_strdup(host);
139         cert->port = port;
140         return cert;
141 }
142
143 static void ssl_certificate_save (SSLCertificate *cert)
144 {
145         gchar *file, *port;
146         FILE *fp;
147
148         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
149                           "certs", G_DIR_SEPARATOR_S, NULL);
150         
151         if (!is_dir_exist(file))
152                 make_dir_hier(file);
153         g_free(file);
154
155         port = g_strdup_printf("%d", cert->port);
156         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
157                           "certs", G_DIR_SEPARATOR_S,
158                           cert->host, ".", port, ".cert", NULL);
159
160         g_free(port);
161         fp = g_fopen(file, "wb");
162         if (fp == NULL) {
163                 g_free(file);
164                 debug_print("Can't save certificate !\n");
165                 return;
166         }
167         i2d_X509_fp(fp, cert->x509_cert);
168         g_free(file);
169         fclose(fp);
170
171 }
172
173 char* ssl_certificate_to_string(SSLCertificate *cert)
174 {
175         char *ret, buf[100];
176         char *issuer_commonname, *issuer_location, *issuer_organization;
177         char *subject_commonname, *subject_location, *subject_organization;
178         char *fingerprint, *sig_status;
179         unsigned int n;
180         unsigned char md[EVP_MAX_MD_SIZE];      
181         
182         /* issuer */    
183         if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
184                                        NID_commonName, buf, 100) >= 0)
185                 issuer_commonname = g_strdup(buf);
186         else
187                 issuer_commonname = g_strdup(_("<not in certificate>"));
188         if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
189                                        NID_localityName, buf, 100) >= 0) {
190                 issuer_location = g_strdup(buf);
191                 if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
192                                        NID_countryName, buf, 100) >= 0)
193                         issuer_location = g_strconcat(issuer_location,", ",buf, NULL);
194         } else if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
195                                        NID_countryName, buf, 100) >= 0)
196                 issuer_location = g_strdup(buf);
197         else
198                 issuer_location = g_strdup(_("<not in certificate>"));
199
200         if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
201                                        NID_organizationName, buf, 100) >= 0)
202                 issuer_organization = g_strdup(buf);
203         else 
204                 issuer_organization = g_strdup(_("<not in certificate>"));
205          
206         /* subject */   
207         if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
208                                        NID_commonName, buf, 100) >= 0)
209                 subject_commonname = g_strdup(buf);
210         else
211                 subject_commonname = g_strdup(_("<not in certificate>"));
212         if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
213                                        NID_localityName, buf, 100) >= 0) {
214                 subject_location = g_strdup(buf);
215                 if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
216                                        NID_countryName, buf, 100) >= 0)
217                         subject_location = g_strconcat(subject_location,", ",buf, NULL);
218         } else if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
219                                        NID_countryName, buf, 100) >= 0)
220                 subject_location = g_strdup(buf);
221         else
222                 subject_location = g_strdup(_("<not in certificate>"));
223
224         if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
225                                        NID_organizationName, buf, 100) >= 0)
226                 subject_organization = g_strdup(buf);
227         else 
228                 subject_organization = g_strdup(_("<not in certificate>"));
229          
230         /* fingerprint */
231         X509_digest(cert->x509_cert, EVP_md5(), md, &n);
232         fingerprint = readable_fingerprint(md, (int)n);
233
234         /* signature */
235         sig_status = ssl_certificate_check_signer(cert->x509_cert);
236
237         ret = g_strdup_printf(_("  Owner: %s (%s) in %s\n  Signed by: %s (%s) in %s\n  Fingerprint: %s\n  Signature status: %s"),
238                                 subject_commonname, subject_organization, subject_location, 
239                                 issuer_commonname, issuer_organization, issuer_location, 
240                                 fingerprint,
241                                 (sig_status==NULL ? "correct":sig_status));
242
243         g_free(issuer_commonname);
244         g_free(issuer_location);
245         g_free(issuer_organization);
246         g_free(subject_commonname);
247         g_free(subject_location);
248         g_free(subject_organization);
249         g_free(fingerprint);
250         g_free(sig_status);
251         return ret;
252 }
253         
254 void ssl_certificate_destroy(SSLCertificate *cert) 
255 {
256         if (cert == NULL)
257                 return;
258
259         if (cert->x509_cert)
260                 X509_free(cert->x509_cert);
261         g_free(cert->host);
262         g_free(cert);
263         cert = NULL;
264 }
265
266 void ssl_certificate_delete_from_disk(SSLCertificate *cert)
267 {
268         gchar *buf;
269         gchar *file;
270         buf = g_strdup_printf("%d", cert->port);
271         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
272                           "certs", G_DIR_SEPARATOR_S,
273                           cert->host, ".", buf, ".cert", NULL);
274         g_unlink (file);
275         g_free(buf);
276         g_free(file);
277 }
278
279 SSLCertificate *ssl_certificate_find (gchar *host, gushort port)
280 {
281         return ssl_certificate_find_lookup (host, port, TRUE);
282 }
283
284 SSLCertificate *ssl_certificate_find_lookup (gchar *host, gushort port, gboolean lookup)
285 {
286         gchar *file;
287         gchar *buf;
288         gchar *fqdn_host;
289         SSLCertificate *cert = NULL;
290         X509 *tmp_x509;
291         FILE *fp;
292
293         if (lookup)
294                 fqdn_host = get_fqdn(host);
295         else
296                 fqdn_host = g_strdup(host);
297
298         buf = g_strdup_printf("%d", port);
299         file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
300                           "certs", G_DIR_SEPARATOR_S,
301                           fqdn_host, ".", buf, ".cert", NULL);
302
303         g_free(buf);
304         fp = g_fopen(file, "rb");
305         if (fp == NULL) {
306                 g_free(file);
307                 g_free(fqdn_host);
308                 return NULL;
309         }
310         
311         
312         if ((tmp_x509 = d2i_X509_fp(fp, 0)) != NULL) {
313                 cert = ssl_certificate_new_lookup(tmp_x509, fqdn_host, port, lookup);
314                 X509_free(tmp_x509);
315         }
316         fclose(fp);
317         g_free(file);
318         g_free(fqdn_host);
319         
320         return cert;
321 }
322
323 static gboolean ssl_certificate_compare (SSLCertificate *cert_a, SSLCertificate *cert_b)
324 {
325         if (cert_a == NULL || cert_b == NULL)
326                 return FALSE;
327         else if (!X509_cmp(cert_a->x509_cert, cert_b->x509_cert))
328                 return TRUE;
329         else
330                 return FALSE;
331 }
332
333 char *ssl_certificate_check_signer (X509 *cert) 
334 {
335         X509_STORE_CTX store_ctx;
336         X509_STORE *store;
337         char *err_msg = NULL;
338
339         store = X509_STORE_new();
340         if (store == NULL) {
341                 printf("Can't create X509_STORE\n");
342                 return NULL;
343         }
344         if (!X509_STORE_set_default_paths(store)) {
345                 X509_STORE_free (store);
346                 return g_strdup(_("Can't load X509 default paths"));
347         }
348         
349         X509_STORE_CTX_init (&store_ctx, store, cert, NULL);
350
351         if(!X509_verify_cert (&store_ctx)) {
352                 err_msg = g_strdup(X509_verify_cert_error_string(
353                                         X509_STORE_CTX_get_error(&store_ctx)));
354                 debug_print("Can't check signer: %s\n", err_msg);
355                 X509_STORE_CTX_cleanup (&store_ctx);
356                 X509_STORE_free (store);
357                 return err_msg;
358                         
359         }
360         X509_STORE_CTX_cleanup (&store_ctx);
361         X509_STORE_free (store);
362         return NULL;
363 }
364
365 gboolean ssl_certificate_check (X509 *x509_cert, gchar *host, gushort port)
366 {
367         SSLCertificate *current_cert = ssl_certificate_new(x509_cert, host, port);
368         SSLCertificate *known_cert;
369         SSLCertHookData cert_hook_data;
370         
371         if (current_cert == NULL) {
372                 debug_print("Buggy certificate !\n");
373                 return FALSE;
374         }
375
376         known_cert = ssl_certificate_find (host, port);
377
378         if (known_cert == NULL) {
379                 cert_hook_data.cert = current_cert;
380                 cert_hook_data.old_cert = NULL;
381                 cert_hook_data.expired = FALSE;
382                 cert_hook_data.accept = FALSE;
383                 
384                 hooks_invoke(SSLCERT_ASK_HOOKLIST, &cert_hook_data);
385                 
386                 if (!cert_hook_data.accept) {
387                         ssl_certificate_destroy(current_cert);
388                         return FALSE;
389                 } else {
390                         ssl_certificate_save(current_cert);
391                         ssl_certificate_destroy(current_cert);
392                         return TRUE;
393                 }
394         } else if (!ssl_certificate_compare (current_cert, known_cert)) {
395                 cert_hook_data.cert = current_cert;
396                 cert_hook_data.old_cert = known_cert;
397                 cert_hook_data.expired = FALSE;
398                 cert_hook_data.accept = FALSE;
399                 
400                 hooks_invoke(SSLCERT_ASK_HOOKLIST, &cert_hook_data);
401
402                 if (!cert_hook_data.accept) {
403                         ssl_certificate_destroy(current_cert);
404                         ssl_certificate_destroy(known_cert);
405                         return FALSE;
406                 } else {
407                         ssl_certificate_save(current_cert);
408                         ssl_certificate_destroy(current_cert);
409                         ssl_certificate_destroy(known_cert);
410                         return TRUE;
411                 }
412         } else if (asn1toTime(X509_get_notAfter(current_cert->x509_cert)) < time(NULL)) {
413                 cert_hook_data.cert = current_cert;
414                 cert_hook_data.old_cert = NULL;
415                 cert_hook_data.expired = TRUE;
416                 cert_hook_data.accept = FALSE;
417                 
418                 hooks_invoke(SSLCERT_ASK_HOOKLIST, &cert_hook_data);
419
420                 if (!cert_hook_data.accept) {
421                         ssl_certificate_destroy(current_cert);
422                         ssl_certificate_destroy(known_cert);
423                         return FALSE;
424                 } else {
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_OPENSSL */