86890704fd
todo: add documentation & wireshark dissector
225 lines
7.2 KiB
Python
Executable file
225 lines
7.2 KiB
Python
Executable file
# This file is part of Scapy
|
|
# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard
|
|
# 2015, 2016, 2017 Maxence Tury
|
|
# This program is published under a GPLv2 license
|
|
|
|
"""
|
|
TLS helpers, provided as out-of-context methods.
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
import struct
|
|
|
|
from scapy.compat import orb, chb
|
|
from scapy.error import warning
|
|
from scapy.fields import (ByteEnumField, ShortEnumField,
|
|
FieldLenField, StrLenField)
|
|
from scapy.packet import Packet
|
|
|
|
from scapy.layers.tls.basefields import _tls_type, _tls_version
|
|
|
|
|
|
class TLSPlaintext(Packet):
|
|
name = "TLS Plaintext"
|
|
fields_desc = [ByteEnumField("type", None, _tls_type),
|
|
ShortEnumField("version", None, _tls_version),
|
|
FieldLenField("len", None, length_of="data", fmt="!H"),
|
|
StrLenField("data", "",
|
|
length_from=lambda pkt: pkt.len)]
|
|
|
|
|
|
class TLSCompressed(TLSPlaintext):
|
|
name = "TLS Compressed"
|
|
|
|
|
|
class TLSCiphertext(TLSPlaintext):
|
|
name = "TLS Ciphertext"
|
|
|
|
|
|
def _tls_compress(alg, p):
|
|
"""
|
|
Compress p (a TLSPlaintext instance) using compression algorithm instance
|
|
alg and return a TLSCompressed instance.
|
|
"""
|
|
c = TLSCompressed()
|
|
c.type = p.type
|
|
c.version = p.version
|
|
c.data = alg.compress(p.data)
|
|
c.len = len(c.data)
|
|
return c
|
|
|
|
|
|
def _tls_decompress(alg, c):
|
|
"""
|
|
Decompress c (a TLSCompressed instance) using compression algorithm
|
|
instance alg and return a TLSPlaintext instance.
|
|
"""
|
|
p = TLSPlaintext()
|
|
p.type = c.type
|
|
p.version = c.version
|
|
p.data = alg.decompress(c.data)
|
|
p.len = len(p.data)
|
|
return p
|
|
|
|
|
|
def _tls_mac_add(alg, c, write_seq_num):
|
|
"""
|
|
Compute the MAC using provided MAC alg instance over TLSCiphertext c using
|
|
current write sequence number write_seq_num. Computed MAC is then appended
|
|
to c.data and c.len is updated to reflect that change. It is the
|
|
caller responsibility to increment the sequence number after the operation.
|
|
The function has no return value.
|
|
"""
|
|
write_seq_num = struct.pack("!Q", write_seq_num)
|
|
h = alg.digest(write_seq_num + bytes(c))
|
|
c.data += h
|
|
c.len += alg.hash_len
|
|
|
|
|
|
def _tls_mac_verify(alg, p, read_seq_num):
|
|
"""
|
|
Verify if the MAC in provided message (message resulting from decryption
|
|
and padding removal) is valid. Current read sequence number is used in
|
|
the verification process.
|
|
|
|
If the MAC is valid:
|
|
- The function returns True
|
|
- The packet p is updated in the following way: trailing MAC value is
|
|
removed from p.data and length is updated accordingly.
|
|
|
|
In case of error, False is returned, and p may have been modified.
|
|
|
|
Also note that it is the caller's responsibility to update the read
|
|
sequence number after the operation.
|
|
"""
|
|
h_size = alg.hash_len
|
|
if p.len < h_size:
|
|
return False
|
|
received_h = p.data[-h_size:]
|
|
p.len -= h_size
|
|
p.data = p.data[:-h_size]
|
|
|
|
read_seq_num = struct.pack("!Q", read_seq_num)
|
|
h = alg.digest(read_seq_num + bytes(p))
|
|
return h == received_h
|
|
|
|
|
|
def _tls_add_pad(p, block_size):
|
|
"""
|
|
Provided with cipher block size parameter and current TLSCompressed packet
|
|
p (after MAC addition), the function adds required, deterministic padding
|
|
to p.data before encryption step, as it is defined for TLS (i.e. not
|
|
SSL and its allowed random padding). The function has no return value.
|
|
"""
|
|
padlen = -p.len % block_size
|
|
padding = chb(padlen) * (padlen + 1)
|
|
p.len += len(padding)
|
|
p.data += padding
|
|
|
|
|
|
def _tls_del_pad(p):
|
|
"""
|
|
Provided with a just decrypted TLSCiphertext (now a TLSPlaintext instance)
|
|
p, the function removes the trailing padding found in p.data. It also
|
|
performs some sanity checks on the padding (length, content, ...). False
|
|
is returned if one of the check fails. Otherwise, True is returned,
|
|
indicating that p.data and p.len have been updated.
|
|
"""
|
|
|
|
if p.len < 1:
|
|
warning("Message format is invalid (padding)")
|
|
return False
|
|
|
|
padlen = orb(p.data[-1])
|
|
padsize = padlen + 1
|
|
|
|
if p.len < padsize:
|
|
warning("Invalid padding length")
|
|
return False
|
|
|
|
if p.data[-padsize:] != chb(padlen) * padsize:
|
|
warning("Padding content is invalid %s", repr(p.data[-padsize:]))
|
|
return False
|
|
|
|
p.data = p.data[:-padsize]
|
|
p.len -= padsize
|
|
|
|
return True
|
|
|
|
|
|
def _tls_encrypt(alg, p):
|
|
"""
|
|
Provided with an already MACed TLSCompressed packet, and a stream or block
|
|
cipher alg, the function converts it into a TLSCiphertext (i.e. encrypts it
|
|
and updates length). The function returns a newly created TLSCiphertext
|
|
instance.
|
|
"""
|
|
c = TLSCiphertext()
|
|
c.type = p.type
|
|
c.version = p.version
|
|
c.data = alg.encrypt(p.data)
|
|
c.len = len(c.data)
|
|
return c
|
|
|
|
|
|
def _tls_decrypt(alg, c):
|
|
"""
|
|
Provided with a TLSCiphertext instance c, and a stream or block cipher alg,
|
|
the function decrypts c.data and returns a newly created TLSPlaintext.
|
|
"""
|
|
p = TLSPlaintext()
|
|
p.type = c.type
|
|
p.version = c.version
|
|
p.data = alg.decrypt(c.data)
|
|
p.len = len(p.data)
|
|
return p
|
|
|
|
|
|
def _tls_aead_auth_encrypt(alg, p, write_seq_num):
|
|
"""
|
|
Provided with a TLSCompressed instance p, the function applies AEAD
|
|
cipher alg to p.data and builds a new TLSCiphertext instance. Unlike
|
|
for block and stream ciphers, for which the authentication step is done
|
|
separately, AEAD alg does it simultaneously: this is the reason why
|
|
write_seq_num is passed to the function, to be incorporated in
|
|
authenticated data. Note that it is the caller's responsibility to increment # noqa: E501
|
|
write_seq_num afterwards.
|
|
"""
|
|
P = bytes(p)
|
|
write_seq_num = struct.pack("!Q", write_seq_num)
|
|
A = write_seq_num + P[:5]
|
|
|
|
c = TLSCiphertext()
|
|
c.type = p.type
|
|
c.version = p.version
|
|
c.data = alg.auth_encrypt(P, A, write_seq_num)
|
|
c.len = len(c.data)
|
|
return c
|
|
|
|
|
|
def _tls_aead_auth_decrypt(alg, c, read_seq_num):
|
|
"""
|
|
Provided with a TLSCiphertext instance c, the function applies AEAD
|
|
cipher alg auth_decrypt function to c.data (and additional data)
|
|
in order to authenticate the data and decrypt c.data. When those
|
|
steps succeed, the result is a newly created TLSCompressed instance.
|
|
On error, None is returned. Note that it is the caller's responsibility to
|
|
increment read_seq_num afterwards.
|
|
"""
|
|
# 'Deduce' TLSCompressed length from TLSCiphertext length
|
|
# There is actually no guaranty of this equality, but this is defined as
|
|
# such in TLS 1.2 specifications, and it works for GCM and CCM at least.
|
|
#
|
|
plen = c.len - getattr(alg, "nonce_explicit_len", 0) - alg.tag_len
|
|
read_seq_num = struct.pack("!Q", read_seq_num)
|
|
A = read_seq_num + struct.pack('!BHH', c.type, c.version, plen)
|
|
|
|
p = TLSCompressed()
|
|
p.type = c.type
|
|
p.version = c.version
|
|
p.len = plen
|
|
p.data = alg.auth_decrypt(A, c.data, read_seq_num)
|
|
|
|
if p.data is None: # Verification failed.
|
|
return None
|
|
return p
|