zeek/src/ssl-protocol.pac

630 lines
18 KiB
JavaScript

# $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.
######################################################################
# General definitions
######################################################################
type uint24 = record {
byte1 : uint8;
byte2 : uint8;
byte3 : uint8;
};
%header{
class to_int {
public:
int operator()(uint24 * num) const
{
return (num->byte1() << 16) | (num->byte2() << 8) | num->byte3();
}
};
%}
extern type to_int;
######################################################################
# state management according to Section 7.3. in spec
######################################################################
enum AnalyzerState {
STATE_INITIAL,
STATE_CLIENT_HELLO_RCVD,
STATE_IN_SERVER_HELLO,
STATE_SERVER_HELLO_DONE,
STATE_CLIENT_CERT,
STATE_CLIENT_KEY_WITH_CERT,
STATE_CLIENT_KEY_NO_CERT,
STATE_CLIENT_CERT_VERIFIED,
STATE_CLIENT_ENCRYPTED,
STATE_CLIENT_FINISHED,
STATE_ABBREV_SERVER_ENCRYPTED,
STATE_ABBREV_SERVER_FINISHED,
STATE_COMM_ENCRYPTED,
STATE_CONN_ESTABLISHED,
STATE_V2_CL_MASTER_KEY_EXPECTED,
STATE_TRACK_LOST,
STATE_ANY
};
%code{
string state_label(int state_nr)
{
switch ( state_nr ) {
case STATE_INITIAL:
return string("INITIAL");
case STATE_CLIENT_HELLO_RCVD:
return string("CLIENT_HELLO_RCVD");
case STATE_IN_SERVER_HELLO:
return string("IN_SERVER_HELLO");
case STATE_SERVER_HELLO_DONE:
return string("SERVER_HELLO_DONE");
case STATE_CLIENT_CERT:
return string("CLIENT_CERT");
case STATE_CLIENT_KEY_WITH_CERT:
return string("CLIENT_KEY_WITH_CERT");
case STATE_CLIENT_KEY_NO_CERT:
return string("CLIENT_KEY_NO_CERT");
case STATE_CLIENT_CERT_VERIFIED:
return string("CLIENT_CERT_VERIFIED");
case STATE_CLIENT_ENCRYPTED:
return string("CLIENT_ENCRYPTED");
case STATE_CLIENT_FINISHED:
return string("CLIENT_FINISHED");
case STATE_ABBREV_SERVER_ENCRYPTED:
return string("ABBREV_SERVER_ENCRYPTED");
case STATE_ABBREV_SERVER_FINISHED:
return string("ABBREV_SERVER_FINISHED");
case STATE_COMM_ENCRYPTED:
return string("COMM_ENCRYPTED");
case STATE_CONN_ESTABLISHED:
return string("CONN_ESTABLISHED");
case STATE_V2_CL_MASTER_KEY_EXPECTED:
return string("STATE_V2_CL_MASTER_KEY_EXPECTED");
case STATE_TRACK_LOST:
return string("TRACK_LOST");
case STATE_ANY:
return string("ANY");
default:
return string(fmt("UNKNOWN (%d)", state_nr));
}
}
string orig_label(bool is_orig)
{
return string(is_orig ? "originator" :"responder");
}
%}
######################################################################
# SSLv3 Handshake Protocols (7.)
######################################################################
enum HandshakeType {
HELLO_REQUEST = 0,
CLIENT_HELLO = 1,
SERVER_HELLO = 2,
CERTIFICATE = 11,
SERVER_KEY_EXCHANGE = 12,
CERTIFICATE_REQUEST = 13,
SERVER_HELLO_DONE = 14,
CERTIFICATE_VERIFY = 15,
CLIENT_KEY_EXCHANGE = 16,
FINISHED = 20
};
%code{
string handshake_type_label(int type)
{
switch ( type ) {
case HELLO_REQUEST: return string("HELLO_REQUEST");
case CLIENT_HELLO: return string("CLIENT_HELLO");
case SERVER_HELLO: return string("SERVER_HELLO");
case CERTIFICATE: return string("CERTIFICATE");
case SERVER_KEY_EXCHANGE: return string("SERVER_KEY_EXCHANGE");
case CERTIFICATE_REQUEST: return string("CERTIFICATE_REQUEST");
case SERVER_HELLO_DONE: return string("SERVER_HELLO_DONE");
case CERTIFICATE_VERIFY: return string("CERTIFICATE_VERIFY");
case CLIENT_KEY_EXCHANGE: return string("CLIENT_KEY_EXCHANGE");
case FINISHED: return string("FINISHED");
default: return string(fmt("UNKNOWN (%d)", type));
}
}
%}
######################################################################
# V3 Change Cipher Spec Protocol (7.1.)
######################################################################
type ChangeCipherSpec = record {
type : uint8;
} &length = 1, &let {
state_changed : bool =
$context.analyzer.transition(STATE_CLIENT_FINISHED,
STATE_COMM_ENCRYPTED, false) ||
$context.analyzer.transition(STATE_IN_SERVER_HELLO,
STATE_ABBREV_SERVER_ENCRYPTED, false) ||
$context.analyzer.transition(STATE_CLIENT_KEY_NO_CERT,
STATE_CLIENT_ENCRYPTED, true) ||
$context.analyzer.transition(STATE_CLIENT_CERT_VERIFIED,
STATE_CLIENT_ENCRYPTED, true) ||
$context.analyzer.transition(STATE_CLIENT_KEY_WITH_CERT,
STATE_CLIENT_ENCRYPTED, true) ||
$context.analyzer.transition(STATE_ABBREV_SERVER_FINISHED,
STATE_COMM_ENCRYPTED, true) ||
$context.analyzer.lost_track();
};
######################################################################
# V3 Alert Protocol (7.2.)
######################################################################
type Alert = record {
level : uint8;
description: uint8;
} &length = 2;
######################################################################
# V2 Error Records (SSLv2 2.7.)
######################################################################
type V2Error = record {
error_code : uint16;
} &length = 2;
######################################################################
# V3 Application Data
######################################################################
# Application data should always be encrypted, so we should not
# reach this point.
type ApplicationData = empty &let {
discard: bool = $context.flow.discard_data();
};
######################################################################
# Handshake Protocol (7.4.)
######################################################################
######################################################################
# V3 Hello Request (7.4.1.1.)
######################################################################
# Hello Request is empty
type HelloRequest = empty &let {
hr: bool = $context.analyzer.set_hello_requested(true);
};
######################################################################
# V3 Client Hello (7.4.1.2.)
######################################################################
type ClientHello = record {
client_version : uint16;
gmt_unix_time : uint32;
random_bytes : bytestring &length = 28 &transient;
session_len : uint8;
session_id : uint8[session_len];
csuit_len : uint16 &check(csuit_len > 1 && csuit_len % 2 == 0);
csuits : uint16[csuit_len/2];
cmeth_len : uint8 &check(cmeth_len > 0);
cmeths : uint8[cmeth_len];
} &let {
state_changed : bool =
$context.analyzer.transition(STATE_INITIAL,
STATE_CLIENT_HELLO_RCVD, true) ||
($context.analyzer.hello_requested() &&
$context.analyzer.transition(STATE_ANY, STATE_CLIENT_HELLO_RCVD, true)) ||
$context.analyzer.lost_track();
};
######################################################################
# V2 Client Hello (SSLv2 2.5.)
######################################################################
type V2ClientHello = record {
client_version : uint16;
csuit_len : uint16;
session_len : uint16;
chal_len : uint16;
ciphers : uint24[csuit_len/3];
session_id : uint8[session_len];
challenge : bytestring &length = chal_len;
} &length = 8 + csuit_len + session_len + chal_len, &let {
state_changed : bool =
$context.analyzer.transition(STATE_INITIAL,
STATE_CLIENT_HELLO_RCVD, true) ||
($context.analyzer.hello_requested() &&
$context.analyzer.transition(STATE_ANY, STATE_CLIENT_HELLO_RCVD, true)) ||
$context.analyzer.lost_track();
};
######################################################################
# V3 Server Hello (7.4.1.3.)
######################################################################
type ServerHello = 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];
compression_method : uint8;
} &let {
state_changed : bool =
$context.analyzer.transition(STATE_CLIENT_HELLO_RCVD,
STATE_IN_SERVER_HELLO, false) ||
$context.analyzer.lost_track();
};
######################################################################
# V2 Server Hello (SSLv2 2.6.)
######################################################################
type V2ServerHello = record {
session_id_hit : uint8;
cert_type : uint8;
server_version : uint16;
cert_len : uint16;
ciph_len : uint16;
conn_id_len : uint16;
cert_data : bytestring &length = cert_len;
ciphers : uint24[ciph_len/3];
conn_id_data : bytestring &length = conn_id_len;
} &length = 10 + cert_len + ciph_len + conn_id_len, &let {
state_changed : bool =
(session_id_hit > 0 ?
$context.analyzer.transition(STATE_CLIENT_HELLO_RCVD,
STATE_CONN_ESTABLISHED, false) :
$context.analyzer.transition(STATE_CLIENT_HELLO_RCVD,
STATE_V2_CL_MASTER_KEY_EXPECTED, false)) ||
$context.analyzer.lost_track();
};
######################################################################
# V3 Server Certificate (7.4.2.)
######################################################################
type X509Certificate = record {
length : uint24;
certificate : bytestring &length = to_int()(length);
};
type CertificateList = X509Certificate[] &until($input.length() == 0);
type Certificate = 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) ||
$context.analyzer.transition(STATE_SERVER_HELLO_DONE,
STATE_CLIENT_CERT, true) ||
$context.analyzer.lost_track();
};
######################################################################
# V3 Server Key Exchange Message (7.4.3.)
######################################################################
# For now ignore details; just eat up complete message
type ServerKeyExchange = record {
cont : bytestring &restofdata &transient;
} &let {
state_changed : bool =
$context.analyzer.transition(STATE_IN_SERVER_HELLO,
STATE_IN_SERVER_HELLO, false) ||
$context.analyzer.lost_track();
};
######################################################################
# V3 Certificate Request (7.4.4.)
######################################################################
# For now, ignore Certificate Request Details; just eat up message.
type CertificateRequest = record {
cont : bytestring &restofdata &transient;
} &let {
state_changed : bool =
$context.analyzer.transition(STATE_IN_SERVER_HELLO,
STATE_IN_SERVER_HELLO, false) ||
$context.analyzer.lost_track();
};
######################################################################
# V3 Server Hello Done (7.4.5.)
######################################################################
# Server Hello Done is empty
type ServerHelloDone = empty &let {
state_changed : bool =
$context.analyzer.transition(STATE_IN_SERVER_HELLO,
STATE_SERVER_HELLO_DONE, false) ||
$context.analyzer.lost_track();
};
######################################################################
# V3 Client Certificate (7.4.6.)
######################################################################
# Client Certificate is identical to Server Certificate;
# no further definition here
######################################################################
# V3 Client Key Exchange Message (7.4.7.)
######################################################################
# For now ignore details of ClientKeyExchange (most of it is
# encrypted anyway); just eat up message.
type ClientKeyExchange = record {
cont : bytestring &restofdata &transient;
} &let {
state_changed : bool =
$context.analyzer.transition(STATE_SERVER_HELLO_DONE,
STATE_CLIENT_KEY_NO_CERT, true) ||
$context.analyzer.transition(STATE_CLIENT_CERT,
STATE_CLIENT_KEY_WITH_CERT, true) ||
$context.analyzer.lost_track();
};
######################################################################
# V2 Client Master Key (SSLv2 2.5.)
######################################################################
type V2ClientMasterKey = record {
cipher_kind : uint24;
cl_key_len : uint16;
en_key_len : uint16;
key_arg_len : uint16;
cl_key_data : bytestring &length = cl_key_len &transient;
en_key_data : bytestring &length = en_key_len &transient;
key_arg_data : bytestring &length = key_arg_len &transient;
} &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) ||
$context.analyzer.lost_track();
};
######################################################################
# V3 Certificate Verify (7.4.8.)
######################################################################
# For now, ignore Certificate Verify; just eat up the message.
type CertificateVerify = record {
cont : bytestring &restofdata &transient;
} &let {
state_changed : bool =
$context.analyzer.transition(STATE_CLIENT_KEY_WITH_CERT,
STATE_CLIENT_CERT_VERIFIED, true) ||
$context.analyzer.lost_track();
};
######################################################################
# V3 Finished (7.4.9.)
######################################################################
# The Finished messages are always sent after encryption is in effect,
# so we will not be able to read those message
######################################################################
# V3 Handshake Protocol (7.)
######################################################################
type UnknownHandshake(msg_type : uint8) = record {
cont : bytestring &restofdata &transient;
} &let {
state_changed : bool = $context.analyzer.lost_track();
};
type Handshake = 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);
};
} &length = 4 + to_int()(length);
######################################################################
# Fragmentation (6.2.1.)
######################################################################
type UnknownRecord = record {
cont : empty;
} &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();
state_changed : bool =
$context.analyzer.transition(STATE_ABBREV_SERVER_ENCRYPTED,
STATE_ABBREV_SERVER_FINISHED, false) ||
$context.analyzer.transition(STATE_CLIENT_ENCRYPTED,
STATE_CLIENT_FINISHED, true) ||
$context.analyzer.transition(STATE_COMM_ENCRYPTED,
STATE_CONN_ESTABLISHED, false) ||
$context.analyzer.transition(STATE_COMM_ENCRYPTED,
STATE_CONN_ESTABLISHED, true) ||
$context.analyzer.transition(STATE_CONN_ESTABLISHED,
STATE_CONN_ESTABLISHED, false) ||
$context.analyzer.transition(STATE_CONN_ESTABLISHED,
STATE_CONN_ESTABLISHED, true) ||
$context.analyzer.lost_track();
};
######################################################################
# 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();
};
######################################################################
# binpac analyzer for SSL including
######################################################################
analyzer SSLAnalyzer {
upflow = SSLFlow(true);
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
%{
current_record_type_ = type;
current_record_version_ = version;
current_record_length_ = rec.length();
current_record_is_orig_ = is_orig;
NewData(is_orig, rec.begin(), rec.end());
return true;
%}
function state() : int %{ return state_; %}
function old_state() : int %{ return old_state_; %}
function transition(olds : AnalyzerState, news : AnalyzerState,
is_orig : bool) : bool
%{
if ( (olds != STATE_ANY && olds != state_) ||
current_record_is_orig_ != is_orig )
return false;
old_state_ = state_;
state_ = news;
return true;
%}
function lost_track() : bool
%{
state_ = STATE_TRACK_LOST;
return false;
%}
function hello_requested() : bool
%{
bool ret = hello_requested_;
hello_requested_ = false;
return ret;
%}
function set_hello_requested(val : bool) : bool
%{
hello_requested_ = val;
return val;
%}
};
######################################################################
# 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;
%}
};