def bind(self): AuthenticatedTransport.bind(self) self._adapter.SetProperty('Discoverable', self._address is None, signature='sv') self._serverSocket = bluetooth.BluetoothSocket(bluetooth.RFCOMM) self._serverSocket.bind(('', bluetooth.PORT_ANY)) self._serverSocket.listen(self['socketConnectionBacklog']) bluetooth.advertise_service(self._serverSocket, 'kozo', service_id=self._uuid, service_classes=[self._uuid, bluetooth.SERIAL_PORT_CLASS], profiles=[bluetooth.SERIAL_PORT_PROFILE]) infoTransport(self, 'Bound on port', self._serverSocket.getsockname()[1], 'with UUID', self._uuid)
def getUnauthenticatedSocket(self, otherTransport, addressIndex, address): targetAddress, targetUuid = address serviceMatches = bluetooth.find_service(name='kozo', address=targetAddress, uuid=targetUuid) infoTransport(self, 'Found', len(serviceMatches), 'services matches for UUID', targetUuid, '/ Address', targetAddress) if not len(serviceMatches): return None sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM) sock.connect((serviceMatches[0]['host'], serviceMatches[0]['port'])) return _BluetoothSocketWrapper(sock)
def connect(self, otherTransport): assert self._privateKey is not None selfNode = self.getNode() selfName = selfNode.getName() remoteNode = otherTransport.getNode() remoteName = remoteNode.getName() remoteAddress = (otherTransport['address'], otherTransport['onionPort']) try: sock = socket.create_connection(remoteAddress, kozoConfig('connectionRetry')) # First, send magic knock-knock message. sock.sendall(self.MAGIC_KNOCKKNOCK + struct.pack('=H', len(selfName)) + selfName.encode('utf8')) # We expect to be asked to sign a message of length up to 32K characters. signLength = struct.unpack('=H', self._readLoop(sock.recv, struct.calcsize('=H')))[0] if signLength > self.MAX_INITIAL_SIGN_LENGTH: raise KozoError('Initial signature length too long:', signLength) # Get message to sign. initialToSign = self._readLoop(sock.recv, signLength) # Expand it. selfDate = int(time.time()) selfRandom = self._genRandom() actualToSign = self.ACTUAL_SIGN_FORMAT.format( initial=initialToSign, date=str(selfDate).encode('utf8'), server=remoteName.encode('utf8'), client=selfName.encode('utf8'), random=selfRandom ) # Sign it. Some versions of Paramiko take a random pool here, others don't. Try both. try: signedMessage = self._privateKey.sign_ssh_data(Crypto.Random.new(), actualToSign) except TypeError: signedMessage = self._privateKey.sign_ssh_data(actualToSign) signed = bytes(signedMessage) # Send signed response back. sock.sendall(struct.pack('=Qii', selfDate, len(selfRandom), len(signed)) + selfRandom + signed) # Expect acknowledgement. acknowledgement = self._readLoop(sock.recv, len(self.SIGN_OK)) if acknowledgement != self.SIGN_OK: raise KozoError('Invalid acknowledgement.') # We're all clear. return OnionChannel(selfNode, remoteNode, sock) except BaseException as e: infoTransport(self, 'Failed to connect to', remoteAddress, e, printTraceback=False)
def accept(self): infoTransport(self, 'Waiting for a connection') assert self._serverSocket is not None selfNode = self.getNode() selfName = selfNode.getName() connection = self._serverSocket.accept()[0] try: connection.settimeout(kozoConfig('connectionRetry')) # Parse header. knockHeader = self._readLoop(connection.recv, len(self.MAGIC_KNOCKKNOCK)) if knockHeader != self.MAGIC_KNOCKKNOCK: raise KozoError('Invalid header received') nodeNameLength = struct.unpack('=H', self._readLoop(connection.recv, struct.calcsize('=H')))[0] if nodeNameLength > self._maxNodeNameLength: raise KozoError('Node length field larger than largest-named node defined in the system:', nodeNameLength) remoteName = self._readLoop(connection.recv, nodeNameLength).decode('utf8') remoteNode = kozoSystem().getNodeByName(remoteName) if remoteNode is None: raise KozoError('Unknown node name received', repr(remoteName)) remoteKey = paramiko.RSAKey(data=base64.b64decode(remoteNode.getPublicKey()[1])) # Generate message to sign. selfDate = int(time.time()) selfRandom = self._genRandom() initialToSign = self.INITIAL_SIGN_FORMAT.format( date=str(selfDate).encode('utf8'), server=selfName.encode('utf8'), client=remoteName.encode('utf8'), random=selfRandom ) # Send it. connection.sendall(struct.pack('=H', len(initialToSign)) + initialToSign) # Get and verify signed response. remoteDate, remoteRandomLength, remoteSignedLength = struct.unpack('=Qii', self._readLoop(connection.recv, struct.calcsize('=Qii'))) if abs(remoteDate - selfDate) > self.MAX_DATE_DELTA: raise KozoError('Clocks differ by', abs(remoteDate - selfDate), 'seconds; rejecting message') if remoteRandomLength < self.RANDOM_LENGTH[0]: raise KozoError('Remote random string is shorter than minimum of', self.RANDOM_LENGTH[0], 'bytes') if remoteRandomLength > self.RANDOM_LENGTH[1]: raise KozoError('Remote random string is longer than maximum of', self.RANDOM_LENGTH[1], 'bytes') remoteRandom = self._readLoop(connection.recv, remoteRandomLength) actualToSign = self.ACTUAL_SIGN_FORMAT.format( initial=initialToSign, date=str(remoteDate).encode('utf8'), server=selfName.encode('utf8'), client=remoteName.encode('utf8'), random=remoteRandom ) if len(actualToSign) > self.MAX_ACTUAL_SIGN_LENGTH: raise KozoError('Message to sign is too long:', len(actualToSign), 'bytes while max is', self.MAX_ACTUAL_SIGN_LENGTH) if remoteSignedLength > len(actualToSign) * self.SIGNED_MAX_EXPANSION_FACTOR: raise KozoError('Signed message is too long:', remoteSignedLength, 'bytes while max is', len(actualToSign) * self.SIGNED_MAX_EXPANSION_FACTOR) actualSigned = paramiko.Message(self._readLoop(connection.recv, remoteSignedLength)) if not remoteKey.verify_ssh_sig(actualToSign, actualSigned): raise KozoError('Invalid signature received') # Send acknowledgement. connection.sendall(self.SIGN_OK) # We're clear. return OnionChannel(remoteNode, selfNode, connection) except BaseException: try: connection.close() except: pass raise