Crates.io | tls-parser |
lib.rs | tls-parser |
version | 0.12.2 |
source | src |
created_at | 2016-10-16 12:13:00.936015 |
updated_at | 2024-09-09 11:57:04.477605 |
description | Parser for the TLS protocol |
homepage | https://github.com/rusticata/tls-parser |
repository | https://github.com/rusticata/tls-parser.git |
max_upload_size | |
id | 6872 |
size | 343,760 |
A TLS parser, implemented with the nom parser combinator framework.
The goal of this parser is to implement TLS messages analysis, for example to use rules from a network IDS, for ex during the TLS handshake.
It implements structures and parsing functions for records and messages, but need additional code to handle fragmentation, or to fully inspect messages. Parsing some TLS messages requires to know the previously selected parameters. See the rusticata TLS parser for a full example.
It is written in pure Rust, fast, and makes extensive use of zero-copy. A lot of care is taken to ensure security and safety of this crate, including design (recursion limit, defensive programming), tests, and fuzzing. It also aims to be panic-free.
The code is available on Github and is part of the Rusticata project.
The main parsing functions are located in the tls.rs file. The entry functions are:
parse_tls_plaintext
: parses a record as plaintextparse_tls_encrypted
: read an encrypted record. The parser has no crypto or decryption features, so the content
will be left as opaque data.use tls_parser::parse_tls_plaintext;
use tls_parser::nom::{Err, IResult};
let bytes : &[u8]= include_bytes!("../assets/client_hello_dhe.bin");
// [ 0x16, 0x03, 0x01 ... ];
let res = parse_tls_plaintext(&bytes);
match res {
Ok((rem,record)) => {
// rem is the remaining data (not parsed)
// record is an object of type TlsRecord
},
Err(Err::Incomplete(needed)) => {
eprintln!("Defragmentation required (TLS record)");
},
Err(e) => { eprintln!("parse_tls_record_with_header failed: {:?}",e); }
}
Note that knowing if a record is plaintext or not is the responsibility of the caller.
As reading TLS records may imply defragmenting records, some functions are provided to only read the record as opaque data (which ensures the record is complete and gives the record header) and then reading messages from data.
Here is an example of two-steps parsing:
// [ 0x16, 0x03, 0x01 ... ];
match parse_tls_raw_record(bytes) {
Ok((rem, ref r)) => {
match parse_tls_record_with_header(r.data, &r.hdr) {
Ok((rem2,ref msg_list)) => {
for msg in msg_list {
// msg has type TlsMessage
}
}
Err(Err::Incomplete(needed)) => { eprintln!("incomplete record") }
Err(_) => { eprintln!("error while parsing record") }
}
}
Err(Err::Incomplete(needed)) => { eprintln!("incomplete record header") }
Err(_) => { eprintln!("error while parsing record header") }
}
Some additional work is required if reading packets from the network, to support reassembly of TCP segments and reassembly of TLS records.
For a complete example of a TLS parser supporting defragmentation and states, see the rusticata/src/tls.rs file of the rusticata crate.
A TLS state machine is provided in tls_states.rs. The state machine is separated from the parsing functions, and is almost independent. It is implemented as a table of transitions, mainly for the handshake phase.
After reading a TLS message using the previous functions, the TLS state can be
updated using the tls_state_transition
function. If the transition succeeds,
it returns Ok(new_state)
, otherwise it returns Err(error_state)
.
struct ParseContext {
state: TlsState,
}
match tls_state_transition(ctx.state, msg, to_server) {
Ok(s) => { ctx.state = s; Ok(()) }
Err(_) => {
ctx.state = TlsState::Invalid;
Err("Invalid state")
}
}
When parsing messages, if a field is an integer corresponding to an enum of known values, it is not parsed as an enum type, but as an integer. While this complicates accesses, it allows to read invalid values and continue parsing (for an IDS, it's better to read values than to get a generic parse error).
parse_content_and_signature
, compiler infers a wrong lifetime
when elided.rand_time
from ClientHello
/ServerHello
(this is deprecated since a long time, and makes
getting the entire random value difficult)
To access sub elements, use the rand_time()
and rand_bytes()
methodsThanks: Daniel McCarney, Lucas Kent, Andrew Finn, Adrien Guinet, Martin Algesten, Benoit Lemarchand
ClientHello
trait for common CH attributesClientHello
TLS_EMPTY_RENEGOTIATION_INFO_SCSV
and TLS_FALLBACK_SCSV
(#16)no_std
Thanks: @JackLiar, @xonatius
Here is a non-exhaustive list of RFCs this parser is based on:
No, it's not implemented
No. Note that most TLS implementations disabled it after the FREAK attack, so
while detecting compression in ServerHello
is possible in tls-parser, it
should probably be interpreted as an alert.
They are built when running cargo build
.
To ease updating the list from the IANA TLS parameters, a script is provided (scripts/extract-iana-ciphers.py). This script will download and pre-parse the list from IANA, and produce a file containing all ciphersuites names and parameters.
During the build, build.rs parses this file and produces a static, read-only hash table of all known ciphers and their properties.
Licensed under either of
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.