Commit 75aeff12 authored by Philipp Winter's avatar Philipp Winter
Browse files

Improve packet morphing algorithm.

We only want to run the packet morphing algorithm when we really need
it -- which is immediately before we send data.  Previously, we would morph
immediately upon receiving data which is not optimal.

This should fix <https://bugs.torproject.org/10991>.  Thanks to Yawning Angel
who pointed out the problem.
parent 4b7d7e5c
Loading
Loading
Loading
Loading
+32 −5
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ morphed network data should then match the probability distribution.

import random

import message
import probdist
import const

@@ -39,14 +40,37 @@ class PacketMorpher( object ):
            self.dist = probdist.new(lambda: random.randint(const.HDR_LENGTH,
                                                            const.MTU))

    def getPadding( self, sendCrypter, sendHMAC, dataLen ):
        """
        Based on the burst's size, return a ready-to-send padding blurb.
        """

        padLen = self.calcPadding(dataLen)

        assert const.HDR_LENGTH <= padLen < (const.MTU + const.HDR_LENGTH), \
               "Invalid padding length %d." % padLen

        # We have to use two padding messages if the padding is > MTU.
        if padLen > const.MTU:
            padMsgs = [message.new("", paddingLen=700 - const.HDR_LENGTH),
                       message.new("", paddingLen=padLen - 700 - \
                                       const.HDR_LENGTH)]
        else:
            padMsgs = [message.new("", paddingLen=padLen - const.HDR_LENGTH)]

        blurbs = [msg.encryptAndHMAC(sendCrypter, sendHMAC) for msg in padMsgs]

        return "".join(blurbs)

    def calcPadding( self, dataLen ):
        """
        Based on `dataLen', determines the padding for a network packet.
        Based on `dataLen', determine and return a burst's padding.

        ScrambleSuit morphs packets which are smaller than the link's MTU.
        This method draws a random sample from the probability distribution
        which is used to determine and return the padding for such packets.
        This effectively gets rid of Tor's 586-byte signature.
        ScrambleSuit morphs the last packet in a burst, i.e., packets which
        don't fill the link's MTU.  This is done by drawing a random sample
        from our probability distribution which is used to determine and return
        the padding for such packets.  This effectively gets rid of Tor's
        586-byte signature.
        """

        # The `is' and `should-be' length of the burst's last packet.
@@ -59,6 +83,9 @@ class PacketMorpher( object ):
        else:
            padLen = (const.MTU - dataLen) + sampleLen

        if padLen < const.HDR_LENGTH:
            padLen += const.MTU

        log.debug("Morphing the last %d-byte packet to %d bytes by adding %d "
                  "bytes of padding." %
                  (dataLen % const.MTU, sampleLen, padLen))
+12 −19
Original line number Diff line number Diff line
@@ -234,27 +234,12 @@ class ScrambleSuitTransport( base.BaseTransport ):

        # Wrap the application's data in ScrambleSuit protocol messages.
        messages = message.createProtocolMessages(data, flags=flags)

        # Let the packet morpher tell us how much we should pad.
        paddingLen = self.pktMorpher.calcPadding(sum([len(msg) for
                                                 msg in messages]))

        # If padding > header length, a single message will do...
        if paddingLen > const.HDR_LENGTH:
            messages.append(message.new("", paddingLen=paddingLen -
                                                       const.HDR_LENGTH))

        # ...otherwise, we use two padding-only messages.
        else:
            messages.append(message.new("", paddingLen=const.MPU -
                                                       const.HDR_LENGTH))
            messages.append(message.new("", paddingLen=paddingLen))

        blurb = "".join([msg.encryptAndHMAC(self.sendCrypter,
                        self.sendHMAC) for msg in messages])

        # Flush data chunk for chunk to obfuscate inter arrival times.
        # Flush data chunk for chunk to obfuscate inter-arrival times.
        if const.USE_IAT_OBFUSCATION:

            if len(self.choppingBuf) == 0:
                self.choppingBuf.write(blurb)
                reactor.callLater(self.iatMorpher.randomSample(),
@@ -262,8 +247,12 @@ class ScrambleSuitTransport( base.BaseTransport ):
            else:
                # flushPieces() is still busy processing the chopping buffer.
                self.choppingBuf.write(blurb)

        else:
            self.circuit.downstream.write(blurb)
            padBlurb = self.pktMorpher.getPadding(self.sendCrypter,
                                                  self.sendHMAC,
                                                  len(blurb))
            self.circuit.downstream.write(blurb + padBlurb)

    def flushPieces( self ):
        """
@@ -283,7 +272,11 @@ class ScrambleSuitTransport( base.BaseTransport ):

        # Drain and send whatever is left in the output buffer.
        else:
            self.circuit.downstream.write(self.choppingBuf.read())
            blurb = self.choppingBuf.read()
            padBlurb = self.pktMorpher.getPadding(self.sendCrypter,
                                                  self.sendHMAC,
                                                  len(blurb))
            self.circuit.downstream.write(blurb + padBlurb)
            return

        reactor.callLater(self.iatMorpher.randomSample(), self.flushPieces)
+36 −0
Original line number Diff line number Diff line
@@ -11,6 +11,8 @@ import shutil
import tempfile
import ticket
import state
import packetmorpher
import probdist

import message

@@ -411,6 +413,40 @@ class TicketTest( unittest.TestCase ):
            else:
                self.assertTrue(ss.receiveTicket(buf))

class PacketMorpher( unittest.TestCase ):

    def test1_calcPadding( self ):

        def checkDistribution( dist ):
            pm = packetmorpher.new(dist)
            for i in xrange(0, const.MTU + 2):
                padLen = pm.calcPadding(i)
                self.assertTrue(const.HDR_LENGTH <= \
                                padLen < \
                                (const.MTU + const.HDR_LENGTH))

        # Test randomly generated distributions.
        for i in xrange(0, 100):
            checkDistribution(None)

        # Test border-case distributions.
        checkDistribution(probdist.new(lambda: 0))
        checkDistribution(probdist.new(lambda: 1))
        checkDistribution(probdist.new(lambda: const.MTU))
        checkDistribution(probdist.new(lambda: const.MTU + 1))

    def test2_getPadding( self ):
        pm = packetmorpher.new()
        sendCrypter = mycrypto.PayloadCrypter()
        sendCrypter.setSessionKey("A" * 32,  "A" * 8)
        sendHMAC = "A" * 32

        for i in xrange(0, const.MTU + 2):
            padLen = len(pm.getPadding(sendCrypter, sendHMAC, i))
            self.assertTrue(const.HDR_LENGTH <= padLen < const.MTU + \
                            const.HDR_LENGTH)


if __name__ == '__main__':
    # Disable all logging as it would yield plenty of warning and error
    # messages.