Repository: secdev/scapy

Issue:
Some TLS notebooks are outdated
#### Brief description

The notebooks 1 to 3 in `doc/notebooks/tls` are not working properly. Below more details, where every notebook fails:

**`notebook1_x509.ipynb`** (first error in [17]):
Error message: `AttributeError: 'PrivKeyRSA' object has no attribute 'key'`

**`notebook2_tls_protected`** (first error in [4]):
Error message `AttributeError: certs`

**`notebook3_tls_compromised`** (first error in [3]):
Error message `AttributeError: 'TLS_RSA_WITH_AES_128_CBC_SHA' object has no attribute '_key_block_len_v1_0'`

**`notebook4_tls13`** (first errror in [6]):
Error message `TypeError: 'NoneType' object is not callable`

#### Environment

-   Scapy version: `2.4.4.dev275`
-   Python version: `3.9.1`
-   Operating System: `ManjaroLinux 20.2.1`

#### How to reproduce

Open the `.ipynb`-files mentioned above with `jupyter notebook` and run it by restarting the kernel.

As I am a new scapy-user I am not sure, if there are problems with the TLS layer or if the simply the notebooks must be updated.




Discussion:
I couldn't replicate any of those issues. Do you have cryptography installed?
The only issue I had was a small issue on record 4 of notebook 4. You didn't mention this one
I thougth so, but probably there was something wrong. Reinstalling cryptography solved most of the issues.

Notebook 1, 2, and 3 are fine.

In notebook 5 I got the Warning "Unknown cipher suite 1049 from ServerHello" running the notebook as it is.
After uncommenting the fifth input (were the keys are set and computed), I have a key error on 'secp256r1'.

I am still doing something wrong?
Those are probably regressions. I'll check them out, thanks for the report !
This is a regression introduced in 3e131e2dec97277982a379784dc07f378c5c047f:

Before `record4_str` was decoded as:
```
###[ TLS ]### 
  type      = handshake
  version   = TLS 1.0
  len       = 115    [deciphered_len= 115]
  iv        = b''
  \msg       \
   |###[ TLS 1.3 Handshake - Server Hello ]### 
   |  msgtype   = server_hello
   |  msglen    = 111
   |  version   = TLS 1.3-d18
   |  random_bytes= 96ff693075d8465651a9c28773f5496542206ba390199b9c997545d9a1266615
   |  cipher    = TLS_AES_128_GCM_SHA256
   |  extlen    = 73
   |  \ext       \
   |   |###[ TLS Extension - Key Share (for ServerHello) ]### 
   |   |  type      = key_share
   |   |  len       = 69
   |   |  \server_share\
   |   |   |###[ Key Share Entry ]### 
   |   |   |  group     = secp256r1
   |   |   |  kxlen     = 65
   |   |   |  key_exchange= '\x04\x8aM\t\xcd\xe5\x8d\xbc\x04\x19U\xb9\xa4\x1aC\xc1im\xc5B\x9f\xfa\x96\xf9\xcd\x19J\x86:\xc7\x82\xf1\x81Y\xf0r\xb4\xf6\x10!]\x86@}\xd76\x8buJ\xb2\xe6O,\x1b?\x9dE|&N+\x17\x81\xa3k'
  mac       = b''
  pad       = b''
  padlen    = None
```

I am unsure that it is worth fixing the code as the notebook examples are based on a TLS 1.3 early draft.

