class connState(object): """ From RFC 5246, section 6.1: A TLS connection state is the operating environment of the TLS Record Protocol. It specifies a compression algorithm, an encryption algorithm, and a MAC algorithm. In addition, the parameters for these algorithms are known: the MAC key and the bulk encryption keys for the connection in both the read and the write directions. Logically, there are always four connection states outstanding: the current read and write states, and the pending read and write states. All records are processed under the current read and write states. The security parameters for the pending states can be set by the TLS Handshake Protocol, and the ChangeCipherSpec can selectively make either of the pending states current, in which case the appropriate current state is disposed of and replaced with the pending state; the pending state is then reinitialized to an empty state. It is illegal to make a state that has not been initialized with security parameters a current state. The initial current state always specifies that no encryption, compression, or MAC will be used. (For practical reasons, Scapy scraps these two last lines, through the implementation of dummy ciphers and MAC with TLS_NULL_WITH_NULL_NULL.) These attributes and behaviours are mostly mapped in this class. Also, note that Scapy may make a current state out of a pending state which has been initialized with dummy security parameters. We need this in order to know when the content of a TLS message is encrypted, whether we possess the right keys to decipher/verify it or not. For instance, when Scapy parses a CKE without knowledge of any secret, and then a CCS, it needs to know that the following Finished is encrypted and signed according to a new cipher suite, even though it cannot decipher the message nor verify its integrity. """ def __init__(self, connection_end="server", read_or_write="read", seq_num=0, compression_alg=Comp_NULL, ciphersuite=None, tls_version=0x0303): self.tls_version = tls_version # It is the user's responsibility to keep the record seq_num # under 2**64-1. If this value gets maxed out, the TLS class in # record.py will crash when trying to encode it with struct.pack(). self.seq_num = seq_num self.connection_end = connection_end self.row = read_or_write if ciphersuite is None: from scapy.layers.tls.crypto.suites import TLS_NULL_WITH_NULL_NULL ciphersuite = TLS_NULL_WITH_NULL_NULL self.ciphersuite = ciphersuite(tls_version=tls_version) if not self.ciphersuite.usable: warning("TLS ciphersuite not usable. Is the cryptography Python module installed ?") # noqa: E501 return self.compression = compression_alg() self.key_exchange = ciphersuite.kx_alg() self.cipher = ciphersuite.cipher_alg() self.hash = ciphersuite.hash_alg() if tls_version > 0x0200: if ciphersuite.cipher_alg.type == "aead": self.hmac = None self.mac_len = self.cipher.tag_len else: self.hmac = ciphersuite.hmac_alg() self.mac_len = self.hmac.hmac_len else: self.hmac = ciphersuite.hmac_alg() # should be Hmac_NULL self.mac_len = self.hash.hash_len if tls_version >= 0x0304: self.hkdf = TLS13_HKDF(self.hash.name.lower()) else: self.prf = PRF(ciphersuite.hash_alg.name, tls_version) def debug_repr(self, name, secret): if conf.debug_tls and secret: log_runtime.debug("TLS: %s %s %s: %s", self.connection_end, self.row, name, repr_hex(secret)) def derive_keys(self, client_random=b"", server_random=b"", master_secret=b""): # XXX Can this be called over a non-usable suite? What happens then? cs = self.ciphersuite # Derive the keys according to the cipher type and protocol version key_block = self.prf.derive_key_block(master_secret, server_random, client_random, cs.key_block_len) # When slicing the key_block, keep the right half of the material skip_first = False if ((self.connection_end == "client" and self.row == "read") or (self.connection_end == "server" and self.row == "write")): skip_first = True pos = 0 cipher_alg = cs.cipher_alg # MAC secret (for block and stream ciphers) if (cipher_alg.type == "stream") or (cipher_alg.type == "block"): start = pos if skip_first: start += cs.hmac_alg.key_len end = start + cs.hmac_alg.key_len mac_secret = key_block[start:end] self.debug_repr("mac_secret", mac_secret) pos += 2 * cs.hmac_alg.key_len else: mac_secret = None # Cipher secret start = pos if skip_first: start += cipher_alg.key_len end = start + cipher_alg.key_len cipher_secret = key_block[start:end] if cs.kx_alg.export: reqLen = cipher_alg.expanded_key_len cipher_secret = self.prf.postprocess_key_for_export(cipher_secret, client_random, server_random, self.connection_end, # noqa: E501 self.row, reqLen) self.debug_repr("cipher_secret", cipher_secret) pos += 2 * cipher_alg.key_len # Implicit IV (for block and AEAD ciphers) start = pos if cipher_alg.type == "block": if skip_first: start += cipher_alg.block_size end = start + cipher_alg.block_size elif cipher_alg.type == "aead": if skip_first: start += cipher_alg.fixed_iv_len end = start + cipher_alg.fixed_iv_len # Now we have the secrets, we can instantiate the algorithms if cs.hmac_alg is None: # AEAD self.hmac = None self.mac_len = cipher_alg.tag_len else: self.hmac = cs.hmac_alg(mac_secret) self.mac_len = self.hmac.hmac_len if cipher_alg.type == "stream": cipher = cipher_alg(cipher_secret) elif cipher_alg.type == "block": # We set an IV every time, even though it does not matter for # TLS 1.1+ as it requires an explicit IV. Indeed the cipher.iv # would get updated in TLS.post_build() or TLS.pre_dissect(). iv = key_block[start:end] if cs.kx_alg.export: reqLen = cipher_alg.block_size iv = self.prf.generate_iv_for_export(client_random, server_random, self.connection_end, self.row, reqLen) cipher = cipher_alg(cipher_secret, iv) self.debug_repr("block iv", iv) elif cipher_alg.type == "aead": fixed_iv = key_block[start:end] nonce_explicit_init = 0 # If you ever wanted to set a random nonce_explicit, use this: # exp_bit_len = cipher_alg.nonce_explicit_len * 8 # nonce_explicit_init = random.randint(0, 2**exp_bit_len - 1) cipher = cipher_alg(cipher_secret, fixed_iv, nonce_explicit_init) self.debug_repr("aead fixed iv", fixed_iv) self.cipher = cipher def sslv2_derive_keys(self, key_material): """ There is actually only one key, the CLIENT-READ-KEY or -WRITE-KEY. Note that skip_first is opposite from the one with SSLv3 derivation. Also, if needed, the IV should be set elsewhere. """ skip_first = True if ((self.connection_end == "client" and self.row == "read") or (self.connection_end == "server" and self.row == "write")): skip_first = False cipher_alg = self.ciphersuite.cipher_alg start = 0 if skip_first: start += cipher_alg.key_len end = start + cipher_alg.key_len cipher_secret = key_material[start:end] self.cipher = cipher_alg(cipher_secret) self.debug_repr("cipher_secret", cipher_secret) def tls13_derive_keys(self, key_material): cipher_alg = self.ciphersuite.cipher_alg key_len = cipher_alg.key_len iv_len = cipher_alg.fixed_iv_len write_key = self.hkdf.expand_label(key_material, b"key", b"", key_len) write_iv = self.hkdf.expand_label(key_material, b"iv", b"", iv_len) self.cipher = cipher_alg(write_key, write_iv) def snapshot(self): """ This is used mostly as a way to keep the cipher state and the seq_num. """ snap = connState(connection_end=self.connection_end, read_or_write=self.row, seq_num=self.seq_num, compression_alg=type(self.compression), ciphersuite=type(self.ciphersuite), tls_version=self.tls_version) snap.cipher = self.cipher.snapshot() if self.hmac: snap.hmac.key = self.hmac.key return snap def __repr__(self): def indent(s): if s and s[-1] == '\n': s = s[:-1] s = '\n'.join('\t' + x for x in s.split('\n')) + '\n' return s res = "Connection end : %s\n" % self.connection_end.upper() res += "Cipher suite : %s (0x%04x)\n" % (self.ciphersuite.name, self.ciphersuite.val) res += "Compression : %s (0x%02x)\n" % (self.compression.name, self.compression.val) tabsize = 4 return res.expandtabs(tabsize)
1) Client Write Key, 2) Client Write IV, 3) Client Write Mac Key, 4) Server Write Key, 5) Server Write IV, 6) Server Write Mac Key Hint: KeyBlock Layout is as the following - Byte00-Byte015: Server Write IV - Byte16-Byte031: Client Write IV - Byte32-Byte047: Server Write Key - Byte48-Byte063: Client Write Key - Byte64-Byte095: Server MAC Key - Byte96-Byte127: Client MAC Key """ key_block = prf.derive_key_block(master_secret, server_random, client_random, 128) # Your answer should be printed here print('Key Block: %s ' % bytearray(key_block)) # Your code goes here ''' server_write_IV = key_block[:16] client_write_IV = key_block[16:32] server_write_key = key_block[32:48] client_write_key = key_block[48:64] server_write_MAC_key = key_block[64:96] client_write_MAC_key = key_block[96:] ''' client_write_MAC_key = key_block[:32] server_write_MAC_key = key_block[32:64]
class connState(object): """ From RFC 5246, section 6.1: A TLS connection state is the operating environment of the TLS Record Protocol. It specifies a compression algorithm, an encryption algorithm, and a MAC algorithm. In addition, the parameters for these algorithms are known: the MAC key and the bulk encryption keys for the connection in both the read and the write directions. Logically, there are always four connection states outstanding: the current read and write states, and the pending read and write states. All records are processed under the current read and write states. The security parameters for the pending states can be set by the TLS Handshake Protocol, and the ChangeCipherSpec can selectively make either of the pending states current, in which case the appropriate current state is disposed of and replaced with the pending state; the pending state is then reinitialized to an empty state. It is illegal to make a state that has not been initialized with security parameters a current state. The initial current state always specifies that no encryption, compression, or MAC will be used. (For practical reasons, Scapy scraps these two last lines, through the implementation of dummy ciphers and MAC with TLS_NULL_WITH_NULL_NULL.) These attributes and behaviours are mostly mapped in this class. Also, note that Scapy may make a current state out of a pending state which has been initialized with dummy security parameters. We need this in order to know when the content of a TLS message is encrypted, whether we possess the right keys to decipher/verify it or not. For instance, when Scapy parses a CKE without knowledge of any secret, and then a CCS, it needs to know that the following Finished is encrypted and signed according to a new cipher suite, even though it cannot decipher the message nor verify its integrity. """ def __init__(self, connection_end="server", read_or_write="read", seq_num=0, compression_alg=Comp_NULL, ciphersuite=None, tls_version=0x0303): self.tls_version = tls_version # It is the user's responsibility to keep the record seq_num # under 2**64-1. If this value gets maxed out, the TLS class in # record.py will crash when trying to encode it with struct.pack(). self.seq_num = seq_num self.connection_end = connection_end self.row = read_or_write if ciphersuite is None: from scapy.layers.tls.crypto.suites import TLS_NULL_WITH_NULL_NULL ciphersuite = TLS_NULL_WITH_NULL_NULL self.ciphersuite = ciphersuite(tls_version=tls_version) self.compression = compression_alg() self.key_exchange = ciphersuite.kx_alg() self.cipher = ciphersuite.cipher_alg() self.hash = ciphersuite.hash_alg() if tls_version > 0x0200: if ciphersuite.cipher_alg.type == "aead": self.hmac = None self.mac_len = self.cipher.tag_len else: self.hmac = ciphersuite.hmac_alg() self.mac_len = self.hmac.hmac_len else: self.hmac = ciphersuite.hmac_alg() # should be Hmac_NULL self.mac_len = self.hash.hash_len if tls_version >= 0x0304: self.hkdf = TLS13_HKDF(self.hash.name.lower()) else: self.prf = PRF(ciphersuite.hash_alg.name, tls_version) def debug_repr(self, name, secret): if conf.debug_tls and secret: print("%s %s %s: %s" % (self.connection_end, self.row, name, repr_hex(secret))) def derive_keys(self, client_random="", server_random="", master_secret=""): #XXX Can this be called over a non-usable suite? What happens then? cs = self.ciphersuite # Derive the keys according to the cipher type and protocol version key_block = self.prf.derive_key_block(master_secret, server_random, client_random, cs.key_block_len) # When slicing the key_block, keep the right half of the material skip_first = False if ((self.connection_end == "client" and self.row == "read") or (self.connection_end == "server" and self.row == "write")): skip_first = True pos = 0 cipher_alg = cs.cipher_alg ### MAC secret (for block and stream ciphers) if (cipher_alg.type == "stream") or (cipher_alg.type == "block"): start = pos if skip_first: start += cs.hmac_alg.key_len end = start + cs.hmac_alg.key_len mac_secret = key_block[start:end] self.debug_repr("mac_secret", mac_secret) pos += 2*cs.hmac_alg.key_len else: mac_secret = None ### Cipher secret start = pos if skip_first: start += cipher_alg.key_len end = start + cipher_alg.key_len cipher_secret = key_block[start:end] if cs.kx_alg.export: reqLen = cipher_alg.expanded_key_len cipher_secret = self.prf.postprocess_key_for_export(cipher_secret, client_random, server_random, self.connection_end, self.row, reqLen) self.debug_repr("cipher_secret", cipher_secret) pos += 2*cipher_alg.key_len ### Implicit IV (for block and AEAD ciphers) start = pos if cipher_alg.type == "block": if skip_first: start += cipher_alg.block_size end = start + cipher_alg.block_size elif cipher_alg.type == "aead": if skip_first: start += cipher_alg.fixed_iv_len end = start + cipher_alg.fixed_iv_len ### Now we have the secrets, we can instantiate the algorithms if cs.hmac_alg is None: # AEAD self.hmac = None self.mac_len = cipher_alg.tag_len else: self.hmac = cs.hmac_alg(mac_secret) self.mac_len = self.hmac.hmac_len if cipher_alg.type == "stream": cipher = cipher_alg(cipher_secret) elif cipher_alg.type == "block": # We set an IV every time, even though it does not matter for # TLS 1.1+ as it requires an explicit IV. Indeed the cipher.iv # would get updated in TLS.post_build() or TLS.pre_dissect(). iv = key_block[start:end] if cs.kx_alg.export: reqLen = cipher_alg.block_size iv = self.prf.generate_iv_for_export(client_random, server_random, self.connection_end, self.row, reqLen) cipher = cipher_alg(cipher_secret, iv) self.debug_repr("block iv", iv) elif cipher_alg.type == "aead": fixed_iv = key_block[start:end] nonce_explicit_init = 0 # If you ever wanted to set a random nonce_explicit, use this: #exp_bit_len = cipher_alg.nonce_explicit_len * 8 #nonce_explicit_init = random.randint(0, 2**exp_bit_len - 1) cipher = cipher_alg(cipher_secret, fixed_iv, nonce_explicit_init) self.debug_repr("aead fixed iv", fixed_iv) self.cipher = cipher def sslv2_derive_keys(self, key_material): """ There is actually only one key, the CLIENT-READ-KEY or -WRITE-KEY. Note that skip_first is opposite from the one with SSLv3 derivation. Also, if needed, the IV should be set elsewhere. """ skip_first = True if ((self.connection_end == "client" and self.row == "read") or (self.connection_end == "server" and self.row == "write")): skip_first = False cipher_alg = self.ciphersuite.cipher_alg start = 0 if skip_first: start += cipher_alg.key_len end = start + cipher_alg.key_len cipher_secret = key_material[start:end] self.cipher = cipher_alg(cipher_secret) self.debug_repr("cipher_secret", cipher_secret) def tls13_derive_keys(self, key_material): cipher_alg = self.ciphersuite.cipher_alg key_len = cipher_alg.key_len iv_len = cipher_alg.fixed_iv_len write_key = self.hkdf.expand_label(key_material, "key", "", key_len) write_iv = self.hkdf.expand_label(key_material, "iv", "", iv_len) self.cipher = cipher_alg(write_key, write_iv) def snapshot(self): """ This is used mostly as a way to keep the cipher state and the seq_num. """ snap = connState(connection_end=self.connection_end, read_or_write=self.row, seq_num=self.seq_num, compression_alg=type(self.compression), ciphersuite=type(self.ciphersuite), tls_version=self.tls_version) snap.cipher = self.cipher.snapshot() if self.hmac: snap.hmac.key = self.hmac.key return snap def __repr__(self): def indent(s): if s and s[-1] == '\n': s = s[:-1] s = '\n'.join('\t' + x for x in s.split('\n')) + '\n' return s res = "Connection end : %s\n" % self.connection_end.upper() res += "Cipher suite : %s (0x%04x)\n" % (self.ciphersuite.name, self.ciphersuite.val) res += "Compression : %s (0x%02x)\n" % (self.compression.name, self.compression.val) tabsize = 4 return res.expandtabs(tabsize)
class connState(object): """ From RFC 5246, section 6.1: A TLS connection state is the operating environment of the TLS Record Protocol. It specifies a compression algorithm, an encryption algorithm, and a MAC algorithm. In addition, the parameters for these algorithms are known: the MAC key and the bulk encryption keys for the connection in both the read and the write directions. Logically, there are always four connection states outstanding: the current read and write states, and the pending read and write states. All records are processed under the current read and write states. The security parameters for the pending states can be set by the TLS Handshake Protocol, and the ChangeCipherSpec can selectively make either of the pending states current, in which case the appropriate current state is disposed of and replaced with the pending state; the pending state is then reinitialized to an empty state. It is illegal to make a state that has not been initialized with security parameters a current state. The initial current state always specifies that no encryption, compression, or MAC will be used. These attributes and behaviours are mostly mapped in this class. Also, note that scapy may make a current state out of a pending state which has been initialized with dummy security parameters. We need this in order to know when the content of a TLS message is encrypted, whether we possess the right keys to decipher/verify it or not. For instance, when scapy parses a CKE without knowledge of any secret, and then a CCS, it needs to know that the following Finished is encrypted and signed according to a new cipher suite, even though it cannot decipher the message nor verify its integrity. """ def __init__(self, connection_end="client", read_or_write="read", compression_alg=Comp_NULL, ciphersuite=None, tls_version=0x0303): # It is the user's responsibility to keep the record seq_num # under 2**64-1. If this value gets maxed out, the TLS class in # record.py will crash when trying to encode it with struct.pack(). self.seq_num = 0 self.connection_end = connection_end self.row = read_or_write if ciphersuite is None: from scapy.layers.tls.crypto.suites import TLS_NULL_WITH_NULL_NULL ciphersuite = TLS_NULL_WITH_NULL_NULL self.ciphersuite = ciphersuite(tls_version=tls_version) self.compression = compression_alg() self.key_exchange = ciphersuite.kx_alg() self.prf = PRF(ciphersuite.hash_alg.name, tls_version) # The attributes below usually get updated by .derive_keys() # As discussed, we need to initialize cipher and mac with dummy values. self.master_secret = None # 48-byte shared secret self.cipher_secret = None # key for the symmetric cipher self.mac_secret = None # key for the MAC (stays None for AEAD) self.cipher = ciphersuite.cipher_alg() if ciphersuite.hmac_alg is None: # AEAD self.hmac = None self.mac_len = self.cipher.tag_len else: self.hmac = ciphersuite.hmac_alg() self.mac_len = self.hmac.hmac_len def debug_repr(self, name, secret): if conf.debug_tls and secret: print("%s %s %s: %s" % (self.connection_end, self.row, name, repr_hex(secret))) def derive_keys(self, client_random="", server_random="", master_secret=""): cs = self.ciphersuite self.master_secret = master_secret # Derive the keys according to the cipher type and protocol version key_block = self.prf.derive_key_block(master_secret, server_random, client_random, cs.key_block_len) # When slicing the key_block, keep the right half of the material skip_first = False if ((self.connection_end == "client" and self.row == "read") or (self.connection_end == "server" and self.row == "write")): skip_first = True pos = 0 cipher_alg = cs.cipher_alg ### MAC secret (for block and stream ciphers) if (cipher_alg.type == "stream") or (cipher_alg.type == "block"): start = pos if skip_first: start += cs.hmac_alg.key_len end = start + cs.hmac_alg.key_len self.mac_secret = key_block[start:end] self.debug_repr("mac_secret", self.mac_secret) pos += 2 * cs.hmac_alg.key_len else: self.mac_secret = None ### Cipher secret start = pos if skip_first: start += cipher_alg.key_len end = start + cipher_alg.key_len key = key_block[start:end] if cs.kx_alg.export: reqLen = cipher_alg.expanded_key_len key = self.prf.postprocess_key_for_export(key, client_random, server_random, self.connection_end, self.row, reqLen) self.cipher_secret = key self.debug_repr("cipher_secret", self.cipher_secret) pos += 2 * cipher_alg.key_len ### Implicit IV (for block and AEAD ciphers) start = pos if cipher_alg.type == "block": if skip_first: start += cipher_alg.block_size end = start + cipher_alg.block_size elif cipher_alg.type == "aead": if skip_first: start += cipher_alg.salt_len end = start + cipher_alg.salt_len ### Now we have the secrets, we can instantiate the algorithms if cs.hmac_alg is None: # AEAD self.hmac = None self.mac_len = cipher_alg.tag_len else: self.hmac = cs.hmac_alg(self.mac_secret) self.mac_len = self.hmac.hmac_len if cipher_alg.type == "stream": cipher = cipher_alg(self.cipher_secret) elif cipher_alg.type == "block": # We set an IV every time, even though it does not matter for # TLS 1.1+ as it requires an explicit IV. Indeed the cipher.iv # would get updated in TLS.post_build() or TLS.pre_dissect(). iv = key_block[start:end] if cs.kx_alg.export: reqLen = cipher_alg.block_size iv = self.prf.generate_iv_for_export(client_random, server_random, self.connection_end, self.row, reqLen) cipher = cipher_alg(self.cipher_secret, iv) self.debug_repr("block iv", iv) elif cipher_alg.type == "aead": salt = key_block[start:end] nonce_explicit_init = 0 # If you ever wanted to set a random nonce_explicit, use this: #exp_bit_len = cipher_alg.nonce_explicit_len * 8 #nonce_explicit_init = random.randint(0, 2**exp_bit_len - 1) cipher = cipher_alg(self.cipher_secret, salt, nonce_explicit_init) self.debug_repr("aead salt", salt) self.cipher = cipher def __repr__(self): def indent(s): if s and s[-1] == '\n': s = s[:-1] s = '\n'.join('\t' + x for x in s.split('\n')) + '\n' return s res = "Connection end : %s\n" % self.connection_end.upper() res += "Cipher suite : %s (0x%04x)\n" % (self.ciphersuite.name, self.ciphersuite.val) res += "Compression Alg: %s (0x%02x)\n" % (self.compression.name, self.compression.val) tabsize = 4 return res.expandtabs(tabsize)
4) Server Write Key, 5) Server Write IV, 6) Server Write Mac Key Hint: KeyBlock Layout is as the following - Byte000-Byte031: Client MAC Key - Byte032-Byte063: Server MAC Key - Byte064-Byte079: Client Write Key - Byte080-Byte095: Server Write Key - Byte096-Byte111: Client Write IV - Byte112-Byte127: Server Write IV """ key_block = f.derive_key_block(master_secret, hex_to_data(server_random), hex_to_data(client_random), 128) # Your answer should be printed here print('Key Block: %s \n' % data_to_hex(key_block)) # Your code goes here server_write_IV = data_to_hex(key_block[112:128]) client_write_IV = data_to_hex(key_block[96:112]) server_write_key = data_to_hex(key_block[80:96]) client_write_key = data_to_hex(key_block[64:80]) server_write_MAC_key = data_to_hex(key_block[32:64]) client_write_MAC_key = data_to_hex(key_block[0:32]) # Your answer should be printed here print('Server Write IV: %s' % server_write_IV)