Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quic ietf 4967 v3 #7095

Closed
wants to merge 9 commits into from
Closed

Conversation

catenacyber
Copy link
Contributor

Link to redmine ticket:
https://redmine.openinfosecfoundation.org/issues/4967
https://redmine.openinfosecfoundation.org/issues/5143

Describe changes:

  • Parses standardized QUIC IETF v1
  • bump up rust carte tls_parser already used by rep, so that it can be used by quic
  • Rustfmt previous code

suricata-verify-pr: 775
OISF/suricata-verify#775

Replaces #7082 with

  • default features false for added crate rustls
  • detection for sni
  • detection for ja3 string

Still to do :

Ticket: 4967

The format of initial packet for quic ietf, ie quic v1,
is described in rfc 9000, section 17.2.2
so that we can use new functions in quic parser
and logs interesting extensions from crypto frame
As it can be 4, but it can also be 1, based on the first
decrypted byte
The way to determine if the payload is encrypted is by storing
in the state if we have seen a crypto frame in both directions...
So as to keep parse not too big
@catenacyber catenacyber mentioned this pull request Mar 2, 2022
@codecov
Copy link

codecov bot commented Mar 2, 2022

Codecov Report

Merging #7095 (065f1ae) into master (ec01a94) will decrease coverage by 0.14%.
The diff coverage is 90.69%.

@@            Coverage Diff             @@
##           master    #7095      +/-   ##
==========================================
- Coverage   75.54%   75.39%   -0.15%     
==========================================
  Files         628      629       +1     
  Lines      185325   185363      +38     
==========================================
- Hits       139996   139761     -235     
- Misses      45329    45602     +273     
Flag Coverage Δ
fuzzcorpus 54.38% <38.46%> (-0.34%) ⬇️
suricata-verify 54.58% <85.71%> (-0.03%) ⬇️
unittests 63.17% <69.04%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

@suricata-qa
Copy link

WARNING:

field test baseline %
tlpw1_stats_chk
.app_layer.tx.quic 2592 1140 227.37%
.app_layer.error.quic.parser 3768 1810 208.18%
tlpr1_stats_chk
.app_layer.tx.quic 13818 4198 329.16%
.app_layer.error.quic.parser 1416606 43951 3223.15%

Pipeline 6451

pub(crate) struct Crypto {
pub ciphers: Vec<TlsCipherSuiteID>,
pub extv: Vec<QuicTlsExtension>,
//does not work, because of lifetime due to references to the slice used for parsing
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this comment move to extv just above and explain why it is like this instead of using Vec<TlsExtension>? Comments about why code that is in use are like they are, are more useful than comments explaining commented out things, I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Copy link
Member

@victorjulien victorjulien left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments, mostly code tidiness suggestions. Will also give this a run on my own quic traffic.

_ => {}
}
let mut values = Vec::new();
match e {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be broken out into a util func?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasonish is there a better rustier way to build a string and write integer into it ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could do

let mut s = String::new();
s.push_str(&my_u16.to_string());

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you Jason, looks better indeed

}

// get interesting stuff out of parsed tls extensions
fn quic_get_tls_extensions(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this func be done in a more compact way?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more compact with Jason advice

}
extv.push(QuicTlsExtension { etype, values })
}
if client {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps move this into a client specific util func

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

let mut dash = false;
for c in &ch.ciphers {
if dash {
match write!(&mut ja3, "-") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these kinds of statements are taking 3 lines each for no good reason. Can these be made more compact?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let _ = write...

(noted above already)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

push_str seems nice

if framebuf.len() < 4 + hkey.sample_len() {
return false;
}
let mut h2 = Vec::with_capacity(hlen + header.length);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the max size we might alloc here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not more than the UDP payload size...
Should it be more obvious or debug tested ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I was trying to figure out where it is set and the logic is quite hard to follow but ultimately seems to come from quic_var_uint, so it might be higher if not properly checked?

If it shouldn't be > UDP payload size, could it be u16?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It comes indeed from quic_var_uint
But then, we have if header.length > rest.len() {
before `let (mut framebuf, next_buf) = rest.split_at(header.length);

Do you want the check to happen earlier ?

framebuf = &output;
}
buf = next_buf;
match QuicData::from_bytes(framebuf) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this func be broken up, for example handling the following in a until func? Not only is the func large, the indent levels are also getting hard to track

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

@victorjulien
Copy link
Member

Should the ja3 string here end in a comma?

{
  "version": "1",
  "sni": "mask.icloud.com",
  "ja3": "771,51914-4865-4866-4867,64250-0-10-16-5-13-18-51-45-43-57-27-20-64250,6682-29-23-24-25,",
  "extensions": [
    {
      "type": "Grease"
    },
    {
      "type": "ServerName",
      "values": [
        "mask.icloud.com"
      ]
    },
    {
      "type": "SupportedGroups"
    },
    {
      "type": "ApplicationLayerProtocolNegotiation",
      "values": [
        "h3"
      ]
    },
    {
      "type": "StatusRequest"
    },
    {
      "type": "SignatureAlgorithms"
    },
    {
      "type": "SignedCertificateTimestamp"
    },
    {
      "type": "KeyShare"
    },
    {
      "type": "PskExchangeModes"
    },
    {
      "type": "SupportedVersions"
    },
    {
      "type": "TlsExtensionType(57 / 0x39)"
    },
    {
      "type": "TlsExtensionType(27 / 0x1b)"
    },
    {
      "type": "ServerCertificateType"
    },
    {
      "type": "Grease"
    }
  ]
}

@victorjulien
Copy link
Member

@jasonish any thoughts on the logging here (see example above). I find the extentions rather verbose, but can't immediately come up with an alternative.

Additionally I wonder how to log the unknown extensions. If we log them this way and then later add support for them its in a way a breaking output change? :)

@catenacyber
Copy link
Contributor Author

Should the ja3 string here end in a comma?

Yes it is
I used https://infosecwriteups.com/demystifying-ja3-one-handshake-at-a-time-c80b04ccb393 to learn about JA3
It uses extension EllipticCurvePointFormat which got obsolete in TLS1.3

My understanding is that every ja3 should contain 4 commas to distinguish the 5 different fields

Comment on lines +198 to +200
match write!(ja3, "{}", u16::from(etype)) {
_ => {}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idiomatic way to avoid the result warning is:

let _ = write!(...

Comment on lines +317 to +320
if state.parse(buf, false) {
return AppLayerResult::ok();
}
return AppLayerResult::err();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really tiny nit.. Idiomatic Rust would be:

if state.parse(buf, false) {
    AppLayerResult::ok()
} else {
    AppLayerResult::err()
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I did not know Rust was in the explicit else side

@jasonish
Copy link
Member

jasonish commented Mar 3, 2022

@jasonish any thoughts on the logging here (see example above). I find the extentions rather verbose, but can't immediately come up with an alternative.

I think it looks fine as I can't really see another way. Would we want to log all the extensions by default? Or should we have an extended mode that logs extensions which is off by default.

Additionally I wonder how to log the unknown extensions. If we log them this way and then later add support for them its in a way a breaking output change? :)

Maybe something like:

"extensions": [
    {
      "type": 0,
      "name": "ServerName",
      "values": [
        "mask.icloud.com"
      ]
    },
    {
      "type": 99
    },

type is the only required field.

I also noticed that we're using names like ServerName, while Wireshark uses server_name. Whats the most common here?

@catenacyber
Copy link
Contributor Author

I also noticed that we're using names like ServerName, while Wireshark uses server_name. Whats the most common here?

In the RFC https://datatracker.ietf.org/doc/html/rfc3546
server_name is the enum value and ServerName the name of the structure...

@catenacyber catenacyber mentioned this pull request Mar 3, 2022
@catenacyber
Copy link
Contributor Author

Replaced by #7101

@catenacyber catenacyber closed this Mar 3, 2022
@jasonish
Copy link
Member

jasonish commented Mar 3, 2022

In the RFC https://datatracker.ietf.org/doc/html/rfc3546
server_name is the enum value and ServerName the name of the structure...

I think using a name of server_name is more consistent with the RFC.

@victorjulien
Copy link
Member

In the RFC https://datatracker.ietf.org/doc/html/rfc3546
server_name is the enum value and ServerName the name of the structure...

I think using a name of server_name is more consistent with the RFC.

Wonder if we should also make things more compact, e.g. instead of ApplicationLayerProtocolNegotiation use ALPN

@catenacyber
Copy link
Contributor Author

These wordings come from crate tls-parser...
Should Suricata define its own wording ?

@jasonish
Copy link
Member

jasonish commented Mar 4, 2022

Should Suricata define its own wording ?

I'm not opposed to it. An upstream library could change its naming which would affect us resulting in a breaking change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants