def test_rtcp_any_ssrc(self): # protect RCTP tx_session = Session(policy=Policy( key=KEY, ssrc_type=Policy.SSRC_ANY_OUTBOUND)) protected = tx_session.protect_rtcp(RTCP) self.assertEqual(len(protected), 42) # bad type with self.assertRaises(TypeError) as cm: tx_session.protect_rtcp(4567) self.assertEqual(str(cm.exception), 'packet must be bytes') # bad length with self.assertRaises(ValueError) as cm: tx_session.protect_rtcp(b'0' * 1500) self.assertEqual(str(cm.exception), 'packet is too long') # unprotect RTCP rx_session = Session(policy=Policy( key=KEY, ssrc_type=Policy.SSRC_ANY_INBOUND)) unprotected = rx_session.unprotect_rtcp(protected) self.assertEqual(len(unprotected), 28) self.assertEqual(unprotected, RTCP)
class RTCDtlsTransport(EventEmitter): """ The :class:`RTCDtlsTransport` object includes information relating to Datagram Transport Layer Security (DTLS) transport. :param: transport: An :class:`RTCIceTransport`. :param: certificates: A list of :class:`RTCCertificate` (only one is allowed currently). """ def __init__(self, transport, certificates): assert len(certificates) == 1 certificate = certificates[0] super().__init__() self.closed = asyncio.Event() self.encrypted = False self._role = 'auto' self._rtp_mid_header_id = None self._rtp_router = RtpRouter() self._start = None self._state = State.NEW self._transport = transport self.data_queue = asyncio.Queue() self.data = Channel(closed=self.closed, queue=self.data_queue) # SSL init self.__ctx = create_ssl_context(certificate) ssl = lib.SSL_new(self.__ctx) self.ssl = ffi.gc(ssl, lib.SSL_free) self.read_bio = lib.BIO_new(lib.BIO_s_mem()) self.read_cdata = ffi.new('char[]', 1500) self.write_bio = lib.BIO_new(lib.BIO_s_mem()) self.write_cdata = ffi.new('char[]', 1500) lib.SSL_set_bio(self.ssl, self.read_bio, self.write_bio) self.__local_parameters = RTCDtlsParameters( fingerprints=certificate.getFingerprints()) @property def state(self): """ The current state of the DTLS transport. """ return str(self._state)[6:].lower() @property def transport(self): """ The associated :class:`RTCIceTransport` instance. """ return self._transport def getLocalParameters(self): """ Get the local parameters of the DTLS transport. :rtype: :class:`RTCDtlsParameters` """ return self.__local_parameters async def start(self, remoteParameters): """ Start DTLS transport negotiation with the parameters of the remote DTLS transport. :param: remoteParameters: An :class:`RTCDtlsParameters`. """ assert self._state not in [State.CLOSED, State.FAILED] assert len(remoteParameters.fingerprints) # handle the case where start is already in progress if self._start is not None: return await self._start.wait() self._start = asyncio.Event() if self.transport.role == 'controlling': self._role = 'server' lib.SSL_set_accept_state(self.ssl) else: self._role = 'client' lib.SSL_set_connect_state(self.ssl) self._set_state(State.CONNECTING) while not self.encrypted: result = lib.SSL_do_handshake(self.ssl) await self._write_ssl() if result > 0: self.encrypted = True break error = lib.SSL_get_error(self.ssl, result) if error == lib.SSL_ERROR_WANT_READ: await self._recv_next() else: self._set_state(State.FAILED) raise DtlsError('DTLS handshake failed (error %d)' % error) # check remote fingerprint x509 = lib.SSL_get_peer_certificate(self.ssl) remote_fingerprint = certificate_digest(x509) fingerprint_is_valid = False for f in remoteParameters.fingerprints: if f.algorithm == 'sha-256' and f.value.lower( ) == remote_fingerprint.lower(): fingerprint_is_valid = True break if not fingerprint_is_valid: self._set_state(State.FAILED) raise DtlsError('DTLS fingerprint does not match') # generate keying material buf = ffi.new('unsigned char[]', 2 * (SRTP_KEY_LEN + SRTP_SALT_LEN)) extractor = b'EXTRACTOR-dtls_srtp' _openssl_assert( lib.SSL_export_keying_material(self.ssl, buf, len( buf), extractor, len(extractor), ffi.NULL, 0, 0) == 1) view = ffi.buffer(buf) if self._role == 'server': srtp_tx_key = get_srtp_key_salt(view, 1) srtp_rx_key = get_srtp_key_salt(view, 0) else: srtp_tx_key = get_srtp_key_salt(view, 0) srtp_rx_key = get_srtp_key_salt(view, 1) rx_policy = Policy(key=srtp_rx_key, ssrc_type=Policy.SSRC_ANY_INBOUND) rx_policy.allow_repeat_tx = True rx_policy.window_size = 1024 self._rx_srtp = Session(rx_policy) tx_policy = Policy(key=srtp_tx_key, ssrc_type=Policy.SSRC_ANY_OUTBOUND) tx_policy.allow_repeat_tx = True tx_policy.window_size = 1024 self._tx_srtp = Session(tx_policy) # start data pump self.__log_debug('- DTLS handshake complete') self._set_state(State.CONNECTED) asyncio.ensure_future(self.__run()) self._start.set() async def stop(self): """ Stop and close the DTLS transport. """ if self._state in [State.CONNECTING, State.CONNECTED]: lib.SSL_shutdown(self.ssl) try: await self._write_ssl() except ConnectionError: pass self.__log_debug('- DTLS shutdown complete') self.closed.set() async def __run(self): try: while True: await self._recv_next() except ConnectionError: pass finally: self._set_state(State.CLOSED) self.closed.set() async def _handle_rtcp_data(self, data): packets = RtcpPacket.parse(data) for packet in packets: receiver = None if hasattr(packet, 'ssrc'): # SR and RR receiver = self._rtp_router.route(packet.ssrc) elif getattr(packet, 'chunks', None): # SDES receiver = self._rtp_router.route(packet.chunks[0].ssrc) elif getattr(packet, 'sources', None): # BYE receiver = self._rtp_router.route(packet.sources[0]) if receiver is not None: await receiver._handle_rtcp_packet(packet) async def _handle_rtp_data(self, data): packet = RtpPacket.parse(data) # get muxId from RTP header extensions mid = None for x_id, x_value in get_header_extensions(packet): if x_id == self._rtp_mid_header_id: mid = x_value.decode('utf8') break # route RTP packet receiver = self._rtp_router.route(packet.ssrc, mid=mid) if receiver is not None: await receiver._handle_rtp_packet(packet) async def _recv_next(self): # get timeout ptv_sec = ffi.new('time_t *') ptv_usec = ffi.new('long *') if lib.Cryptography_DTLSv1_get_timeout(self.ssl, ptv_sec, ptv_usec): timeout = ptv_sec[0] + (ptv_usec[0] / 1000000) else: timeout = None try: data = await first_completed(self.transport._connection.recv(), self.closed.wait(), timeout=timeout) except TimeoutError: self.__log_debug('x DTLS handling timeout') lib.DTLSv1_handle_timeout(self.ssl) await self._write_ssl() return if data is True: # session was closed raise ConnectionError first_byte = data[0] if first_byte > 19 and first_byte < 64: # DTLS lib.BIO_write(self.read_bio, data, len(data)) result = lib.SSL_read(self.ssl, self.read_cdata, len(self.read_cdata)) await self._write_ssl() if result == 0: self.__log_debug('- DTLS shutdown by remote party') raise ConnectionError elif result > 0: await self.data_queue.put( ffi.buffer(self.read_cdata)[0:result]) elif first_byte > 127 and first_byte < 192: # SRTP / SRTCP try: if is_rtcp(data): data = self._rx_srtp.unprotect_rtcp(data) await self._handle_rtcp_data(data) else: data = self._rx_srtp.unprotect(data) await self._handle_rtp_data(data) except pylibsrtp.Error as exc: self.__log_debug('x SRTP unprotect failed: %s', exc) def _register_rtp_receiver(self, receiver, parameters): # make note of the RTP header extension used for muxId for ext in parameters.headerExtensions: if ext.uri == 'urn:ietf:params:rtp-hdrext:sdes:mid': self._rtp_mid_header_id = ext.id self._rtp_router.register(receiver, parameters) async def _send_data(self, data): if self._state != State.CONNECTED: raise ConnectionError('Cannot send encrypted data, not connected') lib.SSL_write(self.ssl, data, len(data)) await self._write_ssl() async def _send_rtp(self, data): if self._state != State.CONNECTED: raise ConnectionError('Cannot send encrypted RTP, not connected') if is_rtcp(data): data = self._tx_srtp.protect_rtcp(data) else: data = self._tx_srtp.protect(data) await self.transport._connection.send(data) def _set_state(self, state): if state != self._state: self.__log_debug('- %s -> %s', self._state, state) self._state = state self.emit('statechange') async def _write_ssl(self): """ Flush outgoing data which OpenSSL put in our BIO to the transport. """ pending = lib.BIO_ctrl_pending(self.write_bio) if pending > 0: result = lib.BIO_read(self.write_bio, self.write_cdata, len(self.write_cdata)) await self.transport._connection.send( ffi.buffer(self.write_cdata)[0:result]) def __log_debug(self, msg, *args): logger.debug(self._role + ' ' + msg, *args)
class RTCDtlsTransport(EventEmitter): """ The :class:`RTCDtlsTransport` object includes information relating to Datagram Transport Layer Security (DTLS) transport. :param: transport: An :class:`RTCIceTransport`. :param: certificates: A list of :class:`RTCCertificate` (only one is allowed currently). """ def __init__(self, transport, certificates): assert len(certificates) == 1 certificate = certificates[0] super().__init__() self.encrypted = False self._data_receiver = None self._role = 'auto' self._rtp_header_extensions_map = rtp.HeaderExtensionsMap() self._rtp_router = RtpRouter() self._state = State.NEW self._stats_id = 'transport_' + str(id(self)) self._task = None self._transport = transport # counters self.__rx_bytes = 0 self.__rx_packets = 0 self.__tx_bytes = 0 self.__tx_packets = 0 # SRTP self._rx_srtp = None self._tx_srtp = None # SSL init self.__ctx = create_ssl_context(certificate) ssl = lib.SSL_new(self.__ctx) self.ssl = ffi.gc(ssl, lib.SSL_free) self.read_bio = lib.BIO_new(lib.BIO_s_mem()) self.read_cdata = ffi.new('char[]', 1500) self.write_bio = lib.BIO_new(lib.BIO_s_mem()) self.write_cdata = ffi.new('char[]', 1500) lib.SSL_set_bio(self.ssl, self.read_bio, self.write_bio) self.__local_certificate = certificate @property def state(self): """ The current state of the DTLS transport. One of `'new'`, `'connecting'`, `'connected'`, `'closed'` or `'failed'`. """ return str(self._state)[6:].lower() @property def transport(self): """ The associated :class:`RTCIceTransport` instance. """ return self._transport def getLocalParameters(self): """ Get the local parameters of the DTLS transport. :rtype: :class:`RTCDtlsParameters` """ return RTCDtlsParameters( fingerprints=self.__local_certificate.getFingerprints()) async def start(self, remoteParameters): """ Start DTLS transport negotiation with the parameters of the remote DTLS transport. :param: remoteParameters: An :class:`RTCDtlsParameters`. """ assert self._state == State.NEW assert len(remoteParameters.fingerprints) if self.transport.role == 'controlling': self._role = 'server' lib.SSL_set_accept_state(self.ssl) else: self._role = 'client' lib.SSL_set_connect_state(self.ssl) self._set_state(State.CONNECTING) try: while not self.encrypted: result = lib.SSL_do_handshake(self.ssl) await self._write_ssl() if result > 0: self.encrypted = True break error = lib.SSL_get_error(self.ssl, result) if error == lib.SSL_ERROR_WANT_READ: await self._recv_next() else: self.__log_debug('x DTLS handshake failed (error %d)', error) for info in get_error_queue(): self.__log_debug('x %s', ':'.join(info)) self._set_state(State.FAILED) return except ConnectionError: self.__log_debug('x DTLS handshake failed (connection error)') self._set_state(State.FAILED) return # check remote fingerprint x509 = lib.SSL_get_peer_certificate(self.ssl) remote_fingerprint = certificate_digest(x509) fingerprint_is_valid = False for f in remoteParameters.fingerprints: if f.algorithm.lower() == 'sha-256' and f.value.lower( ) == remote_fingerprint.lower(): fingerprint_is_valid = True break if not fingerprint_is_valid: self.__log_debug('x DTLS handshake failed (fingerprint mismatch)') self._set_state(State.FAILED) return # generate keying material buf = ffi.new('unsigned char[]', 2 * (SRTP_KEY_LEN + SRTP_SALT_LEN)) extractor = b'EXTRACTOR-dtls_srtp' _openssl_assert( lib.SSL_export_keying_material(self.ssl, buf, len( buf), extractor, len(extractor), ffi.NULL, 0, 0) == 1) view = ffi.buffer(buf) if self._role == 'server': srtp_tx_key = get_srtp_key_salt(view, 1) srtp_rx_key = get_srtp_key_salt(view, 0) else: srtp_tx_key = get_srtp_key_salt(view, 0) srtp_rx_key = get_srtp_key_salt(view, 1) rx_policy = Policy(key=srtp_rx_key, ssrc_type=Policy.SSRC_ANY_INBOUND) rx_policy.allow_repeat_tx = True rx_policy.window_size = 1024 self._rx_srtp = Session(rx_policy) tx_policy = Policy(key=srtp_tx_key, ssrc_type=Policy.SSRC_ANY_OUTBOUND) tx_policy.allow_repeat_tx = True tx_policy.window_size = 1024 self._tx_srtp = Session(tx_policy) # start data pump self.__log_debug('- DTLS handshake complete') self._set_state(State.CONNECTED) self._task = asyncio.ensure_future(self.__run()) async def stop(self): """ Stop and close the DTLS transport. """ if self._task is not None: self._task.cancel() self._task = None if self._state in [State.CONNECTING, State.CONNECTED]: lib.SSL_shutdown(self.ssl) try: await self._write_ssl() except ConnectionError: pass self.__log_debug('- DTLS shutdown complete') async def __run(self): try: while True: await self._recv_next() except ConnectionError: for receiver in self._rtp_router.receivers: receiver._handle_disconnect() finally: self._set_state(State.CLOSED) def _get_stats(self): report = RTCStatsReport() report.add( RTCTransportStats( # RTCStats timestamp=clock.current_datetime(), type='transport', id=self._stats_id, # RTCTransportStats, packetsSent=self.__tx_packets, packetsReceived=self.__rx_packets, bytesSent=self.__tx_bytes, bytesReceived=self.__rx_bytes, iceRole=self.transport.role, dtlsState=self.state, )) return report async def _handle_rtcp_data(self, data): try: packets = RtcpPacket.parse(data) except ValueError as exc: self.__log_debug('x RTCP parsing failed: %s', exc) return for packet in packets: # route RTCP packet for recipient in self._rtp_router.route_rtcp(packet): await recipient._handle_rtcp_packet(packet) async def _handle_rtp_data(self, data, arrival_time_ms): try: packet = RtpPacket.parse(data, self._rtp_header_extensions_map) except ValueError as exc: self.__log_debug('x RTP parsing failed: %s', exc) return # route RTP packet receiver = self._rtp_router.route_rtp(packet) if receiver is not None: await receiver._handle_rtp_packet(packet, arrival_time_ms=arrival_time_ms) async def _recv_next(self): # get timeout timeout = None if not self.encrypted: ptv_sec = ffi.new('time_t *') ptv_usec = ffi.new('long *') if lib.Cryptography_DTLSv1_get_timeout(self.ssl, ptv_sec, ptv_usec): timeout = ptv_sec[0] + (ptv_usec[0] / 1000000) # receive next datagram if timeout is not None: try: data = await asyncio.wait_for(self.transport._recv(), timeout=timeout) except asyncio.TimeoutError: self.__log_debug('x DTLS handling timeout') lib.DTLSv1_handle_timeout(self.ssl) await self._write_ssl() return else: data = await self.transport._recv() self.__rx_bytes += len(data) self.__rx_packets += 1 first_byte = data[0] if first_byte > 19 and first_byte < 64: # DTLS lib.BIO_write(self.read_bio, data, len(data)) result = lib.SSL_read(self.ssl, self.read_cdata, len(self.read_cdata)) await self._write_ssl() if result == 0: self.__log_debug('- DTLS shutdown by remote party') raise ConnectionError elif result > 0 and self._data_receiver: data = ffi.buffer(self.read_cdata)[0:result] await self._data_receiver._handle_data(data) elif first_byte > 127 and first_byte < 192 and self._rx_srtp: # SRTP / SRTCP arrival_time_ms = clock.current_ms() try: if is_rtcp(data): data = self._rx_srtp.unprotect_rtcp(data) await self._handle_rtcp_data(data) else: data = self._rx_srtp.unprotect(data) await self._handle_rtp_data( data, arrival_time_ms=arrival_time_ms) except pylibsrtp.Error as exc: self.__log_debug('x SRTP unprotect failed: %s', exc) def _register_data_receiver(self, receiver): assert self._data_receiver is None self._data_receiver = receiver def _register_rtp_receiver(self, receiver, parameters: RTCRtpReceiveParameters): ssrcs = set() for encoding in parameters.encodings: ssrcs.add(encoding.ssrc) self._rtp_header_extensions_map.configure(parameters) self._rtp_router.register_receiver( receiver, ssrcs=list(ssrcs), payload_types=[codec.payloadType for codec in parameters.codecs], mid=parameters.muxId) def _register_rtp_sender(self, sender, parameters: RTCRtpSendParameters): self._rtp_header_extensions_map.configure(parameters) self._rtp_router.register_sender(sender, ssrc=sender._ssrc) async def _send_data(self, data): if self._state != State.CONNECTED: raise ConnectionError('Cannot send encrypted data, not connected') lib.SSL_write(self.ssl, data, len(data)) await self._write_ssl() async def _send_rtp(self, data): if self._state != State.CONNECTED: raise ConnectionError('Cannot send encrypted RTP, not connected') if is_rtcp(data): data = self._tx_srtp.protect_rtcp(data) else: data = self._tx_srtp.protect(data) await self.transport._send(data) self.__tx_bytes += len(data) self.__tx_packets += 1 def _set_state(self, state): if state != self._state: self.__log_debug('- %s -> %s', self._state, state) self._state = state self.emit('statechange') def _unregister_data_receiver(self, receiver): if self._data_receiver == receiver: self._data_receiver = None def _unregister_rtp_receiver(self, receiver): self._rtp_router.unregister_receiver(receiver) def _unregister_rtp_sender(self, sender): self._rtp_router.unregister_sender(sender) async def _write_ssl(self): """ Flush outgoing data which OpenSSL put in our BIO to the transport. """ pending = lib.BIO_ctrl_pending(self.write_bio) if pending > 0: result = lib.BIO_read(self.write_bio, self.write_cdata, len(self.write_cdata)) await self.transport._send(ffi.buffer(self.write_cdata)[0:result]) self.__tx_bytes += result self.__tx_packets += 1 def __log_debug(self, msg, *args): logger.debug(self._role + ' ' + msg, *args)
class DtlsSrtpSession: def __init__(self, context, is_server, transport): self.encrypted = False self.is_server = is_server self.remote_fingerprint = None self.transport = transport self.data_queue = asyncio.Queue() self.data = Channel(recv=self.data_queue.get, send=self._send_data) self.rtp_queue = asyncio.Queue() self.rtp = Channel(recv=self.rtp_queue.get, send=self._send_rtp) ssl = lib.SSL_new(context.ctx) self.ssl = ffi.gc(ssl, lib.SSL_free) self.read_bio = lib.BIO_new(lib.BIO_s_mem()) self.write_bio = lib.BIO_new(lib.BIO_s_mem()) lib.SSL_set_bio(self.ssl, self.read_bio, self.write_bio) if self.is_server: lib.SSL_set_accept_state(self.ssl) else: lib.SSL_set_connect_state(self.ssl) async def connect(self): while not self.encrypted: result = lib.SSL_do_handshake(self.ssl) if result > 0: self.encrypted = True break error = lib.SSL_get_error(self.ssl, result) await self._write_ssl() if error == lib.SSL_ERROR_WANT_READ: data = await self.transport.recv() lib.BIO_write(self.read_bio, data, len(data)) else: raise Exception('DTLS handshake failed (error %d)' % error) await self._write_ssl() # check remote fingerprint x509 = lib.SSL_get_peer_certificate(self.ssl) remote_fingerprint = certificate_digest(x509) if remote_fingerprint != self.remote_fingerprint.upper(): raise Exception('DTLS fingerprint does not match') # generate keying material buf = ffi.new("char[]", 2 * (SRTP_KEY_LEN + SRTP_SALT_LEN)) extractor = b'EXTRACTOR-dtls_srtp' if not lib.SSL_export_keying_material(self.ssl, buf, len(buf), extractor, len(extractor), ffi.NULL, 0, 0): raise Exception('DTLS could not extract SRTP keying material') view = ffi.buffer(buf) if self.is_server: srtp_tx_key = get_srtp_key_salt(view, 1) srtp_rx_key = get_srtp_key_salt(view, 0) else: srtp_tx_key = get_srtp_key_salt(view, 0) srtp_rx_key = get_srtp_key_salt(view, 1) logger.info('DTLS handshake complete') rx_policy = Policy(key=srtp_rx_key, ssrc_type=Policy.SSRC_ANY_INBOUND) self._rx_srtp = Session(rx_policy) tx_policy = Policy(key=srtp_tx_key, ssrc_type=Policy.SSRC_ANY_OUTBOUND) self._tx_srtp = Session(tx_policy) async def run(self): while True: data = await self.transport.recv() first_byte = data[0] if first_byte > 19 and first_byte < 64: # DTLS lib.BIO_write(self.read_bio, data, len(data)) buf = ffi.new("char[]", 1500) result = lib.SSL_read(self.ssl, buf, len(buf)) await self.data_queue.put(ffi.buffer(buf)[0:result]) elif first_byte > 127 and first_byte < 192: # SRTP / SRTCP if is_rtcp(data): data = self._rx_srtp.unprotect_rtcp(data) else: data = self._rx_srtp.unprotect(data) await self.rtp_queue.put(data) async def _send_data(self, data): lib.SSL_write(self.ssl, data, len(data)) await self._write_ssl() async def _send_rtp(self, data): if is_rtcp(data): data = self._tx_srtp.protect_rtcp(data) else: data = self._tx_srtp.protect(data) await self.transport.send(data) async def _write_ssl(self): pending = lib.BIO_ctrl_pending(self.write_bio) if pending > 0: buf = ffi.new("char[]", pending) lib.BIO_read(self.write_bio, buf, len(buf)) data = b''.join(buf) await self.transport.send(data)
class DtlsSrtpSession: def __init__(self, context, is_server, transport): self.closed = asyncio.Event() self.encrypted = False self.is_server = is_server self.remote_fingerprint = None self.role = self.is_server and 'server' or 'client' self.state = self.State.CLOSED self.transport = transport self.data_queue = asyncio.Queue() self.data = Channel(closed=self.closed, queue=self.data_queue, send=self._send_data) self.rtp_queue = asyncio.Queue() self.rtp = Channel(closed=self.closed, queue=self.rtp_queue, send=self._send_rtp) ssl = lib.SSL_new(context.ctx) self.ssl = ffi.gc(ssl, lib.SSL_free) self.read_bio = lib.BIO_new(lib.BIO_s_mem()) self.read_cdata = ffi.new('char[]', 1500) self.write_bio = lib.BIO_new(lib.BIO_s_mem()) self.write_cdata = ffi.new('char[]', 1500) lib.SSL_set_bio(self.ssl, self.read_bio, self.write_bio) if self.is_server: lib.SSL_set_accept_state(self.ssl) else: lib.SSL_set_connect_state(self.ssl) # local fingerprint x509 = lib.SSL_get_certificate(self.ssl) self.local_fingerprint = certificate_digest(x509) async def close(self): if self.state != self.State.CLOSED: lib.SSL_shutdown(self.ssl) await self._write_ssl() logger.debug('%s - DTLS shutdown complete', self.role) self.closed.set() async def connect(self): assert self.state == self.State.CLOSED self._set_state(self.State.CONNECTING) while not self.encrypted: result = lib.SSL_do_handshake(self.ssl) await self._write_ssl() if result > 0: self.encrypted = True break error = lib.SSL_get_error(self.ssl, result) if error == lib.SSL_ERROR_WANT_READ: await self._recv_next() else: raise DtlsError('DTLS handshake failed (error %d)' % error) # check remote fingerprint x509 = lib.SSL_get_peer_certificate(self.ssl) remote_fingerprint = certificate_digest(x509) if remote_fingerprint != self.remote_fingerprint.upper(): raise DtlsError('DTLS fingerprint does not match') # generate keying material buf = ffi.new('unsigned char[]', 2 * (SRTP_KEY_LEN + SRTP_SALT_LEN)) extractor = b'EXTRACTOR-dtls_srtp' _openssl_assert( lib.SSL_export_keying_material(self.ssl, buf, len( buf), extractor, len(extractor), ffi.NULL, 0, 0) == 1) view = ffi.buffer(buf) if self.is_server: srtp_tx_key = get_srtp_key_salt(view, 1) srtp_rx_key = get_srtp_key_salt(view, 0) else: srtp_tx_key = get_srtp_key_salt(view, 0) srtp_rx_key = get_srtp_key_salt(view, 1) rx_policy = Policy(key=srtp_rx_key, ssrc_type=Policy.SSRC_ANY_INBOUND) self._rx_srtp = Session(rx_policy) tx_policy = Policy(key=srtp_tx_key, ssrc_type=Policy.SSRC_ANY_OUTBOUND) self._tx_srtp = Session(tx_policy) # start data pump logger.debug('%s - DTLS handshake complete', self.role) self._set_state(self.State.CONNECTED) asyncio.ensure_future(self.__run()) async def __run(self): try: while True: await self._recv_next() except ConnectionError: pass finally: self._set_state(self.State.CLOSED) self.closed.set() async def _recv_next(self): data = await first_completed(self.transport.recv(), self.closed.wait()) if data is True: # session was closed raise ConnectionError first_byte = data[0] if first_byte > 19 and first_byte < 64: # DTLS lib.BIO_write(self.read_bio, data, len(data)) result = lib.SSL_read(self.ssl, self.read_cdata, len(self.read_cdata)) if result == 0: logger.debug('%s - DTLS shutdown by remote party' % self.role) raise ConnectionError elif result > 0: await self.data_queue.put( ffi.buffer(self.read_cdata)[0:result]) elif first_byte > 127 and first_byte < 192: # SRTP / SRTCP if is_rtcp(data): data = self._rx_srtp.unprotect_rtcp(data) else: data = self._rx_srtp.unprotect(data) await self.rtp_queue.put(data) async def _send_data(self, data): if self.state != self.State.CONNECTED: raise ConnectionError('Cannot send encrypted data, not connected') lib.SSL_write(self.ssl, data, len(data)) await self._write_ssl() async def _send_rtp(self, data): if self.state != self.State.CONNECTED: raise ConnectionError('Cannot send encrypted RTP, not connected') if is_rtcp(data): data = self._tx_srtp.protect_rtcp(data) else: data = self._tx_srtp.protect(data) await self.transport.send(data) def _set_state(self, state): if state != self.state: logger.debug('%s - %s -> %s', self.role, self.state, state) self.state = state async def _write_ssl(self): """ Flush outgoing data which OpenSSL put in our BIO to the transport. """ pending = lib.BIO_ctrl_pending(self.write_bio) if pending > 0: result = lib.BIO_read(self.write_bio, self.write_cdata, len(self.write_cdata)) await self.transport.send(ffi.buffer(self.write_cdata)[0:result]) class State(enum.Enum): CLOSED = 0 CONNECTING = 1 CONNECTED = 2