1## This file is part of Scapy
2## Copyright (C) 2017 Maxence Tury
3## This program is published under a GPLv2 license
4
5"""
6SSLv2 Record.
7"""
8
9import struct
10
11from scapy.config import conf
12from scapy.error import log_runtime
13from scapy.compat import *
14from scapy.fields import *
15from scapy.packet import *
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, length_from)
34
35    def m2i(self, pkt, m):
36        cls = Raw
37        if len(m) >= 1:
38            msgtype = orb(m[0])
39            cls = _sslv2_handshake_cls.get(msgtype, Raw)
40
41        if cls is Raw:
42            return Raw(m)
43        else:
44            return cls(m, tls_session=pkt.tls_session)
45
46    def i2m(self, pkt, p):
47       cur = b""
48       if isinstance(p, _GenericTLSSessionInheritance):
49           p.tls_session = pkt.tls_session
50           if not pkt.tls_session.frozen:
51               cur = p.raw_stateful()
52               p.post_build_tls_session_update(cur)
53           else:
54               cur = raw(p)
55       else:
56           cur = raw(p)
57       return cur
58
59    def addfield(self, pkt, s, val):
60        res = b""
61        for p in val:
62            res += self.i2m(pkt, p)
63        return s + res
64
65
66class SSLv2(TLS):
67    """
68    The encrypted_data is the encrypted version of mac+msg+pad.
69    """
70    __slots__ = ["with_padding", "protected_record"]
71    name = "SSLv2"
72    fields_desc = [ _SSLv2LengthField("len", None),
73                    _SSLv2PadLenField("padlen", None),
74                    _TLSMACField("mac", b""),
75                    _SSLv2MsgListField("msg", []),
76                    _SSLv2PadField("pad", "") ]
77
78    def __init__(self, *args, **kargs):
79        self.with_padding = kargs.get("with_padding", False)
80        self.protected_record = kargs.get("protected_record", None)
81        super(SSLv2, self).__init__(*args, **kargs)
82
83    ### Parsing methods
84
85    def _sslv2_mac_verify(self, msg, mac):
86        secret = self.tls_session.rcs.cipher.key
87        if secret is None:
88            return True
89
90        mac_len = self.tls_session.rcs.mac_len
91        if mac_len == 0:            # should be TLS_NULL_WITH_NULL_NULL
92            return True
93        if len(mac) != mac_len:
94            return False
95
96        read_seq_num = struct.pack("!I", self.tls_session.rcs.seq_num)
97        alg = self.tls_session.rcs.hash
98        h = alg.digest(secret + msg + read_seq_num)
99        return h == mac
100
101    def pre_dissect(self, s):
102        if len(s) < 2:
103            raise Exception("Invalid record: header is too short.")
104
105        msglen = struct.unpack("!H", s[:2])[0]
106        if msglen & 0x8000:
107            hdrlen = 2
108            msglen_clean = msglen & 0x7fff
109        else:
110            hdrlen = 3
111            msglen_clean = msglen & 0x3fff
112
113        hdr = s[:hdrlen]
114        efrag = s[hdrlen:hdrlen+msglen_clean]
115        self.protected_record = s[:hdrlen+msglen_clean]
116        r = s[hdrlen+msglen_clean:]
117
118        mac = pad = b""
119
120        cipher_type = self.tls_session.rcs.cipher.type
121
122        # Decrypt (with implicit IV if block cipher)
123        mfrag = self._tls_decrypt(efrag)
124
125        # Extract MAC
126        maclen = self.tls_session.rcs.mac_len
127        if maclen == 0:
128            mac, pfrag = b"", mfrag
129        else:
130            mac, pfrag = mfrag[:maclen], mfrag[maclen:]
131
132        # Extract padding
133        padlen = 0
134        if hdrlen == 3:
135            padlen = orb(s[2])
136        if padlen == 0:
137            cfrag, pad = pfrag, b""
138        else:
139            cfrag, pad = pfrag[:-padlen], pfrag[-padlen:]
140
141        # Verify integrity
142        is_mac_ok = self._sslv2_mac_verify(cfrag + pad, mac)
143        if not is_mac_ok:
144            pkt_info = self.firstlayer().summary()
145            log_runtime.info("TLS: record integrity check failed [%s]", pkt_info)
146
147        reconstructed_body = mac + cfrag + pad
148        return hdr + reconstructed_body + r
149
150    def post_dissect(self, s):
151        """
152        SSLv2 may force us to commit the write connState here.
153        """
154        if self.tls_session.triggered_prcs_commit:
155            if self.tls_session.prcs is not None:
156                self.tls_session.rcs = self.tls_session.prcs
157                self.tls_session.prcs = None
158            self.tls_session.triggered_prcs_commit = False
159        if self.tls_session.triggered_pwcs_commit:
160            if self.tls_session.pwcs is not None:
161                self.tls_session.wcs = self.tls_session.pwcs
162                self.tls_session.pwcs = None
163            self.tls_session.triggered_pwcs_commit = False
164
165        if self.tls_session.prcs is not None:
166            self.tls_session.prcs.seq_num += 1
167        self.tls_session.rcs.seq_num += 1
168        return s
169
170    def do_dissect_payload(self, s):
171        if s:
172            try:
173                p = SSLv2(s, _internal=1, _underlayer=self,
174                          tls_session = self.tls_session)
175            except KeyboardInterrupt:
176                raise
177            except:
178                p = conf.raw_layer(s, _internal=1, _underlayer=self)
179            self.add_payload(p)
180
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            l = self.len
237            if not self.with_padding:
238                l |= 0x8000
239            hdr = struct.pack("!H", l) + 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
274