mirror of
https://github.com/zeek/zeek.git
synced 2025-10-08 01:28:20 +00:00
ssl: rudimentary decryption for TLS 1.2
Several limitations still apply: - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 is the only supported cipher suite - Some tests are broken due to a failing assertion regarding bytestring - No newly written tests for decryption (the patch was tested extensively for our paper) - Several small open technical questions marked with FIXME - Architecture in the ssl module might not be optimal
This commit is contained in:
parent
db534b79fb
commit
2d950ffde9
21 changed files with 541 additions and 13 deletions
|
@ -1,5 +1,6 @@
|
|||
#include "zeek/analyzer/protocol/ssl/SSL.h"
|
||||
|
||||
#include "zeek/analyzer/Manager.h"
|
||||
#include "zeek/analyzer/protocol/tcp/TCP_Reassembler.h"
|
||||
#include "zeek/Reporter.h"
|
||||
#include "zeek/util.h"
|
||||
|
@ -8,6 +9,73 @@
|
|||
#include "zeek/analyzer/protocol/ssl/ssl_pac.h"
|
||||
#include "zeek/analyzer/protocol/ssl/tls-handshake_pac.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/kdf.h>
|
||||
|
||||
#define MSB(a) ((a>>8)&0xff)
|
||||
#define LSB(a) (a&0xff)
|
||||
|
||||
static void fmt_seq(uint32_t num, u_char* buf)
|
||||
{
|
||||
memset(buf, 0, 8);
|
||||
uint32_t netnum = htonl(num);
|
||||
memcpy(buf+4, &netnum, 4);
|
||||
}
|
||||
|
||||
static void print_hex(std::string name, u_char* data, int len)
|
||||
{
|
||||
int i = 0;
|
||||
printf("%s (%d): ", name.c_str(), len);
|
||||
if (len > 0)
|
||||
printf("0x%02x", data[0]);
|
||||
|
||||
for (i = 1; i < len; i++)
|
||||
{
|
||||
printf(" 0x%02x", data[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static bool tls1_prf(const u_char *secret, int secret_len, const char* label,
|
||||
const u_char* rnd1, int rnd1_len, const u_char* rnd2, int rnd2_len, u_char* out, size_t out_len)
|
||||
{
|
||||
// alloc buffers
|
||||
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_TLS1_PRF, NULL);
|
||||
size_t seed_len = strlen(label) + rnd1_len + rnd2_len;
|
||||
u_char *seed = (u_char*) malloc(seed_len);
|
||||
u_char *ptr = seed;
|
||||
|
||||
// seed = label + rnd1 + rnd2
|
||||
memcpy(ptr, label, strlen(label));
|
||||
ptr += strlen(label);
|
||||
memcpy(ptr, rnd1, rnd1_len);
|
||||
ptr += rnd1_len;
|
||||
memcpy(ptr, rnd2, rnd2_len);
|
||||
ptr += rnd2_len;
|
||||
|
||||
if (EVP_PKEY_derive_init(pctx) <= 0)
|
||||
goto abort; /* Error */
|
||||
// FIXME: sha384 should not be hardcoded
|
||||
if (EVP_PKEY_CTX_set_tls1_prf_md(pctx, EVP_sha384()) <= 0)
|
||||
goto abort; /* Error */
|
||||
if (EVP_PKEY_CTX_set1_tls1_prf_secret(pctx, secret, secret_len) <= 0)
|
||||
goto abort; /* Error */
|
||||
if (EVP_PKEY_CTX_add1_tls1_prf_seed(pctx, seed, seed_len) <= 0)
|
||||
goto abort; /* Error */
|
||||
if (EVP_PKEY_derive(pctx, out, &out_len) <= 0)
|
||||
goto abort; /* Error */
|
||||
|
||||
EVP_PKEY_CTX_free(pctx);
|
||||
free(seed);
|
||||
return true;
|
||||
|
||||
abort:
|
||||
EVP_PKEY_CTX_free(pctx);
|
||||
free(seed);
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace zeek::analyzer::ssl {
|
||||
|
||||
SSL_Analyzer::SSL_Analyzer(Connection* c)
|
||||
|
@ -16,12 +84,20 @@ SSL_Analyzer::SSL_Analyzer(Connection* c)
|
|||
interp = new binpac::SSL::SSL_Conn(this);
|
||||
handshake_interp = new binpac::TLSHandshake::Handshake_Conn(this);
|
||||
had_gap = false;
|
||||
c_seq = 0;
|
||||
s_seq = 0;
|
||||
// FIXME: should this be initialized to nullptr?
|
||||
secret = new zeek::StringVal(0, "");
|
||||
keys = new zeek::StringVal(0, "");
|
||||
pia = nullptr;
|
||||
}
|
||||
|
||||
SSL_Analyzer::~SSL_Analyzer()
|
||||
{
|
||||
delete interp;
|
||||
delete handshake_interp;
|
||||
delete secret;
|
||||
delete keys;
|
||||
}
|
||||
|
||||
void SSL_Analyzer::Done()
|
||||
|
@ -98,4 +174,166 @@ void SSL_Analyzer::Undelivered(uint64_t seq, int len, bool orig)
|
|||
interp->NewGap(orig, len);
|
||||
}
|
||||
|
||||
void SSL_Analyzer::SetSecret(const u_char* data, int len)
|
||||
{
|
||||
// FIXME: Is this the proper way to initialize a zeek::StringVal?
|
||||
// the alternative requires a cast: new zeek::StringVal(len, data)
|
||||
secret = new zeek::StringVal(new zeek::String(data, len, true));
|
||||
}
|
||||
|
||||
void SSL_Analyzer::SetKeys(const u_char* data, int len)
|
||||
{
|
||||
keys = new zeek::StringVal(new zeek::String(data, len, true));
|
||||
}
|
||||
|
||||
bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version)
|
||||
{
|
||||
// Unsupported cipher suite. Currently supported:
|
||||
// - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 == 0xC030
|
||||
auto cipher = handshake_interp->chosen_cipher();
|
||||
if ( cipher != 0xC030 )
|
||||
{
|
||||
//printf("Unsupported cipher suite: %d\n", cipher);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Neither secret or key present: abort
|
||||
if ( secret->Len() == 0 && keys->Len() == 0 )
|
||||
{
|
||||
// FIXME: this is just for debugging
|
||||
printf("Could not decrypt packet (missing key):\n");
|
||||
print_hex("->client_random:", handshake_interp->client_random().data(), handshake_interp->client_random().length());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Secret present, but no keys derived yet: derive keys
|
||||
if ( secret->Len() != 0 && keys->Len() == 0 )
|
||||
{
|
||||
uint32_t ts = htonl((uint32_t) handshake_interp->gmt_unix_time());
|
||||
|
||||
u_char crand[32] = {0x00};
|
||||
u_char keybuf[72];
|
||||
|
||||
auto c_rnd = handshake_interp->client_random();
|
||||
auto s_rnd = handshake_interp->server_random();
|
||||
memcpy(crand, &(ts), 4);
|
||||
memcpy(crand + 4, c_rnd.data(), c_rnd.length());
|
||||
|
||||
tls1_prf(secret->Bytes(), secret->Len(), "key expansion", s_rnd.data(), s_rnd.length(),
|
||||
crand, sizeof(crand), keybuf, sizeof(keybuf));
|
||||
|
||||
// save derived keys
|
||||
SetKeys(keybuf, sizeof(keybuf));
|
||||
}
|
||||
|
||||
// Keys present: decrypt TLS application data
|
||||
if ( keys->Len() != 0 )
|
||||
{
|
||||
// session keys & AEAD data
|
||||
u_char c_wk[32];
|
||||
u_char s_wk[32];
|
||||
u_char c_iv[4];
|
||||
u_char s_iv[4];
|
||||
u_char s_aead_nonce[12];
|
||||
u_char s_aead_tag[13];
|
||||
|
||||
memcpy(c_wk, keys->Bytes(), 32);
|
||||
memcpy(s_wk, &(keys->Bytes()[32]), 32);
|
||||
memcpy(c_iv, &(keys->Bytes()[64]), 4);
|
||||
memcpy(s_iv, &(keys->Bytes()[68]), 4);
|
||||
|
||||
// FIXME: should we change types here?
|
||||
u_char* encrypted = (u_char*)data;
|
||||
size_t encrypted_len = len;
|
||||
|
||||
// FIXME: should this be moved to SSL_Conn?
|
||||
if ( is_orig )
|
||||
c_seq++;
|
||||
else
|
||||
s_seq++;
|
||||
|
||||
if ( is_orig )
|
||||
memcpy(s_aead_nonce, c_iv, 4);
|
||||
else
|
||||
memcpy(s_aead_nonce, s_iv, 4);
|
||||
memcpy(&(s_aead_nonce[4]), encrypted, 8);
|
||||
|
||||
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
|
||||
EVP_CIPHER_CTX_init(ctx);
|
||||
EVP_CipherInit(ctx, EVP_aes_256_gcm(), NULL, NULL, 0);
|
||||
|
||||
encrypted += 8;
|
||||
// FIXME: is this because of nonce and aead tag?
|
||||
encrypted_len -= 8;
|
||||
encrypted_len -= 16;
|
||||
|
||||
if (is_orig)
|
||||
EVP_DecryptInit(ctx, EVP_aes_256_gcm(), c_wk, s_aead_nonce);
|
||||
else
|
||||
EVP_DecryptInit(ctx, EVP_aes_256_gcm(), s_wk, s_aead_nonce);
|
||||
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, encrypted + encrypted_len);
|
||||
|
||||
if (is_orig)
|
||||
fmt_seq(c_seq, s_aead_tag);
|
||||
else
|
||||
fmt_seq(s_seq, s_aead_tag);
|
||||
|
||||
s_aead_tag[8] = content_type;
|
||||
s_aead_tag[9] = MSB(raw_tls_version);
|
||||
s_aead_tag[10] = LSB(raw_tls_version);
|
||||
s_aead_tag[11] = MSB(encrypted_len);
|
||||
s_aead_tag[12] = LSB(encrypted_len);
|
||||
|
||||
u_char *decrypted = (u_char*)malloc(encrypted_len);
|
||||
int decrypted_len = 0;
|
||||
|
||||
EVP_DecryptUpdate(ctx, NULL, &decrypted_len, s_aead_tag, 13);
|
||||
EVP_DecryptUpdate(ctx, decrypted, &decrypted_len, (const u_char*) encrypted, encrypted_len);
|
||||
|
||||
int res = 0;
|
||||
if ( ! (res = EVP_DecryptFinal(ctx, NULL, &res)) )
|
||||
{
|
||||
printf("Decryption failed with return code %d. Invalid key?\n", res);
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
free(decrypted);
|
||||
return false;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
ForwardDecryptedData(decrypted_len, reinterpret_cast<const u_char*>(decrypted), is_orig);
|
||||
|
||||
free(decrypted);
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is only reached if key derivation somehow failed
|
||||
return false;
|
||||
}
|
||||
|
||||
void SSL_Analyzer::ForwardDecryptedData(int len, const u_char* data, bool is_orig)
|
||||
{
|
||||
if ( ! pia )
|
||||
{
|
||||
pia = new analyzer::pia::PIA_TCP(Conn());
|
||||
if ( AddChildAnalyzer(pia) )
|
||||
{
|
||||
pia->FirstPacket(true, nullptr);
|
||||
pia->FirstPacket(false, nullptr);
|
||||
}
|
||||
else
|
||||
reporter->FatalError("Could not initialize PIA");
|
||||
|
||||
// FIXME: Also statically add HTTP/H2 at the moment.
|
||||
// We should move this bit to scriptland
|
||||
auto http = analyzer_mgr->InstantiateAnalyzer("HTTP", Conn());
|
||||
if ( http )
|
||||
AddChildAnalyzer(http);
|
||||
auto http2 = analyzer_mgr->InstantiateAnalyzer("HTTP2", Conn());
|
||||
if ( http2 )
|
||||
AddChildAnalyzer(http2);
|
||||
}
|
||||
|
||||
ForwardStream(len, data, is_orig);
|
||||
}
|
||||
|
||||
} // namespace zeek::analyzer::ssl
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue