/* * TLS Diffie Hellmann extension. * * mICQ TLS extension Copyright (C) © 2003-2007 Roman Hoog Antink * * This extension is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 dated June, 1991. * * This extension is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public * License for more details. * * In addition, as a special exception permission is granted to link the * code of this release of mICQ with the OpenSSL project's "OpenSSL" * library, and distribute the linked executables. You must obey the GNU * General Public License in all respects for all of the code used other * than "OpenSSL". If you modify this file, you may extend this exception * to your version of the file, but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your version * of this file. * * You should have received a copy of the GNU General Public License * along with this package; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * $Id$ */ #include "micq.h" #include #if ENABLE_SSL #include "preferences.h" #include "conv.h" #include "util_ssl.h" #include "util_str.h" #include "util_tcl.h" #include "util_ui.h" #include "tcp.h" #include "connection.h" #include "contact.h" #include "packet.h" #include #ifndef ENABLE_TCL #define TCLMessage(from, text) {} #define TCLEvent(from, type, data) {} #endif static char ssl_init_ok = 0; #if ENABLE_GNUTLS #include #include #define SSL_FAIL(s, e) { const char *t = s; \ if (prG->verbose & DEB_SSL || \ e != GNUTLS_E_UNEXPECTED_PACKET_LENGTH) \ rl_printf (i18n (2374, "SSL error: %s [%d]\n"), \ t ? t : "unknown", __LINE__); \ } #define SSL_CHECK_SUCCESS_1_OK(status, ret, msg1, msg2) SSL_CHECK_SUCCESS (status, ret, 1, msg1, msg2) #define SSL_CHECK_SUCCESS_0_OK(status, ret, msg1, msg2) SSL_CHECK_SUCCESS (status, ret, 0, msg1, msg2) #define SSL_CHECK_SUCCESS(status, ret, ok, msg1, msg2) { if (status != ok) { \ SSL_FAIL (s_sprintf ("%s %s %s", \ msg1 ? msg1 : "", msg2 ? msg2 : "", \ gnutls_strerror (status)), status); \ return ret; \ } \ } static gnutls_anon_client_credentials client_cred; static gnutls_anon_server_credentials server_cred; static gnutls_dh_params dh_parm; #else /* ENABLE_GNUTLS */ #include #include #include #include #include static SSL_CTX *gSSL_CTX; /* AUTOGENERATED by openssl dhparam -5 -C */ static DH *get_dh512() { static unsigned char dh512_p[]={ 0xA5,0x82,0x78,0x01,0x4D,0x7A,0x33,0x49,0x2A,0xD2,0x15,0x74, 0x8E,0x95,0x67,0x26,0xB1,0x5D,0x8A,0x01,0xDA,0x0A,0x08,0x51, 0x31,0x64,0x71,0xD7,0x38,0x53,0xEA,0xD2,0xCF,0x3D,0xB9,0x26, 0x78,0x5F,0x75,0xF3,0x90,0xC5,0x63,0x7B,0x9A,0x4F,0x4D,0x03, 0xAB,0x38,0x8D,0x79,0x58,0x19,0xD8,0x83,0x55,0x90,0xC8,0xEC, 0xA0,0x8F,0x01,0x2F, }; static unsigned char dh512_g[]={ 0x05, }; DH *dh; if ((dh=DH_new()) == NULL) return(NULL); dh->p=BN_bin2bn(dh512_p,sizeof(dh512_p),NULL); dh->g=BN_bin2bn(dh512_g,sizeof(dh512_g),NULL); if ((dh->p == NULL) || (dh->g == NULL)) { DH_free(dh); return(NULL); } return(dh); } static void openssl_info_callback (SSL *s, int where, int ret) { const char *str; int w; w = where & ~SSL_ST_MASK; if (w & SSL_ST_CONNECT) str = "SSL_connect"; else if (w & SSL_ST_ACCEPT) str = "SSL_accept"; else str = "undefined"; if (where & SSL_CB_LOOP) { rl_printf ("%s:%s\n", str, SSL_state_string_long (s)); } else if (where & SSL_CB_ALERT) { str = (where & SSL_CB_READ) ? "read" : "write"; rl_printf ("SSL3 alert %s:%s:%s\n", str, SSL_alert_type_string_long (ret), SSL_alert_desc_string_long (ret)); } else if (where & SSL_CB_EXIT) { if (ret == 0) rl_printf ("%s:failed in %s\n", str, SSL_state_string_long(s)); else if (ret < 0) { rl_printf ("%s:%s\n",str,SSL_state_string_long(s)); } } else if (where & SSL_CB_ALERT) { str = (where & SSL_CB_READ) ? "read" : "write"; rl_printf ("SSL3 alert %s:%s:%s\n", str, SSL_alert_type_string_long (ret), SSL_alert_desc_string_long (ret)); } else if (where & SSL_CB_EXIT) { if (ret == 0) rl_printf ("%s:failed in %s\n", str, SSL_state_string_long(s)); else if (ret < 0) { rl_printf ("%s:error in %s\n", str, SSL_state_string_long(s)); } } } #endif /* ENABLE_GNUTLS */ const char *ssl_strerror (Connection *conn, ssl_errno_t err, int e) { #if ENABLE_GNUTLS if (conn->ssl_status == SSL_STATUS_OK && err) return gnutls_strerror (err); #else if (conn->ssl_status == SSL_STATUS_OK && err) return "OpenSSL error"; #endif return strerror (e); } /* * Initialize ssl library * * Return -1 means failure, 0 means ok. */ int SSLInit () { #if ENABLE_GNUTLS #if !HAVE_DH_GENPARAM2 gnutls_datum p1, p2; #endif int ret; ssl_init_ok = 0; if (!libgnutls_is_present) { rl_printf (i18n (2581, "Install the GnuTLS library and enjoy encrypted connections to peers!\n")); return -1; } ret = gnutls_global_init (); SSL_CHECK_SUCCESS_0_OK (ret, -1, "gnutls_global_init", NULL); ret = gnutls_anon_allocate_client_credentials (&client_cred); SSL_CHECK_SUCCESS_0_OK (ret, -1, "allocate_credentials", "[client]"); ret = gnutls_anon_allocate_server_credentials (&server_cred); SSL_CHECK_SUCCESS_0_OK (ret, -1, "allocate_credentials", "[server]"); ret = gnutls_dh_params_init (&dh_parm); SSL_CHECK_SUCCESS_0_OK (ret, -1, "DH param init", "[server]"); #if HAVE_DH_GENPARAM2 ret = gnutls_dh_params_generate2 (dh_parm, DH_OFFER_BITS); SSL_CHECK_SUCCESS_0_OK (ret, -1, "DH param generate2", "[server]"); #else ret = gnutls_dh_params_generate (&p1, &p2, DH_OFFER_BITS); SSL_CHECK_SUCCESS_0_OK (ret, -1, "DH param generate", "[server]"); ret = gnutls_dh_params_set (dh_parm, p1, p2, DH_OFFER_BITS); SSL_CHECK_SUCCESS_0_OK (ret, -1, "DH param set", "[server]"); free (p1.data); free (p2.data); #endif gnutls_anon_set_server_dh_params (server_cred, dh_parm); #else /* ENABLE_GNUTLS */ DH *dh; SSL_library_init(); gSSL_CTX = SSL_CTX_new (TLSv1_method ()); #if OPENSSL_VERSION_NUMBER >= 0x00905000L SSL_CTX_set_cipher_list (gSSL_CTX, "ADH:@STRENGTH"); #else SSL_CTX_set_cipher_list (gSSL_CTX, "ADH"); #endif if (prG->verbose & DEB_SSL) SSL_CTX_set_info_callback (gSSL_CTX, (void (*)())openssl_info_callback); dh = get_dh512 (); if (!dh) return -1; SSL_CTX_set_tmp_dh (gSSL_CTX, dh); DH_free (dh); #endif /* ENABLE_GNUTLS */ ssl_init_ok = 1; return 0; } #if ENABLE_GNUTLS struct ssl_md5ctx_s { gcry_md_hd_t h; }; ssl_md5ctx_t *ssl_md5_init () { ssl_md5ctx_t *ctx = malloc (sizeof *ctx); if (!ctx) return NULL; gcry_error_t err = gcry_md_open (&ctx->h, GCRY_MD_MD5, 0); if (gcry_err_code (err)) { free (ctx); return NULL; } return ctx; } void ssl_md5_write (ssl_md5ctx_t *ctx, char *buf, size_t len) { if (!ctx) return; gcry_md_write (ctx->h, buf, len); } int ssl_md5_final (ssl_md5ctx_t *ctx, char *buf) { if (!ctx) return -1; gcry_md_final (ctx->h); unsigned char *hash = gcry_md_read (ctx->h, 0); assert (hash); memcpy (buf, hash, 16); gcry_md_close (ctx->h); free (ctx); return 0; } #else struct ssl_md5ctx_s { MD5_CTX ctx; }; ssl_md5ctx_t *ssl_md5_init () { ssl_md5ctx_t *ctx = malloc (sizeof *ctx); if (!ctx) return NULL; MD5_Init (&ctx->ctx); return ctx; } void ssl_md5_write (ssl_md5ctx_t *ctx, char *buf, size_t len) { if (!ctx) return; MD5_Update (&ctx->ctx, buf, len); } int ssl_md5_final (ssl_md5ctx_t *ctx, char *buf) { if (!ctx) return -1; MD5_Final ((unsigned char *)buf, &ctx->ctx); free (ctx); return 0; } #endif /* * Check whether peer supports SSL * * Returns 0 if SSL/TLS not supported by peer. */ #undef ssl_supported int ssl_supported (Connection *conn DEBUGPARAM) { Contact *cont; UBYTE status_save = conn->ssl_status; if (!ssl_init_ok) return 0; /* our SSL core is not working :( */ if (conn->ssl_status == SSL_STATUS_OK) return 1; /* SSL session already established */ if (conn->ssl_status == SSL_STATUS_FAILED) return 0; /* ssl handshake with peer already failed. So don't try again */ conn->ssl_status = SSL_STATUS_FAILED; if (!(conn->type & TYPEF_ANY_PEER)) return 0; cont = conn->cont; /* check for peer capabilities * Note: we never initialize SSL for incoming direct connections yet * in order to avoid mutual SSL init trials among mICQ peers. */ if (!cont) return 0; if (!(HAS_CAP (cont->caps, CAP_SIMNEW) || HAS_CAP (cont->caps, CAP_MICQ) || HAS_CAP (cont->caps, CAP_LICQNEW) || (cont->dc && (cont->dc->id1 & 0xFFFF0000) == LICQ_WITHSSL))) { Debug (DEB_SSL, "%s (%s) is no SSL candidate", cont->nick, cont->screen); TCLEvent (cont, "ssl", "no_candidate"); return 0; } conn->ssl_status = status_save; Debug (DEB_SSL, "%s (%s) is an SSL candidate", cont->nick, cont->screen); TCLEvent (cont, "ssl", "candidate"); return 1; } /* * ssl_connect * * execute SSL handshake * * return: 1 means ok. 0 failed. */ #undef ssl_connect int ssl_connect (Connection *conn, BOOL is_client DEBUGPARAM) { #if ENABLE_GNUTLS int ret; int kx_prio[2] = { GNUTLS_KX_ANON_DH, 0 }; #ifdef ENABLE_TCL Contact *cont = conn->cont; #endif Debug (DEB_SSL, "ssl_connect"); if (!ssl_init_ok || (conn->ssl_status != SSL_STATUS_NA && conn->ssl_status != SSL_STATUS_INIT && conn->ssl_status != SSL_STATUS_REQUEST)) { TCLEvent (cont, "ssl", "failed precondition"); return 0; } conn->ssl_status = SSL_STATUS_FAILED; conn->connect = 1; ret = gnutls_init (&conn->ssl, is_client ? GNUTLS_CLIENT : GNUTLS_SERVER); if (ret) TCLEvent (cont, "ssl", "failed init"); SSL_CHECK_SUCCESS_0_OK (ret, 0, "init", is_client ? "[client]" : "[server]"); gnutls_set_default_priority (conn->ssl); gnutls_kx_set_priority (conn->ssl, kx_prio); if (is_client) ret = gnutls_credentials_set (conn->ssl, GNUTLS_CRD_ANON, client_cred); else ret = gnutls_credentials_set (conn->ssl, GNUTLS_CRD_ANON, server_cred); if (ret) TCLEvent (cont, "ssl", "failed key"); SSL_CHECK_SUCCESS_0_OK (ret, 0, "credentials_set", is_client ? "[client]" : "[server]"); if (is_client) /* reduce minimal prime bits expected for licq interoperability */ gnutls_dh_set_prime_bits (conn->ssl, DH_EXPECT_BITS); gnutls_transport_set_ptr (conn->ssl, (gnutls_transport_ptr)conn->sok); /* return type void */ #else /* ENABLE_GNUTLS */ conn->ssl = SSL_new (gSSL_CTX); SSL_set_session (conn->ssl, NULL); SSL_set_fd (conn->ssl, conn->sok); if (is_client) SSL_set_connect_state (conn->ssl); else SSL_set_accept_state (conn->ssl); #endif /* ENABLE_GNUTLS */ return ssl_handshake (conn) != 0; } /* * sockread wrapper for network connections * * Calls default sockread() for non-SSL connections. */ ssl_errno_t ssl_read (Connection *conn, UBYTE *data, UWORD len_p) { int len, rc; #if !ENABLE_GNUTLS int tmp; #endif Contact *cont = conn->cont; if (conn->ssl_status == SSL_STATUS_HANDSHAKE) { rc = ssl_handshake (conn); DebugH (DEB_SSL, "ssl_sockread calling handshake [ret=%d errno=%d]", rc, errno); errno = EAGAIN; return -1; } if (conn->ssl_status != SSL_STATUS_OK) { len = sockread (conn->sok, data, len_p); rc = errno; if (!len && !rc) rc = ECONNRESET; errno = rc; return len; } #if ENABLE_GNUTLS len = gnutls_record_recv (conn->ssl, data, len_p); if (len > 0) DebugH (DEB_SSL, "read %d bytes from %s", len, cont->nick); if (len < 0) { SSL_FAIL (s_sprintf (i18n (2376, "SSL read from %s [ERR=%d]: %s"), cont->nick, len, gnutls_strerror (len)), len); ssl_disconnect (conn); } #else DebugH (DEB_SSL, "SSL_read pre"); len = SSL_read (conn->ssl, data, len_p); DebugH (DEB_SSL, "SSL_read post"); if (len > 0) { DebugH (DEB_SSL, "read %d bytes from %s", len, cont->nick); /* setting _W to trigger more reading */ conn->connect |= CONNECT_SELECT_R | CONNECT_SELECT_W; return len; } tmp = SSL_get_error (conn->ssl, len); if (tmp != SSL_ERROR_NONE) { int line = -1; const char *file = ""; switch (tmp) { case SSL_ERROR_WANT_WRITE: conn->connect |= CONNECT_SELECT_W; conn->connect &= ~CONNECT_SELECT_R; len = 0; break; case SSL_ERROR_WANT_READ: conn->connect |= CONNECT_SELECT_R; conn->connect &= ~CONNECT_SELECT_W; len = 0; break; case SSL_ERROR_SSL: ERR_get_error_line (&file, &line); rl_printf (i18n (2527, "OpenSSL internal error: %s:%i\n"), file, line); return -1; case SSL_ERROR_SYSCALL: rl_printf (i18n (2528, "OpenSSL read error: %s\n"), strerror (errno)); ssl_disconnect (conn); return -1; case SSL_ERROR_ZERO_RETURN: ssl_disconnect (conn); return -1; default: rl_printf (i18n (2529, "OpenSSL read error %d (unknown)\n"), tmp); ssl_disconnect (conn); return -1; } } #endif if (!len) { errno = EAGAIN; len = -1; } return len; } /* * sockwrite wrapper for network connections * * Calls default sockwrite() for non-SSL connections. */ ssl_errno_t ssl_write (Connection *conn, UBYTE *data, UWORD len_p) { int len = 0; #if !ENABLE_GNUTLS int tmp; #endif Contact *cont = conn->cont; if (conn->ssl_status == SSL_STATUS_HANDSHAKE) { ssl_handshake (conn); return 0; } if (conn->ssl_status != SSL_STATUS_OK) return sockwrite (conn->sok, data, len_p); #if ENABLE_GNUTLS len = gnutls_record_send (conn->ssl, data, len_p); if (len > 0) DebugH (DEB_SSL, "write %d bytes to %s", len, cont->nick); if (len < 0) { SSL_FAIL (s_sprintf (i18n (2377, "SSL write to %s [ERR=%d]: %s"), cont->nick, len, gnutls_strerror (len)), len); ssl_disconnect (conn); } #else len = SSL_write (conn->ssl, data, len_p); if (len > 0) { DebugH (DEB_SSL, "sent %d/%d bytes to %s", len, len_p, cont->nick); return len; } tmp = SSL_get_error (conn->ssl, len); if (tmp != SSL_ERROR_NONE) { switch (tmp) { case SSL_ERROR_WANT_WRITE: conn->connect |= CONNECT_SELECT_W; conn->connect &= ~CONNECT_SELECT_R; errno = EAGAIN; len = -1; break; case SSL_ERROR_WANT_READ: conn->connect |= CONNECT_SELECT_R; conn->connect &= ~CONNECT_SELECT_W; errno = EAGAIN; len = -1; break; case SSL_ERROR_SYSCALL: rl_printf (i18n (2530, "OpenSSL write error: %s\n"), strerror (tmp)); ssl_disconnect (conn); return -1; default: rl_printf (i18n (2531, "OpenSSL write error %d (unknown)\n"), tmp); ssl_disconnect (conn); return -1; } } #endif return len; } /* * call handshake in SSL lib. * return: 0=failed, 1=ongoing (EAGAIN), 2=ok */ #undef ssl_handshake int ssl_handshake (Connection *conn DEBUGPARAM) { Contact *cont = conn->cont; int ret = 0; #if ENABLE_GNUTLS ret = gnutls_handshake (conn->ssl); DebugH (DEB_SSL, "handshake %d (%d,%d)", ret, GNUTLS_E_AGAIN, GNUTLS_E_INTERRUPTED); if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) { conn->ssl_status = SSL_STATUS_HANDSHAKE; conn->connect &= ~CONNECT_SELECT_R & ~CONNECT_SELECT_W; if (gnutls_record_get_direction (conn->ssl)) conn->connect |= CONNECT_SELECT_W; else conn->connect |= CONNECT_SELECT_R; return 1; } #else int err_i = SSL_do_handshake (conn->ssl); int err_j = SSL_get_error (conn->ssl, err_i); if (err_j != SSL_ERROR_NONE) { const char *file = ""; int line = -1; switch (err_j) { #ifdef SSL_ERROR_WANT_ACCEPT case SSL_ERROR_WANT_ACCEPT: #endif case SSL_ERROR_WANT_READ: conn->ssl_status = SSL_STATUS_HANDSHAKE; conn->connect |= CONNECT_SELECT_R; return 1; case SSL_ERROR_WANT_CONNECT: case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_X509_LOOKUP: conn->ssl_status = SSL_STATUS_HANDSHAKE; conn->connect |= CONNECT_SELECT_R | CONNECT_SELECT_W; return 1; case SSL_ERROR_SSL: ERR_get_error_line (&file, &line); rl_printf (i18n (2527, "OpenSSL internal error: %s:%i\n"), file, line); ret = 1; default: rl_printf (i18n (2532, "OpenSSL error: SSL_RET=%d, SSL_ERR=%d\n"), err_i, err_j); ret = 1; } } #endif if (ret) { TCLEvent (cont, "ssl", "failed handshake"); #if ENABLE_GNUTLS SSL_FAIL (s_sprintf ("%s %s", "handshake", gnutls_strerror (ret)), ret); #else rl_printf (i18n (2533, "SSL handshake failed.\n")); #endif conn->ssl_status = SSL_STATUS_FAILED; ssl_disconnect (conn); conn->connect = 0; return 0; } conn->ssl_status = SSL_STATUS_OK; conn->connect = CONNECT_OK | CONNECT_SELECT_R; if (prG->verbose) { rl_log_for (cont->nick, COLCONTACT); rl_printf (i18n (2375, "SSL handshake ok.\n")); } TCLEvent (cont, "ssl", "ok"); return 2; } /* * Shutdown SSL session and release SSL memory */ #undef ssl_close void ssl_close (Connection *conn DEBUGPARAM) { DebugH (DEB_SSL, "ssl_close"); if (conn->ssl_status == SSL_STATUS_OK) { DebugH (DEB_SSL, "ssl_close calling ssl_disconnect"); ssl_disconnect (conn); } if (conn->sok != -1) sockclose (conn->sok); conn->sok = -1; } /* * Stop SSL traffic on given Connection. * * Note: does not close socket. Frees SSL data only. */ #undef ssl_disconnect void ssl_disconnect (Connection *conn DEBUGPARAM) { DebugH (DEB_SSL, "ssl_disconnect"); if (conn->ssl_status == SSL_STATUS_OK) { conn->ssl_status = SSL_STATUS_NA; if (conn->ssl) #if ENABLE_GNUTLS free (conn->ssl); #else { SSL_shutdown (conn->ssl); SSL_free (conn->ssl); } #endif conn->ssl = NULL; } } /* * Request secure channel in licq's way. */ BOOL TCPSendSSLReq (Connection *list, Contact *cont) { Connection *peer; UBYTE ret; ret = PeerSendMsg (list, cont, MSG_SSL_OPEN, ""); if ((peer = ConnectionFind (TYPE_MSGDIRECT, cont, list))) peer->ssl_status = SSL_STATUS_REQUEST; return ret; } #endif