source: lib/ssl_gnutls.c @ 5eab9260

Last change on this file since 5eab9260 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
RevLine 
[5ebff60]1/********************************************************************\
[b7d3cc34]2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
[9b67285]4  * Copyright 2002-2012 Wilmer van der Gaast and others                *
[b7d3cc34]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;
[6f10697]22  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
23  Fifth Floor, Boston, MA  02110-1301  USA
[b7d3cc34]24*/
25
26#include <gnutls/gnutls.h>
[486ddb5]27#include <gnutls/x509.h>
[83e47ec]28#include <gcrypt.h>
[701acdd4]29#include <fcntl.h>
30#include <unistd.h>
[b7d3cc34]31#include "proxy.h"
32#include "ssl_client.h"
33#include "sock.h"
34#include "stdlib.h"
[486ddb5]35#include "bitlbee.h"
[b7d3cc34]36
[701acdd4]37int ssl_errno = 0;
38
[b7d3cc34]39static gboolean initialized = FALSE;
[2fb1262]40gnutls_certificate_credentials_t xcred;
[b7d3cc34]41
[56f260a]42#include <limits.h>
43
[ca974d7]44#define SSLDEBUG 0
45
[5ebff60]46struct scd {
[3d64e5b]47        ssl_input_function func;
[b7d3cc34]48        gpointer data;
49        int fd;
50        gboolean established;
[701acdd4]51        int inpa;
[486ddb5]52        char *hostname;
53        gboolean verify;
[5ebff60]54
[2fb1262]55        gnutls_session_t session;
[b7d3cc34]56};
57
[9b67285]58static GHashTable *session_cache;
59
[5ebff60]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);
[b7d3cc34]63
[5ebff60]64static void ssl_deinit(void);
[b7d3cc34]65
[5ebff60]66static void ssl_log(int level, const char *line)
[632f3d4]67{
[5ebff60]68        printf("%d %s", level, line);
[632f3d4]69}
70
[5ebff60]71void ssl_init(void)
[ba5add7]72{
[5ebff60]73        if (initialized) {
[83e47ec]74                return;
[5ebff60]75        }
76
[ba5add7]77        gnutls_global_init();
[5ebff60]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
[8f976e6]82                /* Not needed in GnuTLS 2.11+ (enabled by default there) so
83                   don't do it (resets possible other defaults). */
[5ebff60]84                if (!gnutls_check_version("2.11")) {
85                        gnutls_certificate_set_verify_flags(xcred, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
86                }
[59cd92b]87        }
[ba5add7]88        initialized = TRUE;
[5ebff60]89
90        gnutls_global_set_log_function(ssl_log);
[632f3d4]91        /*
92        gnutls_global_set_log_level( 3 );
93        */
[5ebff60]94
95        session_cache = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
96
97        atexit(ssl_deinit);
[59cd92b]98}
99
[5ebff60]100static void ssl_deinit(void)
[59cd92b]101{
102        gnutls_global_deinit();
[5ebff60]103        gnutls_certificate_free_credentials(xcred);
104        g_hash_table_destroy(session_cache);
[9b67285]105        session_cache = NULL;
[ba5add7]106}
107
[5ebff60]108void *ssl_connect(char *host, int port, gboolean verify, ssl_input_function func, gpointer data)
[b7d3cc34]109{
[5ebff60]110        struct scd *conn = g_new0(struct scd, 1);
111
[b7d3cc34]112        conn->func = func;
113        conn->data = data;
[701acdd4]114        conn->inpa = -1;
[5ebff60]115        conn->hostname = g_strdup(host);
[a72dc2b]116        conn->verify = verify && global.conf->cafile;
[5ebff60]117        conn->fd = proxy_connect(host, port, ssl_connected, conn);
118
119        if (conn->fd < 0) {
[098a75b]120                g_free(conn->hostname);
[5ebff60]121                g_free(conn);
[42127dc]122                return NULL;
[b7d3cc34]123        }
[5ebff60]124
[42127dc]125        return conn;
126}
127
[5ebff60]128void *ssl_starttls(int fd, char *hostname, gboolean verify, ssl_input_function func, gpointer data)
[42127dc]129{
[5ebff60]130        struct scd *conn = g_new0(struct scd, 1);
131
[42127dc]132        conn->fd = fd;
133        conn->func = func;
134        conn->data = data;
135        conn->inpa = -1;
[5ebff60]136        conn->hostname = g_strdup(hostname);
137
[486ddb5]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;
[5ebff60]143
[c1ed6527]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.
[5ebff60]149
[c1ed6527]150           In short, doing things like this makes the rest of the code a lot
151           simpler. */
[5ebff60]152
153        b_timeout_add(1, ssl_starttls_real, conn);
154
[42127dc]155        return conn;
[b7d3cc34]156}
157
[5ebff60]158static gboolean ssl_starttls_real(gpointer data, gint source, b_input_condition cond)
[c1ed6527]159{
160        struct scd *conn = data;
[5ebff60]161
162        return ssl_connected(conn, conn->fd, B_EV_IO_WRITE);
[c1ed6527]163}
[701acdd4]164
[5ebff60]165static int verify_certificate_callback(gnutls_session_t session)
[486ddb5]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;
[2fb1262]173        struct scd *conn;
[486ddb5]174
[5ebff60]175        conn = gnutls_session_get_ptr(session);
176
177        gnutlsret = gnutls_certificate_verify_peers2(session, &status);
178        if (gnutlsret < 0) {
[486ddb5]179                return VERIFY_CERT_ERROR;
[5ebff60]180        }
[486ddb5]181
[5ebff60]182        if (status & GNUTLS_CERT_INVALID) {
[486ddb5]183                verifyret |= VERIFY_CERT_INVALID;
[5ebff60]184        }
[486ddb5]185
[5ebff60]186        if (status & GNUTLS_CERT_REVOKED) {
[486ddb5]187                verifyret |= VERIFY_CERT_REVOKED;
[5ebff60]188        }
[486ddb5]189
[5ebff60]190        if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
[486ddb5]191                verifyret |= VERIFY_CERT_SIGNER_NOT_FOUND;
[5ebff60]192        }
[486ddb5]193
[5ebff60]194        if (status & GNUTLS_CERT_SIGNER_NOT_CA) {
[486ddb5]195                verifyret |= VERIFY_CERT_SIGNER_NOT_CA;
[5ebff60]196        }
[486ddb5]197
[5ebff60]198        if (status & GNUTLS_CERT_INSECURE_ALGORITHM) {
[486ddb5]199                verifyret |= VERIFY_CERT_INSECURE_ALGORITHM;
[5ebff60]200        }
[486ddb5]201
[5513f3e]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) */
[5ebff60]205        if (status & GNUTLS_CERT_NOT_ACTIVATED) {
[486ddb5]206                verifyret |= VERIFY_CERT_NOT_ACTIVATED;
[5ebff60]207        }
[486ddb5]208
[5ebff60]209        if (status & GNUTLS_CERT_EXPIRED) {
[486ddb5]210                verifyret |= VERIFY_CERT_EXPIRED;
[5ebff60]211        }
[5513f3e]212#endif
[486ddb5]213
[5ebff60]214        if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509 || gnutls_x509_crt_init(&cert) < 0) {
[486ddb5]215                return VERIFY_CERT_ERROR;
[5ebff60]216        }
[486ddb5]217
[5ebff60]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) {
[486ddb5]220                return VERIFY_CERT_ERROR;
[5ebff60]221        }
[486ddb5]222
[5ebff60]223        if (!gnutls_x509_crt_check_hostname(cert, conn->hostname)) {
[486ddb5]224                verifyret |= VERIFY_CERT_INVALID;
225                verifyret |= VERIFY_CERT_WRONG_HOSTNAME;
226        }
227
[5ebff60]228        gnutls_x509_crt_deinit(cert);
[486ddb5]229
230        return verifyret;
231}
232
[5ebff60]233struct ssl_session {
[9b67285]234        size_t size;
235        char data[];
236};
237
[5ebff60]238static void ssl_cache_add(struct scd *conn)
[9b67285]239{
[b7cd22d]240        size_t data_size = 0;
[9b67285]241        struct ssl_session *data;
242        char *hostname;
[5ebff60]243
244        if (!conn->hostname ||
245            gnutls_session_get_data(conn->session, NULL, &data_size) != 0) {
[9b67285]246                return;
[5ebff60]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);
[9b67285]252                return;
253        }
[5ebff60]254
255        hostname = g_strdup(conn->hostname);
256        g_hash_table_insert(session_cache, hostname, data);
[9b67285]257}
258
[5ebff60]259static void ssl_cache_resume(struct scd *conn)
[9b67285]260{
261        struct ssl_session *data;
[5ebff60]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);
[9b67285]267        }
268}
269
[5ebff60]270char *ssl_verify_strerror(int code)
[78b8401]271{
[5ebff60]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, ");
[78b8401]288        }
[5ebff60]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);
[78b8401]302        }
303}
304
[5ebff60]305static gboolean ssl_connected(gpointer data, gint source, b_input_condition cond)
[b7d3cc34]306{
307        struct scd *conn = data;
[5ebff60]308
309        if (source == -1) {
310                conn->func(conn->data, 0, NULL, cond);
[098a75b]311                g_free(conn->hostname);
[5ebff60]312                g_free(conn);
[2b7d2d1]313                return FALSE;
[701acdd4]314        }
[5ebff60]315
[83e47ec]316        ssl_init();
[5ebff60]317
318        gnutls_init(&conn->session, GNUTLS_CLIENT);
319        gnutls_session_set_ptr(conn->session, (void *) conn);
[80acb6d]320#if GNUTLS_VERSION_NUMBER < 0x020c00
[5ebff60]321        gnutls_transport_set_lowat(conn->session, 0);
[80acb6d]322#endif
[5ebff60]323        gnutls_set_default_priority(conn->session);
324        gnutls_credentials_set(conn->session, GNUTLS_CRD_CERTIFICATE, xcred);
[5eab9260]325        if (conn->hostname && !g_hostname_is_ip_address(conn->hostname)) {
[5ebff60]326                gnutls_server_name_set(conn->session, GNUTLS_NAME_DNS,
327                                       conn->hostname, strlen(conn->hostname));
328        }
329
330        sock_make_nonblocking(conn->fd);
[720f7a9]331        gnutls_transport_set_ptr(conn->session, (gnutls_transport_ptr_t) (long) conn->fd);
[5ebff60]332
333        ssl_cache_resume(conn);
334
335        return ssl_handshake(data, source, cond);
[701acdd4]336}
337
[5ebff60]338static gboolean ssl_handshake(gpointer data, gint source, b_input_condition cond)
[701acdd4]339{
340        struct scd *conn = data;
[486ddb5]341        int st, stver;
[5ebff60]342
[286cd48]343        /* This function returns false, so avoid calling b_event_remove again */
344        conn->inpa = -1;
[486ddb5]345
[5ebff60]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
[098a75b]353                        ssl_disconnect(conn);
[486ddb5]354                }
[5ebff60]355        } else {
356                if (conn->verify && (stver = verify_certificate_callback(conn->session)) != 0) {
357                        conn->func(conn->data, stver, NULL, cond);
358
[098a75b]359                        ssl_disconnect(conn);
[5ebff60]360                } else {
[486ddb5]361                        /* For now we can't handle non-blocking perfectly everywhere... */
[5ebff60]362                        sock_make_blocking(conn->fd);
363
364                        ssl_cache_add(conn);
[486ddb5]365                        conn->established = TRUE;
[5ebff60]366                        conn->func(conn->data, 0, conn, cond);
[486ddb5]367                }
[701acdd4]368        }
[5ebff60]369
[2b7d2d1]370        return FALSE;
[b7d3cc34]371}
372
[5ebff60]373int ssl_read(void *conn, char *buf, int len)
[b7d3cc34]374{
[8a9afe4]375        int st;
[5ebff60]376
377        if (!((struct scd*) conn)->established) {
[701acdd4]378                ssl_errno = SSL_NOHANDSHAKE;
[80acb6d]379                return -1;
[701acdd4]380        }
[5ebff60]381
382        st = gnutls_record_recv(((struct scd*) conn)->session, buf, len);
383
[8a9afe4]384        ssl_errno = SSL_OK;
[5ebff60]385        if (st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED) {
[8a9afe4]386                ssl_errno = SSL_AGAIN;
[5ebff60]387        }
388
389        if (SSLDEBUG && getenv("BITLBEE_DEBUG") && st > 0) {
390                len = write(2, buf, st);
391        }
392
[8a9afe4]393        return st;
[b7d3cc34]394}
395
[5ebff60]396int ssl_write(void *conn, const char *buf, int len)
[b7d3cc34]397{
[8a9afe4]398        int st;
[5ebff60]399
400        if (!((struct scd*) conn)->established) {
[701acdd4]401                ssl_errno = SSL_NOHANDSHAKE;
[80acb6d]402                return -1;
[701acdd4]403        }
[5ebff60]404
405        st = gnutls_record_send(((struct scd*) conn)->session, buf, len);
406
[8a9afe4]407        ssl_errno = SSL_OK;
[5ebff60]408        if (st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED) {
[8a9afe4]409                ssl_errno = SSL_AGAIN;
[5ebff60]410        }
411
412        if (SSLDEBUG && getenv("BITLBEE_DEBUG") && st > 0) {
413                len = write(2, buf, st);
414        }
415
[8a9afe4]416        return st;
[b7d3cc34]417}
418
[5ebff60]419int ssl_pending(void *conn)
[8a2221a7]420{
[5ebff60]421        if (conn == NULL) {
[80acb6d]422                return 0;
[5ebff60]423        }
424
425        if (!((struct scd*) conn)->established) {
[80acb6d]426                ssl_errno = SSL_NOHANDSHAKE;
427                return 0;
428        }
[632f3d4]429
430#if GNUTLS_VERSION_NUMBER >= 0x03000d && GNUTLS_VERSION_NUMBER <= 0x030012
[5ebff60]431        if (ssl_errno == SSL_AGAIN) {
[632f3d4]432                return 0;
[5ebff60]433        }
[632f3d4]434#endif
[5ebff60]435
436        return gnutls_record_check_pending(((struct scd*) conn)->session) != 0;
[8a2221a7]437}
438
[5ebff60]439void ssl_disconnect(void *conn_)
[b7d3cc34]440{
441        struct scd *conn = conn_;
[5ebff60]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
[0db6618]451        proxy_disconnect(conn->fd);
[5ebff60]452
453        if (conn->session) {
454                gnutls_deinit(conn->session);
455        }
456        g_free(conn->hostname);
457        g_free(conn);
[b7d3cc34]458}
459
[5ebff60]460int ssl_getfd(void *conn)
[b7d3cc34]461{
[5ebff60]462        return(((struct scd*) conn)->fd);
[b7d3cc34]463}
[8a9afe4]464
[5ebff60]465b_input_condition ssl_getdirection(void *conn)
[8a9afe4]466{
[5ebff60]467        return(gnutls_record_get_direction(((struct scd*) conn)->session) ?
468               B_EV_IO_WRITE : B_EV_IO_READ);
[8a9afe4]469}
[83e47ec]470
[5ebff60]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)
[83e47ec]473{
474        gcry_cipher_hd_t gcr;
475        gcry_error_t st;
[5ebff60]476
[83e47ec]477        ssl_init();
[5ebff60]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) {
[83e47ec]488                return input_len;
[5ebff60]489        }
490
491        g_free(*res);
[83e47ec]492        return 0;
493}
Note: See TracBrowser for help on using the repository browser.