Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TLS in Scapy should have more doc #4377

Open
BlobbyBob opened this issue May 3, 2024 · 8 comments
Open

TLS in Scapy should have more doc #4377

BlobbyBob opened this issue May 3, 2024 · 8 comments

Comments

@BlobbyBob
Copy link

Brief description

When connecting to a TLS server using TLS1.3 that negotiates TLS_CHACHA20_POLY1305, using tlsSessions fails with ValueError: not enough values to unpack (expected 3, got 2)

Scapy version

2.5.0

Python version

3.12.3

Operating system

Linux Kernel 6.8.8

Additional environment information

No response

How to reproduce

Send a client record such as

client_hello = TLSClientHello(
    ciphers=[
        tls.TLS_CHACHA20_POLY1305_SHA256,
    ],
    ext=[
        tls.TLS_Ext_ServerName(servernames=[tls.ServerName(servername=b"localhost")]),
        tls.TLS_Ext_ExtendedMasterSecret(),
        tls.TLS_Ext_EncryptThenMAC(),
        tls.TLS_Ext_SupportedGroups(groups=["x25519", "secp256r1", "x448"]),
        tls.TLS_Ext_SupportedVersion_CH(versions=["TLS 1.3"]),
        tls.TLS_Ext_SignatureAlgorithms(sig_algs=["ed25519", "sha256+rsaepss", "sha256+ecdsa"]),
        tls.TLS_Ext_KeyShare_CH(client_shares=[tls.KeyShareEntry(group="x25519")]),
    ],
)
ch_record = TLS(type=22, version=0x0303, len=len(client_hello.build())) / client_hello

to a server like openssl s_server and try to decrypt the response through the tlsSession.

Actual result

No response

Expected result

No response

Related resources

The error occurs at

File .venv/lib/python3.12/site-packages/scapy/layers/tls/record.py:537, in TLS.pre_dissect(self, s)
    535         cfrag, mac = self._tls_auth_decrypt(hdr, efrag)
    536     else:
--> 537         iv, cfrag, mac = self._tls_auth_decrypt(hdr, efrag)
    538     decryption_success = True       # see XXX above
    540 frag = self._tls_decompress(cfrag)

ValueError: not enough values to unpack (expected 3, got 2)

Quick debugging showed that

type(tls_session.rcs.cipher) = scapy.layers.tls.crypto.cipher_aead.Cipher_CHACHA20_POLY1305_TLS13
isinstance(tls_session.rcs.cipher, tls.Cipher_CHACHA20_POLY1305) = False

as Cipher_CHACHA20_POLY1305 is a subclass of Cipher_CHACHA20_POLY1305_TLS13 and not reverse.

Thus, the check in scapy/layers/tls/record.py does not cover this case.

@guedou
Copy link
Member

guedou commented May 3, 2024 via email

@BlobbyBob
Copy link
Author

Of course, here you go:

Script test.py:

import socket

import scapy.layers.tls.all as tls

client_hello = tls.TLSClientHello(
    ciphers=[
        tls.TLS_CHACHA20_POLY1305_SHA256
    ],
    ext=[
        tls.TLS_Ext_ServerName(servernames=[tls.ServerName(servername=b"localhost")]),
        tls.TLS_Ext_ExtendedMasterSecret(),
        tls.TLS_Ext_EncryptThenMAC(),
        tls.TLS_Ext_SupportedGroups(groups=["x25519"]),
        tls.TLS_Ext_SupportedVersion_CH(versions=["TLS 1.3"]),
        tls.TLS_Ext_SignatureAlgorithms(sig_algs=["sha256+rsaepss"]),
        tls.TLS_Ext_KeyShare_CH(client_shares=[tls.KeyShareEntry(group="x25519")]),
    ],
)
chrecord = tls.TLS(type=22, version=0x0303, len=len(client_hello.build())) / client_hello
chrecord.tls_session.tls13_client_privshares["x25519"] = client_hello[tls.KeyShareEntry].privkey
payload = chrecord.build()

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
    sock.connect(("localhost", 4433))
    sock.sendall(payload)
    resp_bytes = sock.recv(2**12)

    # Iterate through records
    session = chrecord.tls_session.mirror()
    i = 0
    while i < len(resp_bytes):
        server_record = tls.TLS(resp_bytes[i:], tls_session=session)
        i += server_record.len + 5
        server_record.show()
