Esempio n. 1
0
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)
Esempio n. 2
0
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)
Esempio n. 3
0
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)