diff options
-rw-r--r-- | AUTHORS | bin | 72809 -> 72871 bytes | |||
-rw-r--r-- | Makefile.am | 9 | ||||
-rw-r--r-- | acinclude.m4 | 157 | ||||
-rw-r--r-- | configure.in | 19 | ||||
-rw-r--r-- | epan/dissectors/packet-ssl-utils.c | 1265 | ||||
-rw-r--r-- | epan/dissectors/packet-ssl-utils.h | 172 | ||||
-rw-r--r-- | epan/dissectors/packet-ssl.c | 1069 | ||||
-rw-r--r-- | gtk/Makefile.common | 1 | ||||
-rw-r--r-- | gtk/main.c | 2 | ||||
-rw-r--r-- | gtk/menu.c | 14 | ||||
-rw-r--r-- | gtk/ssl-dlg.c | 1049 |
11 files changed, 3570 insertions, 187 deletions
Binary files differ diff --git a/Makefile.am b/Makefile.am index 6b8960ba52..4dcce27016 100644 --- a/Makefile.am +++ b/Makefile.am @@ -304,7 +304,8 @@ ethereal_LDADD = \ @SNMP_LIBS@ @SSL_LIBS@ \ $(plugin_ldadd) \ @PCRE_LIBS@ \ - @PCAP_LIBS@ @GTK_LIBS@ @ADNS_LIBS@ @KRB5_LIBS@ @FRAMEWORKS@ + @PCAP_LIBS@ @GTK_LIBS@ @ADNS_LIBS@ @KRB5_LIBS@ @FRAMEWORKS@ \ + @LIBGNUTLS_LIBS@ # Additional libs that I know how to build. These will be # linked into the tethereal executable. @@ -326,7 +327,8 @@ tethereal_LDADD = \ $(plugin_ldadd) \ @PCRE_LIBS@ \ @GLIB_LIBS@ -lm \ - @PCAP_LIBS@ @SOCKET_LIBS@ @NSL_LIBS@ @ADNS_LIBS@ @KRB5_LIBS@ + @PCAP_LIBS@ @SOCKET_LIBS@ @NSL_LIBS@ @ADNS_LIBS@ @KRB5_LIBS@ \ + @LIBGNUTLS_LIBS@ if ENABLE_STATIC tethereal_LDFLAGS = -Wl,-static -all-static @@ -442,7 +444,8 @@ dftest_LDADD = \ $(plugin_ldadd) \ @PCRE_LIBS@ \ @GLIB_LIBS@ -lm \ - @PCAP_LIBS@ @SOCKET_LIBS@ @NSL_LIBS@ @ADNS_LIBS@ @KRB5_LIBS@ + @PCAP_LIBS@ @SOCKET_LIBS@ @NSL_LIBS@ @ADNS_LIBS@ @KRB5_LIBS@ \ + @LIBGNUTLS_LIBS@ dftest_LDFLAGS = -export-dynamic diff --git a/acinclude.m4 b/acinclude.m4 index 2e15ce5eb1..991decceed 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -1411,3 +1411,160 @@ AC_DEFUN([AC_ETHEREAL_KRB5_CHECK], fi AC_SUBST(KRB5_LIBS) ]) + +dnl Autoconf macros for libgnutls + +# Modified for LIBGNUTLS -- nmav +# Configure paths for LIBGCRYPT +# Shamelessly stolen from the one of XDELTA by Owen Taylor +# Werner Koch 99-12-09 + +dnl AM_PATH_LIBGNUTLS([MINIMUM-VERSION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]]) +dnl Test for libgnutls, and define LIBGNUTLS_CFLAGS and LIBGNUTLS_LIBS +dnl +AC_DEFUN([AM_PATH_LIBGNUTLS], +[dnl +dnl Get the cflags and libraries from the libgnutls-config script +dnl +AC_ARG_WITH(libgnutls-prefix, + [ --with-libgnutls-prefix=PFX Prefix where libgnutls is installed (optional)], + libgnutls_config_prefix="$withval", libgnutls_config_prefix="") + + if test x$libgnutls_config_prefix != x ; then + if test x${LIBGNUTLS_CONFIG+set} != xset ; then + LIBGNUTLS_CONFIG=$libgnutls_config_prefix/bin/libgnutls-config + fi + fi + + AC_PATH_PROG(LIBGNUTLS_CONFIG, libgnutls-config, no) + min_libgnutls_version=ifelse([$1], ,0.1.0,$1) + AC_MSG_CHECKING(for libgnutls - version >= $min_libgnutls_version) + no_libgnutls="" + if test "$LIBGNUTLS_CONFIG" = "no" ; then + no_libgnutls=yes + else + LIBGNUTLS_CFLAGS=`$LIBGNUTLS_CONFIG $libgnutls_config_args --cflags` + LIBGNUTLS_LIBS=`$LIBGNUTLS_CONFIG $libgnutls_config_args --libs` + libgnutls_config_version=`$LIBGNUTLS_CONFIG $libgnutls_config_args --version` + + ac_save_CFLAGS="$CFLAGS" + ac_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $LIBGNUTLS_CFLAGS" + LIBS="$LIBS $LIBGNUTLS_LIBS" +dnl +dnl Now check if the installed libgnutls is sufficiently new. Also sanity +dnl checks the results of libgnutls-config to some extent +dnl + rm -f conf.libgnutlstest + AC_TRY_RUN([ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <gnutls/gnutls.h> + +int +main () +{ + system ("touch conf.libgnutlstest"); + + if( strcmp( gnutls_check_version(NULL), "$libgnutls_config_version" ) ) + { + printf("\n*** 'libgnutls-config --version' returned %s, but LIBGNUTLS (%s)\n", + "$libgnutls_config_version", gnutls_check_version(NULL) ); + printf("*** was found! If libgnutls-config was correct, then it is best\n"); + printf("*** to remove the old version of LIBGNUTLS. You may also be able to fix the error\n"); + printf("*** by modifying your LD_LIBRARY_PATH enviroment variable, or by editing\n"); + printf("*** /etc/ld.so.conf. Make sure you have run ldconfig if that is\n"); + printf("*** required on your system.\n"); + printf("*** If libgnutls-config was wrong, set the environment variable LIBGNUTLS_CONFIG\n"); + printf("*** to point to the correct copy of libgnutls-config, and remove the file config.cache\n"); + printf("*** before re-running configure\n"); + } + else if ( strcmp(gnutls_check_version(NULL), LIBGNUTLS_VERSION ) ) + { + printf("\n*** LIBGNUTLS header file (version %s) does not match\n", LIBGNUTLS_VERSION); + printf("*** library (version %s)\n", gnutls_check_version(NULL) ); + } + else + { + if ( gnutls_check_version( "$min_libgnutls_version" ) ) + { + return 0; + } + else + { + printf("no\n*** An old version of LIBGNUTLS (%s) was found.\n", + gnutls_check_version(NULL) ); + printf("*** You need a version of LIBGNUTLS newer than %s. The latest version of\n", + "$min_libgnutls_version" ); + printf("*** LIBGNUTLS is always available from ftp://gnutls.hellug.gr/pub/gnutls.\n"); + printf("*** \n"); + printf("*** If you have already installed a sufficiently new version, this error\n"); + printf("*** probably means that the wrong copy of the libgnutls-config shell script is\n"); + printf("*** being found. The easiest way to fix this is to remove the old version\n"); + printf("*** of LIBGNUTLS, but you can also set the LIBGNUTLS_CONFIG environment to point to the\n"); + printf("*** correct copy of libgnutls-config. (In this case, you will have to\n"); + printf("*** modify your LD_LIBRARY_PATH enviroment variable, or edit /etc/ld.so.conf\n"); + printf("*** so that the correct libraries are found at run-time))\n"); + } + } + return 1; +} +],, no_libgnutls=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + + if test "x$no_libgnutls" = x ; then + AC_MSG_RESULT(yes) + ifelse([$2], , :, [$2]) + else + if test -f conf.libgnutlstest ; then + : + else + AC_MSG_RESULT(no) + fi + if test "$LIBGNUTLS_CONFIG" = "no" ; then + echo "*** The libgnutls-config script installed by LIBGNUTLS could not be found" + echo "*** If LIBGNUTLS was installed in PREFIX, make sure PREFIX/bin is in" + echo "*** your path, or set the LIBGNUTLS_CONFIG environment variable to the" + echo "*** full path to libgnutls-config." + else + if test -f conf.libgnutlstest ; then + : + else + echo "*** Could not run libgnutls test program, checking why..." + CFLAGS="$CFLAGS $LIBGNUTLS_CFLAGS" + LIBS="$LIBS $LIBGNUTLS_LIBS" + AC_TRY_LINK([ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <gnutls/gnutls.h> +], [ return !!gnutls_check_version(NULL); ], + [ echo "*** The test program compiled, but did not run. This usually means" + echo "*** that the run-time linker is not finding LIBGNUTLS or finding the wrong" + echo "*** version of LIBGNUTLS. If it is not finding LIBGNUTLS, you'll need to set your" + echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" + echo "*** to the installed location Also, make sure you have run ldconfig if that" + echo "*** is required on your system" + echo "***" + echo "*** If you have an old version installed, it is best to remove it, although" + echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH" + echo "***" ], + [ echo "*** The test program failed to compile or link. See the file config.log for the" + echo "*** exact error that occured. This usually means LIBGNUTLS was incorrectly installed" + echo "*** or that you have moved LIBGNUTLS since it was installed. In the latter case, you" + echo "*** may want to edit the libgnutls-config script: $LIBGNUTLS_CONFIG" ]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + fi + LIBGNUTLS_CFLAGS="" + LIBGNUTLS_LIBS="" + ifelse([$3], , :, [$3]) + fi + rm -f conf.libgnutlstest + AC_SUBST(LIBGNUTLS_CFLAGS) + AC_SUBST(LIBGNUTLS_LIBS) +]) diff --git a/configure.in b/configure.in index 3d52e711bc..eae4c49c3e 100644 --- a/configure.in +++ b/configure.in @@ -63,6 +63,24 @@ AC_PATH_PROG(DOXYGEN, doxygen) AC_CHECK_PROG(HAVE_DOXYGEN, doxygen, "yes", "no") AM_CONDITIONAL(HAVE_DOXYGEN, test x$HAVE_DOXYGEN = xyes) +# gnu tls +AM_PATH_LIBGNUTLS(1.0.0, + [ + echo "gnuTLS found, enabling ssl decryption" + AC_DEFINE(HAVE_LIBGNUTLS, 1, [Define to use gnutls library]) + tls_message="yes" + ] + , [ + if test x$libgnutls_config_prefix != x ; then + AC_MSG_ERROR([[gnuTLS not found; install gnuTLS-devel package for your system]]) + else + echo echo "gnuTLS not found, disabling ssl decryption" + tls_message="no" + fi + ] +) + + # Check for xsltproc AC_PATH_PROG(XSLTPROC, xsltproc) AC_CHECK_PROG(HAVE_XSLTPROC, xsltproc, "yes", "no") @@ -1391,3 +1409,4 @@ echo " Use GNU ADNS library : $adns_message" echo " Use SSL crypto library : $ssl_message" echo " Use IPv6 name resolution : $enable_ipv6" echo " Use UCD SNMP/Net-SNMP library : $snmp_libs_message" +echo " Use gnutls library : $tls_message" diff --git a/epan/dissectors/packet-ssl-utils.c b/epan/dissectors/packet-ssl-utils.c new file mode 100644 index 0000000000..be6b363f58 --- /dev/null +++ b/epan/dissectors/packet-ssl-utils.c @@ -0,0 +1,1265 @@ +/* packet-ssl-utils.c + * + * $Id$ + * + * ssl manipulation functions + * By Paolo Abeni <paolo.abeni@email.com> + * + * This program 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; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "packet-ssl-utils.h" + +#ifdef HAVE_LIBGNUTLS + +/* hmac abstraction layer */ +#define SSL_HMAC gcry_md_hd_t + +inline void ssl_hmac_init(SSL_HMAC* md, const void * key, int len, int algo) +{ + if (*(md)) + gcry_md_close(*(md)); + gcry_md_open(md,algo, GCRY_MD_FLAG_HMAC); + gcry_md_setkey (*(md), key, len); +} + +inline void ssl_hmac_update(SSL_HMAC* md, const void* data, int len) +{ + gcry_md_write(*(md), data, len); +} +inline void ssl_hmac_final(SSL_HMAC* md, unsigned char* data, unsigned int* datalen) +{ + int algo = gcry_md_get_algo (*(md)); + unsigned int len = gcry_md_get_algo_dlen(algo); + memcpy(data, gcry_md_read(*(md), algo), len); + *datalen =len; +} +inline void ssl_hmac_cleanup(SSL_HMAC* md) { gcry_md_close(*(md)); } + +/* memory digest abstraction layer*/ +#define SSL_MD gcry_md_hd_t + +inline void ssl_md_init(SSL_MD* md, int algo) +{ + if (*(md)) + gcry_md_close(*(md)); + gcry_md_open(md,algo, 0); +} +inline void ssl_md_update(SSL_MD* md, unsigned char* data, int len) +{ + gcry_md_write(*(md), data, len); +} +inline void ssl_md_final(SSL_MD* md, unsigned char* data, unsigned int* datalen) +{ + int algo = gcry_md_get_algo (*(md)); + int len = gcry_md_get_algo_dlen (algo); + memcpy(data, gcry_md_read(*(md), algo), len); + *datalen = len; +} +inline void ssl_md_cleanup(SSL_MD* md) { gcry_md_close(*(md)); } + +/* md5 /sha abstraction layer */ +#define SSL_SHA_CTX gcry_md_hd_t +#define SSL_MD5_CTX gcry_md_hd_t + +void ssl_sha_init(SSL_SHA_CTX* md) +{ + if (*(md)) + gcry_md_close(*(md)); + gcry_md_open(md,GCRY_MD_SHA1, 0); +} +inline void ssl_sha_update(SSL_SHA_CTX* md, unsigned char* data, int len) +{ + gcry_md_write(*(md), data, len); +} +inline void ssl_sha_final(unsigned char* buf, SSL_SHA_CTX* md) +{ + memcpy(buf, gcry_md_read(*(md), GCRY_MD_SHA1), + gcry_md_get_algo_dlen(GCRY_MD_SHA1)); +} + +inline int ssl_md5_init(SSL_MD5_CTX* md) +{ + if (*(md)) + gcry_md_close(*(md)); + return gcry_md_open(md,GCRY_MD_MD5, 0); +} +inline void ssl_md5_update(SSL_MD5_CTX* md, unsigned char* data, int len) +{ + gcry_md_write(*(md), data, len); +} +inline void ssl_md5_final(unsigned char* buf, SSL_MD5_CTX* md) +{ + memcpy(buf, gcry_md_read(*(md), GCRY_MD_MD5), + gcry_md_get_algo_dlen(GCRY_MD_MD5)); +} + + +/* stream cipher abstraction layer*/ +int ssl_cipher_init(gcry_cipher_hd_t *cipher, int algo, unsigned char* sk, + unsigned char* iv, int mode) +{ + int gcry_modes[]={ + GCRY_CIPHER_MODE_STREAM, + GCRY_CIPHER_MODE_CBC + }; + int err = gcry_cipher_open(cipher, algo, gcry_modes[mode], 0); + if (err !=0) + return -1; + err = gcry_cipher_setkey(*(cipher), sk, gcry_cipher_get_algo_keylen (algo)); + if (err != 0) + return -1; + err = gcry_cipher_setiv(*(cipher), iv, gcry_cipher_get_algo_blklen (algo)); + if (err != 0) + return -1; + return 0; +} +inline int ssl_cipher_decrypt(gcry_cipher_hd_t *cipher, unsigned char * out, int outl, + const unsigned char * in,int inl) +{ + return gcry_cipher_decrypt ( *(cipher), out, outl, in, inl); +} +inline int ssl_get_digest_by_name(const char*name) +{ + return gcry_md_map_name(name); +} +inline int ssl_get_cipher_by_name(const char* name) +{ + return gcry_cipher_map_name(name); +} + +/* private key abstraction layer */ +inline int ssl_get_key_len(SSL_PRIVATE_KEY* pk) {return gcry_pk_get_nbits (pk); } + +gcry_err_code_t +_gcry_rsa_decrypt (int algo, gcry_mpi_t *result, gcry_mpi_t *data, + gcry_mpi_t *skey, int flags); + +#define PUBKEY_FLAG_NO_BLINDING (1 << 0) + +/* decrypt data with private key. Store decrypted data directly into input + * buffer */ +int ssl_private_decrypt(unsigned int len, unsigned char* encr_data, SSL_PRIVATE_KEY* pk) +{ + int rc; + size_t decr_len = 0; + gcry_sexp_t s_data, s_plain; + gcry_mpi_t encr_mpi; + size_t i, encr_len = len; + unsigned char* decr_data_ptr; + gcry_mpi_t text=NULL; + + /* build up a mpi rappresentation for encrypted data */ + rc = gcry_mpi_scan(&encr_mpi, GCRYMPI_FMT_USG,encr_data, encr_len, &encr_len); + if (rc != 0 ) { + ssl_debug_printf("pcry_private_decrypt: can't convert encr_data to mpi (size %d):%s\n", + len, gcry_strerror(rc)); + return 0; + } + +#ifndef SSL_FAST + /* put the data into a simple list */ + rc = gcry_sexp_build(&s_data, NULL, "(enc-val(rsa(a%m)))", encr_mpi); + if (rc != 0) { + ssl_debug_printf("pcry_private_decrypt: can't build encr_sexp:%s \n", + gcry_strerror(rc)); + return 0; + } + + /* pass it to libgcrypt */ + rc = gcry_pk_decrypt(&s_plain, s_data, pk); + gcry_sexp_release(s_data); + if (rc != 0) + { + ssl_debug_printf("pcry_private_decrypt: can't decrypt key:%s\n", + gcry_strerror(rc)); + goto out; + } + + /* convert plain text sexp to mpi format */ + text = gcry_sexp_nth_mpi(s_plain, 0, 0); + + /* compute size requested for plaintext buffer */ + decr_len = len; + if (gcry_mpi_print(GCRYMPI_FMT_USG, NULL, decr_len, &decr_len, text) != 0) { + ssl_debug_printf("pcry_private_decrypt: can't compute decr size:%s\n", + gcry_strerror(rc)); + decr_len = 0; + goto out; + } + + /* sanity check on out buffer */ + if (decr_len > len) { + ssl_debug_printf("pcry_private_decrypt: decrypted data is too long ?!? (%d max %d)\n", + decr_len, len); + return 0; + } + + /* write plain text to encrypted data buffer */ + decr_data_ptr = encr_data; + if (gcry_mpi_print( GCRYMPI_FMT_USG, decr_data_ptr, decr_len, &decr_len, + text) != 0) { + ssl_debug_printf("pcry_private_decrypt: can't print decr data to mpi (size %d):%s\n", + decr_len, gcry_strerror(rc)); + g_free(decr_data_ptr); + decr_len = 0; + goto out; + } + + /* strip the padding*/ + rc = 0; + for (i = 1; i < decr_len; i++) { + if (decr_data_ptr[i] == 0) { + rc = i+1; + break; + } + } + + ssl_debug_printf("pcry_private_decrypt: stripping %d bytes, decr_len %d\n", + rc, decr_len); + ssl_print_data("decypted_unstrip_pre_master", decr_data_ptr, decr_len); + g_memmove(decr_data_ptr, &decr_data_ptr[rc], decr_len - rc); + decr_len -= rc; + +out: + gcry_sexp_release(s_plain); +#else + rc = _gcry_rsa_decrypt(0, &text, &encr_mpi, pk,0); + gcry_mpi_print( GCRYMPI_FMT_USG, 0, 0, &decr_len, text); + + /* sanity check on out buffer */ + if (decr_len > len) { + ssl_debug_printf("pcry_private_decrypt: decrypted data is too long ?!? (%d max %d)\n", + decr_len, len); + return 0; + } + + /* write plain text to newly allocated buffer */ + decr_data_ptr = encr_data; + if (gcry_mpi_print( GCRYMPI_FMT_USG, decr_data_ptr, decr_len, &decr_len, + text) != 0) { + ssl_debug_printf("pcry_private_decrypt: can't print decr data to mpi (size %d):%s\n", + decr_len, gcry_strerror(rc)); + return 0; + } + + /* strip the padding*/ + rc = 0; + for (i = 1; i < decr_len; i++) { + if (decr_data_ptr[i] == 0) { + rc = i+1; + break; + } + } + + ssl_debug_printf("pcry_private_decrypt: stripping %d bytes, decr_len %d\n", + rc, decr_len); + ssl_print_data("decypted_unstrip_pre_master", decr_data_ptr, decr_len); + g_memmove(decr_data_ptr, &decr_data_ptr[rc], decr_len - rc); + decr_len -= rc; +#endif + gcry_mpi_release(text); + return decr_len; +} + + +#define PRF(ssl,secret,usage,rnd1,rnd2,out) ((ssl->version_netorder==SSLV3_VERSION)? \ + ssl3_prf(secret,usage,rnd1,rnd2,out): \ + tls_prf(secret,usage,rnd1,rnd2,out)) + +static const char *digests[]={ + "MD5", + "SHA1" +}; + +static const char *ciphers[]={ + "DES", + "DES3", + "ARCFOUR", /* gnutls does not support rc4, but this should be 100% compatible*/ + "RC2", + "IDEA", + "AES", + "AES256" +}; + +/* look in openssl/ssl/ssl_lib.c for a complete list of available cipersuite*/ +static SslCipherSuite cipher_suites[]={ + {1,KEX_RSA,SIG_RSA,ENC_NULL,0,0,0,DIG_MD5,16,0, SSL_CIPHER_MODE_STREAM}, + {2,KEX_RSA,SIG_RSA,ENC_NULL,0,0,0,DIG_SHA,20,0, SSL_CIPHER_MODE_STREAM}, + {3,KEX_RSA,SIG_RSA,ENC_RC4,1,128,40,DIG_MD5,16,1, SSL_CIPHER_MODE_STREAM}, + {4,KEX_RSA,SIG_RSA,ENC_RC4,1,128,128,DIG_MD5,16,0, SSL_CIPHER_MODE_STREAM}, + {5,KEX_RSA,SIG_RSA,ENC_RC4,1,128,128,DIG_SHA,20,0, SSL_CIPHER_MODE_STREAM}, + {6,KEX_RSA,SIG_RSA,ENC_RC2,8,128,40,DIG_SHA,20,1, SSL_CIPHER_MODE_STREAM}, + {7,KEX_RSA,SIG_RSA,ENC_IDEA,8,128,128,DIG_SHA,20,0, SSL_CIPHER_MODE_STREAM}, + {8,KEX_RSA,SIG_RSA,ENC_DES,8,64,40,DIG_SHA,20,1, SSL_CIPHER_MODE_STREAM}, + {9,KEX_RSA,SIG_RSA,ENC_DES,8,64,64,DIG_SHA,20,0, SSL_CIPHER_MODE_STREAM}, + {10,KEX_RSA,SIG_RSA,ENC_3DES,8,192,192,DIG_SHA,20,0, SSL_CIPHER_MODE_STREAM}, + {11,KEX_DH,SIG_DSS,ENC_DES,8,64,40,DIG_SHA,20,1, SSL_CIPHER_MODE_STREAM}, + {12,KEX_DH,SIG_DSS,ENC_DES,8,64,64,DIG_SHA,20,0, SSL_CIPHER_MODE_STREAM}, + {13,KEX_DH,SIG_DSS,ENC_3DES,8,192,192,DIG_SHA,20,0, SSL_CIPHER_MODE_STREAM}, + {14,KEX_DH,SIG_RSA,ENC_DES,8,64,40,DIG_SHA,20,1, SSL_CIPHER_MODE_STREAM}, + {15,KEX_DH,SIG_RSA,ENC_DES,8,64,64,DIG_SHA,20,0, SSL_CIPHER_MODE_STREAM}, + {16,KEX_DH,SIG_RSA,ENC_3DES,8,192,192,DIG_SHA,20,0, SSL_CIPHER_MODE_STREAM}, + {17,KEX_DH,SIG_DSS,ENC_DES,8,64,40,DIG_SHA,20,1, SSL_CIPHER_MODE_STREAM}, + {18,KEX_DH,SIG_DSS,ENC_DES,8,64,64,DIG_SHA,20,0, SSL_CIPHER_MODE_STREAM}, + {19,KEX_DH,SIG_DSS,ENC_3DES,8,192,192,DIG_SHA,20,0, SSL_CIPHER_MODE_STREAM}, + {20,KEX_DH,SIG_RSA,ENC_DES,8,64,40,DIG_SHA,20,1, SSL_CIPHER_MODE_STREAM}, + {21,KEX_DH,SIG_RSA,ENC_DES,8,64,64,DIG_SHA,20,0, SSL_CIPHER_MODE_STREAM}, + {22,KEX_DH,SIG_RSA,ENC_3DES,8,192,192,DIG_SHA,20,0, SSL_CIPHER_MODE_STREAM}, + {23,KEX_DH,SIG_NONE,ENC_RC4,1,128,40,DIG_MD5,16,1, SSL_CIPHER_MODE_STREAM}, + {24,KEX_DH,SIG_NONE,ENC_RC4,1,128,128,DIG_MD5,16,0, SSL_CIPHER_MODE_STREAM}, + {25,KEX_DH,SIG_NONE,ENC_DES,8,64,40,DIG_MD5,16,1, SSL_CIPHER_MODE_STREAM}, + {26,KEX_DH,SIG_NONE,ENC_DES,8,64,64,DIG_MD5,16,0, SSL_CIPHER_MODE_STREAM}, + {27,KEX_DH,SIG_NONE,ENC_3DES,8,192,192,DIG_MD5,16,0, SSL_CIPHER_MODE_STREAM}, + {47,KEX_RSA,SIG_RSA,ENC_AES,16,128,128,DIG_SHA,20,0, SSL_CIPHER_MODE_CBC}, + {53,KEX_RSA,SIG_RSA,ENC_AES256,16,256,256,DIG_SHA,20,0, SSL_CIPHER_MODE_CBC}, + {96,KEX_RSA,SIG_RSA,ENC_RC4,1,128,56,DIG_MD5,16,1, SSL_CIPHER_MODE_STREAM}, + {97,KEX_RSA,SIG_RSA,ENC_RC2,1,128,56,DIG_MD5,16,1, SSL_CIPHER_MODE_STREAM}, + {98,KEX_RSA,SIG_RSA,ENC_DES,8,64,64,DIG_SHA,20,1, SSL_CIPHER_MODE_STREAM}, + {99,KEX_DH,SIG_DSS,ENC_DES,8,64,64,DIG_SHA,16,1, SSL_CIPHER_MODE_STREAM}, + {100,KEX_RSA,SIG_RSA,ENC_RC4,1,128,56,DIG_SHA,20,1, SSL_CIPHER_MODE_STREAM}, + {101,KEX_DH,SIG_DSS,ENC_RC4,1,128,56,DIG_SHA,20,1, SSL_CIPHER_MODE_STREAM}, + {102,KEX_DH,SIG_DSS,ENC_RC4,1,128,128,DIG_SHA,20,0, SSL_CIPHER_MODE_STREAM}, + {-1, 0,0,0,0,0,0,0,0,0, 0} +}; + +#define MAX_BLOCK_SIZE 16 +#define MAX_KEY_SIZE 32 + +int ssl_find_cipher(int num,SslCipherSuite* cs) +{ + SslCipherSuite *c; + + for(c=cipher_suites;c->number!=-1;c++){ + if(c->number==num){ + *cs=*c; + return 0; + } + } + + return -1; +} + +static int tls_hash(StringInfo* secret, + StringInfo* seed, int md, StringInfo* out) +{ + guint8 *ptr=out->data; + unsigned int left=out->data_len; + int tocpy; + guint8 *A; + guint8 _A[20],tmp[20]; + unsigned int A_l,tmp_l; + SSL_HMAC hm; + + memset(&hm, 0, sizeof(hm)); + ssl_print_string("tls_hash: hash secret", secret); + ssl_print_string("tls_hash: hash seed", seed); + A=seed->data; + A_l=seed->data_len; + + while(left){ + ssl_hmac_init(&hm,secret->data,secret->data_len,md); + ssl_hmac_update(&hm,A,A_l); + ssl_hmac_final(&hm,_A,&A_l); + A=_A; + + ssl_hmac_init(&hm,secret->data,secret->data_len,md); + ssl_hmac_update(&hm,A,A_l); + ssl_hmac_update(&hm,seed->data,seed->data_len); + ssl_hmac_final(&hm,tmp,&tmp_l); + + tocpy=MIN(left,tmp_l); + memcpy(ptr,tmp,tocpy); + ptr+=tocpy; + left-=tocpy; + } + + ssl_hmac_cleanup(&hm); + ssl_print_string("hash out", out); + return (0); +} + +static int tls_prf(StringInfo* secret, const char *usage, + StringInfo* rnd1, StringInfo* rnd2, StringInfo* out) +{ + StringInfo seed, sha_out, md5_out; + guint8 *ptr; + StringInfo s1, s2; + unsigned int i,s_l, r=-1; + int usage_len = strlen(usage); + + /* initalize buffer for sha, md5 random seed*/ + if (ssl_data_alloc(&sha_out, MAX(out->data_len,20)) < 0) + return -1; + if (ssl_data_alloc(&md5_out, MAX(out->data_len,16)) < 0) + goto free_sha; + if (ssl_data_alloc(&seed, usage_len+rnd1->data_len+rnd2->data_len) < 0) + goto free_md5; + + ptr=seed.data; + memcpy(ptr,usage,usage_len); ptr+=usage_len; + memcpy(ptr,rnd1->data,rnd1->data_len); ptr+=rnd1->data_len; + memcpy(ptr,rnd2->data,rnd2->data_len); ptr+=rnd2->data_len; + + /* initalize buffer for client/server seeds*/ + s_l=secret->data_len/2 + secret->data_len%2; + if (ssl_data_alloc(&s1, s_l) < 0) + goto free_seed; + if (ssl_data_alloc(&s2, s_l) < 0) + goto free_s1; + + memcpy(s1.data,secret->data,s_l); + memcpy(s2.data,secret->data + (secret->data_len - s_l),s_l); + + ssl_debug_printf("tls_prf: tls_hash(md5 secret_len %d seed_len %d )\n", s1.data_len, seed.data_len); + if(tls_hash(&s1,&seed,ssl_get_digest_by_name("MD5"),&md5_out) != 0) + goto free_all; + ssl_debug_printf("tls_prf: tls_hash(sha)\n"); + if(tls_hash(&s2,&seed,ssl_get_digest_by_name("SHA1"),&sha_out) != 0) + goto free_all; + + for(i=0;i<out->data_len;i++) + out->data[i]=md5_out.data[i] ^ sha_out.data[i]; + r =0; + + ssl_print_string("PRF out",out); +free_all: + free(s2.data); +free_s1: + free(s1.data); +free_seed: + free(seed.data); +free_md5: + free(md5_out.data); +free_sha: + free(sha_out.data); + return r; +} + +static int ssl3_generate_export_iv(StringInfo* r1, + StringInfo* r2, StringInfo* out) +{ + SSL_MD5_CTX md5; + guint8 tmp[16]; + + ssl_md5_init(&md5); + ssl_md5_update(&md5,r1->data,r1->data_len); + ssl_md5_update(&md5,r2->data,r2->data_len); + ssl_md5_final(tmp,&md5); + + memcpy(out->data,tmp,out->data_len); + ssl_print_string("export iv", out); + + return(0); +} + +static int ssl3_prf(StringInfo* secret, const char* usage, + StringInfo* r1, + StringInfo* r2,StringInfo* out) +{ + SSL_MD5_CTX md5; + SSL_SHA_CTX sha; + StringInfo *rnd1,*rnd2; + unsigned int off; + int i=0,j; + guint8 buf[20]; + + rnd1=r1; rnd2=r2; + + ssl_md5_init(&md5); + memset(&sha,0,sizeof(sha)); + ssl_sha_init(&sha); + + for(off=0;off<out->data_len;off+=16){ + unsigned char outbuf[16]; + int tocpy; + i++; + + ssl_debug_printf("ssl3_prf: sha1_update(%d)\n",i); + /* A, BB, CCC, ... */ + for(j=0;j<i;j++){ + buf[j]=64+i; + } + + ssl_sha_update(&sha,buf,i); + if (secret) + ssl_sha_update(&sha,secret->data,secret->data_len); + + if(!strcmp(usage,"client write key") || !strcmp(usage,"server write key")){ + ssl_sha_update(&sha,rnd2->data,rnd2->data_len); + ssl_sha_update(&sha,rnd1->data,rnd1->data_len); + } + else{ + ssl_sha_update(&sha,rnd1->data,rnd1->data_len); + ssl_sha_update(&sha,rnd2->data,rnd2->data_len); + } + + ssl_sha_final(buf,&sha); + + ssl_sha_init(&sha); + + ssl_debug_printf("ssl3_prf: md5_update(%d)\n",i); + ssl_md5_update(&md5,secret->data,secret->data_len); + ssl_md5_update(&md5,buf,20); + ssl_md5_final(outbuf,&md5); + tocpy=MIN(out->data_len-off,16); + memcpy(out->data+off,outbuf,tocpy); + + ssl_md5_init(&md5); + } + + return(0); +} + +int ssl_create_decoder(SslDecoder *dec, SslCipherSuite *cipher_suite, + guint8 *mk, guint8 *sk, guint8 *iv) +{ + int ciph=0; + + /* Find the SSLeay cipher */ + if(cipher_suite->enc!=ENC_NULL) { + ssl_debug_printf("ssl_create_decoder CIPHER: %s\n", ciphers[cipher_suite->enc-0x30]); + ciph=ssl_get_cipher_by_name(ciphers[cipher_suite->enc-0x30]); + } + if (ciph == 0) { + ssl_debug_printf("ssl_create_decoder can't find cipher %s\n", + ciphers[cipher_suite->enc-0x30]); + return -1; + } + + /* init mac buffer: mac storage is embedded into decoder struct to save a + memory allocation and waste samo more memory*/ + dec->cipher_suite=cipher_suite; + dec->mac_key.data = dec->_mac_key; + ssl_data_set(&dec->mac_key, mk, cipher_suite->dig_len); + + if (ssl_cipher_init(&dec->evp,ciph,sk,iv,cipher_suite->mode) < 0) { + ssl_debug_printf("ssl_create_decoder: can't create cipher id:%d mode:%d\n", + ciph, cipher_suite->mode); + return -1; + } + + ssl_debug_printf("decoder initialized (digest len %d)\n", cipher_suite->dig_len); + return 0; +} + +int ssl_generate_keyring_material(SslDecryptSession*ssl_session) +{ + StringInfo key_block; + guint8 _iv_c[MAX_BLOCK_SIZE],_iv_s[MAX_BLOCK_SIZE]; + guint8 _key_c[MAX_KEY_SIZE],_key_s[MAX_KEY_SIZE]; + int needed; + guint8 *ptr,*c_wk,*s_wk,*c_mk,*s_mk,*c_iv = _iv_c,*s_iv = _iv_s; + + /* if master_key is not yet generate, create it now*/ + if (!(ssl_session->state & SSL_MASTER_SECRET)) { + ssl_debug_printf("ssl_generate_keyring_material:PRF(pre_master_secret)\n"); + if (PRF(ssl_session,&ssl_session->pre_master_secret,"master secret", + &ssl_session->client_random, + &ssl_session->server_random, &ssl_session->master_secret)) { + ssl_debug_printf("ssl_generate_keyring_material can't generate master_secret\n"); + return -1; + } + ssl_print_string("master secret",&ssl_session->master_secret); + } + + /* Compute the key block. First figure out how much data we need*/ + needed=ssl_session->cipher_suite.dig_len*2; + needed+=ssl_session->cipher_suite.bits / 4; + if(ssl_session->cipher_suite.block>1) + needed+=ssl_session->cipher_suite.block*2; + + key_block.data_len = needed; + key_block.data = g_malloc(needed); + if (!key_block.data) { + ssl_debug_printf("ssl_generate_keyring_material can't allacate key_block\n"); + return -1; + } + ssl_debug_printf("ssl_generate_keyring_material sess key generation\n"); + if (PRF(ssl_session,&ssl_session->master_secret,"key expansion", + &ssl_session->server_random,&ssl_session->client_random, + &key_block)) { + ssl_debug_printf("ssl_generate_keyring_material can't generate key_block\n"); + goto fail; + } + ssl_print_string("key expansion", &key_block); + + ptr=key_block.data; + c_mk=ptr; ptr+=ssl_session->cipher_suite.dig_len; + s_mk=ptr; ptr+=ssl_session->cipher_suite.dig_len; + + c_wk=ptr; ptr+=ssl_session->cipher_suite.eff_bits/8; + s_wk=ptr; ptr+=ssl_session->cipher_suite.eff_bits/8; + + if(ssl_session->cipher_suite.block>1){ + c_iv=ptr; ptr+=ssl_session->cipher_suite.block; + s_iv=ptr; ptr+=ssl_session->cipher_suite.block; + } + + if(ssl_session->cipher_suite.export){ + StringInfo iv_c,iv_s; + StringInfo key_c,key_s; + StringInfo k; + + if(ssl_session->cipher_suite.block>1){ + + /* We only have room for MAX_BLOCK_SIZE bytes IVs, but that's + all we should need. This is a sanity check */ + if(ssl_session->cipher_suite.block>MAX_BLOCK_SIZE) { + ssl_debug_printf("ssl_generate_keyring_material cipher suite block must be at most %d nut is %d\n", + MAX_BLOCK_SIZE, ssl_session->cipher_suite.block); + goto fail; + } + + iv_c.data = _iv_c; + iv_c.data_len = ssl_session->cipher_suite.block; + iv_s.data = _iv_s; + iv_s.data_len = ssl_session->cipher_suite.block; + + if(ssl_session->version_netorder==SSLV3_VERSION){ + ssl_debug_printf("ssl_generate_keyring_material ssl3_generate_export_iv\n"); + if (ssl3_generate_export_iv(&ssl_session->client_random, + &ssl_session->server_random,&iv_c)) { + ssl_debug_printf("ssl_generate_keyring_material can't generate sslv3 client iv\n"); + goto fail; + } + ssl_debug_printf("ssl_generate_keyring_material ssl3_generate_export_iv(2)\n"); + if (ssl3_generate_export_iv(&ssl_session->server_random, + &ssl_session->client_random,&iv_s)) { + ssl_debug_printf("ssl_generate_keyring_material can't generate sslv3 server iv\n"); + goto fail; + } + } + else{ + guint8 _iv_block[MAX_BLOCK_SIZE * 2]; + StringInfo iv_block; + StringInfo key_null; + guint8 _key_null; + + key_null.data = &_key_null; + key_null.data_len = 0; + + iv_block.data = _iv_block; + iv_block.data_len = ssl_session->cipher_suite.block*2; + + ssl_debug_printf("ssl_generate_keyring_material prf(iv_block)\n"); + if(PRF(ssl_session,&key_null, "IV block", + &ssl_session->client_random, + &ssl_session->server_random,&iv_block)) { + ssl_debug_printf("ssl_generate_keyring_material can't generate tls31 iv block\n"); + goto fail; + } + + memcpy(_iv_c,iv_block.data,ssl_session->cipher_suite.block); + memcpy(_iv_s,iv_block.data+ssl_session->cipher_suite.block, + ssl_session->cipher_suite.block); + } + + c_iv=_iv_c; + s_iv=_iv_s; + } + + if (ssl_session->version_netorder==SSLV3_VERSION){ + + SSL_MD5_CTX md5; + ssl_debug_printf("ssl_generate_keyring_material MD5(client_random)\n"); + ssl_md5_init(&md5); + ssl_md5_update(&md5,c_wk,ssl_session->cipher_suite.eff_bits/8); + ssl_md5_update(&md5,ssl_session->client_random.data, + ssl_session->client_random.data_len); + ssl_md5_update(&md5,ssl_session->server_random.data, + ssl_session->server_random.data_len); + ssl_md5_final(_key_c,&md5); + c_wk=_key_c; + + ssl_md5_init(&md5); + ssl_debug_printf("ssl_generate_keyring_material MD5(server_random)\n"); + ssl_md5_update(&md5,s_wk,ssl_session->cipher_suite.eff_bits/8); + ssl_md5_update(&md5,ssl_session->server_random.data, + ssl_session->server_random.data_len); + ssl_md5_update(&md5,ssl_session->client_random.data, + ssl_session->client_random.data_len); + ssl_md5_final(_key_s,&md5); + s_wk=_key_s; + } + else{ + key_c.data = _key_c; + key_c.data_len = sizeof(_key_c); + key_s.data = _key_s; + key_s.data_len = sizeof(_key_s); + + k.data = c_wk; + k.data_len = ssl_session->cipher_suite.eff_bits/8; + ssl_debug_printf("ssl_generate_keyring_material PRF(key_c)\n"); + if (PRF(ssl_session,&k,"client write key", + &ssl_session->client_random, + &ssl_session->server_random, &key_c)) { + ssl_debug_printf("ssl_generate_keyring_material can't generate tll31 server key \n"); + goto fail; + } + c_wk=_key_c; + + k.data = s_wk; + k.data_len = ssl_session->cipher_suite.eff_bits/8; + ssl_debug_printf("ssl_generate_keyring_material PRF(key_s)\n"); + if(PRF(ssl_session,&k,"server write key", + &ssl_session->client_random, + &ssl_session->server_random, &key_s)) { + ssl_debug_printf("ssl_generate_keyring_material can't generate tll31 client key \n"); + goto fail; + } + s_wk=_key_s; + } + } + + /* show key material info */ + ssl_print_data("Client MAC key",c_mk,ssl_session->cipher_suite.dig_len); + ssl_print_data("Server MAC key",s_mk,ssl_session->cipher_suite.dig_len); + ssl_print_data("Client Write key",c_wk,ssl_session->cipher_suite.bits/8); + ssl_print_data("Server Write key",s_wk,ssl_session->cipher_suite.bits/8); + + if(ssl_session->cipher_suite.block>1) { + ssl_print_data("Client Write IV",c_iv,ssl_session->cipher_suite.block); + ssl_print_data("Server Write IV",s_iv,ssl_session->cipher_suite.block); + } + else { + ssl_print_data("Client Write IV",c_iv,8); + ssl_print_data("Server Write IV",s_iv,8); + } + + /* create both client and server ciphers*/ + ssl_debug_printf("ssl_generate_keyring_material ssl_create_decoder(client)\n"); + if (ssl_create_decoder(&ssl_session->client, + &ssl_session->cipher_suite,c_mk,c_wk,c_iv)) { + ssl_debug_printf("ssl_generate_keyring_material can't init client decoder\n"); + goto fail; + } + ssl_debug_printf("ssl_generate_keyring_material ssl_create_decoder(server)\n"); + if (ssl_create_decoder(&ssl_session->server, + &ssl_session->cipher_suite,s_mk,s_wk,s_iv)) { + ssl_debug_printf("ssl_generate_keyring_material can't init client decoder\n"); + goto fail; + } + + g_free(key_block.data); + return 0; + +fail: + free(key_block.data); + return -1; +} + +int ssl_decrypt_pre_master_secret(SslDecryptSession*ssl_session, + StringInfo* entrypted_pre_master, SSL_PRIVATE_KEY *pk) +{ + int i; + + if(ssl_session->cipher_suite.kex!=KEX_RSA) { + ssl_debug_printf("ssl_decrypt_pre_master_secret key %d diferent from KEX_RSA(%d)\n", + ssl_session->cipher_suite.kex, KEX_RSA); + return(-1); + } + +#if 0 + /* can't find any place where ephemeral_rsa is set ...*/ + if(d->ephemeral_rsa) { + ssl_debug_printf("ssl_decrypt_pre_master_secret ephimeral RSA\n"); + return(-1); + } +#endif + + /* with tls key loading will fail if not rsa type, so no need to check*/ + ssl_print_string("pre master encrypted",entrypted_pre_master); + ssl_debug_printf("ssl_decrypt_pre_master_secret:RSA_private_decrypt\n"); + i=ssl_private_decrypt(entrypted_pre_master->data_len, + entrypted_pre_master->data, pk); + + if (i!=48) { + ssl_debug_printf("ssl_decrypt_pre_master_secret wrong " + "pre_master_secret lenght (%d, expected %d)\n", i, 48); + return -1; + } + + /* the decrypted data has been written into the pre_master key buffer */ + ssl_session->pre_master_secret.data = entrypted_pre_master->data; + ssl_session->pre_master_secret.data_len=48; + ssl_print_string("pre master secret",&ssl_session->pre_master_secret); + + /* Remove the master secret if it was there. + This force keying material regeneration in + case we're renegotiating */ + ssl_session->state &= ~SSL_MASTER_SECRET; + return 0; +} + +#define MSB(a) ((a>>8)&0xff) +#define LSB(a) (a&0xff) + +/* convert network byte order 32 byte number to right-aligned host byte order * + * 8 bytes buffer */ +static int fmt_seq(guint32 num, guint8* buf) +{ + guint32 netnum; + + memset(buf,0,8); + netnum=g_htonl(num); + memcpy(buf+4,&netnum,4); + + return(0); +} + +static int tls_check_mac(SslDecoder*decoder, int ct,int ver, guint8* data, + guint32 datalen, guint8* mac) +{ + SSL_HMAC hm; + int md; + guint32 l; + guint8 buf[20]; + + memset(&hm, 0, sizeof(hm)); + md=ssl_get_digest_by_name(digests[decoder->cipher_suite->dig-0x40]); + ssl_debug_printf("tls_check_mac mac type:%s md %d\n", + digests[decoder->cipher_suite->dig-0x40], md); + + ssl_hmac_init(&hm,decoder->mac_key.data,decoder->mac_key.data_len,md); + + fmt_seq(decoder->seq,buf); + decoder->seq++; + ssl_hmac_update(&hm,buf,8); + + buf[0]=ct; + ssl_hmac_update(&hm,buf,1); + + buf[0]=MSB(ver); + buf[1]=LSB(ver); + ssl_hmac_update(&hm,buf,2); + + buf[0]=MSB(datalen); + buf[1]=LSB(datalen); + ssl_hmac_update(&hm,buf,2); + + ssl_hmac_update(&hm,data,datalen); + + ssl_hmac_final(&hm,buf,&l); + ssl_print_data("Mac", buf, l); + if(memcmp(mac,buf,l)) + return -1; + + ssl_hmac_cleanup(&hm); + return(0); +} + +int ssl3_check_mac(SslDecoder*decoder,int ct,guint8* data, + guint32 datalen, guint8* mac) +{ + SSL_MD mc; + int md; + guint32 l; + guint8 buf[64],dgst[20]; + int pad_ct; + + pad_ct=(decoder->cipher_suite->dig==DIG_SHA)?40:48; + + /* get cipher used for digest comptuation */ + md=ssl_get_digest_by_name(digests[decoder->cipher_suite->dig-0x40]); + ssl_debug_printf("ssl3_check_mac digest%s md %d\n", + digests[decoder->cipher_suite->dig-0x40], md); + memset(&mc, 0, sizeof(mc)); + ssl_md_init(&mc,md); + ssl_debug_printf("ssl3_check_mac memory digest %p\n",mc); + + /* do hash computation on data && padding */ + ssl_md_update(&mc,decoder->mac_key.data,decoder->mac_key.data_len); + + memset(buf,0x36,pad_ct); + ssl_md_update(&mc,buf,pad_ct); + + fmt_seq(decoder->seq,buf); + decoder->seq++; + ssl_md_update(&mc,buf,8); + + buf[0]=ct; + ssl_md_update(&mc,buf,1); + + buf[0]=MSB(datalen); + buf[1]=LSB(datalen); + ssl_md_update(&mc,buf,2); + ssl_md_update(&mc,data,datalen); + + ssl_md_final(&mc,dgst,&l); + + ssl_md_init(&mc,md); + + ssl_md_update(&mc,decoder->mac_key.data,decoder->mac_key.data_len); + + memset(buf,0x5c,pad_ct); + ssl_md_update(&mc,buf,pad_ct); + ssl_md_update(&mc,dgst,l); + + ssl_md_final(&mc,dgst,&l); + + if(memcmp(mac,dgst,l)) + return -1; + + return(0); +} + +int ssl_decrypt_record(SslDecryptSession*ssl,SslDecoder* decoder, int ct, + const unsigned char* in, int inl,unsigned char*out,int* outl) +{ + int pad, worklen; + guint8 *mac; + + ssl_debug_printf("ssl_decrypt_record ciphertext len %d\n", inl); + ssl_print_data("Ciphertext",in, inl); + + /* First decrypt*/ + if ((pad = ssl_cipher_decrypt(&decoder->evp,out,*outl,in,inl))!= 0) + ssl_debug_printf("ssl_decrypt_record: %s %s\n", gcry_strsource (pad), + gcry_strerror (pad)); + + ssl_print_data("Plaintext",out,inl); + worklen=inl; + + /* Now strip off the padding*/ + if(decoder->cipher_suite->block!=1){ + pad=out[inl-1]; + worklen-=(pad+1); + ssl_debug_printf("ssl_decrypt_record found padding %d final len %d\n", + pad, *outl); + } + + /* And the MAC */ + worklen-=decoder->cipher_suite->dig_len; + if (worklen < 0) + { + ssl_debug_printf("ssl_decrypt_record wrong record len/padding outlen %d\n work %d\n",*outl, worklen); + return -1; + } + mac=out+worklen; + /*ssl_print_data("Record data",out,*outl);*/ + + /* Now check the MAC */ + ssl_debug_printf("checking mac (len %d, version %X, ct %d)\n", worklen,ssl->version_netorder, ct); + if(ssl->version_netorder==0x300){ + if(ssl3_check_mac(decoder,ct,out,worklen,mac) < 0) { + ssl_debug_printf("ssl_decrypt_record: mac falied\n"); + return -1; + } + } + else{ + if(tls_check_mac(decoder,ct,ssl->version_netorder,out,worklen,mac)< 0) { + ssl_debug_printf("ssl_decrypt_record: mac falied\n"); + return -1; + } + } + ssl_debug_printf("ssl_decrypt_record: mac ok\n"); + *outl = worklen; + return(0); +} + +/* old relase of gnutls does not define the appropriate macros, so get + * them from the string*/ +void ssl_get_version(int* major, int* minor, int* patch) +{ + const char* str = gnutls_check_version(NULL); + + ssl_debug_printf("ssl_get_version: %s\n", str); + sscanf(str, "%d.%d.%d", major, minor, patch); +} + + +SSL_PRIVATE_KEY* ssl_load_key(FILE* fp) +{ + /* gnutls make our work much harded, since we have to work internally with + * s-exp formatted data, but PEM loader export only in "gnutls_datum" + * format, and a datum -> s-exp convertion function does not exist. + */ + struct gnutls_x509_privkey_int* priv_key; + gnutls_datum key; + gnutls_datum m, e, d, p,q, u; + int size, major, minor, patch; + unsigned int bytes; + unsigned int tmp_size; +#ifdef SSL_FAST + gcry_mpi_t* rsa_params = g_malloc(sizeof(gcry_mpi_t)*6); +#else + gcry_mpi_t rsa_params[6]; +#endif + gcry_sexp_t rsa_priv_key; + + /* init private key data*/ + gnutls_x509_privkey_init(&priv_key); + + /* compute file size and load all file contents into a datum buffer*/ + if (fseek(fp, 0, SEEK_END) < 0) { + ssl_debug_printf("ssl_load_key: can't fseek file\n"); + return NULL; + } + if ((size = ftell(fp)) < 0) { + ssl_debug_printf("ssl_load_key: can't ftell file\n"); + return NULL; + } + if (fseek(fp, 0, SEEK_SET) < 0) { + ssl_debug_printf("ssl_load_key: can't refseek file\n"); + return NULL; + } + key.data = g_malloc(size); + key.size = size; + bytes = fread(key.data, 1, key.size, fp); + if (bytes < key.size) { + ssl_debug_printf("ssl_load_key: can't read from file %d bytes, got %d\n", + key.size, bytes); + return NULL; + } + + /* import PEM data*/ + if (gnutls_x509_privkey_import(priv_key, &key, GNUTLS_X509_FMT_PEM)!=0) { + ssl_debug_printf("ssl_load_key: can't import pem data\n"); + return NULL; + } + free(key.data); + + /* RSA get parameter */ + if (gnutls_x509_privkey_export_rsa_raw(priv_key, &m, &e, &d, &p, &q, &u) != 0) { + ssl_debug_printf("ssl_load_key: can't export rsa param (is a rsa private key file ?!?)\n"); + return NULL; + } + + /* convert each rsa parameter to mpi format*/ + if (gcry_mpi_scan( &rsa_params[0], GCRYMPI_FMT_USG, m.data, m.size, &tmp_size) !=0) { + ssl_debug_printf("ssl_load_key: can't convert m rsa param to int (size %d)\n", m.size); + return NULL; + } + + if (gcry_mpi_scan( &rsa_params[1], GCRYMPI_FMT_USG, e.data, e.size, &tmp_size) != 0) { + ssl_debug_printf("ssl_load_key: can't convert e rsa param to int (size %d)\n", e.size); + return NULL; + } + + // note: openssl and gnutls use 'p' and 'q' with opposite meaning: + // our 'p' must be equal to 'q' as provided from openssl and viceversa + if (gcry_mpi_scan( &rsa_params[2], GCRYMPI_FMT_USG, d.data, d.size, &tmp_size) !=0) { + ssl_debug_printf("ssl_load_key: can't convert d rsa param to int (size %d)\n", d.size); + return NULL; + } + + if (gcry_mpi_scan( &rsa_params[3], GCRYMPI_FMT_USG, q.data, q.size, &tmp_size) !=0) { + ssl_debug_printf("ssl_load_key: can't convert q rsa param to int (size %d)\n", q.size); + return NULL; + } + + if (gcry_mpi_scan( &rsa_params[4], GCRYMPI_FMT_USG, p.data, p.size, &tmp_size) !=0) { + ssl_debug_printf("ssl_load_key: can't convert p rsa param to int (size %d)\n", p.size); + return NULL; + } + + if (gcry_mpi_scan( &rsa_params[5], GCRYMPI_FMT_USG, u.data, u.size, &tmp_size) !=0) { + ssl_debug_printf("ssl_load_key: can't convert u rsa param to int (size %d)\n", m.size); + return NULL; + } + + ssl_get_version(&major, &minor, &patch); + + /* certain versions of gnutls require swap of rsa params 'p' and 'q' */ + if ((major <= 1) && (minor <= 0) && (patch <=13)) + { + gcry_mpi_t tmp; + ssl_debug_printf("ssl_load_key: swapping p and q parametes\n"); + tmp = rsa_params[4]; + rsa_params[4] = rsa_params[3]; + rsa_params[3] = tmp; + } + + if (gcry_sexp_build( &rsa_priv_key, NULL, + "(private-key(rsa((n%m)(e%m)(d%m)(p%m)(q%m)(u%m))))", rsa_params[0], + rsa_params[1], rsa_params[2], rsa_params[3], rsa_params[4], + rsa_params[5]) != 0) { + ssl_debug_printf("ssl_load_key: can't built rsa private key s-exp\n"); + return NULL; + } + +#if SSL_FAST + return rsa_params; +#else + { + int i; + for (i=0; i< 6; i++) + gcry_mpi_release(rsa_params[i]); + } + return rsa_priv_key; +#endif +} + +void ssl_free_key(SSL_PRIVATE_KEY* key) +{ +#if SSL_FAST + int i; + for (i=0; i< 6; i++) + gcry_mpi_release(key[i]); +#else + gcry_sexp_release(key); +#endif +} + +static FILE* myout=NULL; +void ssl_lib_init(void) +{ + gnutls_global_init(); + +#ifdef _WIN32 + /* we don't have standard I/O file available, open a log */ + myout = fopen("ssl-decrypt.txt","w"); + if (!myout) +#endif + myout = stderr; +} + + +#else +/* no libgnutl: dummy operation to keep interface consistent*/ +void ssl_lib_init(void) +{ +} + +SSL_PRIVATE_KEY* ssl_load_key(FILE* fp) +{ + ssl_debug_printf("ssl_load_key: impossible without glutls. fp %p\n",fp); + return NULL; +} +void ssl_free_key(SSL_PRIVATE_KEY* key) +{ +} + +int ssl_find_cipher(int num,SslCipherSuite* cs) +{ + ssl_debug_printf("ssl_find_cipher: dummy without glutls. num %d cs %p\n", + num,cs); + return 0; +} +int ssl_generate_keyring_material(SslDecryptSession*ssl) +{ + ssl_debug_printf("ssl_generate_keyring_material: impossible without glutls. ssl %p\n", + ssl); + return 0; +} +int ssl_decrypt_pre_master_secret(SslDecryptSession* ssl_session, + StringInfo* entrypted_pre_master, SSL_PRIVATE_KEY *pk) +{ + ssl_debug_printf("ssl_decrypt_pre_master_secret: impossible without glutls." + " ssl %p entrypted_pre_master %p pk %p\n", ssl_session, + entrypted_pre_master, pk); + return 0; +} + +int ssl_decrypt_record(SslDecryptSession*ssl,SslDecoder* decoder, int ct, + const unsigned char* in, int inl,unsigned char*out,int* outl) +{ + ssl_debug_printf("ssl_decrypt_record: impossible without gnutls. ssl %p" + "decoder %p ct %d, in %p inl %d out %p outl %d\n", ssl, decoder, ct, + in, inl, out, outl); + return 0; +} + +#endif + +/* get ssl data for this session. if no ssl data is found allocate a new one*/ +void ssl_session_init(SslDecryptSession* ssl_session) +{ + ssl_debug_printf("ssl_session_init: initializing ptr %p size %d\n", + ssl_session, sizeof(SslDecryptSession)); + + ssl_session->master_secret.data = ssl_session->_master_secret; + ssl_session->session_id.data = ssl_session->_session_id; + ssl_session->client_random.data = ssl_session->_client_random; + ssl_session->server_random.data = ssl_session->_server_random; + ssl_session->master_secret.data_len = 48; +} + +int ssl_data_init(StringInfo* str, unsigned char* src, unsigned int len) +{ + str->data = g_realloc(str->data,len); + if (!str->data) + return -1; + if (src) + memcpy(str->data, src,len); + str->data_len = len; + return 0; +} + +int ssl_data_alloc(StringInfo* str, unsigned int len) +{ + str->data = g_malloc(len); + if (!str->data) + return -1; + str->data_len = len; + return 0; +} + +int ssl_data_set(StringInfo* str, unsigned char* data, unsigned int len) +{ + memcpy(str->data, data, len); + str->data_len = len; + return 0; +} + +#ifdef SSL_DECRYPT_DEBUG +void ssl_debug_printf(const char* fmt, ...) +{ + va_list ap; + int ret=0; + va_start(ap, fmt); + ret += vfprintf(myout, fmt, ap); + va_end(ap); + fflush(myout); +} + + +void ssl_print_text_data(const char* name, const unsigned char* data, int len) +{ + int i; + fprintf(myout,"%s: ",name); + for (i=0; i< len; i++) { + fprintf(myout,"%c",data[i]); + } + fprintf(myout,"\n"); + fflush(myout); +} + +void ssl_print_data(const char* name, const unsigned char* data, int len) +{ + int i; + fprintf(myout,"%s[%d]:\n",name, len); + for (i=0; i< len; i++) { + if ((i>0) && (i%16 == 0)) + fprintf(myout,"\n"); + fprintf(myout,"%.2x ",data[i]&255); + } + fprintf(myout,"\n"); + fflush(myout); +} + +void ssl_print_string(const char* name, const StringInfo* data) +{ + ssl_print_data(name, data->data, data->data_len); +} +#endif diff --git a/epan/dissectors/packet-ssl-utils.h b/epan/dissectors/packet-ssl-utils.h new file mode 100644 index 0000000000..0ec60353c0 --- /dev/null +++ b/epan/dissectors/packet-ssl-utils.h @@ -0,0 +1,172 @@ +/* packet-ss-utils.c + * + * $Id$ + * + * ssl manipulation functions + * By Paolo Abeni <paolo.abeni@email.com> + * + * This program 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; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __SSL_UTILS_H_ +#define __SSL_UTILS_H_ + +#include <glib.h> + +#ifdef HAVE_LIBGNUTLS + +#ifdef _WIN32 +/* #include <gnutls_conf.h> */ +#include <gcrypt_conf.h> +#endif + +#include <stdio.h> +#include <gcrypt.h> +#include <gnutls/x509.h> +#include <gnutls/openssl.h> + +/* #define SSL_FAST 1 */ +#define SSL_DECRYPT_DEBUG + +#define SSL_CIPHER_CTX gcry_cipher_hd_t +#ifdef SSL_FAST +#define SSL_PRIVATE_KEY gcry_mpi_t +#else +#define SSL_PRIVATE_KEY struct gcry_sexp +#endif +#else +#define SSL_CIPHER_CTX void* +#define SSL_PRIVATE_KEY void +#endif + +typedef struct _StringInfo { + unsigned char* data; + unsigned int data_len; +} StringInfo; + +#define SSL_WRITE_KEY 1 + +#define SSLV3_VERSION 0x300 +#define TLSV1_VERSION 0x301 + +#define SSL_CLIENT_RANDOM 1 +#define SSL_SERVER_RANDOM 2 +#define SSL_CIPHER 4 +#define SSL_HAVE_SESSION_KEY 8 +#define SSL_VERSION 0x10 +#define SSL_MASTER_SECRET 0x20 + +#define SSL_CIPHER_MODE_STREAM 0 +#define SSL_CIPHER_MODE_CBC 1 + +typedef struct _SslCipherSuite { + int number; + int kex; + int sig; + int enc; + int block; + int bits; + int eff_bits; + int dig; + int dig_len; + int export; + int mode; +} SslCipherSuite; + +typedef struct _SslDecoder { + SslCipherSuite* cipher_suite; + unsigned char _mac_key[20]; + StringInfo mac_key; + SSL_CIPHER_CTX evp; + guint32 seq; +} SslDecoder; + +#define KEX_RSA 0x10 +#define KEX_DH 0x11 + +#define SIG_RSA 0x20 +#define SIG_DSS 0x21 +#define SIG_NONE 0x22 + +#define ENC_DES 0x30 +#define ENC_3DES 0x31 +#define ENC_RC4 0x32 +#define ENC_RC2 0x33 +#define ENC_IDEA 0x34 +#define ENC_AES 0x35 +#define ENC_AES256 0x36 +#define ENC_NULL 0x37 + +#define DIG_MD5 0x40 +#define DIG_SHA 0x41 + +/*typedef struct _SslService { + address addr; + guint port; +} SslService;*/ + +typedef struct _SslDecryptSession { + unsigned char _master_secret[48]; + unsigned char _session_id[256]; + unsigned char _client_random[32]; + unsigned char _server_random[32]; + StringInfo session_id; + StringInfo server_random; + StringInfo client_random; + StringInfo master_secret; + StringInfo pre_master_secret; + + int cipher; + int state; + SslCipherSuite cipher_suite; + SslDecoder server; + SslDecoder client; + SSL_PRIVATE_KEY* private_key; + guint32 version; + guint16 version_netorder; + +} SslDecryptSession; + +void ssl_lib_init(void); +void ssl_session_init(SslDecryptSession*); +int ssl_data_alloc(StringInfo* str, unsigned int len); +int ssl_data_set(StringInfo* data, unsigned char* src, unsigned int len); + +SSL_PRIVATE_KEY* ssl_load_key(FILE* fp); +void ssl_free_key(SSL_PRIVATE_KEY*); + +int ssl_find_cipher(int num,SslCipherSuite* cs); + +int ssl_generate_keyring_material(SslDecryptSession*ssl_session); + +int ssl_decrypt_pre_master_secret(SslDecryptSession*ssl_session, + StringInfo* entrypted_pre_master, SSL_PRIVATE_KEY *pk); + +int ssl_decrypt_record(SslDecryptSession*ssl,SslDecoder* decoder, int ct, + const unsigned char* in, int inl,unsigned char*out,int* outl); + +#ifdef SSL_DECRYPT_DEBUG +void ssl_debug_printf(const char* fmt,...); +void ssl_print_data(const char* name, const unsigned char* data, int len); +void ssl_print_string(const char* name, const StringInfo* data); +void ssl_print_text_data(const char* name, const unsigned char* data, int len); +#else +static inline char* ssl_debug_printf(const char* fmt,...) { return fmt; } +#define ssl_print_data(a, b, c) +#define ssl_print_string(a, b) +#define ssl_print_text_data(a, b, c) +#endif + +#endif diff --git a/epan/dissectors/packet-ssl.c b/epan/dissectors/packet-ssl.c index fd3357887f..8373c36d43 100644 --- a/epan/dissectors/packet-ssl.c +++ b/epan/dissectors/packet-ssl.c @@ -56,14 +56,7 @@ * * Notes: * - * - Uses conversations in a no-malloc fashion. Since we just want to - * remember the version of the conversation, we store the version - * integer directly in the void *data member of the conversation - * structure. This means that we don't have to manage any memory, - * but will cause problems if anyone assumes that all data pointers - * are actually pointers to memory allocated by g_mem_chunk_alloc. - * - * - Does not support decryption of encrypted frames, nor dissection + * - Does not support dissection * of frames that would require state maintained between frames * (e.g., single ssl records spread across multiple tcp frames) * @@ -82,6 +75,17 @@ * - Request Certificate * - Client Certificate * + * - Decryption is supported only for session that use RSA key exchange, + * if the host private key is provided via preference. + * + * - Decryption need to be performed 'sequentially', so it's done + * at packet reception time. This may cause a significative packet capture + * slow down. This also cause do dissect some ssl info that in previous + * dissector version were dissected only when a proto_tree context was + * available + * + * We are at Packet reception if time pinfo->fd->flags.visited == 0 + * */ #ifdef HAVE_CONFIG_H @@ -96,7 +100,12 @@ #include <epan/conversation.h> #include <epan/prefs.h> +#include <epan/inet_v6defs.h> #include <epan/dissectors/packet-x509af.h> +#include <epan/emem.h> +#include <epan/tap.h> +#include "packet-ssl-utils.h" + static gboolean ssl_desegment = TRUE; @@ -108,12 +117,14 @@ static gboolean ssl_desegment = TRUE; *********************************************************************/ /* Initialize the protocol and registered fields */ +static int ssl_tap = -1; static int proto_ssl = -1; static int hf_ssl_record = -1; static int hf_ssl_record_content_type = -1; static int hf_ssl_record_version = -1; static int hf_ssl_record_length = -1; static int hf_ssl_record_appdata = -1; +static int hf_ssl_record_appdata_decrypted = -1; static int hf_ssl2_record = -1; static int hf_ssl2_record_is_escape = -1; static int hf_ssl2_record_padding_length = -1; @@ -199,6 +210,321 @@ static gint ett_pct_hash_suites = -1; static gint ett_pct_cert_suites = -1; static gint ett_pct_exch_suites = -1; +typedef struct { + unsigned int ssl_port; + unsigned int decrypted_port; + dissector_handle_t handle; + char* info; +} SslAssociation; + +#ifdef _WIN32 +#define TEST_DIR "\\Program Files\\Ethereal\\esp_data\\" +#else +#define TEST_DIR "/usr/share/ethereal-ssl-decrypt/" +#endif +static char* ssl_keys_list = "127.0.0.1:443:"TEST_DIR"server.key," + "127.0.0.1:4433:"TEST_DIR"server.pem"; +static char* ssl_ports_list = NULL; + +typedef struct _SslService { + address addr; + guint port; +} SslService; + +static GHashTable *ssl_session_hash = NULL; +static GHashTable *ssl_key_hash = NULL; +static GTree* ssl_associations = NULL; +static dissector_handle_t ssl_handle = NULL; +static StringInfo ssl_decrypted_data = {NULL, 0}; + +/* Hash Functions for ssl sessions table and private keys table*/ +static gint ssl_equal (gconstpointer v, gconstpointer v2) +{ + const StringInfo *val1 = (const StringInfo *)v; + const StringInfo *val2 = (const StringInfo *)v2; + + if (val1->data_len == val2->data_len && + !memcmp(val1->data, val2->data, val2->data_len)) { + return 1; + } + return 0; +} + +static guint ssl_hash (gconstpointer v) +{ + guint l,hash = 0; + StringInfo* id = (StringInfo*) v; + guint* cur = (guint*) id->data; + for (l=4; (l<id->data_len); l+=4, cur++) + hash = hash ^ (*cur); + + return hash; +} + +static gint ssl_private_key_equal (gconstpointer v, gconstpointer v2) +{ + const SslService *val1 = (const SslService *)v; + const SslService *val2 = (const SslService *)v2; + + if ((val1->port == val2->port) && + ! CMP_ADDRESS(&val1->addr, &val2->addr)) { + return 1; + } + return 0; +} + +static guint ssl_private_key_hash (gconstpointer v) +{ + const SslService *key = (const SslService *)v; + guint l,hash = key->port, len = key->addr.len; + + guint* cur = (guint*) key->addr.data; + for (l=4; (l<len); l+=4, cur++) + hash = hash ^ (*cur); + + return hash; +} + +/* private key table entries have a scope 'larger' then packet capture, + * so we can't relay on se_alloc** function */ +static void ssl_private_key_free(gpointer id, gpointer key, gpointer dummy) +{ + g_free(id); + ssl_free_key((SSL_PRIVATE_KEY*) key); +} + +/* handling of association between ssl ports and clear text protocol */ +static void ssl_association_add(unsigned int port, unsigned int ctport, + const char* info) +{ + dissector_table_t tcp_dissectors = find_dissector_table( "tcp.port"); + SslAssociation* assoc = g_malloc(sizeof(SslAssociation)+strlen(info)+1); + + assoc->info = (char*) assoc+sizeof(SslAssociation); + strcpy(assoc->info, info); + assoc->ssl_port = port; + assoc->decrypted_port = ctport; + assoc->handle = dissector_get_port_handle(tcp_dissectors, ctport); + + ssl_debug_printf("ssl_association_add port %d ctport %d info %s handle %p\n", + port, ctport, info, assoc->handle); + + dissector_add("tcp.port", port, ssl_handle); + g_tree_insert(ssl_associations, (gpointer)port, assoc); +} + +static gint ssl_association_cmp(gconstpointer a, gconstpointer b) +{ + return (gint)a-(gint)b; +} + +static inline SslAssociation* ssl_association_find(unsigned int port) +{ + register SslAssociation* ret = g_tree_lookup(ssl_associations, (gpointer)port); + ssl_debug_printf("ssl_association_find: port %d found %p\n", port, ret); + return ret; +} + +static gint ssl_association_remove_handle (gpointer key, + gpointer data, gpointer user_data) +{ + SslAssociation* assoc = (SslAssociation*) data; + ssl_debug_printf("ssl_association_remove_handle removing ptr %p handle\n", + data, assoc->handle); + if (assoc->handle) + dissector_delete("tcp.port", assoc->ssl_port, assoc->handle); + g_free(data); + return 0; +} + +static inline int ssl_packet_from_server(unsigned int port) +{ + register int ret = ssl_association_find(port) != 0; + ssl_debug_printf("ssl_packet_from_server: is from server %d\n", ret); + return ret; +} + +/* initialize/reset per capture state data (ssl sessions cache) */ +static void ssl_init(void) +{ + if (ssl_session_hash) + g_hash_table_destroy(ssl_session_hash); + ssl_session_hash = g_hash_table_new(ssl_hash, ssl_equal); + if (ssl_decrypted_data.data) + g_free(ssl_decrypted_data.data); + ssl_decrypted_data.data = g_malloc0(32); + ssl_decrypted_data.data_len = 32; +} + +/* parse ssl related preferences (private keys and ports association strings) */ +static void ssl_parse(void) +{ + if (ssl_key_hash) + { + g_hash_table_foreach(ssl_key_hash, ssl_private_key_free, NULL); + g_hash_table_destroy(ssl_key_hash); + } + if (ssl_associations) + { + g_tree_traverse(ssl_associations, ssl_association_remove_handle, G_IN_ORDER, NULL); + g_tree_destroy(ssl_associations); + } + + /* parse private keys string, load available keys and put them in key hash*/ + ssl_key_hash = g_hash_table_new(ssl_private_key_hash,ssl_private_key_equal); + ssl_associations = g_tree_new(ssl_association_cmp); + + if (ssl_keys_list && (ssl_keys_list[0] != 0)) + { + char* end; + char* start = strdup(ssl_keys_list); + char* tmp = start; + + ssl_debug_printf("ssl_init keys string %s\n", start); + do { + char* addr, *port, *filename; + unsigned char* ip; + SslService* service; + SSL_PRIVATE_KEY * private_key; + FILE* fp; + + addr = start; + /* split ip/file couple with ',' separator*/ + end = strchr(start, ','); + if (end) { + *end = 0; + start = end+1; + } + + /* for each entry split ip, port, filename with ':' separator */ + ssl_debug_printf("ssl_init found host entry %s\n", addr); + port = strchr(addr, ':'); + if (!port) + break; + *port = 0; + port++; + + filename = strchr(port,':'); + if (!filename) + break; + *filename=0; + filename++; + + /* convert ip and port string to network rappresentation*/ + service = g_malloc(sizeof(SslService) + 4); + service->addr.type = AT_IPv4; + service->addr.len = 4; + service->addr.data = ip = ((unsigned char*)service) + sizeof(SslService); + sscanf(addr, "%hhu.%hhu.%hhu.%hhu", &ip[0], &ip[1], &ip[2], &ip[3]); + service->port = atoi(port); + ssl_debug_printf("ssl_init addr %hhu.%hhu.%hhu.%hhu port %d filename %s\n", + ip[0], ip[1], ip[2], ip[3], service->port, filename); + + /* try to load pen file*/ + fp = fopen(filename, "rb"); + if (!fp) { + fprintf(stderr, "can't open file %s \n",filename); + break; + } + + private_key = ssl_load_key(fp); + if (!private_key) { + fprintf(stderr,"can't load private key from %s\n", + filename); + break; + } + fclose(fp); + + ssl_debug_printf("ssl_init private key file %s successfully loaded\n", + filename); + g_hash_table_insert(ssl_key_hash, service, private_key); + + } while (end != NULL); + free(tmp); + } + + /* parse ssl ports string and add ssl dissector to specifed port[s]*/ + if (ssl_ports_list && (ssl_ports_list[0] != 0)) + { + char* end; + char* start = strdup(ssl_ports_list); + char* tmp = start; + + ssl_debug_printf("ssl_init ports string %s\n", start); + do { + char* port, *ctport, *info; + unsigned int portn, ctportn; + + port = start; + /* split ip/file couple with ',' separator*/ + end = strchr(start, ','); + if (end) { + *end = 0; + start = end+1; + } + + /* for each entry split ip, port, filename with ':' separator */ + ssl_debug_printf("ssl_init found port entry %s\n", port); + ctport = strchr(port, ':'); + if (!ctport) + break; + *ctport = 0; + ctport++; + + info = strchr(ctport,':'); + if (!info) + break; + *info=0; + info++; + + /* add dissector to this port */ + portn = atoi(port); + ctportn = atoi(ctport); + if (!portn || !ctportn) + break; + + ssl_debug_printf("ssl_init adding dissector to port %d (ct port %d)\n", portn, ctportn); + ssl_association_add(portn, ctportn, info); + } while (end != NULL); + free(tmp); + } + + /* [re] add ssl dissection to defaults ports */ + ssl_association_add(443, 80, "Hypertext transfer protocol"); + ssl_association_add(636, 389, "Lightweight directory access protocol"); + ssl_association_add(993, 143, "Interactive mail access protocol"); + ssl_association_add(995, 110, "Post office protocol"); +} + +/* store master secret into session data cache */ +static void ssl_save_session(SslDecryptSession* ssl) +{ + /* allocate stringinfo chunks for session id and master secret data*/ + StringInfo* session_id = se_alloc0(sizeof(StringInfo) + ssl->session_id.data_len); + StringInfo* master_secret = se_alloc0(48 + sizeof(StringInfo)); + + master_secret->data = ((unsigned char*)master_secret+sizeof(StringInfo)); + session_id->data = ((unsigned char*)session_id+sizeof(StringInfo)); + + ssl_data_set(session_id, ssl->session_id.data, ssl->session_id.data_len); + ssl_data_set(master_secret, ssl->master_secret.data, ssl->master_secret.data_len); + g_hash_table_insert(ssl_session_hash, session_id, master_secret); + ssl_print_string("ssl_save_session stored session id", session_id); + ssl_print_string("ssl_save_session stored master secret", master_secret); +} + +static void ssl_restore_session(SslDecryptSession* ssl) +{ + StringInfo* ms = g_hash_table_lookup(ssl_session_hash, &ssl->session_id); + if (!ms) { + ssl_debug_printf("ssl_restore_session can't find stored session\n"); + return; + } + ssl_data_set(&ssl->master_secret, ms->data, ms->data_len); + ssl->state |= SSL_MASTER_SECRET; + ssl_debug_printf("ssl_restore_session master key retrived\n"); +} + /* The TCP port to associate with by default */ #define TCP_PORT_SSL 443 #define TCP_PORT_SSL_LDAP 636 @@ -704,7 +1030,8 @@ static const value_string tls_hello_extension_types[] = { static int dissect_ssl3_record(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset, guint *conv_version, - gboolean *need_desegmentation); + gboolean *need_desegmentation, + SslDecryptSession *conv_data); /* change cipher spec dissector */ static void dissect_ssl3_change_cipher_spec(tvbuff_t *tvb, @@ -721,16 +1048,19 @@ static void dissect_ssl3_alert(tvbuff_t *tvb, packet_info *pinfo, static void dissect_ssl3_handshake(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset, guint32 record_length, - guint *conv_version, guint8 content_type); + guint *conv_version, + SslDecryptSession *conv_data, guint8 content_type); static void dissect_ssl3_hnd_cli_hello(tvbuff_t *tvb, proto_tree *tree, - guint32 offset, guint32 length); + guint32 offset, guint32 length, + SslDecryptSession* ssl); static void dissect_ssl3_hnd_srv_hello(tvbuff_t *tvb, proto_tree *tree, - guint32 offset, guint32 length); + guint32 offset, guint32 length, + SslDecryptSession* ssl); static void dissect_ssl3_hnd_cert(tvbuff_t *tvb, proto_tree *tree, guint32 offset, packet_info *pinfo); @@ -742,7 +1072,7 @@ static void dissect_ssl3_hnd_cert_req(tvbuff_t *tvb, static void dissect_ssl3_hnd_finished(tvbuff_t *tvb, proto_tree *tree, guint32 offset, - guint *conv_version); + guint* conv_version); /* @@ -754,12 +1084,14 @@ static void dissect_ssl3_hnd_finished(tvbuff_t *tvb, static int dissect_ssl2_record(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset, guint *conv_version, - gboolean *need_desegmentation); + gboolean *need_desegmentation, + SslDecryptSession* ssl); /* client hello dissector */ static void dissect_ssl2_hnd_client_hello(tvbuff_t *tvb, proto_tree *tree, - guint32 offset); + guint32 offset, + SslDecryptSession* ssl); static void dissect_pct_msg_client_hello(tvbuff_t *tvb, proto_tree *tree, @@ -794,7 +1126,7 @@ static void dissect_pct_msg_error(tvbuff_t *tvb, * Support Functions * */ -static void ssl_set_conv_version(packet_info *pinfo, guint version); +/*static void ssl_set_conv_version(packet_info *pinfo, guint version);*/ static int ssl_is_valid_handshake_type(guint8 type); static int ssl_is_valid_content_type(guint8 type); static int ssl_is_valid_ssl_version(guint16 version); @@ -809,7 +1141,6 @@ static int ssl_looks_like_valid_v2_handshake(tvbuff_t *tvb, static int ssl_looks_like_valid_pct_handshake(tvbuff_t *tvb, guint32 offset, guint32 record_length); - /********************************************************************* * * Main dissector @@ -824,12 +1155,13 @@ dissect_ssl(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) conversation_t *conversation; void *conv_data; - guint conv_version = SSL_VER_UNKNOWN; proto_item *ti = NULL; proto_tree *ssl_tree = NULL; guint32 offset = 0; gboolean first_record_in_frame = TRUE; gboolean need_desegmentation; + SslDecryptSession* ssl_session = NULL; + guint* conv_version; /* Track the version using conversations to reduce the * chance that a packet that simply *looks* like a v2 or @@ -852,10 +1184,48 @@ dissect_ssl(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) pinfo->srcport, pinfo->destport, 0); } conv_data = conversation_get_proto_data(conversation, proto_ssl); + + /* PAOLO: manage ssl decryption data */ + /*get a valid ssl session pointer*/ if (conv_data != NULL) - { - conv_version = GPOINTER_TO_UINT(conv_data); + ssl_session = conv_data; + else { + SslService dummy; + + ssl_session = se_alloc0(sizeof(SslDecryptSession)); + ssl_session_init(ssl_session); + ssl_session->version = SSL_VER_UNKNOWN; + conversation_add_proto_data(conversation, proto_ssl, ssl_session); + + /* we need to know witch side of conversation is speaking*/ + if (ssl_packet_from_server(pinfo->srcport)) { + dummy.addr = pinfo->net_src; + dummy.port = pinfo->srcport; + } + else { + dummy.addr = pinfo->net_dst; + dummy.port = pinfo->destport; + } + ssl_debug_printf("dissect_ssl server %hhd.%hhd.%hhd.%hhd:%d\n", + dummy.addr.data[0], + dummy.addr.data[1],dummy.addr.data[2], + dummy.addr.data[3],dummy.port); + + /* try to retrive private key for this service. Do it now 'cause pinfo + * is not always available + * Note that with HAVE_LIBGNUTLS undefined private_key is allways 0 + * and thus decryption never engaged*/ + ssl_session->private_key = g_hash_table_lookup(ssl_key_hash, &dummy); + if (!ssl_session->private_key) + ssl_debug_printf("dissect_ssl can't find private key for this server!\n"); } + conv_version= & ssl_session->version; + + /* try decryption only the first time we see this packet + * (to keep cipher syncronized)and only if we have + * the server private key*/ + if (!ssl_session->private_key || pinfo->fd->flags.visited) + ssl_session = NULL; /* Initialize the protocol column; we'll set it later when we * figure out what flavor of SSL it is (assuming we don't @@ -910,12 +1280,13 @@ dissect_ssl(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) /* first try to dispatch off the cached version * known to be associated with the conversation */ - switch(conv_version) { + switch(*conv_version) { case SSL_VER_SSLv2: case SSL_VER_PCT: offset = dissect_ssl2_record(tvb, pinfo, ssl_tree, - offset, &conv_version, - &need_desegmentation); + offset, conv_version, + &need_desegmentation, + ssl_session); break; case SSL_VER_SSLv3: @@ -929,14 +1300,16 @@ dissect_ssl(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) if (ssl_is_v2_client_hello(tvb, offset)) { offset = dissect_ssl2_record(tvb, pinfo, ssl_tree, - offset, &conv_version, - &need_desegmentation); + offset, conv_version, + &need_desegmentation, + ssl_session); } else { offset = dissect_ssl3_record(tvb, pinfo, ssl_tree, - offset, &conv_version, - &need_desegmentation); + offset, conv_version, + &need_desegmentation, + ssl_session); } break; @@ -948,15 +1321,17 @@ dissect_ssl(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { /* looks like sslv2 or pct client hello */ offset = dissect_ssl2_record(tvb, pinfo, ssl_tree, - offset, &conv_version, - &need_desegmentation); + offset, conv_version, + &need_desegmentation, + ssl_session); } else if (ssl_looks_like_sslv3(tvb, offset)) { /* looks like sslv3 or tls */ offset = dissect_ssl3_record(tvb, pinfo, ssl_tree, - offset, &conv_version, - &need_desegmentation); + offset, conv_version, + &need_desegmentation, + ssl_session); } else { @@ -972,7 +1347,7 @@ dissect_ssl(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) if (check_col(pinfo->cinfo, COL_PROTOCOL)) { col_set_str(pinfo->cinfo, COL_PROTOCOL, - ssl_version_short_names[conv_version]); + ssl_version_short_names[*conv_version]); } } break; @@ -981,22 +1356,96 @@ dissect_ssl(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) /* Desegmentation return check */ if (need_desegmentation) return; - - /* If we haven't already set the version information for - * this conversation, do so. */ - if (conv_data == NULL) - { - conv_data = GINT_TO_POINTER(conv_version); - conversation_add_proto_data(conversation, proto_ssl, conv_data); - } - /* set up for next record in frame, if any */ first_record_in_frame = FALSE; } + tap_queue_packet(ssl_tap, pinfo, (gpointer)proto_ssl); +} +static void +decrypt_ssl3_record(tvbuff_t *tvb, packet_info *pinfo, guint32 offset, + guint32 record_length, guint8 content_type, SslDecryptSession* ssl, + gboolean save_plaintext) +{ + int len, direction; + SslDecoder* decoder; + + /* if we can decrypt and decryption have success + * add decrypted data to this packet info*/ + ssl_debug_printf("decrypt_ssl3_record: app_data len %d ssl state %X\n", + record_length, ssl->state); + if (!(ssl->state & SSL_HAVE_SESSION_KEY)) { + ssl_debug_printf("decrypt_ssl3_record: no session key\n"); + return ; + } + + /* retrive decoder for this packet direction*/ + if ((direction = ssl_packet_from_server(pinfo->srcport)) != 0) { + ssl_debug_printf("decrypt_ssl3_record: using server decoder\n"); + decoder = &ssl->server; + } + else { + ssl_debug_printf("decrypt_ssl3_record: using client decoder\n"); + decoder = &ssl->client; + } + + /* ensure we have enough storage space for decrypted data */ + if (record_length > ssl_decrypted_data.data_len) + { + ssl_debug_printf("decrypt_ssl3_record: allocating %d bytes" + " for decrypt data (old len %d)\n", + record_length + 32, ssl_decrypted_data.data_len); + ssl_decrypted_data.data = g_realloc(ssl_decrypted_data.data, + record_length + 32); + ssl_decrypted_data.data_len = record_length + 32; + } + + /* run decryption and add decrypted payload to protocol data, if decryption + * is successful*/ + len = ssl_decrypted_data.data_len; + if ((ssl_decrypt_record(ssl, decoder, + content_type, tvb_get_ptr(tvb, offset, record_length), + record_length, ssl_decrypted_data.data, &len) == 0) && + save_plaintext) + { + StringInfo* data = p_get_proto_data(pinfo->fd, proto_ssl); + if (!data) + { + ssl_debug_printf("decrypt_ssl3_record: allocating app_data %d " + "bytes for app data\n", len); + /* first app data record: allocate and put packet data*/ + data = se_alloc(sizeof(StringInfo)); + data->data = se_alloc(len); + data->data_len = len; + memcpy(data->data, ssl_decrypted_data.data, len); + } + else { + unsigned char* store; + /* update previus record*/ + ssl_debug_printf("decrypt_ssl3_record: reallocating app_data " + "%d bytes for app data (total %d appdata bytes)\n", + len, data->data_len + len); + store = se_alloc(data->data_len + len); + memcpy(store, data->data, data->data_len); + memcpy(&store[data->data_len], ssl_decrypted_data.data, len); + data->data_len += len; + + /* old decrypted data ptr here appare to be leaked, but it's + * collected by emem allocator */ + data->data = store; + + /* data ptr is changed, so remove old one and re-add the new one*/ + ssl_debug_printf("decrypt_ssl3_record: removing old app_data ptr\n"); + p_rem_proto_data(pinfo->fd, proto_ssl); + } + + ssl_debug_printf("decrypt_ssl3_record: setting decrypted app_data ptr %p\n",data); + p_add_proto_data(pinfo->fd, proto_ssl, data); + } } + /********************************************************************* * * SSL version 3 and TLS Dissection Routines @@ -1005,7 +1454,8 @@ dissect_ssl(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) static int dissect_ssl3_record(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset, - guint *conv_version, gboolean *need_desegmentation) + guint *conv_version, gboolean *need_desegmentation, + SslDecryptSession* ssl) { /* @@ -1033,6 +1483,8 @@ dissect_ssl3_record(tvbuff_t *tvb, packet_info *pinfo, proto_tree *ti = NULL; proto_tree *ssl_record_tree = NULL; guint32 available_bytes = 0; + StringInfo* decrypted; + SslAssociation* association; available_bytes = tvb_length_remaining(tvb, offset); @@ -1152,12 +1604,21 @@ dissect_ssl3_record(tvbuff_t *tvb, packet_info *pinfo, if (version == 0x0300) { *conv_version = SSL_VER_SSLv3; - ssl_set_conv_version(pinfo, *conv_version); + if (ssl) { + ssl->version_netorder = version; + ssl->state |= SSL_VERSION; + } + /*ssl_set_conv_version(pinfo, ssl->version);*/ } else if (version == 0x0301) { + *conv_version = SSL_VER_TLS; - ssl_set_conv_version(pinfo, *conv_version); + if (ssl) { + ssl->version_netorder = version; + ssl->state |= SSL_VERSION; + } + /*ssl_set_conv_version(pinfo, ssl->version);*/ } } if (check_col(pinfo->cinfo, COL_PROTOCOL)) @@ -1182,6 +1643,11 @@ dissect_ssl3_record(tvbuff_t *tvb, packet_info *pinfo, /* * now dissect the next layer */ + ssl_debug_printf("dissect_ssl3_record: content_type %d\n",content_type); + + /* PAOLO try to decrypt each record (we must keep ciphers "in sync") + * store plain text only for app data */ + switch (content_type) { case SSL_ID_CHG_CIPHER_SPEC: if (check_col(pinfo->cinfo, COL_INFO)) @@ -1190,26 +1656,76 @@ dissect_ssl3_record(tvbuff_t *tvb, packet_info *pinfo, offset, conv_version, content_type); break; case SSL_ID_ALERT: + if (ssl) + decrypt_ssl3_record(tvb, pinfo, offset, + record_length, content_type, ssl, FALSE); dissect_ssl3_alert(tvb, pinfo, ssl_record_tree, offset, conv_version); break; case SSL_ID_HANDSHAKE: + if (ssl) + decrypt_ssl3_record(tvb, pinfo, offset, + record_length, content_type, ssl, FALSE); dissect_ssl3_handshake(tvb, pinfo, ssl_record_tree, offset, - record_length, conv_version, content_type); + record_length, conv_version, ssl, content_type); break; case SSL_ID_APP_DATA: + if (ssl) + decrypt_ssl3_record(tvb, pinfo, offset, + record_length, content_type, ssl, TRUE); + + /* show on info colum what we are decoding */ if (check_col(pinfo->cinfo, COL_INFO)) col_append_str(pinfo->cinfo, COL_INFO, "Application Data"); - if (ssl_record_tree) + + if (!ssl_record_tree) + break; + + /* we need dissector information when the selected packet is shown. + * ssl session pointer is NULL at that time, so we can't access + * info cached there*/ + association = ssl_association_find(pinfo->srcport); + association = association ? association: ssl_association_find(pinfo->destport); + + proto_item_set_text(ssl_record_tree, + "%s Record Layer: %s Protocol: %s", + ssl_version_short_names[*conv_version], + val_to_str(content_type, ssl_31_content_type, "unknown"), + association?association->info:"Application Data"); + + /* show decrypted data info, if available */ + decrypted = p_get_proto_data(pinfo->fd, proto_ssl); + if (decrypted) { - proto_item_set_text(ssl_record_tree, - "%s Record Layer: %s Protocol: Application Data", - ssl_version_short_names[*conv_version], - val_to_str(content_type, ssl_31_content_type, "unknown")); + tvbuff_t* new_tvb; + + /* try to dissect decrypted data*/ + ssl_debug_printf("dissect_ssl3_record decrypted len %d\n", decrypted->data_len); + + /* create new tvbuff for the decrypted data */ + new_tvb = tvb_new_real_data(decrypted->data, + decrypted->data_len, decrypted->data_len); + tvb_set_free_cb(new_tvb, g_free); + //tvb_set_child_real_data_tvbuff(tvb, new_tvb); + + /* find out a dissector using server port*/ + if (association && association->handle) { + ssl_debug_printf("dissect_ssl3_record found association %p\n", association); + ssl_print_text_data("decrypted app data",decrypted->data, + decrypted->data_len); + + call_dissector(association->handle, new_tvb, pinfo, ssl_record_tree); + } + /* add raw decrypted data only if a decoder is not found*/ + else + proto_tree_add_string(ssl_record_tree, hf_ssl_record_appdata_decrypted, tvb, + offset, decrypted->data_len, (char*) decrypted->data); + } + else { tvb_ensure_bytes_exist(tvb, offset, record_length); proto_tree_add_item(ssl_record_tree, hf_ssl_record_appdata, tvb, - offset, record_length, 0); - } + offset, record_length, 0); + } break; default: @@ -1227,7 +1743,7 @@ dissect_ssl3_record(tvbuff_t *tvb, packet_info *pinfo, static void dissect_ssl3_change_cipher_spec(tvbuff_t *tvb, proto_tree *tree, guint32 offset, - guint *conv_version, guint8 content_type) + guint* conv_version, guint8 content_type) { /* * struct { @@ -1250,7 +1766,7 @@ dissect_ssl3_change_cipher_spec(tvbuff_t *tvb, static void dissect_ssl3_alert(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset, - guint *conv_version) + guint* conv_version) { /* struct { * AlertLevel level; @@ -1294,7 +1810,7 @@ dissect_ssl3_alert(tvbuff_t *tvb, packet_info *pinfo, if (check_col(pinfo->cinfo, COL_INFO)) col_append_str(pinfo->cinfo, COL_INFO, "Encrypted Alert"); } - + if (tree) { if (level && desc) @@ -1325,7 +1841,8 @@ dissect_ssl3_alert(tvbuff_t *tvb, packet_info *pinfo, static void dissect_ssl3_handshake(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset, - guint32 record_length, guint *conv_version, guint8 content_type) + guint32 record_length, guint *conv_version, + SslDecryptSession* ssl, guint8 content_type) { /* struct { * HandshakeType msg_type; @@ -1367,6 +1884,8 @@ dissect_ssl3_handshake(tvbuff_t *tvb, packet_info *pinfo, msg_type_str = match_strval(msg_type, ssl_31_handshake_type); length = tvb_get_ntoh24(tvb, offset + 1); + ssl_debug_printf("dissect_ssl3_handshake iteration %d type %d offset %d lenght %d " + "bytes, remaning %d \n", first_iteration, msg_type, offset, length, record_length); if (!msg_type_str && !first_iteration) { /* only dissect / report messages if they're @@ -1425,17 +1944,18 @@ dissect_ssl3_handshake(tvbuff_t *tvb, packet_info *pinfo, /* if we don't have a valid handshake type, just quit dissecting */ if (!msg_type_str) - { return; - } - - if (ssl_hand_tree) + + /* PAOLO: if we are doing ssl decryption we must dissect some requests type */ + if (ssl_hand_tree || ssl) { /* add nodes for the message type and message length */ - proto_tree_add_item(ssl_hand_tree, hf_ssl_handshake_type, - tvb, offset, 1, msg_type); + if (ssl_hand_tree) + proto_tree_add_item(ssl_hand_tree, hf_ssl_handshake_type, + tvb, offset, 1, msg_type); offset++; - proto_tree_add_uint(ssl_hand_tree, hf_ssl_handshake_length, + if (ssl_hand_tree) + proto_tree_add_uint(ssl_hand_tree, hf_ssl_handshake_length, tvb, offset, 3, length); offset += 3; @@ -1446,11 +1966,11 @@ dissect_ssl3_handshake(tvbuff_t *tvb, packet_info *pinfo, break; case SSL_HND_CLIENT_HELLO: - dissect_ssl3_hnd_cli_hello(tvb, ssl_hand_tree, offset, length); + dissect_ssl3_hnd_cli_hello(tvb, ssl_hand_tree, offset, length, ssl); break; case SSL_HND_SERVER_HELLO: - dissect_ssl3_hnd_srv_hello(tvb, ssl_hand_tree, offset, length); + dissect_ssl3_hnd_srv_hello(tvb, ssl_hand_tree, offset, length, ssl); break; case SSL_HND_CERTIFICATE: @@ -1473,8 +1993,51 @@ dissect_ssl3_handshake(tvbuff_t *tvb, packet_info *pinfo, /* unimplemented */ break; - case SSL_HND_CLIENT_KEY_EXCHG: - /* unimplemented */ + case SSL_HND_CLIENT_KEY_EXCHG: + { + /* PAOLO: here we can have all the data to build session key*/ + StringInfo encrypted_pre_master; + int ret; + + if (!ssl) + break; + + /* check for required session data */ + ssl_debug_printf("dissect_ssl3_handshake found SSL_HND_CLIENT_KEY_EXCHG state %X\n", + ssl->state); + if ((ssl->state & (SSL_CIPHER|SSL_CLIENT_RANDOM|SSL_SERVER_RANDOM|SSL_VERSION)) != + (SSL_CIPHER|SSL_CLIENT_RANDOM|SSL_SERVER_RANDOM|SSL_VERSION)) { + ssl_debug_printf("dissect_ssl3_handshake not enough data to generate key (required %X)\n", + (SSL_CIPHER|SSL_CLIENT_RANDOM|SSL_SERVER_RANDOM|SSL_VERSION)); + break; + } + + /* get encrypted data, we must skip tls record len && version and + * 2 bytes of record data */ + encrypted_pre_master.data = se_alloc(length - 2); + encrypted_pre_master.data_len = length-2; + tvb_memcpy(tvb, encrypted_pre_master.data, offset+2, length-2); + + if (!ssl->private_key) { + ssl_debug_printf("dissect_ssl3_handshake can't find private key\n"); + break; + } + + /* go with ssl key processessing; encrypted_pre_master + * will be used for master secret store*/ + ret = ssl_decrypt_pre_master_secret(ssl, &encrypted_pre_master, ssl->private_key); + if (ret < 0) { + ssl_debug_printf("dissect_ssl3_handshake can't decrypt pre master secret\n"); + break; + } + if (ssl_generate_keyring_material(ssl)<0) { + ssl_debug_printf("dissect_ssl3_handshake can't generate keyring material\n"); + break; + } + ssl->state |= SSL_HAVE_SESSION_KEY; + ssl_save_session(ssl); + ssl_debug_printf("dissect_ssl3_handshake session keys succesfully generated\n"); + } break; case SSL_HND_FINISHED: @@ -1485,9 +2048,8 @@ dissect_ssl3_handshake(tvbuff_t *tvb, packet_info *pinfo, } else - { - offset += 4; /* skip the handshake header */ - } + offset += 4; /* skip the handshake header when handshake is not processed*/ + offset += length; first_iteration = FALSE; /* set up for next pass, if any */ } @@ -1495,13 +2057,50 @@ dissect_ssl3_handshake(tvbuff_t *tvb, packet_info *pinfo, static int dissect_ssl3_hnd_hello_common(tvbuff_t *tvb, proto_tree *tree, - guint32 offset) + guint32 offset, SslDecryptSession* ssl, gint from_server) { /* show the client's random challenge */ - guint32 initial_offset = offset; nstime_t gmt_unix_time; guint8 session_id_length = 0; + if (ssl) + { + /* PAOLO: get proper peer information*/ + StringInfo* rnd; + if (from_server) + rnd = &ssl->server_random; + else + rnd = &ssl->client_random; + + /* get provided random for keyring generation*/ + tvb_memcpy(tvb, rnd->data, offset, 32); + rnd->data_len = 32; + if (from_server) + ssl->state |= SSL_SERVER_RANDOM; + else + ssl->state |= SSL_CLIENT_RANDOM; + ssl_debug_printf("dissect_ssl3_hnd_hello_common found random state %X\n", + ssl->state); + + session_id_length = tvb_get_guint8(tvb, offset + 32); + /* check stored session id info */ + if (from_server && (session_id_length == ssl->session_id.data_len) && + (tvb_memeql(tvb, offset+33, ssl->session_id.data, session_id_length) == 0)) + { + /* clinet/server id match: try to restore a previous cached session*/ + ssl_restore_session(ssl); + } + else { + /* reset state on renegotiation*/ + if (!from_server) + ssl->state &= ~(SSL_HAVE_SESSION_KEY|SSL_MASTER_SECRET| + SSL_CIPHER|SSL_SERVER_RANDOM); + + tvb_memcpy(tvb,ssl->session_id.data, offset+33, session_id_length); + ssl->session_id.data_len = session_id_length; + } + } + if (tree) { /* show the time */ @@ -1533,7 +2132,9 @@ dissect_ssl3_hnd_hello_common(tvbuff_t *tvb, proto_tree *tree, } } - return offset - initial_offset; + + // XXXX + return session_id_length+33; } static int @@ -1591,7 +2192,8 @@ dissect_ssl3_hnd_hello_ext(tvbuff_t *tvb, static void dissect_ssl3_hnd_cli_hello(tvbuff_t *tvb, - proto_tree *tree, guint32 offset, guint32 length) + proto_tree *tree, guint32 offset, guint32 length, + SslDecryptSession*ssl) { /* struct { * ProtocolVersion client_version; @@ -1610,20 +2212,23 @@ dissect_ssl3_hnd_cli_hello(tvbuff_t *tvb, guint8 compression_method; guint16 start_offset = offset; - if (tree) + if (tree || ssl) { /* show the client version */ - proto_tree_add_item(tree, hf_ssl_handshake_client_version, tvb, + if (tree) + proto_tree_add_item(tree, hf_ssl_handshake_client_version, tvb, offset, 2, FALSE); offset += 2; /* show the fields in common with server hello */ - offset += dissect_ssl3_hnd_hello_common(tvb, tree, offset); + offset += dissect_ssl3_hnd_hello_common(tvb, tree, offset, ssl, 0); /* tell the user how many cipher suites there are */ cipher_suite_length = tvb_get_ntohs(tvb, offset); + if (!tree) + return; proto_tree_add_uint(tree, hf_ssl_handshake_cipher_suites_len, - tvb, offset, 2, cipher_suite_length); + tvb, offset, 2, cipher_suite_length); offset += 2; /* skip opaque length */ if (cipher_suite_length > 0) @@ -1706,7 +2311,7 @@ dissect_ssl3_hnd_cli_hello(tvbuff_t *tvb, static void dissect_ssl3_hnd_srv_hello(tvbuff_t *tvb, - proto_tree *tree, guint32 offset, guint32 length) + proto_tree *tree, guint32 offset, guint32 length, SslDecryptSession* ssl) { /* struct { * ProtocolVersion server_version; @@ -1719,21 +2324,56 @@ dissect_ssl3_hnd_srv_hello(tvbuff_t *tvb, */ guint16 start_offset = offset; - if (tree) + if (tree || ssl) { /* show the server version */ - proto_tree_add_item(tree, hf_ssl_handshake_server_version, tvb, + if (tree) + proto_tree_add_item(tree, hf_ssl_handshake_server_version, tvb, offset, 2, FALSE); offset += 2; /* first display the elements conveniently in * common with client hello */ - offset += dissect_ssl3_hnd_hello_common(tvb, tree, offset); + offset += dissect_ssl3_hnd_hello_common(tvb, tree, offset, ssl, 1); + + /* PAOLO: handle session cipher suite */ + if (ssl) { + /* store selected cipher suite for decryption */ + ssl->cipher = tvb_get_ntohs(tvb, offset); + if (ssl_find_cipher(ssl->cipher,&ssl->cipher_suite) < 0) { + ssl_debug_printf("dissect_ssl3_hnd_srv_hello can't find cipher suite %X\n", ssl->cipher); + goto no_cipher; + } + + ssl->state |= SSL_CIPHER; + ssl_debug_printf("dissect_ssl3_hnd_srv_hello found cipher %X, state %X\n", + ssl->cipher, ssl->state); + + /* if we have restored a session now we can have enought material + * to build session key, check it out*/ + if ((ssl->state & + (SSL_CIPHER|SSL_CLIENT_RANDOM|SSL_SERVER_RANDOM|SSL_VERSION|SSL_MASTER_SECRET)) != + (SSL_CIPHER|SSL_CLIENT_RANDOM|SSL_SERVER_RANDOM|SSL_VERSION|SSL_MASTER_SECRET)) { + ssl_debug_printf("dissect_ssl3_hnd_srv_hello not enough data to generate key (required %X)\n", + (SSL_CIPHER|SSL_CLIENT_RANDOM|SSL_SERVER_RANDOM|SSL_VERSION|SSL_MASTER_SECRET)); + goto no_cipher; + } + + ssl_debug_printf("dissect_ssl3_hnd_srv_hello trying to generate keys\n"); + if (ssl_generate_keyring_material(ssl)<0) { + ssl_debug_printf("dissect_ssl3_hnd_srv_hello can't generate keyring material\n"); + goto no_cipher; + } + ssl->state |= SSL_HAVE_SESSION_KEY; + } +no_cipher: + if (!tree) + return; /* now the server-selected cipher suite */ proto_tree_add_item(tree, hf_ssl_handshake_cipher_suite, - tvb, offset, 2, FALSE); + tvb, offset, 2, FALSE); offset += 2; /* and the server-selected compression method */ @@ -1910,7 +2550,7 @@ dissect_ssl3_hnd_cert_req(tvbuff_t *tvb, static void dissect_ssl3_hnd_finished(tvbuff_t *tvb, proto_tree *tree, guint32 offset, - guint *conv_version) + guint* conv_version) { /* For TLS: * struct { @@ -1957,8 +2597,9 @@ dissect_ssl3_hnd_finished(tvbuff_t *tvb, /* record layer dissector */ static int dissect_ssl2_record(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, - guint32 offset, guint *conv_version, - gboolean *need_desegmentation) + guint32 offset, guint* conv_version, + gboolean *need_desegmentation, + SslDecryptSession* ssl) { guint32 initial_offset = offset; guint8 byte = 0; @@ -2057,13 +2698,13 @@ dissect_ssl2_record(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, (initial_offset + record_length_length), record_length)) { - *conv_version = SSL_VER_PCT; - ssl_set_conv_version(pinfo, *conv_version); + *conv_version = SSL_VER_PCT; + /*ssl_set_conv_version(pinfo, ssl->version);*/ } else if (msg_type >= 2 && msg_type <= 8) { *conv_version = SSL_VER_SSLv2; - ssl_set_conv_version(pinfo, *conv_version); + /*ssl_set_conv_version(pinfo, ssl->version);*/ } } @@ -2136,7 +2777,7 @@ dissect_ssl2_record(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, proto_tree_add_boolean(ssl_record_tree, hf_ssl2_record_is_escape, tvb, initial_offset, 1, is_escape); - } + } if (ssl_record_tree && padding_length != -1) { proto_tree_add_uint(ssl_record_tree, @@ -2166,7 +2807,7 @@ dissect_ssl2_record(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, /* dissect the message (only handle client hello right now) */ switch (msg_type) { case SSL2_HND_CLIENT_HELLO: - dissect_ssl2_hnd_client_hello(tvb, ssl_record_tree, offset); + dissect_ssl2_hnd_client_hello(tvb, ssl_record_tree, offset, ssl); break; case SSL2_HND_CLIENT_MASTER_KEY: @@ -2219,7 +2860,8 @@ dissect_ssl2_record(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, static void dissect_ssl2_hnd_client_hello(tvbuff_t *tvb, - proto_tree *tree, guint32 offset) + proto_tree *tree, guint32 offset, + SslDecryptSession* ssl) { /* struct { * uint8 msg_type; @@ -2241,7 +2883,7 @@ dissect_ssl2_hnd_client_hello(tvbuff_t *tvb, guint16 challenge_length; proto_tree *ti; - proto_tree *cs_tree; + proto_tree *cs_tree=0; version = tvb_get_ntohs(tvb, offset); if (!ssl_is_valid_ssl_version(version)) @@ -2250,46 +2892,54 @@ dissect_ssl2_hnd_client_hello(tvbuff_t *tvb, return; } - if (tree) + if (tree || ssl) { /* show the version */ - proto_tree_add_item(tree, hf_ssl_record_version, tvb, + if (tree) + proto_tree_add_item(tree, hf_ssl_record_version, tvb, offset, 2, FALSE); offset += 2; cipher_spec_length = tvb_get_ntohs(tvb, offset); - proto_tree_add_item(tree, hf_ssl2_handshake_cipher_spec_len, + if (tree) + proto_tree_add_item(tree, hf_ssl2_handshake_cipher_spec_len, tvb, offset, 2, FALSE); offset += 2; session_id_length = tvb_get_ntohs(tvb, offset); - proto_tree_add_item(tree, hf_ssl2_handshake_session_id_len, + if (tree) + proto_tree_add_item(tree, hf_ssl2_handshake_session_id_len, tvb, offset, 2, FALSE); offset += 2; challenge_length = tvb_get_ntohs(tvb, offset); - proto_tree_add_item(tree, hf_ssl2_handshake_challenge_len, + if (tree) + proto_tree_add_item(tree, hf_ssl2_handshake_challenge_len, tvb, offset, 2, FALSE); offset += 2; - /* tell the user how many cipher specs they've won */ - tvb_ensure_bytes_exist(tvb, offset, cipher_spec_length); - ti = proto_tree_add_none_format(tree, hf_ssl_handshake_cipher_suites, + if (tree) + { + /* tell the user how many cipher specs they've won */ + tvb_ensure_bytes_exist(tvb, offset, cipher_spec_length); + ti = proto_tree_add_none_format(tree, hf_ssl_handshake_cipher_suites, tvb, offset, cipher_spec_length, "Cipher Specs (%u specs)", cipher_spec_length/3); - /* make this a subtree and expand the actual specs below */ - cs_tree = proto_item_add_subtree(ti, ett_ssl_cipher_suites); - if (!cs_tree) - { - cs_tree = tree; /* failsafe */ + /* make this a subtree and expand the actual specs below */ + cs_tree = proto_item_add_subtree(ti, ett_ssl_cipher_suites); + if (!cs_tree) + { + cs_tree = tree; /* failsafe */ + } } /* iterate through the cipher specs, showing them */ while (cipher_spec_length > 0) { - proto_tree_add_item(cs_tree, hf_ssl2_handshake_cipher_spec, + if (cs_tree) + proto_tree_add_item(cs_tree, hf_ssl2_handshake_cipher_spec, tvb, offset, 3, FALSE); offset += 3; /* length of one cipher spec */ cipher_spec_length -= 3; @@ -2298,15 +2948,26 @@ dissect_ssl2_hnd_client_hello(tvbuff_t *tvb, /* if there's a session id, show it */ if (session_id_length > 0) { - tvb_ensure_bytes_exist(tvb, offset, session_id_length); - proto_tree_add_bytes_format(tree, - hf_ssl_handshake_session_id, - tvb, offset, session_id_length, - tvb_get_ptr(tvb, offset, session_id_length), - "Session ID (%u byte%s)", - session_id_length, - plurality(session_id_length, "", "s")); - + if (tree) + { + tvb_ensure_bytes_exist(tvb, offset, session_id_length); + proto_tree_add_bytes_format(tree, + hf_ssl_handshake_session_id, + tvb, offset, session_id_length, + tvb_get_ptr(tvb, offset, session_id_length), + "Session ID (%u byte%s)", + session_id_length, + plurality(session_id_length, "", "s")); + } + + //PAOLO: get session id and reset session state for key [re]negotiation + if (ssl) + { + tvb_memcpy(tvb,ssl->session_id.data, offset, session_id_length); + ssl->session_id.data_len = session_id_length; + ssl->state &= ~(SSL_HAVE_SESSION_KEY|SSL_MASTER_SECRET| + SSL_CIPHER|SSL_SERVER_RANDOM); + } offset += session_id_length; } @@ -2314,8 +2975,26 @@ dissect_ssl2_hnd_client_hello(tvbuff_t *tvb, if (challenge_length > 0) { tvb_ensure_bytes_exist(tvb, offset, challenge_length); - proto_tree_add_item(tree, hf_ssl2_handshake_challenge, + + if (tree) + proto_tree_add_item(tree, hf_ssl2_handshake_challenge, tvb, offset, challenge_length, 0); + if (ssl) + { + //PAOLO: get client random data; we get at most 32 bytes from + // challenge + int max = challenge_length > 32? 32: challenge_length; + + ssl_debug_printf("client random len: %d padded to 32\n", + challenge_length); + + // client random is padded with zero and 'right' aligned + memset(ssl->client_random.data, 0, 32 - max); + tvb_memcpy(tvb, &ssl->client_random.data[32 - max], offset, max); + ssl->client_random.data_len = 32; + ssl->state |= SSL_CLIENT_RANDOM; + + } offset += challenge_length; } } @@ -2864,7 +3543,7 @@ dissect_ssl2_hnd_server_hello(tvbuff_t *tvb, * Support Functions * *********************************************************************/ - +#if 0 static void ssl_set_conv_version(packet_info *pinfo, guint version) { @@ -2895,6 +3574,7 @@ ssl_set_conv_version(packet_info *pinfo, guint version) } conversation_add_proto_data(conversation, proto_ssl, GINT_TO_POINTER(version)); } +#endif static int ssl_is_valid_handshake_type(guint8 type) @@ -3181,6 +3861,9 @@ ssl_looks_like_valid_pct_handshake(tvbuff_t *tvb, guint32 offset, * Standard Ethereal Protocol Registration and housekeeping * *********************************************************************/ +#ifndef SSL_SUFFIX +#define SSL_SUFFIX "" +#endif void proto_register_ssl(void) { @@ -3222,6 +3905,12 @@ proto_register_ssl(void) FT_NONE, BASE_NONE, NULL, 0x0, "Payload is application data", HFILL } }, + { &hf_ssl_record_appdata_decrypted, + { "Application Data decrypted", "ssl.app_data_decrypted", + FT_STRING, BASE_NONE, NULL, 0x0, + "Payload is decrypted application data", HFILL } + }, + { & hf_ssl2_record, { "SSLv2/PCT Record Header", "ssl.record", FT_NONE, BASE_DEC, NULL, 0x0, @@ -3497,61 +4186,61 @@ proto_register_ssl(void) FT_NONE, BASE_NONE, NULL, 0x0, "Server's challenge to client", HFILL } }, - { &hf_pct_handshake_cipher_spec, - { "Cipher Spec", "pct.handshake.cipherspec", - FT_NONE, BASE_NONE, NULL, 0x0, - "PCT Cipher specification", HFILL } - }, - { &hf_pct_handshake_cipher, - { "Cipher", "pct.handshake.cipher", - FT_UINT16, BASE_HEX, VALS(pct_cipher_type), 0x0, - "PCT Ciper", HFILL } + { &hf_pct_handshake_cipher_spec, + { "Cipher Spec", "pct.handshake.cipherspec", + FT_NONE, BASE_NONE, NULL, 0x0, + "PCT Cipher specification", HFILL } + }, + { &hf_pct_handshake_cipher, + { "Cipher", "pct.handshake.cipher", + FT_UINT16, BASE_HEX, VALS(pct_cipher_type), 0x0, + "PCT Ciper", HFILL } }, - { &hf_pct_handshake_hash_spec, - { "Hash Spec", "pct.handshake.hashspec", - FT_NONE, BASE_NONE, NULL, 0x0, - "PCT Hash specification", HFILL } - }, - { &hf_pct_handshake_hash, - { "Hash", "pct.handshake.hash", - FT_UINT16, BASE_HEX, VALS(pct_hash_type), 0x0, - "PCT Hash", HFILL } - }, - { &hf_pct_handshake_cert_spec, - { "Cert Spec", "pct.handshake.certspec", - FT_NONE, BASE_NONE, NULL, 0x0, - "PCT Certificate specification", HFILL } - }, - { &hf_pct_handshake_cert, - { "Cert", "pct.handshake.cert", - FT_UINT16, BASE_HEX, VALS(pct_cert_type), 0x0, - "PCT Certificate", HFILL } - }, - { &hf_pct_handshake_exch_spec, - { "Exchange Spec", "pct.handshake.exchspec", - FT_NONE, BASE_NONE, NULL, 0x0, - "PCT Exchange specification", HFILL } - }, - { &hf_pct_handshake_exch, - { "Exchange", "pct.handshake.exch", - FT_UINT16, BASE_HEX, VALS(pct_exch_type), 0x0, - "PCT Exchange", HFILL } - }, - { &hf_pct_handshake_sig, - { "Sig Spec", "pct.handshake.sig", - FT_UINT16, BASE_HEX, VALS(pct_sig_type), 0x0, - "PCT Signature", HFILL } - }, - { &hf_pct_msg_error_type, - { "PCT Error Code", "pct.msg_error_code", - FT_UINT16, BASE_HEX, VALS(pct_error_code), 0x0, - "PCT Error Code", HFILL } - }, - { &hf_pct_handshake_server_cert, - { "Server Cert", "pct.handshake.server_cert", - FT_NONE, BASE_NONE, NULL , 0x0, - "PCT Server Certificate", HFILL } - }, + { &hf_pct_handshake_hash_spec, + { "Hash Spec", "pct.handshake.hashspec", + FT_NONE, BASE_NONE, NULL, 0x0, + "PCT Hash specification", HFILL } + }, + { &hf_pct_handshake_hash, + { "Hash", "pct.handshake.hash", + FT_UINT16, BASE_HEX, VALS(pct_hash_type), 0x0, + "PCT Hash", HFILL } + }, + { &hf_pct_handshake_cert_spec, + { "Cert Spec", "pct.handshake.certspec", + FT_NONE, BASE_NONE, NULL, 0x0, + "PCT Certificate specification", HFILL } + }, + { &hf_pct_handshake_cert, + { "Cert", "pct.handshake.cert", + FT_UINT16, BASE_HEX, VALS(pct_cert_type), 0x0, + "PCT Certificate", HFILL } + }, + { &hf_pct_handshake_exch_spec, + { "Exchange Spec", "pct.handshake.exchspec", + FT_NONE, BASE_NONE, NULL, 0x0, + "PCT Exchange specification", HFILL } + }, + { &hf_pct_handshake_exch, + { "Exchange", "pct.handshake.exch", + FT_UINT16, BASE_HEX, VALS(pct_exch_type), 0x0, + "PCT Exchange", HFILL } + }, + { &hf_pct_handshake_sig, + { "Sig Spec", "pct.handshake.sig", + FT_UINT16, BASE_HEX, VALS(pct_sig_type), 0x0, + "PCT Signature", HFILL } + }, + { &hf_pct_msg_error_type, + { "PCT Error Code", "pct.msg_error_code", + FT_UINT16, BASE_HEX, VALS(pct_error_code), 0x0, + "PCT Error Code", HFILL } + }, + { &hf_pct_handshake_server_cert, + { "Server Cert", "pct.handshake.server_cert", + FT_NONE, BASE_NONE, NULL , 0x0, + "PCT Server Certificate", HFILL } + }, }; /* Setup protocol subtree array */ @@ -3573,8 +4262,8 @@ proto_register_ssl(void) }; /* Register the protocol name and description */ - proto_ssl = proto_register_protocol("Secure Socket Layer", - "SSL", "ssl"); + proto_ssl = proto_register_protocol("Secure Socket Layer"SSL_SUFFIX, + "SSL"SSL_SUFFIX, "ssl"SSL_SUFFIX); /* Required function calls to register the header fields and * subtrees used */ @@ -3582,17 +4271,32 @@ proto_register_ssl(void) proto_register_subtree_array(ett, array_length(ett)); { - module_t *ssl_module = prefs_register_protocol(proto_ssl, NULL); + module_t *ssl_module = prefs_register_protocol(proto_ssl, ssl_parse); prefs_register_bool_preference(ssl_module, "desegment_ssl_records", "Reassemble SSL records spanning multiple TCP segments", "Whether the SSL dissector should reassemble SSL records spanning multiple TCP segments. " "To use this option, you must also enable \"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.", &ssl_desegment); + prefs_register_string_preference(ssl_module, "keys_list", "RSA keys list", + "comma separated list of private RSA keys used for SSL decryption; " + "each list entry must be in the form of <ip>:<port>:<key_file_name>\n" + "<key_file_name> is the local file name of the RSA private key used by the specified server\n", + (const char **)&ssl_keys_list); + prefs_register_string_preference(ssl_module, "ports_list", "SSL ports list", + "comma separated list of tcp ports numbers to be dissectes as SSL; " + "each list entry must be in the form of <port>:<clear-text-port>\n" + "<clear-text-port> is the port numbert associated with the protocol tunneled over SSL for this port\n", + (const char **)&ssl_ports_list); } - register_dissector("ssl", dissect_ssl, proto_ssl); - + register_dissector("ssl"SSL_SUFFIX, dissect_ssl, proto_ssl); + + register_init_routine(ssl_init); + ssl_lib_init(); + ssl_tap = register_tap("ssl"SSL_SUFFIX); + ssl_debug_printf("proto_register_ssl: registered tap %s:%d\n", + "ssl"SSL_SUFFIX, ssl_tap); } /* If this dissector uses sub-dissector registration add a registration @@ -3602,11 +4306,8 @@ proto_register_ssl(void) void proto_reg_handoff_ssl(void) { - dissector_handle_t ssl_handle; - - ssl_handle = find_dissector("ssl"); - dissector_add("tcp.port", TCP_PORT_SSL, ssl_handle); - dissector_add("tcp.port", TCP_PORT_SSL_LDAP, ssl_handle); - dissector_add("tcp.port", TCP_PORT_SSL_IMAP, ssl_handle); - dissector_add("tcp.port", TCP_PORT_SSL_POP, ssl_handle); + ssl_handle = find_dissector("ssl"SSL_SUFFIX); + + /* add now dissector to default ports.*/ + ssl_parse(); } diff --git a/gtk/Makefile.common b/gtk/Makefile.common index 93c14065e6..07e7040af6 100644 --- a/gtk/Makefile.common +++ b/gtk/Makefile.common @@ -95,6 +95,7 @@ ETHEREAL_GTK_SRC = \ sctp_error_dlg.c \ service_response_time_table.c \ simple_dialog.c \ + ssl-dlg.c \ stream_prefs.c \ summary_dlg.c \ supported_protos_dlg.c \ diff --git a/gtk/main.c b/gtk/main.c index 0716ff9201..758d886b82 100644 --- a/gtk/main.c +++ b/gtk/main.c @@ -3575,6 +3575,8 @@ create_main_window (gint pl_size, gint tv_size, gint bv_size, e_prefs *prefs) filter_te); set_menu_object_data("/Analyze/Follow TCP Stream", E_DFILTER_TE_KEY, filter_te); + set_menu_object_data("/Analyze/Follow SSL Stream", E_DFILTER_TE_KEY, + filter_te); set_menu_object_data("/Analyze/Apply as Filter/Selected", E_DFILTER_TE_KEY, filter_te); set_menu_object_data("/Analyze/Apply as Filter/Not Selected", E_DFILTER_TE_KEY, diff --git a/gtk/menu.c b/gtk/menu.c index 1d69b863ac..8be15950bc 100644 --- a/gtk/menu.c +++ b/gtk/menu.c @@ -171,6 +171,8 @@ File/Close: the Gnome HIG suggests putting this item just above the Quit currently opened/captured file only. */ +void +ssl_stream_cb(GtkWidget * w, gpointer data _U_); /* main menu */ static GtkItemFactoryEntry menu_items[] = @@ -377,6 +379,8 @@ static GtkItemFactoryEntry menu_items[] = ITEM_FACTORY_ENTRY("/Analyze/<separator>", NULL, NULL, 0, "<Separator>", NULL), ITEM_FACTORY_ENTRY("/Analyze/_Follow TCP Stream", NULL, follow_stream_cb, 0, NULL, NULL), + ITEM_FACTORY_ENTRY("/Analyze/_Follow SSL Stream", NULL, + ssl_stream_cb, 0, NULL, NULL), ITEM_FACTORY_ENTRY("/_Statistics", NULL, NULL, 0, "<Branch>", NULL), ITEM_FACTORY_STOCK_ENTRY("/Statistics/_Summary", NULL, summary_open_cb, 0, GTK_STOCK_PROPERTIES), ITEM_FACTORY_ENTRY("/Statistics/_Protocol Hierarchy", NULL, @@ -458,6 +462,8 @@ static GtkItemFactoryEntry packet_list_menu_items[] = ITEM_FACTORY_ENTRY("/Follow TCP Stream", NULL, follow_stream_cb, 0, NULL, NULL), + ITEM_FACTORY_ENTRY("/Follow SSL Stream", NULL, ssl_stream_cb, + 0, NULL, NULL), ITEM_FACTORY_ENTRY("/<separator>", NULL, NULL, 0, "<Separator>", NULL), @@ -505,6 +511,8 @@ static GtkItemFactoryEntry tree_view_menu_items[] = ITEM_FACTORY_ENTRY("/Follow TCP Stream", NULL, follow_stream_cb, 0, NULL, NULL), + ITEM_FACTORY_ENTRY("/Follow SSL Stream", NULL, ssl_stream_cb, + 0, NULL, NULL), ITEM_FACTORY_ENTRY("/<separator>", NULL, NULL, 0, "<Separator>", NULL), @@ -1988,6 +1996,12 @@ set_menus_for_selected_packet(capture_file *cf) cf->current_frame != NULL ? (cf->edt->pi.ipproto == IP_PROTO_TCP) : FALSE); set_menu_sensitivity(tree_view_menu_factory, "/Follow TCP Stream", cf->current_frame != NULL ? (cf->edt->pi.ipproto == IP_PROTO_TCP) : FALSE); + set_menu_sensitivity(main_menu_factory, "/Analyze/Follow SSL Stream", + cf->current_frame != NULL ? (cf->edt->pi.ipproto == IP_PROTO_TCP) : FALSE); + set_menu_sensitivity(packet_list_menu_factory, "/Follow SSL Stream", + cf->current_frame != NULL ? (cf->edt->pi.ipproto == IP_PROTO_TCP) : FALSE); + set_menu_sensitivity(tree_view_menu_factory, "/Follow SSL Stream", + cf->current_frame != NULL ? (cf->edt->pi.ipproto == IP_PROTO_TCP) : FALSE); set_menu_sensitivity(main_menu_factory, "/Analyze/Decode As...", cf->current_frame != NULL && decode_as_ok()); set_menu_sensitivity(packet_list_menu_factory, "/Decode As...", diff --git a/gtk/ssl-dlg.c b/gtk/ssl-dlg.c new file mode 100644 index 0000000000..91354dce6b --- /dev/null +++ b/gtk/ssl-dlg.c @@ -0,0 +1,1049 @@ +/* ssl_dlg.c + * + * $Id$ + * + * This program 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; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include <gtk/gtk.h> + +#include <stdio.h> +#include <string.h> + + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <ctype.h> + +#include <color.h> +#include <gtk/colors.h> +#include <gtk/main.h> +#include <epan/follow.h> +#include <gtk/dlg_utils.h> +#include <gtk/keys.h> +#include <globals.h> +#include <alert_box.h> +#include <simple_dialog.h> +#include <epan/dissectors/packet-ipv6.h> +#include <epan/prefs.h> +#include <epan/addr_resolv.h> +#include <epan/charsets.h> +#include <util.h> +#include <gtk/gui_utils.h> +#include <epan/epan_dissect.h> +#include <epan/filesystem.h> +#include <gtk/compat_macros.h> +#include <epan/ipproto.h> +#include <gtk/font_utils.h> +#include <wiretap/file_util.h> +#include <epan/tap.h> + +#ifdef SSL_PLUGIN +#include "packet-ssl-utils.h" +#else +#include <epan/dissectors/packet-ssl-utils.h> +#endif + +#ifndef SSL_SUFFIX +#define SSL_SUFFIX "" +#endif + +/* Show Stream */ +typedef enum { + FROM_CLIENT, + FROM_SERVER, + BOTH_HOSTS +} show_stream_t; + +/* Show Type */ +typedef enum { + SHOW_ASCII, + SHOW_HEXDUMP, + SHOW_CARRAY, + SHOW_RAW +} show_type_t; + +typedef struct { + show_stream_t show_stream; + show_type_t show_type; + GtkWidget *text; + GtkWidget *ascii_bt; + GtkWidget *ebcdic_bt; + GtkWidget *hexdump_bt; + GtkWidget *carray_bt; + GtkWidget *raw_bt; + GtkWidget *follow_save_as_w; + gboolean is_ipv6; + char *filter_out_filter; + GtkWidget *filter_te; + GtkWidget *streamwindow; + GList *ssl_decrypted_data; + guint bytes_written[2]; + guint client_port; + char client_ip[MAX_IPADDR_LEN]; +} follow_info_t; + +static void follow_destroy_cb(GtkWidget * win, gpointer data); +static void follow_charset_toggle_cb(GtkWidget * w, gpointer parent_w); +static void follow_load_text(follow_info_t *follow_info); +static void follow_filter_out_stream(GtkWidget * w, gpointer parent_w); +static void follow_save_as_cmd_cb(GtkWidget * w, gpointer data); +static void follow_save_as_ok_cb(GtkWidget * w, gpointer fs); +static void follow_save_as_destroy_cb(GtkWidget * win, gpointer user_data); +static void follow_stream_om_both(GtkWidget * w, gpointer data); +static void follow_stream_om_client(GtkWidget * w, gpointer data); +static void follow_stream_om_server(GtkWidget * w, gpointer data); + + +#define E_FOLLOW_INFO_KEY "follow_info_key" + +/* List of "follow_info_t" structures for all "Follow TCP Stream" windows, + so we can redraw them all if the colors or font changes. */ +static GList *follow_infos; + +typedef struct { + gboolean is_server; + StringInfo* data; +} SslDecryptedRecord; + +/* Add a "follow_info_t" structure to the list. */ +static void +remember_follow_info(follow_info_t *follow_info) +{ + follow_infos = g_list_append(follow_infos, follow_info); +} + +/* Remove a "follow_info_t" structure from the list. */ +static void +forget_follow_info(follow_info_t *follow_info) +{ + follow_infos = g_list_remove(follow_infos, follow_info); +} + +static int +ssl_queue_packet_data(void *tapdata, packet_info *pinfo, epan_dissect_t *edt, const void *ssl) +{ + follow_info_t* follow_info = tapdata; + SslDecryptedRecord* rec; + int proto_ssl = (int) ssl; + StringInfo* data = p_get_proto_data(pinfo->fd, proto_ssl); + /*ssl_debug_printf("ssl_queue_packet_data: pinfo %p proto_ssl %d data %p\n", + pinfo, proto_ssl, data);*/ + + /* skip packet without decrypted data payload*/ + if (!data) + return 0; + + /* compute packet direction */ + rec = g_malloc(sizeof(SslDecryptedRecord)); + + if (follow_info->client_port == 0) { + follow_info->client_port = pinfo->srcport; + memcpy(follow_info->client_ip, pinfo->src.data, pinfo->src.len); + } + if (memcmp(follow_info->client_ip, pinfo->src.data, pinfo->src.len) == 0 && + follow_info->client_port == pinfo->srcport) { + rec->is_server = 0; + } + else + rec->is_server = 1; + + /* update stream counter */ + follow_info->bytes_written[rec->is_server] += data->data_len; + + /* extract decrypted data and queue it locally */ + rec->data = data; + follow_info->ssl_decrypted_data = g_list_append( + follow_info->ssl_decrypted_data,rec); + /*ssl_debug_printf("ssl_queue_packet_data: ssl_decrypted_data %p data len %d\n", + follow_info->ssl_decrypted_data, data->data_len);*/ + + return 0; +} + +/* Follow the TCP stream, if any, to which the last packet that we called + a dissection routine on belongs (this might be the most recently + selected packet, or it might be the last packet in the file). */ +void +ssl_stream_cb(GtkWidget * w, gpointer data _U_) +{ + GtkWidget *streamwindow, *vbox, *txt_scrollw, *text, *filter_te; + GtkWidget *hbox, *button_hbox, *button, *radio_bt; + GtkWidget *stream_fr, *stream_vb; + GtkWidget *stream_om, *stream_menu, *stream_mi; + GtkTooltips *tooltips; + gchar *follow_filter; + const gchar *previous_filter; + int filter_out_filter_len, previus_filter_len; + const char *hostname0, *hostname1; + char *port0, *port1; + char string[128]; + follow_tcp_stats_t stats; + follow_info_t *follow_info; + GString* msg; + + /* we got tcp so we can follow */ + if (cfile.edt->pi.ipproto != 6) { + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Error following stream. Please make\n" + "sure you have an SSL packet selected."); + return; + } + + follow_info = g_new0(follow_info_t, 1); + + /* data will be passed via tap callback*/ + msg = register_tap_listener("ssl"SSL_SUFFIX, follow_info, NULL, + NULL, ssl_queue_packet_data, NULL); + if (msg) + { + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Can't register ssl tap: %s\n",msg->str); + return; + } + + /* Create a new filter that matches all packets in the TCP stream, + and set the display filter entry accordingly */ + reset_tcp_reassembly(); + follow_filter = build_follow_filter(&cfile.edt->pi); + + /* Set the display filter entry accordingly */ + filter_te = OBJECT_GET_DATA(w, E_DFILTER_TE_KEY); + + /* needed in follow_filter_out_stream(), is there a better way? */ + follow_info->filter_te = filter_te; + + /* save previous filter, const since we're not supposed to alter */ + previous_filter = + (const gchar *)gtk_entry_get_text(GTK_ENTRY(filter_te)); + + /* allocate our new filter. API claims g_malloc terminates program on failure */ + /* my calc for max alloc needed is really +10 but when did a few extra bytes hurt ? */ + previus_filter_len = previous_filter?strlen(previous_filter):0; + filter_out_filter_len = strlen(follow_filter) + previus_filter_len + 16; + follow_info->filter_out_filter = (gchar *)g_malloc(filter_out_filter_len); + + /* append the negation */ + if(previus_filter_len) { + g_snprintf(follow_info->filter_out_filter, filter_out_filter_len, + "%s and !(%s)", previous_filter, follow_filter); + } else { + g_snprintf(follow_info->filter_out_filter, filter_out_filter_len, + "!(%s)", follow_filter); + } + + + gtk_entry_set_text(GTK_ENTRY(filter_te), follow_filter); + + /* Run the display filter so it goes in effect - even if it's the + same as the previous display filter. */ + main_filter_packets(&cfile, follow_filter, TRUE); + + /* Free the filter string, as we're done with it. */ + g_free(follow_filter); + + /* The data_out_file should now be full of the streams information */ + remove_tap_listener(follow_info); + + /* The data_out_filename file now has all the text that was in the session */ + streamwindow = dlg_window_new("Follow TCP stream"); + + /* needed in follow_filter_out_stream(), is there a better way? */ + follow_info->streamwindow = streamwindow; + + gtk_widget_set_name(streamwindow, "TCP stream window"); + gtk_window_set_default_size(GTK_WINDOW(streamwindow), DEF_WIDTH, DEF_HEIGHT); + gtk_container_border_width(GTK_CONTAINER(streamwindow), 6); + + /* setup the container */ + tooltips = gtk_tooltips_new (); + + vbox = gtk_vbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(streamwindow), vbox); + + /* content frame */ + if (incomplete_tcp_stream) { + stream_fr = gtk_frame_new("Stream Content (incomplete)"); + } else { + stream_fr = gtk_frame_new("Stream Content"); + } + gtk_container_add(GTK_CONTAINER(vbox), stream_fr); + gtk_widget_show(stream_fr); + + stream_vb = gtk_vbox_new(FALSE, 6); + gtk_container_set_border_width( GTK_CONTAINER(stream_vb) , 6); + gtk_container_add(GTK_CONTAINER(stream_fr), stream_vb); + + /* create a scrolled window for the text */ + txt_scrollw = scrolled_window_new(NULL, NULL); +#if GTK_MAJOR_VERSION >= 2 + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(txt_scrollw), + GTK_SHADOW_IN); +#endif + gtk_box_pack_start(GTK_BOX(stream_vb), txt_scrollw, TRUE, TRUE, 0); + + /* create a text box */ +#if GTK_MAJOR_VERSION < 2 + text = gtk_text_new(NULL, NULL); + gtk_text_set_editable(GTK_TEXT(text), FALSE); +#else + text = gtk_text_view_new(); + gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE); +#endif + gtk_container_add(GTK_CONTAINER(txt_scrollw), text); + follow_info->text = text; + + + /* stream hbox */ + hbox = gtk_hbox_new(FALSE, 1); + gtk_box_pack_start(GTK_BOX(stream_vb), hbox, FALSE, FALSE, 0); + + /* Create Save As Button */ + button = BUTTON_NEW_FROM_STOCK(GTK_STOCK_SAVE_AS); + SIGNAL_CONNECT(button, "clicked", follow_save_as_cmd_cb, follow_info); + gtk_tooltips_set_tip (tooltips, button, "Save the content as currently displayed ", NULL); + gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); + + /* Stream to show */ + follow_tcp_stats(&stats); + + if (stats.is_ipv6) { + struct e_in6_addr ipaddr; + memcpy(&ipaddr, stats.ip_address[0], 16); + hostname0 = get_hostname6(&ipaddr); + memcpy(&ipaddr, stats.ip_address[0], 16); + hostname1 = get_hostname6(&ipaddr); + } else { + guint32 ipaddr; + memcpy(&ipaddr, stats.ip_address[0], 4); + hostname0 = get_hostname(ipaddr); + memcpy(&ipaddr, stats.ip_address[1], 4); + hostname1 = get_hostname(ipaddr); + } + + port0 = get_tcp_port(stats.tcp_port[0]); + port1 = get_tcp_port(stats.tcp_port[1]); + + follow_info->is_ipv6 = stats.is_ipv6; + + stream_om = gtk_option_menu_new(); + stream_menu = gtk_menu_new(); + + /* Both Stream Directions */ + g_snprintf(string, sizeof(string), + "Entire conversation (%u bytes)", + follow_info->bytes_written[0] + follow_info->bytes_written[1]); + stream_mi = gtk_menu_item_new_with_label(string); + SIGNAL_CONNECT(stream_mi, "activate", follow_stream_om_both, + follow_info); + gtk_menu_append(GTK_MENU(stream_menu), stream_mi); + gtk_widget_show(stream_mi); + follow_info->show_stream = BOTH_HOSTS; + + /* Host 0 --> Host 1 */ + g_snprintf(string, sizeof(string), "%s:%s --> %s:%s (%u bytes)", + hostname0, port0, hostname1, port1, + follow_info->bytes_written[0]); + stream_mi = gtk_menu_item_new_with_label(string); + SIGNAL_CONNECT(stream_mi, "activate", follow_stream_om_client, + follow_info); + gtk_menu_append(GTK_MENU(stream_menu), stream_mi); + gtk_widget_show(stream_mi); + + /* Host 1 --> Host 0 */ + g_snprintf(string, sizeof(string), "%s:%s --> %s:%s (%u bytes)", + hostname1, port1, hostname0, port0, + follow_info->bytes_written[1]); + stream_mi = gtk_menu_item_new_with_label(string); + SIGNAL_CONNECT(stream_mi, "activate", follow_stream_om_server, + follow_info); + gtk_menu_append(GTK_MENU(stream_menu), stream_mi); + gtk_widget_show(stream_mi); + + gtk_option_menu_set_menu(GTK_OPTION_MENU(stream_om), stream_menu); + /* Set history to 0th item, i.e., the first item. */ + gtk_option_menu_set_history(GTK_OPTION_MENU(stream_om), 0); + gtk_tooltips_set_tip (tooltips, stream_om, + "Select the stream direction to display", NULL); + gtk_box_pack_start(GTK_BOX(hbox), stream_om, FALSE, FALSE, 0); + + /* ASCII radio button */ + radio_bt = gtk_radio_button_new_with_label(NULL, "ASCII"); + gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"ASCII\" format", NULL); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), TRUE); + gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0); + SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb, + follow_info); + follow_info->ascii_bt = radio_bt; + follow_info->show_type = SHOW_ASCII; + + /* HEX DUMP radio button */ + radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group + (GTK_RADIO_BUTTON(radio_bt)), + "Hex Dump"); + gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"Hexdump\" format", NULL); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE); + gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0); + SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb, + follow_info); + follow_info->hexdump_bt = radio_bt; + + /* C Array radio button */ + radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group + (GTK_RADIO_BUTTON(radio_bt)), + "C Arrays"); + gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"C Array\" format", NULL); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE); + gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0); + SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb, + follow_info); + follow_info->carray_bt = radio_bt; + + /* Raw radio button */ + radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group + (GTK_RADIO_BUTTON(radio_bt)), + "Raw"); + gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"Raw\" (binary) format. " + "As this contains non printable characters, the screen output will be in ASCII format", NULL); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE); + gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0); + SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb, + follow_info); + follow_info->raw_bt = radio_bt; + + /* button hbox */ + button_hbox = gtk_hbutton_box_new(); + gtk_box_pack_start(GTK_BOX(vbox), button_hbox, FALSE, FALSE, 0); + gtk_button_box_set_layout (GTK_BUTTON_BOX(button_hbox), GTK_BUTTONBOX_END); + gtk_button_box_set_spacing(GTK_BUTTON_BOX(button_hbox), 5); + + /* Create exclude stream button */ + button = gtk_button_new_with_label("Filter out this stream"); + SIGNAL_CONNECT(button, "clicked", follow_filter_out_stream, follow_info); + gtk_tooltips_set_tip (tooltips, button, + "Build a display filter which cuts this stream from the capture", NULL); + gtk_box_pack_start(GTK_BOX(button_hbox), button, FALSE, FALSE, 0); + + /* Create Close Button */ + button = BUTTON_NEW_FROM_STOCK(GTK_STOCK_CLOSE); + gtk_tooltips_set_tip (tooltips, button, + "Close the dialog and keep the current display filter", NULL); + gtk_box_pack_start(GTK_BOX(button_hbox), button, FALSE, FALSE, 0); + GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); + + window_set_cancel_button(streamwindow, button, window_cancel_button_cb); + + /* Tuck away the follow_info object into the window */ + OBJECT_SET_DATA(streamwindow, E_FOLLOW_INFO_KEY, follow_info); + + follow_load_text(follow_info); + remember_follow_info(follow_info); + + SIGNAL_CONNECT(streamwindow, "delete_event", window_delete_event_cb, NULL); + SIGNAL_CONNECT(streamwindow, "destroy", follow_destroy_cb, NULL); + + /* Make sure this widget gets destroyed if we quit the main loop, + so that if we exit, we clean up any temporary files we have + for "Follow TCP Stream" windows. */ + gtk_quit_add_destroy(gtk_main_level(), GTK_OBJECT(streamwindow)); + + gtk_widget_show_all(streamwindow); + window_present(streamwindow); +} + +/* The destroy call back has the responsibility of + * unlinking the temporary file + * and freeing the filter_out_filter */ +static void +follow_destroy_cb(GtkWidget *w, gpointer data _U_) +{ + GList* cur; + follow_info_t *follow_info; + + follow_info = OBJECT_GET_DATA(w, E_FOLLOW_INFO_KEY); + g_free(follow_info->filter_out_filter); + forget_follow_info(follow_info); + + /* free decrypted data list*/ + for (cur = follow_info->ssl_decrypted_data; cur; cur = g_list_next(cur)) + if (cur->data) + { + /*ssl_debug_printf("follow_destroy_cb: freeing chunk %p\n", cur->data);*/ + g_free(cur->data); + cur->data = NULL; + } + g_list_free (follow_info->ssl_decrypted_data); + g_free(follow_info); +} + +/* XXX - can I emulate follow_charset_toggle_cb() instead of having + * 3 different functions here? + * That might not be a bad idea, as it might mean we only reload + * the window once, not twice - see follow_charset_toggle_cb() + * for an explanation. */ +static void +follow_stream_om_both(GtkWidget *w _U_, gpointer data) +{ + follow_info_t *follow_info = data; + follow_info->show_stream = BOTH_HOSTS; + follow_load_text(follow_info); +} + +static void +follow_stream_om_client(GtkWidget *w _U_, gpointer data) +{ + follow_info_t *follow_info = data; + follow_info->show_stream = FROM_CLIENT; + follow_load_text(follow_info); +} + +static void +follow_stream_om_server(GtkWidget *w _U_, gpointer data) +{ + follow_info_t *follow_info = data; + follow_info->show_stream = FROM_SERVER; + follow_load_text(follow_info); +} + + +/* Handles the display style toggling */ +static void +follow_charset_toggle_cb(GtkWidget * w _U_, gpointer data) +{ + follow_info_t *follow_info = data; + + /* + * A radio button toggles when it goes on and when it goes + * off, so when you click a radio button two signals are + * delivered. We only want to reprocess the display once, + * so we do it only when the button goes on. + */ + if (GTK_TOGGLE_BUTTON(w)->active) { + if (w == follow_info->hexdump_bt) + follow_info->show_type = SHOW_HEXDUMP; + else if (w == follow_info->carray_bt) + follow_info->show_type = SHOW_CARRAY; + else if (w == follow_info->ascii_bt) + follow_info->show_type = SHOW_ASCII; + else if (w == follow_info->raw_bt) + follow_info->show_type = SHOW_RAW; + follow_load_text(follow_info); + } +} + +#define FLT_BUF_SIZE 1024 + +typedef enum { + FRS_OK, + FRS_OPEN_ERROR, + FRS_READ_ERROR, + FRS_PRINT_ERROR +} frs_return_t; + +/* + * XXX - the routine pointed to by "print_line" doesn't get handed lines, + * it gets handed bufferfuls. That's fine for "follow_write_raw()" + * and "follow_add_to_gtk_text()", but, as "follow_print_text()" calls + * the "print_line()" routine from "print.c", and as that routine might + * genuinely expect to be handed a line (if, for example, it's using + * some OS or desktop environment's printing API, and that API expects + * to be handed lines), "follow_print_text()" should probably accumulate + * lines in a buffer and hand them "print_line()". (If there's a + * complete line in a buffer - i.e., there's nothing of the line in + * the previous buffer or the next buffer - it can just hand that to + * "print_line()" after filtering out non-printables, as an + * optimization.) + * + * This might or might not be the reason why C arrays display + * correctly but get extra blank lines very other line when printed. + */ +static frs_return_t +follow_read_stream(follow_info_t *follow_info, + gboolean (*print_line) (char *, size_t, gboolean, void *), + void *arg) +{ + int iplen; + guint32 current_pos, global_client_pos = 0, global_server_pos = 0; + guint32 *global_pos; + gboolean skip; + gchar initbuf[256]; + guint32 server_packet_count = 0; + guint32 client_packet_count = 0; + static const gchar hexchars[16] = "0123456789abcdef"; + GList* cur; + + iplen = (follow_info->is_ipv6) ? 16 : 4; + + /*ssl_debug_printf("follow_read_stream: iplen %d list %p\n", iplen, + follow_info->ssl_decrypted_data);*/ + + for (cur = follow_info->ssl_decrypted_data; cur; cur = g_list_next(cur)) { + SslDecryptedRecord* rec = cur->data; + skip = FALSE; + if (!rec->is_server) { + global_pos = &global_client_pos; + if (follow_info->show_stream == FROM_SERVER) { + skip = TRUE; + } + } + else { + global_pos = &global_server_pos; + if (follow_info->show_stream == FROM_CLIENT) { + skip = TRUE; + } + } + + if (!skip) { + size_t nchars = rec->data->data_len; + char* buffer = (char*) rec->data->data; + + /*ssl_debug_printf("follow_read_stream: chunk len %d is_server %d\n", + nchars, rec->is_server);*/ + + switch (follow_info->show_type) { + + case SHOW_ASCII: + /* If our native arch is EBCDIC, call: + * ASCII_TO_EBCDIC(buffer, nchars); + */ + if (!(*print_line) (buffer, nchars, rec->is_server, arg)) + goto print_error; + break; + + case SHOW_RAW: + /* Don't translate, no matter what the native arch + * is. + */ + if (!(*print_line) (buffer, nchars, rec->is_server, arg)) + goto print_error; + break; + + case SHOW_HEXDUMP: + current_pos = 0; + while (current_pos < nchars) { + gchar hexbuf[256]; + int i; + gchar *cur = hexbuf, *ascii_start; + + /* is_server indentation : put 78 spaces at the + * beginning of the string */ + if (rec->is_server && follow_info->show_stream == BOTH_HOSTS) { + memset(cur, ' ', 78); + cur += 78; + } + cur += g_snprintf(cur, 20, "%08X ", *global_pos); + /* 49 is space consumed by hex chars */ + ascii_start = cur + 49; + for (i = 0; i < 16 && current_pos + i < nchars; i++) { + *cur++ = + hexchars[(buffer[current_pos + i] & 0xf0) >> 4]; + *cur++ = + hexchars[buffer[current_pos + i] & 0x0f]; + *cur++ = ' '; + if (i == 7) + *cur++ = ' '; + } + /* Fill it up if column isn't complete */ + while (cur < ascii_start) + *cur++ = ' '; + + /* Now dump bytes as text */ + for (i = 0; i < 16 && current_pos + i < nchars; i++) { + *cur++ = + (isprint((guchar)buffer[current_pos + i]) ? + buffer[current_pos + i] : '.' ); + if (i == 7) { + *cur++ = ' '; + } + } + current_pos += i; + (*global_pos) += i; + *cur++ = '\n'; + *cur = 0; + if (!(*print_line) (hexbuf, strlen(hexbuf), rec->is_server, arg)) + goto print_error; + } + break; + + case SHOW_CARRAY: + current_pos = 0; + g_snprintf(initbuf, sizeof(initbuf), "char peer%d_%d[] = {\n", + rec->is_server ? 1 : 0, + rec->is_server ? server_packet_count++ : client_packet_count++); + if (!(*print_line) (initbuf, strlen(initbuf), rec->is_server, arg)) + goto print_error; + while (current_pos < nchars) { + gchar hexbuf[256]; + int i, cur; + + cur = 0; + for (i = 0; i < 8 && current_pos + i < nchars; i++) { + /* Prepend entries with "0x" */ + hexbuf[cur++] = '0'; + hexbuf[cur++] = 'x'; + hexbuf[cur++] = + hexchars[(buffer[current_pos + i] & 0xf0) >> 4]; + hexbuf[cur++] = + hexchars[buffer[current_pos + i] & 0x0f]; + + /* Delimit array entries with a comma */ + if (current_pos + i + 1 < nchars) + hexbuf[cur++] = ','; + + hexbuf[cur++] = ' '; + } + + /* Terminate the array if we are at the end */ + if (current_pos + i == nchars) { + hexbuf[cur++] = '}'; + hexbuf[cur++] = ';'; + } + + current_pos += i; + (*global_pos) += i; + hexbuf[cur++] = '\n'; + hexbuf[cur] = 0; + if (!(*print_line) (hexbuf, strlen(hexbuf), rec->is_server, arg)) + goto print_error; + } + break; + } + } + } + return FRS_OK; + +print_error: + return FRS_PRINT_ERROR; +} + +/* + * XXX - for text printing, we probably want to wrap lines at 80 characters; + * (PostScript printing is doing this already), and perhaps put some kind of + * dingbat (to use the technical term) to indicate a wrapped line, along the + * lines of what's done when displaying this in a window, as per Warren Young's + * suggestion. + */ +static gboolean +follow_print_text(char *buffer, size_t nchars, gboolean is_server _U_, void *arg) +{ + print_stream_t *stream = arg; + size_t i; + char *str; + + /* convert non printable characters */ + for (i = 0; i < nchars; i++) { + if (buffer[i] == '\n' || buffer[i] == '\r') + continue; + if (! isprint((guchar)buffer[i])) { + buffer[i] = '.'; + } + } + + /* convert unterminated char array to a zero terminated string */ + str = g_malloc(nchars + 1); + memcpy(str, buffer, nchars); + str[nchars] = 0; + print_line(stream, /*indent*/ 0, str); + g_free(str); + + return TRUE; +} + +static gboolean +follow_write_raw(char *buffer, size_t nchars, gboolean is_server _U_, void *arg) +{ + FILE *fh = arg; + size_t nwritten; + + nwritten = fwrite(buffer, 1, nchars, fh); + if (nwritten != nchars) + return FALSE; + + return TRUE; +} + +static void +follow_filter_out_stream(GtkWidget * w _U_, gpointer data) +{ + follow_info_t *follow_info = data; + + /* Lock out user from messing with us. (ie. don't free our data!) */ + gtk_widget_set_sensitive(follow_info->streamwindow, FALSE); + + /* Set the display filter. */ + gtk_entry_set_text(GTK_ENTRY(follow_info->filter_te), follow_info->filter_out_filter); + + /* Run the display filter so it goes in effect. */ + main_filter_packets(&cfile, follow_info->filter_out_filter, FALSE); + + /* we force a subsequent close */ + window_destroy(follow_info->streamwindow); + + return; +} + +/* static variable declarations to speed up the performance + * of follow_load_text and follow_add_to_gtk_text + */ +static GdkColor server_fg, server_bg; +static GdkColor client_fg, client_bg; +#if GTK_MAJOR_VERSION >= 2 +static GtkTextTag *server_tag, *client_tag; +#endif + +static gboolean +follow_add_to_gtk_text(char *buffer, size_t nchars, gboolean is_server, + void *arg) +{ + GtkWidget *text = arg; +#if GTK_MAJOR_VERSION >= 2 + GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); + GtkTextIter iter; +#endif + +#if GTK_MAJOR_VERSION >= 2 || GTK_MINOR_VERSION >= 3 + /* While our isprint() hack is in place, we + * have to use convert some chars to '.' in order + * to be able to see the data we *should* see + * in the GtkText widget. + */ + size_t i; + + for (i = 0; i < nchars; i++) { + if (buffer[i] == '\n' || buffer[i] == '\r') + continue; + if (! isprint(buffer[i])) { + buffer[i] = '.'; + } + } +#endif + +#if GTK_MAJOR_VERSION < 2 + if (is_server) { + gtk_text_insert(GTK_TEXT(text), user_font_get_regular(), &server_fg, + &server_bg, buffer, nchars); + } else { + gtk_text_insert(GTK_TEXT(text), user_font_get_regular(), &client_fg, + &client_bg, buffer, nchars); + } +#else + gtk_text_buffer_get_end_iter(buf, &iter); + if (is_server) { + gtk_text_buffer_insert_with_tags(buf, &iter, buffer, nchars, + server_tag, NULL); + } else { + gtk_text_buffer_insert_with_tags(buf, &iter, buffer, nchars, + client_tag, NULL); + } +#endif + return TRUE; +} + +static void +follow_load_text(follow_info_t *follow_info) +{ +#if GTK_MAJOR_VERSION < 2 + int bytes_already; +#else + GtkTextBuffer *buf; + + buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(follow_info->text)); +#endif + + /* prepare colors one time for repeated use by follow_add_to_gtk_text */ + color_t_to_gdkcolor(&server_fg, &prefs.st_server_fg); + color_t_to_gdkcolor(&server_bg, &prefs.st_server_bg); + color_t_to_gdkcolor(&client_fg, &prefs.st_client_fg); + color_t_to_gdkcolor(&client_bg, &prefs.st_client_bg); + + /* Delete any info already in text box */ +#if GTK_MAJOR_VERSION < 2 + bytes_already = gtk_text_get_length(GTK_TEXT(follow_info->text)); + if (bytes_already > 0) { + gtk_text_set_point(GTK_TEXT(follow_info->text), 0); + gtk_text_forward_delete(GTK_TEXT(follow_info->text), bytes_already); + } + + /* stop the updates while we fill the text box */ + gtk_text_freeze(GTK_TEXT(follow_info->text)); +#else + /* prepare tags one time for repeated use by follow_add_to_gtk_text */ + server_tag = gtk_text_buffer_create_tag(buf, NULL, "foreground-gdk", &server_fg, + "background-gdk", &server_bg, "font-desc", + user_font_get_regular(), NULL); + client_tag = gtk_text_buffer_create_tag(buf, NULL, "foreground-gdk", &client_fg, + "background-gdk", &client_bg, "font-desc", + user_font_get_regular(), NULL); + + gtk_text_buffer_set_text(buf, "", -1); +#endif + follow_read_stream(follow_info, follow_add_to_gtk_text, follow_info->text); +#if GTK_MAJOR_VERSION < 2 + gtk_text_thaw(GTK_TEXT(follow_info->text)); +#endif +} + + +/* + * Keep a static pointer to the current "Save TCP Follow Stream As" window, if + * any, so that if somebody tries to do "Save" + * while there's already a "Save TCP Follow Stream" window up, we just pop + * up the existing one, rather than creating a new one. + */ +static void +follow_save_as_cmd_cb(GtkWidget *w _U_, gpointer data) +{ + GtkWidget *new_win; + follow_info_t *follow_info = data; + + if (follow_info->follow_save_as_w != NULL) { + /* There's already a dialog box; reactivate it. */ + reactivate_window(follow_info->follow_save_as_w); + return; + } + + new_win = file_selection_new("Ethereal: Save TCP Follow Stream As", + FILE_SELECTION_SAVE); + follow_info->follow_save_as_w = new_win; + + /* Tuck away the follow_info object into the window */ + OBJECT_SET_DATA(new_win, E_FOLLOW_INFO_KEY, follow_info); + + SIGNAL_CONNECT(new_win, "destroy", follow_save_as_destroy_cb, follow_info); + +#if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2 + if (gtk_dialog_run(GTK_DIALOG(new_win)) == GTK_RESPONSE_ACCEPT) + { + follow_save_as_ok_cb(new_win, new_win); + } else { + window_destroy(new_win); + } +#else + /* Connect the ok_button to file_save_as_ok_cb function and pass along a + pointer to the file selection box widget */ + SIGNAL_CONNECT(GTK_FILE_SELECTION(new_win)->ok_button, + "clicked", follow_save_as_ok_cb, new_win); + + window_set_cancel_button(new_win, + GTK_FILE_SELECTION(new_win)->cancel_button, window_cancel_button_cb); + + gtk_file_selection_set_filename(GTK_FILE_SELECTION(new_win), ""); + + SIGNAL_CONNECT(new_win, "delete_event", window_delete_event_cb, NULL); + + gtk_widget_show_all(new_win); + window_present(new_win); +#endif +} + + +static void +follow_save_as_ok_cb(GtkWidget * w _U_, gpointer fs) +{ + gchar *to_name; + follow_info_t *follow_info; + FILE *fh; + print_stream_t *stream = NULL; + gchar *dirname; + +#if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2 + to_name = g_strdup(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs))); +#else + to_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(fs))); +#endif + + /* Perhaps the user specified a directory instead of a file. + Check whether they did. */ + if (test_for_directory(to_name) == EISDIR) { + /* It's a directory - set the file selection box to display that + directory, and leave the selection box displayed. */ + set_last_open_dir(to_name); + g_free(to_name); + file_selection_set_current_folder(fs, get_last_open_dir()); + return; + } + + follow_info = OBJECT_GET_DATA(fs, E_FOLLOW_INFO_KEY); + if (follow_info->show_type == SHOW_RAW) { + /* Write the data out as raw binary data */ + fh = eth_fopen(to_name, "wb"); + } else { + /* Write it out as text */ + fh = eth_fopen(to_name, "w"); + } + if (fh == NULL) { + open_failure_alert_box(to_name, errno, TRUE); + g_free(to_name); + return; + } + + gtk_widget_hide(GTK_WIDGET(fs)); + window_destroy(GTK_WIDGET(fs)); + + if (follow_info->show_type == SHOW_RAW) { + switch (follow_read_stream(follow_info, follow_write_raw, fh)) { + case FRS_OK: + if (fclose(fh) == EOF) + write_failure_alert_box(to_name, errno); + break; + + case FRS_OPEN_ERROR: + case FRS_READ_ERROR: + fclose(fh); + break; + + case FRS_PRINT_ERROR: + write_failure_alert_box(to_name, errno); + fclose(fh); + break; + } + } else { + stream = print_stream_text_stdio_new(fh); + switch (follow_read_stream(follow_info, follow_print_text, stream)) { + case FRS_OK: + if (!destroy_print_stream(stream)) + write_failure_alert_box(to_name, errno); + break; + + case FRS_OPEN_ERROR: + case FRS_READ_ERROR: + destroy_print_stream(stream); + break; + + case FRS_PRINT_ERROR: + write_failure_alert_box(to_name, errno); + destroy_print_stream(stream); + break; + } + } + + /* Save the directory name for future file dialogs. */ + dirname = get_dirname(to_name); /* Overwrites to_name */ + set_last_open_dir(dirname); + g_free(to_name); +} + +static void +follow_save_as_destroy_cb(GtkWidget * win _U_, gpointer data) +{ + follow_info_t *follow_info = data; + + /* Note that we no longer have a dialog box. */ + follow_info->follow_save_as_w = NULL; +} |