diff --git a/LICENSE.OpenSSL b/LICENSE.OpenSSL new file mode 100644 index 0000000..86b6398 --- /dev/null +++ b/LICENSE.OpenSSL @@ -0,0 +1,54 @@ +/* ==================================================================== + * Copyright (c) 1998-2011 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + diff --git a/README.md b/README.md index 5226ca5..71dbf98 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,8 @@ Prerequisites - libncurse5 - for UI, windows, panels. - libpcap - for capturing packets. - - libssl - (optional) for TLS transport decrypt + - libssl - (optional) for TLS transport decrypt using OpenSSL and libcrypt + - gnutls - (optional) for TLS transport decrypt using GnuTLS and libgcrypt - libncursesw5 - (optional) for UI, windows, panels (wide-character support) - libpcre - (optional) for Perl Compatible regular expressions @@ -51,7 +52,8 @@ You can pass following flags to ./configure to enable some features | configure flag | Feature | | ------------- | ------------- | -| `--with-gnutls` | Adds GnuTLS support to parse TLS captured messages (req. libssl) | +| `--with-openssl` | Adds OpenSSL support to parse TLS captured messages (req. libssl) | +| `--with-gnutls` | Adds GnuTLS support to parse TLS captured messages (req. gnutls) | | `--with-pcre`| Adds Perl Compatible regular expressions support in regexp fields | | `--enable-unicode` | Adds Ncurses UTF-8/Unicode support (req. libncursesw5) | | `--enable-ipv6` | Enables IPv6 packet capture support. | diff --git a/configure.ac b/configure.ac index 2bd9751..24d4d94 100644 --- a/configure.ac +++ b/configure.ac @@ -120,6 +120,31 @@ AS_IF([test "x$WITH_GNUTLS" == "xyes"], [ AC_DEFINE([WITH_GNUTLS],[],[Compile With GnuTLS compatibility]) ], []) +#### +#### OpenSSL Support +#### +AC_ARG_WITH([openssl], + AS_HELP_STRING([--with-openssl], [Enable SSL Support (TLS SIP Transport)]), + [AC_SUBST(WITH_OPENSSL, $withval)], + [AC_SUBST(WITH_OPENSSL, no)] +) + +AS_IF([test "x$WITH_OPENSSL" == "xyes"], [ + AS_IF([test "x$WITH_GNUTLS" == "xyes"], [ + AC_MSG_ERROR([ GnuTLS and OpenSSL can not be enabled at the same time ]) + ], []) + + AC_CHECK_LIB([ssl], [SSL_new], [], [ + AC_MSG_ERROR([ You need to have libssl installed to compile sngrep]) + ]) + + AC_CHECK_LIB([crypto], [EVP_get_cipherbyname], [], [ + AC_MSG_ERROR([ You need to have libcrypto installed to compile sngrep]) + ]) + AC_DEFINE([WITH_OPENSSL],[],[Compile With Openssl compatibility]) +], []) + + #### #### PCRE Support #### @@ -158,6 +183,7 @@ AS_IF([test "x$USE_IPV6" == "xyes"], [ # Conditional Source inclusion AM_CONDITIONAL([WITH_GNUTLS], [test "x$WITH_GNUTLS" == "xyes"]) +AM_CONDITIONAL([WITH_OPENSSL],[test "x$WITH_OPENSSL" == "xyes"]) ###################################################################### @@ -182,6 +208,7 @@ AC_MSG_NOTICE AC_MSG_NOTICE( sngrep configure finished ) AC_MSG_NOTICE( ====================================================== ) AC_MSG_NOTICE( GnuTLS Support : ${WITH_GNUTLS} ) +AC_MSG_NOTICE( OpenSSL Support : ${WITH_OPENSSL} ) AC_MSG_NOTICE( Unicode Support : ${UNICODE} ) AC_MSG_NOTICE( Perl Expressions Support : ${WITH_PCRE} ) AC_MSG_NOTICE( IPv6 Support : ${USE_IPV6} ) diff --git a/src/Makefile.am b/src/Makefile.am index 0676f13..04a305a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,10 @@ bin_PROGRAMS=sngrep sngrep_SOURCES=capture.c capture_eep.c capture_reasm.c capture_ws.c if WITH_GNUTLS -sngrep_SOURCES+=capture_tls.c +sngrep_SOURCES+=capture_gnutls.c +endif +if WITH_OPENSSL +sngrep_SOURCES+=capture_openssl.c endif sngrep_SOURCES+=sip.c sip_call.c sip_msg.c sip_attr.c main.c option.c sngrep_SOURCES+=group.c filter.c keybinding.c media.c setting.c rtp.c util.c vector.c diff --git a/src/capture.c b/src/capture.c index b2a37ef..86019f6 100644 --- a/src/capture.c +++ b/src/capture.c @@ -37,7 +37,10 @@ #include "capture_reasm.h" #include "capture_eep.h" #ifdef WITH_GNUTLS -#include "capture_tls.h" +#include "capture_gnutls.h" +#endif +#ifdef WITH_OPENSSL +#include "capture_openssl.h" #endif #include "sip.h" #include "rtp.h" @@ -300,7 +303,7 @@ parse_packet(u_char *info, const struct pcap_pkthdr *header, const u_char *packe if (!(pkt = capture_packet_reasm_tcp(pkt, tcp, payload, size_payload))) return; -#ifdef WITH_GNUTLS +#if defined(WITH_GNUTLS) || defined(WITH_OPENSSL) // Check if packet is TLS if (capture_cfg.keyfile) tls_process_segment(pkt, tcp); diff --git a/src/capture_tls.c b/src/capture_gnutls.c similarity index 99% rename from src/capture_tls.c rename to src/capture_gnutls.c index f628b49..22345d5 100644 --- a/src/capture_tls.c +++ b/src/capture_gnutls.c @@ -32,7 +32,7 @@ #include #include "capture.h" -#include "capture_tls.h" +#include "capture_gnutls.h" #include "option.h" #include "util.h" #include "sip.h" diff --git a/src/capture_tls.h b/src/capture_gnutls.h similarity index 100% rename from src/capture_tls.h rename to src/capture_gnutls.h diff --git a/src/capture_openssl.c b/src/capture_openssl.c new file mode 100644 index 0000000..17f2c31 --- /dev/null +++ b/src/capture_openssl.c @@ -0,0 +1,492 @@ +/************************************************************************** + ** + ** sngrep - SIP Messages flow viewer + ** + ** Copyright (C) 2013,2014 Ivan Alonso (Kaian) + ** Copyright (C) 2013,2014 Irontec SL. All rights reserved. + ** + ** 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 3 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, see . + ** + ****************************************************************************/ +/** + * @file capture_tls.c + * @author Ivan Alonso [aka Kaian] + * + * @brief Functions to manage SIP TLS transport for messages + * + * This file contains the functions and structures to manage the SIP messages + * that use TLS as transport. + * + */ + +#include +#include "capture.h" +#include "capture_openssl.h" +#include "option.h" +#include "util.h" +#include "sip.h" + +struct SSLConnection *connections; + +struct CipherSuite TLS_RSA_WITH_AES_128_CBC_SHA = +{ 0x00, 0x2F }; +struct CipherSuite TLS_RSA_WITH_AES_256_CBC_SHA = +{ 0x00, 0x35 }; + +int +P_hash(const char *digest, unsigned char *dest, int dlen, unsigned char *secret, int sslen, + unsigned char *seed, int slen) +{ + unsigned char hmac[20]; + unsigned int hlen; + HMAC_CTX hm; + const EVP_MD *md = EVP_get_digestbyname(digest); + unsigned int tmpslen; + unsigned char tmpseed[slen]; + unsigned char *out = dest; + int pending = dlen; + + // Copy initial seed + memcpy(tmpseed, seed, slen); + tmpslen = slen; + + // Calculate enough data to fill destination + while (pending > 0) { + HMAC_Init(&hm, secret, sslen, md); + HMAC_Update(&hm, tmpseed, tmpslen); + HMAC_Final(&hm, tmpseed, &tmpslen); + + HMAC_Init(&hm, secret, sslen, md); + HMAC_Update(&hm, tmpseed, tmpslen); + HMAC_Update(&hm, seed, slen); + HMAC_Final(&hm, hmac, &hlen); + + hlen = (hlen > pending) ? pending : hlen; + memcpy(out, hmac, hlen); + out += hlen; + pending -= hlen; + } + HMAC_cleanup(&hm); + + return hlen; +} + +int +PRF(unsigned char *dest, int dlen, unsigned char *pre_master_secret, int plen, unsigned char *label, + unsigned char *seed, int slen) +{ + int i; + + // Split the secret by half to generate MD5 and SHA secret parts + int hplen = plen / 2 + plen % 2; + unsigned char md5_secret[hplen]; + unsigned char sha_secret[hplen]; + memcpy(md5_secret, pre_master_secret, hplen); + memcpy(sha_secret, pre_master_secret + plen / 2, plen / 2); + + // This vars will store the values of P_MD5 and P_SHA-1 + unsigned char h_md5[dlen]; + unsigned char h_sha[dlen]; + + // Concatenate given seed to the label to get the final seed + int llen = strlen((const char*) label); + unsigned char fseed[slen + llen]; + memcpy(fseed, label, llen); + memcpy(fseed + llen, seed, slen); + + // Get enough MD5 and SHA1 data to fill output len + P_hash("MD5", h_md5, dlen, pre_master_secret, hplen, fseed, slen + llen); + P_hash("SHA1", h_sha, dlen, pre_master_secret + hplen, hplen, fseed, slen + llen); + + // Final output will be MD5 and SHA1 X-ORed + for (i = 0; i < dlen; i++) + dest[i] = h_md5[i] ^ h_sha[i]; + + return dlen; +} + +struct SSLConnection * +tls_connection_create(struct in_addr caddr, u_short cport, struct in_addr saddr, u_short sport) { + struct SSLConnection *conn = NULL; + conn = sng_malloc(sizeof(struct SSLConnection)); + + memcpy(&conn->client_addr, &caddr, sizeof(struct in_addr)); + memcpy(&conn->server_addr, &saddr, sizeof(struct in_addr)); + memcpy(&conn->client_port, &cport, sizeof(u_short)); + memcpy(&conn->server_port, &sport, sizeof(u_short)); + + SSL_library_init(); + ERR_load_crypto_strings(); + OpenSSL_add_all_algorithms(); + + if (!(conn->ssl_ctx = SSL_CTX_new(SSLv23_server_method()))) + return NULL; + + SSL_CTX_use_PrivateKey_file(conn->ssl_ctx, capture_get_keyfile(), + SSL_FILETYPE_PEM); + if (!(conn->ssl = SSL_new(conn->ssl_ctx))) + return NULL; + + conn->server_private_key = SSL_get_privatekey(conn->ssl); + + // Add this connection to the list + conn->next = connections; + connections = conn; + + return conn; +} + +void +tls_connection_destroy(struct SSLConnection *conn) +{ + struct SSLConnection *c; + + // Remove connection from connections list + if (conn == connections) { + connections = conn->next; + } else { + for (c = connections; c; c = c->next) { + if (c->next == conn) { + c->next = conn->next; + break; + } + } + } + + // Deallocate connection memory + SSL_CTX_free(conn->ssl_ctx); + SSL_free(conn->ssl); + sng_free(conn); +} + +/** + * FIXME Replace this with a tls_load_key function and use it + * in tls_connection_create. + * + * Most probably we only need one context and key for all connections + */ +int +tls_check_keyfile(const char *keyfile) +{ + SSL *ssl; + SSL_CTX *ssl_ctx; + + SSL_library_init(); + ERR_load_crypto_strings(); + OpenSSL_add_all_algorithms(); + + if (access(capture_get_keyfile(), R_OK) != 0) + return 0; + + if (!(ssl_ctx = SSL_CTX_new(SSLv23_server_method()))) + return 0; + + SSL_CTX_use_PrivateKey_file(ssl_ctx, capture_get_keyfile(), SSL_FILETYPE_PEM); + if (!(ssl = SSL_new(ssl_ctx))) + return 0; + + if (!SSL_get_privatekey(ssl)) + return 0; + + return 1; +} + +int +tls_connection_dir(struct SSLConnection *conn, struct in_addr addr, u_short port) +{ + if (conn->client_addr.s_addr == addr.s_addr && conn->client_port == port) + return 0; + if (conn->server_addr.s_addr == addr.s_addr && conn->server_port == port) + return 1; + return -1; +} + +struct SSLConnection* +tls_connection_find(struct in_addr addr, u_short port) { + struct SSLConnection *conn; + + for (conn = connections; conn; conn = conn->next) { + if (tls_connection_dir(conn, addr, port) != -1) { + return conn; + } + } + return NULL; +} + +int +tls_process_segment(capture_packet_t *packet, struct tcphdr *tcp) +{ + struct SSLConnection *conn; + const u_char *payload = capture_packet_get_payload(packet); + uint32_t size_payload = capture_packet_get_payload_len(packet); + uint8 *out; + uint32_t outl = packet->payload_len; + out = sng_malloc(outl); + struct in_addr ip_src, ip_dst; + u_short sport = packet->sport; + u_short dport = packet->dport; + + // Convert addresses + inet_pton(AF_INET, packet->ip_src, &ip_src); + inet_pton(AF_INET, packet->ip_dst, &ip_dst); + + // Try to find a session for this ip + if ((conn = tls_connection_find(ip_src, sport))) { + // Update last connection direction + conn->direction = tls_connection_dir(conn, ip_src, sport); + + // Check current connection state + switch (conn->state) { + case TCP_STATE_SYN: + // First SYN received, this package must be SYN/ACK + if (tcp->th_flags & TH_SYN & ~TH_ACK) + conn->state = TCP_STATE_SYN_ACK; + break; + case TCP_STATE_SYN_ACK: + // We expect an ACK packet here + if (tcp->th_flags & ~TH_SYN & TH_ACK) + conn->state = TCP_STATE_ESTABLISHED; + break; + case TCP_STATE_ACK: + case TCP_STATE_ESTABLISHED: + // Process data segment! + if (tls_process_record(conn, payload, size_payload, &out, &outl) == 0) { + if ((int32_t) outl > 0) { + capture_packet_set_payload(packet, out, outl); + capture_packet_set_type(packet, CAPTURE_PACKET_SIP_TLS); + return 0; + } + } + break; + case TCP_STATE_FIN: + case TCP_STATE_CLOSED: + // We can delete this connection + tls_connection_destroy(conn); + break; + } + } else { + if (tcp->th_flags & TH_SYN & ~TH_ACK) { + // New connection, store it status and leave + tls_connection_create(ip_src, sport, ip_dst, dport); + } + } + + sng_free(out); + return 0; +} + +int +tls_process_record(struct SSLConnection *conn, const uint8 *payload, const int len, uint8 **out, + uint32_t *outl) +{ + struct TLSPlaintext *record; + int record_len; + const opaque *fragment; + + // No record data here! + if (len == 0) + return 0; + + // Get Record data + record = (struct TLSPlaintext *) payload; + record_len = sizeof(struct TLSPlaintext) + UINT16_INT(record->length); + + // Process record fragment + if (UINT16_INT(record->length) > 0) { + // TLSPlaintext fragment pointer + fragment = (opaque *) payload + sizeof(struct TLSPlaintext); + + switch (record->type) { + case handshake: + // Hanshake Record, Try to get MasterSecret data + if (tls_process_record_handshake(conn, fragment) != 0) + return 1; + break; + case change_cipher_spec: + // From now on, this connection will be encrypted using MasterSecret + conn->encrypted = 1; + break; + case application_data: + if (conn->encrypted) { + // Decrypt application data using MasterSecret + tls_process_record_data(conn, fragment, UINT16_INT(record->length), out, outl); + } + break; + default: + break; + } + } + + // MultiRecord packet + if (len > record_len) + return tls_process_record(conn, payload + record_len, len - record_len, out, outl); + + return 0; +} + +int +tls_process_record_handshake(struct SSLConnection *conn, const opaque *fragment) +{ + struct Handshake *handshake; + struct ClientHello *clienthello; + struct ServerHello *serverhello; + struct ClientKeyExchange *clientkeyex; + const opaque *body; + + // Get Handshake data + handshake = (struct Handshake *) fragment; + + if (UINT24_INT(handshake->length) > 0) { + // Hanshake body pointer + body = fragment + sizeof(struct Handshake); + + switch (handshake->type) { + case hello_request: + break; + case client_hello: + // Store client random + clienthello = (struct ClientHello *) body; + memcpy(&conn->client_random, &clienthello->random, sizeof(struct Random)); + + // Check we have a TLS handshake + if (!(clienthello->client_version.major == 0x03 + && clienthello->client_version.minor == 0x01)) { + tls_connection_destroy(conn); + return 1; + } + break; + case server_hello: + // Store server random + serverhello = (struct ServerHello *) body; + memcpy(&conn->server_random, &serverhello->random, sizeof(struct Random)); + // Get the selected cipher + memcpy(&conn->cipher_suite, + body + sizeof(struct ServerHello) + serverhello->session_id_length, + sizeof(uint16)); + // Check if we have a handled cipher + if (tls_connection_load_cipher(conn) != 0) { + tls_connection_destroy(conn); + return 1; + } + break; + case certificate: + case certificate_request: + case server_hello_done: + case certificate_verify: + break; + case client_key_exchange: + // Decrypt PreMasterKey + clientkeyex = (struct ClientKeyExchange *) body; + + RSA_private_decrypt(UINT16_INT(clientkeyex->length), + (const unsigned char *) &clientkeyex->exchange_keys, + (unsigned char *) &conn->pre_master_secret, + conn->server_private_key->pkey.rsa, RSA_PKCS1_PADDING); + + unsigned char *seed = sng_malloc(sizeof(struct Random) * 2); + memcpy(seed, &conn->client_random, sizeof(struct Random)); + memcpy(seed + sizeof(struct Random), &conn->server_random, sizeof(struct Random)); + + // Get MasterSecret + PRF((unsigned char *) &conn->master_secret, sizeof(struct MasterSecret), + (unsigned char *) &conn->pre_master_secret, sizeof(struct PreMasterSecret), + (unsigned char *) "master secret", seed, sizeof(struct Random) * 2); + + memcpy(seed, &conn->server_random, sizeof(struct Random)); + memcpy(seed + sizeof(struct Random), &conn->client_random, sizeof(struct Random)); + + // Generate MACs, Write Keys and IVs + PRF((unsigned char *) &conn->key_material, sizeof(struct tls_data), + (unsigned char *) &conn->master_secret, sizeof(struct MasterSecret), + (unsigned char *) "key expansion", seed, sizeof(struct Random) * 2); + + // Done with the seed + sng_free(seed); + + // Create Client decoder + EVP_CIPHER_CTX_init(&conn->client_cipher_ctx); + EVP_CipherInit(&conn->client_cipher_ctx, conn->ciph, + conn->key_material.client_write_key, conn->key_material.client_write_IV, + 0); + + EVP_CIPHER_CTX_init(&conn->server_cipher_ctx); + EVP_CipherInit(&conn->server_cipher_ctx, conn->ciph, + conn->key_material.server_write_key, conn->key_material.server_write_IV, + 0); + + break; + case finished: + break; + default: + if (conn->encrypted) { + // Encrypted Hanshake Message + unsigned char *decoded = sng_malloc(48); + uint32_t decodedlen; + tls_process_record_data(conn, fragment, 48, &decoded, &decodedlen); + sng_free(decoded); + } + break; + } + } + + return 0; +} + +int +tls_process_record_data(struct SSLConnection *conn, const opaque *fragment, const int len, + uint8 **out, uint32_t *outl) +{ + EVP_CIPHER_CTX *evp; + unsigned char pad; + unsigned char *decoded; + uint32_t dlen; + + if (conn->direction == 0) { + evp = &conn->client_cipher_ctx; + } else { + evp = &conn->server_cipher_ctx; + } + + decoded = sng_malloc(len); + EVP_Cipher(evp, decoded, (unsigned char *) fragment, len); + + // Get padding counter and remove from data + pad = decoded[len - 1]; + dlen = (len - (pad + 1) - /* Trailing MAC */20); + + if ((int32_t)dlen > 0 && dlen <= *outl) { + memcpy(*out, decoded, dlen); + *outl = dlen; + } + + // Clenaup decoded memory + sng_free(decoded); + return *outl; +} + +int +tls_connection_load_cipher(struct SSLConnection *conn) +{ + if (conn->cipher_suite.cs1 != 0x00) + return 1; + + if (conn->cipher_suite.cs2 == TLS_RSA_WITH_AES_256_CBC_SHA.cs2) { + conn->ciph = EVP_get_cipherbyname("AES256"); + } else if (conn->cipher_suite.cs2 == TLS_RSA_WITH_AES_128_CBC_SHA.cs2) { + conn->ciph = EVP_get_cipherbyname("AES128"); + } else { + return 1; + } + return 0; +} diff --git a/src/capture_openssl.h b/src/capture_openssl.h new file mode 100644 index 0000000..4208066 --- /dev/null +++ b/src/capture_openssl.h @@ -0,0 +1,412 @@ +/************************************************************************** + ** + ** sngrep - SIP Messages flow viewer + ** + ** Copyright (C) 2013,2014 Ivan Alonso (Kaian) + ** Copyright (C) 2013,2014 Irontec SL. All rights reserved. + ** + ** 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 3 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, see . + ** + ** In addition, as a special exception, the copyright holders give + ** permission to link the code of portions of this program with the + ** OpenSSL library under certain conditions as described in each + ** individual source file, and distribute linked combinations + ** including the two. + ** You must obey the GNU General Public License in all respects + ** for all of the code used other than OpenSSL. If you modify + ** file(s) with this exception, you may extend this exception to your + ** version of the file(s), but you are not obligated to do so. If you + ** do not wish to do so, delete this exception statement from your + ** version. If you delete this exception statement from all source + ** files in the program, then also delete it here. + ** + ****************************************************************************/ +/** + * @file capture_tls.h + * @author Ivan Alonso [aka Kaian] + * + * @brief Functions to manage SIP TLS messages + * + * This file contains the functions and structures to manage the SIP messages + * that use TLS as transport. + * + */ + +#ifndef __SNGREP_CAPTURE_TLS_ +#define __SNGREP_CAPTURE_TLS_ + +#include "config.h" +#include +#include +#include +#include +#include "capture.h" + +//! Cast two bytes into decimal (Big Endian) +#define UINT16_INT(i) ((i.x[0] << 8) | i.x[1]) +//! Cast three bytes into decimal (Big Endian) +#define UINT24_INT(i) ((i.x[0] << 16) | (i.x[1] << 8) | i.x[2]) + +//! One byte unsigned integer +typedef unsigned char uint8; + +//! Two bytes unsigned integer +typedef struct uint16 { + unsigned char x[2]; +} uint16; + +//! Three bytes unsigned integer +typedef struct uint24 { + unsigned char x[3]; +} uint24; + +//! Four bytes unsigned interger +typedef struct uint32 { + unsigned char x[4]; +} uint32; + +//! One byte generic type +typedef unsigned char opaque; + +//! SSLConnections states +enum SSLConnectionState { + //! Initial SYN packet has been received from client + TCP_STATE_SYN = 0, + //! SYN/ACK packet has been sent from the server + TCP_STATE_SYN_ACK, + //! Client ACK'ed the connection + TCP_STATE_ACK, + //! Connection is up, now SSL handshake should start! + TCP_STATE_ESTABLISHED, + //! Connection about to end + TCP_STATE_FIN, + //! Connection closed + TCP_STATE_CLOSED +}; + +//! ContentType values as defined in RFC5246 +enum ContentType { + change_cipher_spec = SSL3_RT_CHANGE_CIPHER_SPEC, + alert = SSL3_RT_ALERT, + handshake = SSL3_RT_HANDSHAKE, + application_data = SSL3_RT_APPLICATION_DATA +}; + +//! HanshakeType values as defined in RFC5246 +enum HandshakeType { + hello_request = SSL3_MT_HELLO_REQUEST, + client_hello = SSL3_MT_CLIENT_HELLO, + server_hello = SSL3_MT_SERVER_HELLO, + certificate = SSL3_MT_CERTIFICATE, + certificate_request = SSL3_MT_CERTIFICATE_REQUEST, + server_hello_done = SSL3_MT_SERVER_DONE, + certificate_verify = SSL3_MT_CERTIFICATE_VERIFY, + client_key_exchange = SSL3_MT_CLIENT_KEY_EXCHANGE, + finished = SSL3_MT_FINISHED +}; + +//! ProtocolVersion header as defined in RFC5246 +struct ProtocolVersion { + uint8 major; + uint8 minor; +}; + +//! TLSPlaintext record structure +struct TLSPlaintext { + uint8 type; + struct ProtocolVersion version; + uint16 length; +}; + +//! Hanshake record structure +struct Handshake { + uint8 type; + uint24 length; +}; + +//! Handshake random structure +struct Random { + uint32 gmt_unix_time; + opaque random_bytes[28]; +}; + +struct CipherSuite { + uint8 cs1; + uint8 cs2; +}; + +//! ClientHello type in Handshake records +struct ClientHello { + struct ProtocolVersion client_version; + struct Random random; +// uint8 session_id_length; +// CipherSuite cipher_suite; +// Extension extensions; +}; + +//! ServerHello type in Handshake records +struct ServerHello { + struct ProtocolVersion server_version; + struct Random random; + uint8 session_id_length; +// SessionID session_id; +// CipherSuite cipher_suite; +// CompressionMethod compression_method; +}; + +struct MasterSecret { + uint8 random[48]; +}; + +struct PreMasterSecret { + struct ProtocolVersion client_version; + uint8 random[46]; +}; + +struct EncryptedPreMasterSecret { + uint8 pre_master_secret[128]; +}; + +//! ClientKeyExchange type in Handshake records +struct ClientKeyExchange { + uint16 length; + struct EncryptedPreMasterSecret exchange_keys; +}; + +/** + * Structure to store all information from a TLS + * connection. This is also used as linked list + * node. + */ +struct SSLConnection { + //! Connection status + enum SSLConnectionState state; + //! Current packet direction + int direction; + //! Data is encrypted flag + int encrypted; + + //! Client IP address + struct in_addr client_addr; + //! Server IP address + struct in_addr server_addr; + //! Client port + u_short client_port; + //! Server port + u_short server_port; + + SSL *ssl; + SSL_CTX *ssl_ctx; + EVP_PKEY *server_private_key; + const EVP_CIPHER *ciph; + struct Random client_random; + struct Random server_random; + struct CipherSuite cipher_suite; + struct PreMasterSecret pre_master_secret; + struct MasterSecret master_secret; + + struct tls_data { + uint8 client_write_MAC_key[20]; + uint8 server_write_MAC_key[20]; + uint8 client_write_key[32]; + uint8 server_write_key[32]; + uint8 client_write_IV[16]; + uint8 server_write_IV[16]; + } key_material; + + EVP_CIPHER_CTX client_cipher_ctx; + EVP_CIPHER_CTX server_cipher_ctx; + + struct SSLConnection *next; +}; + +/** + * @brief P_hash expansion function as defined in RFC5246 + * + * This function will expand Secret and Seed into output using digest + * hash function. The amount of data generated will be determined by output + * length (dlen). + * + * @param digest Digest name to get the hash function + * @param dest Destination of hash function result. Memory must be already allocated + * @param dlen Destination length in bytes + * @param secret Input for the hash function + * @param sslen Secret length in bytes + * @param seed Input for the hash function + * @param slen Seed length in bytes + * @return Output bytes + */ +int +P_hash(const char *digest, unsigned char *dest, int dlen, unsigned char *secret, int sslen, + unsigned char *seed, int slen); + +/** + * @brief Pseudorandom Function as defined in RFC5246 + * + * This function will generate MasterSecret and KeyMaterial data from PreMasterSecret and Seed + * + * @param dest Destination of PRF function result. Memory must be already allocated + * @param dlen Destination length in bytes + * @param pre_master_secret PreMasterSecret decrypted from ClientKeyExchange Handhsake record + * @param pslen PreMasterSecret length in bytes + * @param label Fixed ASCII string + * @param seed Concatenation of Random data from Hello Handshake records + * @param slen Seed length in bytes + * @return destination length in bytes + */ +int +PRF(unsigned char *dest, int dlen, unsigned char *pre_master_secret, int plen, unsigned char *label, + unsigned char *seed, int slen); + +/** + * @brief Create a new SSLConnection + * + * This will allocate enough memory to store all connection data + * from a detected SSL connection. This will also add this structure to + * the connections linked list. + * + * @param caddr Client address + * @param cport Client port + * @param saddr Server address + * @param sport Server port + * @return a pointer to a new allocated SSLConnection structure + */ +struct SSLConnection * +tls_connection_create(struct in_addr caddr, u_short cport, struct in_addr saddr, u_short sport); + +/** + * @brief Destroys an existing SSLConnection + * + * This will free all allocated memory of SSLConnection also removing + * the connection from connections list. + * + * @param conn Existing connection pointer + */ +void +tls_connection_destroy(struct SSLConnection *conn); + +/** + * @brief Check if given keyfile is valid + * + * This can be used to check if a file contains valid RSA data + * + * @param keyfile Absolute path the keyfile + * @return 1 if file contains RSA private info, 0 otherwise + */ +int +tls_check_keyfile(const char *keyfile); + +/** + * @brief Determines packet direction + * + * Determine if the given address is from client or server. + * + * @param conn Existing connection pointer + * @param addr Client or server address + * @param port Client or server port + * @return 0 if address belongs to client, 1 to server or -1 otherwise + */ +int +tls_connection_dir(struct SSLConnection *conn, struct in_addr addr, u_short port); + +/** + * @brief Find a connection + * + * Try to find connection data for a given address and port. + * This address:port convination can be the client or server one. + * + * @param addr Client or server address + * @param port Client or server port + * @return an existing Connection pointer or NULL if not found + */ +struct SSLConnection* +tls_connection_find(struct in_addr addr, u_short port); + +/** + * @brief Process a TCP segment to check TLS data + * + * Check if a TCP segment contains TLS data. In case a TLS record is found + * process it and return decrypted data if case of application_data record. + * + * @param tcp Pointer to tcp header of the packet + * @param out Pointer to the output char array. Memory must be already allocated + * @param out Number of bytes returned by this function + * @return 0 in all cases + */ +int +tls_process_segment(capture_packet_t *packet, struct tcphdr *tcp); + +/** + * @brief Process TLS record data + * + * Process a TLS record + * - If the record type is Handshake process it in tls_process_record_handshake + * - If the record type is Application Data process it in tls_process_record_data + * + * @param conn Existing connection pointer + * @param payload Packet peyload + * @param len Payload length + * @param out pointer to store decryted data + * @param outl decrypted data length + * @return Decrypted data length + */ +int +tls_process_record(struct SSLConnection *conn, const uint8 *payload, const int len, uint8 **out, + uint32_t *outl); + +/** + * @brief Process TLS Handshake record types + * + * Process all types of Handshake records to store and compute all required + * data to decrypt application data packets + * + * @param conn Existing connection pointer + * @param fragment Handshake record data + * @return 0 on valid record processed, 1 otherwise + */ +int +tls_process_record_handshake(struct SSLConnection *conn, const opaque *fragment); + +/** + * @brief Process TLS ApplicationData record types + * + * Process application data record, trying to decrypt it with connection + * information + * + * @param conn Existing connection pointer + * @param fragment Application record data + * @param len record length in bytes + * @param out pointer to store decryted data + * @param outl decrypted data length + * @return decoded data length + */ +int +tls_process_record_data(struct SSLConnection *conn, const opaque *fragment, const int len, + uint8 **out, uint32_t *outl); + + +/** + * @brief Get the cipher data from the given connection + * + * Load cipher pointer depending on the selected cipher in + * Handshake messages. + * + * This function can be used to test is a cipher decrypting is supported + * @param conn Existing connection pointer + * @return 0 on valid cipher, 1 otherwise + */ +int +tls_connection_load_cipher(struct SSLConnection *conn); + +#endif diff --git a/src/main.c b/src/main.c index 631eeff..fdc9d2d 100644 --- a/src/main.c +++ b/src/main.c @@ -38,7 +38,10 @@ #include "capture.h" #include "capture_eep.h" #ifdef WITH_GNUTLS -#include "capture_tls.h" +#include "capture_gnutls.h" +#endif +#ifdef WITH_OPENSSL +#include "capture_openssl.h" #endif /** @@ -50,7 +53,7 @@ void usage() { printf("Usage: %s [-hVcivNq] [-IO pcap_dump] [-d dev] [-l limit]" -#ifdef WITH_GNUTLS +#if defined(WITH_GNUTLS) || defined(WITH_OPENSSL) " [-k keyfile]" #endif " [] []\n\n" @@ -69,7 +72,7 @@ usage() " -H --eep-send\t Homer sipcapture url (udp:X.X.X.X:XXXX)\n" " -L --eep-listen\t Listen for encapsulated packets (udp:X.X.X.X:XXXX)\n" " -q --quiet\t\t Don't print captured dialogs in no interface mode\n" -#ifdef WITH_GNUTLS +#if defined(WITH_GNUTLS) || defined(WITH_OPENSSL) " -k --keyfile\t RSA private keyfile to decrypt captured packets\n" #endif "\n",PACKAGE); @@ -87,6 +90,9 @@ version() #ifdef WITH_GNUTLS " * Compiled with GnuTLS support.\n" #endif +#ifdef WITH_OPENSSL + " * Compiled with OpenSSL support.\n" +#endif #ifdef WITH_UNICODE " * Compiled with Wide-character support.\n" #endif @@ -228,7 +234,7 @@ main(int argc, char* argv[]) } } -#ifdef WITH_GNUTLS +#if defined(WITH_GNUTLS) || defined(WITH_OPENSSL) // Set capture decrypt key file capture_set_keyfile(keyfile); // Check if we have a keyfile and is valid