xref: /aosp_15_r20/external/scapy/scapy/layers/tls/automaton.py (revision 7dc08ffc4802948ccbc861daaf1e81c405c2c4bd)
1*7dc08ffcSJunyu Lai## This file is part of Scapy
2*7dc08ffcSJunyu Lai## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard
3*7dc08ffcSJunyu Lai##               2015, 2016, 2017 Maxence Tury
4*7dc08ffcSJunyu Lai## This program is published under a GPLv2 license
5*7dc08ffcSJunyu Lai
6*7dc08ffcSJunyu Lai"""
7*7dc08ffcSJunyu LaiThe _TLSAutomaton class provides methods common to both TLS client and server.
8*7dc08ffcSJunyu Lai"""
9*7dc08ffcSJunyu Lai
10*7dc08ffcSJunyu Laiimport struct
11*7dc08ffcSJunyu Lai
12*7dc08ffcSJunyu Laifrom scapy.automaton import Automaton
13*7dc08ffcSJunyu Laifrom scapy.error import log_interactive
14*7dc08ffcSJunyu Laifrom scapy.packet import Raw
15*7dc08ffcSJunyu Laifrom scapy.layers.tls.basefields import _tls_type
16*7dc08ffcSJunyu Laifrom scapy.layers.tls.cert import Cert, PrivKey
17*7dc08ffcSJunyu Laifrom scapy.layers.tls.record import TLS
18*7dc08ffcSJunyu Laifrom scapy.layers.tls.record_sslv2 import SSLv2
19*7dc08ffcSJunyu Laifrom scapy.layers.tls.record_tls13 import TLS13
20*7dc08ffcSJunyu Lai
21*7dc08ffcSJunyu Lai
22*7dc08ffcSJunyu Laiclass _TLSAutomaton(Automaton):
23*7dc08ffcSJunyu Lai    """
24*7dc08ffcSJunyu Lai    SSLv3 and TLS 1.0-1.2 typically need a 2-RTT handshake:
25*7dc08ffcSJunyu Lai
26*7dc08ffcSJunyu Lai    Client        Server
27*7dc08ffcSJunyu Lai      | --------->>> |    C1 - ClientHello
28*7dc08ffcSJunyu Lai      | <<<--------- |    S1 - ServerHello
29*7dc08ffcSJunyu Lai      | <<<--------- |    S1 - Certificate
30*7dc08ffcSJunyu Lai      | <<<--------- |    S1 - ServerKeyExchange
31*7dc08ffcSJunyu Lai      | <<<--------- |    S1 - ServerHelloDone
32*7dc08ffcSJunyu Lai      | --------->>> |    C2 - ClientKeyExchange
33*7dc08ffcSJunyu Lai      | --------->>> |    C2 - ChangeCipherSpec
34*7dc08ffcSJunyu Lai      | --------->>> |    C2 - Finished [encrypted]
35*7dc08ffcSJunyu Lai      | <<<--------- |    S2 - ChangeCipherSpec
36*7dc08ffcSJunyu Lai      | <<<--------- |    S2 - Finished [encrypted]
37*7dc08ffcSJunyu Lai
38*7dc08ffcSJunyu Lai    We call these successive groups of messages:
39*7dc08ffcSJunyu Lai    ClientFlight1, ServerFlight1, ClientFlight2 and ServerFlight2.
40*7dc08ffcSJunyu Lai
41*7dc08ffcSJunyu Lai    We want to send our messages from the same flight all at once through the
42*7dc08ffcSJunyu Lai    socket. This is achieved by managing a list of records in 'buffer_out'.
43*7dc08ffcSJunyu Lai    We may put several messages (i.e. what RFC 5246 calls the record fragments)
44*7dc08ffcSJunyu Lai    in the same record when possible, but we may need several records for the
45*7dc08ffcSJunyu Lai    same flight, as with ClientFlight2.
46*7dc08ffcSJunyu Lai
47*7dc08ffcSJunyu Lai    However, note that the flights from the opposite side may be spread wildly
48*7dc08ffcSJunyu Lai    accross TLS records and TCP packets. This is why we use a 'get_next_msg'
49*7dc08ffcSJunyu Lai    method for feeding a list of received messages, 'buffer_in'. Raw data
50*7dc08ffcSJunyu Lai    which has not yet been interpreted as a TLS record is kept in 'remain_in'.
51*7dc08ffcSJunyu Lai    """
52*7dc08ffcSJunyu Lai    def parse_args(self, mycert=None, mykey=None, **kargs):
53*7dc08ffcSJunyu Lai
54*7dc08ffcSJunyu Lai        super(_TLSAutomaton, self).parse_args(**kargs)
55*7dc08ffcSJunyu Lai
56*7dc08ffcSJunyu Lai        self.socket = None
57*7dc08ffcSJunyu Lai        self.remain_in = b""
58*7dc08ffcSJunyu Lai        self.buffer_in = []         # these are 'fragments' inside records
59*7dc08ffcSJunyu Lai        self.buffer_out = []        # these are records
60*7dc08ffcSJunyu Lai
61*7dc08ffcSJunyu Lai        self.cur_session = None
62*7dc08ffcSJunyu Lai        self.cur_pkt = None         # this is usually the latest parsed packet
63*7dc08ffcSJunyu Lai
64*7dc08ffcSJunyu Lai        if mycert:
65*7dc08ffcSJunyu Lai            self.mycert = Cert(mycert)
66*7dc08ffcSJunyu Lai        else:
67*7dc08ffcSJunyu Lai            self.mycert = None
68*7dc08ffcSJunyu Lai
69*7dc08ffcSJunyu Lai        if mykey:
70*7dc08ffcSJunyu Lai            self.mykey = PrivKey(mykey)
71*7dc08ffcSJunyu Lai        else:
72*7dc08ffcSJunyu Lai            self.mykey = None
73*7dc08ffcSJunyu Lai
74*7dc08ffcSJunyu Lai        self.verbose = kargs.get("verbose", True)
75*7dc08ffcSJunyu Lai
76*7dc08ffcSJunyu Lai
77*7dc08ffcSJunyu Lai    def get_next_msg(self, socket_timeout=2, retry=2):
78*7dc08ffcSJunyu Lai        """
79*7dc08ffcSJunyu Lai        The purpose of the function is to make next message(s) available in
80*7dc08ffcSJunyu Lai        self.buffer_in. If the list is not empty, nothing is done. If not, in
81*7dc08ffcSJunyu Lai        order to fill it, the function uses the data already available in
82*7dc08ffcSJunyu Lai        self.remain_in from a previous call and waits till there are enough to
83*7dc08ffcSJunyu Lai        dissect a TLS packet. Once dissected, the content of the TLS packet
84*7dc08ffcSJunyu Lai        (carried messages, or 'fragments') is appended to self.buffer_in.
85*7dc08ffcSJunyu Lai
86*7dc08ffcSJunyu Lai        We have to grab enough data to dissect a TLS packet. We start by
87*7dc08ffcSJunyu Lai        reading the first 2 bytes. Unless we get anything different from
88*7dc08ffcSJunyu Lai        \\x14\\x03, \\x15\\x03, \\x16\\x03 or \\x17\\x03 (which might indicate
89*7dc08ffcSJunyu Lai        an SSLv2 record, whose first 2 bytes encode the length), we retrieve
90*7dc08ffcSJunyu Lai        3 more bytes in order to get the length of the TLS record, and
91*7dc08ffcSJunyu Lai        finally we can retrieve the remaining of the record.
92*7dc08ffcSJunyu Lai        """
93*7dc08ffcSJunyu Lai        if self.buffer_in:
94*7dc08ffcSJunyu Lai            # A message is already available.
95*7dc08ffcSJunyu Lai            return
96*7dc08ffcSJunyu Lai
97*7dc08ffcSJunyu Lai        self.socket.settimeout(socket_timeout)
98*7dc08ffcSJunyu Lai        is_sslv2_msg = False
99*7dc08ffcSJunyu Lai        still_getting_len = True
100*7dc08ffcSJunyu Lai        grablen = 2
101*7dc08ffcSJunyu Lai        while retry and (still_getting_len or len(self.remain_in) < grablen):
102*7dc08ffcSJunyu Lai            if not is_sslv2_msg and grablen == 5 and len(self.remain_in) >= 5:
103*7dc08ffcSJunyu Lai                grablen = struct.unpack('!H', self.remain_in[3:5])[0] + 5
104*7dc08ffcSJunyu Lai                still_getting_len = False
105*7dc08ffcSJunyu Lai            elif grablen == 2 and len(self.remain_in) >= 2:
106*7dc08ffcSJunyu Lai                byte0 = struct.unpack("B", self.remain_in[:1])[0]
107*7dc08ffcSJunyu Lai                byte1 = struct.unpack("B", self.remain_in[1:2])[0]
108*7dc08ffcSJunyu Lai                if (byte0 in _tls_type) and (byte1 == 3):
109*7dc08ffcSJunyu Lai                    # Retry following TLS scheme. This will cause failure
110*7dc08ffcSJunyu Lai                    # for SSLv2 packets with length 0x1{4-7}03.
111*7dc08ffcSJunyu Lai                    grablen = 5
112*7dc08ffcSJunyu Lai                else:
113*7dc08ffcSJunyu Lai                    # Extract the SSLv2 length.
114*7dc08ffcSJunyu Lai                    is_sslv2_msg = True
115*7dc08ffcSJunyu Lai                    still_getting_len = False
116*7dc08ffcSJunyu Lai                    if byte0 & 0x80:
117*7dc08ffcSJunyu Lai                        grablen = 2 + 0 + ((byte0 & 0x7f) << 8) + byte1
118*7dc08ffcSJunyu Lai                    else:
119*7dc08ffcSJunyu Lai                        grablen = 2 + 1 + ((byte0 & 0x3f) << 8) + byte1
120*7dc08ffcSJunyu Lai            elif not is_sslv2_msg and grablen == 5 and len(self.remain_in) >= 5:
121*7dc08ffcSJunyu Lai                grablen = struct.unpack('!H', self.remain_in[3:5])[0] + 5
122*7dc08ffcSJunyu Lai
123*7dc08ffcSJunyu Lai            if grablen == len(self.remain_in):
124*7dc08ffcSJunyu Lai                break
125*7dc08ffcSJunyu Lai
126*7dc08ffcSJunyu Lai            try:
127*7dc08ffcSJunyu Lai                tmp = self.socket.recv(grablen - len(self.remain_in))
128*7dc08ffcSJunyu Lai                if not tmp:
129*7dc08ffcSJunyu Lai                    retry -= 1
130*7dc08ffcSJunyu Lai                else:
131*7dc08ffcSJunyu Lai                    self.remain_in += tmp
132*7dc08ffcSJunyu Lai            except:
133*7dc08ffcSJunyu Lai                self.vprint("Could not join host ! Retrying...")
134*7dc08ffcSJunyu Lai                retry -= 1
135*7dc08ffcSJunyu Lai
136*7dc08ffcSJunyu Lai        if len(self.remain_in) < 2 or len(self.remain_in) != grablen:
137*7dc08ffcSJunyu Lai            # Remote peer is not willing to respond
138*7dc08ffcSJunyu Lai            return
139*7dc08ffcSJunyu Lai
140*7dc08ffcSJunyu Lai        p = TLS(self.remain_in, tls_session=self.cur_session)
141*7dc08ffcSJunyu Lai        self.cur_session = p.tls_session
142*7dc08ffcSJunyu Lai        self.remain_in = b""
143*7dc08ffcSJunyu Lai        if isinstance(p, SSLv2) and not p.msg:
144*7dc08ffcSJunyu Lai            p.msg = Raw("")
145*7dc08ffcSJunyu Lai        if self.cur_session.tls_version is None or \
146*7dc08ffcSJunyu Lai           self.cur_session.tls_version < 0x0304:
147*7dc08ffcSJunyu Lai            self.buffer_in += p.msg
148*7dc08ffcSJunyu Lai        else:
149*7dc08ffcSJunyu Lai            if isinstance(p, TLS13):
150*7dc08ffcSJunyu Lai                self.buffer_in += p.inner.msg
151*7dc08ffcSJunyu Lai            else:
152*7dc08ffcSJunyu Lai                # should be TLS13ServerHello only
153*7dc08ffcSJunyu Lai                self.buffer_in += p.msg
154*7dc08ffcSJunyu Lai
155*7dc08ffcSJunyu Lai        while p.payload:
156*7dc08ffcSJunyu Lai            if isinstance(p.payload, Raw):
157*7dc08ffcSJunyu Lai                self.remain_in += p.payload.load
158*7dc08ffcSJunyu Lai                p = p.payload
159*7dc08ffcSJunyu Lai            elif isinstance(p.payload, TLS):
160*7dc08ffcSJunyu Lai                p = p.payload
161*7dc08ffcSJunyu Lai                if self.cur_session.tls_version is None or \
162*7dc08ffcSJunyu Lai                   self.cur_session.tls_version < 0x0304:
163*7dc08ffcSJunyu Lai                    self.buffer_in += p.msg
164*7dc08ffcSJunyu Lai                else:
165*7dc08ffcSJunyu Lai                    self.buffer_in += p.inner.msg
166*7dc08ffcSJunyu Lai
167*7dc08ffcSJunyu Lai    def raise_on_packet(self, pkt_cls, state, get_next_msg=True):
168*7dc08ffcSJunyu Lai        """
169*7dc08ffcSJunyu Lai        If the next message to be processed has type 'pkt_cls', raise 'state'.
170*7dc08ffcSJunyu Lai        If there is no message waiting to be processed, we try to get one with
171*7dc08ffcSJunyu Lai        the default 'get_next_msg' parameters.
172*7dc08ffcSJunyu Lai        """
173*7dc08ffcSJunyu Lai        # Maybe we already parsed the expected packet, maybe not.
174*7dc08ffcSJunyu Lai        if get_next_msg:
175*7dc08ffcSJunyu Lai            self.get_next_msg()
176*7dc08ffcSJunyu Lai        if (not self.buffer_in or
177*7dc08ffcSJunyu Lai            not isinstance(self.buffer_in[0], pkt_cls)):
178*7dc08ffcSJunyu Lai            return
179*7dc08ffcSJunyu Lai        self.cur_pkt = self.buffer_in[0]
180*7dc08ffcSJunyu Lai        self.buffer_in = self.buffer_in[1:]
181*7dc08ffcSJunyu Lai        raise state()
182*7dc08ffcSJunyu Lai
183*7dc08ffcSJunyu Lai    def add_record(self, is_sslv2=None, is_tls13=None):
184*7dc08ffcSJunyu Lai        """
185*7dc08ffcSJunyu Lai        Add a new TLS or SSLv2 or TLS 1.3 record to the packets buffered out.
186*7dc08ffcSJunyu Lai        """
187*7dc08ffcSJunyu Lai        if is_sslv2 is None and is_tls13 is None:
188*7dc08ffcSJunyu Lai            v = (self.cur_session.tls_version or
189*7dc08ffcSJunyu Lai                 self.cur_session.advertised_tls_version)
190*7dc08ffcSJunyu Lai            if v in [0x0200, 0x0002]:
191*7dc08ffcSJunyu Lai                is_sslv2 = True
192*7dc08ffcSJunyu Lai            elif v >= 0x0304:
193*7dc08ffcSJunyu Lai                is_tls13 = True
194*7dc08ffcSJunyu Lai        if is_sslv2:
195*7dc08ffcSJunyu Lai            self.buffer_out.append(SSLv2(tls_session=self.cur_session))
196*7dc08ffcSJunyu Lai        elif is_tls13:
197*7dc08ffcSJunyu Lai            self.buffer_out.append(TLS13(tls_session=self.cur_session))
198*7dc08ffcSJunyu Lai        else:
199*7dc08ffcSJunyu Lai            self.buffer_out.append(TLS(tls_session=self.cur_session))
200*7dc08ffcSJunyu Lai
201*7dc08ffcSJunyu Lai    def add_msg(self, pkt):
202*7dc08ffcSJunyu Lai        """
203*7dc08ffcSJunyu Lai        Add a TLS message (e.g. TLSClientHello or TLSApplicationData)
204*7dc08ffcSJunyu Lai        inside the latest record to be sent through the socket.
205*7dc08ffcSJunyu Lai        We believe a good automaton should not use the first test.
206*7dc08ffcSJunyu Lai        """
207*7dc08ffcSJunyu Lai        if not self.buffer_out:
208*7dc08ffcSJunyu Lai            self.add_record()
209*7dc08ffcSJunyu Lai        r = self.buffer_out[-1]
210*7dc08ffcSJunyu Lai        if isinstance(r, TLS13):
211*7dc08ffcSJunyu Lai            self.buffer_out[-1].inner.msg.append(pkt)
212*7dc08ffcSJunyu Lai        else:
213*7dc08ffcSJunyu Lai            self.buffer_out[-1].msg.append(pkt)
214*7dc08ffcSJunyu Lai
215*7dc08ffcSJunyu Lai    def flush_records(self):
216*7dc08ffcSJunyu Lai        """
217*7dc08ffcSJunyu Lai        Send all buffered records and update the session accordingly.
218*7dc08ffcSJunyu Lai        """
219*7dc08ffcSJunyu Lai        s = b"".join(p.raw_stateful() for p in self.buffer_out)
220*7dc08ffcSJunyu Lai        self.socket.send(s)
221*7dc08ffcSJunyu Lai        self.buffer_out = []
222*7dc08ffcSJunyu Lai
223*7dc08ffcSJunyu Lai    def vprint(self, s=""):
224*7dc08ffcSJunyu Lai        if self.verbose:
225*7dc08ffcSJunyu Lai            log_interactive.info("> %s", s)
226*7dc08ffcSJunyu Lai
227