• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# SPDX-License-Identifier: GPL-2.0-only
2# This file is part of Scapy
3# See https://scapy.net/ for more information
4# Copyright (C) 2017 Maxence Tury
5
6"""
7SSLv2 Record.
8"""
9
10import struct
11
12from scapy.config import conf
13from scapy.error import log_runtime
14from scapy.compat import orb, raw
15from scapy.packet import Raw
16from scapy.layers.tls.session import _GenericTLSSessionInheritance
17from scapy.layers.tls.record import _TLSMsgListField, TLS
18from scapy.layers.tls.handshake_sslv2 import _sslv2_handshake_cls
19from scapy.layers.tls.basefields import (_SSLv2LengthField, _SSLv2PadField,
20                                         _SSLv2PadLenField, _TLSMACField)
21
22
23###############################################################################
24#   SSLv2 Record Protocol                                                     #
25###############################################################################
26
27class _SSLv2MsgListField(_TLSMsgListField):
28    def __init__(self, name, default, length_from=None):
29        if not length_from:
30            length_from = lambda pkt: ((pkt.len & 0x7fff) -
31                                       (pkt.padlen or 0) -
32                                       len(pkt.mac))
33        super(_SSLv2MsgListField, self).__init__(name, default,
34                                                 length_from=length_from)
35
36    def m2i(self, pkt, m):
37        cls = Raw
38        if len(m) >= 1:
39            msgtype = orb(m[0])
40            cls = _sslv2_handshake_cls.get(msgtype, Raw)
41
42        if cls is Raw:
43            return Raw(m)
44        else:
45            return cls(m, tls_session=pkt.tls_session)
46
47    def i2m(self, pkt, p):
48        cur = b""
49        if isinstance(p, _GenericTLSSessionInheritance):
50            p.tls_session = pkt.tls_session
51            if not pkt.tls_session.frozen:
52                cur = p.raw_stateful()
53                p.post_build_tls_session_update(cur)
54            else:
55                cur = raw(p)
56        else:
57            cur = raw(p)
58        return cur
59
60    def addfield(self, pkt, s, val):
61        res = b""
62        for p in val:
63            res += self.i2m(pkt, p)
64        return s + res
65
66
67class SSLv2(TLS):
68    """
69    The encrypted_data is the encrypted version of mac+msg+pad.
70    """
71    __slots__ = ["with_padding", "protected_record"]
72    name = "SSLv2"
73    fields_desc = [_SSLv2LengthField("len", None),
74                   _SSLv2PadLenField("padlen", None),
75                   _TLSMACField("mac", b""),
76                   _SSLv2MsgListField("msg", []),
77                   _SSLv2PadField("pad", "")]
78
79    def __init__(self, *args, **kargs):
80        self.with_padding = kargs.get("with_padding", False)
81        self.protected_record = kargs.get("protected_record", None)
82        super(SSLv2, self).__init__(*args, **kargs)
83
84    # Parsing methods
85
86    def _sslv2_mac_verify(self, msg, mac):
87        secret = self.tls_session.rcs.cipher.key
88        if secret is None:
89            return True
90
91        mac_len = self.tls_session.rcs.mac_len
92        if mac_len == 0:            # should be TLS_NULL_WITH_NULL_NULL
93            return True
94        if len(mac) != mac_len:
95            return False
96
97        read_seq_num = struct.pack("!I", self.tls_session.rcs.seq_num)
98        alg = self.tls_session.rcs.hash
99        h = alg.digest(secret + msg + read_seq_num)
100        return h == mac
101
102    def pre_dissect(self, s):
103        if len(s) < 2:
104            raise Exception("Invalid record: header is too short.")
105
106        msglen = struct.unpack("!H", s[:2])[0]
107        if msglen & 0x8000:
108            hdrlen = 2
109            msglen_clean = msglen & 0x7fff
110        else:
111            hdrlen = 3
112            msglen_clean = msglen & 0x3fff
113
114        hdr = s[:hdrlen]
115        efrag = s[hdrlen:hdrlen + msglen_clean]
116        self.protected_record = s[:hdrlen + msglen_clean]
117        r = s[hdrlen + msglen_clean:]
118
119        mac = pad = b""
120
121        # Decrypt (with implicit IV if block cipher)
122        mfrag = self._tls_decrypt(efrag)
123
124        # Extract MAC
125        maclen = self.tls_session.rcs.mac_len
126        if maclen == 0:
127            mac, pfrag = b"", mfrag
128        else:
129            mac, pfrag = mfrag[:maclen], mfrag[maclen:]
130
131        # Extract padding
132        padlen = 0
133        if hdrlen == 3:
134            padlen = orb(s[2])
135        if padlen == 0:
136            cfrag, pad = pfrag, b""
137        else:
138            cfrag, pad = pfrag[:-padlen], pfrag[-padlen:]
139
140        # Verify integrity
141        is_mac_ok = self._sslv2_mac_verify(cfrag + pad, mac)
142        if not is_mac_ok:
143            pkt_info = self.firstlayer().summary()
144            log_runtime.info("SSLv2: record integrity check failed [%s]", pkt_info)  # noqa: E501
145
146        reconstructed_body = mac + cfrag + pad
147        return hdr + reconstructed_body + r
148
149    def post_dissect(self, s):
150        """
151        SSLv2 may force us to commit the write connState here.
152        """
153        if self.tls_session.triggered_prcs_commit:
154            if self.tls_session.prcs is not None:
155                self.tls_session.rcs = self.tls_session.prcs
156                self.tls_session.prcs = None
157            self.tls_session.triggered_prcs_commit = False
158        if self.tls_session.triggered_pwcs_commit:
159            if self.tls_session.pwcs is not None:
160                self.tls_session.wcs = self.tls_session.pwcs
161                self.tls_session.pwcs = None
162            self.tls_session.triggered_pwcs_commit = False
163
164        if self.tls_session.prcs is not None:
165            self.tls_session.prcs.seq_num += 1
166        self.tls_session.rcs.seq_num += 1
167        return s
168
169    def do_dissect_payload(self, s):
170        if s:
171            try:
172                p = SSLv2(s, _internal=1, _underlayer=self,
173                          tls_session=self.tls_session)
174            except KeyboardInterrupt:
175                raise
176            except Exception:
177                if conf.debug_dissector:
178                    raise
179                p = conf.raw_layer(s, _internal=1, _underlayer=self)
180            self.add_payload(p)
181
182    # Building methods
183
184    def _sslv2_mac_add(self, msg):
185        secret = self.tls_session.wcs.cipher.key
186        if secret is None:
187            return msg
188
189        write_seq_num = struct.pack("!I", self.tls_session.wcs.seq_num)
190        alg = self.tls_session.wcs.hash
191        h = alg.digest(secret + msg + write_seq_num)
192        return h + msg
193
194    def _sslv2_pad(self, s):
195        padding = b""
196        block_size = self.tls_session.wcs.cipher.block_size
197        padlen = block_size - (len(s) % block_size)
198        if padlen == block_size:
199            padlen = 0
200        padding = b"\x00" * padlen
201        return s + padding
202
203    def post_build(self, pkt, pay):
204        if self.protected_record is not None:
205            # we do not update the tls_session
206            return self.protected_record + pay
207
208        if self.padlen is None:
209            cfrag = pkt[2:]
210        else:
211            cfrag = pkt[3:]
212
213        if self.pad == b"" and self.tls_session.wcs.cipher.type == 'block':
214            pfrag = self._sslv2_pad(cfrag)
215        else:
216            pad = self.pad or b""
217            pfrag = cfrag + pad
218
219        padlen = self.padlen
220        if padlen is None:
221            padlen = len(pfrag) - len(cfrag)
222        hdr = pkt[:2]
223        if padlen > 0:
224            hdr += struct.pack("B", padlen)
225
226        # Integrity
227        if self.mac == b"":
228            mfrag = self._sslv2_mac_add(pfrag)
229        else:
230            mfrag = self.mac + pfrag
231
232        # Encryption
233        efrag = self._tls_encrypt(mfrag)
234
235        if self.len is not None:
236            tmp_len = self.len
237            if not self.with_padding:
238                tmp_len |= 0x8000
239            hdr = struct.pack("!H", tmp_len) + hdr[2:]
240        else:
241            # Update header with the length of TLSCiphertext.fragment
242            msglen_new = len(efrag)
243            if padlen:
244                if msglen_new > 0x3fff:
245                    raise Exception("Invalid record: encrypted data too long.")
246            else:
247                if msglen_new > 0x7fff:
248                    raise Exception("Invalid record: encrypted data too long.")
249                msglen_new |= 0x8000
250            hdr = struct.pack("!H", msglen_new) + hdr[2:]
251
252        # Now we commit the pending write state if it has been triggered (e.g.
253        # by an underlying TLSChangeCipherSpec or a SSLv2ClientMasterKey). We
254        # update nothing if the pwcs was not set. This probably means that
255        # we're working out-of-context (and we need to keep the default wcs).
256        # SSLv2 may force us to commit the reading connState here.
257        if self.tls_session.triggered_pwcs_commit:
258            if self.tls_session.pwcs is not None:
259                self.tls_session.wcs = self.tls_session.pwcs
260                self.tls_session.pwcs = None
261            self.tls_session.triggered_pwcs_commit = False
262        if self.tls_session.triggered_prcs_commit:
263            if self.tls_session.prcs is not None:
264                self.tls_session.rcs = self.tls_session.prcs
265                self.tls_session.prcs = None
266            self.tls_session.triggered_prcs_commit = False
267
268        if self.tls_session.pwcs is not None:
269            self.tls_session.pwcs.seq_num += 1
270        self.tls_session.wcs.seq_num += 1
271
272        return hdr + efrag + pay
273