analyzer/protocol: Reformat with spicy-format

This commit is contained in:
Arne Welzel 2025-07-28 09:39:40 +02:00
parent aa2afa3e9b
commit d70bcd07b9
6 changed files with 1433 additions and 1519 deletions

View file

@ -55,5 +55,4 @@ repos:
rev: v0.26.0
hooks:
- id: spicy-format
# TODO: Reformat existing large analyzers just before 8.0.
exclude: '(^testing/.*)|(protocol/ldap/.*)|(protocol/quic/.*)|(protocol/websocket/.*)'
exclude: '^testing/.*'

View file

@ -50,7 +50,7 @@ public type ASN1Type = enum {
GeneralString = 27,
UniversalString = 28,
CharacterString = 29,
BMPString = 30
BMPString = 30,
};
#- ASN.1 data classes --------------------------------------------------------
@ -59,7 +59,7 @@ public type ASN1Class = enum {
Universal = 0,
Application = 1,
ContextSpecific = 2,
Private = 3
Private = 3,
};
#- ASN.1 tag definition (including length) ------------------------------------
@ -68,19 +68,17 @@ type LengthType = unit {
var len: uint64;
var tag_len: uint8;
data : bitfield(8) {
data: bitfield(8) {
num: 0..6;
islong: 7;
};
switch ( self.data.islong ) {
switch (self.data.islong) {
0 -> : void {
self.len = self.data.num;
self.tag_len = 1;
}
1 -> : bytes &size=self.data.num
&convert=$$.to_uint(spicy::ByteOrder::Network) {
1 -> : bytes &size=self.data.num &convert=$$.to_uint(spicy::ByteOrder::Network) {
self.len = $$;
self.tag_len = self.data.num + 1;
}
@ -112,7 +110,7 @@ type ASN1BitString = unit(len: uint64, constructed: bool) {
# https://www.obj-sys.com/asn1tutorial/node10.html
type ASN1OctetString = unit(len: uint64, constructed: bool) {
value: bytes &size = len;
value: bytes &size=len;
# TODO - constructed form
};
@ -124,17 +122,15 @@ type ASN1String = unit(tag: ASN1Tag, len: uint64) {
var encoding: spicy::Charset;
on %init {
switch ( tag.type_ ) {
switch (tag.type_) {
# see "Restricted Character String Types" in
# "Generic String Encoding Rules (GSER) for ASN.1 Types"
# (https://datatracker.ietf.org/doc/html/rfc3641#section-3.2)
case ASN1Type::PrintableString,
ASN1Type::GeneralizedTime,
ASN1Type::UTCTime: {
self.encoding = spicy::Charset::ASCII;
}
case ASN1Type::UTF8String,
ASN1Type::GeneralString,
ASN1Type::CharacterString,
@ -160,7 +156,7 @@ type ASN1String = unit(tag: ASN1Tag, len: uint64) {
# https://www.obj-sys.com/asn1tutorial/node124.html
type ASN1ObjectIdentifierNibble = unit {
data : bitfield(8) {
data: bitfield(8) {
num: 0..6;
more: 7;
};
@ -171,7 +167,7 @@ type ASN1ObjectIdentifier = unit(len: uint64) {
var temp: uint64;
var oidstring: string;
: uint8 if ( len >= 1 ) {
: uint8 if(len >= 1) {
self.temp = $$ / 40;
self.oidbytes += ("%d" % (self.temp)).encode();
self.temp = $$ % 40;
@ -180,8 +176,8 @@ type ASN1ObjectIdentifier = unit(len: uint64) {
}
sublist: ASN1ObjectIdentifierNibble[len - 1] foreach {
self.temp = ( self.temp<<7 ) | $$.num;
if ( $$.more != 1 ) {
self.temp = (self.temp << 7) | $$.num;
if ($$.more != 1) {
self.oidbytes += (".%d" % (self.temp)).encode();
self.temp = 0;
}
@ -192,7 +188,6 @@ type ASN1ObjectIdentifier = unit(len: uint64) {
}
};
#- ASN.1 message header (tag + length information) ----------------------------
public type ASN1Header = unit {
@ -203,24 +198,14 @@ public type ASN1Header = unit {
#- ASN.1 message body ---------------------------------------------------------
public type ASN1Body = unit(head: ASN1Header, recursive: bool) {
switch ( head.tag.type_ ) {
ASN1Type::Boolean -> bool_value: uint8 &convert=cast<bool>($$) &requires=head.len.len==1;
switch (head.tag.type_) {
ASN1Type::Boolean -> bool_value: uint8 &convert=cast<bool>($$) &requires=head.len.len == 1;
ASN1Type::Integer,
ASN1Type::Enumerated -> num_value: bytes &size=head.len.len
&convert=$$.to_int(spicy::ByteOrder::Big);
ASN1Type::NullVal -> null_value: bytes &size=0 &requires=head.len.len==0;
ASN1Type::Enumerated -> num_value: bytes &size=head.len.len &convert=$$.to_int(spicy::ByteOrder::Big);
ASN1Type::NullVal -> null_value: bytes &size=0 &requires=head.len.len == 0;
ASN1Type::BitString -> bitstr_value: ASN1BitString(head.len.len, head.tag.constructed);
ASN1Type::OctetString -> str_value: ASN1OctetString(head.len.len, head.tag.constructed)
&convert=$$.value.decode(spicy::Charset::ASCII);
ASN1Type::ObjectIdentifier -> str_value: ASN1ObjectIdentifier(head.len.len)
&convert=$$.oidstring;
ASN1Type::OctetString -> str_value: ASN1OctetString(head.len.len, head.tag.constructed) &convert=$$.value.decode(spicy::Charset::ASCII);
ASN1Type::ObjectIdentifier -> str_value: ASN1ObjectIdentifier(head.len.len) &convert=$$.oidstring;
ASN1Type::BMPString,
ASN1Type::CharacterString,
ASN1Type::GeneralizedTime,
@ -235,8 +220,8 @@ public type ASN1Body = unit(head: ASN1Header, recursive: bool) {
ASN1Type::VideotextString,
ASN1Type::VisibleString,
ASN1Type::UniversalString -> str_value: ASN1String(head.tag, head.len.len);
ASN1Type::Sequence, ASN1Type::Set -> seq: ASN1SubMessages(head.len.len) if (recursive);
ASN1Type::Sequence,
ASN1Type::Set -> seq: ASN1SubMessages(head.len.len) if(recursive);
# TODO: ASN1Type values not handled yet
ASN1Type::ObjectDescriptor,
@ -265,15 +250,12 @@ public type ASN1Message = unit(recursive: bool) {
var application_id: int32;
head: ASN1Header;
switch ( self.head.tag.class ) {
switch (self.head.tag.class) {
ASN1Class::Universal -> body: ASN1Body(self.head, recursive);
ASN1Class::Application,
ASN1Class::ContextSpecific,
ASN1Class::Private -> application_data: bytes &size=self.head.len.len {
self.application_id = cast<int32>(self.head.tag.type_);
}
};
};

View file

@ -115,12 +115,9 @@ public type ResultCode = enum {
#-----------------------------------------------------------------------------
public type Result = unit {
code: ASN1::ASN1Message(True) &convert=cast<ResultCode>(cast<uint8>($$.body.num_value))
&default=ResultCode::Undef;
matchedDN: ASN1::ASN1Message(True) &convert=$$.body.str_value
&default="";
diagnosticMessage: ASN1::ASN1Message(True) &convert=$$.body.str_value
&default="";
code: ASN1::ASN1Message(True) &convert=cast<ResultCode>(cast<uint8>($$.body.num_value)) &default=ResultCode::Undef;
matchedDN: ASN1::ASN1Message(True) &convert=$$.body.str_value &default="";
diagnosticMessage: ASN1::ASN1Message(True) &convert=$$.body.str_value &default="";
# TODO: if we want to parse referral URIs in result
# https://tools.ietf.org/html/rfc4511#section-4.1.10
@ -152,7 +149,7 @@ public type Messages = unit {
#-----------------------------------------------------------------------------
public type MessageDispatch = unit(ctx: Ctx&) {
switch( ctx.messageMode ) {
switch (ctx.messageMode) {
MessageMode::Undef -> : Message(ctx);
MessageMode::MS_KRB5 -> : SaslMsKrb5Stripper(ctx);
MessageMode::TLS -> : TlsForward; # never returns
@ -162,7 +159,6 @@ public type MessageDispatch = unit(ctx: Ctx&) {
};
};
#-----------------------------------------------------------------------------
type MaybeEncrypted = unit(ctx: Ctx&) {
# A plaintext LDAP message always starts with at least 3 bytes and the first
@ -183,7 +179,7 @@ type MaybeEncrypted = unit(ctx: Ctx&) {
}
first: uint8 {
if ( $$ == 0x30 ) {
if ($$ == 0x30) {
ctx.messageMode = MessageMode::CLEARTEXT;
} else {
ctx.messageMode = MessageMode::ENCRYPTED;
@ -195,12 +191,12 @@ type MaybeEncrypted = unit(ctx: Ctx&) {
# into the SASL payload and check if it starts with a 0504 (WRAP_TOKEN).
# If so, switch into KRB mode assuming that's what is being used and
# have a chance seeing some more plaintext LDAP in non-sealed tokens.
rem: uint8[3] if ( ctx.messageMode == MessageMode::ENCRYPTED && (|self.mech| == 0 || self.mech.starts_with(b"GSS")) ) {
rem: uint8[3] if(ctx.messageMode == MessageMode::ENCRYPTED && (|self.mech| == 0 || self.mech.starts_with(b"GSS"))) {
self.saslLen = (uint64(self.first) << 24) + (uint64($$[0]) << 16) + (uint64($$[1]) << 8) + uint64($$[2]);
}
: uint16 if ( self.saslLen >= 2 ) {
if ( $$ == 0x0504 ) {
: uint16 if(self.saslLen >= 2) {
if ($$ == 0x0504) {
ctx.messageMode = MessageMode::MS_KRB5;
}
}
@ -242,14 +238,15 @@ type KrbWrapToken = unit {
};
filler: skip b"\xff";
ec: uint16; # extra count
rrc: uint16 { # right rotation count
rrc: uint16 {
# right rotation count
# Handle rrc == ec or rrc == 0.
if ( self.rrc == self.ec ) {
if (self.rrc == self.ec) {
self.header_ec = self.ec;
} else if ( self.rrc == 0 ) {
} else if (self.rrc == 0) {
self.trailer_ec = self.ec;
} else {
if ( ! self.ctx_flags.sealed )
if (!self.ctx_flags.sealed)
# If it's sealed, we'll consume until &eod anyhow
# and ec/rrc shouldn't apply, otherwise, bail.
throw "Unhandled rc %s and ec %s" % (self.ec, self.rrc);
@ -271,24 +268,24 @@ type SaslMsKrb5Stripper = unit(ctx: Ctx&) {
len: uint32;
krb5_tok_id: uint16;
switch ( self.krb5_tok_id ) {
switch (self.krb5_tok_id) {
0x0504 -> krb_wrap_token: KrbWrapToken;
* -> : void;
};
: skip bytes &size=0 {
self.switch_size = self.len - (self.offset() - 4);
if ( self?.krb_wrap_token )
if (self?.krb_wrap_token)
self.switch_size -= self.krb_wrap_token.trailer_ec;
}
switch ( self?.krb_wrap_token && ! self.krb_wrap_token.ctx_flags.sealed ) {
switch (self?.krb_wrap_token && !self.krb_wrap_token.ctx_flags.sealed) {
True -> : Message(ctx)[] &eod;
* -> : skip bytes &eod;
} &size=self.switch_size;
# Consume the wrap token trailer, if any.
trailer_e: skip bytes &size=self.krb_wrap_token.trailer_ec if (self?.krb_wrap_token);
trailer_e: skip bytes &size=self.krb_wrap_token.trailer_ec if(self?.krb_wrap_token);
};
#-----------------------------------------------------------------------------
@ -323,7 +320,7 @@ public type Message = unit(ctx: Ctx&) {
self.opLen = $$.len.len;
}
switch ( self.opcode ) {
switch (self.opcode) {
ProtocolOpcode::BIND_REQUEST -> BIND_REQUEST: BindRequest(self, ctx);
ProtocolOpcode::BIND_RESPONSE -> BIND_RESPONSE: BindResponse(self, ctx);
ProtocolOpcode::UNBIND_REQUEST -> UNBIND_REQUEST: UnbindRequest(self);
@ -400,23 +397,23 @@ type GSS_SPNEGO_Init = unit {
-> ntlmSignature: skip b"NTLMSSP"; # Unsupported, should forward to child analyzer!
};
spnegoLen: skip ASN1::LengthType if (self?.spnegoInitByte || self?.spnegoChoiceByte);
spnegoLen: skip ASN1::LengthType if(self?.spnegoInitByte || self?.spnegoChoiceByte);
# Peak into the SPNEGO_negTokenInit
spnegoInitial: skip GSS_SPNEGO_negTokenInit if (self?.spnegoInitByte);
spnegoInitial: skip GSS_SPNEGO_negTokenInit if(self?.spnegoInitByte);
};
type SaslCredentials = unit() {
type SaslCredentials = unit {
mechanism: ASN1::ASN1Message(False) &convert=$$.body.str_value;
# Peak into GSS-SPNEGO payload if we have any.
switch ( self.mechanism ) {
switch (self.mechanism) {
"GSS-SPNEGO" -> gss_spnego: GSS_SPNEGO_Init;
* -> : skip bytes &eod;
};
};
type SicilyMessage = unit() {
type SicilyMessage = unit {
# Just ensure the signature matches. We could do more,
# but it'd be better to forward to an NTLM analyzer.
signature: skip b"NTLMSSP";
@ -429,8 +426,8 @@ type GSS_SPNEGO_Subsequent = unit {
-> ntmlSignature: skip b"NTLMSSP"; # Unsupported, should forward to NTLM!
};
spnegoChoiceLen: skip ASN1::LengthType if (self?.spnegoChoiceByte);
negTokenResp: GSS_SPNEGO_negTokenResp if (self?.spnegoChoiceByte);
spnegoChoiceLen: skip ASN1::LengthType if(self?.spnegoChoiceByte);
negTokenResp: GSS_SPNEGO_negTokenResp if(self?.spnegoChoiceByte);
};
type GSS_SPNEGO_negTokenResp = unit {
@ -440,15 +437,15 @@ type GSS_SPNEGO_negTokenResp = unit {
# Parse the contained Sequence.
seq: ASN1::ASN1Message(True) {
for ( msg in $$.body.seq.submessages ) {
for (msg in $$.body.seq.submessages) {
# https://www.rfc-editor.org/rfc/rfc4178#section-4.2.2
if ( msg.application_id == 0 ) {
if (msg.application_id == 0) {
self.accepted = msg.application_data == b"\x0a\x01\x00";
} else if ( msg.application_id == 1 ) {
} else if (msg.application_id == 1) {
self.supportedMech = msg;
} else if ( msg.application_id == 2 ) {
} else if (msg.application_id == 2) {
self.responseToken = msg.application_data;
} else if ( msg.application_id == 3 ) {
} else if (msg.application_id == 3) {
# ignore mechListMec
} else {
throw "unhandled NegTokenResp id %s" % msg.application_id;
@ -456,7 +453,7 @@ type GSS_SPNEGO_negTokenResp = unit {
}
}
switch ( self?.supportedMech ) {
switch (self?.supportedMech) {
True -> supportedMechOid: ASN1::ASN1Message(False) &convert=$$.body.str_value;
* -> : void;
} &parse-from=self.supportedMech.application_data;
@ -484,25 +481,20 @@ type BindRequest = unit(inout message: Message, ctx: Ctx&) {
}
}
if ( |self.authData| > 0 ) {
switch ( self.authType ) {
BindAuthType::BIND_AUTH_SIMPLE ->
: void {
if (|self.authData| > 0) {
switch (self.authType) {
BindAuthType::BIND_AUTH_SIMPLE -> : void {
self.simpleCreds = self.authData.decode();
message.arg = self.simpleCreds;
}
BindAuthType::BIND_AUTH_SASL ->
saslCreds: SaslCredentials {
BindAuthType::BIND_AUTH_SASL -> saslCreds: SaslCredentials {
message.arg = self.saslCreds.mechanism;
ctx.saslMechanism = self.saslCreds.mechanism;
}
BindAuthType::SICILY_NEGOTIATE, BindAuthType::SICILY_RESPONSE ->
sicilyMessage: SicilyMessage {
BindAuthType::SICILY_NEGOTIATE,
BindAuthType::SICILY_RESPONSE -> sicilyMessage: SicilyMessage {
message.arg = self.sicilyMessage.signature_decoded;
}
* -> : void;
} &parse-from=self.authData;
};
@ -527,7 +519,7 @@ type BindResponse = unit(inout message: Message, ctx: Ctx&) {
#
# Note, messageMode may be changed to something more specific like
# MS_KRB5 below.
if ( |ctx.saslMechanism| > 0 && $$.code == ResultCode::SUCCESS ) {
if (|ctx.saslMechanism| > 0 && $$.code == ResultCode::SUCCESS) {
ctx.messageMode = MessageMode::MAYBE_ENCRYPTED;
}
}
@ -543,13 +535,12 @@ type BindResponse = unit(inout message: Message, ctx: Ctx&) {
# If the client requested GSS-SPNEGO, try to parse the server's response
# to switch message mode.
gss_spnego: GSS_SPNEGO_Subsequent &parse-from=self.serverSaslCreds[0].payload
if (ctx.saslMechanism == "GSS-SPNEGO" && |self.serverSaslCreds| > 0) {
gss_spnego: GSS_SPNEGO_Subsequent &parse-from=self.serverSaslCreds[0].payload if(ctx.saslMechanism == "GSS-SPNEGO" && |self.serverSaslCreds| > 0) {
if ( $$?.negTokenResp ) {
if ($$?.negTokenResp) {
local token = $$.negTokenResp;
if ( token.accepted && token?.supportedMechOid ) {
if ( token.supportedMechOid == GSSAPI_MECH_MS_KRB5 && token.responseToken ) {
if (token.accepted && token?.supportedMechOid) {
if (token.supportedMechOid == GSSAPI_MECH_MS_KRB5 && token.responseToken) {
ctx.messageMode = MessageMode::MS_KRB5;
}
}
@ -603,8 +594,7 @@ public type AttributeSelection = unit {
# https://tools.ietf.org/html/rfc4511#section-4.5.1
# and decide how deep that should be fleshed out.
: ASN1::ASN1Message(True) {
if (($$.head.tag.type_ == ASN1::ASN1Type::Sequence) &&
($$.body?.seq)) {
if (($$.head.tag.type_ == ASN1::ASN1Type::Sequence) && ($$.body?.seq)) {
for (i in $$.body.seq.submessages) {
if (i.body?.str_value) {
self.attributes.push_back(i.body.str_value);
@ -619,9 +609,7 @@ type AttributeValueAssertion = unit {
var val: string = "";
: ASN1::ASN1Message(True) {
if (($$.head.tag.type_ == ASN1::ASN1Type::Sequence) &&
($$.body?.seq) &&
(|$$.body.seq.submessages| >= 2)) {
if (($$.head.tag.type_ == ASN1::ASN1Type::Sequence) && ($$.body?.seq) && (|$$.body.seq.submessages| >= 2)) {
if ($$.body.seq.submessages[0].body?.str_value) {
self.desc = $$.body.seq.submessages[0].body.str_value;
}
@ -643,22 +631,22 @@ type ParseNestedNot = unit {
# Helper functions to properly format some custom data structures
public function utf16_guid_to_hex_repr(bts: bytes) : string {
public function utf16_guid_to_hex_repr(bts: bytes): string {
# Rather ugly workaround to pretty-print the CLDAP DomainGuid UTF16-LE encoded string
# in the same format as Wireshark (aabbccdd-eeff-gghh-iijj-kkllmmnnoopp)
# We need to have exactly 16 bytes...
if ( |bts| != 16 ) {
if (|bts| != 16) {
# ... and otherwise just return an error code
return "GUID_FORMAT_FAILED";
}
local ret = "";
for ( i in [[3, 2, 1, 0], [5, 4], [7, 6], [8, 9], [10, 11, 12, 13, 14, 15]] ) {
for ( j in i ) {
for (i in [[3, 2, 1, 0], [5, 4], [7, 6], [8, 9], [10, 11, 12, 13, 14, 15]]) {
for (j in i) {
local bt: uint8 = *bts.at(j);
ret = ret + "%02x" % bt;
if ( j in [0, 4, 6, 9] ) {
if (j in [0, 4, 6, 9]) {
ret = ret + "-";
}
}
@ -666,15 +654,15 @@ public function utf16_guid_to_hex_repr(bts: bytes) : string {
return ret;
}
public function bytes_sid_to_hex_repr(bts: bytes) : string {
public function bytes_sid_to_hex_repr(bts: bytes): string {
local ret = "";
local cnt = 0;
while ( cnt < |bts| ) {
while (cnt < |bts|) {
local bt: uint8 = *bts.at(cnt);
ret = ret + "%02x" % bt;
if ( cnt < |bts|-1 ) {
if (cnt < |bts| - 1) {
ret = ret + ":";
}
cnt += 1;
@ -682,11 +670,11 @@ public function bytes_sid_to_hex_repr(bts: bytes) : string {
return ret;
}
public function bytes_sid_to_SID_repr(bts: bytes) : string {
public function bytes_sid_to_SID_repr(bts: bytes): string {
# Example: SID -> S-1-5-21-1153942841-488947194-1912431946
# Needs to be exactly 24 bytes
if ( |bts| != 24 ) {
if (|bts| != 24) {
# ... and otherwise just return an error code
return "SID_FORMAT_FAILED";
}
@ -696,9 +684,9 @@ public function bytes_sid_to_SID_repr(bts: bytes) : string {
# Mixed little and big endian, so turn everything to big endian first...
# Byte 1 seems to be skipped when parsing the SID
for ( i in [[0], [2, 3, 4, 5, 6, 7], [11, 10, 9, 8], [15, 14, 13, 12], [19, 18, 17, 16], [23, 22, 21, 20]] ) {
for (i in [[0], [2, 3, 4, 5, 6, 7], [11, 10, 9, 8], [15, 14, 13, 12], [19, 18, 17, 16], [23, 22, 21, 20]]) {
local dec_val_rep: bytes = b"";
for ( j in i ) {
for (j in i) {
local bt: uint8 = *bts.at(j);
dec_val_rep += bt;
cnt += 1;
@ -708,24 +696,23 @@ public function bytes_sid_to_SID_repr(bts: bytes) : string {
ret = ret + "%u" % dec_val_rep.to_uint(spicy::ByteOrder::Big);
# Only print the dash when we're not at the end
if ( cnt < 23 ) {
if (cnt < 23) {
ret = ret + "-";
}
}
return ret;
}
public function uint32_to_hex_repr(bts: bytes) : string {
public function uint32_to_hex_repr(bts: bytes): string {
# Needs to be exactly 4 bytes
if ( |bts| != 4 ) {
if (|bts| != 4) {
# ... and otherwise just return an error code
return "HEX_FORMAT_FAILED";
}
# Workaround to print the hex value of an uint32, prepended with '0x'
local ret = "0x";
for ( i in [3, 2, 1, 0] ) {
for (i in [3, 2, 1, 0]) {
local bt: uint8 = *bts.at(i);
ret = ret + "%02x" % bt;
}
@ -736,19 +723,18 @@ public function uint32_to_hex_repr(bts: bytes) : string {
public function string_representation(search_filter: SearchFilter): string {
local repr: string;
switch ( local fType = search_filter.filterType ) {
switch (local fType = search_filter.filterType) {
# The NOT, AND and OR filter types are trees and may hold many leaf nodes. So recursively get
# the stringPresentations for the leaf nodes and add them all in one final statement.
case FilterType::FILTER_NOT: {
repr = "(!%s)" % search_filter.FILTER_NOT.searchfilter.stringRepresentation;
}
case FilterType::FILTER_AND, FilterType::FILTER_OR: {
case FilterType::FILTER_AND,
FilterType::FILTER_OR: {
local nestedObj: ParseNestedAndOr;
local printChar = "";
if ( fType == FilterType::FILTER_AND ) {
if (fType == FilterType::FILTER_AND) {
printChar = "&";
nestedObj = search_filter.FILTER_AND;
} else {
@ -777,15 +763,12 @@ public function string_representation(search_filter: SearchFilter): string {
#
local i = 0;
for ( searchFilter in nestedObj.searchfilters ) {
switch ( i ) {
for (searchFilter in nestedObj.searchfilters) {
switch (i) {
case 0: {
repr = "(%s%s%s" % (
printChar,
searchFilter.stringRepresentation,
repr = "(%s%s%s" % (printChar, searchFilter.stringRepresentation,
# If we have exactly one element immediately close the statement since we are done.
|nestedObj.searchfilters| == 1 ? ")" : ""
);
|nestedObj.searchfilters| == 1 ? ")" : "");
}
case 1: {
repr = repr + searchFilter.stringRepresentation + ")";
@ -799,39 +782,29 @@ public function string_representation(search_filter: SearchFilter): string {
}
# The following FilterTypes are leaf nodes and can thus be represented in a statement
case FilterType::FILTER_EXT: {
# For extended search filters the meaning of the individual fields in
# `DecodedAttributeValue` is slightly different.
repr = "(%s:%s:=%s)" % (search_filter.FILTER_EXT.assertionValueDecoded,
search_filter.FILTER_EXT.attributeDesc.decode(),
search_filter.FILTER_EXT.matchValue);
repr = "(%s:%s:=%s)" % (search_filter.FILTER_EXT.assertionValueDecoded, search_filter.FILTER_EXT.attributeDesc.decode(), search_filter.FILTER_EXT.matchValue);
}
case FilterType::FILTER_APPROX: {
repr = "(%s~=%s)" % (search_filter.FILTER_APPROX.attributeDesc.decode(),
search_filter.FILTER_APPROX.assertionValueDecoded);
repr = "(%s~=%s)" % (search_filter.FILTER_APPROX.attributeDesc.decode(), search_filter.FILTER_APPROX.assertionValueDecoded);
}
case FilterType::FILTER_EQ: {
repr = "(%s=%s)" % (search_filter.FILTER_EQ.attributeDesc.decode(),
search_filter.FILTER_EQ.assertionValueDecoded);
repr = "(%s=%s)" % (search_filter.FILTER_EQ.attributeDesc.decode(), search_filter.FILTER_EQ.assertionValueDecoded);
}
case FilterType::FILTER_GE: {
repr = "(%s>=%s)" % (search_filter.FILTER_GE.attributeDesc.decode(),
search_filter.FILTER_GE.assertionValueDecoded);
repr = "(%s>=%s)" % (search_filter.FILTER_GE.attributeDesc.decode(), search_filter.FILTER_GE.assertionValueDecoded);
}
case FilterType::FILTER_LE: {
repr = "(%s<=%s)" % (search_filter.FILTER_LE.attributeDesc.decode(),
search_filter.FILTER_LE.assertionValueDecoded);
repr = "(%s<=%s)" % (search_filter.FILTER_LE.attributeDesc.decode(), search_filter.FILTER_LE.assertionValueDecoded);
}
case FilterType::FILTER_SUBSTR: {
local anys: string = "";
if ( |search_filter.FILTER_SUBSTR.anys| > 0 )
if (|search_filter.FILTER_SUBSTR.anys| > 0)
anys = b"*".join(search_filter.FILTER_SUBSTR.anys).decode() + "*";
repr = "(%s=%s*%s%s)" % (search_filter.FILTER_SUBSTR.attributeDesc.decode(),
search_filter.FILTER_SUBSTR.initial,
anys,
search_filter.FILTER_SUBSTR.final);
repr = "(%s=%s*%s%s)" % (search_filter.FILTER_SUBSTR.attributeDesc.decode(), search_filter.FILTER_SUBSTR.initial, anys, search_filter.FILTER_SUBSTR.final);
}
case FilterType::FILTER_PRESENT: {
repr = "(%s=*)" % search_filter.FILTER_PRESENT;
@ -854,27 +827,24 @@ type DecodedAttributeValue = unit(fType: FilterType) {
assertionValue: bytes &size=self.assertionValue_len;
# Only for the FILTER_EXT type, parse extra fields
: uint8 if ( fType == FilterType::FILTER_EXT );
matchValue_len: uint8 if( fType == FilterType::FILTER_EXT );
matchValue: bytes &size=self.matchValue_len if ( fType == FilterType::FILTER_EXT );
: uint8 if(fType == FilterType::FILTER_EXT);
matchValue_len: uint8 if(fType == FilterType::FILTER_EXT);
matchValue: bytes &size=self.matchValue_len if(fType == FilterType::FILTER_EXT);
on %done {
switch ( self.attributeDesc ) {
switch (self.attributeDesc) {
# Special parsing required for some CLDAP attributes,
# see https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/895a7744-aff3-4f64-bcfa-f8c05915d2e9
case b"DomainGuid": {
self.assertionValueDecoded = utf16_guid_to_hex_repr(self.assertionValue);
}
case b"objectSid", b"AAC": {
case b"objectSid",
b"AAC": {
self.assertionValueDecoded = bytes_sid_to_hex_repr(self.assertionValue);
}
case b"DomainSid": {
self.assertionValueDecoded = bytes_sid_to_SID_repr(self.assertionValue);
}
case b"NtVer": {
self.assertionValueDecoded = uint32_to_hex_repr(self.assertionValue);
}
@ -902,11 +872,11 @@ type SubstringFilter = unit {
header: ASN1::ASN1Header;
: ASN1::ASN1Message(False)[] &size=self.header.len.len foreach {
local data = $$.application_data.decode();
if ( $$.application_id == 0 ) {
if ($$.application_id == 0) {
self.initial = data;
} else if ( $$.application_id == 1 ) {
} else if ($$.application_id == 1) {
self.anys.push_back(data);
} else if ( $$.application_id == 2 ) {
} else if ($$.application_id == 2) {
self.final = data;
} else {
throw "invalid substring choice %s" % $$.application_id;
@ -930,34 +900,21 @@ type SearchFilter = unit {
}
}
switch ( self.filterType ) {
switch (self.filterType) {
# FilterTypes that hold one or more SearchFilters inside them
FilterType::FILTER_AND -> FILTER_AND: ParseNestedAndOr()
&parse-from=self.filterBytes;
FilterType::FILTER_OR -> FILTER_OR: ParseNestedAndOr()
&parse-from=self.filterBytes;
FilterType::FILTER_NOT -> FILTER_NOT: ParseNestedNot()
&parse-from=self.filterBytes;
FilterType::FILTER_AND -> FILTER_AND: ParseNestedAndOr() &parse-from=self.filterBytes;
FilterType::FILTER_OR -> FILTER_OR: ParseNestedAndOr() &parse-from=self.filterBytes;
FilterType::FILTER_NOT -> FILTER_NOT: ParseNestedNot() &parse-from=self.filterBytes;
# FilterTypes that we can actually convert to a string
FilterType::FILTER_EQ -> FILTER_EQ: DecodedAttributeValue(FilterType::FILTER_EQ)
&parse-from=self.filterBytes;
FilterType::FILTER_SUBSTR -> FILTER_SUBSTR: SubstringFilter
&parse-from=self.filterBytes;
FilterType::FILTER_GE -> FILTER_GE: DecodedAttributeValue(FilterType::FILTER_GE)
&parse-from=self.filterBytes;
FilterType::FILTER_LE -> FILTER_LE: DecodedAttributeValue(FilterType::FILTER_LE)
&parse-from=self.filterBytes;
FilterType::FILTER_APPROX -> FILTER_APPROX: DecodedAttributeValue(FilterType::FILTER_APPROX)
&parse-from=self.filterBytes;
FilterType::FILTER_EXT -> FILTER_EXT: DecodedAttributeValue(FilterType::FILTER_EXT)
&parse-from=self.filterBytes;
FilterType::FILTER_PRESENT -> FILTER_PRESENT: ASN1::ASN1OctetString(self.filterLen, False)
&convert=$$.value.decode(spicy::Charset::ASCII)
&parse-from=self.filterBytes;
FilterType::FILTER_EQ -> FILTER_EQ: DecodedAttributeValue(FilterType::FILTER_EQ) &parse-from=self.filterBytes;
FilterType::FILTER_SUBSTR -> FILTER_SUBSTR: SubstringFilter &parse-from=self.filterBytes;
FilterType::FILTER_GE -> FILTER_GE: DecodedAttributeValue(FilterType::FILTER_GE) &parse-from=self.filterBytes;
FilterType::FILTER_LE -> FILTER_LE: DecodedAttributeValue(FilterType::FILTER_LE) &parse-from=self.filterBytes;
FilterType::FILTER_APPROX -> FILTER_APPROX: DecodedAttributeValue(FilterType::FILTER_APPROX) &parse-from=self.filterBytes;
FilterType::FILTER_EXT -> FILTER_EXT: DecodedAttributeValue(FilterType::FILTER_EXT) &parse-from=self.filterBytes;
FilterType::FILTER_PRESENT -> FILTER_PRESENT: ASN1::ASN1OctetString(self.filterLen, False) &convert=$$.value.decode(spicy::Charset::ASCII) &parse-from=self.filterBytes;
};
# So when you're done with recursively parsing the filters, we can now leverage the tree structure to
@ -970,19 +927,16 @@ type SearchFilter = unit {
on %error {
self.stringRepresentation = "FILTER_PARSING_ERROR";
}
};
public type SearchRequest = unit(inout message: Message) {
baseObject: ASN1::ASN1Message(True) &convert=$$.body.str_value {
message.obj = self.baseObject;
}
scope: ASN1::ASN1Message(True) &convert=cast<SearchScope>(cast<uint8>($$.body.num_value))
&default=SearchScope::Undef {
scope: ASN1::ASN1Message(True) &convert=cast<SearchScope>(cast<uint8>($$.body.num_value)) &default=SearchScope::Undef {
message.arg = "%s" % self.scope;
}
deref: ASN1::ASN1Message(True) &convert=cast<SearchDerefAlias>(cast<uint8>($$.body.num_value))
&default=SearchDerefAlias::Undef;
deref: ASN1::ASN1Message(True) &convert=cast<SearchDerefAlias>(cast<uint8>($$.body.num_value)) &default=SearchDerefAlias::Undef;
sizeLimit: ASN1::ASN1Message(True) &convert=$$.body.num_value &default=0;
timeLimit: ASN1::ASN1Message(True) &convert=$$.body.num_value &default=0;
typesOnly: ASN1::ASN1Message(True) &convert=$$.body.bool_value &default=False;
@ -1110,9 +1064,7 @@ type ExtendedRequest = unit(inout message: Message, ctx: Ctx&) {
}
# If there's more byte to parse, it's the requestValue.
: ASN1::ASN1Message(False)
&requires=($$.head.tag.class == ASN1::ASN1Class::ContextSpecific)
if ( message.opLen > self.offset() ) {
: ASN1::ASN1Message(False) &requires=($$.head.tag.class == ASN1::ASN1Class::ContextSpecific) if(message.opLen > self.offset()) {
self.requestValue = $$.application_data;
}
@ -1121,7 +1073,7 @@ type ExtendedRequest = unit(inout message: Message, ctx: Ctx&) {
# Did the client request StartTLS?
#
# https://datatracker.ietf.org/doc/html/rfc4511#section-4.14.1
if ( self.requestName == "1.3.6.1.4.1.1466.20037" )
if (self.requestName == "1.3.6.1.4.1.1466.20037")
ctx.startTlsRequested = True;
}
};
@ -1129,9 +1081,9 @@ type ExtendedRequest = unit(inout message: Message, ctx: Ctx&) {
#-----------------------------------------------------------------------------
type ExtendedResponseEntry = unit(inout r: ExtendedResponse) {
: ASN1::ASN1Message(False) &requires=($$.head.tag.class == ASN1::ASN1Class::ContextSpecific) {
if ( $$.head.tag.type_ == ASN1::ASN1Type(10) )
if ($$.head.tag.type_ == ASN1::ASN1Type(10))
r.responseName = $$.application_data;
else if ( $$.head.tag.type_ == ASN1::ASN1Type(11) )
else if ($$.head.tag.type_ == ASN1::ASN1Type(11))
r.responseValue = $$.application_data;
else
throw "Unhandled extended response tag %s" % $$.head.tag;
@ -1148,12 +1100,12 @@ type ExtendedResponse = unit(inout message: Message, ctx: Ctx&) {
# Try to parse two ASN1 entries if there are bytes left in the unit.
# Both are optional and identified by context specific tagging.
: ExtendedResponseEntry(self) if ( message.opLen > self.offset() );
: ExtendedResponseEntry(self) if ( message.opLen > self.offset() );
: ExtendedResponseEntry(self) if(message.opLen > self.offset());
: ExtendedResponseEntry(self) if(message.opLen > self.offset());
on %done {
# Client had requested StartTLS and it was successful? Switch to SSL.
if ( ctx.startTlsRequested && message.result_.code == ResultCode::SUCCESS )
if (ctx.startTlsRequested && message.result_.code == ResultCode::SUCCESS)
ctx.messageMode = MessageMode::TLS;
}
};

View file

@ -7,31 +7,23 @@ import spicy;
import zeek;
# The interface to the C++ code that handles the decryption of the INITIAL packet payload using well-known keys
public function decrypt_crypto_payload(
version: uint32,
data: bytes,
connection_id: bytes,
encrypted_offset: uint64,
payload_offset: uint64,
from_client: bool
): bytes &cxxname="QUIC_decrypt_crypto_payload";
public function decrypt_crypto_payload(version: uint32, data: bytes, connection_id: bytes, encrypted_offset: uint64, payload_offset: uint64, from_client: bool): bytes &cxxname="QUIC_decrypt_crypto_payload";
# Can we decrypt?
function can_decrypt(long_header: LongHeaderPacket, context: Context, crypto: CryptoSinkUnit&): bool {
if ( ! long_header.is_initial )
if (!long_header.is_initial)
return False;
if ( crypto == Null )
if (crypto == Null)
return False;
# Can only decrypt the responder if we've seen the initial destination conn id.
if ( ! crypto.is_orig && ! context.initial_destination_conn_id )
if (!crypto.is_orig && !context.initial_destination_conn_id)
return False;
# Only attempt decryption if we haven't flushed some SSL data yet.
return ! crypto.finished;
return !crypto.finished;
}
function reset_crypto(context: Context&) {
@ -76,7 +68,8 @@ type CryptoSinkUnit = unit(is_orig: bool, context: Context&) {
self.buffered += $$[2];
}
: void &requires=(self.length <= 2**14 + 256) { # The length MUST NOT exceed 2^14 + 256 bytes (RFC 8446)
: void &requires=(self.length <= 2**14 + 256) {
# The length MUST NOT exceed 2^14 + 256 bytes (RFC 8446)
# The client or server hello data is forwarded to the SSL analyzer as a
# TLSPlaintext record with legacy_record_version set to \x03\x03 (1.3).
@ -233,7 +226,7 @@ type VariableLengthInteger = unit {
self.result_ = $$ & 0x3F;
}
: uint8[self.bytes_to_parse - 1] if (self.bytes_to_parse > 1) foreach {
: uint8[self.bytes_to_parse - 1] if(self.bytes_to_parse > 1) foreach {
self.result_ = (self.result_ << 8) | $$;
}
};
@ -243,38 +236,30 @@ type VariableLengthInteger = unit {
# Generic units
##############
public type LongHeaderPacketV1 = unit(inout outer: LongHeaderPacket) {
switch ( LongPacketTypeV1(outer.first_byte.packet_type) ) {
LongPacketTypeV1::INITIAL -> initial_hdr : InitialPacket(outer) {
switch (LongPacketTypeV1(outer.first_byte.packet_type)) {
LongPacketTypeV1::INITIAL -> initial_hdr: InitialPacket(outer) {
outer.is_initial = True;
outer.encrypted_offset = outer.offset() +
self.initial_hdr.length.bytes_to_parse +
self.initial_hdr.token_length.bytes_to_parse +
self.initial_hdr.token_length.result_;
outer.encrypted_offset = outer.offset() + self.initial_hdr.length.bytes_to_parse + self.initial_hdr.token_length.bytes_to_parse + self.initial_hdr.token_length.result_;
outer.payload_length = self.initial_hdr.length.result_;
}
LongPacketTypeV1::ZERO_RTT -> zerortt_hdr : ZeroRTTPacket(outer);
LongPacketTypeV1::HANDSHAKE -> handshake_hdr : HandshakePacket(outer);
LongPacketTypeV1::RETRY -> retry_hdr : RetryPacket(outer) {
LongPacketTypeV1::ZERO_RTT -> zerortt_hdr: ZeroRTTPacket(outer);
LongPacketTypeV1::HANDSHAKE -> handshake_hdr: HandshakePacket(outer);
LongPacketTypeV1::RETRY -> retry_hdr: RetryPacket(outer) {
outer.is_retry = True;
}
};
};
public type LongHeaderPacketV2 = unit(inout outer: LongHeaderPacket) {
switch ( LongPacketTypeV2(outer.first_byte.packet_type) ) {
LongPacketTypeV2::INITIAL -> initial_hdr : InitialPacket(outer) {
switch (LongPacketTypeV2(outer.first_byte.packet_type)) {
LongPacketTypeV2::INITIAL -> initial_hdr: InitialPacket(outer) {
outer.is_initial = True;
outer.encrypted_offset = outer.offset() +
self.initial_hdr.length.bytes_to_parse +
self.initial_hdr.token_length.bytes_to_parse +
self.initial_hdr.token_length.result_;
outer.encrypted_offset = outer.offset() + self.initial_hdr.length.bytes_to_parse + self.initial_hdr.token_length.bytes_to_parse + self.initial_hdr.token_length.result_;
outer.payload_length = self.initial_hdr.length.result_;
}
LongPacketTypeV2::ZERO_RTT -> zerortt_hdr : ZeroRTTPacket(outer);
LongPacketTypeV2::HANDSHAKE -> handshake_hdr : HandshakePacket(outer);
LongPacketTypeV2::RETRY -> retry_hdr : RetryPacket(outer) {
LongPacketTypeV2::ZERO_RTT -> zerortt_hdr: ZeroRTTPacket(outer);
LongPacketTypeV2::HANDSHAKE -> handshake_hdr: HandshakePacket(outer);
LongPacketTypeV2::RETRY -> retry_hdr: RetryPacket(outer) {
outer.is_retry = True;
}
};
@ -302,12 +287,16 @@ public type LongHeaderPacket = unit {
};
version: uint32;
dest_conn_id_len: uint8 { self.server_conn_id_length = $$; }
dest_conn_id_len: uint8 {
self.server_conn_id_length = $$;
}
dest_conn_id: bytes &size=self.server_conn_id_length;
src_conn_id_len: uint8 { self.client_conn_id_length = $$; }
src_conn_id_len: uint8 {
self.client_conn_id_length = $$;
}
src_conn_id: bytes &size=self.client_conn_id_length;
switch ( self.version ) {
switch (self.version) {
VersionDraft22,
VersionDraft23,
VersionDraft24,
@ -337,10 +326,10 @@ public type LongHeaderPacket = unit {
# A QUIC Frame.
public type Frame = unit(header: LongHeaderPacket, from_client: bool, crypto: CryptoSinkUnit, crypto_sink: sink&) {
frame_type : uint8 &convert=cast<FrameType>($$);
frame_type: uint8 &convert=cast<FrameType>($$);
# TODO: add other FrameTypes as well
switch ( self.frame_type ) {
switch (self.frame_type) {
FrameType::ACK1 -> a: ACKPayload(FrameType::ACK1);
FrameType::ACK2 -> b: ACKPayload(FrameType::ACK2);
FrameType::CRYPTO -> c: CRYPTOPayload(from_client) {
@ -351,11 +340,11 @@ public type Frame = unit(header: LongHeaderPacket, from_client: bool, crypto: Cr
# don't attempt to write more bytes into the sink. If it doesn't,
# use 2000 bytes as an arbitrary limit required to observe the
# length of the contained Client Hello or Server Hello.
if ( crypto.length > 0 ) {
if ( |crypto_sink| > crypto.length )
throw "too much crypto data received %s > %s" % ( |crypto_sink|, crypto.length);
if (crypto.length > 0) {
if (|crypto_sink| > crypto.length)
throw "too much crypto data received %s > %s" % (|crypto_sink|, crypto.length);
} else {
if ( |crypto_sink| > 2000 )
if (|crypto_sink| > 2000)
throw "too much crypto data without length received %s" % |crypto_sink|;
}
}
@ -407,7 +396,6 @@ type ConnectionClosePayload = unit(header: LongHeaderPacket) {
reason_phrase: bytes &size=self.reason_phrase_length.result_;
};
##############
# Long packets
# Specific long packet type units
@ -448,7 +436,6 @@ type HandshakePacket = unit(header: LongHeaderPacket) {
payload: skip bytes &size=self.length.result_;
};
type RetryPacket = unit(header: LongHeaderPacket) {
var header: LongHeaderPacket = header;
var retry_token: bytes;
@ -499,15 +486,15 @@ type Packet = unit(from_client: bool, context: Context&) {
# Attach an SSL analyzer to this connection once.
on %init {
if ( ! context?.ssl_handle ) {
if (!context?.ssl_handle) {
context.ssl_handle = zeek::protocol_handle_get_or_create("SSL");
}
self.start = self.input();
# Initialize crypto state in context for both sides if not already done.
if ( context.client_crypto == Null ) {
assert ! context.server_crypto;
if (context.client_crypto == Null) {
assert !context.server_crypto;
context.client_crypto = new CryptoSinkUnit(True, context);
context.client_sink = new sink;
context.client_sink.connect(context.client_crypto);
@ -517,7 +504,7 @@ type Packet = unit(from_client: bool, context: Context&) {
context.server_sink.connect(context.server_crypto);
}
if ( from_client ) {
if (from_client) {
self.crypto = context.client_crypto;
self.crypto_sink = context.client_sink;
} else {
@ -538,14 +525,14 @@ type Packet = unit(from_client: bool, context: Context&) {
}
# Depending on the header, parse it and update the src/dest ConnectionID's
switch ( self.first_byte.header_form ) {
switch (self.first_byte.header_form) {
HeaderForm::SHORT -> short_header: ShortHeader(context.client_cid_len);
HeaderForm::LONG -> long_header: LongHeaderPacket {
# For now, only allow a change of src/dest ConnectionID's for INITIAL packets.
# If we see a retry packet from the responder, reset the decryption
# context such that the next DCID from the client is used for decryption.
if ( self.long_header.is_retry ) {
if (self.long_header.is_retry) {
reset_crypto(context);
self.crypto = Null;
@ -555,23 +542,23 @@ type Packet = unit(from_client: bool, context: Context&) {
};
: void {
if ( self?.long_header && can_decrypt(self.long_header, context, self.crypto ) )
if (self?.long_header && can_decrypt(self.long_header, context, self.crypto))
# If we have parsed an initial packet that we can decrypt the payload,
# determine the size to store into a buffer.
self.packet_size = self.offset();
}
# Buffer the whole packet if we determined we have a chance to decrypt.
packet_data: bytes &parse-at=self.start &size=self.packet_size if ( self.packet_size > 0 ) {
packet_data: bytes &parse-at=self.start &size=self.packet_size if(self.packet_size > 0) {
if ( from_client ) {
if (from_client) {
context.server_cid_len = self.long_header.dest_conn_id_len;
context.client_cid_len = self.long_header.src_conn_id_len;
# This is the first INITIAL packet we attempt to decrypt and it is
# coming from the client. Use its destination connection ID for
# decryption purposes.
if ( ! context.initial_destination_conn_id ) {
if (!context.initial_destination_conn_id) {
context.initial_destination_conn_id = self.long_header.dest_conn_id;
}
@ -585,7 +572,6 @@ type Packet = unit(from_client: bool, context: Context&) {
self.long_header.payload_length,
from_client
);
} else {
context.server_cid_len = self.long_header.src_conn_id_len;
context.client_cid_len = self.long_header.dest_conn_id_len;
@ -603,7 +589,7 @@ type Packet = unit(from_client: bool, context: Context&) {
# We attempted decryption, but it failed. Just reject the
# input and assume Zeek will disable the analyzer for this
# connection.
if ( |self.decrypted_data| == 0 )
if (|self.decrypted_data| == 0)
throw "decryption failed";
# We were able to decrypt the INITIAL packet. Confirm QUIC!
@ -612,11 +598,11 @@ type Packet = unit(from_client: bool, context: Context&) {
# If this packet has a SHORT header, consume until &eod, there's nothing
# we can do with it anyhow.
: ShortPacketPayload if (self.first_byte.header_form == HeaderForm::SHORT);
: ShortPacketPayload if(self.first_byte.header_form == HeaderForm::SHORT);
# If this was packet with a long header and decrypted data exists, attempt
# to parse the plain QUIC frames from it.
frames: Frame(self.long_header, from_client, self.crypto, self.crypto_sink)[] &parse-from=self.decrypted_data if (self.first_byte.header_form == HeaderForm::LONG && |self.decrypted_data| > 0);
frames: Frame(self.long_header, from_client, self.crypto, self.crypto_sink)[] &parse-from=self.decrypted_data if(self.first_byte.header_form == HeaderForm::LONG && |self.decrypted_data| > 0);
};
##############

View file

@ -12,11 +12,7 @@ const OPCODE_CLOSE = 0x08;
const OPCODE_PING = 0x09;
const OPCODE_PONG = 0x0a;
public function fast_unmask(
masking_key_idx: uint64,
masking_key: vector<uint8>,
chunk: bytes
): bytes &cxxname="hlt_websocket::WebSocket::fast_unmask";
public function fast_unmask(masking_key_idx: uint64, masking_key: vector<uint8>, chunk: bytes): bytes &cxxname="hlt_websocket::WebSocket::fast_unmask";
type Frame = unit(m: Message) {
var payload_len: uint64;
@ -41,25 +37,25 @@ type Frame = unit(m: Message) {
self.effective_opcode = m.opcode != OPCODE_CONTINUATION ? m.opcode : self.opcode;
}
payload_len2: uint16 if (self.payload_len1 == 126);
payload_len8: uint64 if (self.payload_len1 == 127);
payload_len2: uint16 if(self.payload_len1 == 126);
payload_len8: uint64 if(self.payload_len1 == 127);
: void {
self.payload_len = self.payload_len1;
if ( self?.payload_len2 )
if (self?.payload_len2)
self.payload_len = self.payload_len2;
else if ( self?.payload_len8 )
else if (self?.payload_len8)
self.payload_len = self.payload_len8;
}
# This being an uint8[] allows masking_key[x] indexing, while a bytes
# object would require *masking_key.at(i) which took roughly 20% more
# runtime when I tested it.
masking_key: uint8[] &size=4 if (self.mask);
masking_key: uint8[] &size=4 if(self.mask);
chunk: bytes &size=self.payload_len &chunked {
# Don't use &convert with &chunked: https://github.com/zeek/spicy/issues/1661
if ( self.mask ) {
if (self.mask) {
self.chunk = fast_unmask(self.masking_key_idx, self.masking_key, $$);
self.masking_key_idx += |$$|;
} else {
@ -67,12 +63,12 @@ type Frame = unit(m: Message) {
}
# Forward TEXT and BINARY data to dowstream analyzers.
if ( self.effective_opcode == OPCODE_TEXT || self.effective_opcode == OPCODE_BINARY )
if (self.effective_opcode == OPCODE_TEXT || self.effective_opcode == OPCODE_BINARY)
zeek::protocol_data_in(zeek::is_orig(), $$);
# Accumulate the unmasked data in close_data if this a close frame
# so it can be parsed by the outer Message. It's a bit of a hack.
if ( self.effective_opcode == OPCODE_CLOSE )
if (self.effective_opcode == OPCODE_CLOSE)
self.close_data += $$;
}
};
@ -82,14 +78,13 @@ type CloseFrame = unit {
var reason: bytes;
: bytes &eod {
if ( |$$| > 0 ) {
if (|$$| > 0) {
self.status = cast<uint16>($$.sub(0, 2).to_uint(spicy::ByteOrder::Network));
self.reason = $$.sub(2, 0);
}
}
};
public type Message = unit {
# transient trickery
var done: bool = False;
@ -103,11 +98,11 @@ public type Message = unit {
self.done = $$.fin;
}
: Frame(self)[] &until=(self.done) if (!self.done) foreach {
: Frame(self)[] &until=(self.done) if(!self.done) foreach {
self.done = $$.fin;
}
: CloseFrame &parse-from=self.first_frame.close_data if (self.opcode == OPCODE_CLOSE);
: CloseFrame &parse-from=self.first_frame.close_data if(self.opcode == OPCODE_CLOSE);
on %done {
spicy::accept_input();