Home | History | Annotate | Download | only in tls
      1 ## This file is part of Scapy
      2 ## Copyright (C) 2017 Maxence Tury
      3 ## This program is published under a GPLv2 license
      4 
      5 """
      6 SSLv2 Record.
      7 """
      8 
      9 import struct
     10 
     11 from scapy.config import conf
     12 from scapy.error import log_runtime
     13 from scapy.compat import *
     14 from scapy.fields import *
     15 from scapy.packet import *
     16 from scapy.layers.tls.session import _GenericTLSSessionInheritance
     17 from scapy.layers.tls.record import _TLSMsgListField, TLS
     18 from scapy.layers.tls.handshake_sslv2 import _sslv2_handshake_cls
     19 from scapy.layers.tls.basefields import (_SSLv2LengthField, _SSLv2PadField,
     20                                          _SSLv2PadLenField, _TLSMACField)
     21 
     22 
     23 ###############################################################################
     24 ### SSLv2 Record Protocol                                                   ###
     25 ###############################################################################
     26 
     27 class _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 
     66 class 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