[Pkg-privacy-commits] [obfsproxy] 221/353: Import the ScrambleSuit transport protocol.

Ximin Luo infinity0 at moszumanska.debian.org
Sat Aug 22 13:02:02 UTC 2015


This is an automated email from the git hooks/post-receive script.

infinity0 pushed a commit to branch master
in repository obfsproxy.

commit 6fc79b981e384618b7bba313826df72a6faa3e5c
Author: Philipp Winter <phw at torproject.org>
Date:   Tue Feb 4 00:16:09 2014 +0100

    Import the ScrambleSuit transport protocol.
    
    ScrambleSuit implements a superset of the obfs3 protocol.  Its original
    repository is available here:
    <https://gitweb.torproject.org/user/phw/scramblesuit.git>
    The project web site is available here:
    <http://www.cs.kau.se/philwint/scramblesuit/>
---
 .../scramblesuit}/__init__.py                      |   0
 obfsproxy/transports/scramblesuit/const.py         | 106 ++++
 obfsproxy/transports/scramblesuit/fifobuf.py       | 121 ++++
 obfsproxy/transports/scramblesuit/message.py       | 226 ++++++++
 obfsproxy/transports/scramblesuit/mycrypto.py      | 155 +++++
 obfsproxy/transports/scramblesuit/packetmorpher.py |  69 +++
 obfsproxy/transports/scramblesuit/probdist.py      |  98 ++++
 obfsproxy/transports/scramblesuit/replay.py        |  85 +++
 obfsproxy/transports/scramblesuit/scramblesuit.py  | 624 +++++++++++++++++++++
 obfsproxy/transports/scramblesuit/state.py         | 161 ++++++
 obfsproxy/transports/scramblesuit/ticket.py        | 391 +++++++++++++
 obfsproxy/transports/scramblesuit/uniformdh.py     | 185 ++++++
 obfsproxy/transports/scramblesuit/util.py          | 175 ++++++
 obfsproxy/transports/transports.py                 |   4 +
 14 files changed, 2400 insertions(+)

