diff --git a/scapy/layers/tls/handshake.py b/scapy/layers/tls/handshake.py index 65458e9e53c..e504b97fcee 100644 --- a/scapy/layers/tls/handshake.py +++ b/scapy/layers/tls/handshake.py @@ -49,7 +49,8 @@ TLS_Ext_SupportedVersion_SH, TLS_Ext_EarlyDataIndication, _tls_hello_retry_magic, - TLS_Ext_ExtendedMasterSecret) + TLS_Ext_ExtendedMasterSecret, + TLS_Ext_EncryptThenMAC) from scapy.layers.tls.keyexchange import (_TLSSignature, _TLSServerParamsField, _TLSSignatureField, ServerRSAParams, SigAndHashAlgsField, _tls_hash_sig, @@ -542,12 +543,12 @@ def tls_session_update(self, msg_str): s.server_random = self.random_bytes s.sid = self.sid - # EXTMS if self.ext: for e in self.ext: if isinstance(e, TLS_Ext_ExtendedMasterSecret): self.tls_session.extms = True - break + if isinstance(e, TLS_Ext_EncryptThenMAC): + self.tls_session.encrypt_then_mac = True cs_cls = None if self.cipher: diff --git a/scapy/layers/tls/record.py b/scapy/layers/tls/record.py index d0031e909cc..992eb9f4432 100644 --- a/scapy/layers/tls/record.py +++ b/scapy/layers/tls/record.py @@ -445,9 +445,32 @@ def pre_dissect(self, s): cipher_type = self.tls_session.rcs.cipher.type + def extract_mac(data): + """Extract MAC.""" + tmp_len = self.tls_session.rcs.mac_len + if tmp_len != 0: + frag, mac = data[:-tmp_len], data[-tmp_len:] + else: + frag, mac = data, b"" + return frag, mac + + def verify_mac(hdr, cfrag, mac): + """Verify integrity.""" + chdr = hdr[:3] + struct.pack('!H', len(cfrag)) + is_mac_ok = self._tls_hmac_verify(chdr, cfrag, mac) + if not is_mac_ok: + pkt_info = self.firstlayer().summary() + log_runtime.info( + "TLS: record integrity check failed [%s]", pkt_info, + ) + if cipher_type == 'block': version = struct.unpack("!H", s[1:3])[0] + if self.tls_session.encrypt_then_mac: + efrag, mac = extract_mac(efrag) + verify_mac(hdr, efrag, mac) + # Decrypt try: if version >= 0x0302: @@ -481,19 +504,11 @@ def pre_dissect(self, s): mfrag, pad = pfrag[:-padlen], pfrag[-padlen:] self.padlen = padlen - # Extract MAC - tmp_len = self.tls_session.rcs.mac_len - if tmp_len != 0: - cfrag, mac = mfrag[:-tmp_len], mfrag[-tmp_len:] + if self.tls_session.encrypt_then_mac: + cfrag = mfrag else: - cfrag, mac = mfrag, b"" - - # Verify integrity - chdr = hdr[:3] + struct.pack('!H', len(cfrag)) - is_mac_ok = self._tls_hmac_verify(chdr, cfrag, mac) - if not is_mac_ok: - pkt_info = self.firstlayer().summary() - log_runtime.info("TLS: record integrity check failed [%s]", pkt_info) # noqa: E501 + cfrag, mac = extract_mac(mfrag) + verify_mac(hdr, cfrag, mac) elif cipher_type == 'stream': # Decrypt @@ -504,21 +519,8 @@ def pre_dissect(self, s): cfrag = e.args[0] else: decryption_success = True - mfrag = pfrag - - # Extract MAC - tmp_len = self.tls_session.rcs.mac_len - if tmp_len != 0: - cfrag, mac = mfrag[:-tmp_len], mfrag[-tmp_len:] - else: - cfrag, mac = mfrag, b"" - - # Verify integrity - chdr = hdr[:3] + struct.pack('!H', len(cfrag)) - is_mac_ok = self._tls_hmac_verify(chdr, cfrag, mac) - if not is_mac_ok: - pkt_info = self.firstlayer().summary() - log_runtime.info("TLS: record integrity check failed [%s]", pkt_info) # noqa: E501 + cfrag, mac = extract_mac(pfrag) + verify_mac(hdr, cfrag, mac) elif cipher_type == 'aead': # Authenticated encryption @@ -671,7 +673,8 @@ def post_build(self, pkt, pay): if cipher_type == 'block': # Integrity - mfrag = self._tls_hmac_add(hdr, cfrag) + if not self.tls_session.encrypt_then_mac: + cfrag = self._tls_hmac_add(hdr, cfrag) # Excerpt below better corresponds to TLS 1.1 IV definition, # but the result is the same as with TLS 1.2 anyway. @@ -681,7 +684,7 @@ def post_build(self, pkt, pay): # mfrag = iv + mfrag # Add padding - pfrag = self._tls_pad(mfrag) + pfrag = self._tls_pad(cfrag) # Encryption if self.version >= 0x0302: @@ -695,6 +698,9 @@ def post_build(self, pkt, pay): # Implicit IV for SSLv3 and TLS 1.0 efrag = self._tls_encrypt(pfrag) + if self.tls_session.encrypt_then_mac: + efrag = self._tls_hmac_add(hdr, efrag) + elif cipher_type == "stream": # Integrity mfrag = self._tls_hmac_add(hdr, cfrag) diff --git a/scapy/layers/tls/session.py b/scapy/layers/tls/session.py index 6ab6d52fe60..e3b342e25d7 100644 --- a/scapy/layers/tls/session.py +++ b/scapy/layers/tls/session.py @@ -457,6 +457,8 @@ def __init__(self, self.extms = False self.session_hash = None + self.encrypt_then_mac = False + # All exchanged TLS packets. # XXX no support for now # self.exchanged_pkts = [] diff --git a/test/tls.uts b/test/tls.uts index 40e80840392..4a583b715af 100644 --- a/test/tls.uts +++ b/test/tls.uts @@ -1030,6 +1030,32 @@ l3 = r3.getlayer(TLS, 3) assert isinstance(l3.msg[0], TLSFinished) assert l3.msg[0][TLSFinished].vdata == b'\x15\xd6\xd5\xea\x84\xee\xb3\xdd\xd6\x10\xd8\x11' += Reading TLS test session - Encrypt-then-MAC extension +from scapy.layers.tls.cert import PrivKey +from scapy.layers.tls.handshake import TLSFinished +from scapy.layers.tls.record import TLS + +client_hello = hex_bytes(b'16030100c9010000c50303611a2f42b70345cfbc5c5c4da1929bea8a2cb8b1fd10ab1341e43ffaa8856a63000038c02cc030009fcca9cca8ccaac02bc02f009ec024c028006bc023c0270067c00ac0140039c009c0130033009d009c003d003c0035002f00ff01000064000b000403000102000a000c000a001d0017001e00190018337400000010000e000c02683208687474702f312e310016000000170000000d002a0028040305030603080708080809080a080b080408050806040105010601030303010302040205020602') +server_hello = hex_bytes(b'1603030059020000550303a22c975875df69bea936cbd28b083cde754693b4f34a15a036e5e57b7f4755cf20226e6386f90e3751723beea9196640d5bbe6c7c9f314568fa3645cb7218e9159003d00000dff010001000016000000170000160303036e0b00036a0003670003643082036030820248a003020102020900eb73b71c3e2f9fdc300d06092a864886f70d01010b05003045310b30090603550406130241553113301106035504080c0a536f6d652d53746174653121301f060355040a0c18496e7465726e6574205769646769747320507479204c7464301e170d3139303231353135313430335a170d3239303231323135313430335a3045310b30090603550406130241553113301106035504080c0a536f6d652d53746174653121301f060355040a0c18496e7465726e6574205769646769747320507479204c746430820122300d06092a864886f70d01010105000382010f003082010a0282010100d2f7d36b233a5619368fc3a7db0f2364dc71986dd4eebcbee85b783e139cfeb0a80de50147c936aa84230e2fa2eb91ef1737410387b932440ac7cfda3c5966eef88f688bcbee6a5d0dfaa075b77dfe836f2cb318375ff5be2b35f6d62dd7c4b147224f67d53d95c7fcc11cda7bc622369b4d4a685655169fb28f66e511724f0c9af2f74ea4cdf09b92f917246a582f67fade3eff7eca2c794d713c13f80cd53f847aa196d0adc04494790a628e327f4b53d05b83025c3ea541195f953ce6fc37edcc68a8fd6eca621f38bc08bc2d8d72cfcdf85c68f9f4f4485b32133c63299f85ffd62bde5a9d585e5a896f08319448277f19e86d5d6878bc53768b2ef9b3210203010001a3533051301d0603551d0e041604148297fb8cf3d9ddf2f60ec90f92815c28e3f3ff35301f0603551d230418301680148297fb8cf3d9ddf2f60ec90f92815c28e3f3ff35300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003820101003c001f0f5d106072e0beedfa47895329d4623422080e66caa4beb4c3d9b6868fa107467d6160e512ede7cbbefdeb17c09b9f594f86f1a9922c982ccf9655d4d67eda061f667eeb25fe7203bea00e40f150a490a78ca963d87c185ade8b7c294f5052fb1e1edb3403831e33e4026c8e56717cb77bc32321858be37a77fe7f5511ef8c2013c86e2730c5b2366875131cae0c7616fdc7c8605696133e7a685f20203c0db8e0ff1d1a5991d2f058f48f20b10a5fb0df27a1f9874cc0fe8d6ebf77e9a7ba38490e9d63241a0fb3fd7701ff3b130c9aa7aa77770280b7003c1bb5e0784c34aacb74ce8114960e50eee04602a7ab20e5c878028e4292e90e40fd631fee16030300040e000000') +client_finished = hex_bytes(b'1603030106100001020100482bf86fa7047c767ecc5f46e971f2349232d57d4c40b04856b6ea2b5645b5b233c0cd2ad7b05101d6a3fcbd2698b25064501ba4f0cde40c8189abc29aebfffcb87413d4590cae7cf3589fa371ad5e0d161da9c275a4b8ca1aa9a400a3d76021f92b872403a72a22bad6368276010209ca1344971adf7d7a9cdeefd534cd933ec3d2852ea1dfff217f7cd55eac7d2b18f7c5600c56f28746389d1d6c33cd2ac24817632fc0fbd81ffcf528b1c2a5b328a0105e88513e6b2f95b51ca3adf390146662115a721bfd718eae3033388aaa5cb37e2c16428a6f7c994f961137f6a7f933327ed300f15621500d427d261f39970bbf40f4ba303963609439007d34e6bc1403030001011603030050f4b7962d5455e9244efe886bbd4156ca20936e4b8868d80c82b06ceac7cff6d69f130a610f2aa4c4fd8cb2681f84e3ebecad1b563bcd258255aa509ba2b6388f90ac5f1c1f84f1569dc3809667b86ba4') +server_finished = hex_bytes(b'14030300010116030300509e8e5fd6aebaa98263e98266fffcf7fd21eb50fb0510b8598660afb65c57a025374c1e63aff3e260dd5d027180e8aa0d85d43e0c0b54e8783e4ce51a71ef0ae555ab81404020342ca1a34643ce713688') + +# Load TLS session +r1 = TLS(client_hello) +r1.tls_session.server_rsa_key = ssl_key +r2 = TLS(server_hello, tls_session=r1.tls_session.mirror()) +r3 = TLS(client_finished, tls_session=r2.tls_session.mirror()) +r4 = TLS(server_finished, tls_session=r3.tls_session.mirror()) + +client_finished = r3.getlayer(TLS, 3).msg[0] +server_finished = r4.getlayer(TLS, 2).msg[0] + +assert r4.tls_session.encrypt_then_mac +assert isinstance(client_finished, TLSFinished) +assert isinstance(server_finished, TLSFinished) +assert client_finished.vdata == hex_bytes(b'771049b4ff714ac71253f84f') +assert server_finished.vdata == hex_bytes(b'42c9765e833997b6714fec75') + ### ### Other/bug tests ### @@ -1230,6 +1256,19 @@ ch.ext = [ext1, ext2, ext3, ext4, ext5, ext6, ext7, ext8, ext9] t = TLS(type='handshake', version='TLS 1.0', msg=ch) raw(t) == b'\x16\x03\x01\x00\xc7\x01\x00\x00\xc3\x03\x03&\xee-\xddX\xe1\xb1T\xaa\xb1\x0b\xa0zlg\xf8\xd14]%\xa9\x91d\x08\xc7t\xcd6\xd4"\x9f\xcf\x00\x00\x16\xc0+\xc0/\xc0\n\xc0\t\xc0\x13\xc0\x14\x003\x009\x00/\x005\x00\n\x01\x00\x00\x84\x00\x00\x00\x11\x00\x0f\x00\x00\x0cmn.scapy.wtv\xff\x01\x00\x01\x00\x00\n\x00\x08\x00\x06\x00\x17\x00\x18\x00\x19\x00\x0b\x00\x02\x01\x00\x00#\x00\x003t\x00\x00\x00\x10\x00)\x00\'\x05h2-16\x05h2-15\x05h2-14\x02h2\x08spdy/3.1\x08http/1.1\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\r\x00\x16\x00\x14\x04\x01\x05\x01\x06\x01\x02\x01\x04\x03\x05\x03\x06\x03\x02\x03\x04\x02\x02\x02' += Building packets - application data with Encrypt-then-MAC +session = tlsSession( + rcs=connState(ciphersuite=TLS_RSA_WITH_AES_256_CBC_SHA256), + wcs=connState(ciphersuite=TLS_RSA_WITH_AES_256_CBC_SHA256), +) +session.encrypt_then_mac = True +session.tls_version = 0x0303 +session.rcs.cipher.key = b'A' * 32 +session.wcs.cipher.key = b'A' * 32 +payload = b'PAYLOAD' +tlsdata = TLS(msg=TLSApplicationData(data=payload), tls_session=session) +t = TLS(raw(tlsdata), tls_session=session.mirror()) +assert t[0].msg[0].data == payload = Building packets - ServerHello context linking from scapy.layers.tls.crypto.kx_algs import KX_ECDHE_RSA