async def run_putfile_client(app: NDNApp, **kwargs): """ Async helper function to run the PutfileClient. This function is necessary because it's responsible for calling app.shutdown(). """ client = PutfileClient(app, Name.from_str(kwargs['repo_name'])) await client.insert_file(kwargs['file_path'], Name.from_str(kwargs['name_at_repo'])) app.shutdown()
async def run_getfile_client(app: NDNApp, **kwargs): """ Async helper function to run the GetfileClient. This function is necessary because it's responsible for calling app.shutdown(). """ client = GetfileClient(app, kwargs['repo_name']) await client.fetch_file(kwargs['name_at_repo']) app.shutdown()
async def run_delete_client(app: NDNApp, **kwargs): """ Async helper function to run the DeleteClient. This function is necessary because it's responsible for calling app.shutdown(). """ client = DeleteClient(app, kwargs['client_prefix'], kwargs['repo_name']) await client.delete_file(kwargs['name_at_repo'], kwargs['start_block_id'], kwargs['end_block_id']) app.shutdown()
async def run_putfile_client(app: NDNApp, **kwargs): """ Async helper function to run the PutfileClient. This function is necessary because it's responsible for calling app.shutdown(). """ client = PutfileClient(app, kwargs['client_prefix'], kwargs['repo_name']) await client.insert_file(kwargs['file_path'], kwargs['name_at_repo'], kwargs['segment_size'], kwargs['freshness_period'], kwargs['cpu_count']) app.shutdown()
async def run_check(app: NDNApp, **kwargs): """ Async helper function to run the CommandChecker. This function is necessary because it's responsible for calling app.shutdown(). """ client = CommandChecker(app) response = await client.check_insert(kwargs['repo_name'], kwargs['process_id']) if response: status_code = response.status_code print('Status Code: {}'.format(status_code)) app.shutdown()
async def run_publisher(app: NDNApp, publisher_prefix: NonStrictName): pb = PubSub(app, publisher_prefix) await pb.wait_for_ready() topic = Name.from_str('/topic_foo') msg = f'pubsub message generated at {str(datetime.datetime.now())}'.encode( ) pb.publish(topic, msg) # wait for msg to be fetched by subsciber await aio.sleep(10) app.shutdown()
async def main(app: NDNApp): """ Async helper function to run the concurrent fetcher. This function is necessary because it's responsible for calling app.shutdown(). :param app: NDNApp """ semaphore = aio.Semaphore(20) async for data_bytes in concurrent_fetcher(app, Name.from_str('/test1.pdf'), 0, 161, semaphore): (data_name, meta_info, content, sig) = ndn_format_0_3.parse_data(data_bytes, with_tl=False) print(Name.to_str(data_name)) app.shutdown()
async def run_putfile_client(app: NDNApp, **kwargs): """ Async helper function to run the PutfileClient. This function is necessary because it's responsible for calling app.shutdown(). """ print(kwargs) client = PutfileClient(app=app, prefix=kwargs['client_prefix'], repo_name=kwargs['repo_name']) await client.insert_file(file_path=kwargs['file_path'], name_at_repo=kwargs['name_at_repo'], segment_size=kwargs['segment_size'], freshness_period=kwargs['freshness_period'], cpu_count=kwargs['cpu_count'], forwarding_hint=kwargs['forwarding_hint'], register_prefix=kwargs['register_prefix']) app.shutdown()
async def send(app: NDNApp, name: FormalName): logging.info('Interest Sent: {}'.format(Name.to_str(name))) try: data_name, meta_info, content = await app.express_interest( name, must_be_fresh=True, can_be_prefix=False, nonce=gen_nonce(), lifetime=1000) # Print out Data Name, MetaInfo and its conetnt. logging.info('Data Received: {}'.format(Name.to_str(data_name))) except InterestNack as e: # A NACK is received logging.warning(f'Interest Nacked with reason={e.reason}\n') except InterestTimeout: # Interest times out logging.warning(f'Interest Timeout\n') finally: app.shutdown()
async def main(app: NDNApp) -> int: try: source = Component.from_number(6, Component.TYPE_SEQUENCE_NUM) print(source) print(Component.to_number(source)) """ name = Name.from_str("/b")*2928 interest = make_interest(name, InterestParam(can_be_prefix=True, must_be_fresh=True, lifetime=6000)) print(f'size: {len(interest)}') _, _, _, pkt = await app.express_interest(interest) data_name, meta_info, content, sig_ptrs = parse_data(pkt) content = content if content else bytes("NONE") print(f'CONTENT: {bytes(content).decode()}') print(f'CONTENT-TYPE: {meta_info.content_type}') """ except (InterestNack, InterestTimeout, InterestCanceled, ValidationFailure) as e: print(f'Nacked with reason={e.reason}') finally: app.shutdown()
async def run_delete_client(app: NDNApp, **kwargs): """ Async helper function to run the DeleteClient. This function is necessary because it's responsible for calling app.shutdown(). """ client = DeleteClient(app=app, prefix=kwargs['client_prefix'], repo_name=kwargs['repo_name']) # Set pubsub to register ``check_prefix`` directly, so all prefixes under ``check_prefix`` will # be handled with interest filters. This reduces the number of registered prefixes at NFD, when # inserting multiple files with one client check_prefix = kwargs['client_prefix'] client.pb.set_base_prefix(check_prefix) await client.delete_file(prefix=kwargs['name_at_repo'], start_block_id=kwargs['start_block_id'], end_block_id=kwargs['end_block_id'], register_prefix=kwargs['register_prefix'], check_prefix=check_prefix) app.shutdown()
async def run_putfile_client(app: NDNApp, **kwargs): """ Async helper function to run the PutfileClient. This function is necessary because it's responsible for calling app.shutdown(). """ client = PutfileClient(app=app, prefix=kwargs['client_prefix'], repo_name=kwargs['repo_name']) # Set pubsub to register ``check_prefix`` directly, so all prefixes under ``check_prefix`` will # be handled with interest filters. This reduces the number of registered prefixes at NFD, when # inserting multiple files with one client check_prefix = kwargs['client_prefix'] await client.insert_file(file_path=kwargs['file_path'], name_at_repo=kwargs['name_at_repo'], segment_size=kwargs['segment_size'], freshness_period=kwargs['freshness_period'], cpu_count=kwargs['cpu_count'], forwarding_hint=kwargs['forwarding_hint'], register_prefix=kwargs['register_prefix'], check_prefix=check_prefix) app.shutdown()
async def after_start(app: NDNApp, repo_prefix: str, repo_name: str, git_repo: GitRepo, local_repo_path: str): options = {'cloning': False} handlers = {} running = True empty_cnt = 0 refs = {} class CommandHandler: def __init__(self, name=None): self.name = name def __call__(self, func): name = func.__name__.replace('_', '-') if not self.name else self.name handlers[name] = func return func @CommandHandler() async def capabilities(_args): print("push") print("fetch") print("option") print("") @CommandHandler() async def option(args): opt_name, opt_val = args if opt_name == "cloning": options['cloning'] = (opt_val == 'true') print("ok") else: print("unsupported") @CommandHandler(name='list') async def list_command(_args): nonlocal running, refs try: _, _, data = await app.express_interest(repo_prefix + '/ref-list', must_be_fresh=True, can_be_prefix=True) except (InterestNack, InterestTimeout, InterestTimeout, ValidationFailure) as e: print_out(f"error: Cannot connect to {repo_prefix} for {type(e)}") running = False print("") return reflist = bytes(data).decode() print(reflist) if reflist.strip() == "": refs = {} else: refs = { item.split(" ")[1]: item.split(" ")[0] for item in reflist.strip().split("\n") } @CommandHandler() async def fetch(args): nonlocal fetcher, running, cmd while True: hash_name, ref_name = args new_head = bytes.fromhex(hash_name) # Fetch files try: await fetcher.fetch('commit', new_head) except (ValueError, InterestCanceled, InterestTimeout, InterestNack, ValidationFailure) as e: print_out( f"error: Failed to fetch commit {hash_name} for {type(e)}") running = False return # Set refs file git_repo.set_head(ref_name, new_head) # Batched commands cmd = sys.stdin.readline().rstrip("\n\r") if not cmd.startswith("fetch"): break args = cmd.split()[1:] print("") @CommandHandler() async def push(args): nonlocal running, cmd while True: # Parse push request try: ref_name, commit, force = parse_push(args[0], local_repo_path) except FileNotFoundError: print_out(f"error: Specified local ref not found") running = False return # Push Interest pr = packet.PushRequest() pr.force = force pr.ret_info = packet.RefInfo() pr.ret_info.ref_name = ref_name.encode() pr.ret_info.ref_head = bytes.fromhex(commit) pr_wire = pr.encode() try: _, _, data = await app.express_interest(repo_prefix + '/push', app_param=pr_wire, must_be_fresh=True, lifetime=600000) if data == b'SUCCEEDED': print_out(f"OK push succeeded {ref_name}->{commit}") print(f"ok {ref_name}") elif data == b'FAILED': print_out(f"ERROR push failed {ref_name}->{commit}") print(f"error {ref_name} FAILED") elif data == b'PENDING': print_out( f"PROCESSING push not finished {ref_name}->{commit}") print(f"error {ref_name} PENDING") else: print_out( f"error: Failed to send push request {ref_name}->{commit}, unknown response" ) running = False return except (InterestCanceled, InterestTimeout, InterestNack, ValidationFailure) as e: print_out( f"ERROR cannot send push interest {ref_name}->{commit} {type(e)} {e}" ) print(f"error {ref_name} DISCONNECTED") # Batched commands cmd = sys.stdin.readline().rstrip("\n\r") if not cmd.startswith("push"): break args = cmd.split()[1:] print("") # after_start try: fetcher = ObjectFetcher(app, git_repo, Name.from_str(repo_prefix + '/objects')) while empty_cnt < 2 and running: cmd = sys.stdin.readline().rstrip("\n\r") if cmd == '': empty_cnt += 1 else: cmd = cmd.split() cmd_name = cmd[0] cmd_args = cmd[1:] if cmd_name in handlers: await handlers[cmd_name](cmd_args) sys.stdout.flush() except BrokenPipeError: pass finally: app.shutdown()
class Controller: """ NDN IoT Controller. :ivar app: the python-ndn app :ivar system_prefix: a string representing the home namespace :ivar system_anchor: a TLV format NDN certificate :ivar db: the database handler :ivar device_list: the list of device :ivar access_list: the list of access rights :ivar shared_secret_list: the list of already-shared secrets """ def __init__(self, emit_func): self.newly_pub_command = None self.newly_pub_payload = None self.wait_fetch_cmd_event = None self.emit = emit_func self.running = True self.listen_to_boot_request = False self.listen_to_cert_request = False self.boot_state = None self.boot_event = None self.app = NDNApp() self.system_prefix = None self.system_anchor = None self.db = None self.device_list = DeviceList() self.service_list = ServiceList() self.access_list = AccessList() self.shared_secret_list = SharedSecrets() def save_db(self): """ Save the state into the database. """ logging.debug('Save state to DB') if self.db: wb = self.db.write_batch() logging.debug(self.shared_secret_list.encode()) wb.put(b'device_list', self.device_list.encode()) wb.put(b'service_list', self.service_list.encode()) wb.put(b'access_list', self.access_list.encode()) wb.put(b'shared_secret_list', self.shared_secret_list.encode()) wb.write() self.db.close() def system_init(self): """ Init the system in terms of: Step 1: Create/load system prefix and system anchor from the storage if any Step 2: Create/load device list, service list, access rights, and shared secrets from the storage """ logging.info("Server starts its initialization") # create or get existing state # Step One: Meta Info # 1. get system prefix from storage (from Level DB) import os db_dir = os.path.expanduser('~/.ndn-iot-controller/') if not os.path.exists(db_dir): os.makedirs(db_dir) self.db = plyvel.DB(db_dir, create_if_missing=True) ret = self.db.get(b'system_prefix') if ret: logging.info('Found system prefix from db') self.system_prefix = ret.decode() else: self.system_prefix = default_prefix self.db.put(b'system_prefix', default_prefix.encode()) # 2. get system root anchor certificate and private key (from keychain) anchor_identity = self.app.keychain.touch_identity(self.system_prefix) anchor_key = anchor_identity.default_key() self.system_anchor = anchor_key.default_cert().data logging.info("Server finishes the step 1 initialization") # Step Two: App Layer Support (from Level DB) # 1. DEVICES: get all the certificates for devices from storage ret = self.db.get(b'device_list') if ret: logging.info('Found device list from db') self.device_list = DeviceList.parse(ret) # 2. SERVICES: get service list and corresponding providers ret = self.db.get(b'service_list') if ret: logging.info('Found service list from db') self.service_list = ServiceList.parse(ret) # 3. ACCESS CONTROL: get all the encryption/decryption key pairs ret = self.db.get(b'access_list') if ret: logging.info('Found access list from db') self.access_list = AccessList.parse(ret) # 4. SHARED SECRETS: get all shared secrets ret = self.db.get(b'shared_secret_list') if ret: logging.info('Found shared secret from db') self.shared_secret_list = SharedSecrets.parse(ret) logging.info("Server finishes the step 2 initialization") async def iot_connectivity_init(self): """ Init the system in terms of: Step 3: Configure network interface, forwarding strategy, and route """ # Step Three: Configure Face and Route # 1. Find/create NFD's UDP Multicast Face, BLE Multicast Face, etc. face_id = await query_face_id(self.app, default_udp_multi_uri) if not face_id: logging.fatal("Cannot find existing udp multicast face") return logging.info("Successfully found UDP multicast face: %d", face_id) # 2. Set up NFD's route from IoT system prefix to multicast faces ret = await add_route(self.app, self.system_prefix, face_id) if ret is True: logging.info("Successfully add route.") else: logging.fatal("Cannot set up the route for IoT prefix") # 3. Set up NFD's multicast strategy for IoT system namespace ret = await set_strategy(self.app, self.system_prefix, "/localhost/nfd/strategy/multicast") if ret is True: logging.info("Successfully add multicast strategy.") logging.info("Server finishes the step 3 initialization") else: logging.fatal("Cannot set up the strategy for IoT prefix") @self.app.route('/ndn/sign-on', validator=self.verify_device_sign_on_ecdsa_signature) def on_sign_on_interest(name: FormalName, param: InterestParam, app_param: Optional[BinaryStr]): """ OnInterest callback when there is a security bootstrapping request :param name: Interest packet name :param param: Interest parameters :app_param: Interest application paramters TODO:Verifying the signature """ if not self.listen_to_boot_request: return self.process_sign_on_request(name, app_param) await asyncio.sleep(0.1) @self.app.route(self.system_prefix + '/cert', validator=self.verify_device_sign_on_ecdsa_signature) def on_cert_request_interest(name: FormalName, param: InterestParam, app_param: Optional[BinaryStr]): """ OnInterest callback when there is a certificate request during bootstrapping :param name: Interest packet name :param param: Interest parameters :app_param: Interest application paramters TODO:Verifying the signature """ if not self.listen_to_cert_request: return self.process_cert_request(name, app_param) await asyncio.sleep(0.1) @self.app.route([ self.system_prefix, bytearray(b'\x08\x01\x01'), bytearray(b'\x08\x01\x00') ], validator=self.verify_device_ecdsa_signature) def on_sd_adv_interest(name: FormalName, param: InterestParam, app_param: Optional[BinaryStr]): """ OnInterest callback when there is an service advertisement :param name: Interest packet name :param param: Interest parameters :app_param: Interest application paramters Packet format: prefix = /<home-prefix>/<SD=1>/<ADV=0>/device-id App Parameter format: TODO:Verifying the signature """ locator = name[3:-1] logging.debug("Adv Interest sender locator: %s", Name.to_str(locator)) fresh_period = struct.unpack("!I", app_param[:4])[0] logging.debug("Adv Interest freshness: %s", str(fresh_period)) service_ids = [sid for sid in app_param[4:]] logging.debug('service ids %s', str(service_ids)) cur_time = self.get_time_now_ms() for sid in service_ids: # Name format: /<home-prefix>/<service>/<locator> sname = [self.system_prefix, b'\x08\x01' + bytes([sid]) ] + locator sname = Name.to_str(sname) logging.debug('Service Name: %s', sname) already_added = False for item in self.service_list.services: if Name.to_str(item.service_name) == sname: already_added = True item.exp_time = cur_time + fresh_period if not already_added: service = ServiceItem() service.service_name = sname service.exp_time = cur_time + fresh_period service.service_id = sid logging.debug('Add new service into the service list') self.service_list.services.append(service) already_added = False for service_meta in self.service_list.service_meta_items: if service_meta.service_id == sid: already_added = True if not already_added: service_meta = ServiceMetaItem() service_meta.service_id = sid aes_key = urandom(16) service_meta.encryption_key = aes_key logging.debug('Add new service meta into the service list') logging.debug('AES key: ') self.service_list.service_meta_items.append(service_meta) await asyncio.sleep(0.1) @self.app.route([ self.system_prefix, bytearray(b'\x08\x01\x02'), bytearray(b'\x08\x01\x00') ], validator=self.verify_device_ecdsa_signature) def on_sd_ctl_interest(name: FormalName, param: InterestParam, app_param: Optional[BinaryStr]): """ OnInterest callback when device want to query the existing services in the system :param name: Interest packet name :param param: Interest parameters :app_param: Interest application paramters TODO:Verifying the signature """ logging.info("Service query from device") if app_param is None: logging.error("Malformed Interest") return interested_ids = {sid for sid in app_param} result = b'' cur_time = self.get_time_now_ms() for service in self.service_list.services: if service.service_id not in interested_ids: continue if service.exp_time > cur_time: result += Name.encode(service.service_name) result += struct.pack("i", service.exp_time - cur_time) if len(result) > 0: self.app.put_data(name, result, freshness_period=3000, identity=self.system_prefix) logging.debug("Replied service data back to the device") else: logging.debug( "Don't have services needed by the device, won't reply") await asyncio.sleep(0.1) @self.app.route([ self.system_prefix, bytearray(b'\x08\x01\x03'), bytearray(b'\x08\x01\x00') ], validator=self.verify_device_ecdsa_signature) def on_access_control_ekey_request(name: FormalName, param: InterestParam, app_param: Optional[BinaryStr]): target_service = name[-2] logging.debug(bytes(target_service)) target_service = bytes(target_service)[-1] logging.debug('target service id: %s', str(target_service)) for service_meta in self.service_list.service_meta_items: if service_meta.service_id == target_service: # TODO encrypt the content when signature info can be accessed in onInterest callback device = self.device_list.devices[0] # AES encryption iv = urandom(16) cipher = AES.new(bytes(device.aes_key), AES.MODE_CBC, iv) logging.debug( 'Use Device AES KEY to encrypt Service AES key') logging.debug('IV:') logging.debug(iv) logging.debug('AES KEY:') logging.debug(bytes(device.aes_key)) logging.debug('Service AES Key: ') logging.debug(bytes(service_meta.encryption_key)) ct_bytes = cipher.encrypt( bytes(service_meta.encryption_key)) content_tlv = CipherBlock() content_tlv.iv = iv content_tlv.cipher = ct_bytes self.app.put_data(name, content_tlv.encode(), freshness_period=3000, identity=self.system_prefix) await asyncio.sleep(0.1) @self.app.route([ self.system_prefix, bytearray(b'\x08\x01\x03'), bytearray(b'\x08\x01\x01') ], validator=self.verify_device_ecdsa_signature) def on_access_control_dkey_request(name: FormalName, param: InterestParam, app_param: Optional[BinaryStr]): target_service = name[-2] logging.debug(bytes(target_service)) target_service = bytes(target_service)[-1] logging.debug('target service id: %s', str(target_service)) for service_meta in self.service_list.service_meta_items: if service_meta.service_id == target_service: # TODO encrypt the content when signature info can be accessed in onInterest callback device = self.device_list.devices[0] # AES encryption iv = urandom(16) cipher = AES.new(bytes(device.aes_key), AES.MODE_CBC, iv) ct_bytes = cipher.encrypt( bytes(service_meta.encryption_key)) content_tlv = CipherBlock() content_tlv.iv = iv content_tlv.cipher = ct_bytes self.app.put_data(name, content_tlv.encode(), freshness_period=3000, identity=self.system_prefix) def process_sign_on_request(self, name, app_param): """ Process device's sign on request. :param name: Interest packet name :param app_param: Interest application parameters """ logging.info("[SIGN ON]: interest received") if not app_param: logging.error("[SIGN ON]: interest has no parameter") return m_measure_tp1 = time.time() request = SignOnRequest.parse(app_param) m_measure_tp2 = time.time() logging.debug( F'BOOTSTRAPPING-INT1-PKT-DECODING: {m_measure_tp2 - m_measure_tp1}' ) if not request.identifier or not request.capabilities or not request.ecdh_n1: logging.error( "[SIGN ON]: lack parameters in application parameters") return self.boot_state['DeviceIdentifier'] = bytes(request.identifier) self.boot_state['DeviceCapability'] = bytes(request.capabilities) self.boot_state['N1PublicKey'] = bytes(request.ecdh_n1) logging.info(self.boot_state) shared_secret = None for ss in self.shared_secret_list.shared_secrets: if bytes(ss.device_identifier) == bytes(request.identifier): shared_secret = ss break if not shared_secret: logging.error( "[SIGN ON]: no pre-shared information about the device") return self.boot_state['SharedPublicKey'] = bytes.fromhex( bytes(shared_secret.public_key).decode()) self.boot_state['SharedSymmetricKey'] = bytes.fromhex( bytes(shared_secret.symmetric_key).decode()) # TODO: check whether the device has already bootstrapped # TODO: Verify the signature:pre_installed_ecc_key logging.info(self.system_anchor) m = sha256() m.update(self.system_anchor) self.boot_state['TrustAnchorDigest'] = m.digest() # ECDH m_measure_tp1 = time.time() ecdh = ECDH() m_measure_tp2 = time.time() logging.debug( F'BOOTSTRAPPING-INT1-ECDH-KEYGEN: {m_measure_tp2 - m_measure_tp1}') self.boot_state['N2PrivateKey'] = ecdh.prv_key.to_string() self.boot_state['N2PublicKey'] = ecdh.pub_key.to_string() # random 16 bytes for salt self.boot_state['Salt'] = urandom(16) ecdh.encrypt(self.boot_state['N1PublicKey'], self.boot_state['Salt']) self.boot_state['SharedAESKey'] = ecdh.derived_key response = SignOnResponse() response.salt = self.boot_state['Salt'] response.ecdh_n2 = self.boot_state['N2PublicKey'] cert_bytes = parse_and_check_tl(self.system_anchor, TypeNumber.DATA) response.anchor = cert_bytes m_measure_tp2 = time.time() logging.info(response.encode()) m_measure_tp1 = time.time() signer = HmacSha256Signer('pre-shared', self.boot_state['SharedSymmetricKey']) self.app.put_data(name, response.encode(), freshness_period=3000, signer=signer) m_measure_tp2 = time.time() logging.debug( F'BOOTSTRAPPING-DATA1-HMAC+ENCODING: {m_measure_tp2 - m_measure_tp1}' ) self.listen_to_cert_request = True def process_cert_request(self, name, app_param): logging.info("[CERT REQ]: interest received") logging.info(name) if not app_param: logging.error("[CERT REQ]: interest has no parameter") return request = CertRequest.parse(app_param) if not request.identifier or not request.ecdh_n2 or not request.anchor_digest or not request.ecdh_n1: raise KeyError( "[CERT REQ]: lacking parameters in application parameters") logging.info(bytes(request.identifier)) logging.info(bytes(request.ecdh_n2)) logging.info(bytes(request.anchor_digest)) logging.info(bytes(request.ecdh_n1)) if bytes(request.identifier) != self.boot_state['DeviceIdentifier'] or \ bytes(request.ecdh_n2) != self.boot_state['N2PublicKey'] or \ bytes(request.anchor_digest) != self.boot_state['TrustAnchorDigest'] or \ bytes(request.ecdh_n1) != self.boot_state['N1PublicKey']: logging.error("[CERT REQ]: unauthenticated request") return # anchor signed certificate # create identity and key for the device # TODO Remove hardcoded livingroom and ask user for which room the device belongs to m_measure_tp1 = time.time() device_name = '/' + self.system_prefix + '/livingroom' + '/' + bytes( request.identifier).decode() device_key = self.app.keychain.touch_identity( device_name).default_key() private_key = get_prv_key_from_safe_bag(device_name) default_cert = device_key.default_cert().data m_measure_tp2 = time.time() logging.debug( F'BOOTSTRAPPING-DATA2-ECDSA-KENGEN: {m_measure_tp2 - m_measure_tp1}' ) # re-sign certificate using anchor's key cert = parse_certificate(default_cert) new_cert_name = cert.name[:-2] logging.debug(new_cert_name) new_cert_name.append('home') new_cert_name.append( Name.Component.from_version(SystemRandom().randint( 10000000, 99999999))) logging.debug(new_cert_name) m_measure_tp1 = time.time() cert = self.app.prepare_data(new_cert_name, cert.content, identity=self.system_prefix) m_measure_tp2 = time.time() logging.debug( F'BOOTSTRAPPING-DATA2-ECDSA-RESIGN: {m_measure_tp2 - m_measure_tp1}' ) m_measure_tp1 = time.time() # AES iv = urandom(16) cipher = AES.new(self.boot_state['SharedAESKey'], AES.MODE_CBC, iv) ct_bytes = cipher.encrypt(private_key) m_measure_tp2 = time.time() logging.debug( F'BOOTSTRAPPING-DATA2-AES-ENC: {m_measure_tp2 - m_measure_tp1}') logging.info('raw private key') logging.info(private_key) logging.info('Symmetric Key') logging.info(self.boot_state['SharedAESKey']) # AES IV logging.info("IV:") logging.info(iv) logging.info("Cipher:") logging.info(ct_bytes) logging.info('Cipher length: ' + str(len(ct_bytes))) # encrypted device private key with temporary symmetric key # ct = b64encode(ct_bytes) response = CertResponse() response.cipher = ct_bytes response.iv = iv cert_bytes = parse_and_check_tl(cert, TypeNumber.DATA) response.id_cert = cert_bytes m_measure_tp1 = time.time() signer = HmacSha256Signer('pre-shared', self.boot_state['SharedSymmetricKey']) self.app.put_data(name, response.encode(), freshness_period=3000, signer=signer) m_measure_tp2 = time.time() logging.debug( F'BOOTSTRAPPING-DATA2-ECDSA+ENCODING: {m_measure_tp2 - m_measure_tp1}' ) self.boot_state["DeviceIdentityName"] = device_name self.boot_state['Success'] = True self.boot_event.set() # TODO: Publish certificates to repo, one cert for each service async def bootstrapping(self): self.boot_state = { 'DeviceIdentifier': None, 'DeviceCapability': None, 'N1PublicKey': None, 'N2PrivateKey': None, 'N2PublicKey': None, 'SharedAESKey': None, 'Salt': None, 'TrustAnchorDigest': None, 'SharedPublicKey': None, 'SharedSymmetricKey': None, 'DeviceIdentityName': None, 'Success': False } self.boot_event = asyncio.Event() self.listen_to_boot_request = True try: await asyncio.wait_for(self.boot_event.wait(), timeout=8.0) except asyncio.TimeoutError: self.boot_event.set() if self.boot_state['Success']: new_device = DeviceItem() new_device.device_id = self.boot_state["DeviceIdentifier"] new_device.device_info = self.boot_state["DeviceCapability"] new_device.device_identity_name = self.boot_state[ "DeviceIdentityName"] new_device.aes_key = self.boot_state['SharedAESKey'] self.device_list.devices = [ device for device in self.device_list.devices if bytes(device.device_id) != self.boot_state["DeviceIdentifier"] ] self.device_list.devices.append(new_device) return { 'st_code': 200, 'device_id': self.boot_state['DeviceIdentityName'] } await asyncio.sleep(1) self.boot_event = None self.listen_to_boot_request = False self.listen_to_cert_request = False return {'st_code': 500} async def verify_device_ecdsa_signature(self, name: FormalName, sig: SignaturePtrs) -> bool: sig_info = sig.signature_info covered_part = sig.signature_covered_part sig_value = sig.signature_value_buf if not sig_info or sig_info.signature_type != SignatureType.SHA256_WITH_ECDSA: return False if not covered_part or not sig_value: return False identity = [sig_info.key_locator.name[0] ] + sig_info.key_locator.name[-4:-2] logging.debug('Extract identity id from key id: %s', Name.to_str(identity)) key_bits = None try: key_bits = self.app.keychain.get(identity).default_key().key_bits except (KeyError, AttributeError): logging.error('Cannot find pub key from keychain') return False pk = ECC.import_key(key_bits) verifier = DSS.new(pk, 'fips-186-3', 'der') sha256_hash = SHA256.new() for blk in covered_part: sha256_hash.update(blk) logging.debug(bytes(sig_value)) logging.debug(len(bytes(sig_value))) try: verifier.verify(sha256_hash, bytes(sig_value)) except ValueError: return False return True async def verify_device_sign_on_ecdsa_signature( self, name: FormalName, sig: SignaturePtrs) -> bool: sig_info = sig.signature_info covered_part = sig.signature_covered_part sig_value = sig.signature_value_buf if not sig_info or sig_info.signature_type != SignatureType.SHA256_WITH_ECDSA: return False if not covered_part or not sig_value: return False device_identifier = Name.to_str([sig_info.key_locator.name[0]])[1:] logging.debug('Extract device id from key locator: %s', device_identifier) pk = None for ss in self.shared_secret_list.shared_secrets: if bytes(ss.device_identifier) == device_identifier.encode(): pub_key_bytes = bytes.fromhex(bytes(ss.public_key).decode()) pk = ECC.construct(curve='p256', point_x=int.from_bytes(pub_key_bytes[:32], byteorder='big'), point_y=int.from_bytes(pub_key_bytes[32:], byteorder='big')) # pk = ECC.import_key(bytes(ss.public_key)) break if not pk: logging.error( "[SIGN ON]: no pre-shared public key about the device") return False verifier = DSS.new(pk, 'fips-186-3', 'der') sha256_hash = SHA256.new() for blk in covered_part: sha256_hash.update(blk) logging.debug(bytes(sig_value)) logging.debug(len(bytes(sig_value))) try: verifier.verify(sha256_hash, bytes(sig_value)) except ValueError: return False return True async def use_service(self, service: str, is_cmd: str, name_or_cmd: str, param: str): """ Use NDN-LITE service cmd interest name: /home/SERVICE/CMD/room/device-id/command notification interest name: /home/SERVICE/NOTIFY/CMD/room/device-id/command data fetching name: /home/SERVICE/DATA/room/device-id :param service: full service name, in the format of /home/SERVICE/room/device-id :param is_cmd: whether to send a command to the service :param name_or_cmd: the command id (if is_command is true) or the content-id (if is_cmd is false) :param param: content payload or command parameters :return: a dict object containing the state """ service_name = Name.from_str(service) service_id = service_name[1][2] logging.debug(f'Use service: {str(service_id)}') encryption_key = None for service_meta in self.service_list.service_meta_items: if service_meta.service_id == service_id: encryption_key = service_meta.encryption_key if is_cmd == 'true': logging.debug( F'******Command publish timestamp: {int(round(time.time() * 1000))}' ) service_name.insert(2, 'CMD') service_name = service_name + Name.from_str(name_or_cmd) service_name.append(Component.from_timestamp(timestamp())) notification_name = service_name[:] notification_name.insert(2, 'NOTIFY') need_register = False need_unregister = False if self.newly_pub_command is None: need_register = True elif Name.to_str(self.newly_pub_command[:3]) != Name.to_str( service_name[:3]): need_register = True need_unregister = True if need_unregister: success = await self.app.unregister(self.newly_pub_command[:3]) if not success: logging.debug( 'cannot unregister prefix for command publish') self.newly_pub_command = service_name self.newly_pub_payload = param.encode() logging.debug(f'New pub info: {param}') logging.debug('Encryption Key: ') logging.debug(bytes(encryption_key)) logging.debug('Plaintext: ') logging.debug(self.newly_pub_payload) # AES encryption iv = urandom(16) cipher = AES.new(bytes(encryption_key), AES.MODE_CBC, iv) ct_bytes = cipher.encrypt(pad(self.newly_pub_payload, 16)) content_tlv = CipherBlock() content_tlv.iv = iv content_tlv.cipher = ct_bytes self.newly_pub_payload = content_tlv.encode() logging.info( F'Publish new content Data packet {Name.to_str(self.newly_pub_command)}' ) if need_register: def on_service_fetch(name: FormalName, int_param: InterestParam, app_param: Optional[BinaryStr]): logging.debug( 'received Interest to fetch newly published command') self.app.put_data(self.newly_pub_command, self.newly_pub_payload, freshness_period=3000, identity=self.system_prefix) return success = await self.app.register(service_name[:3], on_service_fetch) if not success: logging.debug('cannot register prefix for command publish') coroutine = self.app.express_interest(notification_name, must_be_fresh=True, can_be_prefix=True, identity=self.system_prefix) ret = { 'name': Name.to_str(service_name), 'response_type': 'CommandPublished' } else: service_name.insert(2, 'DATA') service_name = service_name + Name.from_str(name_or_cmd) time1 = time.time() ret = await self.express_interest(service_name, None, True, True, False) time2 = time.time() logging.debug( F'******Data Fetching Round Trip Time: {time2 - time1}s******') if ret['response_type'] == 'Data': content = ret['content'] content = CipherBlock.parse(content) iv = bytes(content.iv) cipher = AES.new(bytes(encryption_key), AES.MODE_CBC, iv) payload = cipher.decrypt(bytes(content.cipher)) time2 = time.time() logging.debug( F'******Data Fetching Finish Time: {time2 - time1}s******') payload = unpad(payload, 16) ret['content'] = payload.decode() return ret async def manage_policy_add(self, device_name: str, data_name: str, key_name: str, policy_name: str): interest_name = Name.from_str(device_name) interest_name.insert(1, 'POLICY') interest_name = interest_name + Name.from_str(policy_name) param = PolicyAddRequest() param.data_name = data_name.encode() param.key_name = key_name.encode() time1 = time.time() ret = await self.express_interest(interest_name, param.encode(), True, True, True) time2 = time.time() logging.debug( F'******Policy Update Round Trip Time: {time2 - time1}s******') return ret async def manage_policy_remove(self, policy_to_del): pass async def express_interest(self, name, app_param, be_fresh: bool, be_prefix: bool, need_sig: bool): ret = {'name': Name.to_str(name)} try: if need_sig: data_name, meta_info, content = await self.app.express_interest( name, app_param, must_be_fresh=be_fresh, can_be_prefix=be_prefix, identity=self.system_prefix, validator=self.verify_device_ecdsa_signature) else: data_name, meta_info, content = await self.app.express_interest( name, app_param, must_be_fresh=be_fresh, can_be_prefix=be_prefix) except InterestNack as e: ret['response_type'] = 'NetworkNack' ret['reason'] = e.reason except InterestTimeout: ret['response_type'] = 'Timeout' else: ret['response_type'] = 'Data' ret['name'] = Name.to_str(data_name) ret['freshness_period'] = meta_info.freshness_period ret['content_type'] = meta_info.content_type ret['content'] = content return ret async def run(self): logging.info("Restarting app...") while True: try: await self.app.main_loop(self.iot_connectivity_init()) except KeyboardInterrupt: logging.info('Receiving Ctrl+C, shutdown') break except (FileNotFoundError, ConnectionRefusedError): logging.info("NFD disconnected...") finally: self.app.shutdown() await asyncio.sleep(3.0) ################### @staticmethod def get_time_now_ms(): return round(time.time() * 1000.0) def on_register_failed(self, prefix): logging.fatal("Prefix registration failed: %s", prefix) def setup_sd(self): # /<home-prefix>/<SD=1> sd_prefix = Name(self.system_prefix).append( Name.Component.fromNumber(1)) self.face.registerPrefix(sd_prefix, None, self.on_register_failed) # /<home-prefix>/<SD=1>/<SD_ADV=0> self.face.setInterestFilter( Name(sd_prefix).append(Name.Component.fromNumber(0)), self.on_sd_adv_interest) # /<home-prefix>/<SD_CTL=2> sd_ctl_prefix = Name(self.system_prefix).append( Name.Component.fromNumber(2)) self.face.registerPrefix(sd_ctl_prefix, None, self.on_register_failed) # /<home-prefix>/<SD_CTL=2>/<SD_CTL_META=0> self.face.setInterestFilter( Name(sd_ctl_prefix).append(Name.Component.fromNumber(0)), self.on_sd_ctl_interest)
class Server: def __init__(self, emit_func): self.emit = emit_func self.running = True self.event_list = [] self.app = NDNApp() def start_reconnection(self): """ Start reconnection process. NOT thread safe. """ self.app.shutdown() def connection_test(self): return self.app.face.running @staticmethod def decode_to_str(dic): for k, v in dic.items(): if isinstance(v, bytes): dic[k] = v.decode() elif isinstance(v, Enum): dic[k] = v.name elif isinstance(v, Flag): s = str(v) dic[k] = s.split('.')[1] elif k == 'name': dic[k] = Name.to_str(v) elif k == 'routes': dic[k] = [Server.decode_to_str(r) for r in v] return dic async def run(self): logging.info("Restarting app...") while True: try: await self.app.main_loop(self.face_event()) except KeyboardInterrupt: logging.info('Receiving Ctrl+C, shutdown') break except (FileNotFoundError, ConnectionRefusedError): logging.info("NFD disconnected...") finally: self.app.shutdown() await asyncio.sleep(3.0) async def fetch_list(self, module_name: str): name = "/localhost/nfd/" + module_name try: _, _, data = await self.app.express_interest(name, lifetime=1000, can_be_prefix=True, must_be_fresh=True) except (InterestCanceled, InterestTimeout, InterestNack, ValidationFailure, NetworkError): logging.info("No response: " + module_name) raise web.HTTPFound('/') return data async def get_face_list(self): msg = FaceStatusMsg.parse(await self.fetch_list('faces/list')) face_list = [self.decode_to_str(fs.asdict()) for fs in msg.face_status] return face_list async def get_fib_list(self): msg = FibStatus.parse(await self.fetch_list('fib/list')) fib_list = [self.decode_to_str(ent.asdict()) for ent in msg.entries] return fib_list async def get_rib_list(self): msg = RibStatus.parse(await self.fetch_list('rib/list')) rib_list = [self.decode_to_str(ent.asdict()) for ent in msg.entries] return rib_list async def face_event(self): last_seq = -1 name_prefix = Name.from_str('/localhost/nfd/faces/events') while True: if last_seq >= 0: name = name_prefix + [ Component.from_sequence_num(last_seq + 1) ] init = False else: name = name_prefix init = True logging.info("Face event notification stream %s", Name.to_str(name)) try: data_name, _, content = await self.app.express_interest( name, must_be_fresh=init, can_be_prefix=init, lifetime=60000) last_seq = Component.to_number(data_name[-1]) timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") if not content: print('ERROR: Face event is empty') elif content[0] == 0x65: msg = parse_response(content) print('Query failed with response', msg['status_code'], msg['status_text']) else: dic = self.face_event_to_dict(content) dic['seq'] = str(last_seq) dic['time'] = timestamp await self.emit('face event', dic) self.event_list.append(dic) except (InterestCanceled, NetworkError): break except InterestTimeout: last_seq = -1 except InterestNack as e: print(f'Face events nacked with reason={e.reason}') last_seq = -1 except ValidationFailure: print('Face events failed to validate') last_seq = -1 await asyncio.sleep(0.1) @staticmethod def face_event_to_dict(msg): ret = {} event = FaceEventNotification.parse(msg) ret['face_event_kind'] = event.event.face_event_kind.name ret['face_id'] = str(event.event.face_id) ret['local_uri'] = event.event.local_uri ret['remote_uri'] = event.event.uri ret['face_scope'] = event.event.face_scope.name ret['face_persistency'] = event.event.face_persistency.name ret['link_type'] = event.event.link_type.name ret['flags'] = str(event.event.flags)[10:] # Remove 'FaceFlags.' return ret async def issue_command_interest(self, cmd): try: logging.info('Issuing command %s', Name.to_str(cmd)) _, _, data = await self.app.express_interest(cmd, lifetime=1000, can_be_prefix=True, must_be_fresh=True) except (InterestCanceled, InterestTimeout, InterestNack, ValidationFailure, NetworkError): logging.error(f'Command failed') return None return parse_response(data) async def add_face(self, uri): # It's not easy to distinguish udp4://127.0.0.1 and udp4://spurs.cs.ucla.edu # if reduce(lambda a, b: a or b, (x.isalpha() for x in uri)): # uri = socket.gethostbyname(uri) if uri[-1] == "/": uri = uri[:-1] if uri.find("://") < 0: uri = "udp4://" + uri if len(uri.split(":")) < 3: uri = uri + ":6363" interest = make_command('faces', 'create', uri=uri.encode()) return await self.issue_command_interest(interest) async def remove_face(self, face_id: int): interest = make_command('faces', 'destroy', face_id=face_id) return await self.issue_command_interest(interest) async def add_route(self, name: str, face_id: int): interest = make_command('rib', 'register', name=name, face_id=face_id) return await self.issue_command_interest(interest) async def remove_route(self, name: str, face_id: int): interest = make_command('rib', 'unregister', name=name, face_id=face_id) return await self.issue_command_interest(interest) async def set_strategy(self, name: str, strategy: str): interest = make_command('strategy-choice', 'set', name=name, strategy=strategy) return await self.issue_command_interest(interest) async def unset_strategy(self, name: str): interest = make_command('strategy-choice', 'unset', name=name) return await self.issue_command_interest(interest) def list_key_tree(self): """ Return the id-key-cert tree in a JSON like dict object. """ def get_key_type(key): key = bytes(key) try: RSA.import_key(key) return 'RSA' except ValueError: pass try: ECC.import_key(key) return 'ECC' except ValueError: pass return 'Unknown' sig_type_dic = { SignatureType.NOT_SIGNED: 'NotSigned', SignatureType.DIGEST_SHA256: 'DigestSha256', SignatureType.SHA256_WITH_RSA: 'SignatureSha256WithRsa', SignatureType.SHA256_WITH_ECDSA: 'SignatureSha256WithEcdsa', SignatureType.HMAC_WITH_SHA256: 'SignatureHmacWithSha256' } pib = self.app.keychain ret = {} for id_name, id_obj in pib.items(): cur_id = {'default': '*' if id_obj.is_default else ' ', 'keys': {}} for key_name, key_obj in id_obj.items(): cur_key = { 'default': '*' if key_obj.is_default else ' ', 'key_type': get_key_type(key_obj.key_bits), 'certs': {} } for cert_name, cert_obj in key_obj.items(): cert_v2 = parse_certificate(cert_obj.data) cur_cert = { 'default': '*' if cert_obj.is_default else ' ', 'not_before': bytes(cert_v2.signature_info.validity_period.not_before ).decode(), 'not_after': bytes(cert_v2.signature_info.validity_period.not_after ).decode(), 'issuer_id': Component.to_str(cert_v2.name[-2]), 'key_locator': Name.to_str(cert_v2.signature_info.key_locator.name), 'signature_type': sig_type_dic.get(cert_v2.signature_info.signature_type, 'Unknown') } cur_key['certs'][Name.to_str(cert_name)] = cur_cert cur_id['keys'][Name.to_str(key_name)] = cur_key ret[Name.to_str(id_name)] = cur_id return ret def create_identity(self, name): if name in self.app.keychain: self.app.keychain.new_key(name) else: self.app.keychain.new_identity(name) def delete_security_object(self, name, kind): keychain = self.app.keychain if kind == "c": keychain.del_cert(name) elif kind == "k": keychain.del_key(name) else: keychain.del_identity(name) async def query_face_id(self, uri): query_filter = FaceQueryFilter() query_filter.face_query_filter = FaceQueryFilterValue() query_filter.face_query_filter.uri = uri query_filter_msg = query_filter.encode() name = Name.from_str("/localhost/nfd/faces/query") + [ Component.from_bytes(query_filter_msg) ] try: _, _, data = await self.app.express_interest(name, lifetime=1000, can_be_prefix=True, must_be_fresh=True) except (InterestCanceled, InterestTimeout, InterestNack, ValidationFailure, NetworkError): logging.error(f'Query failed') return None msg = FaceStatusMsg.parse(data) if len(msg.face_status) <= 0: return None return msg.face_status[0].face_id async def autoconf(self): """ Automatically connect to ndn testbed. Add route /ndn and /localhop/nfd. """ uri = urllib.request.urlopen( "http://ndn-fch.named-data.net/").read().decode('utf-8') uri = socket.gethostbyname(uri) uri = "udp4://" + uri + ":6363" cmd = make_command('faces', 'create', uri=uri) ret = await self.issue_command_interest(cmd) if not isinstance(ret, dict): return False, "Create face failed" face_id = await self.query_face_id(uri) if face_id is None: return False, "Create face failed" route = '/ndn' cmd = make_command('rib', 'register', name=route, face_id=face_id, origin=66, cost=100) await self.issue_command_interest(cmd) route = '/localhop/nfd' cmd = make_command('rib', 'register', name=route, face_id=face_id, origin=66, cost=100) await self.issue_command_interest(cmd) return True, "Auto-configuration finished"
class Controller: def __init__(self, emit_func): self.emit = emit_func self.running = True self.networking_ready = False self.listen_to_boot_request = False self.listen_to_cert_request = False self.boot_state = None self.app = NDNApp() self.system_prefix = None self.system_anchor = None self.db = None self.device_list = DeviceList() self.service_list = ServiceList() self.access_list = AccessList() self.shared_secret_list = SharedSecrets() def save_db(self): logging.debug('Save state to DB') if self.db: wb = self.db.write_batch() logging.debug(self.shared_secret_list.encode()) wb.put(b'device_list', self.device_list.encode()) wb.put(b'service_list', self.service_list.encode()) wb.put(b'access_list', self.access_list.encode()) wb.put(b'shared_secret_list', self.shared_secret_list.encode()) wb.write() self.db.close() def system_init(self): logging.info("Server starts its initialization") # create or get existing state # Step One: Meta Info # 1. get system prefix from storage (from Level DB) import os db_dir = os.path.expanduser('~/.ndn-iot-controller/') if not os.path.exists(db_dir): os.makedirs(db_dir) self.db = plyvel.DB(db_dir, create_if_missing=True) ret = self.db.get(b'system_prefix') if ret: logging.info('Found system prefix from db') self.system_prefix = ret.decode() else: self.system_prefix = default_prefix self.db.put(b'system_prefix', default_prefix.encode()) # 2. get system root anchor certificate and private key (from keychain) anchor_identity = self.app.keychain.touch_identity(self.system_prefix) anchor_key = anchor_identity.default_key() self.system_anchor = anchor_key.default_cert().data logging.info("Server finishes the step 1 initialization") # Step Two: App Layer Support (from Level DB) # 1. DEVICES: get all the certificates for devices from storage ret = self.db.get(b'device_list') if ret: logging.info('Found device list from db') self.device_list = DeviceList.parse(ret) # 2. SERVICES: get service list and corresponding providers ret = self.db.get(b'service_list') if ret: logging.info('Found service list from db') self.service_list = ServiceList.parse(ret) # 3. ACCESS CONTROL: get all the encryption/decryption key pairs ret = self.db.get(b'access_list') if ret: logging.info('Found access list from db') self.access_list = AccessList.parse(ret) # 4. SHARED SECRETS: get all shared secrets ret = self.db.get(b'shared_secret_list') if ret: logging.info('Found shared secret from db') self.shared_secret_list = SharedSecrets.parse(ret) logging.info("Server finishes the step 2 initialization") async def iot_connectivity_init(self): # Step Three: Configure Face and Route # 1. Find/create NFD's UDP Multicast Face, BLE Multicast Face, etc. face_id = await self.query_face_id(default_udp_multi_uri) if not face_id: logging.fatal("Cannot find existing udp multicast face") return logging.info("Successfully found UDP multicast face: %d", face_id) # 2. Set up NFD's route from IoT system prefix to multicast faces ret = await self.add_route(self.system_prefix, face_id) if ret is True: logging.info("Successfully add route.") else: logging.fatal("Cannot set up the route for IoT prefix") # 3. Set up NFD's multicast strategy for IoT system namespace ret = await self.set_strategy(self.system_prefix, "/localhost/nfd/strategy/multicast") if ret is True: self.networking_ready = True logging.info("Successfully add multicast strategy.") logging.info("Server finishes the step 3 initialization") else: logging.fatal("Cannot set up the strategy for IoT prefix") @self.app.route('/ndn/sign-on') def on_sign_on_interest(name: FormalName, param: InterestParam, app_param: Optional[BinaryStr]): # TODO:Verifying the signature if not self.listen_to_boot_request: return self.process_sign_on_request(name, app_param) await asyncio.sleep(0.1) @self.app.route(self.system_prefix + '/cert') def on_cert_request_interest(name: FormalName, param: InterestParam, app_param: Optional[BinaryStr]): # TODO:Verifying the signature if not self.listen_to_cert_request: return self.process_cert_request(name, app_param) await asyncio.sleep(0.1) @self.app.route([self.system_prefix, bytearray(b'\x08\x01\x01'), bytearray(b'\x08\x01\x00')]) def on_sd_adv_interest(name: FormalName, param: InterestParam, app_param: Optional[BinaryStr]): # prefix = /<home-prefix>/<SD=1>/<ADV=0> locator = name[3:-1] fresh_period = struct.unpack("!I", app_param[:4])[0] service_ids = [sid for sid in app_param[4:]] logging.debug("ON ADV: %s %s %s", locator, fresh_period, service_ids) cur_time = self.get_time_now_ms() for sid in service_ids: # /<home-prefix>/<SD=1>/<service>/<locator> sname = [self.system_prefix, bytearray(b'\x08\x011'), sid, locator] sname = Name.normalize(sname) logging.debug("SNAME: %s", sname) self.real_service_list[sname] = cur_time + fresh_period await asyncio.sleep(0.1) @self.app.route([self.system_prefix, bytearray(b'\x08\x01\x02'), bytearray(b'\x08\x01\x00')]) def on_sd_ctl_interest(name: FormalName, param: InterestParam, app_param: Optional[BinaryStr]): logging.info("SD : on interest") if app_param is None: logging.error("Malformed Interest") return interested_ids = {sid for sid in app_param} result = b'' cur_time = self.get_time_now_ms() for sname, exp_time in self.real_service_list.items(): sid = sname[2][2] if sid in interested_ids and exp_time > cur_time: result += Name.encode(sname) result += struct.pack("i", exp_time - cur_time) self.app.put_data(name, result, freshness_period=3000, identity=self.system_prefix) logging.debug("PutData") logging.debug(name) def process_sign_on_request(self, name, app_param): logging.info("[SIGN ON]: interest received") if not app_param: logging.error("[SIGN ON]: interest has no parameter") return state = {'DeviceIdentifier': None, 'DeviceCapability': None, 'N1PublicKey': None, 'N2PrivateKey':None, 'N2PublicKey':None, 'SharedKey':None, 'Salt':None, 'TrustAnchorDigest':None, 'SharedPublicKey':None, 'SharedSymmetricKey':None, 'DeviceIdentityName':None} request = SignOnRequest.parse(app_param) if not request.identifier or not request.capabilities or not request.ecdh_n1: logging.error("[SIGN ON]: lack parameters in application parameters") return state['DeviceIdentifier'] = bytes(request.identifier) state['DeviceCapability'] = bytes(request.capabilities) state['N1PublicKey'] = bytes(request.ecdh_n1) logging.info(state) shared_secret = None for ss in self.shared_secret_list.shared_secrets: if bytes(ss.device_identifier) == bytes(request.identifier): shared_secret = ss break if not shared_secret: logging.error("[SIGN ON]: no pre-shared information about the device") return state['SharedPublicKey'] = bytes.fromhex(bytes(shared_secret.public_key).decode()) state['SharedSymmetricKey'] = bytes.fromhex(bytes(shared_secret.symmetric_key).decode()) # TODO: check whether the device has already bootstrapped # TODO: Verify the signature:pre_installed_ecc_key logging.info(self.system_anchor) m = sha256() m.update(self.system_anchor) state['TrustAnchorDigest'] = m.digest() # ECDH ecdh = ECDH() state['N2PrivateKey'] = ecdh.prv_key.to_string() state['N2PublicKey'] = ecdh.pub_key.to_string() # random 16 bytes for salt state['Salt'] = urandom(16) ecdh.encrypt(state['N1PublicKey'], state['Salt']) state['SharedKey'] = ecdh.derived_key response = SignOnResponse() response.salt = state['Salt'] response.ecdh_n2 = state['N2PublicKey'] cert_bytes = parse_and_check_tl(self.system_anchor, TypeNumber.DATA) response.anchor = cert_bytes logging.info(response.encode()) signer = HmacSha256Signer('pre-shared', state['SharedSymmetricKey']) self.app.put_data(name, response.encode(), freshness_period=3000, signer=signer) self.boot_state = state self.listen_to_cert_request = True def process_cert_request(self, name, app_param): logging.info("[CERT REQ]: interest received") logging.info(name) if not app_param: logging.error("[CERT REQ]: interest has no parameter") return request = CertRequest.parse(app_param) if not request.identifier or not request.ecdh_n2 or not request.anchor_digest or not request.ecdh_n1: raise KeyError("[CERT REQ]: lacking parameters in application parameters") logging.info(bytes(request.identifier)) logging.info(bytes(request.ecdh_n2)) logging.info(bytes(request.anchor_digest)) logging.info(bytes(request.ecdh_n1)) if bytes(request.identifier) != self.boot_state['DeviceIdentifier'] or \ bytes(request.ecdh_n2) != self.boot_state['N2PublicKey'] or \ bytes(request.anchor_digest) != self.boot_state['TrustAnchorDigest'] or \ bytes(request.ecdh_n1) != self.boot_state['N1PublicKey']: logging.error("[CERT REQ]: unauthenticated request") return # anchor signed certificate # create identity and key for the device device_name = self.system_prefix + '/' + bytes(request.identifier).decode() device_key = self.app.keychain.touch_identity(device_name).default_key() private_key = get_prv_key_from_safe_bag(device_name) default_cert = device_key.default_cert().data # resign certificate using anchor's key cert = parse_certificate(default_cert) new_cert_name = cert.name[:-2] new_cert_name.append('home') new_cert_name.append('001') cert = self.app.prepare_data(new_cert_name, cert.content, identity=self.system_prefix) # AES iv = urandom(16) cipher = AES.new(self.boot_state['SharedKey'], AES.MODE_CBC, iv) ct_bytes = cipher.encrypt(pad(private_key, AES.block_size)) logging.info('Symmetic Key') logging.info(self.boot_state['SharedKey']) # AES IV logging.info("IV:") logging.info(iv) # encrpted device private key with temporary symmetric key ct = b64encode(ct_bytes) logging.info("Cipher:") logging.info(ct) response = CertResponse() response.cipher = ct response.iv = iv cert_bytes = parse_and_check_tl(cert, TypeNumber.DATA) response.id_cert = cert_bytes signer = HmacSha256Signer('pre-shared', self.boot_state['SharedSymmetricKey']) self.app.put_data(name, response.encode(), freshness_period=3000, signer=signer) async def bootstrapping(self): self.listen_to_boot_request = True # # # TODO: wait for bootstrapping process # new_device = self.device_list.device.add() # new_device.device_id = self.boot_state["DeviceIdentifier"] # new_device.device_info = self.boot_state["DeviceCapability"] # new_device.device_cert_name = self.boot_state["DeviceIdentityName"] # return {'st_code':200,'device_id': self.boot_state['DeviceIdentifier'].decode('utf-8')} def get_access_status(self, parameter_list): pass def invoke_service(self, parameter_list): pass async def query_face_id(self, uri): query_filter = FaceQueryFilter() query_filter.face_query_filter = FaceQueryFilterValue() query_filter.face_query_filter.uri = uri.encode('utf-8') query_filter_msg = query_filter.encode() name = Name.from_str("/localhost/nfd/faces/query") + [Component.from_bytes(query_filter_msg)] try: _, _, data = await self.app.express_interest(name, lifetime=1000, can_be_prefix=True, must_be_fresh=True) except (InterestCanceled, InterestTimeout, InterestNack, ValidationFailure, NetworkError): logging.error(f'Query failed') return None ret = FaceStatusMsg.parse(data) logging.info(ret) return ret.face_status[0].face_id async def add_route(self, name: str, face_id: int): interest = make_command('rib', 'register', name=name, face_id=face_id) try: _, _, data = await self.app.express_interest(interest, lifetime=1000, can_be_prefix=True, must_be_fresh=True) except (InterestCanceled, InterestTimeout, InterestNack, ValidationFailure, NetworkError): logging.error(f'Command failed') return False ret = parse_response(data) if ret['status_code'] <= 399: return True return False async def remove_route(self, name: str, face_id: int): interest = make_command('rib', 'unregister', name=name, face_id=face_id) try: _, _, data = await self.app.express_interest(interest, lifetime=1000, can_be_prefix=True, must_be_fresh=True) except (InterestCanceled, InterestTimeout, InterestNack, ValidationFailure, NetworkError): logging.error(f'Command failed') return False ret = parse_response(data) if ret['status_code'] <= 399: return True return False async def set_strategy(self, name: str, strategy: str): interest = make_command('strategy-choice', 'set', name=name, strategy=strategy) try: _, _, data = await self.app.express_interest(interest, lifetime=1000, can_be_prefix=True, must_be_fresh=True) except (InterestCanceled, InterestTimeout, InterestNack, ValidationFailure, NetworkError): logging.error(f'Command failed') return False ret = parse_response(data) if ret['status_code'] <= 399: return True return False async def unset_strategy(self, name: str): interest = make_command('strategy-choice', 'unset', name=name) try: _, _, data = await self.app.express_interest(interest, lifetime=1000, can_be_prefix=True, must_be_fresh=True) except (InterestCanceled, InterestTimeout, InterestNack, ValidationFailure, NetworkError): logging.error(f'Command failed') return False ret = parse_response(data) if ret['status_code'] <= 399: return True return False async def run(self): logging.info("Restarting app...") while True: try: await self.app.main_loop(self.iot_connectivity_init()) except KeyboardInterrupt: logging.info('Receiving Ctrl+C, shutdown') break except (FileNotFoundError, ConnectionRefusedError): logging.info("NFD disconnected...") finally: self.app.shutdown() await asyncio.sleep(3.0) ################### @staticmethod def get_time_now_ms(): return round(time.time() * 1000.0) def on_register_failed(self, prefix): logging.fatal("Prefix registration failed: %s", prefix) def setup_sd(self): # /<home-prefix>/<SD=1> sd_prefix = Name(self.system_prefix).append(Name.Component.fromNumber(1)) self.face.registerPrefix(sd_prefix, None, self.on_register_failed) # /<home-prefix>/<SD=1>/<SD_ADV=0> self.face.setInterestFilter(Name(sd_prefix).append(Name.Component.fromNumber(0)), self.on_sd_adv_interest) # /<home-prefix>/<SD_CTL=2> sd_ctl_prefix = Name(self.system_prefix).append(Name.Component.fromNumber(2)) self.face.registerPrefix(sd_ctl_prefix, None, self.on_register_failed) # /<home-prefix>/<SD_CTL=2>/<SD_CTL_META=0> self.face.setInterestFilter(Name(sd_ctl_prefix).append(Name.Component.fromNumber(0)), self.on_sd_ctl_interest) def get_service_list(self): ret = ServiceList() for sname, exp_time in self.real_service_list.items(): item = ServiceItem() item.service_id = Name(sname)[2].toNumber() item.service_name = sname item.exp_time = exp_time ret.service.append(item) return ret def set_service_list(self, srv_lst): self.real_service_list = {} cur_time = self.get_time_now_ms() for item in srv_lst.service: if item.exp_time > cur_time: self.real_service_list[item.service_name] = item.exp_time service_list = property(get_service_list, set_service_list)