Resolving patch:
diff --git a/scapy/layers/tls/session.py b/scapy/layers/tls/session.py
--- a/scapy/layers/tls/session.py
+++ b/scapy/layers/tls/session.py
@@ -8,6 +8,7 @@
 TLS session handler.
 """
 
+import binascii
 import socket
 import struct
 
@@ -24,6 +25,53 @@
 from scapy.layers.tls.crypto.hkdf import TLS13_HKDF
 from scapy.layers.tls.crypto.prf import PRF
 
+# Typing imports
+from scapy.compat import Dict
+
+
+def load_nss_keys(filename):
+    # type: (str) -> Dict[str, bytes]
+    """
+    Parses a NSS Keys log and returns unpacked keys in a dictionary.
+    """
+    keys = {}
+    try:
+        fd = open(filename)
+    except FileNotFoundError:
+        warning("Cannot open NSS Key Log: %s", filename)
+        return {}
+    else:
+        with open(filename) as fd:
+            for line in fd:
+                if line.startswith("#"):
+                    continue
+                data = line.strip().split(" ")
+                if len(data) != 3 or data[0] != data[0].upper():
+                    warning("Invalid NSS Key Log Entry: %s", line.strip())
+                    return {}
+
+                try:
+                    client_random = binascii.unhexlify(data[1])
+                except binascii.Error:
+                    warning("Invalid ClientRandom: %s", data[1])
+                    return {}
+
+                try:
+                    secret = binascii.unhexlify(data[2])
+                except binascii.Error:
+                    warning("Invalid Secret: %s", data[2])
+                    return {}
+
+                # Warn that a duplicated entry was detected. The latest one
+                # will be kept in the resulting dictionary.
+                if data[0] in keys:
+                    warning("Duplicated entry for %s !", data[0])
+
+                keys[data[0]] = {"ClientRandom": client_random,
+                                 "Secret": secret}
+        return keys
+
+
 # Note the following import may happen inside connState.__init__()
 # in order to avoid to avoid cyclical dependencies.
 # from scapy.layers.tls.crypto.suites import TLS_NULL_WITH_NULL_NULL
@@ -366,6 +414,10 @@ def __init__(self,
         self.server_rsa_key = None
         # self.server_ecdsa_key = None
 
+        # A dictionary containing keys extracted from a NSS Keys Log using
+        # the load_nss_keys() function.
+        self.nss_keys = None
+
         # Back in the dreadful EXPORT days, US servers were forbidden to use
         # RSA keys longer than 512 bits for RSAkx. When their usual RSA key
         # was longer than this, they had to create a new key and send it via
@@ -546,7 +598,14 @@ def compute_master_secret(self):
             log_runtime.debug("TLS: master secret: %s", repr_hex(ms))
 
     def compute_ms_and_derive_keys(self):
-        self.compute_master_secret()
+        # Load the master secret from an NSS Key dictionary
+        if self.nss_keys and self.nss_keys.get("CLIENT_RANDOM", False) and \
+           self.nss_keys["CLIENT_RANDOM"].get("Secret", False):
+            self.master_secret = self.nss_keys["CLIENT_RANDOM"]["Secret"]
+
+        if not self.master_secret:
+            self.compute_master_secret()
+
         self.prcs.derive_keys(client_random=self.client_random,
                               server_random=self.server_random,
                               master_secret=self.master_secret)
@@ -894,15 +953,25 @@ def __init__(self, _pkt="", post_transform=None, _internal=0,
                 self.tls_session.ipdst = tcp.underlayer.dst
             except AttributeError:
                 pass
+
+            # Load a NSS Key Log file
+            if conf.tls_nss_filename is not None:
+                if conf.tls_nss_keys is None:
+                    conf.tls_nss_keys = load_nss_keys(conf.tls_nss_filename)
+
             if conf.tls_session_enable:
                 if newses:
                     s = conf.tls_sessions.find(self.tls_session)
                     if s:
+                        if conf.tls_nss_keys is not None:
+                            s.nss_keys = conf.tls_nss_keys
                         if s.dport == self.tls_session.dport:
                             self.tls_session = s
                         else:
                             self.tls_session = s.mirror()
                     else:
+                        if conf.tls_nss_keys is not None:
+                            self.tls_session.nss_keys = conf.tls_nss_keys
                         conf.tls_sessions.add(self.tls_session)
             if self.tls_session.connection_end == "server":
                 srk = conf.tls_sessions.server_rsa_key
@@ -1110,3 +1179,7 @@ def toPacketList(self):
 conf.tls_sessions = _tls_sessions()
 conf.tls_session_enable = False
 conf.tls_verbose = False
+# Filename containing NSS Keys Log
+conf.tls_nss_filename = None
+# Dictionary containing parsed NSS Keys
+conf.tls_nss_keys = None


Test patch:
diff --git a/test/tls.uts b/test/tls.uts
--- a/test/tls.uts
+++ b/test/tls.uts
@@ -1565,3 +1565,16 @@ assert [type(x) for x in pkt.msg] == [TLSServerHello, TLSCertificate, TLSCertifi
 # see test/tls/tests_tls_netaccess.uts
 
 
+###############################################################################
+####################### Decrypt packets from a pcap ##########################
+###############################################################################
+
+bck_conf = conf
+conf.tls_session_enable = True
+conf.tls_nss_filename = scapy_path("doc/notebooks/tls/raw_data/tls_nss_example.keys.txt")
+
+packets = rdpcap(scapy_path("doc/notebooks/tls/raw_data/tls_nss_example.pcap"))
+assert b"GET /secret.txt HTTP/1.0\n" in packets[11].msg[0].data
+assert b"z2|gxarIKOxt,G1d>.Q2MzGY[k@" in packets[13].msg[0].data
+
+conf = bck_conf