async def set_key(self, module, conn_id, conn_io, encryption, key): assert module.node is self assert encryption in module.get_supported_encryption() io_id = await conn_io.get_index(module) nonce = module.nonce module.nonce += 1 ad = tools.pack_int8(encryption) + \ tools.pack_int16(conn_id) + \ tools.pack_int16(io_id) + \ tools.pack_int16(nonce) cipher = await Encryption.AES.encrypt(await module.get_key(), ad, key) payload = tools.pack_int16(module.id) + \ tools.pack_int16(ReactiveEntrypoint.SetKey) + \ ad + \ cipher command = CommandMessage(ReactiveCommand.Call, Message(payload), self.ip_address, self.reactive_port) await self._send_reactive_command( command, log='Setting key of connection {} ({}:{}) on {} to {}'.format( conn_id, module.name, conn_io.name, self.name, binascii.hexlify(key).decode('ascii')))
async def connect(self, to_module, conn_id): """ ### Description ### Coroutine. Inform the EM of the source module that a new connection has been established, so that events can be correctly forwared to the recipient ### Parameters ### self: Node object to_module (XXXModule): destination module conn_id (int): ID of the connection ### Returns ### """ module_id = await to_module.get_id() payload = tools.pack_int16(conn_id) + \ tools.pack_int16(module_id) + \ tools.pack_int8(int(to_module.node is self)) + \ tools.pack_int16(to_module.node.reactive_port) + \ to_module.node.ip_address.packed command = CommandMessage(ReactiveCommand.Connect, Message(payload), self.ip_address, self.reactive_port) await self._send_reactive_command(command, log='Connecting id {} to {}'.format( conn_id, to_module.name))
async def register_entrypoint(self, module, entry, frequency): """ ### Description ### Coroutine. Register an entry point for periodic tasks ### Parameters ### self: Node object module (XXXModule): target module entry (str): entry point to call frequency (int): desired frequency of which the entry point will be called ### Returns ### """ assert module.node is self module_id, entry_id = \ await asyncio.gather(module.get_id(), module.get_entry_id(entry)) payload = tools.pack_int16(module_id) + \ tools.pack_int16(entry_id) + \ tools.pack_int32(frequency) command = CommandMessage(ReactiveCommand.RegisterEntrypoint, Message(payload), self.ip_address, self.reactive_port) await self._send_reactive_command( command, log='Sending RegisterEntrypoint command of {}:{} ({}:{}) on {}'. format(module.name, entry, module_id, entry_id, self.name))
async def attest(self, module): assert module.node is self module_id, module_key = await asyncio.gather(module.id, module.key) challenge = tools.generate_key(16) # The payload format is [sm_id, entry_id, 16 bit nonce, index, wrapped(key), tag] # where the tag includes the nonce and the index. payload = tools.pack_int16(module_id) + \ tools.pack_int16(ReactiveEntrypoint.Attest) + \ tools.pack_int16(len(challenge)) + \ challenge command = CommandMessage(ReactiveCommand.Call, Message(payload), self.ip_address, self.reactive_port) res = await self._send_reactive_command(command, log='Attesting {}'.format( module.name)) # The result format is [tag] where the tag is the challenge's MAC challenge_response = res.message.payload expected_tag = await Encryption.SPONGENT.mac(module_key, challenge) if challenge_response != expected_tag: raise Error('Attestation of {} failed'.format(module.name)) logging.info("Attestation of {} succeeded".format(module.name)) module.attested = True
async def deploy(self, module): assert module.node is self if module.deployed: return async with aiofile.AIOFile(await module.binary, "rb") as f: file_data = await f.read() # The packet format is [NAME \0 VID ELF_FILE] payload = module.name.encode('ascii') + b'\0' + \ tools.pack_int16(self.vendor_id) + \ file_data command = CommandMessage(ReactiveCommand.Load, Message(payload), self.ip_address, self.deploy_port) res = await self._send_reactive_command( command, log='Deploying {} on {}'.format(module.name, self.name)) sm_id = tools.unpack_int16(res.message.payload[:2]) if sm_id == 0: raise Error('Deploying {} on {} failed'.format( module.name, self.name)) symtab = res.message.payload[2:] symtab_file = tools.create_tmp(suffix='.ld', dir_name=module.out_dir) # aiofile for write operations is bugged (version 3.3.3) # I get a "bad file descriptor" error after writes. with open(symtab_file, "wb") as f: f.write(symtab[:-1]) # Drop last 0 byte module.deployed = True return sm_id, symtab_file
async def set_key(self, module, conn_id, conn_io, encryption, key): assert module.node is self assert encryption in module.get_supported_encryption() module_id, module_key, io_id = await asyncio.gather( module.id, module.key, conn_io.get_index(module)) nonce = tools.pack_int16(module.nonce) io_id = tools.pack_int16(io_id) conn_id_packed = tools.pack_int16(conn_id) ad = conn_id_packed + io_id + nonce module.nonce += 1 cipher = await encryption.SPONGENT.encrypt(module_key, ad, key) # The payload format is [sm_id, entry_id, 16 bit nonce, index, wrapped(key), tag] # where the tag includes the nonce and the index. payload = tools.pack_int16(module_id) + \ tools.pack_int16(ReactiveEntrypoint.SetKey) + \ ad + \ cipher command = CommandMessage(ReactiveCommand.Call, Message(payload), self.ip_address, self.reactive_port) await self._send_reactive_command( command, log='Setting key of {}:{} on {} to {}'.format( module.name, conn_io.name, self.name, binascii.hexlify(key).decode('ascii')))
async def attest(self, module): assert module.node is self module_id = await module.get_id() challenge = tools.generate_key(16) payload = tools.pack_int16(module_id) + \ tools.pack_int16(ReactiveEntrypoint.Attest) + \ tools.pack_int16(len(challenge)) + \ challenge command = CommandMessage(ReactiveCommand.Call, Message(payload), self.ip_address, self.reactive_port) res = await self._send_reactive_command(command, log='Attesting {}'.format( module.name)) # The result format is [tag] where the tag is the challenge's MAC challenge_response = res.message.payload expected_tag = await Encryption.AES.mac(await module.key, challenge) if challenge_response != expected_tag: logging.debug("Key: {}".format(await module.key)) logging.debug("Challenge: {}".format(challenge)) logging.debug("Resp: {}".format(challenge_response)) logging.debug("Expected: {}".format(expected_tag)) raise Error('Attestation of {} failed'.format(module.name)) logging.info("Attestation of {} succeeded".format(module.name)) module.attested = True
async def request(self, connection, arg=None, output=None): """ ### Description ### Coroutine. Trigger the 'request' event of a direct connection ### Parameters ### self: Node object connection (Connection): connection object arg (bytes): argument to pass as a byte array (can be None) ### Returns ### """ assert connection.to_module.node is self module_id = await connection.to_module.get_id() if arg is None: data = b'' else: data = arg cipher = await connection.encryption.encrypt( connection.key, tools.pack_int16(connection.nonce), data) payload = tools.pack_int16(module_id) + \ tools.pack_int16(connection.id) + \ cipher command = CommandMessage(ReactiveCommand.RemoteRequest, Message(payload), self.ip_address, self.reactive_port) response = await self._send_reactive_command( command, log='Sending handle_request command of connection {}:{} to {} on {}' .format(connection.id, connection.name, connection.to_module.name, self.name)) if not response.ok(): logging.error("Received error code {}".format(str(response.code))) return resp_encrypted = response.message.payload plaintext = await connection.encryption.decrypt( connection.key, tools.pack_int16(connection.nonce + 1), resp_encrypted) if output is None: logging.info("Response: \"{}\"".format( binascii.hexlify(plaintext).decode('ascii'))) else: with open(output, "wb") as f: f.write(plaintext)
async def call(self, module, entry, arg=None, output=None): """ ### Description ### Coroutine. Call the entry point of a module ### Parameters ### self: Node object to_module (XXXModule): target module entry (str): name of the entry point to call arg (bytes): argument to pass as a byte array (can be None) ### Returns ### """ assert module.node is self module_id, entry_id = \ await asyncio.gather(module.get_id(), module.get_entry_id(entry)) payload = tools.pack_int16(module_id) + \ tools.pack_int16(entry_id) + \ (b'' if arg is None else arg) command = CommandMessage(ReactiveCommand.Call, Message(payload), self.ip_address, self.reactive_port) response = await self._send_reactive_command( command, log='Sending call command to {}:{} ({}:{}) on {}'.format( module.name, entry, module_id, entry_id, self.name)) if not response.ok(): logging.error("Received error code {}".format(str(response.code))) return if output is None: logging.info("Response: \"{}\"".format( binascii.hexlify(response.message.payload).decode('ascii'))) else: with open(output, "wb") as f: f.write(response.message.payload)
async def output(self, connection, arg=None): """ ### Description ### Coroutine. Trigger the 'output' event of a direct connection ### Parameters ### self: Node object connection (Connection): connection object arg (bytes): argument to pass as a byte array (can be None) ### Returns ### """ assert connection.to_module.node is self module_id = await connection.to_module.get_id() if arg is None: data = b'' else: data = arg cipher = await connection.encryption.encrypt( connection.key, tools.pack_int16(connection.nonce), data) payload = tools.pack_int16(module_id) + \ tools.pack_int16(connection.id) + \ cipher command = CommandMessage(ReactiveCommand.RemoteOutput, Message(payload), self.ip_address, self.reactive_port) await self._send_reactive_command( command, log='Sending handle_output command of connection {}:{} to {} on {}' .format(connection.id, connection.name, connection.to_module.name, self.name))