def handle_pair_verify(self): """Handles arbitrary step of the pair verify process. Pair verify is session negotiation. """ if not self.state.paired: raise NotAllowedInStateException length = int(self.headers["Content-Length"]) tlv_objects = tlv.decode(self.rfile.read(length)) sequence = tlv_objects[HAP_TLV_TAGS.SEQUENCE_NUM] if sequence == b'\x01': self._pair_verify_one(tlv_objects) elif sequence == b'\x03': self._pair_verify_two(tlv_objects) else: raise ValueError
def handle_pairings(self): """Handles a client request to update or remove a pairing.""" if not self.is_encrypted: self._send_authentication_error_tlv_response(HAP_TLV_STATES.M2) return tlv_objects = tlv.decode(self.request_body) request_type = tlv_objects[HAP_TLV_TAGS.REQUEST_TYPE][0] if request_type == 3: self._handle_add_pairing(tlv_objects) elif request_type == 4: self._handle_remove_pairing(tlv_objects) elif request_type == 5: self._handle_list_pairings() else: raise ValueError( "Unknown pairing request type of %s during pair verify" % (request_type))
def handle_pair_verify(self): """Handles arbitrary step of the pair verify process. Pair verify is session negotiation. """ if not self.state.paired: raise NotAllowedInStateException tlv_objects = tlv.decode(self.request_body) sequence = tlv_objects[HAP_TLV_TAGS.SEQUENCE_NUM] if sequence == b"\x01": self._pair_verify_one(tlv_objects) elif sequence == b"\x03": self._pair_verify_two(tlv_objects) else: raise ValueError( "Unknown pairing sequence of %s during pair verify" % (sequence))
def handle_pair_verify(self): """Handles arbitrary step of the pair verify process. Pair verify is session negotiation. """ if not self.state.paired: self._send_authentication_error_tlv_response(HAP_TLV_STATES.M2) return tlv_objects = tlv.decode(self.request_body) sequence = tlv_objects[HAP_TLV_TAGS.SEQUENCE_NUM] if sequence == HAP_TLV_STATES.M1: self._pair_verify_one(tlv_objects) elif sequence == HAP_TLV_STATES.M3: self._pair_verify_two(tlv_objects) else: raise ValueError( "Unknown pairing sequence of %s during pair verify" % (sequence))
def _handle_pair_verify(self, indata, enc_context=None): """Handles arbitrary step of the pair verify process. Pair verify is session negotiation. """ if not self._state.paired: raise NotAllowedInStateException tlv_objects = tlv.decode(indata) sequence = tlv_objects[HAP_TLV_TAGS.SEQUENCE_NUM] if sequence == b'\x01': return self._pair_verify_one(tlv_objects) elif sequence == b'\x03': if enc_context is None: raise ValueError( 'Encyrption context must be given when executing ' 'the second pair verify step') return self._pair_verify_two(tlv_objects, enc_context) else: raise ValueError('Unknown pair verify sequence number')
def handle_pairing(self): """Handles arbitrary step of the pairing process.""" if self.state.paired: self._send_tlv_pairing_response( tlv.encode( HAP_TLV_TAGS.SEQUENCE_NUM, HAP_TLV_STATES.M2, HAP_TLV_TAGS.ERROR_CODE, HAP_TLV_ERRORS.UNAVAILABLE, )) return tlv_objects = tlv.decode(self.request_body) sequence = tlv_objects[HAP_TLV_TAGS.SEQUENCE_NUM] if sequence == HAP_TLV_STATES.M1: self._pairing_one() elif sequence == HAP_TLV_STATES.M3: self._pairing_two(tlv_objects) elif sequence == HAP_TLV_STATES.M5: self._pairing_three(tlv_objects)
def _stop_stream(self, objs): """Stop the stream for the specified session. :param objs: TLV-decoded SelectedRTPStreamConfiguration value. :param objs: ``dict`` """ session_objs = tlv.decode( objs[SELECTED_STREAM_CONFIGURATION_TYPES['SESSION']]) session_id = UUID(bytes=session_objs[SETUP_TYPES['SESSION_ID']]) session_info = self.sessions.get(session_id) if not session_info: logging.error( 'Requested to stop stream for session %s, but no ' 'such session was found', session_id) return self.stop_stream(session_info) del self.sessions[session_id] self.streaming_status = STREAMING_STATUS['AVAILABLE']
def test_pair_verify_one_not_paired(driver): """Verify an unencrypted pair verify one.""" driver.add_accessory(Accessory(driver, "TestAcc")) handler = hap_handler.HAPServerHandler(driver, "peername") handler.is_encrypted = False response = hap_handler.HAPResponse() handler.response = response handler.request_body = tlv.encode( hap_handler.HAP_TLV_TAGS.SEQUENCE_NUM, hap_handler.HAP_TLV_STATES.M1, hap_handler.HAP_TLV_TAGS.PUBLIC_KEY, PUBLIC_KEY, ) handler.handle_pair_verify() tlv_objects = tlv.decode(response.body) assert tlv_objects == { hap_handler.HAP_TLV_TAGS.SEQUENCE_NUM: hap_handler.HAP_TLV_STATES.M2, hap_handler.HAP_TLV_TAGS.ERROR_CODE: hap_handler.HAP_TLV_ERRORS.AUTHENTICATION, }
def _pair_verify_two(self, tlv_objects, enc_context): """Verify the client proof and upgrade to encrypted transport. @param tlv_objects: The TLV data received from the client. @type tlv_object: dict """ logging.debug('Pair verify [2/2]') encrypted_data = tlv_objects[HAP_TLV_TAGS.ENCRYPTED_DATA] cipher = CHACHA20_POLY1305(enc_context['pre_session_key'], 'python') decrypted_data = cipher.open(PVERIFY_2_NONCE, bytearray(encrypted_data), b'') assert decrypted_data is not None # TODO: dec_tlv_objects = tlv.decode(bytes(decrypted_data)) client_username = dec_tlv_objects[HAP_TLV_TAGS.USERNAME] material = enc_context['client_public'] \ + client_username \ + enc_context['public_key'].serialize() client_uuid = uuid.UUID(str(client_username, 'ascii')) perm_client_public = self._state.paired_clients.get(client_uuid) if perm_client_public is None: logging.debug( 'Client %s attempted pair verify without being paired first.', client_uuid) return tlv.encode(HAP_TLV_TAGS.ERROR_CODE, HAP_OPERATION_CODE.INVALID_REQUEST) verifying_key = ed25519.VerifyingKey(perm_client_public) try: verifying_key.verify(dec_tlv_objects[HAP_TLV_TAGS.PROOF], material) except ed25519.BadSignatureError: logging.error('Bad signature, abort.') return tlv.encode(HAP_TLV_TAGS.ERROR_CODE, HAP_OPERATION_CODE.INVALID_REQUEST) return tlv.encode(HAP_TLV_TAGS.SEQUENCE_NUM, b'\x04'), None
def _pairing_three(self, tlv_objects): """Expand the SRP session key to obtain a new key. Use it to verify and decrypt the recieved data. Continue to step four. @param tlv_objects: The TLV data received from the client. @type tlv_object: dict """ logger.debug("Pairing [3/5]") encrypted_data = tlv_objects[HAP_TLV_TAGS.ENCRYPTED_DATA] session_key = self.accessory_handler.srp_verifier.get_session_key() hkdf_enc_key = hap_hkdf(long_to_bytes(session_key), self.PAIRING_3_SALT, self.PAIRING_3_INFO) cipher = ChaCha20Poly1305(hkdf_enc_key) decrypted_data = cipher.decrypt(self.PAIRING_3_NONCE, bytes(encrypted_data), b"") assert decrypted_data is not None dec_tlv_objects = tlv.decode(bytes(decrypted_data)) client_username = dec_tlv_objects[HAP_TLV_TAGS.USERNAME] client_ltpk = dec_tlv_objects[HAP_TLV_TAGS.PUBLIC_KEY] client_proof = dec_tlv_objects[HAP_TLV_TAGS.PROOF] self._pairing_four(client_username, client_ltpk, client_proof, hkdf_enc_key)
def _pair_verify_two(self, tlv_objects): """Verify the client proof and upgrade to encrypted transport. @param tlv_objects: The TLV data received from the client. @type tlv_object: dict """ logger.debug("Pair verify [2/2]") encrypted_data = tlv_objects[HAP_TLV_TAGS.ENCRYPTED_DATA] cipher = CHACHA20_POLY1305(self.enc_context["pre_session_key"], "python") decrypted_data = cipher.open(self.PVERIFY_2_NONCE, bytearray(encrypted_data), b"") assert decrypted_data is not None #TODO dec_tlv_objects = tlv.decode(bytes(decrypted_data)) client_username = dec_tlv_objects[HAP_TLV_TAGS.USERNAME] material = self.enc_context["client_public"] \ + client_username \ + self.enc_context["public_key"].serialize() #TODO: verify we have a client_public client_uuid = uuid.UUID(str(client_username, "ascii")) perm_client_public = self.accessory.paired_clients[client_uuid] verifying_key = ed25519.VerifyingKey(perm_client_public) try: verifying_key.verify(dec_tlv_objects[HAP_TLV_TAGS.PROOF], material) except ed25519.BadSignatureError: logger.error("Bad signature, abort.") raise #TODO: logger.debug("Pair verify with client '%s' completed. Switching to " "encrypted transport.", self.client_address) data = tlv.encode(HAP_TLV_TAGS.SEQUENCE_NUM, b'\x04') self.send_response(200) self.send_header("Content-Type", self.PAIRING_RESPONSE_TYPE) self.end_response(data) self._upgrade_to_encrypted() del self.enc_context
async def _start_stream(self, objs, reconfigure): # pylint: disable=unused-argument """Start or reconfigure video streaming for the given session. Schedules ``self.start_stream`` or ``self.reconfigure``. No support for reconfigure currently. :param objs: TLV-decoded SelectedRTPStreamConfiguration :type objs: ``dict`` :param reconfigure: Whether the stream should be reconfigured instead of started. :type reconfigure: bool """ video_tlv = objs.get(SELECTED_STREAM_CONFIGURATION_TYPES['VIDEO']) audio_tlv = objs.get(SELECTED_STREAM_CONFIGURATION_TYPES['AUDIO']) opts = {} if video_tlv: video_objs = tlv.decode(video_tlv) video_codec_params = video_objs.get(VIDEO_TYPES['CODEC_PARAM']) if video_codec_params: video_codec_param_objs = tlv.decode(video_codec_params) opts['v_profile_id'] = \ video_codec_param_objs[VIDEO_CODEC_PARAM_TYPES['PROFILE_ID']] opts['v_level'] = \ video_codec_param_objs[VIDEO_CODEC_PARAM_TYPES['LEVEL']] video_attrs = video_objs.get(VIDEO_TYPES['ATTRIBUTES']) if video_attrs: video_attr_objs = tlv.decode(video_attrs) opts['width'] = struct.unpack('<H', video_attr_objs[VIDEO_ATTRIBUTES_TYPES['IMAGE_WIDTH']])[0] opts['height'] = struct.unpack('<H', video_attr_objs[VIDEO_ATTRIBUTES_TYPES['IMAGE_HEIGHT']])[0] opts['fps'] = struct.unpack('<B', video_attr_objs[VIDEO_ATTRIBUTES_TYPES['FRAME_RATE']])[0] video_rtp_param = video_objs.get(VIDEO_TYPES['RTP_PARAM']) if video_rtp_param: video_rtp_param_objs = tlv.decode(video_rtp_param) if RTP_PARAM_TYPES['SYNCHRONIZATION_SOURCE'] in video_rtp_param_objs: opts['v_ssrc'] = struct.unpack('<I', video_rtp_param_objs.get( RTP_PARAM_TYPES['SYNCHRONIZATION_SOURCE']))[0] if RTP_PARAM_TYPES['PAYLOAD_TYPE'] in video_rtp_param_objs: opts['v_payload_type'] = \ video_rtp_param_objs.get(RTP_PARAM_TYPES['PAYLOAD_TYPE']) if RTP_PARAM_TYPES['MAX_BIT_RATE'] in video_rtp_param_objs: opts['v_max_bitrate'] = struct.unpack('<H', video_rtp_param_objs.get(RTP_PARAM_TYPES['MAX_BIT_RATE']))[0] if RTP_PARAM_TYPES['RTCP_SEND_INTERVAL'] in video_rtp_param_objs: opts['v_rtcp_interval'] = struct.unpack('<f', video_rtp_param_objs.get(RTP_PARAM_TYPES['RTCP_SEND_INTERVAL']))[0] if RTP_PARAM_TYPES['MAX_MTU'] in video_rtp_param_objs: opts['v_max_mtu'] = video_rtp_param_objs.get(RTP_PARAM_TYPES['MAX_MTU']) if audio_tlv: audio_objs = tlv.decode(audio_tlv) opts['a_codec'] = audio_objs[AUDIO_TYPES['CODEC']] audio_codec_param_objs = tlv.decode( audio_objs[AUDIO_TYPES['CODEC_PARAM']]) audio_rtp_param_objs = tlv.decode( audio_objs[AUDIO_TYPES['RTP_PARAM']]) opts['a_comfort_noise'] = audio_objs[AUDIO_TYPES['COMFORT_NOISE']] opts['a_channel'] = \ audio_codec_param_objs[AUDIO_CODEC_PARAM_TYPES['CHANNEL']][0] opts['a_bitrate'] = struct.unpack('?', audio_codec_param_objs[AUDIO_CODEC_PARAM_TYPES['BIT_RATE']])[0] opts['a_sample_rate'] = 8 * ( 1 + audio_codec_param_objs[AUDIO_CODEC_PARAM_TYPES['SAMPLE_RATE']][0]) opts['a_packet_time'] = struct.unpack('<B', audio_codec_param_objs[AUDIO_CODEC_PARAM_TYPES['PACKET_TIME']])[0] opts['a_ssrc'] = struct.unpack('<I', audio_rtp_param_objs[RTP_PARAM_TYPES['SYNCHRONIZATION_SOURCE']])[0] opts['a_payload_type'] = audio_rtp_param_objs[RTP_PARAM_TYPES['PAYLOAD_TYPE']] opts['a_max_bitrate'] = struct.unpack('<H', audio_rtp_param_objs[RTP_PARAM_TYPES['MAX_BIT_RATE']])[0] opts['a_rtcp_interval'] = struct.unpack('<f', audio_rtp_param_objs[RTP_PARAM_TYPES['RTCP_SEND_INTERVAL']])[0] opts['a_comfort_payload_type'] = \ audio_rtp_param_objs[RTP_PARAM_TYPES['COMFORT_NOISE_PAYLOAD_TYPE']] session_objs = tlv.decode(objs[SELECTED_STREAM_CONFIGURATION_TYPES['SESSION']]) session_id = UUID(bytes=session_objs[SETUP_TYPES['SESSION_ID']]) session_info = self.sessions[session_id] stream_idx = session_info['stream_idx'] opts.update(session_info) success = await self.reconfigure_stream(session_info, opts) if reconfigure \ else await self.start_stream(session_info, opts) if success: self._streaming_status[stream_idx] = STREAMING_STATUS['STREAMING'] else: logger.error( '[%s] Failed to start/reconfigure stream, deleting session.', session_id ) self._streaming_status[stream_idx] = STREAMING_STATUS['AVAILABLE'] del self.sessions[session_id]
def set_endpoints(self, value): """Configure streaming endpoints. Called when iOS sets the SetupEndpoints ``Characteristic``. The endpoint information for the camera should be set as the current value of SetupEndpoints. :param value: The base64-encoded stream session details in TLV format. :param value: ``str`` """ objs = tlv.decode(value, from_base64=True) session_id = UUID(bytes=objs[SETUP_TYPES['SESSION_ID']]) # Extract address info address_tlv = objs[SETUP_TYPES['ADDRESS']] address_info_objs = tlv.decode(address_tlv) is_ipv6 = struct.unpack( '?', address_info_objs[SETUP_ADDR_INFO['ADDRESS_VER']])[0] address = address_info_objs[SETUP_ADDR_INFO['ADDRESS']].decode('utf8') target_video_port = struct.unpack( '<H', address_info_objs[SETUP_ADDR_INFO['VIDEO_RTP_PORT']])[0] target_audio_port = struct.unpack( '<H', address_info_objs[SETUP_ADDR_INFO['AUDIO_RTP_PORT']])[0] # Video SRTP Params video_srtp_tlv = objs[SETUP_TYPES['VIDEO_SRTP_PARAM']] video_info_objs = tlv.decode(video_srtp_tlv) video_crypto_suite = video_info_objs[SETUP_SRTP_PARAM['CRYPTO']][0] video_master_key = video_info_objs[SETUP_SRTP_PARAM['MASTER_KEY']] video_master_salt = video_info_objs[SETUP_SRTP_PARAM['MASTER_SALT']] # Audio SRTP Params audio_srtp_tlv = objs[SETUP_TYPES['AUDIO_SRTP_PARAM']] audio_info_objs = tlv.decode(audio_srtp_tlv) audio_crypto_suite = audio_info_objs[SETUP_SRTP_PARAM['CRYPTO']][0] audio_master_key = audio_info_objs[SETUP_SRTP_PARAM['MASTER_KEY']] audio_master_salt = audio_info_objs[SETUP_SRTP_PARAM['MASTER_SALT']] logger.debug( 'Received endpoint configuration:' '\nsession_id: %s\naddress: %s\nis_ipv6: %s' '\ntarget_video_port: %s\ntarget_audio_port: %s' '\nvideo_crypto_suite: %s\nvideo_srtp: %s' '\naudio_crypto_suite: %s\naudio_srtp: %s', session_id, address, is_ipv6, target_video_port, target_audio_port, video_crypto_suite, to_base64_str(video_master_key + video_master_salt), audio_crypto_suite, to_base64_str(audio_master_key + audio_master_salt)) # Configure the SetupEndpoints response if self.has_srtp: video_srtp_tlv = tlv.encode( SETUP_SRTP_PARAM['CRYPTO'], SRTP_CRYPTO_SUITES['AES_CM_128_HMAC_SHA1_80'], SETUP_SRTP_PARAM['MASTER_KEY'], video_master_key, SETUP_SRTP_PARAM['MASTER_SALT'], video_master_salt) audio_srtp_tlv = tlv.encode( SETUP_SRTP_PARAM['CRYPTO'], SRTP_CRYPTO_SUITES['AES_CM_128_HMAC_SHA1_80'], SETUP_SRTP_PARAM['MASTER_KEY'], audio_master_key, SETUP_SRTP_PARAM['MASTER_SALT'], audio_master_salt) else: video_srtp_tlv = NO_SRTP audio_srtp_tlv = NO_SRTP video_ssrc = int.from_bytes(os.urandom(3), byteorder="big") audio_ssrc = int.from_bytes(os.urandom(3), byteorder="big") res_address_tlv = tlv.encode(SETUP_ADDR_INFO['ADDRESS_VER'], self.stream_address_isv6, SETUP_ADDR_INFO['ADDRESS'], self.stream_address.encode('utf-8'), SETUP_ADDR_INFO['VIDEO_RTP_PORT'], struct.pack('<H', target_video_port), SETUP_ADDR_INFO['AUDIO_RTP_PORT'], struct.pack('<H', target_audio_port)) response_tlv = tlv.encode(SETUP_TYPES['SESSION_ID'], session_id.bytes, SETUP_TYPES['STATUS'], SETUP_STATUS['SUCCESS'], SETUP_TYPES['ADDRESS'], res_address_tlv, SETUP_TYPES['VIDEO_SRTP_PARAM'], video_srtp_tlv, SETUP_TYPES['AUDIO_SRTP_PARAM'], audio_srtp_tlv, SETUP_TYPES['VIDEO_SSRC'], struct.pack('<I', video_ssrc), SETUP_TYPES['AUDIO_SSRC'], struct.pack('<I', audio_ssrc), to_base64=True) self.sessions[session_id] = { 'id': session_id, 'address': address, 'v_port': target_video_port, 'v_srtp_key': to_base64_str(video_master_key + video_master_salt), 'v_ssrc': video_ssrc, 'a_port': target_audio_port, 'a_srtp_key': to_base64_str(audio_master_key + audio_master_salt), 'a_ssrc': audio_ssrc } self.get_service('CameraRTPStreamManagement')\ .get_characteristic('SetupEndpoints')\ .set_value(response_tlv)