Complete rewrite to SSL analyzer.

* I haven't removed handwritten analyzer code yet although it isn't built anymore.
* The ssl.bro script is just an example and doesn't keep any state yet.
This commit is contained in:
Seth Hall 2011-03-28 12:15:53 -04:00
parent 871eff9f90
commit 7faf3e0f3b
15 changed files with 1225 additions and 1020 deletions

View file

@ -11,6 +11,7 @@ global no_handler: event(name: string, val: any);
# Type declarations
type string_array: table[count] of string;
type string_set: set[string];
type count_set: set[count];
type index_vec: vector of count;
type string_vec: vector of string;
@ -917,15 +918,19 @@ global dns_max_queries = 5;
const ssl_max_cipherspec_size = 68 &redef;
# SSL and X.509 types.
type cipher_suites_list: set[count];
type SSL_sessionID: table[count] of count;
type X509_extension: table[count] of string;
type X509Extensions: table[count] of string;
type X509: record {
issuer: string;
version: count;
serial: string;
subject: string;
orig_addr: addr;
issuer: string;
not_valid_before: time;
not_valid_after: time;
};
# This is indexed with the CA's name and yields a DER (binary) encoded certificate.
const root_ca_certs: table[string] of string = {} &redef;
type http_stats_rec: record {
num_requests: count;
num_replies: count;

167
policy/ssl-mozilla-CAs.bro Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -34,7 +34,6 @@
#include "Portmap.h"
#include "POP3.h"
#include "SSH.h"
#include "SSLProxy.h"
#include "SSL-binpac.h"
// Keep same order here as in AnalyzerTag definition!
@ -113,8 +112,6 @@ const Analyzer::Config Analyzer::analyzer_configs[] = {
SMTP_Analyzer::Available, 0, false },
{ AnalyzerTag::SSH, "SSH", SSH_Analyzer::InstantiateAnalyzer,
SSH_Analyzer::Available, 0, false },
{ AnalyzerTag::SSL, "SSL", SSLProxy_Analyzer::InstantiateAnalyzer,
SSLProxy_Analyzer::Available, 0, false },
{ AnalyzerTag::Telnet, "TELNET", Telnet_Analyzer::InstantiateAnalyzer,
Telnet_Analyzer::Available, 0, false },
@ -133,7 +130,7 @@ const Analyzer::Config Analyzer::analyzer_configs[] = {
{ AnalyzerTag::RPC_UDP_BINPAC, "RPC_UDP_BINPAC",
RPC_UDP_Analyzer_binpac::InstantiateAnalyzer,
RPC_UDP_Analyzer_binpac::Available, 0, false },
{ AnalyzerTag::SSL_BINPAC, "SSL_BINPAC",
{ AnalyzerTag::SSL, "SSL",
SSL_Analyzer_binpac::InstantiateAnalyzer,
SSL_Analyzer_binpac::Available, 0, false },
@ -165,7 +162,6 @@ const Analyzer::Config Analyzer::analyzer_configs[] = {
{ AnalyzerTag::Contents_SMB, "CONTENTS_SMB", 0, 0, 0, false },
{ AnalyzerTag::Contents_RPC, "CONTENTS_RPC", 0, 0, 0, false },
{ AnalyzerTag::Contents_NFS, "CONTENTS_NFS", 0, 0, 0, false },
{ AnalyzerTag::Contents_SSL, "CONTENTS_SSL", 0, 0, 0, false },
};
AnalyzerTimer::~AnalyzerTimer()

View file

@ -29,12 +29,11 @@ namespace AnalyzerTag {
DCE_RPC, DNS, Finger, FTP, Gnutella, HTTP, Ident, IRC,
Login, NCP, NetbiosSSN, NFS, NTP, POP3, Portmapper, Rlogin,
RPC, Rsh, SMB, SMTP, SSH,
SSL,
Telnet,
// Application-layer analyzers, binpac-generated.
DHCP_BINPAC, DNS_TCP_BINPAC, DNS_UDP_BINPAC,
HTTP_BINPAC, RPC_UDP_BINPAC, SSL_BINPAC,
HTTP_BINPAC, RPC_UDP_BINPAC, SSL,
// Other
File, Backdoor, InterConn, SteppingStone, TCPStats,
@ -43,7 +42,6 @@ namespace AnalyzerTag {
Contents, ContentLine, NVT, Zip, Contents_DNS, Contents_NCP,
Contents_NetbiosSSN, Contents_Rlogin, Contents_Rsh,
Contents_DCE_RPC, Contents_SMB, Contents_RPC, Contents_NFS,
Contents_SSL,
// End-marker.
LastAnalyzer
};

View file

@ -199,8 +199,6 @@ binpac_target(smb.pac
smb-protocol.pac smb-pipe.pac smb-mailslot.pac)
binpac_target(ssl.pac
ssl-defs.pac ssl-protocol.pac ssl-analyzer.pac)
binpac_target(ssl-record-layer.pac
ssl-defs.pac ssl.pac)
########################################################################
## bro target
@ -222,8 +220,10 @@ set(dns_SRCS nb_dns.c nb_dns.h)
set_source_files_properties(nb_dns.c PROPERTIES COMPILE_FLAGS
-fno-strict-aliasing)
set(openssl_SRCS X509.cc SSLCiphers.cc SSLInterpreter.cc SSLProxy.cc
SSLv2.cc SSLv3.cc SSLv3Automaton.cc)
#set(openssl_SRCS X509.cc SSLCiphers.cc SSLInterpreter.cc SSLProxy.cc
# SSLv2.cc SSLv3.cc SSLv3Automaton.cc)
set(openssl_SRCS)
if (USE_NMALLOC)
set(malloc_SRCS malloc.c)

View file

@ -18,6 +18,7 @@ RecordType* pcap_packet;
RecordType* signature_state;
EnumType* transport_proto;
TableType* string_set;
TableType* count_set;
RecordType* net_stats;
@ -201,8 +202,6 @@ StringVal* ssl_private_key;
StringVal* ssl_passphrase;
StringVal* x509_crl_file;
TableType* x509_extension;
TableType* SSL_sessionID;
Val* profiling_file;
double profiling_interval;
@ -366,10 +365,7 @@ void init_net_var()
x509_trusted_cert_path = opt_internal_string("X509_trusted_cert_path");
ssl_store_cert_path = opt_internal_string("ssl_store_cert_path");
x509_type = internal_type("X509")->AsRecordType();
cipher_suites_list = internal_type("cipher_suites_list")->AsTableType();
x509_crl_file = opt_internal_string("X509_crl_file");
x509_extension = internal_type("X509_extension")->AsTableType();
SSL_sessionID = internal_type("SSL_sessionID")->AsTableType();
non_analyzed_lifetime = opt_internal_double("non_analyzed_lifetime");
tcp_inactivity_timeout = opt_internal_double("tcp_inactivity_timeout");

View file

@ -21,6 +21,7 @@ extern RecordType* SYN_packet;
extern RecordType* pcap_packet;
extern EnumType* transport_proto;
extern TableType* string_set;
extern TableType* count_set;
extern RecordType* net_stats;
@ -61,11 +62,8 @@ extern int ssl_store_key_material;
extern int ssl_max_cipherspec_size;
extern StringVal* ssl_store_cert_path;
extern StringVal* x509_trusted_cert_path;
extern TableType* cipher_suites_list;
extern RecordType* x509_type;
extern StringVal* x509_crl_file;
extern TableType* x509_extension;
extern TableType* SSL_sessionID;
extern double non_analyzed_lifetime;
extern double tcp_inactivity_timeout;

View file

@ -1,5 +1,3 @@
// $Id:$
#include "SSL-binpac.h"
#include "TCP_Reassembler.h"
#include "util.h"
@ -8,13 +6,10 @@
bool SSL_Analyzer_binpac::warnings_generated = false;
SSL_Analyzer_binpac::SSL_Analyzer_binpac(Connection* c)
: TCP_ApplicationAnalyzer(AnalyzerTag::SSL_BINPAC, c)
: TCP_ApplicationAnalyzer(AnalyzerTag::SSL, c)
{
ssl = new binpac::SSL::SSLAnalyzer;
ssl->set_bro_analyzer(this);
records = new binpac::SSLRecordLayer::SSLRecordLayerAnalyzer;
records->set_ssl_analyzer(ssl);
interp = new binpac::SSL::SSLAnalyzer;
interp->set_bro_analyzer(this);
if ( ! warnings_generated )
generate_warnings();
@ -22,23 +17,21 @@ SSL_Analyzer_binpac::SSL_Analyzer_binpac(Connection* c)
SSL_Analyzer_binpac::~SSL_Analyzer_binpac()
{
delete records;
delete ssl;
delete interp;
}
void SSL_Analyzer_binpac::Done()
{
TCP_ApplicationAnalyzer::Done();
records->FlowEOF(true);
records->FlowEOF(false);
interp->FlowEOF(true);
interp->FlowEOF(false);
}
void SSL_Analyzer_binpac::EndpointEOF(TCP_Reassembler* endp)
{
TCP_ApplicationAnalyzer::EndpointEOF(endp);
records->FlowEOF(endp->IsOrig());
ssl->FlowEOF(endp->IsOrig());
interp->FlowEOF(endp->IsOrig());
}
void SSL_Analyzer_binpac::DeliverStream(int len, const u_char* data, bool orig)
@ -50,13 +43,13 @@ void SSL_Analyzer_binpac::DeliverStream(int len, const u_char* data, bool orig)
if ( TCP()->IsPartial() )
return;
records->NewData(orig, data, data + len);
interp->NewData(orig, data, data + len);
}
void SSL_Analyzer_binpac::Undelivered(int seq, int len, bool orig)
{
TCP_ApplicationAnalyzer::Undelivered(seq, len, orig);
records->NewGap(orig, len);
interp->NewGap(orig, len);
}
void SSL_Analyzer_binpac::warn_(const char* msg)

View file

@ -1,5 +1,3 @@
// $Id:$
#ifndef ssl_binpac_h
#define ssl_binpac_h
@ -23,11 +21,9 @@ public:
static bool Available()
{
return FLAGS_use_binpac &&
(ssl_certificate_seen || ssl_certificate ||
ssl_conn_attempt || ssl_conn_server_reply ||
ssl_conn_established || ssl_conn_reused ||
ssl_conn_alert);
return ( ssl_client_hello || ssl_server_hello ||
ssl_established || ssl_extension || ssl_alert ||
x509_certificate || x509_extension || x509_error );
}
static bool warnings_generated;
@ -35,8 +31,7 @@ public:
static void generate_warnings();
protected:
binpac::SSLRecordLayer::SSLRecordLayerAnalyzer* records;
binpac::SSL::SSLAnalyzer* ssl;
binpac::SSL::SSLAnalyzer* interp;
};
#endif

View file

@ -264,19 +264,17 @@ event http_stats%(c: connection, stats: http_stats_rec%);
event ssh_client_version%(c: connection, version: string%);
event ssh_server_version%(c: connection, version: string%);
event ssl_certificate_seen%(c: connection, is_server: bool%);
event ssl_certificate%(c: connection, cert: X509, is_server: bool%);
event ssl_conn_attempt%(c: connection, version: count, ciphers: cipher_suites_list%);
event ssl_conn_server_reply%(c: connection, version: count, ciphers: cipher_suites_list%);
event ssl_conn_established%(c: connection, version: count, cipher_suite: count%);
event ssl_conn_reused%(c: connection, session_id: SSL_sessionID%);
event ssl_conn_alert%(c: connection, version: count, level: count,
description: count%);
event ssl_conn_weak%(name: string, c: connection%);
event ssl_client_hello%(c: connection, version: count, possible_ts: time, session_id: string, ciphers: count_set%);
event ssl_server_hello%(c: connection, version: count, possible_ts: time, session_id: string, cipher: count, comp_method: count%);
event ssl_extension%(c: connection, code: count, val: string%);
event ssl_established%(c: connection%);
event ssl_alert%(c: connection, level: count, desc: count%);
event ssl_session_insertion%(c: connection, id: SSL_sessionID%);
event process_X509_extensions%(c: connection, ex: X509_extension%);
event ssl_X509_error%(c: connection, err: int, err_string: string%);
event x509_certificate%(c: connection, cert: X509, is_server: bool, chain_idx: count, chain_len: count%);
event x509_extension%(c: connection, data: string%);
event x509_error%(c: connection, err: int, err_string: string%);
event x509_cert_validated%(c: connection%);
event x509_cert_body%(c: connection, cert: string%);
event stp_create_endp%(c: connection, e: int, is_orig: bool%);
event stp_resume_endp%(e: int%);

View file

@ -1,5 +1,3 @@
# $Id:$
# Analyzer for SSL (Bro-specific part).
%extern{
@ -12,7 +10,7 @@
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>
#include "X509.h"
#include <openssl/asn1.h>
%}
@ -46,22 +44,15 @@
%}
function to_table_val(data : uint8[]) : TableVal
function to_string_val(data : uint8[]) : StringVal
%{
TableVal* tv = new TableVal(SSL_sessionID);
for ( unsigned int i = 0; i < data->size(); i += 4 )
{
uint32 temp = 0;
for ( unsigned int j = 0; j < 4; ++j )
if ( i + j < data->size() )
temp |= (*data)[i + j] << (24 - 8 * j);
char tmp[32];
memset(tmp, 0, sizeof(tmp));
if ( data )
for ( unsigned int i = data->size(); i > 0; --i )
tmp[i-1] = (*data)[i-1];
Val* idx = new Val(i / 4, TYPE_COUNT);
tv->Assign(idx, new Val((*data)[i], TYPE_COUNT));
Unref(idx);
}
return tv;
return new StringVal(32, tmp);
%}
function version_ok(vers : uint16) : bool
@ -101,23 +92,27 @@ function convert_ciphers_uint16(ciph : uint16[]) : int[]
refine analyzer SSLAnalyzer += {
%member{
Analyzer* bro_analyzer_;
vector<uint8>* client_session_id_;
vector<int>* advertised_ciphers_;
int version_;
int cipher_;
X509_STORE* ctx;
%}
%init{
bro_analyzer_ = 0;
ctx = 0;
if ( !ctx )
{
ctx = X509_STORE_new();
TableVal* root_certs = opt_internal_table("root_ca_certs")->AsTableVal();
ListVal* idxs = root_certs->ConvertToPureList();
client_session_id_ = 0;
advertised_ciphers_ = new vector<int>;
version_ = -1;
cipher_ = -1;
if ( ! X509_Cert::bInited )
X509_Cert::init();
for ( int i = 0; i < idxs->Length(); ++i )
{
Val* key = idxs->Index(i);
StringVal *sv = root_certs->Lookup(key)->AsStringVal();
const uint8* data = sv->Bytes();
X509* x = d2i_X509_binpac(NULL, &data, sv->Len());
X509_STORE_add_cert(ctx, x);
}
}
%}
%eof{
@ -128,11 +123,6 @@ refine analyzer SSLAnalyzer += {
%}
%cleanup{
delete client_session_id_;
client_session_id_ = 0;
delete advertised_ciphers_;
advertised_ciphers_ = 0;
%}
function bro_analyzer() : Analyzer
@ -145,186 +135,132 @@ refine analyzer SSLAnalyzer += {
bro_analyzer_ = a;
%}
function check_cipher(cipher : int) : bool
%{
if ( ! ssl_compare_cipherspecs )
return true;
if ( std::find(advertised_ciphers_->begin(),
advertised_ciphers_->end(), cipher) ==
advertised_ciphers_->end() )
{
bro_analyzer()->ProtocolViolation("chosen cipher not advertised before");
return false;
}
return true;
%}
function certificate_error(err_num : int) : void
%{
StringVal* err_str =
new StringVal(X509_verify_cert_error_string(err_num));
BifEvent::generate_ssl_X509_error(bro_analyzer_, bro_analyzer_->Conn(),
BifEvent::generate_x509_error(bro_analyzer_, bro_analyzer_->Conn(),
err_num, err_str);
%}
function proc_change_cipher_spec(msg : ChangeCipherSpec) : bool
function proc_change_cipher_spec(rec: SSLRecord) : bool
%{
if ( state_ == STATE_TRACK_LOST )
bro_analyzer()->ProtocolViolation(fmt("unexpected ChangeCipherSpec from %s at state %s",
orig_label(current_record_is_orig_).c_str(),
orig_label(${rec.is_orig}).c_str(),
state_label(old_state_).c_str()));
return true;
%}
function proc_application_data(msg : ApplicationData) : bool
function proc_application_data(rec: SSLRecord) : bool
%{
if ( state_ != STATE_CONN_ESTABLISHED )
bro_analyzer()->ProtocolViolation(fmt("unexpected ApplicationData from %s at state %s",
orig_label(current_record_is_orig_).c_str(),
orig_label(${rec.is_orig}).c_str(),
state_label(old_state_).c_str()));
return true;
%}
function proc_alert(level : int, description : int) : bool
function proc_alert(rec: SSLRecord, level : int, desc : int) : bool
%{
BifEvent::generate_ssl_conn_alert(bro_analyzer_, bro_analyzer_->Conn(),
current_record_version_, level,
description);
BifEvent::generate_ssl_alert(bro_analyzer_, bro_analyzer_->Conn(),
level, desc);
return true;
%}
function proc_client_hello(version : uint16, session_id : uint8[],
csuits : int[]) : bool
function proc_client_hello(rec: SSLRecord,
version : uint16, ts : double,
session_id : uint8[],
cipher_suites : int[]) : bool
%{
if ( state_ == STATE_TRACK_LOST )
bro_analyzer()->ProtocolViolation(fmt("unexpected client hello message from %s in state %s",
orig_label(current_record_is_orig_).c_str(),
orig_label(${rec.is_orig}).c_str(),
state_label(old_state_).c_str()));
if ( ! version_ok(version) )
bro_analyzer()->ProtocolViolation(fmt("unsupported client SSL version 0x%04x", version));
delete client_session_id_;
client_session_id_ = new vector<uint8>(*session_id);
TableVal* cipher_table = new TableVal(cipher_suites_list);
for ( unsigned int i = 0; i < csuits->size(); ++i )
if ( ssl_client_hello )
{
Val* ciph = new Val((*csuits)[i], TYPE_COUNT);
cipher_table->Assign(ciph, 0);
BroType* count_t = base_type(TYPE_COUNT);
TypeList* set_index = new TypeList(count_t);
set_index->Append(count_t);
SetType* s = new SetType(set_index, 0);
TableVal* cipher_set = new TableVal(s);
for ( unsigned int i = 0; i < cipher_suites->size(); ++i )
{
Val* ciph = new Val((*cipher_suites)[i], TYPE_COUNT);
cipher_set->Assign(ciph, 0);
Unref(ciph);
}
BifEvent::generate_ssl_conn_attempt(bro_analyzer_, bro_analyzer_->Conn(),
version, cipher_table);
if ( ssl_compare_cipherspecs )
{
delete advertised_ciphers_;
advertised_ciphers_ = csuits;
BifEvent::generate_ssl_client_hello(bro_analyzer_, bro_analyzer_->Conn(),
version, ts,
to_string_val(session_id),
cipher_set);
}
else
delete csuits;
return true;
%}
function proc_server_hello(version : uint16, session_id : uint8[],
ciphers : int[], v2_sess_hit : int) : bool
function proc_server_hello(rec: SSLRecord,
version : uint16, ts : double,
session_id : uint8[],
cipher_suite : uint16,
comp_method : uint8) : bool
%{
if ( state_ == STATE_TRACK_LOST )
bro_analyzer()->ProtocolViolation(fmt("unexpected server hello message from %s in state %s",
orig_label(current_record_is_orig_).c_str(),
orig_label(${rec.is_orig}).c_str(),
state_label(old_state_).c_str()));
if ( ! version_ok(version) )
bro_analyzer()->ProtocolViolation(fmt("unsupported server SSL version 0x%04x", version));
version_ = version;
TableVal* chosen_ciphers = new TableVal(cipher_suites_list);
for ( unsigned int i = 0; i < ciphers->size(); ++i )
if ( ssl_server_hello )
{
Val* ciph = new Val((*ciphers)[i], TYPE_COUNT);
chosen_ciphers->Assign(ciph, 0);
Unref(ciph);
}
BifEvent::generate_ssl_conn_server_reply(bro_analyzer_,
BifEvent::generate_ssl_server_hello(bro_analyzer_,
bro_analyzer_->Conn(),
version_, chosen_ciphers);
if ( v2_sess_hit < 0 )
{ // this is SSLv3
cipher_ = (*ciphers)[0];
check_cipher(cipher_);
TableVal* tv = to_table_val(session_id);
if ( client_session_id_ &&
*client_session_id_ == *session_id )
BifEvent::generate_ssl_conn_reused(bro_analyzer_,
bro_analyzer_->Conn(), tv);
else
BifEvent::generate_ssl_session_insertion(bro_analyzer_,
bro_analyzer_->Conn(), tv);
delete ciphers;
}
else if ( v2_sess_hit > 0 )
{ // this is SSLv2 and a session hit
if ( client_session_id_ )
{
TableVal* tv = to_table_val(client_session_id_);
BifEvent::generate_ssl_conn_reused(bro_analyzer_,
bro_analyzer_->Conn(), tv);
}
// We don't know the chosen cipher, as there is
// no session storage.
BifEvent::generate_ssl_conn_established(bro_analyzer_,
bro_analyzer_->Conn(),
version_, 0xffffffff);
delete ciphers;
}
else
{
// This is SSLv2; we have to set advertised
// ciphers to server ciphers.
if ( ssl_compare_cipherspecs )
{
delete advertised_ciphers_;
advertised_ciphers_ = ciphers;
}
version, ts,
to_string_val(session_id),
cipher_suite, comp_method);
}
bro_analyzer()->ProtocolConfirmation();
return true;
%}
function proc_certificate(certificates : bytestring[]) : bool
function proc_ssl_extension(type: int, data: bytestring) : bool
%{
if ( ssl_extension )
BifEvent::generate_ssl_extension(bro_analyzer_,
bro_analyzer_->Conn(), type,
new StringVal(data.length(), (const char*) data.data()));
return true;
%}
function proc_certificate(rec: SSLRecord, certificates : bytestring[]) : bool
%{
if ( state_ == STATE_TRACK_LOST )
bro_analyzer()->ProtocolViolation(fmt("unexpected certificate message from %s in state %s",
orig_label(current_record_is_orig_).c_str(),
orig_label(${rec.is_orig}).c_str(),
state_label(old_state_).c_str()));
if ( ! ssl_analyze_certificates )
return true;
if ( certificates->size() == 0 )
return true;
BifEvent::generate_ssl_certificate_seen(bro_analyzer_,
bro_analyzer_->Conn(),
! current_record_is_orig_);
STACK_OF(X509)* untrusted_certs = 0;
const bytestring& cert = (*certificates)[0];
if ( x509_certificate )
{
X509* pCert = 0;
for ( unsigned int i = 0; i < certificates->size(); ++i )
{
const bytestring& cert = (*certificates)[i];
const uint8* data = cert.data();
X509* pCert = d2i_X509_binpac(NULL, &data, cert.length());
if ( ! pCert )
X509* pTemp = d2i_X509_binpac(NULL, &data, cert.length());
if ( ! pTemp )
{
// X509_V_UNABLE_TO_DECRYPT_CERT_SIGNATURE
certificate_error(4);
@ -332,95 +268,117 @@ refine analyzer SSLAnalyzer += {
}
RecordVal* pX509Cert = new RecordVal(x509_type);
char tmp[256];
X509_NAME_oneline(X509_get_issuer_name(pCert), tmp, sizeof tmp);
pX509Cert->Assign(0, new StringVal(tmp));
X509_NAME_oneline(X509_get_subject_name(pCert), tmp, sizeof tmp);
pX509Cert->Assign(0, new Val((uint64) X509_get_version(pTemp), TYPE_COUNT));
pX509Cert->Assign(1, new StringVal(16, (const char*) X509_get_serialNumber(pTemp)->data));
X509_NAME_oneline(X509_get_subject_name(pTemp), tmp, sizeof tmp);
pX509Cert->Assign(2, new StringVal(tmp));
X509_NAME_oneline(X509_get_issuer_name(pTemp), tmp, sizeof tmp);
pX509Cert->Assign(3, new StringVal(tmp));
pX509Cert->Assign(4, new Val(get_time_from_asn1(X509_get_notBefore(pTemp)), TYPE_TIME));
pX509Cert->Assign(5, new Val(get_time_from_asn1(X509_get_notAfter(pTemp)), TYPE_TIME));
pX509Cert->Assign(1, new StringVal(tmp));
pX509Cert->Assign(2, new AddrVal(bro_analyzer_->Conn()->OrigAddr()));
BifEvent::generate_x509_certificate(bro_analyzer_, bro_analyzer_->Conn(),
pX509Cert,
${rec.is_orig},
i, certificates->size()-1);
BifEvent::generate_ssl_certificate(bro_analyzer_, bro_analyzer_->Conn(),
pX509Cert, current_record_is_orig_);
if ( X509_get_ext_count(pCert) > 0 )
// Are there any X509 extensions?
if ( x509_extension && X509_get_ext_count(pTemp) > 0 )
{
TableVal* x509ex = new TableVal(x509_extension);
for ( int k = 0; k < X509_get_ext_count(pCert); ++k )
BroType* count_t = base_type(TYPE_COUNT);
TypeList* set_index = new TypeList(count_t);
set_index->Append(count_t);
SetType* s = new SetType(set_index, 0);
TableVal* x509ex = new TableVal(s);
int num_ext = X509_get_ext_count(pTemp);
for ( int k = 0; k < num_ext; ++k )
{
X509_EXTENSION* ex = X509_get_ext(pCert, k);
ASN1_OBJECT* obj = X509_EXTENSION_get_object(ex);
char *pBuffer = 0;
int length = 0;
char buf[256];
i2t_ASN1_OBJECT(buf, sizeof(buf), obj);
Val* index = new Val(k+1, TYPE_COUNT);
Val* value = new StringVal(strlen(buf), buf);
x509ex->Assign(index, value);
Unref(index);
X509_EXTENSION* ex = X509_get_ext(pTemp, k);
if (ex)
{
ASN1_STRING *pString = X509_EXTENSION_get_data(ex);
length = ASN1_STRING_to_UTF8((unsigned char**)&pBuffer, pString);
//i2t_ASN1_OBJECT(&pBuffer, length, obj)
// -1 indicates an error.
if ( length < 0 ) continue;
StringVal* value = new StringVal(length, pBuffer);
BifEvent::generate_x509_extension(bro_analyzer_,
bro_analyzer_->Conn(), value);
OPENSSL_free(pBuffer);
}
}
}
BifEvent::generate_process_X509_extensions(bro_analyzer_,
bro_analyzer_->Conn(), x509ex);
// Only grab the cert body for the first
// certificate.
if ( x509_cert_body && i == 0 )
{
StringVal* der = new StringVal(cert.length(), (const char*) cert.data());
BifEvent::generate_x509_cert_body(bro_analyzer_,
bro_analyzer_->Conn(), der);
}
if ( ssl_verify_certificates )
{
STACK_OF(X509)* untrusted_certs = 0;
if ( certificates->size() > 1 )
if ( i == 0 )
// Store the first cert
pCert = pTemp;
else if ( i == 1 )
{
// Init the cert stack on the
// second cert seen.
untrusted_certs = sk_X509_new_null();
if ( ! untrusted_certs )
{
// X509_V_ERR_OUT_OF_MEM;
certificate_error(17);
return false;
}
for ( unsigned int i = 1;
i < certificates->size(); ++i )
{
const bytestring& temp =
(*certificates)[i];
const uint8* tdata = temp.data();
X509* pTemp = d2i_X509_binpac(NULL,
&tdata, temp.length());
if ( ! pTemp )
{
// X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT
certificate_error(2);
return false;
certificate_error(X509_V_ERR_OUT_OF_MEM);
}
if ( i > 0 )
// If this isn't the first cert,
// push it onto the cert stack.
sk_X509_push(untrusted_certs, pTemp);
}
}
// Verify first cert or full chain upon
// reaching the last cert.
if ( certificates->size() == i+1 )
{
X509_STORE_CTX csc;
X509_STORE_CTX_init(&csc, X509_Cert::ctx,
pCert, untrusted_certs);
X509_STORE_CTX_set_time(&csc, 0, time_t(network_time()));
if (! X509_verify_cert(&csc))
certificate_error(csc.error);
X509_STORE_CTX_cleanup(&csc);
X509_STORE_CTX_init(&csc, ctx,
pCert,
untrusted_certs);
X509_STORE_CTX_set_time(&csc, 0, (time_t) network_time());
if ( X509_verify_cert(&csc) )
BifEvent::generate_x509_cert_validated(bro_analyzer_,
bro_analyzer_->Conn());
else
certificate_error(csc.error);
X509_STORE_CTX_cleanup(&csc);
if ( untrusted_certs )
sk_X509_pop_free(untrusted_certs, X509_free);
}
X509_free(pCert);
}
}
}
return true;
%}
function proc_v2_certificate(cert : bytestring) : bool
function proc_v2_certificate(rec: SSLRecord, cert : bytestring) : bool
%{
vector<bytestring>* cert_list = new vector<bytestring>(1,cert);
bool ret = proc_certificate(cert_list);
bool ret = proc_certificate(rec, cert_list);
delete cert_list;
return ret;
%}
function proc_v3_certificate(cl : CertificateList) : bool
function proc_v3_certificate(rec: SSLRecord, cl : CertificateList) : bool
%{
vector<X509Certificate*>* certs = cl->val();
vector<bytestring>* cert_list = new vector<bytestring>();
@ -428,65 +386,61 @@ refine analyzer SSLAnalyzer += {
std::transform(certs->begin(), certs->end(),
std::back_inserter(*cert_list), extract_certs());
bool ret = proc_certificate(cert_list);
bool ret = proc_certificate(rec, cert_list);
delete cert_list;
return ret;
%}
function proc_v2_client_master_key(cipher : int) : bool
function proc_v2_client_master_key(rec: SSLRecord, cipher_kind: int) : bool
%{
if ( state_ == STATE_TRACK_LOST )
bro_analyzer()->ProtocolViolation(fmt("unexpected v2 client master key message from %s in state %s",
orig_label(current_record_is_orig_).c_str(),
orig_label(${rec.is_orig}).c_str(),
state_label(old_state_).c_str()));
check_cipher(cipher);
BifEvent::generate_ssl_conn_established(bro_analyzer_,
bro_analyzer_->Conn(), version_, cipher);
BifEvent::generate_ssl_established(bro_analyzer_,
bro_analyzer_->Conn());
return true;
%}
function proc_unknown_handshake(msg_type : int) : bool
function proc_unknown_handshake(hs: Handshake, is_orig: bool) : bool
%{
bro_analyzer()->ProtocolViolation(fmt("unknown handshake message (%d) from %s",
msg_type, orig_label(current_record_is_orig_).c_str()));
${hs.msg_type}, orig_label(is_orig).c_str()));
return true;
%}
function proc_handshake(msg : Handshake) : bool
function proc_handshake(hs: Handshake, is_orig: bool) : bool
%{
if ( state_ == STATE_TRACK_LOST )
bro_analyzer()->ProtocolViolation(fmt("unexpected Handshake message %s from %s in state %s",
handshake_type_label(msg->msg_type()).c_str(),
orig_label(current_record_is_orig_).c_str(),
handshake_type_label(${hs.msg_type}).c_str(),
orig_label(is_orig).c_str(),
state_label(old_state_).c_str()));
return true;
%}
function proc_unknown_record(msg : UnknownRecord) : bool
function proc_unknown_record(rec: SSLRecord) : bool
%{
bro_analyzer()->ProtocolViolation(fmt("unknown SSL record type (%d) from %s",
current_record_type_,
orig_label(current_record_is_orig_).c_str()));
${rec.content_type},
orig_label(${rec.is_orig}).c_str()));
return true;
%}
function proc_ciphertext_record(msg : CiphertextRecord) : bool
function proc_ciphertext_record(rec : SSLRecord) : bool
%{
if ( state_ == STATE_TRACK_LOST )
bro_analyzer()->ProtocolViolation(fmt("unexpected ciphertext record from %s in state %s",
orig_label(current_record_is_orig_).c_str(),
orig_label(${rec.is_orig}).c_str(),
state_label(old_state_).c_str()));
if ( state_ == STATE_CONN_ESTABLISHED &&
else if ( state_ == STATE_CONN_ESTABLISHED &&
old_state_ == STATE_COMM_ENCRYPTED )
{
BifEvent::generate_ssl_conn_established(bro_analyzer_,
bro_analyzer_->Conn(),
version_, cipher_);
}
BifEvent::generate_ssl_established(bro_analyzer_,
bro_analyzer_->Conn());
return true;
%}
@ -494,72 +448,80 @@ refine analyzer SSLAnalyzer += {
};
refine typeattr ChangeCipherSpec += &let {
proc : bool = $context.analyzer.proc_change_cipher_spec(this)
proc : bool = $context.analyzer.proc_change_cipher_spec(rec)
&requires(state_changed);
};
refine typeattr Alert += &let {
proc : bool = $context.analyzer.proc_alert(level, description);
proc : bool = $context.analyzer.proc_alert(rec, level, description);
};
refine typeattr V2Error += &let {
proc : bool = $context.analyzer.proc_alert(-1, error_code);
proc : bool = $context.analyzer.proc_alert(rec, -1, error_code);
};
refine typeattr ApplicationData += &let {
proc : bool = $context.analyzer.proc_application_data(this);
proc : bool = $context.analyzer.proc_application_data(rec);
};
refine typeattr ClientHello += &let {
proc : bool = $context.analyzer.proc_client_hello(client_version,
proc : bool = $context.analyzer.proc_client_hello(rec, client_version,
gmt_unix_time,
session_id, convert_ciphers_uint16(csuits))
&requires(state_changed);
};
refine typeattr V2ClientHello += &let {
proc : bool = $context.analyzer.proc_client_hello(client_version,
proc : bool = $context.analyzer.proc_client_hello(rec, client_version, 0,
session_id, convert_ciphers_uint24(ciphers))
&requires(state_changed);
};
refine typeattr ServerHello += &let {
proc : bool = $context.analyzer.proc_server_hello(server_version,
session_id, convert_ciphers_uint16(cipher_suite), -1)
proc : bool = $context.analyzer.proc_server_hello(rec, server_version,
gmt_unix_time, session_id, cipher_suite,
compression_method)
&requires(state_changed);
};
refine typeattr V2ServerHello += &let {
proc : bool = $context.analyzer.proc_server_hello(server_version, 0,
convert_ciphers_uint24(ciphers), session_id_hit)
proc : bool = $context.analyzer.proc_server_hello(rec, server_version, 0, 0,
convert_ciphers_uint24(ciphers)[0], 0)
&requires(state_changed);
cert : bool = $context.analyzer.proc_v2_certificate(cert_data)
cert : bool = $context.analyzer.proc_v2_certificate(rec, cert_data)
&requires(proc);
};
refine typeattr Certificate += &let {
proc : bool = $context.analyzer.proc_v3_certificate(certificates)
proc : bool = $context.analyzer.proc_v3_certificate(rec, certificates)
&requires(state_changed);
};
refine typeattr V2ClientMasterKey += &let {
proc : bool = $context.analyzer.proc_v2_client_master_key(to_int()(cipher_kind))
proc : bool = $context.analyzer.proc_v2_client_master_key(rec, to_int()(cipher_kind))
&requires(state_changed);
};
refine typeattr UnknownHandshake += &let {
proc : bool = $context.analyzer.proc_unknown_handshake(msg_type);
proc : bool = $context.analyzer.proc_unknown_handshake(hs, is_orig);
};
refine typeattr Handshake += &let {
proc : bool = $context.analyzer.proc_handshake(this);
proc : bool = $context.analyzer.proc_handshake(this, rec.is_orig);
};
refine typeattr UnknownRecord += &let {
proc : bool = $context.analyzer.proc_unknown_record(this);
proc : bool = $context.analyzer.proc_unknown_record(rec);
};
refine typeattr CiphertextRecord += &let {
proc : bool = $context.analyzer.proc_ciphertext_record(this)
&requires(state_changed);
proc : bool = $context.analyzer.proc_ciphertext_record(rec);
}
refine typeattr SSLExtension += &let {
proc : bool = $context.analyzer.proc_ssl_extension(type, data);
};

View file

@ -1,5 +1,3 @@
# $Id:$
# Analyzer for SSL messages (general part).
# To be used in conjunction with an SSL record-layer analyzer.
# Separation is necessary due to possible fragmentation of SSL records.
@ -26,6 +24,57 @@ type uint24 = record {
extern type to_int;
type SSLRecord(is_orig: bool) = record {
head0 : uint8;
head1 : uint8;
head2 : uint8;
head3 : uint8;
head4 : uint8;
rec : RecordText(this, is_orig) &requires(content_type), &restofdata;
} &length = length+5, &byteorder=bigendian,
&let {
version : int =
$context.analyzer.determine_ssl_version(head0, head1, head2);
content_type : int = case version of {
UNKNOWN_VERSION -> 0;
SSLv20 -> head2+300;
default -> head0;
};
length : int = case version of {
UNKNOWN_VERSION -> 0;
SSLv20 -> (((head0 & 0x7f) << 8) | head1) - 3;
default -> (head3 << 8) | head4;
};
};
type RecordText(rec: SSLRecord, is_orig: bool) = case $context.analyzer.state() of {
STATE_ABBREV_SERVER_ENCRYPTED, STATE_CLIENT_ENCRYPTED,
STATE_COMM_ENCRYPTED, STATE_CONN_ESTABLISHED
-> ciphertext : CiphertextRecord(rec, is_orig);
default
-> plaintext : PlaintextRecord(rec, is_orig);
};
type PlaintextRecord(rec: SSLRecord, is_orig: bool) = case rec.content_type of {
CHANGE_CIPHER_SPEC -> ch_cipher : ChangeCipherSpec(rec);
ALERT -> alert : Alert(rec);
HANDSHAKE -> handshake : Handshake(rec)[];
APPLICATION_DATA -> app_data : ApplicationData(rec);
V2_ERROR -> v2_error : V2Error(rec);
V2_CLIENT_HELLO -> v2_client_hello : V2ClientHello(rec);
V2_CLIENT_MASTER_KEY -> v2_client_master_key : V2ClientMasterKey(rec);
V2_SERVER_HELLO -> v2_server_hello : V2ServerHello(rec);
default -> unknown_record : UnknownRecord(rec);
};
type SSLExtension = record {
type: uint16;
data_len: uint16;
data: bytestring &length=data_len;
};
######################################################################
# state management according to Section 7.3. in spec
######################################################################
@ -99,6 +148,96 @@ enum AnalyzerState {
{
return string(is_orig ? "originator" :"responder");
}
double get_time_from_asn1(const ASN1_TIME * atime)
{
time_t lResult = 0;
char lBuffer[24];
char * pBuffer = lBuffer;
size_t lTimeLength = atime->length;
char * pString = (char *) atime->data;
if ( atime->type == V_ASN1_UTCTIME )
{
if ( lTimeLength < 11 || lTimeLength > 17 )
return 0;
memcpy(pBuffer, pString, 10);
pBuffer += 10;
pString += 10;
}
else
{
if ( lTimeLength < 13 )
return 0;
memcpy(pBuffer, pString, 12);
pBuffer += 12;
pString += 12;
}
if ((*pString == 'Z') || (*pString == '-') || (*pString == '+'))
{
*(pBuffer++) = '0';
*(pBuffer++) = '0';
}
else
{
*(pBuffer++) = *(pString++);
*(pBuffer++) = *(pString++);
// Skip any fractional seconds...
if (*pString == '.')
{
pString++;
while ((*pString >= '0') && (*pString <= '9'))
pString++;
}
}
*(pBuffer++) = 'Z';
*(pBuffer++) = '\0';
time_t lSecondsFromUTC;
if ( *pString == 'Z' )
lSecondsFromUTC = 0;
else
{
if ((*pString != '+') && (pString[5] != '-'))
return 0;
lSecondsFromUTC = ((pString[1]-'0') * 10 + (pString[2]-'0')) * 60;
lSecondsFromUTC += (pString[3]-'0') * 10 + (pString[4]-'0');
if (*pString == '-')
lSecondsFromUTC = -lSecondsFromUTC;
}
tm lTime;
lTime.tm_sec = ((lBuffer[10] - '0') * 10) + (lBuffer[11] - '0');
lTime.tm_min = ((lBuffer[8] - '0') * 10) + (lBuffer[9] - '0');
lTime.tm_hour = ((lBuffer[6] - '0') * 10) + (lBuffer[7] - '0');
lTime.tm_mday = ((lBuffer[4] - '0') * 10) + (lBuffer[5] - '0');
lTime.tm_mon = (((lBuffer[2] - '0') * 10) + (lBuffer[3] - '0')) - 1;
lTime.tm_year = ((lBuffer[0] - '0') * 10) + (lBuffer[1] - '0');
if (lTime.tm_year < 50)
lTime.tm_year += 100; // RFC 2459
lTime.tm_wday = 0;
lTime.tm_yday = 0;
lTime.tm_isdst = 0; // No DST adjustment requested
lResult = mktime(&lTime);
if ( lResult )
{
if ( 0 != lTime.tm_isdst )
lResult -= 3600; // mktime may adjust for DST (OS dependent)
lResult += lSecondsFromUTC;
}
else
lResult = 0;
return lResult;
}
%}
######################################################################
@ -115,7 +254,9 @@ enum HandshakeType {
SERVER_HELLO_DONE = 14,
CERTIFICATE_VERIFY = 15,
CLIENT_KEY_EXCHANGE = 16,
FINISHED = 20
FINISHED = 20,
CERTIFICATE_URL = 21, # RFC 3546
CERTIFICATE_STATUS = 22, # RFC 3546
};
%code{
@ -132,6 +273,8 @@ enum HandshakeType {
case CERTIFICATE_VERIFY: return string("CERTIFICATE_VERIFY");
case CLIENT_KEY_EXCHANGE: return string("CLIENT_KEY_EXCHANGE");
case FINISHED: return string("FINISHED");
case CERTIFICATE_URL: return string("CERTIFICATE_URL");
case CERTIFICATE_STATUS: return string("CERTIFICATE_STATUS");
default: return string(fmt("UNKNOWN (%d)", type));
}
}
@ -142,22 +285,24 @@ enum HandshakeType {
# V3 Change Cipher Spec Protocol (7.1.)
######################################################################
type ChangeCipherSpec = record {
type ChangeCipherSpec(rec: SSLRecord) = record {
type : uint8;
} &length = 1, &let {
state_changed : bool =
$context.analyzer.transition(STATE_CLIENT_FINISHED,
STATE_COMM_ENCRYPTED, false) ||
STATE_COMM_ENCRYPTED, rec.is_orig, false) ||
$context.analyzer.transition(STATE_IN_SERVER_HELLO,
STATE_ABBREV_SERVER_ENCRYPTED, false) ||
STATE_ABBREV_SERVER_ENCRYPTED, rec.is_orig, false) ||
$context.analyzer.transition(STATE_CLIENT_KEY_NO_CERT,
STATE_CLIENT_ENCRYPTED, true) ||
STATE_CLIENT_ENCRYPTED, rec.is_orig, true) ||
$context.analyzer.transition(STATE_CLIENT_CERT_VERIFIED,
STATE_CLIENT_ENCRYPTED, true) ||
STATE_CLIENT_ENCRYPTED, rec.is_orig, true) ||
#$context.analyzer.transition(STATE_CLIENT_CERT,
# STATE_CLIENT_ENCRYPTED, rec.is_orig, true) ||
$context.analyzer.transition(STATE_CLIENT_KEY_WITH_CERT,
STATE_CLIENT_ENCRYPTED, true) ||
STATE_CLIENT_ENCRYPTED, rec.is_orig, true) ||
$context.analyzer.transition(STATE_ABBREV_SERVER_FINISHED,
STATE_COMM_ENCRYPTED, true) ||
STATE_COMM_ENCRYPTED, rec.is_orig, true) ||
$context.analyzer.lost_track();
};
@ -166,19 +311,19 @@ type ChangeCipherSpec = record {
# V3 Alert Protocol (7.2.)
######################################################################
type Alert = record {
type Alert(rec: SSLRecord) = record {
level : uint8;
description: uint8;
} &length = 2;
};
######################################################################
# V2 Error Records (SSLv2 2.7.)
######################################################################
type V2Error = record {
type V2Error(rec: SSLRecord) = record {
error_code : uint16;
} &length = 2;
};
######################################################################
@ -187,9 +332,7 @@ type V2Error = record {
# Application data should always be encrypted, so we should not
# reach this point.
type ApplicationData = empty &let {
discard: bool = $context.flow.discard_data();
};
type ApplicationData(rec: SSLRecord) = empty;
######################################################################
# Handshake Protocol (7.4.)
@ -200,7 +343,7 @@ type ApplicationData = empty &let {
######################################################################
# Hello Request is empty
type HelloRequest = empty &let {
type HelloRequest(rec: SSLRecord) = empty &let {
hr: bool = $context.analyzer.set_hello_requested(true);
};
@ -209,7 +352,7 @@ type HelloRequest = empty &let {
# V3 Client Hello (7.4.1.2.)
######################################################################
type ClientHello = record {
type ClientHello(rec: SSLRecord) = record {
client_version : uint16;
gmt_unix_time : uint32;
random_bytes : bytestring &length = 28 &transient;
@ -219,12 +362,16 @@ type ClientHello = record {
csuits : uint16[csuit_len/2];
cmeth_len : uint8 &check(cmeth_len > 0);
cmeths : uint8[cmeth_len];
# This weirdness is to deal with the possible existence or absence
# of the following fields.
ext_len: uint16[] &until($element == 0 || $element != 0);
extensions : SSLExtension[] &until($input.length() == 0);
} &let {
state_changed : bool =
$context.analyzer.transition(STATE_INITIAL,
STATE_CLIENT_HELLO_RCVD, true) ||
STATE_CLIENT_HELLO_RCVD, rec.is_orig, true) ||
($context.analyzer.hello_requested() &&
$context.analyzer.transition(STATE_ANY, STATE_CLIENT_HELLO_RCVD, true)) ||
$context.analyzer.transition(STATE_ANY, STATE_CLIENT_HELLO_RCVD, rec.is_orig, true)) ||
$context.analyzer.lost_track();
};
@ -233,7 +380,7 @@ type ClientHello = record {
# V2 Client Hello (SSLv2 2.5.)
######################################################################
type V2ClientHello = record {
type V2ClientHello(rec: SSLRecord) = record {
client_version : uint16;
csuit_len : uint16;
session_len : uint16;
@ -244,9 +391,9 @@ type V2ClientHello = record {
} &length = 8 + csuit_len + session_len + chal_len, &let {
state_changed : bool =
$context.analyzer.transition(STATE_INITIAL,
STATE_CLIENT_HELLO_RCVD, true) ||
STATE_CLIENT_HELLO_RCVD, rec.is_orig, true) ||
($context.analyzer.hello_requested() &&
$context.analyzer.transition(STATE_ANY, STATE_CLIENT_HELLO_RCVD, true)) ||
$context.analyzer.transition(STATE_ANY, STATE_CLIENT_HELLO_RCVD, rec.is_orig, true)) ||
$context.analyzer.lost_track();
};
@ -255,18 +402,18 @@ type V2ClientHello = record {
# V3 Server Hello (7.4.1.3.)
######################################################################
type ServerHello = record {
type ServerHello(rec: SSLRecord) = record {
server_version : uint16;
gmt_unix_time : uint32;
random_bytes : bytestring &length = 28 &transient;
session_len : uint8;
session_id : uint8[session_len];
cipher_suite : uint16[1];
cipher_suite : uint16;
compression_method : uint8;
} &let {
state_changed : bool =
$context.analyzer.transition(STATE_CLIENT_HELLO_RCVD,
STATE_IN_SERVER_HELLO, false) ||
STATE_IN_SERVER_HELLO, rec.is_orig, false) ||
$context.analyzer.lost_track();
};
@ -275,7 +422,7 @@ type ServerHello = record {
# V2 Server Hello (SSLv2 2.6.)
######################################################################
type V2ServerHello = record {
type V2ServerHello(rec: SSLRecord) = record {
session_id_hit : uint8;
cert_type : uint8;
server_version : uint16;
@ -289,9 +436,9 @@ type V2ServerHello = record {
state_changed : bool =
(session_id_hit > 0 ?
$context.analyzer.transition(STATE_CLIENT_HELLO_RCVD,
STATE_CONN_ESTABLISHED, false) :
STATE_CONN_ESTABLISHED, rec.is_orig, false) :
$context.analyzer.transition(STATE_CLIENT_HELLO_RCVD,
STATE_V2_CL_MASTER_KEY_EXPECTED, false)) ||
STATE_V2_CL_MASTER_KEY_EXPECTED, rec.is_orig, false)) ||
$context.analyzer.lost_track();
};
@ -307,15 +454,15 @@ type X509Certificate = record {
type CertificateList = X509Certificate[] &until($input.length() == 0);
type Certificate = record {
type Certificate(rec: SSLRecord) = record {
length : uint24;
certificates : CertificateList &length = to_int()(length);
} &let {
state_changed : bool =
$context.analyzer.transition(STATE_IN_SERVER_HELLO,
STATE_IN_SERVER_HELLO, false) ||
STATE_IN_SERVER_HELLO, rec.is_orig, false) ||
$context.analyzer.transition(STATE_SERVER_HELLO_DONE,
STATE_CLIENT_CERT, true) ||
STATE_CLIENT_CERT, rec.is_orig, true) ||
$context.analyzer.lost_track();
};
@ -325,12 +472,12 @@ type Certificate = record {
######################################################################
# For now ignore details; just eat up complete message
type ServerKeyExchange = record {
cont : bytestring &restofdata &transient;
type ServerKeyExchange(rec: SSLRecord) = record {
key : bytestring &restofdata;
} &let {
state_changed : bool =
$context.analyzer.transition(STATE_IN_SERVER_HELLO,
STATE_IN_SERVER_HELLO, false) ||
STATE_IN_SERVER_HELLO, rec.is_orig, false) ||
$context.analyzer.lost_track();
};
@ -340,12 +487,12 @@ type ServerKeyExchange = record {
######################################################################
# For now, ignore Certificate Request Details; just eat up message.
type CertificateRequest = record {
type CertificateRequest(rec: SSLRecord) = record {
cont : bytestring &restofdata &transient;
} &let {
state_changed : bool =
$context.analyzer.transition(STATE_IN_SERVER_HELLO,
STATE_IN_SERVER_HELLO, false) ||
STATE_IN_SERVER_HELLO, rec.is_orig, false) ||
$context.analyzer.lost_track();
};
@ -355,10 +502,10 @@ type CertificateRequest = record {
######################################################################
# Server Hello Done is empty
type ServerHelloDone = empty &let {
type ServerHelloDone(rec: SSLRecord) = empty &let {
state_changed : bool =
$context.analyzer.transition(STATE_IN_SERVER_HELLO,
STATE_SERVER_HELLO_DONE, false) ||
STATE_SERVER_HELLO_DONE, rec.is_orig, false) ||
$context.analyzer.lost_track();
};
@ -377,14 +524,16 @@ type ServerHelloDone = empty &let {
# For now ignore details of ClientKeyExchange (most of it is
# encrypted anyway); just eat up message.
type ClientKeyExchange = record {
type ClientKeyExchange(rec: SSLRecord) = record {
cont : bytestring &restofdata &transient;
} &let {
state_changed : bool =
$context.analyzer.transition(STATE_SERVER_HELLO_DONE,
STATE_CLIENT_KEY_NO_CERT, true) ||
STATE_CLIENT_KEY_NO_CERT, rec.is_orig, true) ||
$context.analyzer.transition(STATE_CLIENT_CERT,
STATE_CLIENT_KEY_WITH_CERT, true) ||
STATE_CLIENT_KEY_WITH_CERT, rec.is_orig, true) ||
$context.analyzer.transition(STATE_CLIENT_CERT,
STATE_CLIENT_KEY_WITH_CERT, rec.is_orig, true) ||
$context.analyzer.lost_track();
};
@ -392,7 +541,7 @@ type ClientKeyExchange = record {
# V2 Client Master Key (SSLv2 2.5.)
######################################################################
type V2ClientMasterKey = record {
type V2ClientMasterKey(rec: SSLRecord) = record {
cipher_kind : uint24;
cl_key_len : uint16;
en_key_len : uint16;
@ -403,7 +552,7 @@ type V2ClientMasterKey = record {
} &length = 9 + cl_key_len + en_key_len + key_arg_len, &let {
state_changed : bool =
$context.analyzer.transition(STATE_V2_CL_MASTER_KEY_EXPECTED,
STATE_CONN_ESTABLISHED, true) ||
STATE_CONN_ESTABLISHED, rec.is_orig, true) ||
$context.analyzer.lost_track();
};
@ -413,12 +562,12 @@ type V2ClientMasterKey = record {
######################################################################
# For now, ignore Certificate Verify; just eat up the message.
type CertificateVerify = record {
type CertificateVerify(rec: SSLRecord) = record {
cont : bytestring &restofdata &transient;
} &let {
state_changed : bool =
$context.analyzer.transition(STATE_CLIENT_KEY_WITH_CERT,
STATE_CLIENT_CERT_VERIFIED, true) ||
STATE_CLIENT_CERT_VERIFIED, rec.is_orig, true) ||
$context.analyzer.lost_track();
};
@ -435,27 +584,32 @@ type CertificateVerify = record {
# V3 Handshake Protocol (7.)
######################################################################
type UnknownHandshake(msg_type : uint8) = record {
type UnknownHandshake(hs: Handshake, is_orig: bool) = record {
cont : bytestring &restofdata &transient;
} &let {
state_changed : bool = $context.analyzer.lost_track();
# TODO: an unknown handshake could just be an encrypted handshake
# before a server sends the change cipher spec message.
# I have no clue why this happens, but it does seem to happen.
# This should be solved in a different way eventually.
#state_changed : bool = $context.analyzer.lost_track();
};
type Handshake = record {
type Handshake(rec: SSLRecord) = record {
msg_type : uint8;
length : uint24;
body : case msg_type of {
HELLO_REQUEST -> hello_request : HelloRequest;
CLIENT_HELLO -> client_hello : ClientHello;
SERVER_HELLO -> server_hello : ServerHello;
CERTIFICATE -> certificate : Certificate;
SERVER_KEY_EXCHANGE -> server_key_exchange : ServerKeyExchange;
CERTIFICATE_REQUEST -> certificate_request : CertificateRequest;
SERVER_HELLO_DONE -> server_hello_done : ServerHelloDone;
CERTIFICATE_VERIFY -> certificate_verify : CertificateVerify;
CLIENT_KEY_EXCHANGE -> client_key_exchange : ClientKeyExchange;
default -> unknown_handshake : UnknownHandshake(msg_type);
HELLO_REQUEST -> hello_request : HelloRequest(rec);
CLIENT_HELLO -> client_hello : ClientHello(rec);
SERVER_HELLO -> server_hello : ServerHello(rec);
CERTIFICATE -> certificate : Certificate(rec);
SERVER_KEY_EXCHANGE -> server_key_exchange : ServerKeyExchange(rec);
CERTIFICATE_REQUEST -> certificate_request : CertificateRequest(rec);
SERVER_HELLO_DONE -> server_hello_done : ServerHelloDone(rec);
CERTIFICATE_VERIFY -> certificate_verify : CertificateVerify(rec);
CLIENT_KEY_EXCHANGE -> client_key_exchange : ClientKeyExchange(rec);
default -> unknown_handshake : UnknownHandshake(this, rec.is_orig);
};
} &length = 4 + to_int()(length);
@ -464,40 +618,26 @@ type Handshake = record {
# Fragmentation (6.2.1.)
######################################################################
type UnknownRecord = record {
cont : empty;
type UnknownRecord(rec: SSLRecord) = record {
cont : bytestring &restofdata &transient;
} &let {
discard : bool = $context.flow.discard_data();
state_changed : bool = $context.analyzer.lost_track();
};
type PlaintextRecord = case $context.analyzer.current_record_type() of {
CHANGE_CIPHER_SPEC -> ch_cipher : ChangeCipherSpec;
ALERT -> alert : Alert;
HANDSHAKE -> handshakes : Handshake;
APPLICATION_DATA -> app_data : ApplicationData;
V2_ERROR -> v2_error : V2Error;
V2_CLIENT_HELLO -> v2_client_hello : V2ClientHello;
V2_CLIENT_MASTER_KEY -> v2_client_master_key : V2ClientMasterKey;
V2_SERVER_HELLO -> v2_server_hello : V2ServerHello;
UNKNOWN_OR_V2_ENCRYPTED -> unknown_record : UnknownRecord;
};
type CiphertextRecord = empty &let {
discard : bool = $context.flow.discard_data();
type CiphertextRecord(rec: SSLRecord, is_orig: bool) = empty &let {
state_changed : bool =
$context.analyzer.transition(STATE_ABBREV_SERVER_ENCRYPTED,
STATE_ABBREV_SERVER_FINISHED, false) ||
STATE_ABBREV_SERVER_FINISHED, rec.is_orig, false) ||
$context.analyzer.transition(STATE_CLIENT_ENCRYPTED,
STATE_CLIENT_FINISHED, true) ||
STATE_CLIENT_FINISHED, rec.is_orig, true) ||
$context.analyzer.transition(STATE_COMM_ENCRYPTED,
STATE_CONN_ESTABLISHED, false) ||
STATE_CONN_ESTABLISHED, rec.is_orig, false) ||
$context.analyzer.transition(STATE_COMM_ENCRYPTED,
STATE_CONN_ESTABLISHED, true) ||
STATE_CONN_ESTABLISHED, rec.is_orig, true) ||
$context.analyzer.transition(STATE_CONN_ESTABLISHED,
STATE_CONN_ESTABLISHED, false) ||
STATE_CONN_ESTABLISHED, rec.is_orig, false) ||
$context.analyzer.transition(STATE_CONN_ESTABLISHED,
STATE_CONN_ESTABLISHED, true) ||
STATE_CONN_ESTABLISHED, rec.is_orig, true) ||
$context.analyzer.lost_track();
};
@ -506,15 +646,9 @@ type CiphertextRecord = empty &let {
# initial datatype for binpac
######################################################################
type SSLPDU = case $context.analyzer.state() of {
STATE_ABBREV_SERVER_ENCRYPTED, STATE_CLIENT_ENCRYPTED,
STATE_COMM_ENCRYPTED, STATE_CONN_ESTABLISHED
-> ciphertext : CiphertextRecord;
default
-> plaintext : PlaintextRecord;
} &byteorder = bigendian, &let {
consumed : bool = $context.flow.consume_data();
};
type SSLPDU(is_orig: bool) = record {
records : SSLRecord(is_orig)[] &until($element == 0);
} &byteorder = bigendian;
######################################################################
@ -526,60 +660,48 @@ analyzer SSLAnalyzer {
downflow = SSLFlow(false);
%member{
int current_record_type_;
int current_record_version_;
int current_record_length_;
bool current_record_is_orig_;
int state_;
int old_state_;
bool hello_requested_;
%}
%init{
current_record_type_ = -1;
current_record_version_ = -1;
current_record_length_ = -1;
current_record_is_orig_ = false;
state_ = STATE_INITIAL;
old_state_ = STATE_INITIAL;
hello_requested_ = false;
%}
function current_record_type() : int
%{ return current_record_type_; %}
function current_record_version() : int
%{ return current_record_version_; %}
function current_record_length() : int
%{ return current_record_length_; %}
function current_record_is_orig() : bool
%{ return current_record_is_orig_; %}
function next_record(rec : const_bytestring, type : int,
version : int, is_orig : bool) : bool
function determine_ssl_version(head0 : uint8, head1 : uint8,
head2 : uint8) : int
%{
current_record_type_ = type;
current_record_version_ = version;
current_record_length_ = rec.length();
current_record_is_orig_ = is_orig;
if ( head0 >= 20 && head0 <= 23 &&
head1 == 0x03 && head2 < 0x03 )
// This is most probably SSL version 3.
return (head1 << 8) | head2;
NewData(is_orig, rec.begin(), rec.end());
else if ( head0 >= 128 && head2 < 5 && head2 != 3 )
// Not very strong evidence, but we suspect
// this to be SSLv2.
return SSLv20;
return true;
else
return UNKNOWN_VERSION;
%}
function state() : int %{ return state_; %}
function old_state() : int %{ return old_state_; %}
function transition(olds : AnalyzerState, news : AnalyzerState,
is_orig : bool) : bool
current_record_is_orig : bool, is_orig : bool) : bool
%{
if ( (olds != STATE_ANY && olds != state_) ||
current_record_is_orig_ != is_orig )
current_record_is_orig != is_orig )
return false;
old_state_ = state_;
state_ = news;
//printf("transitioning from %s to %s\n", state_label(old_state()).c_str(), state_label(state()).c_str());
return true;
%}
@ -603,28 +725,3 @@ analyzer SSLAnalyzer {
%}
};
######################################################################
# binpac flow for SSL
######################################################################
flow SSLFlow(is_orig : bool) {
flowunit = SSLPDU withcontext(connection, this);
function discard_data() : bool
%{
flow_buffer_->DiscardData();
return true;
%}
function data_available() : bool
%{
return flow_buffer_->data_available();
%}
function consume_data() : bool
%{
flow_buffer_->NewFrame(0, false);
return true;
%}
};

View file

@ -23,37 +23,6 @@ using binpac::SSL::SSLAnalyzer;
extern type const_bytestring;
type SSLPDU = record {
head0 : uint8;
head1 : uint8;
head2 : uint8;
head3 : uint8;
head4 : uint8;
fragment : bytestring &restofdata;
} &length = 5 + length, &byteorder = bigendian, &let {
version : int =
$context.analyzer.determine_ssl_version(head0, head1, head2);
length : int = case version of {
UNKNOWN_VERSION -> 0;
SSLv20 -> (((head0 & 0x7f) << 8) | head1) - 3;
default -> (head3 << 8) | head4;
};
fw : bool = case version of {
UNKNOWN_VERSION ->
$context.analyzer.forward_record(const_bytestring(),
UNKNOWN_OR_V2_ENCRYPTED, UNKNOWN_VERSION,
$context.flow.is_orig)
&& $context.flow.discard_data();
SSLv20 -> $context.analyzer.forward_v2_record(head2, head3, head4,
fragment, $context.flow.is_orig);
default -> $context.analyzer.forward_record(fragment, head0,
(head1 << 8) | head2, $context.flow.is_orig);
};
};
# binpac-specific definitions
analyzer SSLRecordLayerAnalyzer {
@ -62,15 +31,10 @@ analyzer SSLRecordLayerAnalyzer {
%member{
SSLAnalyzer* ssl_analyzer_;
int ssl_version_;
int record_length_;
%}
%init{
ssl_analyzer_ = 0;
ssl_version_ = UNKNOWN_VERSION;
record_length_ = 0;
%}
%eof{
@ -81,27 +45,6 @@ analyzer SSLRecordLayerAnalyzer {
function set_ssl_analyzer(a : SSLAnalyzer) : void
%{ ssl_analyzer_ = a; %}
function ssl_version() : int %{ return ssl_version_; %}
function record_length() : int %{ return record_length_; %}
function determine_ssl_version(head0 : uint8, head1 : uint8,
head2 : uint8) : int
%{
if ( head0 >= 20 && head0 <= 23 &&
head1 == 0x03 && head2 < 0x03 )
// This is most probably SSL version 3.
ssl_version_ = (head1 << 8) | head2;
else if ( head0 >= 128 && head2 < 5 && head2 != 3 )
// Not very strong evidence, but we suspect
// this to be SSLv2.
ssl_version_ = SSLv20;
else
ssl_version_ = UNKNOWN_VERSION;
return ssl_version_;
%}
function forward_record(fragment : const_bytestring, type : int,
version : uint16, is_orig : bool) : bool

View file

@ -15,8 +15,10 @@ analyzer SSL withcontext {
flow : SSLFlow;
};
%include ssl-defs.pac
%include ssl-protocol.pac
%include ssl-analyzer.pac
%include ssl-defs.pac
flow SSLFlow(is_orig : bool) {
flowunit = SSLPDU(is_orig) withcontext(connection, this);
};