Beispiel #1
0
def kozoTransport(transport, transportName, nodeName):
    global _transports
    if transport in _transports:
        return _transports[transport]
    transportFile = None
    paths = [os.path.dirname(os.path.abspath(__file__))]
    if 'KOZOTRANSPORTPATH' in os.environ:
        paths.extend(os.environ['KOZOTRANSPORTPATH'].split(':'))
    if kozoConfig('transportPath'):
        paths.extend(kozoConfig('transportPath').split(':'))
    for path in paths:
        if os.path.isdir(path) and os.path.isfile(path + os.sep + transport +
                                                  '.py'):
            transportFile = path + os.sep + transport + '.py'
            break
    if transportFile is None:
        raise KozoError('Could not find transport:', transport, paths)
    importPaths = kozoConfig('importPath').split(':')
    if 'KOZOIMPORTPATH' in os.environ:
        importPaths.extend(os.environ['KOZOIMPORTPATH'].split(':'))
    try:
        transportData = _importFile(
            transportFile, extraPaths=filter(lambda p: p, importPaths))
    except BaseException as e:
        raise KozoError('Error while trying to import transport',
                        transportFile, e)
    if 'transportInfo' not in transportData.__dict__:
        raise KozoError('transportInfo not found in', transportFile)
    if type(transportData.transportInfo) is not type({}):
        raise KozoError('transportInfo is not a dictionary in', transportFile)
    for key in ('format', 'class', 'config'):
        if key not in transportData.transportInfo:
            raise KozoError(transportFile, '- Key not found in transportInfo:',
                            key)
    if transportData.transportInfo['format'] != '1.0':
        raise KozoError(transport, 'has unsupported transport format',
                        transportData.transportInfo['format'])
    transportClass = transportData.transportInfo['class']
    transportDefaultConfig = {'type': transport}
    transportConfigRequired = []
    for key in transportData.transportInfo['config']:
        if 'default' in transportData.transportInfo['config'][key]:
            transportDefaultConfig[key] = transportData.transportInfo[
                'config'][key]['default']
        else:
            transportConfigRequired.append(key)
    transportClass._transportConfig = transportDefaultConfig
    transportClass._transportConfigRequired = transportConfigRequired
    _transports[transport] = transportClass
    return transportClass
Beispiel #2
0
def kozoRole(role, roleName, nodeName):
	global _roles
	if role in _roles:
		return _roles[role]
	roleFile = None
	paths = [os.path.dirname(os.path.abspath(__file__))]
	if 'KOZOROLEPATH' in os.environ:
		paths.extend(os.environ['KOZOROLEPATH'].split(':'))
	if kozoConfig('rolePath'):
		paths.extend(kozoConfig('rolePath').split(':'))
	for path in paths:
		if os.path.isdir(path) and os.path.isfile(path + os.sep + role + '.py'):
			roleFile = path + os.sep + role + '.py'
			break
	if roleFile is None:
		raise KozoError('Could not find role:', role, paths)
	importPaths = kozoConfig('importPath').split(':')
	if 'KOZOIMPORTPATH' in os.environ:
		importPaths.extend(os.environ['KOZOIMPORTPATH'].split(':'))
	try:
		roleData = _importFile(roleFile, extraPaths=filter(lambda p: p, importPaths))
	except BaseException as e:
		raise KozoError('Error while trying to import role', roleFile, e)
	if 'roleInfo' not in roleData.__dict__:
		raise KozoError('roleInfo not found in', roleFile)
	if type(roleData.roleInfo) is not type({}):
		raise KozoError('roleInfo is not a dictionary in', roleFile)
	for key in ('format', 'class', 'config'):
		if key not in roleData.roleInfo:
			raise KozoError(roleFile, '- Key not found in roleInfo:', key)
	if roleData.roleInfo['format'] != '1.0':
		raise KozoError(role, 'has unsupported role format', roleData.roleInfo['format'])
	roleClass = roleData.roleInfo['class']
	roleDefaultConfig = {'type': role}
	roleConfigRequired = []
	for key in roleData.roleInfo['config']:
		if 'default' in roleData.roleInfo['config'][key]:
			roleDefaultConfig[key] = roleData.roleInfo['config'][key]['default']
		else:
			roleConfigRequired.append(key)
	roleClass._roleConfig = roleDefaultConfig
	roleClass._roleConfigRequired = roleConfigRequired
	_roles[role] = roleClass
	return roleClass
Beispiel #3
0
	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)
Beispiel #4
0
	def getUnauthenticatedSocket(self, otherTransport, addressIndex, address):
		return socket.create_connection(address, kozoConfig('connectionRetry'))
Beispiel #5
0
	def _readLoop(self, read, bytes):
		data = _readLoop(read, bytes, kozoConfig('connectionRetry'))
		if data is None:
			raise KozoError('Could not read', bytes, 'bytes before timeout')
		return data
Beispiel #6
0
	def _genRandom(self):
		numBytes = random.randint(*self.RANDOM_LENGTH) # We can be more lax with the range
		data = _readLoop(Crypto.Random.new().read, numBytes, min(self.RANDOM_DEADLINE, kozoConfig('connectionRetry')))
		if data is None:
			raise KozoError('Could not generate', numBytes, 'of random data fast enough')
		return data
Beispiel #7
0
	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