zeek/src/ssl-protocol.pac
Seth Hall 0a6104fe66 More bugfixs, cleanup, and test for SSL analyzer
- SSL related files and classes renamed to remove the "binpac" term.

- A small fix for DPD scripts to make the DPD log more helpful if
  there are multiple continued failures.  Also, fixed the SSL
  analyzer to make it stop doing repeated violation messages for
  some handshake failures.

- Added a $issuer_subject to the SSL log.

- Created a basic test for SSL.
2012-05-03 10:52:24 -04:00

744 lines
23 KiB
JavaScript

# 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();
}
};
string state_label(int state_nr);
double get_time_from_asn1(const ASN1_TIME * atime);
%}
extern type to_int;
type SSLRecord(is_orig: bool) = record {
head0 : uint8;
head1 : uint8;
head2 : uint8;
head3 : uint8;
head4 : uint8;
rec : RecordText(this)[] &length=length, &requires(content_type);
} &length = length+5, &byteorder=bigendian,
&let {
version : int =
$context.connection.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) = case $context.connection.state() of {
STATE_ABBREV_SERVER_ENCRYPTED, STATE_CLIENT_ENCRYPTED,
STATE_COMM_ENCRYPTED, STATE_CONN_ESTABLISHED
-> ciphertext : CiphertextRecord(rec);
default
-> plaintext : PlaintextRecord(rec);
};
type PlaintextRecord(rec: SSLRecord) = 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(rec: SSLRecord) = record {
type: uint16;
data_len: uint16;
data: bytestring &length=data_len;
};
######################################################################
# 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));
}
}
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;
}
%}
######################################################################
# SSLv3 Handshake Protocols (7.)
######################################################################
enum HandshakeType {
HELLO_REQUEST = 0,
CLIENT_HELLO = 1,
SERVER_HELLO = 2,
SESSION_TICKET = 4, # RFC 5077
CERTIFICATE = 11,
SERVER_KEY_EXCHANGE = 12,
CERTIFICATE_REQUEST = 13,
SERVER_HELLO_DONE = 14,
CERTIFICATE_VERIFY = 15,
CLIENT_KEY_EXCHANGE = 16,
FINISHED = 20,
CERTIFICATE_URL = 21, # RFC 3546
CERTIFICATE_STATUS = 22, # RFC 3546
};
######################################################################
# V3 Change Cipher Spec Protocol (7.1.)
######################################################################
type ChangeCipherSpec(rec: SSLRecord) = record {
type : uint8;
} &length = 1, &let {
state_changed : bool =
$context.connection.transition(STATE_CLIENT_FINISHED,
STATE_COMM_ENCRYPTED, rec.is_orig, false) ||
$context.connection.transition(STATE_IN_SERVER_HELLO,
STATE_ABBREV_SERVER_ENCRYPTED, rec.is_orig, false) ||
$context.connection.transition(STATE_CLIENT_KEY_NO_CERT,
STATE_CLIENT_ENCRYPTED, rec.is_orig, true) ||
$context.connection.transition(STATE_CLIENT_CERT_VERIFIED,
STATE_CLIENT_ENCRYPTED, rec.is_orig, true) ||
$context.connection.transition(STATE_CLIENT_CERT,
STATE_CLIENT_ENCRYPTED, rec.is_orig, true) ||
$context.connection.transition(STATE_CLIENT_KEY_WITH_CERT,
STATE_CLIENT_ENCRYPTED, rec.is_orig, true) ||
$context.connection.transition(STATE_ABBREV_SERVER_FINISHED,
STATE_COMM_ENCRYPTED, rec.is_orig, true) ||
$context.connection.lost_track();
};
######################################################################
# V3 Alert Protocol (7.2.)
######################################################################
type Alert(rec: SSLRecord) = record {
level : uint8;
description: uint8;
};
######################################################################
# V2 Error Records (SSLv2 2.7.)
######################################################################
type V2Error(rec: SSLRecord) = record {
data: bytestring &restofdata &transient;
} &let {
error_code : uint16 = ((rec.head3 << 8) | rec.head4);
};
######################################################################
# V3 Application Data
######################################################################
# Application data should always be encrypted, so we should not
# reach this point.
type ApplicationData(rec: SSLRecord) = record {
data : bytestring &restofdata &transient;
};
######################################################################
# Handshake Protocol (7.4.)
######################################################################
######################################################################
# V3 Hello Request (7.4.1.1.)
######################################################################
# Hello Request is empty
type HelloRequest(rec: SSLRecord) = empty &let {
hr: bool = $context.connection.set_hello_requested(true);
};
######################################################################
# V3 Client Hello (7.4.1.2.)
######################################################################
type ClientHello(rec: SSLRecord) = 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];
# 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(rec)[] &until($input.length() == 0);
} &let {
state_changed : bool =
$context.connection.transition(STATE_INITIAL,
STATE_CLIENT_HELLO_RCVD, rec.is_orig, true) ||
($context.connection.hello_requested() &&
$context.connection.transition(STATE_ANY, STATE_CLIENT_HELLO_RCVD, rec.is_orig, true)) ||
$context.connection.lost_track();
};
######################################################################
# V2 Client Hello (SSLv2 2.5.)
######################################################################
type V2ClientHello(rec: SSLRecord) = record {
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 = 6 + csuit_len + session_len + chal_len, &let {
state_changed : bool =
$context.connection.transition(STATE_INITIAL,
STATE_CLIENT_HELLO_RCVD, rec.is_orig, true) ||
($context.connection.hello_requested() &&
$context.connection.transition(STATE_ANY, STATE_CLIENT_HELLO_RCVD, rec.is_orig, true)) ||
$context.connection.lost_track();
client_version : int = rec.version;
};
######################################################################
# V3 Server Hello (7.4.1.3.)
######################################################################
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];
compression_method : uint8;
# 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(rec)[] &until($input.length() == 0);
} &let {
state_changed : bool =
$context.connection.transition(STATE_CLIENT_HELLO_RCVD,
STATE_IN_SERVER_HELLO, rec.is_orig, false) ||
$context.connection.lost_track();
};
######################################################################
# V2 Server Hello (SSLv2 2.6.)
######################################################################
type V2ServerHello(rec: SSLRecord) = 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;
} &let {
state_changed : bool =
(session_id_hit > 0 ?
$context.connection.transition(STATE_CLIENT_HELLO_RCVD,
STATE_CONN_ESTABLISHED, rec.is_orig, false) :
$context.connection.transition(STATE_CLIENT_HELLO_RCVD,
STATE_V2_CL_MASTER_KEY_EXPECTED, rec.is_orig, false)) ||
$context.connection.lost_track();
session_id_hit : uint8 = rec.head3;
cert_type : uint8 = rec.head4;
};
######################################################################
# 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(rec: SSLRecord) = record {
length : uint24;
certificates : CertificateList &length = to_int()(length);
} &let {
state_changed : bool =
$context.connection.transition(STATE_IN_SERVER_HELLO,
STATE_IN_SERVER_HELLO, rec.is_orig, false) ||
$context.connection.transition(STATE_SERVER_HELLO_DONE,
STATE_CLIENT_CERT, rec.is_orig, true) ||
$context.connection.lost_track();
};
######################################################################
# V3 Server Key Exchange Message (7.4.3.)
######################################################################
# For now ignore details; just eat up complete message
type ServerKeyExchange(rec: SSLRecord) = record {
key : bytestring &restofdata &transient;
} &let {
state_changed : bool =
$context.connection.transition(STATE_IN_SERVER_HELLO,
STATE_IN_SERVER_HELLO, rec.is_orig, false) ||
$context.connection.lost_track();
};
######################################################################
# V3 Certificate Request (7.4.4.)
######################################################################
# For now, ignore Certificate Request Details; just eat up message.
type CertificateRequest(rec: SSLRecord) = record {
cont : bytestring &restofdata &transient;
} &let {
state_changed : bool =
$context.connection.transition(STATE_IN_SERVER_HELLO,
STATE_IN_SERVER_HELLO, rec.is_orig, false) ||
$context.connection.lost_track();
};
######################################################################
# V3 Server Hello Done (7.4.5.)
######################################################################
# Server Hello Done is empty
type ServerHelloDone(rec: SSLRecord) = empty &let {
state_changed : bool =
$context.connection.transition(STATE_IN_SERVER_HELLO,
STATE_SERVER_HELLO_DONE, rec.is_orig, false) ||
$context.connection.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(rec: SSLRecord) = record {
key : bytestring &restofdata &transient;
} &let {
state_changed : bool =
$context.connection.transition(STATE_SERVER_HELLO_DONE,
STATE_CLIENT_KEY_NO_CERT, rec.is_orig, true) ||
$context.connection.transition(STATE_CLIENT_CERT,
STATE_CLIENT_KEY_WITH_CERT, rec.is_orig, true) ||
$context.connection.transition(STATE_CLIENT_CERT,
STATE_CLIENT_KEY_WITH_CERT, rec.is_orig, true) ||
$context.connection.lost_track();
};
######################################################################
# V2 Client Master Key (SSLv2 2.5.)
######################################################################
type V2ClientMasterKey(rec: SSLRecord) = record {
cipher_kind_8 : uint8;
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 = 7 + cl_key_len + en_key_len + key_arg_len, &let {
state_changed : bool =
$context.connection.transition(STATE_V2_CL_MASTER_KEY_EXPECTED,
STATE_CONN_ESTABLISHED, rec.is_orig, true) ||
$context.connection.lost_track();
cipher_kind : int = (((rec.head3 << 16) | (rec.head4 << 8)) | cipher_kind_8);
};
######################################################################
# V3 Certificate Verify (7.4.8.)
######################################################################
# For now, ignore Certificate Verify; just eat up the message.
type CertificateVerify(rec: SSLRecord) = record {
cont : bytestring &restofdata &transient;
} &let {
state_changed : bool =
$context.connection.transition(STATE_CLIENT_KEY_WITH_CERT,
STATE_CLIENT_CERT_VERIFIED, rec.is_orig, true) ||
$context.connection.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 messages.
type Finished(rec: SSLRecord) = record {
cont : bytestring &restofdata &transient;
} &let {
state_changed : bool =
$context.connection.transition(STATE_SERVER_HELLO_DONE,
STATE_COMM_ENCRYPTED, rec.is_orig, true) ||
$context.connection.transition(STATE_CLIENT_FINISHED,
STATE_COMM_ENCRYPTED, rec.is_orig, false) ||
$context.connection.lost_track();
};
type SessionTicketHandshake(rec: SSLRecord) = record {
ticket_lifetime_hint: uint32;
data: bytestring &restofdata;
};
######################################################################
# V3 Handshake Protocol (7.)
######################################################################
type UnknownHandshake(hs: Handshake, is_orig: bool) = record {
data : bytestring &restofdata &transient;
} &let {
state_changed : bool = $context.connection.lost_track();
};
type Handshake(rec: SSLRecord) = record {
msg_type : uint8;
length : uint24;
body : case msg_type of {
HELLO_REQUEST -> hello_request : HelloRequest(rec);
CLIENT_HELLO -> client_hello : ClientHello(rec);
SERVER_HELLO -> server_hello : ServerHello(rec);
SESSION_TICKET -> session_ticket : SessionTicketHandshake(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);
FINISHED -> finished : Finished(rec);
CERTIFICATE_URL -> certificate_url : bytestring &restofdata &transient;
CERTIFICATE_STATUS -> certificate_status : bytestring &restofdata &transient;
default -> unknown_handshake : UnknownHandshake(this, rec.is_orig);
} &length = to_int()(length);
};
######################################################################
# Fragmentation (6.2.1.)
######################################################################
type UnknownRecord(rec: SSLRecord) = record {
cont : bytestring &restofdata &transient;
} &let {
state_changed : bool = $context.connection.lost_track();
};
type CiphertextRecord(rec: SSLRecord) = record {
cont : bytestring &restofdata &transient;
} &let {
state_changed : bool =
$context.connection.transition(STATE_CLIENT_FINISHED,
STATE_CLIENT_FINISHED, rec.is_orig, false) ||
$context.connection.transition(STATE_CLIENT_FINISHED,
STATE_CLIENT_FINISHED, rec.is_orig, true) ||
$context.connection.transition(STATE_ABBREV_SERVER_ENCRYPTED,
STATE_ABBREV_SERVER_FINISHED, rec.is_orig, false) ||
$context.connection.transition(STATE_CLIENT_ENCRYPTED,
STATE_CLIENT_FINISHED, rec.is_orig, true) ||
$context.connection.transition(STATE_COMM_ENCRYPTED,
STATE_CONN_ESTABLISHED, rec.is_orig, false) ||
$context.connection.transition(STATE_COMM_ENCRYPTED,
STATE_CONN_ESTABLISHED, rec.is_orig, true) ||
$context.connection.transition(STATE_CONN_ESTABLISHED,
STATE_CONN_ESTABLISHED, rec.is_orig, false) ||
$context.connection.transition(STATE_CONN_ESTABLISHED,
STATE_CONN_ESTABLISHED, rec.is_orig, true) ||
$context.connection.lost_track();
};
######################################################################
# initial datatype for binpac
######################################################################
type SSLPDU(is_orig: bool) = record {
records : SSLRecord(is_orig)[] &transient &until($element <= 0);
} &byteorder = bigendian;
######################################################################
# binpac analyzer for SSL including
######################################################################
refine connection SSL_Conn += {
%member{
int state_;
int old_state_;
bool hello_requested_;
%}
%init{
state_ = STATE_INITIAL;
old_state_ = STATE_INITIAL;
hello_requested_ = false;
%}
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.
return (head1 << 8) | head2;
else if ( head0 >= 128 && head2 < 5 && head2 != 3 )
// Not very strong evidence, but we suspect
// this to be SSLv2.
return SSLv20;
else
return UNKNOWN_VERSION;
%}
function state() : int %{ return state_; %}
function old_state() : int %{ return old_state_; %}
function transition(olds : AnalyzerState, news : AnalyzerState,
current_record_is_orig : bool, is_orig : bool) : bool
%{
if ( (olds != STATE_ANY && olds != state_) ||
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;
%}
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;
%}
};