Commit 4b7d7e5c authored by Philipp Winter's avatar Philipp Winter
Browse files

Merge branch 'bug_10893'

Conflicts:
	unittests.py
parents 8f978aab efe7a483
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
2014-XX-XX - Changes in version 2014.XX.X:
  - Extend the spec and fix several issues.  This should fix bug
    <https://bugs.torproject.org/10893>.  Thanks to Yawning Angel.
  - Use a randomly generated fallback password if the bridge operator did not
    use Tor's ServerTransportOptions.
  - Fix bug <https://bugs.torproject.org/11100> which broke the server's key
+84 −31
Original line number Diff line number Diff line
@@ -65,13 +65,14 @@
    The mark is used to easily locate the MAC which is the last element of the
    client's handshake message.  The 16-byte MAC is defined as:

      MAC = HMAC-SHA256-128(k_B, X | P_C | E)
      MAC = HMAC-SHA256-128(k_B, X | P_C | M_C | E)

    The variable E is a string representation of the current Unix epoch divided
    by 3600.  It represents the amount of hours which have passed since the
    epoch.  It is used by the client and the server to prove liveness.  For
    example, the Unix timestamp 1378768086 would map to E = 1378768086 / 3600 =
    "382991".
    "382991".  While the client MUST determine E, the server can simply echo
    the client's E in its response.

    The server's handshake message is created analogous to the client.

@@ -81,14 +82,14 @@
    discussed in Section 2.3.

    Client                              Server     Legend:
      |  X | P_C | M_C | MAC(X | P_C | E)  |    X: client public key
      | ---------------------------------> |    Y: server public key
      |  Y | P_S | M_S | MAC(X | P_S | E)  |    P_{C,S}: padding
      | <--------------------------------- |    M_{C,S}: mark to locate the MAC
      |  X | P_C | M_C | MAC(X | P_C | M_C | E)  | X: client public key
      | ---------------------------------------> | Y: server public key
      |  Y | P_S | M_S | MAC(Y | P_S | M_S | E)  | P_{C,S}: padding
      | <--------------------------------------- | M_{C,S}: mark to locate MAC
      |            AEnc(k_t+1 | T_t+1)           | E: approximate timestamp
      | <--------------------------------- |    k_t+1: future master key
      | <--------------------------------------- | k_t+1: future master key
      |             AEnc(Tor traffic)            | T_t+1: future ticket
      | <--------------------------------> |
      | <--------------------------------------> |

    Immediately after the handshake succeeded, the server proceeds to issue and
    send a new session ticket T_t+1 together with the according master key
@@ -103,7 +104,7 @@
    A client can authenticate itself towards a ScrambleSuit server by redeeming
    a 112-byte session ticket T.  Such a ticket contains the master key k_t and
    is encrypted and authenticated using keys only known to the server.  The
    structure of a session ticket is discussed in Section XXX.  If a valid
    structure of a session ticket is discussed in Section 5.1.  If a valid
    session ticket is available, a client SHOULD redeem it rather than conduct
    a UniformDH handshake.

@@ -113,26 +114,29 @@
    uniformly chosen from {0..1388} bytes.  After the padding, a 16-byte mark M
    is appended which is defined as:

      M = HMAC-SHA256-128(k_t, T)
      M = HMAC-SHA256-128(k_sh, T)

    The mark is used to easily locate the MAC which is the last part of the
    handshake.  The MAC is defined as:
    handshake.  k_sh is the 256-bit HMAC key which is used by the client to
    authenticate outgoing data.  It is derived from k_t (which is embedded in
    the ticket) as described in Section 2.3.  The MAC is defined as:

      MAC = HMAC-SHA256-128(k_t, T | P | E)
      MAC = HMAC-SHA256-128(k_sh, T | P | M | E)

    The variable E is a string representation of the current Unix epoch divided
    by 3600.  It represents the amount of hours which have passed since the
    epoch.  It is used by the client and the server to prove liveness.  For
    example, the Unix timestamp 1378768086 would map to E = 1378768086 / 3600 =
    "382991".
    "382991".  While the client MUST determine E, the server can simply echo
    the client's E in its response.

    Client                            Server   Legend:
      |  T | P | M | MAC(T | P | E)  |     T: session ticket
      | ---------------------------> |     P: random padding
      |  T | P | M | MAC(T | P | M | E)  |     T: session ticket
      | -------------------------------> |     P: random padding
      |        AEnc(k_t+1 | T_t+1)       |     M: mark to locate the MAC
      | <--------------------------- |     E: approximate timestamp
      | <------------------------------- |     E: approximate timestamp
      |         AEnc(Tor traffic)        |     k_t+1: future master key
      | <--------------------------> |     T_t+1: future ticket
      | <------------------------------> |     T_t+1: future ticket

    The server is initially unable to distinguish between a session ticket
    handshake and a UniformDH handshake as both handshakes are computationally
@@ -140,10 +144,10 @@
    opportunistically decrypt the session ticket T after verifying its MAC.  If
    the ticket's MAC (which should not be confused with the handshake message's
    MAC) is valid and the ticket can be decrypted and is not yet expired, the
    server then verifies the MAC which is built over T | P | E.  If this MAC is
    valid, the handshake succeeded.  The server, like the client, then proceeds
    to derive session keys from the 256-bit master key as described in Section
    2.3.
    server then verifies the MAC which is built over T | P | M | E.  If this
    MAC is valid, the handshake succeeded.  The server, like the client, then
    proceeds to derive session keys from the 256-bit master key as described in
    Section 2.3.

    After a ticket handshake succeeded, the server replies by issuing a new
    session ticket T_t+1 together with the according master key k_t+1.  The
@@ -191,7 +195,7 @@
    padding which is used for packet length obfuscation.  Note that both fields
    can be set to 0 which results in an empty protocol message.  ScrambleSuit's
    maximum message length is 1448 bytes.  Exluding the header, this results in
    1437 bytes for the transported data.
    1427 bytes for the transported data.

    The 1-byte flag field is used for protocol signalling.  Below, all defined
    flags along with their semantics are explained.
@@ -231,9 +235,58 @@
    random samples dictate specific inter-arrival times and packet lengths.
    Both probability distributions are generated based on a random 256-bit PRNG
    seed which is unique for every ScrambleSuit server.  Servers communicate
    their seed to clients in a protocol message whose FLAG_PRNG_SEED bit is set
    to "1".  The client then extracts the PRNG seed and derives the same
    probability distributions as the server.
    their seed to clients in a dedicated protocol message whose FLAG_PRNG_SEED
    bit is set.  The client then extracts the PRNG seed and derives its own
    probability distributions.

4.1 Deriving Probability Distributions

    Probability distributions SHOULD be derived from the 256-bit seed using a
    cryptographically secure PRNG.  After the CSPRNG was seeded, the amount of
    bins for the respective probability distribution must be determined.
    Depending on the CSPRNG's output, the amount SHOULD be uniformly chosen
    from {1..100}.  The exact way how the CSPRNG's output is used is up to the
    implementation.

    After the amount of bins has been determined, every bin is assigned a value
    together with a corresponding probability which is in the interval ]0, 1].
    The probability of all bins sums up to 1.  Again, the exact way how the
    CSPRNG's output is used is up to the implementation.

    For the packet length distribution, all values SHOULD be in {21..1448}.

    For the inter-arrival time distribution, all values SHOULD be in the
    interval [0, 0.01].

    Since the distributions are generated randomly, it is possible that they
    cause particularly bad throughput.  To prevent this, implementations MAY
    trade off obfuscation for additional throughput by carefully tuning the
    above parameters.

4.2 Packet Length Obfuscation

    In general, ScrambleSuit transmits MTU-sized segments as long as there is
    enough data in the send buffer.  Packet length obfuscation only kicks in
    once the send buffer is almost processed and a segment smaller than the MTU
    would have to be sent.

    Instead of simply flushing the send buffer, a random sample from the
    discrete packet length probability distribution is drawn.  Padding messages
    are then appended so that the size of the last segment in the burst equals
    the freshly drawn sample.

4.3 Inter-arrival Time Obfuscation

    To obfuscate inter-arrival times, implementations could maintain a
    dedicated send buffer.  As long as there is data in the send buffer, random
    samples from the inter-arrival time distribution are drawn.  The thread
    processing the send buffer is then paused for the duration of the freshly
    drawn sample until the next MTU-sized chunk is written to the wire.  This
    process is repeated until the send buffer is empty.

    Note that inter-arrival time obfuscation has a drastic impact on
    throughput.  As a result, implementations MAY implement packet length
    obfuscation but ignore inter-arrival time obfuscation.

5.  Session Tickets

+14 −8
Original line number Diff line number Diff line
@@ -396,14 +396,20 @@ class ScrambleSuitTransport( base.BaseTransport ):
        existingHMAC = potentialTicket[index + const.MARK_LENGTH:
                                       index + const.MARK_LENGTH +
                                       const.HMAC_SHA256_128_LENGTH]
        authenticated = False
        for epoch in util.expandedEpoch():
            myHMAC = mycrypto.HMAC_SHA256_128(self.recvHMAC,
                                          potentialTicket[0:
                                          index + const.MARK_LENGTH] +
                                          util.getEpoch())
                                              potentialTicket[0:index + \
                                              const.MARK_LENGTH] + epoch)

        if not util.isValidHMAC(myHMAC, existingHMAC, self.recvHMAC):
            log.warning("The HMAC is invalid: `%s' vs. `%s'." %
                        (myHMAC.encode('hex'), existingHMAC.encode('hex')))
            if util.isValidHMAC(myHMAC, existingHMAC, self.recvHMAC):
                authenticated = True
                break

            log.debug("HMAC invalid.  Trying next epoch value.")

        if not authenticated:
            log.warning("Could not verify the authentication message's HMAC.")
            return False

        # Do nothing if the ticket is replayed.  Immediately closing the
+24 −8
Original line number Diff line number Diff line
@@ -46,6 +46,9 @@ class UniformDH( object ):
        # Uniform Diffie-Hellman object (implemented in obfs3_dh.py).
        self.udh = None

        # Used by the server so it can simply echo the client's epoch.
        self.echoEpoch = None

    def getRemotePublicKey( self ):
        """
        Return the cached remote UniformDH public key.
@@ -121,13 +124,21 @@ class UniformDH( object ):
        hmacStart = index + const.MARK_LENGTH
        existingHMAC = handshake[hmacStart:
                                 (hmacStart + const.HMAC_SHA256_128_LENGTH)]

        authenticated = False
        for epoch in util.expandedEpoch():
            myHMAC = mycrypto.HMAC_SHA256_128(self.sharedSecret,
                                          handshake[0 : hmacStart] +
                                          util.getEpoch())
                                              handshake[0 : hmacStart] + epoch)

            if util.isValidHMAC(myHMAC, existingHMAC, self.sharedSecret):
                self.echoEpoch = epoch
                authenticated = True
                break

        if not util.isValidHMAC(myHMAC, existingHMAC, self.sharedSecret):
            log.warning("The HMAC is invalid: `%s' vs. `%s'." %
                        (myHMAC.encode('hex'), existingHMAC.encode('hex')))
            log.debug("HMAC invalid.  Trying next epoch value.")

        if not authenticated:
            log.warning("Could not verify the authentication message's HMAC.")
            return False

        # Do nothing if the ticket is replayed.  Immediately closing the
@@ -174,10 +185,15 @@ class UniformDH( object ):
        # Add a mark which enables efficient location of the HMAC.
        mark = mycrypto.HMAC_SHA256_128(self.sharedSecret, publicKey)

        if self.echoEpoch is None:
            epoch = util.getEpoch()
        else:
            epoch = self.echoEpoch
            log.debug("Echoing epoch rather than recreating it.")

        # Authenticate the handshake including the current approximate epoch.
        mac = mycrypto.HMAC_SHA256_128(self.sharedSecret,
                                       publicKey + padding + mark +
                                       util.getEpoch())
                                       publicKey + padding + mark + epoch)

        return publicKey + padding + mark + mac

+56 −0
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ import scramblesuit
import base64
import shutil
import tempfile
import ticket
import state

import message
@@ -161,6 +162,29 @@ class UniformDHTest( unittest.TestCase ):

        self.failIf(self.udh.receivePublicKey(buf, lambda x: x) == True)

    def test4_extractPublicKey( self ):

        # Create UniformDH authentication message.
        sharedSecret = "A" * const.SHARED_SECRET_LENGTH

        realEpoch = util.getEpoch

        # Try three valid and one invalid epoch value.
        for epoch in util.expandedEpoch() + ["000000"]:
            udh = uniformdh.new(sharedSecret, True)

            util.getEpoch = lambda: epoch
            authMsg = udh.createHandshake()
            util.getEpoch = realEpoch

            buf = obfs_buf.Buffer()
            buf.write(authMsg)

            if epoch == "000000":
                self.assertFalse(udh.extractPublicKey(buf))
            else:
                self.assertTrue(udh.extractPublicKey(buf))

class UtilTest( unittest.TestCase ):

    def test1_isValidHMAC( self ):
@@ -354,6 +378,38 @@ class MessageTest( unittest.TestCase ):
        self.assertRaises(base.PluggableTransportError,
                          message.ProtocolMessage, "1", paddingLen=const.MPU)

class TicketTest( unittest.TestCase ):

    def test1_authentication( self ):
        srvState = state.State()
        srvState.genState()

        ss = scramblesuit.ScrambleSuitTransport()
        ss.srvState = srvState

        realEpoch = util.getEpoch

        # Try three valid and one invalid epoch value.
        for epoch in util.expandedEpoch() + ["000000"]:

            util.getEpoch = lambda: epoch

            # Prepare ticket message.
            blurb = ticket.issueTicketAndKey(srvState)
            rawTicket = blurb[const.MASTER_KEY_LENGTH:]
            masterKey = blurb[:const.MASTER_KEY_LENGTH]
            ss.deriveSecrets(masterKey)
            ticketMsg = ticket.createTicketMessage(rawTicket, ss.recvHMAC)

            util.getEpoch = realEpoch

            buf = obfs_buf.Buffer()
            buf.write(ticketMsg)

            if epoch == "000000":
                self.assertFalse(ss.receiveTicket(buf))
            else:
                self.assertTrue(ss.receiveTicket(buf))

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