Beispiel #1
0
    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
Beispiel #2
0
    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))
Beispiel #3
0
    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))
Beispiel #4
0
    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))
Beispiel #5
0
    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')
Beispiel #6
0
    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)
Beispiel #7
0
    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,
    }
Beispiel #9
0
    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
Beispiel #10
0
    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)
Beispiel #11
0
   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
Beispiel #12
0
    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]
Beispiel #13
0
    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)