diff --git a/obfsproxy/common/__init__.py b/obfsproxy/transports/scramblesuit/__init__.py
similarity index 100%
copy from obfsproxy/common/__init__.py
copy to obfsproxy/transports/scramblesuit/__init__.py
diff --git a/obfsproxy/transports/scramblesuit/const.py b/obfsproxy/transports/scramblesuit/const.py
new file mode 100644
index 0000000..ca6f8ea
--- /dev/null
+++ b/obfsproxy/transports/scramblesuit/const.py
@@ -0,0 +1,106 @@
+"""
+This module defines constant values for the ScrambleSuit protocol.
+
+While some values can be changed, in general they should not.  If you do not
+obey, be at least careful because the protocol could easily break.
+"""
+
+# Length of the key of the HMAC which used to authenticate tickets in bytes.
+TICKET_HMAC_KEY_LENGTH = 32
+
+# Length of the AES key used to encrypt tickets in bytes.
+TICKET_AES_KEY_LENGTH = 16
+
+# Length of the IV for AES-CBC which is used to encrypt tickets in bytes.
+TICKET_AES_CBC_IV_LENGTH = 16
+
+# Directory where long-lived information is stored.  It defaults to the current
+# directory but is later set by `setStateLocation()' in util.py.
+STATE_LOCATION = ""
+
+# Divisor (in seconds) for the Unix epoch used to defend against replay
+# attacks.
+EPOCH_GRANULARITY = 3600
+
+# Flags which can be set in a ScrambleSuit protocol message.
+FLAG_PAYLOAD =        (1 << 0)
+FLAG_NEW_TICKET =     (1 << 1)
+FLAG_PRNG_SEED =      (1 << 2)
+
+# Length of ScrambleSuit's header in bytes.
+HDR_LENGTH = 16 + 2 + 2 + 1
+
+# Length of the HMAC-SHA256-128 digest in bytes.
+HMAC_SHA256_128_LENGTH = 16
+
+# Whether or not to use inter-arrival time obfuscation.  Disabling this option
+# makes the transported protocol more identifiable but increases throughput a
+# lot.
+USE_IAT_OBFUSCATION = False
+
+# Key rotation time for session ticket keys in seconds.
+KEY_ROTATION_TIME = 60 * 60 * 24 * 7
+
+# Mark used to easily locate the HMAC authenticating handshake messages in
+# bytes.
+MARK_LENGTH = 16
+
+# The master key's length in bytes.
+MASTER_KEY_LENGTH = 32
+
+# Maximum amount of seconds, a packet is delayed due to inter arrival time
+# obfuscation.
+MAX_PACKET_DELAY = 0.01
+
+# The maximum amount of padding to be appended to handshake data.
+MAX_PADDING_LENGTH = 1500
+
+# Length of ScrambleSuit's MTU in bytes.  Note that this is *not* the link MTU
+# which is probably 1500.
+MTU = 1448
+
+# Maximum payload unit of a ScrambleSuit message in bytes.
+MPU = MTU - HDR_LENGTH
+
+# The minimum amount of distinct bins for probability distributions.
+MIN_BINS = 1
+
+# The maximum amount of distinct bins for probability distributions.
+MAX_BINS = 100
+
+# Length of a UniformDH public key in bytes.
+PUBLIC_KEY_LENGTH = 192
+
+# Length of the PRNG seed used to generate probability distributions in bytes.
+PRNG_SEED_LENGTH = 32
+
+# File which holds the server's state information.
+SERVER_STATE_FILE = "server_state.cpickle"
+
+# Life time of session tickets in seconds.
+SESSION_TICKET_LIFETIME = KEY_ROTATION_TIME
+
+# SHA256's digest length in bytes.
+SHA256_LENGTH = 32
+
+# The length of the UniformDH shared secret in bytes.  It should be a multiple
+# of 5 bytes since outside ScrambleSuit it is encoded in Base32.  That way, we
+# can avoid padding which might confuse users.
+SHARED_SECRET_LENGTH = 20
+
+# States which are used for the protocol state machine.
+ST_WAIT_FOR_AUTH = 0
+ST_CONNECTED = 1
+
+# File which holds the client's session tickets.
+CLIENT_TICKET_FILE = "session_ticket.yaml"
+
+# Static validation string embedded in all tickets.  Must be a multiple of 16
+# bytes due to AES' block size.
+TICKET_IDENTIFIER = "ScrambleSuitTicket"
+
+# Length of a session ticket in bytes.
+TICKET_LENGTH = 112
+
+# The protocol name which is used in log messages.
+TRANSPORT_NAME = "ScrambleSuit"
diff --git a/obfsproxy/transports/scramblesuit/fifobuf.py b/obfsproxy/transports/scramblesuit/fifobuf.py
new file mode 100644
index 0000000..221a6cb
--- /dev/null
+++ b/obfsproxy/transports/scramblesuit/fifobuf.py
@@ -0,0 +1,121 @@
+"""
+Provides an interface for a fast FIFO buffer.
+
+The interface implements only 'read()', 'write()' and 'len()'.  The
+implementation below is a modified version of the code originally written by
+Ben Timby: http://ben.timby.com/?p=139
+"""
+
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
+MAX_BUFFER = 1024**2*4
+
+class Buffer( object ):
+
+    """
+    Implements a fast FIFO buffer.
+
+    Internally, the buffer consists of a list of StringIO objects.  New
+    StringIO objects are added and delete as data is written to and read from
+    the FIFO buffer.
+    """
+
+    def __init__( self, max_size=MAX_BUFFER ):
+        """
+        Initialise a Buffer object.
+        """
+
+        self.buffers = []
+        self.max_size = max_size
+        self.read_pos = 0
+        self.write_pos = 0
+
+    def write( self, data ):
+        """
+        Write `data' to the FIFO buffer.
+
+        If necessary, a new internal buffer is created.
+        """
+
+        # Add a StringIO buffer if none exists yet.
+        if not self.buffers:
+            self.buffers.append(StringIO())
+            self.write_pos = 0
+
+        lastBuf = self.buffers[-1]
+        lastBuf.seek(self.write_pos)
+        lastBuf.write(data)
+
+        # If we are over the limit, a new internal buffer is created.
+        if lastBuf.tell() >= self.max_size:
+            lastBuf = StringIO()
+            self.buffers.append(lastBuf)
+
+        self.write_pos = lastBuf.tell()
+
+    def read( self, length=-1 ):
+        """
+        Read `length' elements of the FIFO buffer.
+
+        Drained data is automatically deleted.
+        """
+
+        read_buf = StringIO()
+        remaining = length
+
+        while True:
+
+            if not self.buffers:
+                break
+
+            firstBuf = self.buffers[0]
+            firstBuf.seek(self.read_pos)
+            read_buf.write(firstBuf.read(remaining))
+            self.read_pos = firstBuf.tell()
+
+            if length == -1:
+
+                # We did not limit the read, we exhausted the buffer, so delete
+                # it.  Keep reading from the remaining buffers.
+                del self.buffers[0]
+                self.read_pos = 0
+
+            else:
+
+                # We limited the read so either we exhausted the buffer or not.
+                remaining = length - read_buf.tell()
+
+                if remaining > 0:
+                    # Exhausted, remove buffer, read more.  Keep reading from
+                    # remaining buffers.
+                    del self.buffers[0]
+                    self.read_pos = 0
+                else:
+                    # Did not exhaust buffer, but read all that was requested.
+                    # Break to stop reading and return data of requested
+                    # length.
+                    break
+
+        return read_buf.getvalue()
+
+    def __len__(self):
+        """
+        Return the length of the Buffer object.
+        """
+
+        length = 0
+
+        for buf in self.buffers:
+
+            # Jump to the end of the internal buffer.
+            buf.seek(0, 2)
+
+            if buf == self.buffers[0]:
+                length += buf.tell() - self.read_pos
+            else:
+                length += buf.tell()
+
+        return length
diff --git a/obfsproxy/transports/scramblesuit/message.py b/obfsproxy/transports/scramblesuit/message.py
new file mode 100644
index 0000000..ad8d447
--- /dev/null
+++ b/obfsproxy/transports/scramblesuit/message.py
@@ -0,0 +1,226 @@
+"""
+This module provides code to handle ScrambleSuit protocol messages.
+
+The exported classes and functions provide interfaces to handle protocol
+messages, check message headers for validity and create protocol messages out
+of application data.
+"""
+
+import obfsproxy.common.log as logging
+import obfsproxy.common.serialize as pack
+import obfsproxy.transports.base as base
+
+import mycrypto
+import const
+
+log = logging.get_obfslogger()
+
+
+def createProtocolMessages( data, flags=const.FLAG_PAYLOAD ):
+    """
+    Create protocol messages out of the given payload.
+
+    The given `data' is turned into a list of protocol messages with the given
+    `flags' set.  The list is then returned.  If possible, all messages fill
+    the MTU.
+    """
+
+    messages = []
+
+    while len(data) > const.MPU:
+        messages.append(ProtocolMessage(data[:const.MPU], flags=flags))
+        data = data[const.MPU:]
+
+    messages.append(ProtocolMessage(data, flags=flags))
+
+    log.debug("Created %d protocol messages." % len(messages))
+
+    return messages
+
+
+def getFlagNames( flags ):
+    """
+    Return the flag name encoded in the integer `flags' as string.
+
+    This function is only useful for printing easy-to-read flag names in debug
+    log messages.
+    """
+
+    if flags == 1:
+        return "PAYLOAD"
+
+    elif flags == 2:
+        return "NEW_TICKET"
+
+    elif flags == 4:
+        return "PRNG_SEED"
+
+    else:
+        return "Undefined"
+
+
+def isSane( totalLen, payloadLen, flags ):
+    """
+    Verifies whether the given header fields are sane.
+
+    The values of the fields `totalLen', `payloadLen' and `flags' are checked
+    for their sanity.  If they are in the expected range, `True' is returned.
+    If any of these fields has an invalid value, `False' is returned.
+    """
+
+    def isFine( length ):
+        """
+        Check if the given length is fine.
+        """
+
+        return True if (0 <= length <= const.MPU) else False
+
+    log.debug("Message header: totalLen=%d, payloadLen=%d, flags"
+              "=%s" % (totalLen, payloadLen, getFlagNames(flags)))
+
+    validFlags = [
+        const.FLAG_PAYLOAD,
+        const.FLAG_NEW_TICKET,
+        const.FLAG_PRNG_SEED,
+    ]
+
+    return isFine(totalLen) and \
+           isFine(payloadLen) and \
+           totalLen >= payloadLen and \
+           (flags in validFlags)
+
+
+class ProtocolMessage( object ):
+
+    """
+    Represents a ScrambleSuit protocol message.
+
+    This class provides methods to deal with protocol messages.  The methods
+    make it possible to add padding as well as to encrypt and authenticate
+    protocol messages.
+    """
+
+    def __init__( self, payload="", paddingLen=0, flags=const.FLAG_PAYLOAD ):
+        """
+        Initialises a ProtocolMessage object.
+        """
+
+        payloadLen = len(payload)
+        if (payloadLen + paddingLen) > const.MPU:
+            raise base.PluggableTransportError("No overly long messages.")
+
+        self.totalLen = payloadLen + paddingLen
+        self.payloadLen = payloadLen
+        self.payload = payload
+        self.flags = flags
+
+    def encryptAndHMAC( self, crypter, hmacKey ):
+        """
+        Encrypt and authenticate this protocol message.
+
+        This protocol message is encrypted using `crypter' and authenticated
+        using `hmacKey'.  Finally, the encrypted message prepended by a
+        HMAC-SHA256-128 is returned and ready to be sent over the wire.
+        """
+
+        encrypted = crypter.encrypt(pack.htons(self.totalLen) +
+                                    pack.htons(self.payloadLen) +
+                                    chr(self.flags) + self.payload +
+                                    (self.totalLen - self.payloadLen) * '\0')
+
+        hmac = mycrypto.HMAC_SHA256_128(hmacKey, encrypted)
+
+        return hmac + encrypted
+
+    def addPadding( self, paddingLen ):
+        """
+        Add padding to this protocol message.
+
+        Padding is added to this protocol message.  The exact amount is
+        specified by `paddingLen'.
+        """
+
+        # The padding must not exceed the message size.
+        if (self.totalLen + paddingLen) > const.MPU:
+            raise base.PluggableTransportError("Can't pad more than the MTU.")
+
+        if paddingLen == 0:
+            return
+
+        log.debug("Adding %d bytes of padding to %d-byte message." %
+                  (paddingLen, const.HDR_LENGTH + self.totalLen))
+        self.totalLen += paddingLen
+
+    def __len__( self ):
+        """
+        Return the length of this protocol message.
+        """
+
+        return const.HDR_LENGTH + self.totalLen
+
+# Alias class name in order to provide a more intuitive API.
+new = ProtocolMessage
+
+class MessageExtractor( object ):
+
+    """
+    Extracts ScrambleSuit protocol messages out of an encrypted stream.
+    """
+
+    def __init__( self ):
+        """
+        Initialise a new MessageExtractor object.
+        """
+
+        self.recvBuf = ""
+        self.totalLen = None
+        self.payloadLen = None
+        self.flags = None
+
+    def extract( self, data, aes, hmacKey ):
+        """
+        Extracts (i.e., decrypts and authenticates) protocol messages.
+
+        The raw `data' coming directly from the wire is decrypted using `aes'
+        and authenticated using `hmacKey'.  The payload is then returned as
+        unencrypted protocol messages.  In case of invalid headers or HMACs, an
+        exception is raised.
+        """
+
+        self.recvBuf += data
+        msgs = []
+
+        # Keep trying to unpack as long as there is at least a header.
+        while len(self.recvBuf) >= const.HDR_LENGTH:
+
+            # If necessary, extract the header fields.
+            if self.totalLen == self.payloadLen == self.flags == None:
+                self.totalLen = pack.ntohs(aes.decrypt(self.recvBuf[16:18]))
+                self.payloadLen = pack.ntohs(aes.decrypt(self.recvBuf[18:20]))
+                self.flags = ord(aes.decrypt(self.recvBuf[20]))
+
+                if not isSane(self.totalLen, self.payloadLen, self.flags):
+                    raise base.PluggableTransportError("Invalid header.")
+
+            # Parts of the message are still on the wire; waiting.
+            if (len(self.recvBuf) - const.HDR_LENGTH) < self.totalLen:
+                break
+
+            rcvdHMAC = self.recvBuf[0:const.HMAC_SHA256_128_LENGTH]
+            vrfyHMAC = mycrypto.HMAC_SHA256_128(hmacKey,
+                              self.recvBuf[const.HMAC_SHA256_128_LENGTH:
+                              (self.totalLen + const.HDR_LENGTH)])
+
+            if rcvdHMAC != vrfyHMAC:
+                raise base.PluggableTransportError("Invalid message HMAC.")
+
+            # Decrypt the message and remove it from the input buffer.
+            extracted = aes.decrypt(self.recvBuf[const.HDR_LENGTH:
+                         (self.totalLen + const.HDR_LENGTH)])[:self.payloadLen]
+            msgs.append(ProtocolMessage(payload=extracted, flags=self.flags))
+            self.recvBuf = self.recvBuf[const.HDR_LENGTH + self.totalLen:]
+
+            # Protocol message processed; now reset length fields.
+            self.totalLen = self.payloadLen = self.flags = None
+
+        return msgs
diff --git a/obfsproxy/transports/scramblesuit/mycrypto.py b/obfsproxy/transports/scramblesuit/mycrypto.py
new file mode 100644
index 0000000..a424795
--- /dev/null
+++ b/obfsproxy/transports/scramblesuit/mycrypto.py
@@ -0,0 +1,155 @@
+"""
+This module provides cryptographic functions not implemented in PyCrypto.
+
+The implemented algorithms include HKDF-SHA256, HMAC-SHA256-128, (CS)PRNGs and
+an interface for encryption and decryption using AES in counter mode.
+"""
+
+import Crypto.Hash.SHA256
+import Crypto.Hash.HMAC
+import Crypto.Util.Counter
+import Crypto.Cipher.AES
+
+import obfsproxy.transports.base as base
+import obfsproxy.common.log as logging
+
+import math
+import os
+
+import const
+
+log = logging.get_obfslogger()
+
+
+class HKDF_SHA256( object ):
+
+    """
+    Implements HKDF using SHA256: https://tools.ietf.org/html/rfc5869
+
+    This class only implements the `expand' but not the `extract' stage since
+    the provided PRK already exhibits strong entropy.
+    """
+
+    def __init__( self, prk, info="", length=32 ):
+        """
+        Initialise a HKDF_SHA256 object.
+        """
+
+        self.hashLen = const.SHA256_LENGTH
+
+        if length > (self.hashLen * 255):
+            raise ValueError("The OKM's length cannot be larger than %d." %
+                             (self.hashLen * 255))
+
+        if len(prk) < self.hashLen:
+            raise ValueError("The PRK must be at least %d bytes in length "
+                             "(%d given)." % (self.hashLen, len(prk)))
+
+        self.N = math.ceil(float(length) / self.hashLen)
+        self.prk = prk
+        self.info = info
+        self.length = length
+        self.ctr = 1
+        self.T = ""
+
+    def expand( self ):
+        """
+        Return the expanded output key material.
+
+        The output key material is calculated based on the given PRK, info and
+        L.
+        """
+
+        tmp = ""
+
+        # Prevent the accidental re-use of output keying material.
+        if len(self.T) > 0:
+            raise base.PluggableTransportError("HKDF-SHA256 OKM must not "
+                                               "be re-used by application.")
+
+        while self.length > len(self.T):
+            tmp = Crypto.Hash.HMAC.new(self.prk, tmp + self.info +
+                                       chr(self.ctr),
+                                       Crypto.Hash.SHA256).digest()
+            self.T += tmp
+            self.ctr += 1
+
+        return self.T[:self.length]
+
+
+def HMAC_SHA256_128( key, msg ):
+    """
+    Return the HMAC-SHA256-128 of the given `msg' authenticated by `key'.
+    """
+
+    assert(len(key) >= const.SHARED_SECRET_LENGTH)
+
+    h = Crypto.Hash.HMAC.new(key, msg, Crypto.Hash.SHA256)
+
+    # Return HMAC truncated to 128 out of 256 bits.
+    return h.digest()[:16]
+
+
+def strongRandom( size ):
+    """
+    Return `size' bytes of strong randomness suitable for cryptographic use.
+    """
+
+    return os.urandom(size)
+
+
+class PayloadCrypter:
+
+    """
+    Provides methods to encrypt data using AES in counter mode.
+
+    This class provides methods to set a session key as well as an
+    initialisation vector and to encrypt and decrypt data.
+    """
+
+    def __init__( self ):
+        """
+        Initialise a PayloadCrypter object.
+        """
+
+        log.debug("Initialising AES-CTR instance.")
+
+        self.sessionKey = None
+        self.crypter = None
+        self.counter = None
+
+    def setSessionKey( self, key, iv ):
+        """
+        Set AES' session key and the initialisation vector for counter mode.
+
+        The given `key' and `iv' are used as 256-bit AES key and as 128-bit
+        initialisation vector for counter mode.  Both, the key as well as the
+        IV must come from a CSPRNG.
+        """
+
+        self.sessionKey = key
+
+        # Our 128-bit counter has the following format:
+        # [ 64-bit static and random IV ] [ 64-bit incrementing counter ]
+        # Counter wrapping is not allowed which makes it possible to transfer
+        # 2^64 * 16 bytes of data while avoiding counter reuse.  That amount is
+        # effectively out of reach given today's networking performance.
+        log.debug("Setting IV for AES-CTR.")
+        self.counter = Crypto.Util.Counter.new(64,
+                                               prefix = iv,
+                                               initial_value = 1,
+                                               allow_wraparound = False)
+
+        log.debug("Setting session key for AES-CTR.")
+        self.crypter = Crypto.Cipher.AES.new(key, Crypto.Cipher.AES.MODE_CTR,
+                                             counter=self.counter)
+
+    def encrypt( self, data ):
+        """
+        Encrypts the given `data' using AES in counter mode.
+        """
+
+        return self.crypter.encrypt(data)
+
+    # Encryption equals decryption in AES-CTR.
+    decrypt = encrypt
diff --git a/obfsproxy/transports/scramblesuit/packetmorpher.py b/obfsproxy/transports/scramblesuit/packetmorpher.py
new file mode 100644
index 0000000..4a72f0b
--- /dev/null
+++ b/obfsproxy/transports/scramblesuit/packetmorpher.py
@@ -0,0 +1,69 @@
+"""
+Provides code to morph a chunk of data to a given probability distribution.
+
+The class provides an interface to morph a network packet's length to a
+previously generated probability distribution.  The packet lengths of the
+morphed network data should then match the probability distribution.
+"""
+
+import random
+
+import probdist
+import const
+
+import obfsproxy.common.log as logging
+
+log = logging.get_obfslogger()
+
+class PacketMorpher( object ):
+
+    """
+    Implements methods to morph data to a target probability distribution.
+
+    This class is used to modify ScrambleSuit's packet length distribution on
+    the wire.  The class provides a method to determine the padding for packets
+    smaller than the MTU.
+    """
+
+    def __init__( self, dist=None ):
+        """
+        Initialise the packet morpher with the given distribution `dist'.
+
+        If `dist' is `None', a new discrete probability distribution is
+        generated randomly.
+        """
+
+        if dist:
+            self.dist = dist
+        else:
+            self.dist = probdist.new(lambda: random.randint(const.HDR_LENGTH,
+                                                            const.MTU))
+
+    def calcPadding( self, dataLen ):
+        """
+        Based on `dataLen', determines the padding for a network packet.
+
+        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.
+        """
+
+        # The `is' and `should-be' length of the burst's last packet.
+        dataLen = dataLen % const.MTU
+        sampleLen = self.dist.randomSample()
+
+        # Now determine the padding length which is in {0..MTU-1}.
+        if sampleLen >= dataLen:
+            padLen = sampleLen - dataLen
+        else:
+            padLen = (const.MTU - dataLen) + sampleLen
+
+        log.debug("Morphing the last %d-byte packet to %d bytes by adding %d "
+                  "bytes of padding." %
+                  (dataLen % const.MTU, sampleLen, padLen))
+
+        return padLen
+
+# Alias class name in order to provide a more intuitive API.
+new = PacketMorpher
diff --git a/obfsproxy/transports/scramblesuit/probdist.py b/obfsproxy/transports/scramblesuit/probdist.py
new file mode 100644
index 0000000..620a662
--- /dev/null
+++ b/obfsproxy/transports/scramblesuit/probdist.py
@@ -0,0 +1,98 @@
+"""
+This module provides code to generate and sample probability distributions.
+
+The class RandProbDist provides an interface to randomly generate probability
+distributions.  Random samples can then be drawn from these distributions.
+"""
+
+import random
+
+import const
+
+import obfsproxy.common.log as logging
+
+log = logging.get_obfslogger()
+
+
+class RandProbDist:
+
+    """
+    Provides code to generate, sample and dump probability distributions.
+    """
+
+    def __init__( self, genSingleton, seed=None ):
+        """
+        Initialise a discrete probability distribution.
+
+        The parameter `genSingleton' is expected to be a function which yields
+        singletons for the probability distribution.  The optional `seed' can
+        be used to seed the PRNG so that the probability distribution is
+        generated deterministically.
+        """
+
+        self.prng = random if (seed is None) else random.Random(seed)
+
+        self.sampleList = []
+        self.dist = self.genDistribution(genSingleton)
+        self.dumpDistribution()
+
+    def genDistribution( self, genSingleton ):
+        """
+        Generate a discrete probability distribution.
+
+        The parameter `genSingleton' is a function which is used to generate
+        singletons for the probability distribution.
+        """
+
+        dist = {}
+
+        # Amount of distinct bins, i.e., packet lengths or inter arrival times.
+        bins = self.prng.randint(const.MIN_BINS, const.MAX_BINS)
+
+        # Cumulative probability of all bins.
+        cumulProb = 0
+
+        for _ in xrange(bins):
+            prob = self.prng.uniform(0, (1 - cumulProb))
+            cumulProb += prob
+
+            singleton = genSingleton()
+            dist[singleton] = prob
+            self.sampleList.append((cumulProb, singleton,))
+
+        dist[genSingleton()] = (1 - cumulProb)
+
+        return dist
+
+    def dumpDistribution( self ):
+        """
+        Dump the probability distribution using the logging object.
+
+        Only probabilities > 0.01 are dumped.
+        """
+
+        log.debug("Dumping probability distribution.")
+
+        for singleton in self.dist.iterkeys():
+            # We are not interested in tiny probabilities.
+            if self.dist[singleton] > 0.01:
+                log.debug("P(%s) = %.3f" %
+                          (str(singleton), self.dist[singleton]))
+
+    def randomSample( self ):
+        """
+        Draw and return a random sample from the probability distribution.
+        """
+
+        assert len(self.sampleList) > 0
+
+        rand = random.random()
+
+        for cumulProb, singleton in self.sampleList:
+            if rand <= cumulProb:
+                return singleton
+
+        return self.sampleList[-1][1]
+
+# Alias class name in order to provide a more intuitive API.
+new = RandProbDist
diff --git a/obfsproxy/transports/scramblesuit/replay.py b/obfsproxy/transports/scramblesuit/replay.py
new file mode 100644
index 0000000..7643229
--- /dev/null
+++ b/obfsproxy/transports/scramblesuit/replay.py
@@ -0,0 +1,85 @@
+"""
+This module implements a mechanism to protect against replay attacks.
+
+The replay protection mechanism is based on a dictionary which caches
+previously observed keys.  New keys can be added to the dictionary and existing
+ones can be queried.  A pruning mechanism deletes expired keys from the
+dictionary.
+"""
+
+import time
+
+import const
+
+import obfsproxy.common.log as logging
+
+log = logging.get_obfslogger()
+
+
+class Tracker( object ):
+
+    """
+    Implement methods to keep track of replayed keys.
+
+    This class provides methods to add new keys (elements), check whether keys
+    are already present in the dictionary and to prune the lookup table.
+    """
+
+    def __init__( self ):
+        """
+        Initialise a `Tracker' object.
+        """
+
+        self.table = dict()
+
+    def addElement( self, element ):
+        """
+        Add the given `element' to the lookup table.
+        """
+
+        if self.isPresent(element):
+            raise LookupError("Element already present in table.")
+
+        # The key is a HMAC and the value is the current Unix timestamp.
+        self.table[element] = int(time.time())
+
+    def isPresent( self, element ):
+        """
+        Check if the given `element' is already present in the lookup table.
+
+        Return `True' if `element' is already in the lookup table and `False'
+        otherwise.
+        """
+
+        log.debug("Looking for existing element in size-%d lookup table." %
+                  len(self.table))
+
+        # Prune the replay table before looking up the given `element'.  This
+        # could be done more efficiently, e.g. by pruning every n minutes and
+        # only checking the timestamp of this particular element.
+        self.prune()
+
+        return (element in self.table)
+
+    def prune( self ):
+        """
+        Delete expired elements from the lookup table.
+
+        Keys whose Unix timestamps are older than `const.EPOCH_GRANULARITY' are
+        being removed from the lookup table.
+        """
+
+        log.debug("Pruning the replay table.")
+
+        deleteList = []
+        now = int(time.time())
+
+        for element in self.table.iterkeys():
+            if (now - self.table[element]) > const.EPOCH_GRANULARITY:
+                deleteList.append(element)
+
+        # We can't delete from a dictionary while iterating over it; therefore
+        # this construct.
+        for elem in deleteList:
+            log.debug("Deleting expired element.")
+            del self.table[elem]
diff --git a/obfsproxy/transports/scramblesuit/scramblesuit.py b/obfsproxy/transports/scramblesuit/scramblesuit.py
new file mode 100644
index 0000000..9262b34
--- /dev/null
+++ b/obfsproxy/transports/scramblesuit/scramblesuit.py
@@ -0,0 +1,624 @@
+"""
+The scramblesuit module implements the ScrambleSuit obfuscation protocol.
+
+The paper discussing the design and evaluation of the ScrambleSuit pluggable
+transport protocol is available here:
+http://www.cs.kau.se/philwint/scramblesuit/
+"""
+
+from twisted.internet import reactor
+
+import obfsproxy.transports.base as base
+import obfsproxy.common.log as logging
+
+import random
+import base64
+import yaml
+
+import probdist
+import mycrypto
+import message
+import const
+import util
+import packetmorpher
+import ticket
+import uniformdh
+import state
+import fifobuf
+
+
+log = logging.get_obfslogger()
+
+
+class ScrambleSuitTransport( base.BaseTransport ):
+
+    """
+    Implement the ScrambleSuit protocol.
+
+    The class implements methods which implement the ScrambleSuit protocol.  A
+    large part of the protocol's functionality is outsources to different
+    modules.
+    """
+
+    def __init__( self ):
+        """
+        Initialise a ScrambleSuitTransport object.
+        """
+
+        log.error("\n\n################################################\n"
+                  "Do NOT rely on ScrambleSuit for strong security!\n"
+                  "################################################\n")
+
+        log.debug("Initialising %s." % const.TRANSPORT_NAME)
+
+        super(ScrambleSuitTransport, self).__init__()
+
+        # Load the server's persistent state from file.
+        if self.weAreServer:
+            self.srvState = state.load()
+
+        # Initialise the protocol's state machine.
+        log.debug("Switching to state ST_WAIT_FOR_AUTH.")
+        self.protoState = const.ST_WAIT_FOR_AUTH
+
+        # Buffer for outgoing data.
+        self.sendBuf = ""
+
+        # Buffer for inter-arrival time obfuscation.
+        self.choppingBuf = fifobuf.Buffer()
+
+        # AES instances to decrypt incoming and encrypt outgoing data.
+        self.sendCrypter = mycrypto.PayloadCrypter()
+        self.recvCrypter = mycrypto.PayloadCrypter()
+
+        # Packet morpher to modify the protocol's packet length distribution.
+        self.pktMorpher = packetmorpher.new(self.srvState.pktDist
+                                            if self.weAreServer else None)
+
+        # Inter-arrival time morpher to obfuscate inter arrival times.
+        self.iatMorpher = self.srvState.iatDist if self.weAreServer else \
+                          probdist.new(lambda: random.random() %
+                                       const.MAX_PACKET_DELAY)
+
+        # Used to extract protocol messages from encrypted data.
+        self.protoMsg = message.MessageExtractor()
+
+        # Used by the server-side: `True' if the ticket is already
+        # decrypted but not yet authenticated.
+        self.decryptedTicket = False
+
+        # If we are in external mode we should already have a shared
+        # secret set up because of validate_external_mode_cli().
+        if self.weAreExternal:
+            assert(self.uniformDHSecret)
+
+        if self.weAreClient and not self.weAreExternal:
+            # As a client in managed mode, we get the shared secret
+            # from callback `handle_socks_args()' per-connection. Set
+            # the shared secret to None for now.
+            self.uniformDHSecret = None
+
+        self.uniformdh = uniformdh.new(self.uniformDHSecret, self.weAreServer)
+
+    @classmethod
+    def setup( cls, transportConfig ):
+        """
+        Called once when obfsproxy starts.
+        """
+
+        util.setStateLocation(transportConfig.getStateLocation())
+
+        cls.weAreClient = transportConfig.weAreClient
+        cls.weAreServer = not cls.weAreClient
+        cls.weAreExternal = transportConfig.weAreExternal
+
+        # If we are server and in managed mode, we should get the
+        # shared secret from the server transport options.
+        if cls.weAreServer and not cls.weAreExternal:
+            cfg  = transportConfig.getServerTransportOptions()
+            if cfg and "password" in cfg:
+                cls.uniformDHSecret = base64.b32decode(util.sanitiseBase32(
+                        cfg["password"]))
+                cls.uniformDHSecret = cls.uniformDHSecret.strip()
+
+    @classmethod
+    def get_public_server_options( cls, transportOptions ):
+        """
+        Return ScrambleSuit's BridgeDB parameters, i.e., the shared secret.
+
+        As a fallback mechanism, we return an automatically generated password
+        if the bridge operator did not use `ServerTransportOptions'.
+        """
+
+        log.debug("Tor's transport options: %s" % str(transportOptions))
+
+        if not "password" in transportOptions:
+            log.warning("No password found in transport options (use Tor's " \
+                        "`ServerTransportOptions' to set your own password)." \
+                        "  Using automatically generated password instead.")
+            srv = state.load()
+            transportOptions = {"password":
+                                base64.b32encode(srv.fallbackPassword)}
+            cls.uniformDHSecret = srv.fallbackPassword
+
+        return transportOptions
+
+    def deriveSecrets( self, masterKey ):
+        """
+        Derive various session keys from the given `masterKey'.
+
+        The argument `masterKey' is used to derive two session keys and nonces
+        for AES-CTR and two HMAC keys.  The derivation is done using
+        HKDF-SHA256.
+        """
+
+        assert len(masterKey) == const.MASTER_KEY_LENGTH
+
+        log.debug("Deriving session keys from %d-byte master key." %
+                  len(masterKey))
+
+        # We need key material for two symmetric AES-CTR keys, nonces and
+        # HMACs.  In total, this equals 144 bytes of key material.
+        hkdf = mycrypto.HKDF_SHA256(masterKey, "", (32 * 4) + (8 * 2))
+        okm = hkdf.expand()
+        assert len(okm) >= ((32 * 4) + (8 * 2))
+
+        # Set AES-CTR keys and nonces for our two AES instances.
+        self.sendCrypter.setSessionKey(okm[0:32],  okm[32:40])
+        self.recvCrypter.setSessionKey(okm[40:72], okm[72:80])
+
+        # Set the keys for the two HMACs protecting our data integrity.
+        self.sendHMAC = okm[80:112]
+        self.recvHMAC = okm[112:144]
+
+        if self.weAreServer:
+            self.sendHMAC, self.recvHMAC = self.recvHMAC, self.sendHMAC
+            self.sendCrypter, self.recvCrypter = self.recvCrypter, \
+                                                 self.sendCrypter
+
+    def circuitConnected( self ):
+        """
+        Initiate a ScrambleSuit handshake.
+
+        This method is only relevant for clients since servers never initiate
+        handshakes.  If a session ticket is available, it is redeemed.
+        Otherwise, a UniformDH handshake is conducted.
+        """
+
+        # The server handles the handshake passively.
+        if self.weAreServer:
+            return
+
+        # The preferred authentication mechanism is a session ticket.
+        bridge = self.circuit.downstream.transport.getPeer()
+        storedTicket = ticket.findStoredTicket(bridge)
+
+        if storedTicket is not None:
+            log.debug("Redeeming stored session ticket.")
+            (masterKey, rawTicket) = storedTicket
+            self.deriveSecrets(masterKey)
+            self.circuit.downstream.write(ticket.createTicketMessage(rawTicket,
+                                                                self.sendHMAC))
+
+            # We switch to ST_CONNECTED opportunistically since we don't know
+            # yet whether the server accepted the ticket.
+            log.debug("Switching to state ST_CONNECTED.")
+            self.protoState = const.ST_CONNECTED
+
+            self.flushSendBuffer()
+
+        # Conduct an authenticated UniformDH handshake if there's no ticket.
+        else:
+            log.debug("No session ticket to redeem.  Running UniformDH.")
+            self.circuit.downstream.write(self.uniformdh.createHandshake())
+
+    def sendRemote( self, data, flags=const.FLAG_PAYLOAD ):
+        """
+        Send data to the remote end after a connection was established.
+
+        The given `data' is first encapsulated in protocol messages.  Then, the
+        protocol message(s) are sent over the wire.  The argument `flags'
+        specifies the protocol message flags with the default flags signalling
+        payload.
+        """
+
+        log.debug("Processing %d bytes of outgoing data." % len(data))
+
+        # 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.
+        if const.USE_IAT_OBFUSCATION:
+            if len(self.choppingBuf) == 0:
+                self.choppingBuf.write(blurb)
+                reactor.callLater(self.iatMorpher.randomSample(),
+                                  self.flushPieces)
+            else:
+                # flushPieces() is still busy processing the chopping buffer.
+                self.choppingBuf.write(blurb)
+        else:
+            self.circuit.downstream.write(blurb)
+
+    def flushPieces( self ):
+        """
+        Write the application data in chunks to the wire.
+
+        The cached data is sent over the wire in chunks.  After every write
+        call, control is given back to the Twisted reactor so it has a chance
+        to flush the data.  Shortly thereafter, this function is called again
+        to write the next chunk of data.  The delays in between subsequent
+        write calls are controlled by the inter-arrival time obfuscator.
+        """
+
+        # Drain and send an MTU-sized chunk from the chopping buffer.
+        if len(self.choppingBuf) > const.MTU:
+
+            self.circuit.downstream.write(self.choppingBuf.read(const.MTU))
+
+        # Drain and send whatever is left in the output buffer.
+        else:
+            self.circuit.downstream.write(self.choppingBuf.read())
+            return
+
+        reactor.callLater(self.iatMorpher.randomSample(), self.flushPieces)
+
+    def processMessages( self, data ):
+        """
+        Acts on extracted protocol messages based on header flags.
+
+        After the incoming `data' is decrypted and authenticated, this method
+        processes the received data based on the header flags.  Payload is
+        written to the local application, new tickets are stored, or keys are
+        added to the replay table.
+        """
+
+        if (data is None) or (len(data) == 0):
+            return
+
+        # Try to extract protocol messages from the encrypted blurb.
+        msgs  = self.protoMsg.extract(data, self.recvCrypter, self.recvHMAC)
+        if (msgs is None) or (len(msgs) == 0):
+            return
+
+        for msg in msgs:
+            # Forward data to the application.
+            if msg.flags == const.FLAG_PAYLOAD:
+                self.circuit.upstream.write(msg.payload)
+
+            # Store newly received ticket.
+            elif self.weAreClient and (msg.flags == const.FLAG_NEW_TICKET):
+                assert len(msg.payload) == (const.TICKET_LENGTH +
+                                            const.MASTER_KEY_LENGTH)
+                peer = self.circuit.downstream.transport.getPeer()
+                ticket.storeNewTicket(msg.payload[0:const.MASTER_KEY_LENGTH],
+                                      msg.payload[const.MASTER_KEY_LENGTH:
+                                                  const.MASTER_KEY_LENGTH +
+                                                  const.TICKET_LENGTH], peer)
+
+            # Use the PRNG seed to generate the same probability distributions
+            # as the server.  That's where the polymorphism comes from.
+            elif self.weAreClient and (msg.flags == const.FLAG_PRNG_SEED):
+                assert len(msg.payload) == const.PRNG_SEED_LENGTH
+                log.debug("Obtained PRNG seed.")
+                prng = random.Random(msg.payload)
+                pktDist = probdist.new(lambda: prng.randint(const.HDR_LENGTH,
+                                                            const.MTU),
+                                       seed=msg.payload)
+                self.pktMorpher = packetmorpher.new(pktDist)
+                self.iatMorpher = probdist.new(lambda: prng.random() %
+                                               const.MAX_PACKET_DELAY,
+                                               seed=msg.payload)
+
+            else:
+                log.warning("Invalid message flags: %d." % msg.flags)
+
+    def flushSendBuffer( self ):
+        """
+        Flush the application's queued data.
+
+        The application could have sent data while we were busy authenticating
+        the remote machine.  This method flushes the data which could have been
+        queued in the meanwhile in `self.sendBuf'.
+        """
+
+        if len(self.sendBuf) == 0:
+            log.debug("Send buffer is empty; nothing to flush.")
+            return
+
+        # Flush the buffered data, the application is so eager to send.
+        log.debug("Flushing %d bytes of buffered application data." %
+                  len(self.sendBuf))
+
+        self.sendRemote(self.sendBuf)
+        self.sendBuf = ""
+
+    def receiveTicket( self, data ):
+        """
+        Extract and verify a potential session ticket.
+
+        The given `data' is treated as a session ticket.  The ticket is being
+        decrypted and authenticated (yes, in that order).  If all these steps
+        succeed, `True' is returned.  Otherwise, `False' is returned.
+        """
+
+        if len(data) < (const.TICKET_LENGTH + const.MARK_LENGTH +
+                        const.HMAC_SHA256_128_LENGTH):
+            return False
+
+        potentialTicket = data.peek()
+
+        # Now try to decrypt and parse the ticket.  We need the master key
+        # inside to verify the HMAC in the next step.
+        if not self.decryptedTicket:
+            newTicket = ticket.decrypt(potentialTicket[:const.TICKET_LENGTH],
+                                       self.srvState)
+            if newTicket != None and newTicket.isValid():
+                self.deriveSecrets(newTicket.masterKey)
+                self.decryptedTicket = True
+            else:
+                return False
+
+        # First, find the mark to efficiently locate the HMAC.
+        mark = mycrypto.HMAC_SHA256_128(self.recvHMAC,
+                                        potentialTicket[:const.TICKET_LENGTH])
+
+        index = util.locateMark(mark, potentialTicket)
+        if not index:
+            return False
+
+        # Now, verify if the HMAC is valid.
+        existingHMAC = potentialTicket[index + const.MARK_LENGTH:
+                                       index + const.MARK_LENGTH +
+                                       const.HMAC_SHA256_128_LENGTH]
+        myHMAC = mycrypto.HMAC_SHA256_128(self.recvHMAC,
+                                          potentialTicket[0:
+                                          index + const.MARK_LENGTH] +
+                                          util.getEpoch())
+
+        if not util.isValidHMAC(myHMAC, existingHMAC, self.recvHMAC):
+            log.warning("The HMAC is invalid: `%s' vs. `%s'." %
+                        (myHMAC.encode('hex'), existingHMAC.encode('hex')))
+            return False
+
+        # Do nothing if the ticket is replayed.  Immediately closing the
+        # connection would be suspicious.
+        if self.srvState.isReplayed(existingHMAC):
+            log.warning("The HMAC was already present in the replay table.")
+            return False
+
+        data.drain(index + const.MARK_LENGTH + const.HMAC_SHA256_128_LENGTH)
+
+        log.debug("Adding the HMAC authenticating the ticket message to the " \
+                  "replay table: %s." % existingHMAC.encode('hex'))
+        self.srvState.registerKey(existingHMAC)
+
+        log.debug("Switching to state ST_CONNECTED.")
+        self.protoState = const.ST_CONNECTED
+
+        return True
+
+    def receivedUpstream( self, data ):
+        """
+        Sends data to the remote machine or queues it to be sent later.
+
+        Depending on the current protocol state, the given `data' is either
+        directly sent to the remote machine or queued.  The buffer is then
+        flushed once, a connection is established.
+        """
+
+        if self.protoState == const.ST_CONNECTED:
+            self.sendRemote(data.read())
+
+        # Buffer data we are not ready to transmit yet.
+        else:
+            self.sendBuf += data.read()
+            log.debug("Buffered %d bytes of outgoing data." %
+                      len(self.sendBuf))
+
+    def sendTicketAndSeed( self ):
+        """
+        Send a session ticket and the PRNG seed to the client.
+
+        This method is only called by the server after successful
+        authentication.  Finally, the server's send buffer is flushed.
+        """
+
+        log.debug("Sending a new session ticket and the PRNG seed to the " \
+                  "client.")
+
+        self.sendRemote(ticket.issueTicketAndKey(self.srvState),
+                        flags=const.FLAG_NEW_TICKET)
+        self.sendRemote(self.srvState.prngSeed,
+                        flags=const.FLAG_PRNG_SEED)
+        self.flushSendBuffer()
+
+    def receivedDownstream( self, data ):
+        """
+        Receives and processes data coming from the remote machine.
+
+        The incoming `data' is dispatched depending on the current protocol
+        state and whether we are the client or the server.  The data is either
+        payload or authentication data.
+        """
+
+        if self.weAreServer and (self.protoState == const.ST_WAIT_FOR_AUTH):
+
+            # First, try to interpret the incoming data as session ticket.
+            if self.receiveTicket(data):
+                log.debug("Ticket authentication succeeded.")
+
+                self.sendTicketAndSeed()
+
+            # Second, interpret the data as a UniformDH handshake.
+            elif self.uniformdh.receivePublicKey(data, self.deriveSecrets,
+                    self.srvState):
+                # Now send the server's UniformDH public key to the client.
+                handshakeMsg = self.uniformdh.createHandshake()
+
+                log.debug("Sending %d bytes of UniformDH handshake and "
+                          "session ticket." % len(handshakeMsg))
+
+                self.circuit.downstream.write(handshakeMsg)
+                log.debug("UniformDH authentication succeeded.")
+
+                log.debug("Switching to state ST_CONNECTED.")
+                self.protoState = const.ST_CONNECTED
+
+                self.sendTicketAndSeed()
+
+            else:
+                log.debug("Authentication unsuccessful so far.  "
+                          "Waiting for more data.")
+                return
+
+        elif self.weAreClient and (self.protoState == const.ST_WAIT_FOR_AUTH):
+
+            if not self.uniformdh.receivePublicKey(data, self.deriveSecrets):
+                log.debug("Unable to finish UniformDH handshake just yet.")
+                return
+
+            log.debug("UniformDH authentication succeeded.")
+
+            log.debug("Switching to state ST_CONNECTED.")
+            self.protoState = const.ST_CONNECTED
+            self.flushSendBuffer()
+
+        if self.protoState == const.ST_CONNECTED:
+
+            self.processMessages(data.read())
+
+    @classmethod
+    def register_external_mode_cli( cls, subparser ):
+        """
+        Register a CLI arguments to pass a secret or ticket to ScrambleSuit.
+
+        Two options are made available over the command line interface: one to
+        specify a ticket file and one to specify a UniformDH shared secret.
+        """
+
+        subparser.add_argument("--password",
+                               required=True,
+                               type=str,
+                               help="Shared secret for UniformDH",
+                               dest="uniformDHSecret")
+
+        super(ScrambleSuitTransport, cls).register_external_mode_cli(subparser)
+
+    @classmethod
+    def validate_external_mode_cli( cls, args ):
+        """
+        Assign the given command line arguments to local variables.
+        """
+
+        uniformDHSecret = None
+
+        try:
+            uniformDHSecret = base64.b32decode(util.sanitiseBase32(
+                                     args.uniformDHSecret))
+        except (TypeError, AttributeError) as error:
+            log.error(error.message)
+            raise base.PluggableTransportError(
+                "UniformDH password '%s' isn't valid base32!"
+                % args.uniformDHSecret)
+
+        parentalApproval = super(
+            ScrambleSuitTransport, cls).validate_external_mode_cli(args)
+        if not parentalApproval:
+            # XXX not very descriptive nor helpful, but the parent class only
+            #     returns a boolean without telling us what's wrong.
+            raise base.PluggableTransportError(
+                "Pluggable Transport args invalid: %s" % args )
+
+        if uniformDHSecret:
+            rawLength = len(uniformDHSecret)
+            if rawLength != const.SHARED_SECRET_LENGTH:
+                raise base.PluggableTransportError(
+                    "The UniformDH password must be %d bytes in length, ",
+                    "but %d bytes are given."
+                    % (const.SHARED_SECRET_LENGTH, rawLength))
+            else:
+                cls.uniformDHSecret = uniformDHSecret
+
+    def handle_socks_args( self, args ):
+        """
+        Receive arguments `args' passed over a SOCKS connection.
+
+        The SOCKS authentication mechanism is (ab)used to pass arguments to
+        pluggable transports.  This method receives these arguments and parses
+        them.  As argument, we only expect a UniformDH shared secret.
+        """
+
+        log.debug("Received the following arguments over SOCKS: %s." % args)
+
+        if len(args) != 1:
+            raise base.SOCKSArgsError("Too many SOCKS arguments "
+                                      "(expected 1 but got %d)." % len(args))
+
+        # The ScrambleSuit specification defines that the shared secret is
+        # called "password".
+        if not args[0].startswith("password="):
+            raise base.SOCKSArgsError("The SOCKS argument must start with "
+                                      "`password='.")
+
+        # A shared secret might already be set if obfsproxy is in external
+        # mode.
+        if self.uniformDHSecret:
+            log.warning("A UniformDH password was already specified over "
+                        "the command line.  Using the SOCKS secret instead.")
+
+        self.uniformDHSecret = base64.b32decode(util.sanitiseBase32(
+                                      args[0].split('=')[1].strip()))
+
+        rawLength = len(self.uniformDHSecret)
+        if rawLength != const.SHARED_SECRET_LENGTH:
+            raise base.PluggableTransportError("The UniformDH password "
+                    "must be %d bytes in length but %d bytes are given." %
+                    (const.SHARED_SECRET_LENGTH, rawLength))
+
+        self.uniformdh = uniformdh.new(self.uniformDHSecret, self.weAreServer)
+
+
+class ScrambleSuitClient( ScrambleSuitTransport ):
+
+    """
+    Extend the ScrambleSuit class.
+    """
+
+    def __init__( self ):
+        """
+        Initialise a ScrambleSuitClient object.
+        """
+
+        ScrambleSuitTransport.__init__(self)
+
+
+class ScrambleSuitServer( ScrambleSuitTransport ):
+
+    """
+    Extend the ScrambleSuit class.
+    """
+
+    def __init__( self ):
+        """
+        Initialise a ScrambleSuitServer object.
+        """
+
+        ScrambleSuitTransport.__init__(self)
diff --git a/obfsproxy/transports/scramblesuit/state.py b/obfsproxy/transports/scramblesuit/state.py
new file mode 100644
index 0000000..9d32d0c
--- /dev/null
+++ b/obfsproxy/transports/scramblesuit/state.py
@@ -0,0 +1,161 @@
+"""
+Provide a way to store the server's state information on disk.
+
+The server possesses state information which should persist across runs.  This
+includes key material to encrypt and authenticate session tickets, replay
+tables and PRNG seeds.  This module provides methods to load, store and
+generate such state information.
+"""
+
+import os
+import sys
+import time
+import cPickle
+import random
+
+import const
+import replay
+import mycrypto
+import probdist
+
+import obfsproxy.common.log as logging
+
+log = logging.get_obfslogger()
+
+def load( ):
+    """
+    Load the server's state object from file.
+
+    The server's state file is loaded and the state object returned.  If no
+    state file is found, a new one is created and returned.
+    """
+
+    stateFile = const.STATE_LOCATION + const.SERVER_STATE_FILE
+
+    log.info("Attempting to load the server's state file from `%s'." %
+             stateFile)
+
+    if not os.path.exists(stateFile):
+        log.info("The server's state file does not exist (yet).")
+        state = State()
+        state.genState()
+        return state
+
+    try:
+        with open(stateFile, 'r') as fd:
+            stateObject = cPickle.load(fd)
+    except IOError as err:
+        log.error("Error reading server state file from `%s': %s" %
+                  (stateFile, err))
+        sys.exit(1)
+
+    return stateObject
+
+class State( object ):
+
+    """
+    Implement a state class which stores the server's state.
+
+    This class makes it possible to store state information on disk.  It
+    provides methods to generate and write state information.
+    """
+
+    def __init__( self ):
+        """
+        Initialise a `State' object.
+        """
+
+        self.prngSeed = None
+        self.keyCreation = None
+        self.hmacKey = None
+        self.aesKey = None
+        self.oldHmacKey = None
+        self.oldAesKey = None
+        self.ticketReplay = None
+        self.uniformDhReplay = None
+        self.pktDist = None
+        self.iatDist = None
+        self.fallbackPassword = None
+
+    def genState( self ):
+        """
+        Populate all the local variables with values.
+        """
+
+        log.info("Generating parameters for the server's state file.")
+
+        # PRNG seed for the client to reproduce the packet and IAT morpher.
+        self.prngSeed = mycrypto.strongRandom(const.PRNG_SEED_LENGTH)
+
+        # HMAC and AES key used to encrypt and authenticate tickets.
+        self.hmacKey = mycrypto.strongRandom(const.TICKET_HMAC_KEY_LENGTH)
+        self.aesKey = mycrypto.strongRandom(const.TICKET_AES_KEY_LENGTH)
+        self.keyCreation = int(time.time())
+
+        # The previous HMAC and AES keys.
+        self.oldHmacKey = None
+        self.oldAesKey = None
+
+        # Replay dictionary for both authentication mechanisms.
+        self.replayTracker = replay.Tracker()
+
+        # Distributions for packet lengths and inter arrival times.
+        prng = random.Random(self.prngSeed)
+        self.pktDist = probdist.new(lambda: prng.randint(const.HDR_LENGTH,
+                                                         const.MTU),
+                                    seed=self.prngSeed)
+        self.iatDist = probdist.new(lambda: prng.random() %
+                                    const.MAX_PACKET_DELAY,
+                                    seed=self.prngSeed)
+
+        # Fallback UniformDH shared secret.  Only used if the bridge operator
+        # did not set `ServerTransportOptions'.
+        self.fallbackPassword = os.urandom(const.SHARED_SECRET_LENGTH)
+
+        self.writeState()
+
+    def isReplayed( self, hmac ):
+        """
+        Check if `hmac' is present in the replay table.
+
+        Return `True' if the given `hmac' is present in the replay table and
+        `False' otherwise.
+        """
+
+        assert self.replayTracker is not None
+
+        log.debug("Querying if HMAC is present in the replay table.")
+
+        return self.replayTracker.isPresent(hmac)
+
+    def registerKey( self, hmac ):
+        """
+        Add the given `hmac' to the replay table.
+        """
+
+        assert self.replayTracker is not None
+
+        log.debug("Adding a new HMAC to the replay table.")
+        self.replayTracker.addElement(hmac)
+
+        # We must write the data to disk immediately so that other ScrambleSuit
+        # connections can share the same state.
+        self.writeState()
+
+    def writeState( self ):
+        """
+        Write the state object to a file using the `cPickle' module.
+        """
+
+        stateFile = const.STATE_LOCATION + const.SERVER_STATE_FILE
+
+        log.debug("Writing server's state file to `%s'." %
+                  stateFile)
+
+        try:
+            with open(stateFile, 'w') as fd:
+                cPickle.dump(self, fd)
+        except IOError as err:
+            log.error("Error writing state file to `%s': %s" %
+                      (stateFile, err))
+            sys.exit(1)
diff --git a/obfsproxy/transports/scramblesuit/ticket.py b/obfsproxy/transports/scramblesuit/ticket.py
new file mode 100644
index 0000000..8599936
--- /dev/null
+++ b/obfsproxy/transports/scramblesuit/ticket.py
@@ -0,0 +1,391 @@
+#!/usr/bin/env python
+
+"""
+This module provides a session ticket mechanism.
+
+The implemented mechanism is a subset of session tickets as proposed for
+TLS in RFC 5077.
+
+The format of a 112-byte ticket is:
+ +------------+------------------+--------------+
+ | 16-byte IV | 64-byte E(state) | 32-byte HMAC |
+ +------------+------------------+--------------+
+
+The 64-byte encrypted state contains:
+ +-------------------+--------------------+--------------------+-------------+
+ | 4-byte issue date | 18-byte identifier | 32-byte master key | 10-byte pad |
+ +-------------------+--------------------+--------------------+-------------+
+"""
+
+import os
+import time
+import const
+import yaml
+import struct
+import random
+import datetime
+
+from Crypto.Cipher import AES
+from Crypto.Hash import HMAC
+from Crypto.Hash import SHA256
+from twisted.internet.address import IPv4Address
+
+import obfsproxy.common.log as logging
+
+import mycrypto
+import util
+import state
+
+log = logging.get_obfslogger()
+
+
+def createTicketMessage( rawTicket, HMACKey ):
+    """
+    Create and return a ready-to-be-sent ticket authentication message.
+
+    Pseudo-random padding and a mark are added to `rawTicket' and the result is
+    then authenticated using `HMACKey' as key for a HMAC.  The resulting
+    authentication message is then returned.
+    """
+
+    assert len(rawTicket) == const.TICKET_LENGTH
+    assert len(HMACKey) == const.TICKET_HMAC_KEY_LENGTH
+
+    # Subtract the length of the ticket to make the handshake on
+    # average as long as a UniformDH handshake message.
+    padding = mycrypto.strongRandom(random.randint(0,
+                                    const.MAX_PADDING_LENGTH -
+                                    const.TICKET_LENGTH))
+
+    mark = mycrypto.HMAC_SHA256_128(HMACKey, rawTicket)
+
+    hmac = mycrypto.HMAC_SHA256_128(HMACKey, rawTicket + padding +
+                                    mark + util.getEpoch())
+
+    return rawTicket + padding + mark + hmac
+
+
+def issueTicketAndKey( srvState ):
+    """
+    Issue a new session ticket and append it to the according master key.
+
+    The parameter `srvState' contains the key material and is passed on to
+    `SessionTicket'.  The returned ticket and key are ready to be wrapped into
+    a protocol message with the flag FLAG_NEW_TICKET set.
+    """
+
+    log.info("Issuing new session ticket and master key.")
+    masterKey = mycrypto.strongRandom(const.MASTER_KEY_LENGTH)
+    newTicket = (SessionTicket(masterKey, srvState)).issue()
+
+    return masterKey + newTicket
+
+
+def storeNewTicket( masterKey, ticket, bridge ):
+    """
+    Store a new session ticket and the according master key for future use.
+
+    This method is only called by clients.  The given data, `masterKey',
+    `ticket' and `bridge', is YAMLed and stored in the global ticket
+    dictionary.  If there already is a ticket for the given `bridge', it is
+    overwritten.
+    """
+
+    assert len(masterKey) == const.MASTER_KEY_LENGTH
+    assert len(ticket) == const.TICKET_LENGTH
+
+    ticketFile = const.STATE_LOCATION + const.CLIENT_TICKET_FILE
+
+    log.debug("Storing newly received ticket in `%s'." % ticketFile)
+
+    # Add a new (key, ticket) tuple with the given bridge as hash key.
+    tickets = dict()
+    content = util.readFromFile(ticketFile)
+    if (content is not None) and (len(content) > 0):
+        tickets = yaml.safe_load(content)
+
+    # We also store a timestamp so we later know if our ticket already expired.
+    tickets[str(bridge)] = [int(time.time()), masterKey, ticket]
+    util.writeToFile(yaml.dump(tickets), ticketFile)
+
+
+def findStoredTicket( bridge ):
+    """
+    Retrieve a previously stored ticket from the ticket dictionary.
+
+    The global ticket dictionary is loaded and the given `bridge' is used to
+    look up the ticket and the master key.  If the ticket dictionary does not
+    exist (yet) or the ticket data could not be found, `None' is returned.
+    """
+
+    assert bridge
+
+    ticketFile = const.STATE_LOCATION + const.CLIENT_TICKET_FILE
+
+    log.debug("Attempting to read master key and ticket from file `%s'." %
+              ticketFile)
+
+    # Load the ticket hash table from file.
+    yamlBlurb = util.readFromFile(ticketFile)
+    if (yamlBlurb is None) or (len(yamlBlurb) == 0):
+        return None
+    tickets = yaml.safe_load(yamlBlurb)
+
+    try:
+        timestamp, masterKey, ticket = tickets[str(bridge)]
+    except KeyError:
+        log.info("Found no ticket for bridge `%s'." % str(bridge))
+        return None
+
+    # We can remove the ticket now since we are about to redeem it.
+    log.debug("Deleting ticket since it is about to be redeemed.")
+    del tickets[str(bridge)]
+    util.writeToFile(yaml.dump(tickets), ticketFile)
+
+    # If our ticket is expired, we can't redeem it.
+    ticketAge = int(time.time()) - timestamp
+    if ticketAge > const.SESSION_TICKET_LIFETIME:
+        log.warning("We did have a ticket but it already expired %s ago." %
+                    str(datetime.timedelta(seconds=
+                        (ticketAge - const.SESSION_TICKET_LIFETIME))))
+        return None
+
+    return (masterKey, ticket)
+
+
+def checkKeys( srvState ):
+    """
+    Check whether the key material for session tickets must be rotated.
+
+    The key material (i.e., AES and HMAC keys for session tickets) contained in
+    `srvState' is checked if it needs to be rotated.  If so, the old keys are
+    stored and new ones are created.
+    """
+
+    assert (srvState.hmacKey is not None) and \
+           (srvState.aesKey is not None) and \
+           (srvState.keyCreation is not None)
+
+    if (int(time.time()) - srvState.keyCreation) > const.KEY_ROTATION_TIME:
+        log.info("Rotating server key material for session tickets.")
+
+        # Save expired keys to be able to validate old tickets.
+        srvState.oldAesKey = srvState.aesKey
+        srvState.oldHmacKey = srvState.hmacKey
+
+        # Create new key material...
+        srvState.aesKey = mycrypto.strongRandom(const.TICKET_AES_KEY_LENGTH)
+        srvState.hmacKey = mycrypto.strongRandom(const.TICKET_HMAC_KEY_LENGTH)
+        srvState.creationTime = int(time.time())
+
+        # ...and save it to disk.
+        srvState.writeState()
+
+
+def decrypt( ticket, srvState ):
+    """
+    Decrypts, verifies and returns the given `ticket'.
+
+    The key material used to verify the ticket is contained in `srvState'.
+    First, the HMAC over the ticket is verified.  If it is valid, the ticket is
+    decrypted.  Finally, a `ProtocolState()' object containing the master key
+    and the ticket's issue date is returned.  If any of these steps fail,
+    `None' is returned.
+    """
+
+    assert (ticket is not None) and (len(ticket) == const.TICKET_LENGTH)
+    assert (srvState.hmacKey is not None) and (srvState.aesKey is not None)
+
+    log.debug("Attempting to decrypt and verify ticket.")
+
+    checkKeys(srvState)
+
+    # Verify the ticket's authenticity before decrypting.
+    hmac = HMAC.new(srvState.hmacKey, ticket[0:80], digestmod=SHA256).digest()
+    if util.isValidHMAC(hmac, ticket[80:const.TICKET_LENGTH],
+                        srvState.hmacKey):
+        aesKey = srvState.aesKey
+    else:
+        if srvState.oldHmacKey is None:
+            return None
+
+        # Was the HMAC created using the rotated key material?
+        oldHmac = HMAC.new(srvState.oldHmacKey, ticket[0:80],
+                           digestmod=SHA256).digest()
+        if util.isValidHMAC(oldHmac, ticket[80:const.TICKET_LENGTH],
+                            srvState.oldHmacKey):
+            aesKey = srvState.oldAesKey
+        else:
+            return None
+
+    # Decrypt the ticket to extract the state information.
+    aes = AES.new(aesKey, mode=AES.MODE_CBC,
+                  IV=ticket[0:const.TICKET_AES_CBC_IV_LENGTH])
+    plainTicket = aes.decrypt(ticket[const.TICKET_AES_CBC_IV_LENGTH:80])
+
+    issueDate = struct.unpack('I', plainTicket[0:4])[0]
+    identifier = plainTicket[4:22]
+    masterKey = plainTicket[22:54]
+
+    if not (identifier == const.TICKET_IDENTIFIER):
+        log.error("The ticket's HMAC is valid but the identifier is invalid.  "
+                  "The ticket could be corrupt.")
+        return None
+
+    return ProtocolState(masterKey, issueDate=issueDate)
+
+
+class ProtocolState( object ):
+
+    """
+    Defines a ScrambleSuit protocol state contained in a session ticket.
+
+    A protocol state is essentially a master key which can then be used by the
+    server to derive session keys.  Besides, a state object contains an issue
+    date which specifies the expiry date of a ticket.  This class contains
+    methods to check the expiry status of a ticket and to dump it in its raw
+    form.
+    """
+
+    def __init__( self, masterKey, issueDate=int(time.time()) ):
+        """
+        The constructor of the `ProtocolState' class.
+
+        The four class variables are initialised.
+        """
+
+        self.identifier = const.TICKET_IDENTIFIER
+        self.masterKey = masterKey
+        self.issueDate = issueDate
+        # Pad to multiple of 16 bytes to match AES' block size.
+        self.pad = "\0\0\0\0\0\0\0\0\0\0"
+
+    def isValid( self ):
+        """
+        Verifies the expiry date of the object's issue date.
+
+        If the expiry date is not yet reached and the protocol state is still
+        valid, `True' is returned.  If the protocol state has expired, `False'
+        is returned.
+        """
+
+        assert self.issueDate
+
+        lifetime = int(time.time()) - self.issueDate
+        if lifetime > const.SESSION_TICKET_LIFETIME:
+            log.debug("The ticket is invalid and expired %s ago." %
+                      str(datetime.timedelta(seconds=
+                      (lifetime - const.SESSION_TICKET_LIFETIME))))
+            return False
+
+        log.debug("The ticket is still valid for %s." %
+                  str(datetime.timedelta(seconds=
+                  (const.SESSION_TICKET_LIFETIME - lifetime))))
+        return True
+
+    def __repr__( self ):
+        """
+        Return a raw string representation of the object's protocol state.
+
+        The length of the returned representation is exactly 64 bytes; a
+        multiple of AES' 16-byte block size.  That makes it suitable to be
+        encrypted using AES-CBC.
+        """
+
+        return struct.pack('I', self.issueDate) + self.identifier + \
+                           self.masterKey + self.pad
+
+
+class SessionTicket( object ):
+
+    """
+    Encrypts and authenticates an encapsulated `ProtocolState()' object.
+
+    This class implements a session ticket which can be redeemed by clients.
+    The class contains methods to initialise and issue session tickets.
+    """
+
+    def __init__( self, masterKey, srvState ):
+        """
+        The constructor of the `SessionTicket()' class.
+
+        The class variables are initialised and the validity of the symmetric
+        keys for the session tickets is checked.
+        """
+
+        assert (masterKey is not None) and \
+               len(masterKey) == const.MASTER_KEY_LENGTH
+
+        checkKeys(srvState)
+
+        # Initialisation vector for AES-CBC.
+        self.IV = mycrypto.strongRandom(const.TICKET_AES_CBC_IV_LENGTH)
+
+        # The server's (encrypted) protocol state.
+        self.state = ProtocolState(masterKey)
+
+        # AES and HMAC keys to encrypt and authenticate the ticket.
+        self.symmTicketKey = srvState.aesKey
+        self.hmacTicketKey = srvState.hmacKey
+
+    def issue( self ):
+        """
+        Returns a ready-to-use session ticket after prior initialisation.
+
+        After the `SessionTicket()' class was initialised with a master key,
+        this method encrypts and authenticates the protocol state and returns
+        the final result which is ready to be sent over the wire.
+        """
+
+        self.state.issueDate = int(time.time())
+
+        # Encrypt the protocol state.
+        aes = AES.new(self.symmTicketKey, mode=AES.MODE_CBC, IV=self.IV)
+        state = repr(self.state)
+        assert (len(state) % AES.block_size) == 0
+        cryptedState = aes.encrypt(state)
+
+        # Authenticate the encrypted state and the IV.
+        hmac = HMAC.new(self.hmacTicketKey,
+                        self.IV + cryptedState, digestmod=SHA256).digest()
+
+        finalTicket = self.IV + cryptedState + hmac
+        log.debug("Returning %d-byte ticket." % len(finalTicket))
+
+        return finalTicket
+
+
+# Alias class name in order to provide a more intuitive API.
+new = SessionTicket
+
+
+# Give ScrambleSuit server operators a way to manually issue new session
+# tickets for out-of-band distribution.
+if __name__ == "__main__":
+
+    import argparse
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument("ip_addr", type=str, help="The IPv4 address of the "
+                        "%s server." % const.TRANSPORT_NAME)
+    parser.add_argument("tcp_port", type=int, help="The TCP port of the %s "
+                        "server." % const.TRANSPORT_NAME)
+    parser.add_argument("ticket_file", type=str, help="The file, the newly "
+                        "issued ticket is written to.")
+    args = parser.parse_args()
+
+    print "[+] Loading server state file."
+    serverState = state.load()
+
+    print "[+] Generating new session ticket."
+    masterKey = mycrypto.strongRandom(const.MASTER_KEY_LENGTH)
+    ticket = SessionTicket(masterKey, serverState).issue()
+
+    print "[+] Writing new session ticket to `%s'." % args.ticket_file
+    tickets = dict()
+    server = IPv4Address('TCP', args.ip_addr, args.tcp_port)
+    tickets[str(server)] = [int(time.time()), masterKey, ticket]
+
+    util.writeToFile(yaml.dump(tickets), args.ticket_file)
+
+    print "[+] Success."
diff --git a/obfsproxy/transports/scramblesuit/uniformdh.py b/obfsproxy/transports/scramblesuit/uniformdh.py
new file mode 100644
index 0000000..b070b10
--- /dev/null
+++ b/obfsproxy/transports/scramblesuit/uniformdh.py
@@ -0,0 +1,185 @@
+"""
+This module implements a class to deal with Uniform Diffie-Hellman handshakes.
+
+The class `UniformDH' is used by the server as well as by the client to handle
+the Uniform Diffie-Hellman handshake used by ScrambleSuit.
+"""
+
+import const
+import random
+import binascii
+
+import Crypto.Hash.SHA256
+
+import util
+import mycrypto
+
+import obfsproxy.transports.obfs3_dh as obfs3_dh
+import obfsproxy.transports.base as base
+import obfsproxy.common.log as logging
+
+log = logging.get_obfslogger()
+
+class UniformDH( object ):
+
+    """
+    Provide methods to deal with Uniform Diffie-Hellman handshakes.
+
+    The class provides methods to extract public keys and to generate public
+    keys wrapped in a valid UniformDH handshake.
+    """
+
+    def __init__( self, sharedSecret, weAreServer ):
+        """
+        Initialise a UniformDH object.
+        """
+
+        # `True' if we are the server; `False' otherwise.
+        self.weAreServer = weAreServer
+
+        # The shared UniformDH secret.
+        self.sharedSecret = sharedSecret
+
+        # Cache a UniformDH public key until it's added to the replay table.
+        self.remotePublicKey = None
+
+        # Uniform Diffie-Hellman object (implemented in obfs3_dh.py).
+        self.udh = None
+
+    def getRemotePublicKey( self ):
+        """
+        Return the cached remote UniformDH public key.
+        """
+
+        return self.remotePublicKey
+
+    def receivePublicKey( self, data, callback, srvState=None ):
+        """
+        Extract the public key and invoke a callback with the master secret.
+
+        First, the UniformDH public key is extracted out of `data'.  Then, the
+        shared master secret is computed and `callback' is invoked with the
+        master secret as argument.  If any of this fails, `False' is returned.
+        """
+
+        # Extract the public key sent by the remote host.
+        remotePublicKey = self.extractPublicKey(data, srvState)
+        if not remotePublicKey:
+            return False
+
+        if self.weAreServer:
+            self.remotePublicKey = remotePublicKey
+            # As server, we need a DH object; as client, we already have one.
+            self.udh = obfs3_dh.UniformDH()
+
+        assert self.udh is not None
+
+        try:
+            uniformDHSecret = self.udh.get_secret(remotePublicKey)
+        except ValueError:
+            raise base.PluggableTransportError("Corrupted public key.")
+
+        # First, hash the 4096-bit UniformDH secret to obtain the master key.
+        masterKey = Crypto.Hash.SHA256.new(uniformDHSecret).digest()
+
+        # Second, session keys are now derived from the master key.
+        callback(masterKey)
+
+        return True
+
+    def extractPublicKey( self, data, srvState=None ):
+        """
+        Extract and return a UniformDH public key out of `data'.
+
+        Before the public key is touched, the HMAC is verified.  If the HMAC is
+        invalid or some other error occurs, `False' is returned.  Otherwise,
+        the public key is returned.  The extracted data is finally drained from
+        the given `data' object.
+        """
+
+        assert self.sharedSecret is not None
+
+        # Do we already have the minimum amount of data?
+        if len(data) < (const.PUBLIC_KEY_LENGTH + const.MARK_LENGTH +
+                        const.HMAC_SHA256_128_LENGTH):
+            return False
+
+        log.debug("Attempting to extract the remote machine's UniformDH "
+                  "public key out of %d bytes of data." % len(data))
+
+        handshake = data.peek()
+
+        # First, find the mark to efficiently locate the HMAC.
+        publicKey = handshake[:const.PUBLIC_KEY_LENGTH]
+        mark = mycrypto.HMAC_SHA256_128(self.sharedSecret, publicKey)
+
+        index = util.locateMark(mark, handshake)
+        if not index:
+            return False
+
+        # Now that we know where the authenticating HMAC is: verify it.
+        hmacStart = index + const.MARK_LENGTH
+        existingHMAC = handshake[hmacStart:
+                                 (hmacStart + const.HMAC_SHA256_128_LENGTH)]
+        myHMAC = mycrypto.HMAC_SHA256_128(self.sharedSecret,
+                                          handshake[0 : hmacStart] +
+                                          util.getEpoch())
+
+        if not util.isValidHMAC(myHMAC, existingHMAC, self.sharedSecret):
+            log.warning("The HMAC is invalid: `%s' vs. `%s'." %
+                        (myHMAC.encode('hex'), existingHMAC.encode('hex')))
+            return False
+
+        # Do nothing if the ticket is replayed.  Immediately closing the
+        # connection would be suspicious.
+        if srvState is not None and srvState.isReplayed(existingHMAC):
+            log.warning("The HMAC was already present in the replay table.")
+            return False
+
+        data.drain(index + const.MARK_LENGTH + const.HMAC_SHA256_128_LENGTH)
+
+        if srvState is not None:
+            log.debug("Adding the HMAC authenticating the UniformDH message " \
+                      "to the replay table: %s." % existingHMAC.encode('hex'))
+            srvState.registerKey(existingHMAC)
+
+        return handshake[:const.PUBLIC_KEY_LENGTH]
+
+    def createHandshake( self ):
+        """
+        Create and return a ready-to-be-sent UniformDH handshake.
+
+        The returned handshake data includes the public key, pseudo-random
+        padding, the mark and the HMAC.  If a UniformDH object has not been
+        initialised yet, a new instance is created.
+        """
+
+        assert self.sharedSecret is not None
+
+        log.debug("Creating UniformDH handshake message.")
+
+        if self.udh is None:
+            self.udh = obfs3_dh.UniformDH()
+        publicKey = self.udh.get_public()
+
+        assert (const.MAX_PADDING_LENGTH - const.PUBLIC_KEY_LENGTH) >= 0
+
+        # Subtract the length of the public key to make the handshake on
+        # average as long as a redeemed ticket.  That should thwart statistical
+        # length-based attacks.
+        padding = mycrypto.strongRandom(random.randint(0,
+                                        const.MAX_PADDING_LENGTH -
+                                        const.PUBLIC_KEY_LENGTH))
+
+        # Add a mark which enables efficient location of the HMAC.
+        mark = mycrypto.HMAC_SHA256_128(self.sharedSecret, publicKey)
+
+        # Authenticate the handshake including the current approximate epoch.
+        mac = mycrypto.HMAC_SHA256_128(self.sharedSecret,
+                                       publicKey + padding + mark +
+                                       util.getEpoch())
+
+        return publicKey + padding + mark + mac
+
+# Alias class name in order to provide a more intuitive API.
+new = UniformDH
diff --git a/obfsproxy/transports/scramblesuit/util.py b/obfsproxy/transports/scramblesuit/util.py
new file mode 100644
index 0000000..bbb6c6a
--- /dev/null
+++ b/obfsproxy/transports/scramblesuit/util.py
@@ -0,0 +1,175 @@
+"""
+This module implements several commonly used utility functions.
+
+The implemented functions can be used to swap variables, write and read data
+from files and to convert a number to raw text.
+"""
+
+import obfsproxy.common.log as logging
+
+import os
+import time
+import const
+
+import mycrypto
+
+log = logging.get_obfslogger()
+
+def setStateLocation( stateLocation ):
+    """
+    Set the constant `STATE_LOCATION' to the given `stateLocation'.
+
+    The variable `stateLocation' determines where persistent information (such
+    as the server's key material) is stored.  If `stateLocation' is `None', it
+    remains to be the current directory.  In general, however, it should be a
+    subdirectory of Tor's data directory.
+    """
+
+    if stateLocation is None:
+        return
+
+    if not stateLocation.endswith('/'):
+        stateLocation += '/'
+
+    # To be polite, we create a subdirectory inside wherever we are asked to
+    # store data in.
+    stateLocation += (const.TRANSPORT_NAME).lower() + '/'
+
+    # ...and if it does not exist yet, we attempt to create the full
+    # directory path.
+    if not os.path.exists(stateLocation):
+        log.info("Creating directory path `%s'." % stateLocation)
+        os.makedirs(stateLocation)
+
+    log.debug("Setting the state location to `%s'." % stateLocation)
+    const.STATE_LOCATION = stateLocation
+
+
+def isValidHMAC( hmac1, hmac2, key ):
+    """
+    Compares `hmac1' and `hmac2' after HMACing them again using `key'.
+
+    The arguments `hmac1' and `hmac2' are compared.  If they are equal, `True'
+    is returned and otherwise `False'.  To prevent timing attacks, double HMAC
+    verification is used meaning that the two arguments are HMACed again before
+    (variable-time) string comparison.  The idea is taken from:
+    https://www.isecpartners.com/blog/2011/february/double-hmac-verification.aspx
+    """
+
+    assert len(hmac1) == len(hmac2)
+
+    # HMAC the arguments again to prevent timing attacks.
+    doubleHmac1 = mycrypto.HMAC_SHA256_128(key, hmac1)
+    doubleHmac2 = mycrypto.HMAC_SHA256_128(key, hmac2)
+
+    if doubleHmac1 != doubleHmac2:
+        return False
+
+    log.debug("The computed HMAC is valid.")
+
+    return True
+
+
+def locateMark( mark, payload ):
+    """
+    Locate the given `mark' in `payload' and return its index.
+
+    The `mark' is placed before the HMAC of a ScrambleSuit authentication
+    mechanism and makes it possible to efficiently locate the HMAC.  If the
+    `mark' could not be found, `None' is returned.
+    """
+
+    index = payload.find(mark)
+    if index < 0:
+        log.debug("Could not find the mark just yet.")
+        return None
+
+    if (len(payload) - index - const.MARK_LENGTH) < \
+       const.HMAC_SHA256_128_LENGTH:
+        log.debug("Found the mark but the HMAC is still incomplete.")
+        return None
+
+    log.debug("Successfully located the mark.")
+
+    return index
+
+
+def getEpoch( ):
+    """
+    Return the Unix epoch divided by a constant as string.
+
+    This function returns a coarse-grained version of the Unix epoch.  The
+    seconds passed since the epoch are divided by the constant
+    `EPOCH_GRANULARITY'.
+    """
+
+    return str(int(time.time()) / const.EPOCH_GRANULARITY)
+
+
+def writeToFile( data, fileName ):
+    """
+    Writes the given `data' to the file specified by `fileName'.
+
+    If an error occurs, the function logs an error message but does not throw
+    an exception or return an error code.
+    """
+
+    log.debug("Opening `%s' for writing." % fileName)
+
+    try:
+        with open(fileName, "wb") as desc:
+            desc.write(data)
+
+    except IOError as err:
+        log.error("Error writing to `%s': %s." % (fileName, err))
+
+
+def readFromFile( fileName, length=-1 ):
+    """
+    Read `length' amount of bytes from the given `fileName' 
+
+    If `length' equals -1 (the default), the entire file is read and the
+    content returned.  If an error occurs, the function logs an error message
+    but does not throw an exception or return an error code.
+    """
+
+    data = None
+
+    if not os.path.exists(fileName):
+        log.debug("File `%s' does not exist (yet?)." % fileName)
+        return None
+
+    log.debug("Opening `%s' for reading." % fileName)
+
+    try:
+        with open(fileName, "rb") as desc:
+            data = desc.read(length)
+
+    except IOError as err:
+        log.error("Error reading from `%s': %s." % (fileName, err))
+
+    return data
+
+
+def sanitiseBase32( data ):
+    """
+    Try to sanitise a Base32 string if it's slightly wrong.
+
+    ScrambleSuit's shared secret might be distributed verbally which could
+    cause mistakes.  This function fixes simple mistakes, e.g., when a user
+    noted "1" rather than "I".
+    """
+
+    data = data.upper()
+
+    if "1" in data:
+        log.info("Found a \"1\" in Base32-encoded \"%s\".  Assuming " \
+                 "it's actually \"I\"." % data)
+        data = data.replace("1", "I")
+
+    if "0" in data:
+        log.info("Found a \"0\" in Base32-encoded \"%s\".  Assuming " \
+                 "it's actually \"O\"." % data)
+        data = data.replace("0", "O")
+
+    return data
diff --git a/obfsproxy/transports/transports.py b/obfsproxy/transports/transports.py
index 485269b..af1beb9 100644
--- a/obfsproxy/transports/transports.py
+++ b/obfsproxy/transports/transports.py
@@ -3,10 +3,14 @@ import obfsproxy.transports.dummy as dummy
 import obfsproxy.transports.b64 as b64
 import obfsproxy.transports.obfs2 as obfs2
 import obfsproxy.transports.obfs3 as obfs3
+import obfsproxy.transports.scramblesuit.scramblesuit as scramblesuit
 
 transports = { 'dummy' : {'base': dummy.DummyTransport, 'client' : dummy.DummyClient, 'server' : dummy.DummyServer },
                'b64'   : {'base': b64.B64Transport, 'client' : b64.B64Client, 'server' : b64.B64Server },
                'obfs2' : {'base': obfs2.Obfs2Transport, 'client' : obfs2.Obfs2Client, 'server' : obfs2.Obfs2Server },
+               'scramblesuit' : {'base':  scramblesuit.ScrambleSuitTransport,
+                                 'client':scramblesuit.ScrambleSuitClient,
+                                 'server':scramblesuit.ScrambleSuitServer },
                'obfs3' : {'base': obfs3.Obfs3Transport, 'client' : obfs3.Obfs3Client, 'server' : obfs3.Obfs3Server } }
 
 def get_transport_class(name, role):

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-privacy/packages/obfsproxy.git



More information about the Pkg-privacy-commits mailing list