Repository: secdev/scapy

Issue:
Dissecting TLS with NULL cipher
#### Brief description
Scapy incorrectly dissects TLS packets with NULL cipher - message dissecting is too greedy which results in consuming MAC (and possibly padding)

#### Environment

-   Scapy version: ` e44bd373f0839ed67c1c91cbfc94cde58aee8722`
-   Python version: `2.7`
-   Operating System: `Windows`

#### How to reproduce
Execute following code with attached [pcap](https://github.com/secdev/scapy/files/3047186/handshake.zip):

```python
from scapy.all import *
from scapy.layers.tls.record import TLS

bind_layers(TCP, TLS)

CLIENT_IP = '10.192.71.100'
SERVER_IP = '10.192.71.150'
from_pcap = rdpcap("handshake.pcapng")

session = None
tls_only = []
for pkt in from_pcap:
    if pkt.haslayer('TLS'):
        if not session:
            session = pkt[TLS].tls_session
        else:
            connection_end = session.rcs.connection_end
            # depending on who is the target switch TLS session state
            if ((pkt[IP].dst == SERVER_IP and connection_end == 'client') or
                (pkt[IP].dst == CLIENT_IP and connection_end == 'server')):
                session.mirror()
        # dissect again TLS with session context
        tls_only.append(TLS(raw(pkt[TLS]), tls_session=session))
tls_only[5].show()
```
The pcap contains TLS handshake:
![image](https://user-images.githubusercontent.com/25364305/55617374-38216600-5794-11e9-8851-13515034e0be.png)

I marked a TLS record which is a subject of the analysis.

#### Actual result

```
###[ TLS ]### 
        type      = handshake
        version   = TLS 1.2
        len       = 36
        iv        = ''
        \msg       \
         |###[ TLS Handshake - Finished ]### 
         |  msgtype   = finished
         |  msglen    = 12
         |  vdata     = 'YX\xacX\xb81\x1fX\x8f\xbe\x1dJ'
         |###[ TLS Handshake - Client Key Exchange ]### 
         |  msgtype   = client_key_exchange
         |  msglen    = 13552178
         |  \exchkeys  \
         |   |###[ Raw ]### 
         |   |  load      = '\xb4\xc3m\xf1\x16c\xdb\xfc\x08\x16\x1d\x82\x83U\x8c\xe1'
        mac       = ''
        pad       = ''
        padlen    = None
```


#### Expected result

```
###[ TLS ]### 
        type      = handshake
        version   = TLS 1.2
        len       = 36    [deciphered_len= 16]
        iv        = ''
        \msg       \
         |###[ TLS Handshake - Finished ]### 
         |  msgtype   = finished
         |  msglen    = 12
         |  vdata     = 'YX\xacX\xb81\x1fX\x8f\xbe\x1dJ'
        mac       = '\x10\xce\xca2\xb4\xc3m\xf1\x16c\xdb\xfc\x08\x16\x1d\x82\x83U\x8c\xe1'
        pad       = ''
        padlen    = None
```

#### Related resources
[handshake.zip](https://github.com/secdev/scapy/files/3047186/handshake.zip)

#### Fix proposal
I'v got expected result by removing `and not isinstance(self.tls_session.rcs.cipher, Cipher_NULL)` from https://github.com/secdev/scapy/blob/e44bd373f0839ed67c1c91cbfc94cde58aee8722/scapy/layers/tls/record.py#L488
The fix seems to be too easy so I doubt in its correctness. Please provide guidance.


Discussion:
Hi & thanks for the report !
Feel free to provide a PR, if something is wrong, the tests will fail

Resolving patch:
diff --git a/scapy/layers/tls/cert.py b/scapy/layers/tls/cert.py
--- a/scapy/layers/tls/cert.py
+++ b/scapy/layers/tls/cert.py
@@ -298,10 +298,11 @@ def import_from_asn1pkt(self, pubkey):
 
     def encrypt(self, msg, t="pkcs", h="sha256", mgf=None, L=None):
         # no ECDSA encryption support, hence no ECDSA specific keywords here
-        return _EncryptAndVerifyRSA.encrypt(self, msg, t, h, mgf, L)
+        return _EncryptAndVerifyRSA.encrypt(self, msg, t=t, h=h, mgf=mgf, L=L)
 
     def verify(self, msg, sig, t="pkcs", h="sha256", mgf=None, L=None):
-        return _EncryptAndVerifyRSA.verify(self, msg, sig, t, h, mgf, L)
+        return _EncryptAndVerifyRSA.verify(
+            self, msg, sig, t=t, h=h, mgf=mgf, L=L)
 
 
 class PubKeyECDSA(PubKey):
@@ -510,10 +511,11 @@ def import_from_asn1pkt(self, privkey):
 
     def verify(self, msg, sig, t="pkcs", h="sha256", mgf=None, L=None):
         # Let's copy this from PubKeyRSA instead of adding another baseclass :)
-        return _EncryptAndVerifyRSA.verify(self, msg, sig, t, h, mgf, L)
+        return _EncryptAndVerifyRSA.verify(
+            self, msg, sig, t=t, h=h, mgf=mgf, L=L)
 
     def sign(self, data, t="pkcs", h="sha256", mgf=None, L=None):
-        return _DecryptAndSignRSA.sign(self, data, t, h, mgf, L)
+        return _DecryptAndSignRSA.sign(self, data, t=t, h=h, mgf=mgf, L=L)
 
 
 class PrivKeyECDSA(PrivKey):
@@ -668,10 +670,10 @@ def isSelfSigned(self):
 
     def encrypt(self, msg, t="pkcs", h="sha256", mgf=None, L=None):
         # no ECDSA *encryption* support, hence only RSA specific keywords here
-        return self.pubKey.encrypt(msg, t, h, mgf, L)
+        return self.pubKey.encrypt(msg, t=t, h=h, mgf=mgf, L=L)
 
     def verify(self, msg, sig, t="pkcs", h="sha256", mgf=None, L=None):
-        return self.pubKey.verify(msg, sig, t, h, mgf, L)
+        return self.pubKey.verify(msg, sig, t=t, h=h, mgf=mgf, L=L)
 
     def remainingDays(self, now=None):
         """
diff --git a/scapy/layers/tls/record.py b/scapy/layers/tls/record.py
--- a/scapy/layers/tls/record.py
+++ b/scapy/layers/tls/record.py
@@ -485,8 +485,7 @@ def pre_dissect(self, s):
 
         frag = self._tls_decompress(cfrag)
 
-        if (decryption_success and
-                not isinstance(self.tls_session.rcs.cipher, Cipher_NULL)):
+        if decryption_success:
             self.deciphered_len = len(frag)
         else:
             self.deciphered_len = None


Test patch:
diff --git a/test/tls.uts b/test/tls.uts
--- a/test/tls.uts
+++ b/test/tls.uts
@@ -1,5 +1,5 @@
 % Tests for TLS module
-# 
+#
 # Try me with :
 # bash test/run_tests -t test/tls.uts -F
 
@@ -15,7 +15,7 @@
 
 + Test HMACs
 
-= Crypto - Hmac_MD5 instantiation, parameter check 
+= Crypto - Hmac_MD5 instantiation, parameter check
 from scapy.layers.tls.crypto.h_mac import Hmac_MD5
 a = Hmac_MD5("somekey")
 a.key_len == 16 and a.hmac_len == 16
@@ -32,7 +32,7 @@ t7 = a(b'\xaa'*80).digest("Test Using Larger Than Block-Size Key and Larger Than
 t1 and t2 and t3 and t4 and t5 and t6 and t7
 
 
-= Crypto - Hmac_SHA instantiation, parameter check 
+= Crypto - Hmac_SHA instantiation, parameter check
 from scapy.layers.tls.crypto.h_mac import Hmac_SHA
 a = Hmac_SHA("somekey")
 a.key_len == 20 and a.hmac_len == 20
@@ -986,6 +986,45 @@ from scapy.layers.tls.record import _TLSMsgListField
 assert isinstance(_TLSMsgListField.m2i(_TLSMsgListField("", []), TLS(type=0), '\x00\x03\x03\x00\x03abc'), Raw)
 
 
+###############################################################################
+####### Read handshake with TLS_ECDHE_ECDSA_WITH_NULL_SHA #####################
+###############################################################################
+
++ Read handshake with NULL Cipher
+
+= Reading test session - Loading unparsed TLS records
+p1_ch = b'\x16\x03\x01\x00{\x01\x00\x00w\x03\x03\x86C\xf2\xe4x\xbaL\x9a`\xc3\x9aR\xa8\xb4\xac\xd0\r\xe2\xa3N\xe6\xa8]g5z$j\xb1(%\xe3\x00\x00\x08\xc0\x06\xc0#\xc0$\x00\xff\x01\x00\x00F\x00\x0b\x00\x04\x03\x00\x01\x02\x00\n\x00\n\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00#\x00\x00\x00\x16\x00\x00\x00\x17\x00\x00\x00\r\x00 \x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03'
+p2_sh = b'\x16\x03\x03\x006\x02\x00\x002\x03\x03C\nm.s\x07W\xef\x91\xf0\xc7\xd8\xaa\xc3NL}\xb0tw?\xd8\n\x8f\x8d\xc4\xee,fhY\x85\x00\xc0\x06\x00\x00\n\x00\x0b\x00\x02\x01\x00\x00\x17\x00\x00'
+p3_cert = b'\x16\x03\x03\x02\xca\x0b\x00\x02\xc6\x00\x02\xc3\x00\x02\xc00\x82\x02\xbc0\x82\x02\x1d\xa0\x03\x02\x01\x02\x02\x02\x04\xd20\n\x06\x08*\x86H\xce=\x04\x03\x020v1\x0b0\t\x06\x03U\x04\x06\x13\x02PL1\x0b0\t\x06\x03U\x04\x08\x0c\x02PL1\x0c0\n\x06\x03U\x04\x07\x0c\x03KTW1\x0c0\n\x06\x03U\x04\n\x0c\x03ORG1\x0e0\x0c\x06\x03U\x04\x0b\x0c\x05OUNIT1\x110\x0f\x06\x03U\x04\x03\x0c\x08SomeName1\x1b0\x19\x06\t*\x86H\x86\xf7\r\x01\t\x01\x16\x0cemail@adress0\x1e\x17\r190404065502Z\x17\r270621065502Z0\x81\x881\x0b0\t\x06\x03U\x04\x06\x13\x02PL1\x0b0\t\x06\x03U\x04\x08\x0c\x02PL1\x0c0\n\x06\x03U\x04\x07\x0c\x03KTW1\x0c0\n\x06\x03U\x04\n\x0c\x03ORG1\x110\x0f\x06\x03U\x04\x0b\x0c\x08SomeUnit1\x110\x0f\x06\x03U\x04\x03\x0c\x08SomeName1\r0\x0b\x06\x03U\x04\x05\x0c\x0412341\x1b0\x19\x06\t*\x86H\x86\xf7\r\x01\t\x01\x16\x0cemail@adress0Y0\x13\x06\x07*\x86H\xce=\x02\x01\x06\x08*\x86H\xce=\x03\x01\x07\x03B\x00\x04\x97\xfcij\xa2\xeeZh>\x94\n\xad\x1f\x16\x91\x80\x89\xc5\xb3\xc4\xb7\xd1A\xf0(\x96\x93UJ\xca\x98Y\xdec\xad\xa0\xbb\xd9\xebl\x15\xc7\xf2\xa9\xcfl\xbf\x0f\xed"\x08%\x8f\xaf\xd7\xf1K\x98\xf1\xf9\x04.\x05\x81\xa3\x81\x870\x81\x840\t\x06\x03U\x1d\x13\x04\x020\x000\x0e\x06\x03U\x1d\x0f\x01\x01\xff\x04\x04\x03\x02\x03\xa80\'\x06\x03U\x1d%\x04 0\x1e\x06\x08+\x06\x01\x05\x05\x07\x03\x04\x06\x08+\x06\x01\x05\x05\x07\x03\x02\x06\x08+\x06\x01\x05\x05\x07\x03\x010\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x14\xb2\x12\x8c\xe4\x16\x17XjZ%+4G\xa0\xfd\x0b!\x91\xc7\xec0\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x14\xe2\x17\xb1\xb1\xe1\xca3\xe8\xed\xfd\x86\x13\x10\xe7x5H\xdf1\xf50\n\x06\x08*\x86H\xce=\x04\x03\x02\x03\x81\x8c\x000\x81\x88\x02B\x01\xeb\xc9\xbe\xa1^\x12\x85\x10\x03\x9f$\xc6(\xce\xd7x\xc3w\x00\xd2\x8an\\r\xe8\xb3\xb9\x92Q\x8f\x9f\x81v\xa7*\xa0\xb2\xd8\x17\x12\xbe\xef\x04c\x97T\x8c;&B[\xda\xf8\x81c7\xd25\xfb\xae\x19\x81A\x9b\xc6\x02B\x012\xe9G\xd9;9\x97\x9c\xed_\xa19K\xef\x1b\xf1\x8f\x01\x86icw\r\xa1\x19\xb7\xa6\xe6\xc7\xef\xd6\x1bTr\xb1~\x8ae:4\xdb\xdb\x07\xcf&\xd4\xc0,\xf7\xf5\xa7\'m\xe1a\x06\xb5>\xec\xf1kDB\xf7\\'
+p4_ske = b'\x16\x03\x03\x00\x93\x0c\x00\x00\x8f\x03\x00\x17A\x04\xb4\xd4\xf6^\x87(\x97\xc4\xe5)\x19E\xe1\x9e\xfdPOf\x91\xa1PTdk\xdcU\n\xb9\x07\x93\xc8\xd1\xb0\tA\xce\xf9\xcd\x0e\xb6\xd7\xf0\r\xc7\xba\xaa\xd9zA\xe8\x8f(\xe1\x0fE[+&9\x90\xd4\n`O\x06\x03\x00F0D\x02 -\x04\xe5.g\x92\xca\xbe\xe4\x87\x9a\x88\x80~<\x10Q&v\xfa~\xf4h\x7f\xd0\xa1\x16\xf2\xfdN\x8b\xdf\x02 eI\xf0{E6mU0bRt\xb9\xc4\xcff\xf9\x87\xfdL\xdd\xa3d\xcf1\xab| ~"<\xcd'
+p5_shd = b'\x16\x03\x03\x00\x04\x0e\x00\x00\x00'
+p6_cke_ccs_cfin = b'\x16\x03\x03\x00F\x10\x00\x00BA\x04w_\xba\x8cX9\xab\x1f\x1drw\xaa\x08"\xe6\x05\x8eS\x8637\xb75\xe4\x1f\xc3H-\x12\xf4\xbb\x10\xf8\xb8.[?\x11sG\x0b\x18\x03}\x16n\n\xdb\x7f\x92\xear\xd1\x1a\x07.e;\xfc\xcer\x1f\xebA\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00$\x14\x00\x00\x0cYX\xacX\xb81\x1fX\x8f\xbe\x1dJ\x10\xce\xca2\xb4\xc3m\xf1\x16c\xdb\xfc\x08\x16\x1d\x82\x83U\x8c\xe1'
+p7_ccs = b'\x14\x03\x03\x00\x01\x01'
+p8_sfin = b'\x17\x03\x03\x00$\x14\x00\x00\x0c8\x1f\x18\xb6f\x98\xe3\xc0\xa4\xe2\xf8\xba\n\xd7\xd0\xb93y]\x1a\n\xeb\xc39nd\xa5\xd7\x8c\xe5\xf9\x91'
+
+= Reading TLS test session
+t1 = TLS(p1_ch)
+t2 = TLS(p2_sh, tls_session=t1.tls_session.mirror())
+t3 = TLS(p3_cert, tls_session=t2.tls_session)
+t4 = TLS(p4_ske, tls_session=t3.tls_session)
+t5 = TLS(p5_shd, tls_session=t4.tls_session)
+t6 = TLS(p6_cke_ccs_cfin, tls_session=t5.tls_session.mirror())
+
+= Verify TLSClientKeyExchange
+cke = t6.msg[0]
+assert isinstance(cke, TLSClientKeyExchange)
+
+= Verify TLSChangeCipherSpec
+ccs = t6.payload.msg[0]
+assert isinstance(ccs, TLSChangeCipherSpec)
+
+= Verify TLSFinished
+cfin = t6.payload.payload.msg[0]
+assert isinstance(cfin, TLSFinished)
+
+= Verify MAC - TLSFinished record
+assert (t6.payload.payload.mac == b'\x10\xce\xca2\xb4\xc3m\xf1\x16c\xdb\xfc\x08\x16\x1d\x82\x83U\x8c\xe1')
+
 ###############################################################################
 ################## Reading TLS vulnerable test session ########################
 ###############################################################################
@@ -1193,14 +1232,14 @@ def test_tls_tools():
     assert save_comp is not comp
     block_size = 8
     _tls_add_pad(comp, block_size)
-    assert isinstance(comp, TLSCompressed)    
+    assert isinstance(comp, TLSCompressed)
     assert comp.len == save_comp.len + -save_comp.len % block_size + 1
     had_pad = _tls_del_pad(comp)
     assert had_pad
     assert comp == save_comp
     block_size = save_comp.len // 2
     _tls_add_pad(comp, block_size)
-    assert isinstance(comp, TLSCompressed)    
+    assert isinstance(comp, TLSCompressed)
     assert comp.len == save_comp.len + -save_comp.len % block_size + 1
     had_pad = _tls_del_pad(comp)
     assert had_pad