finally:
    sock.close()

Openssl Server (version 3.3.0):

openssl s_server -cert localhost.crt -key localhost.key  # with some self-signed cert + associated privkey

Scapy Commands:

$ docker run --rm -it --net=host python bash
# pip install scapy cryptography\<42
# python test.py

@BlobbyBob
Copy link
Author

I also just discovered that the issue persists when announcing TLS_AES_128_GCM_SHA256 as only cipher suite. Initially, I thought it was only a issue for Chacha due to the mentioned branching instruction in record.py, but now I suspect the underlying issue might be a bit different.

@evverx

This comment was marked as outdated.

@gpotter2 gpotter2 added the tls label May 4, 2024
@gpotter2
Copy link
Member

gpotter2 commented May 5, 2024

There are two issues with your code:

  1. You need to build the TLS packet using the msg field (and not appending the client hello). Doing that, you also don't need the 'hack' where you're injecting tls13_client_privshares:
chrecord = tls.TLS(type=22, version=0x0303, len=len(client_hello.build()), msg=[client_hello])
# instead of
# chrecord = tls.TLS(type=22, version=0x0303, len=len(client_hello.build())) / client_hello
  1. TLS() is greedy, and will automatically dissect all TLS() payloads following the current one. So you don't need the loop at the end of your code.

All in all, this gives the following, which works:

import socket

import scapy.layers.tls.all as tls

client_hello = tls.TLSClientHello(
    ciphers=[
        tls.TLS_CHACHA20_POLY1305_SHA256,
    ],
    ext=[
        tls.TLS_Ext_ServerName(servernames=[tls.ServerName(servername=b"localhost")]),
        tls.TLS_Ext_ExtendedMasterSecret(),
        tls.TLS_Ext_EncryptThenMAC(),
        tls.TLS_Ext_SupportedGroups(groups=["x25519"]),
        tls.TLS_Ext_SupportedVersion_CH(versions=["TLS 1.3"]),
        tls.TLS_Ext_SignatureAlgorithms(sig_algs=["sha256+rsaepss"]),
        tls.TLS_Ext_KeyShare_CH(client_shares=[tls.KeyShareEntry(group="x25519")]),
    ],
)
chrecord = tls.TLS(type=22, version=0x0303, len=len(client_hello.build()), msg=[client_hello])
# chrecord.tls_session.tls13_client_privshares["x25519"] = client_hello[tls.KeyShareEntry].privkey
payload = chrecord.build()

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
    sock.connect(("localhost", 4433))
    sock.sendall(payload)
    resp_bytes = sock.recv(2**12)

    # Iterate through records
    session = chrecord.tls_session.mirror()
    server_record = tls.TLS(resp_bytes, tls_session=session)
    server_record.show()
finally:
    sock.close()

@gpotter2
Copy link
Member

gpotter2 commented May 5, 2024

That being said.

We might want to have a more graceful failure for this case. The error

ValueError: not enough values to unpack (expected 3, got 2)

generally happens when Scapy tries to dissect TLS 1.3 as TLS 1.2, or the opposite. This usually means that the session is invalid (in this case: the session is invalid because msg=[] wasn't used, but our computing of the session happens when building the field that handles msg).

It might also be a good idea to trigger a warning when someone tries to append a TLS(13)ClientHello field to TLS (instead of using msg). Without looking at some examples, that's pretty hard to guess.

@gpotter2
Copy link
Member

gpotter2 commented May 5, 2024

That being also said,

We really need to have examples on https://scapy.readthedocs.io regarding various common TLS usages :(

@BlobbyBob
Copy link
Author

Thanks for your clarifications. I was aware of the second aspect but not of the first one.

We really need to have examples on https://scapy.readthedocs.io/ regarding various common TLS usages :(

Yeah, I can definitely stress that. It is pretty tough to find usage information and I often ended up reading the source files directly to find out what data types belong to what fields and how the sessions really work.
The only example-style hint is included in the docs of scapy.layers.tls.session.tlsSession.mirror(), but fully reverse engineering the intended usage from that is rather difficult and apparently error-prone.

@gpotter2 gpotter2 changed the title ValueError when decrypting Cipher_CHACHA20_POLY1305 in TLS 1.3 TLS in Scapy should have more doc May 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants