Exemple #1
0
    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("Given password '%s' is not " \
                    "valid Base32!  Run 'generate_password.py' to generate " \
                    "a good password." % 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
Exemple #2
0
    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
Exemple #3
0
    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
Exemple #4
0
    def _scan_for_magic(self, data):
        """
        Scan 'data' for the magic string. If found, drain it and all
        the padding before it. Then open the connection.
        """

        log_prefix = "obfs3:_scan_for_magic()"
        log.debug("%s: Searching for magic." % log_prefix)

        assert (self.other_magic_value)
        chunk = data.peek()

        index = chunk.find(self.other_magic_value)
        if index < 0:
            if (len(data) > MAX_PADDING + HASHLEN):
                raise base.PluggableTransportError(
                    "obfs3: Too much padding (%d)!" % len(data))
            log.debug("%s: Did not find magic this time (%d)." %
                      (log_prefix, len(data)))
            return

        index += len(self.other_magic_value)
        log.debug("%s: Found magic. Draining %d bytes." % (log_prefix, index))
        data.drain(index)

        self.state = ST_OPEN
    def unpack(self, data, aes):

        # Input buffer which is not yet processed and forwarded.
        self.recvBuf += data
        fwdBuf = ""

        # Keep trying to unpack as long as there seems to be enough data.
        while len(self.recvBuf) >= const.HDR_LENGTH:

            # Extract length fields if we don't have them already.
            if self.totalLen == None:
                self.totalLen = pack.ntohs(aes.decrypt(self.recvBuf[16:18]))
                self.payloadLen = pack.ntohs(aes.decrypt(self.recvBuf[18:20]))

                # Abort immediately if the extracted lengths do not make sense.
                if not message.saneLengths(self.totalLen, self.payloadLen):
                    raise base.PluggableTransportError("Invalid message " \
                            "length(s): totalLen=%d, payloadLen=%d." % \
                            (self.totalLen, self.payloadLen))
                log.debug("Message header: totalLen=%d, payloadLen=%d." % \
                        (self.totalLen, self.payloadLen))

            if (len(self.recvBuf) - const.HDR_LENGTH) < self.totalLen:
                return fwdBuf

            # We have a full message; let's extract it.
            else:
                log.debug("Extracting fully received protocol message.")
                rcvdHMAC = self.recvBuf[0:const.HMAC_LENGTH]
                vrfyHMAC = mycrypto.MyHMAC_SHA256_128(self.recvHMAC, \
                        self.recvBuf[const.HMAC_LENGTH:(self.totalLen + \
                        const.HDR_LENGTH)])

                # Abort immediately if the HMAC is invalid.
                if rcvdHMAC != vrfyHMAC:
                    raise base.PluggableTransportError("Invalid HMAC!")

                fwdBuf += aes.decrypt(self.recvBuf[const.HDR_LENGTH: \
                        (self.totalLen+const.HDR_LENGTH)])[:self.payloadLen]

                self.recvBuf = self.recvBuf[const.HDR_LENGTH + self.totalLen:]
                # Protocol message extracted - resetting length fields.
                self.totalLen = self.payloadLen = None

        log.debug("Unpacked %d bytes of data: 0x%s..." % \
                (len(fwdBuf), fwdBuf[:10].encode('hex')))
        return fwdBuf
Exemple #6
0
    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="******"The SOCKS argument must start with "
                                      "`password='******'=')[1].strip()))
        except TypeError as error:
            log.error(error.message)
            raise base.PluggableTransportError("Given password '%s' is not " \
                    "valid Base32!  Run 'generate_password.py' to generate " \
                    "a good password." % 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)
Exemple #7
0
 def parseControlFields(self):
     """Extract control message fields."""
     # Parse `opcode`
     if self.opcode == None:
         self.opcode = self.getOpCode()
     # Sanity check of the opcode
     if not isOpCodeSane(self.opcode):
         raise base.PluggableTransportError("Invalid control opcode: %s" %
                                            self.opcode)
     # Parse args
     self.argsLen = self.getargsLen()
     self.args += self.getMessageField(const.ARGS_POS, self.argsLen)
Exemple #8
0
 def parseMinHeaderFields(self):
     """Extract common header fields, if necessary."""
     if not self.totalLen == self.payloadLen == self.flags == None:
         return
     # Parse common header fields
     self.totalLen = self.getTotalLen()
     self.payloadLen = self.getPayloadLen()
     self.flags = self.getFlags()
     # Sanity check of the fields
     if not isSane(self.totalLen, self.payloadLen, self.flags):
         log.error("TotalLen: %s, PayloadLen: %s, Flags: %s", self.totalLen,
                   self.payloadLen, self.flags)
         raise base.PluggableTransportError("Invalid header field.")
Exemple #9
0
    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
Exemple #10
0
    def _read_handshake(self, data, circuit):
        """
        Read handshake message, parse the other peer's public key and
        set up our crypto.
        """

        log_prefix = "obfs3:_read_handshake()"
        if len(data) < PUBKEY_LEN:
            log.debug("%s: Not enough bytes for key (%d)." %
                      (log_prefix, len(data)))
            return

        log.debug("%s: Got %d bytes of handshake data (waiting for key)." %
                  (log_prefix, len(data)))

        # Get the public key from the handshake message, do the DH and
        # get the shared secret.
        other_pubkey = data.read(PUBKEY_LEN)
        try:
            self.shared_secret = self.dh.get_secret(other_pubkey)
        except ValueError:
            raise base.PluggableTransportError(
                "obfs3: Corrupted public key '%s'" % repr(other_pubkey))
        log.debug("Got public key: %s.\nGot shared secret: %s" %
                  (repr(other_pubkey), repr(self.shared_secret)))

        # Set up our crypto.
        self.send_crypto = self._derive_crypto(self.send_keytype)
        self.recv_crypto = self._derive_crypto(self.recv_keytype)
        self.other_magic_value = hmac_sha256.hmac_sha256_digest(
            self.shared_secret, self.recv_magic_const)

        # Send our magic value to the remote end and append the queued outgoing data.
        # Padding is prepended so that the server does not just send the 32-byte magic
        # in a single TCP segment.
        padding_length = random.randint(0, MAX_PADDING / 2)
        magic = hmac_sha256.hmac_sha256_digest(self.shared_secret,
                                               self.send_magic_const)
        message = rand.random_bytes(
            padding_length) + magic + self.send_crypto.crypt(self.queued_data)
        self.queued_data = ''

        log.debug("%s: Transmitting %d bytes (with magic)." %
                  (log_prefix, len(message)))
        circuit.downstream.write(message)

        self.state = ST_SEARCHING_MAGIC
Exemple #11
0
 def __init__(self,
              payload='',
              paddingLen=0,
              flags=const.FLAG_DATA,
              opcode=None,
              args=""):
     self.payload = payload
     self.payloadLen = len(self.payload)
     self.totalLen = self.payloadLen + paddingLen
     if (self.totalLen) > const.MPU:
         raise base.PluggableTransportError(
             "The transport created a message longer than TCP's MTU.")
     self.sndTime = 0
     self.rcvTime = 0
     self.flags = flags
     self.opcode = opcode
     self.argsLen = len(args)
     self.args = args
Exemple #12
0
    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
Exemple #13
0
    def expand(self):
        """Returns the expanded output key material which 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 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:
                try:
                    cls.uniformDHSecret = base64.b32decode(util.sanitiseBase32(
                            cfg["password"]))
                except TypeError as error:
                    log.error(error.message)
                    raise base.PluggableTransportError("Given password '%s' " \
                            "is not valid Base32!  Run " \
                            "'generate_password.py' to generate a good " \
                            "password." % cfg["password"])

                cls.uniformDHSecret = cls.uniformDHSecret.strip()

        if cls.weAreServer:
            if not hasattr(cls, "uniformDHSecret"):
                log.debug("Using fallback password for descriptor file.")
                srv = state.load()
                cls.uniformDHSecret = srv.fallbackPassword
            state.writeServerDescriptor(cls.uniformDHSecret,
                                        transportConfig.getBindAddr(),
                                        cls.weAreExternal)
Exemple #15
0
    def receivedDownstream(self, data):
        """
        Got data from downstream. We need to de-obfuscate them and
        proxy them upstream.
        """
        log_prefix = "obfs2 receivedDownstream" # used in logs

        if self.state == ST_WAIT_FOR_KEY:
            log.debug("%s: Waiting for key." % log_prefix)
            if len(data) < SEED_LENGTH + 8:
                log.debug("%s: Not enough bytes for key (%d)." % (log_prefix, len(data)))
                return data # incomplete

            if self.we_are_initiator:
                self.responder_seed = data.read(SEED_LENGTH)
            else:
                self.initiator_seed = data.read(SEED_LENGTH)

            # Now that we got the other seed, let's set up our crypto.
            self.send_crypto = self._derive_crypto(self.send_keytype)
            self.recv_crypto = self._derive_crypto(self.recv_keytype)
            self.recv_padding_crypto = \
                self._derive_padding_crypto(self.responder_seed if self.we_are_initiator else self.initiator_seed,
                                            self.recv_pad_keytype)

            # XXX maybe faster with a single d() instead of two.
            magic = srlz.ntohl(self.recv_padding_crypto.crypt(data.read(4)))
            padding_length = srlz.ntohl(self.recv_padding_crypto.crypt(data.read(4)))

            log.debug("%s: Got %d bytes of handshake data (padding_length: %d, magic: %s)" % \
                          (log_prefix, len(data), padding_length, hex(magic)))

            if magic != MAGIC_VALUE:
                raise base.PluggableTransportError("obfs2: Corrupted magic value '%s'" % hex(magic))
            if padding_length > MAX_PADDING:
                raise base.PluggableTransportError("obfs2: Too big padding length '%s'" % padding_length)

            self.padding_left_to_read = padding_length
            self.state = ST_WAIT_FOR_PADDING

        while self.padding_left_to_read:
            if not data: return

            n_to_drain = self.padding_left_to_read
            if (self.padding_left_to_read > len(data)):
                n_to_drain = len(data)

            data.drain(n_to_drain)
            self.padding_left_to_read -= n_to_drain
            log.debug("%s: Consumed %d bytes of padding, %d still to come (%d).",
                      log_prefix, n_to_drain, self.padding_left_to_read, len(data))

        self.state = ST_OPEN
        log.debug("%s: Processing %d bytes of application data.",
                  log_prefix, len(data))

        if self.pending_data_to_send:
            log.debug("%s: We got pending data to send and our crypto is ready. Pushing!" % log_prefix)
            self.receivedUpstream(self.circuit.upstream.buffer) # XXX touching guts of network.py
            self.pending_data_to_send = False

        self.circuit.upstream.write(self.recv_crypto.crypt(data.read()))
Exemple #16
0
 def newControl(self, opcode, args="", payload="", paddingLen=0):
     """Shortcut to create a single control message."""
     if len(args) > const.MPU:
         raise base.PluggableTransportError("Arguments are too long.")
     return self.new(payload, paddingLen, const.FLAG_CONTROL, opcode, args)