Statistics
| Revision:

root / libsylph / ssl.c @ 2366

History | View | Annotate | Download (10 kB)

1
/*
2
 * LibSylph -- E-Mail client library
3
 * Copyright (C) 1999-2008 Hiroyuki Yamamoto
4
 *
5
 * This library is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU Lesser General Public
7
 * License as published by the Free Software Foundation; either
8
 * version 2.1 of the License, or (at your option) any later version.
9
 *
10
 * This library 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 GNU
13
 * Lesser General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Lesser General Public
16
 * License along with this library; if not, write to the Free Software
17
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
 */
19
20
#ifdef HAVE_CONFIG_H
21
#  include "config.h"
22
#endif
23
24
#if USE_SSL
25
26
#include "defs.h"
27
28
#include <glib.h>
29
#include <glib/gi18n.h>
30
31
#include "utils.h"
32
#include "ssl.h"
33
34
static SSL_CTX *ssl_ctx_SSLv23 = NULL;
35
static SSL_CTX *ssl_ctx_TLSv1 = NULL;
36
37
static GSList *trust_list = NULL;
38
static GSList *tmp_trust_list = NULL;
39
static GSList *reject_list = NULL;
40
41
static SSLVerifyFunc verify_ui_func = NULL;
42
43
static gchar *find_certs_file(const gchar *certs_dir)
44
{
45
        gchar *certs_file;
46
47
#define LOOK_FOR(crt)                                                           \
48
{                                                                           \
49
        certs_file = g_strconcat(certs_dir, G_DIR_SEPARATOR_S, crt, NULL); \
50
        debug_print("looking for %s\n", certs_file);                           \
51
        if (is_file_exist(certs_file))                                           \
52
                return certs_file;                                           \
53
        g_free(certs_file);                                                   \
54
}
55
56
        if (certs_dir) {
57
                LOOK_FOR("ca-certificates.crt");
58
                LOOK_FOR("ca-bundle.crt");
59
                LOOK_FOR("ca-root.crt");
60
                LOOK_FOR("certs.crt");
61
        }
62
63
#undef LOOK_FOR
64
65
        return NULL;
66
}
67
68
void ssl_init(void)
69
{
70
        gchar *certs_file, *certs_dir;
71
        FILE *fp;
72
73
        SSL_library_init();
74
        SSL_load_error_strings();
75
76
        certs_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "certs", NULL);
77
        if (!is_dir_exist(certs_dir)) {
78
                debug_print("ssl_init(): %s doesn't exist, or not a directory.\n",
79
                            certs_dir);
80
                g_free(certs_dir);
81
#ifdef G_OS_WIN32
82
                certs_dir = g_strconcat(get_startup_dir(), G_DIR_SEPARATOR_S
83
                                        "etc" G_DIR_SEPARATOR_S
84
                                        "ssl" G_DIR_SEPARATOR_S "certs", NULL);
85
#else
86
                certs_dir = g_strdup("/etc/ssl/certs");
87
#endif
88
                if (!is_dir_exist(certs_dir)) {
89
                        debug_print("ssl_init(): %s doesn't exist, or not a directory.\n",
90
                                    certs_dir);
91
                        g_free(certs_dir);
92
                        certs_dir = NULL;
93
                }
94
        }
95
        if (certs_dir)
96
                debug_print("ssl_init(): certs dir %s found.\n", certs_dir);
97
98
        certs_file = find_certs_file(get_rc_dir());
99
100
        if (certs_dir && !certs_file)
101
                certs_file = find_certs_file(certs_dir);
102
103
        if (!certs_file) {
104
#ifdef G_OS_WIN32
105
                certs_dir = g_strconcat(get_startup_dir(),
106
                                        G_DIR_SEPARATOR_S "etc"
107
                                        G_DIR_SEPARATOR_S "ssl", NULL);
108
                certs_file = find_certs_file(certs_dir);
109
                g_free(certs_dir);
110
                certs_dir = NULL;
111
                if (!certs_file) {
112
                        certs_dir = g_strconcat(get_startup_dir(),
113
                                                G_DIR_SEPARATOR_S "etc", NULL);
114
                        certs_file = find_certs_file(certs_dir);
115
                        g_free(certs_dir);
116
                        certs_dir = NULL;
117
                }
118
#else
119
                certs_file = find_certs_file("/etc/ssl");
120
                if (!certs_file)
121
                        certs_file = find_certs_file("/etc");
122
#endif
123
        }
124
125
        if (certs_file)
126
                debug_print("ssl_init(): certs file %s found.\n", certs_file);
127
128
        ssl_ctx_SSLv23 = SSL_CTX_new(SSLv23_client_method());
129
        if (ssl_ctx_SSLv23 == NULL) {
130
                debug_print(_("SSLv23 not available\n"));
131
        } else {
132
                debug_print(_("SSLv23 available\n"));
133
                if ((certs_file || certs_dir) &&
134
                    !SSL_CTX_load_verify_locations(ssl_ctx_SSLv23, certs_file,
135
                                                   certs_dir))
136
                        g_warning("SSLv23 SSL_CTX_load_verify_locations failed.\n");
137
        }
