| ## 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 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="fragment", |
| fmt="!H"), |
| StrLenField("fragment", "", |
| length_from = lambda pkt: pkt.length) ] |
| |
| 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.fragment = alg.compress(p.fragment) |
| c.len = len(c.fragment) |
| 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.fragment = alg.decompress(c.fragment) |
| p.len = len(p.fragment) |
| 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.fragment and c.length is updated to reflect that change. It is the |
| caller responsability 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 + str(c)) |
| c.fragment += 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.fragment 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.fragment[-h_size:] |
| p.len -= h_size |
| p.fragment = p.fragment[:-h_size] |
| |
| read_seq_num = struct.pack("!Q", read_seq_num) |
| h = alg.digest(read_seq_num + str(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.fragment 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 = block_size - ((p.len + 1) % block_size) |
| if padlen == block_size: |
| padlen = 0 |
| padding = chr(padlen) * (padlen + 1) |
| p.len += len(padding) |
| p.fragment += 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.fragment. 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.fragment and p.len have been updated. |
| """ |
| |
| if p.len < 1: |
| warning("Message format is invalid (padding)") |
| return False |
| |
| padlen = ord(p.fragment[-1]) + 1 |
| if (p.len < padlen): |
| warning("Invalid padding length") |
| return False |
| |
| if (p.fragment[-padlen:] != p.fragment[-1] * padlen): |
| warning("Padding content is invalid %s", repr(p.fragment[-padlen:])) |
| return False |
| |
| p.fragment = p.fragment[:-padlen] |
| p.len -= padlen |
| |
| 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.fragment = alg.encrypt(p.fragment) |
| c.len = len(c.fragment) |
| return c |
| |
| def _tls_decrypt(alg, c): |
| """ |
| Provided with a TLSCiphertext instance c, and a stream or block cipher alg, |
| the function decrypts c.fragment and returns a newly created TLSPlaintext. |
| """ |
| p = TLSPlaintext() |
| p.type = c.type |
| p.version = c.version |
| p.fragment = alg.decrypt(c.fragment) |
| p.len = len(p.fragment) |
| 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.fragment 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 |
| write_seq_num afterwards. |
| """ |
| P = str(p) |
| write_seq_num = struct.pack("!Q", write_seq_num) |
| A = write_seq_num + P[:5] |
| |
| c = TLCCiphertext() |
| c.type = p.type |
| c.version = p.version |
| c.fragment = alg.auth_encrypt(P, A) |
| c.len = len(c.fragment) |
| 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.fragment (and additional data) |
| in order to authenticate the data and decrypt c.fragment. 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. |
| l = p.len - alg.nonce_explicit_len - alg.tag_len |
| read_seq_num = struct.pack("!Q", read_seq_num) |
| A = read_seq_num + struct.pack('!BHH', p.type, p.version, l) |
| |
| p = TLSCompressed() |
| p.type = c.type |
| p.version = c.version |
| p.len = l |
| p.fragment = alg.auth_decrypt(A, c.fragment) |
| |
| if p.fragment is None: # Verification failed. |
| return None |
| return p |
| |