source: lib/ssl_gnutls.c @ b86f37d

Last change on this file since b86f37d was 5eab9260, checked in by Christopher Brannon <chris@…>, at 2018-09-09T22:04:41Z

SSL: correctly handle hostnames starting with a digit when SNI is needed.

I noticed that I was unable to connect to a host with bitlbee.
Tracking it down, I found that the problem was that the host needed SNI
but bitlbee wasn't trying to use it. The hostname started with a digit,
so bitlbee was assuming that it was an IP address.

  • Property mode set to 100644
File size: 12.1 KB
Line 
1/********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2012 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* SSL module - GnuTLS version                                          */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
23  Fifth Floor, Boston, MA  02110-1301  USA
24*/
25
26#include <gnutls/gnutls.h>
27#include <gnutls/x509.h>
28#include <gcrypt.h>
29#include <fcntl.h>
30#include <unistd.h>
31#include "proxy.h"
32#include "ssl_client.h"
33#include "sock.h"
34#include "stdlib.h"
35#include "bitlbee.h"
36
37int ssl_errno = 0;
38
39static gboolean initialized = FALSE;
40gnutls_certificate_credentials_t xcred;
41
42#include <limits.h>
43
44#define SSLDEBUG 0
45
46struct scd {
47        ssl_input_function func;
48        gpointer data;
49        int fd;
50        gboolean established;
51        int inpa;
52        char *hostname;
53        gboolean verify;
54
55        gnutls_session_t session;
56};
57
58static GHashTable *session_cache;
59
60static gboolean ssl_connected(gpointer data, gint source, b_input_condition cond);
61static gboolean ssl_starttls_real(gpointer data, gint source, b_input_condition cond);
62static gboolean ssl_handshake(gpointer data, gint source, b_input_condition cond);
63
64static void ssl_deinit(void);
65
66static void ssl_log(int level, const char *line)
67{
68        printf("%d %s", level, line);
69}
70
71void ssl_init(void)
72{
73        if (initialized) {
74                return;
75        }
76
77        gnutls_global_init();
78        gnutls_certificate_allocate_credentials(&xcred);
79        if (global.conf->cafile) {
80                gnutls_certificate_set_x509_trust_file(xcred, global.conf->cafile, GNUTLS_X509_FMT_PEM);
81
82                /* Not needed in GnuTLS 2.11+ (enabled by default there) so
83                   don't do it (resets possible other defaults). */
84                if (!gnutls_check_version("2.11")) {
85                        gnutls_certificate_set_verify_flags(xcred, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
86                }
87        }
88        initialized = TRUE;
89
90        gnutls_global_set_log_function(ssl_log);
91        /*
92        gnutls_global_set_log_level( 3 );
93        */
94
95        session_cache = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
96
97        atexit(ssl_deinit);
98}
99
100static void ssl_deinit(void)
101{
102        gnutls_global_deinit();
103        gnutls_certificate_free_credentials(xcred);
104        g_hash_table_destroy(session_cache);
105        session_cache = NULL;
106}
107
108void *ssl_connect(char *host, int port, gboolean verify, ssl_input_function func, gpointer data)
109{
110        struct scd *conn = g_new0(struct scd, 1);
111
112        conn->func = func;
113        conn->data = data;
114        conn->inpa = -1;
115        conn->hostname = g_strdup(host);
116        conn->verify = verify && global.conf->cafile;
117        conn->fd = proxy_connect(host, port, ssl_connected, conn);
118
119        if (conn->fd < 0) {
120                g_free(conn->hostname);
121                g_free(conn);
122                return NULL;
123        }
124
125        return conn;
126}
127
128void *ssl_starttls(int fd, char *hostname, gboolean verify, ssl_input_function func, gpointer data)
129{
130        struct scd *conn = g_new0(struct scd, 1);
131
132        conn->fd = fd;
133        conn->func = func;
134        conn->data = data;
135        conn->inpa = -1;
136        conn->hostname = g_strdup(hostname);
137
138        /* For now, SSL verification is globally enabled by setting the cafile
139           setting in bitlbee.conf. Commented out by default because probably
140           not everyone has this file in the same place and plenty of folks
141           may not have the cert of their private Jabber server in it. */
142        conn->verify = verify && global.conf->cafile;
143
144        /* This function should be called via a (short) timeout instead of
145           directly from here, because these SSL calls are *supposed* to be
146           *completely* asynchronous and not ready yet when this function
147           (or *_connect, for examle) returns. Also, errors are reported via
148           the callback function, not via this function's return value.
149
150           In short, doing things like this makes the rest of the code a lot
151           simpler. */
152
153        b_timeout_add(1, ssl_starttls_real, conn);
154
155        return conn;
156}
157
158static gboolean ssl_starttls_real(gpointer data, gint source, b_input_condition cond)
159{
160        struct scd *conn = data;
161
162        return ssl_connected(conn, conn->fd, B_EV_IO_WRITE);
163}
164
165static int verify_certificate_callback(gnutls_session_t session)
166{
167        unsigned int status;
168        const gnutls_datum_t *cert_list;
169        unsigned int cert_list_size;
170        int gnutlsret;
171        int verifyret = 0;
172        gnutls_x509_crt_t cert;
173        struct scd *conn;
174
175        conn = gnutls_session_get_ptr(session);
176
177        gnutlsret = gnutls_certificate_verify_peers2(session, &status);
178        if (gnutlsret < 0) {
179                return VERIFY_CERT_ERROR;
180        }
181
182        if (status & GNUTLS_CERT_INVALID) {
183                verifyret |= VERIFY_CERT_INVALID;
184        }
185
186        if (status & GNUTLS_CERT_REVOKED) {
187                verifyret |= VERIFY_CERT_REVOKED;
188        }
189
190        if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
191                verifyret |= VERIFY_CERT_SIGNER_NOT_FOUND;
192        }
193
194        if (status & GNUTLS_CERT_SIGNER_NOT_CA) {
195                verifyret |= VERIFY_CERT_SIGNER_NOT_CA;
196        }
197
198        if (status & GNUTLS_CERT_INSECURE_ALGORITHM) {
199                verifyret |= VERIFY_CERT_INSECURE_ALGORITHM;
200        }
201
202#ifdef GNUTLS_CERT_NOT_ACTIVATED
203        /* Amusingly, the GnuTLS function used above didn't check for expiry
204           until GnuTLS 2.8 or so. (See CVE-2009-1417) */
205        if (status & GNUTLS_CERT_NOT_ACTIVATED) {
206                verifyret |= VERIFY_CERT_NOT_ACTIVATED;
207        }
208
209        if (status & GNUTLS_CERT_EXPIRED) {
210                verifyret |= VERIFY_CERT_EXPIRED;
211        }
212#endif
213
214        if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509 || gnutls_x509_crt_init(&cert) < 0) {
215                return VERIFY_CERT_ERROR;
216        }
217
218        cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
219        if (cert_list == NULL || gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) {
220                return VERIFY_CERT_ERROR;
221        }
222
223        if (!gnutls_x509_crt_check_hostname(cert, conn->hostname)) {
224                verifyret |= VERIFY_CERT_INVALID;
225                verifyret |= VERIFY_CERT_WRONG_HOSTNAME;
226        }
227
228        gnutls_x509_crt_deinit(cert);
229
230        return verifyret;
231}
232
233struct ssl_session {
234        size_t size;
235        char data[];
236};
237
238static void ssl_cache_add(struct scd *conn)
239{
240        size_t data_size = 0;
241        struct ssl_session *data;
242        char *hostname;
243
244        if (!conn->hostname ||
245            gnutls_session_get_data(conn->session, NULL, &data_size) != 0) {
246                return;
247        }
248
249        data = g_malloc(sizeof(struct ssl_session) + data_size);
250        if (gnutls_session_get_data(conn->session, data->data, &data_size) != 0) {
251                g_free(data);
252                return;
253        }
254
255        hostname = g_strdup(conn->hostname);
256        g_hash_table_insert(session_cache, hostname, data);
257}
258
259static void ssl_cache_resume(struct scd *conn)
260{
261        struct ssl_session *data;
262
263        if (conn->hostname &&
264            (data = g_hash_table_lookup(session_cache, conn->hostname))) {
265                gnutls_session_set_data(conn->session, data->data, data->size);
266                g_hash_table_remove(session_cache, conn->hostname);
267        }
268}
269
270char *ssl_verify_strerror(int code)
271{
272        GString *ret = g_string_new("");
273
274        if (code & VERIFY_CERT_REVOKED) {
275                g_string_append(ret, "certificate has been revoked, ");
276        }
277        if (code & VERIFY_CERT_SIGNER_NOT_FOUND) {
278                g_string_append(ret, "certificate hasn't got a known issuer, ");
279        }
280        if (code & VERIFY_CERT_SIGNER_NOT_CA) {
281                g_string_append(ret, "certificate's issuer is not a CA, ");
282        }
283        if (code & VERIFY_CERT_INSECURE_ALGORITHM) {
284                g_string_append(ret, "certificate uses an insecure algorithm, ");
285        }
286        if (code & VERIFY_CERT_NOT_ACTIVATED) {
287                g_string_append(ret, "certificate has not been activated, ");
288        }
289        if (code & VERIFY_CERT_EXPIRED) {
290                g_string_append(ret, "certificate has expired, ");
291        }
292        if (code & VERIFY_CERT_WRONG_HOSTNAME) {
293                g_string_append(ret, "certificate hostname mismatch, ");
294        }
295
296        if (ret->len == 0) {
297                g_string_free(ret, TRUE);
298                return NULL;
299        } else {
300                g_string_truncate(ret, ret->len - 2);
301                return g_string_free(ret, FALSE);
302        }
303}
304
305static gboolean ssl_connected(gpointer data, gint source, b_input_condition cond)
306{
307        struct scd *conn = data;
308
309        if (source == -1) {
310                conn->func(conn->data, 0, NULL, cond);
311                g_free(conn->hostname);
312                g_free(conn);
313                return FALSE;
314        }
315
316        ssl_init();
317
318        gnutls_init(&conn->session, GNUTLS_CLIENT);
319        gnutls_session_set_ptr(conn->session, (void *) conn);
320#if GNUTLS_VERSION_NUMBER < 0x020c00
321        gnutls_transport_set_lowat(conn->session, 0);
322#endif
323        gnutls_set_default_priority(conn->session);
324        gnutls_credentials_set(conn->session, GNUTLS_CRD_CERTIFICATE, xcred);
325        if (conn->hostname && !g_hostname_is_ip_address(conn->hostname)) {
326                gnutls_server_name_set(conn->session, GNUTLS_NAME_DNS,
327                                       conn->hostname, strlen(conn->hostname));
328        }
329
330        sock_make_nonblocking(conn->fd);
331        gnutls_transport_set_ptr(conn->session, (gnutls_transport_ptr_t) (long) conn->fd);
332
333        ssl_cache_resume(conn);
334
335        return ssl_handshake(data, source, cond);
336}
337
338static gboolean ssl_handshake(gpointer data, gint source, b_input_condition cond)
339{
340        struct scd *conn = data;
341        int st, stver;
342
343        /* This function returns false, so avoid calling b_event_remove again */
344        conn->inpa = -1;
345
346        if ((st = gnutls_handshake(conn->session)) < 0) {
347                if (st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED) {
348                        conn->inpa = b_input_add(conn->fd, ssl_getdirection(conn),
349                                                 ssl_handshake, data);
350                } else {
351                        conn->func(conn->data, 0, NULL, cond);
352
353                        ssl_disconnect(conn);
354                }
355        } else {
356                if (conn->verify && (stver = verify_certificate_callback(conn->session)) != 0) {
357                        conn->func(conn->data, stver, NULL, cond);
358
359                        ssl_disconnect(conn);
360                } else {
361                        /* For now we can't handle non-blocking perfectly everywhere... */
362                        sock_make_blocking(conn->fd);
363
364                        ssl_cache_add(conn);
365                        conn->established = TRUE;
366                        conn->func(conn->data, 0, conn, cond);
367                }
368        }
369
370        return FALSE;
371}
372
373int ssl_read(void *conn, char *buf, int len)
374{
375        int st;
376
377        if (!((struct scd*) conn)->established) {
378                ssl_errno = SSL_NOHANDSHAKE;
379                return -1;
380        }
381
382        st = gnutls_record_recv(((struct scd*) conn)->session, buf, len);
383
384        ssl_errno = SSL_OK;
385        if (st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED) {
386                ssl_errno = SSL_AGAIN;
387        }
388
389        if (SSLDEBUG && getenv("BITLBEE_DEBUG") && st > 0) {
390                len = write(2, buf, st);
391        }
392
393        return st;
394}
395
396int ssl_write(void *conn, const char *buf, int len)
397{
398        int st;
399
400        if (!((struct scd*) conn)->established) {
401                ssl_errno = SSL_NOHANDSHAKE;
402                return -1;
403        }
404
405        st = gnutls_record_send(((struct scd*) conn)->session, buf, len);
406
407        ssl_errno = SSL_OK;
408        if (st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED) {
409                ssl_errno = SSL_AGAIN;
410        }
411
412        if (SSLDEBUG && getenv("BITLBEE_DEBUG") && st > 0) {
413                len = write(2, buf, st);
414        }
415
416        return st;
417}
418
419int ssl_pending(void *conn)
420{
421        if (conn == NULL) {
422                return 0;
423        }
424
425        if (!((struct scd*) conn)->established) {
426                ssl_errno = SSL_NOHANDSHAKE;
427                return 0;
428        }
429
430#if GNUTLS_VERSION_NUMBER >= 0x03000d && GNUTLS_VERSION_NUMBER <= 0x030012
431        if (ssl_errno == SSL_AGAIN) {
432                return 0;
433        }
434#endif
435
436        return gnutls_record_check_pending(((struct scd*) conn)->session) != 0;
437}
438
439void ssl_disconnect(void *conn_)
440{
441        struct scd *conn = conn_;
442
443        if (conn->inpa != -1) {
444                b_event_remove(conn->inpa);
445        }
446
447        if (conn->established) {
448                gnutls_bye(conn->session, GNUTLS_SHUT_WR);
449        }
450
451        proxy_disconnect(conn->fd);
452
453        if (conn->session) {
454                gnutls_deinit(conn->session);
455        }
456        g_free(conn->hostname);
457        g_free(conn);
458}
459
460int ssl_getfd(void *conn)
461{
462        return(((struct scd*) conn)->fd);
463}
464
465b_input_condition ssl_getdirection(void *conn)
466{
467        return(gnutls_record_get_direction(((struct scd*) conn)->session) ?
468               B_EV_IO_WRITE : B_EV_IO_READ);
469}
470
471size_t ssl_des3_encrypt(const unsigned char *key, size_t key_len, const unsigned char *input,
472                        size_t input_len, const unsigned char *iv, unsigned char **res)
473{
474        gcry_cipher_hd_t gcr;
475        gcry_error_t st;
476
477        ssl_init();
478
479        *res = g_malloc(input_len);
480        st = gcry_cipher_open(&gcr, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_CBC, 0) ||
481             gcry_cipher_setkey(gcr, key, key_len) ||
482             gcry_cipher_setiv(gcr, iv, 8) ||
483             gcry_cipher_encrypt(gcr, *res, input_len, input, input_len);
484
485        gcry_cipher_close(gcr);
486
487        if (st == 0) {
488                return input_len;
489        }
490
491        g_free(*res);
492        return 0;
493}
Note: See TracBrowser for help on using the repository browser.