138
139
        ssl_ctx_TLSv1 = SSL_CTX_new(TLSv1_client_method());
140
        if (ssl_ctx_TLSv1 == NULL) {
141
                debug_print(_("TLSv1 not available\n"));
142
        } else {
143
                debug_print(_("TLSv1 available\n"));
144
                if ((certs_file || certs_dir) &&
145
                    !SSL_CTX_load_verify_locations(ssl_ctx_TLSv1, certs_file,
146
                                                   certs_dir))
147
                        g_warning("TLSv1 SSL_CTX_load_verify_locations failed.\n");
148
        }
149
150
        g_free(certs_dir);
151
        g_free(certs_file);
152
153
        certs_file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "trust.crt",
154
                                 NULL);
155
        if ((fp = g_fopen(certs_file, "rb")) != NULL) {
156
                X509 *cert;
157
158
                debug_print("ssl_init(): reading trust.crt\n");
159
160
                while ((cert = PEM_read_X509(fp, NULL, NULL, NULL)) != NULL)
161
                        trust_list = g_slist_append(trust_list, cert);
162
                fclose(fp);
163
        }
164
        g_free(certs_file);
165
}
166
167
void ssl_done(void)
168
{
169
        gchar *trust_file;
170
        GSList *cur;
171
        FILE *fp;
172
173
        if (trust_list) {
174
                trust_file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
175
                                         "trust.crt", NULL);
176
                if ((fp = g_fopen(trust_file, "wb")) == NULL) {
177
                        FILE_OP_ERROR(trust_file, "fopen");
178
                }
179
                for (cur = trust_list; cur != NULL; cur = cur->next) {
180
                        if (fp && !PEM_write_X509(fp, (X509 *)cur->data))
181
                                g_warning("can't write X509 to PEM file: %s",
182
                                          trust_file);
183
                        X509_free((X509 *)cur->data);
184
                }
185
                fclose(fp);
186
                g_free(trust_file);
187
                g_slist_free(trust_list);
188
                trust_list = NULL;
189
        }
190
        for (cur = tmp_trust_list; cur != NULL; cur = cur->next)
191
                X509_free((X509 *)cur->data);
192
        g_slist_free(tmp_trust_list);
193
        tmp_trust_list = NULL;
194
        for (cur = reject_list; cur != NULL; cur = cur->next)
195
                X509_free((X509 *)cur->data);
196
        g_slist_free(reject_list);
197
        reject_list = NULL;
198
199
        if (ssl_ctx_SSLv23) {
200
                SSL_CTX_free(ssl_ctx_SSLv23);
201
                ssl_ctx_SSLv23 = NULL;
202
        }
203
204
        if (ssl_ctx_TLSv1) {
205
                SSL_CTX_free(ssl_ctx_TLSv1);
206
                ssl_ctx_TLSv1 = NULL;
207
        }
208
}
209
210
gboolean ssl_init_socket(SockInfo *sockinfo)
211
{
212
        return ssl_init_socket_with_method(sockinfo, SSL_METHOD_SSLv23);
213
}
214
215
static gint x509_cmp_func(gconstpointer a, gconstpointer b)
216
{
217
        const X509 *xa = a;
218
        const X509 *xb = b;
219
220
        return X509_cmp(xa, xb);
221
}
222
223
gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
224
{
225
        X509 *server_cert;
226
        gint err, ret;
227
228
        switch (method) {
229
        case SSL_METHOD_SSLv23:
230
                if (!ssl_ctx_SSLv23) {
231
                        g_warning(_("SSL method not available\n"));
232
                        return FALSE;
233
                }
234
                sockinfo->ssl = SSL_new(ssl_ctx_SSLv23);
235
                break;
236
        case SSL_METHOD_TLSv1:
237
                if (!ssl_ctx_TLSv1) {
238
                        g_warning(_("SSL method not available\n"));
239
                        return FALSE;
240
                }
241
                sockinfo->ssl = SSL_new(ssl_ctx_TLSv1);
242
                break;
243
        default:
244
                g_warning(_("Unknown SSL method *PROGRAM BUG*\n"));
245
                return FALSE;
246
                break;
247
        }
248
249
        if (sockinfo->ssl == NULL) {
250
                g_warning(_("Error creating ssl context\n"));
251
                return FALSE;
252
        }
253
254
        SSL_set_fd(sockinfo->ssl, sockinfo->sock);
255
        while ((ret = SSL_connect(sockinfo->ssl)) != 1) {
256
                err = SSL_get_error(sockinfo->ssl, ret);
257
                if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
258
                        g_usleep(100000);
259
                        g_warning("SSL_connect(): try again\n");
260
                        continue;
261
                }
262
                g_warning("SSL_connect() failed with error %d, ret = %d (%s)\n",
263
                          err, ret, ERR_error_string(ERR_get_error(), NULL));
264
                return FALSE;
265
        }
266
267
        /* Get the cipher */
268
269
        debug_print(_("SSL connection using %s\n"),
270
                    SSL_get_cipher(sockinfo->ssl));
271
272
        /* Get server's certificate (note: beware of dynamic allocation) */
273
274
        if ((server_cert = SSL_get_peer_certificate(sockinfo->ssl)) != NULL) {
275
                glong verify_result;
276
                gboolean expired = FALSE;
277
278
                if (get_debug_mode()) {
279
                        gchar *str;
280
                        guchar keyid[EVP_MAX_MD_SIZE];
281
                        gchar keyidstr[EVP_MAX_MD_SIZE * 3 + 1] = "";
282
                        guint keyidlen = 0;
283
                        gint i;
284
        
285
                        debug_print(_("Server certificate:\n"));
286
287
                        if ((str = X509_NAME_oneline(X509_get_subject_name(server_cert), 0, 0)) != NULL) {
288
                                debug_print(_("  Subject: %s\n"), str);
289
                                OPENSSL_free(str);
290
                        }
291
292
                        if ((str = X509_NAME_oneline(X509_get_issuer_name(server_cert), 0, 0)) != NULL) {
293
                                debug_print(_("  Issuer: %s\n"), str);
294
                                OPENSSL_free(str);
295
                        }
296
                        if (X509_digest(server_cert, EVP_sha1(), keyid, &keyidlen)) {
297
                                for (i = 0; i < keyidlen; i++)
298
                                g_snprintf(keyidstr + i * 3, 4, "%02x:", keyid[i]);
299
                                keyidstr[keyidlen * 3 - 1] = '\0';
300
                                debug_print("  SHA1 fingerprint: %s\n", keyidstr);
301
                        }
302
                        if (X509_digest(server_cert, EVP_md5(), keyid, &keyidlen)) {
303
                                for (i = 0; i < keyidlen; i++)
304
                                g_snprintf(keyidstr + i * 3, 4, "%02x:", keyid[i]);
305
                                keyidstr[keyidlen * 3 - 1] = '\0';
306
                                debug_print("  MD5 fingerprint: %s\n", keyidstr);
307
                        }
308
                }
309
310
                verify_result = SSL_get_verify_result(sockinfo->ssl);
311
                if (verify_result == X509_V_OK) {
312
                        debug_print("SSL verify OK\n");
313
                        X509_free(server_cert);
314
                        return TRUE;
315
                } else if (verify_result == X509_V_ERR_CERT_HAS_EXPIRED) {
316
                        log_message("SSL certificate of %s has expired\n", sockinfo->hostname);
317
                        expired = TRUE;
318
                } else if (g_slist_find_custom(trust_list, server_cert,
319
                                               x509_cmp_func) ||
320
                           g_slist_find_custom(tmp_trust_list, server_cert,
321
                                               x509_cmp_func)) {
322
                        log_message("SSL certificate of %s previously accepted\n", sockinfo->hostname);
323
                        X509_free(server_cert);
324
                        return TRUE;
325
                } else if (g_slist_find_custom(reject_list, server_cert,
326
                                               x509_cmp_func)) {
327
                        log_message("SSL certificate of %s previously rejected\n", sockinfo->hostname);
328
                        X509_free(server_cert);
329
                        return FALSE;
330
                }
331
332
                g_warning("%s: SSL certificate verify failed (%ld: %s)\n",
333
                          sockinfo->hostname, verify_result,
334
                          X509_verify_cert_error_string(verify_result));
335
336
                if (verify_ui_func) {
337
                        gint res;
338
339
                        res = verify_ui_func(sockinfo, sockinfo->hostname,
340
                                             server_cert, verify_result);
341
                        /* 0: accept 1: temporarily accept -1: reject */
342
                        if (res < 0) {
343
                                debug_print("SSL certificate of %s rejected\n",
344
                                            sockinfo->hostname);
345
#if 0
346
                                reject_list = g_slist_prepend
347
                                        (reject_list, X509_dup(server_cert));
348
#endif
349
                                X509_free(server_cert);
350
                                return FALSE;
351
                        } else if (res > 0) {
352
                                debug_print("Temporarily accept SSL certificate of %s\n", sockinfo->hostname);
353
                                if (!expired)
354
                                        tmp_trust_list = g_slist_prepend(tmp_trust_list, X509_dup(server_cert));
355
                        } else {
356
                                debug_print("Permanently accept SSL certificate of %s\n", sockinfo->hostname);
357
                                if (!expired)
358
                                        trust_list = g_slist_prepend(trust_list, X509_dup(server_cert));
359
                        }
360
                }
361
362
                X509_free(server_cert);
363
        } else {
364
                g_warning("%s: couldn't get SSL certificate\n",
365
                          sockinfo->hostname);
366
                return FALSE;
367
        }
368
369
        return TRUE;
370
}
371
372
void ssl_done_socket(SockInfo *sockinfo)
373
{
374
        if (sockinfo->ssl) {
375
                SSL_free(sockinfo->ssl);
376
        }
377
}
378
379
void ssl_set_verify_func(SSLVerifyFunc func)
380
{
381
        verify_ui_func = func;
382
}
383
384
#endif /* USE_SSL */