tls_client_hello_parser/
lib.rs

1use std::io::{Error as IoError, ErrorKind as IoErrorKind, Read};
2
3use rustls::{
4    internal::msgs::{
5        deframer::MessageDeframer,
6        handshake::{ClientHelloPayload as ClientHelloPayloadInner, HandshakePayload},
7        message::{Message, MessagePayload},
8    },
9    ContentType, Error as RustlsError, HandshakeType,
10};
11
12//
13//
14//
15pub mod client_hello;
16pub use client_hello::ClientHello;
17
18//
19//
20//
21pub struct Parser {
22    message_deframer: MessageDeframer,
23}
24
25impl Default for Parser {
26    fn default() -> Self {
27        Self::new()
28    }
29}
30
31impl Parser {
32    pub fn new() -> Self {
33        Self {
34            message_deframer: MessageDeframer::new(),
35        }
36    }
37
38    pub fn parse(&mut self, rd: &mut dyn Read) -> Result<Option<ClientHelloPayload>, ParseError> {
39        let n = self
40            .message_deframer
41            .read(rd)
42            .map_err(ParseError::IoError)?;
43
44        match self
45            .message_deframer
46            .pop()
47            .map_err(ParseError::RustlsError)?
48        {
49            Some(opaque_message) => {
50                let plain_message = opaque_message.into_plain_message();
51                let plain_message_typ = plain_message.typ.to_owned();
52                let message = Message::try_from(plain_message).map_err(ParseError::RustlsError)?;
53                match &message.payload {
54                    MessagePayload::Handshake { parsed, encoded: _ } => match &parsed.payload {
55                        HandshakePayload::ClientHello(chp) => {
56                            let inner = ClientHelloPayloadInner {
57                                client_version: chp.client_version,
58                                random: chp.random.to_owned(),
59                                session_id: chp.session_id,
60                                cipher_suites: chp.cipher_suites.to_owned(),
61                                compression_methods: chp.compression_methods.to_owned(),
62                                extensions: chp.extensions.to_owned(),
63                            };
64                            Ok(Some(ClientHelloPayload(inner)))
65                        }
66                        _ => Err(ParseError::RustlsError(
67                            RustlsError::InappropriateHandshakeMessage {
68                                expect_types: vec![HandshakeType::ClientHello],
69                                got_type: parsed.typ,
70                            },
71                        )),
72                    },
73                    _ => Err(ParseError::RustlsError(RustlsError::InappropriateMessage {
74                        expect_types: vec![ContentType::Handshake],
75                        got_type: plain_message_typ,
76                    })),
77                }
78            }
79            None => {
80                if n == 0 {
81                    Err(ParseError::IoError(IoErrorKind::UnexpectedEof.into()))
82                } else {
83                    Ok(None)
84                }
85            }
86        }
87    }
88}
89
90#[derive(Debug)]
91pub enum ParseError {
92    IoError(IoError),
93    RustlsError(RustlsError),
94}
95impl core::fmt::Display for ParseError {
96    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
97        write!(f, "{self:?}")
98    }
99}
100impl std::error::Error for ParseError {}
101
102//
103//
104//
105#[derive(Debug)]
106pub struct ClientHelloPayload(pub ClientHelloPayloadInner);
107
108impl core::ops::Deref for ClientHelloPayload {
109    type Target = ClientHelloPayloadInner;
110
111    fn deref(&self) -> &Self::Target {
112        &self.0
113    }
114}
115impl core::ops::DerefMut for ClientHelloPayload {
116    fn deref_mut(&mut self) -> &mut Self::Target {
117        &mut self.0
118    }
119}
120
121impl ClientHelloPayload {
122    pub fn client_hello(&self) -> Result<ClientHello<'_>, RustlsError> {
123        use rustls::internal::msgs::handshake::ConvertServerNameList as _;
124
125        Ok(ClientHello::new(
126            self.get_sni_extension()
127                .and_then(|x| x.get_single_hostname())
128                .map(|x| x.to_owned()),
129            self.get_sigalgs_extension().ok_or_else(|| {
130                // https://github.com/rustls/rustls/blob/v/0.20.8/rustls/src/server/hs.rs#L512
131                RustlsError::PeerIncompatibleError(
132                    "client didn't describe signature schemes".into(),
133                )
134            })?,
135            self.get_alpn_extension(),
136            self.cipher_suites.as_ref(),
137        ))
138    }
139}