forked from Mirrors/sngrep
Add GnuTLS support. Fixes #68
This commit is contained in:
parent
d320c4417b
commit
9b06b744f3
|
@ -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).
|
||||
*
|
||||
*/
|
||||
|
|
@ -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. |
|
||||
|
|
27
configure.ac
27
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} )
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
|
||||
#include <unistd.h>
|
||||
#include "capture.h"
|
||||
#include "capture_tls.h"
|
||||
#include "capture_gnutls.h"
|
||||
#include "option.h"
|
||||
#include "util.h"
|
||||
#include "sip.h"
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
**
|
||||
****************************************************************************/
|
||||
/**
|
||||
* @file capture_tls.c
|
||||
* @author Ivan Alonso [aka Kaian] <kaian@irontec.com>
|
||||
*
|
||||
* @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 <unistd.h>
|
||||
#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;
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
**
|
||||
** 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] <kaian@irontec.com>
|
||||
*
|
||||
* @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 <openssl/ssl.h>
|
||||
#include <openssl/tls1.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#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
|
14
src/main.c
14
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
|
||||
" [<match expression>] [<bpf filter>]\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
|
||||
|
|
Loading…
Reference in New Issue